From aa6b2504bdb778b6c6a4989258c2a38f7e9d547b Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sun, 5 Jun 2022 23:43:45 +0200 Subject: [PATCH] Add an argument parser --- Rakefile | 43 +++++++----- source/elna/arguments.d | 146 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 172 insertions(+), 17 deletions(-) create mode 100644 source/elna/arguments.d diff --git a/Rakefile b/Rakefile index acafe3b..1ba8f77 100644 --- a/Rakefile +++ b/Rakefile @@ -4,8 +4,12 @@ require 'open3' DFLAGS = ['--warn-no-deprecated', '-L/usr/lib64/gcc-12'] BINARY = 'build/bin/elna' -TESTS = FileList['tests/*.elna'] - .map { |test| (Pathname.new('build') + test).sub_ext('').to_path } +TESTS = FileList['tests/*.elna'].flat_map do |test| + build = Pathname.new 'build' + asm_test = build + 'asm' + Pathname.new(test).basename('') + + [build + test, asm_test].map { |path| path.sub_ext('').to_path } +end SOURCES = FileList['source/**/*.d'] directory 'build' @@ -13,22 +17,24 @@ directory 'build' CLEAN.include 'build' CLEAN.include '.dub' -rule(/build\/tests\/.+/ => ->(file) { test_for_out(file) }) do |t| +rule(/build\/tests\/[^\/\.]+$/ => ->(file) { test_for_out(file) }) do |t| + sh 'gcc', '-o', t.name, "#{t.name}.o" +end + +rule(/build\/asm\/[^\/\.]+$/ => ->(file) { test_for_object(file) }) do |t| + Pathname.new(t.name).dirname.mkpath + Open3.pipeline [BINARY, t.source], ['gcc', '-x', 'assembler', '-o', t.name, '-'] +end + +rule(/build\/tests\/.+\.o$/ => ->(file) { test_for_object(file) }) do |t| Pathname.new(t.name).dirname.mkpath sh BINARY, t.source - sh 'gcc', '-o', t.name, "#{t.name}.o" - # Open3.pipeline [BINARY, t.source], ['gcc', '-x', 'assembler', '-o', t.name, '-'] end file BINARY => SOURCES do |t| sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc-12') end -file 'build/tests/sample' => BINARY do |t| - sh t.source - sh 'gcc', '-o', t.name, 'build/tests/sample.o' -end - task default: BINARY desc 'Run all tests and check the results' @@ -38,7 +44,7 @@ task test: BINARY do expected = Pathname .new(test) .sub_ext('.txt') - .sub(/^build\/tests\//, 'tests/expectations/') + .sub(/^build\/[[:alpha:]]+\//, 'tests/expectations/') .read .to_i @@ -48,10 +54,6 @@ task test: BINARY do fail "#{test}: Expected #{expected}, got #{actual}" unless expected == actual end - - # system './build/tests/sample' - # actual = $?.exitstatus - # fail "./build/tests/sample: Expected 3, got #{actual}" unless 3 == actual end desc 'Run unittest blocks' @@ -59,11 +61,18 @@ task unittest: SOURCES do |t| sh('dub', 'test', '--compiler=gdc-12') end -def test_for_out(out_file) +def test_for_object(out_file) test_source = Pathname .new(out_file) .sub_ext('.elna') - .sub(/^build\//, '') + .sub(/^build\/[[:alpha:]]+\//, 'tests/') .to_path [test_source, BINARY] end + +def test_for_out(out_file) + Pathname + .new(out_file) + .sub_ext('.o') + .to_path +end diff --git a/source/elna/arguments.d b/source/elna/arguments.d new file mode 100644 index 0000000..5e14dd8 --- /dev/null +++ b/source/elna/arguments.d @@ -0,0 +1,146 @@ +/** + * Argument parsing. + */ +module elna.arguments; + +import std.algorithm; +import std.range; +import std.sumtype; + +struct ArgumentError +{ + enum Type + { + expectedOutputFile, + noInput, + } + + private Type type_; + private string argument_; + + @property Type type() const @nogc nothrow pure @safe + { + return this.type_; + } + + @property string argument() const @nogc nothrow pure @safe + { + return this.argument_; + } + + void toString(OR)(OR range) + if (isOutputRage!OR) + { + final switch (Type) + { + case Type.expectedOutputFile: + put(range, "Expected an output filename after -o"); + break; + case Type.noInput: + put(range, "No input files specified"); + break; + } + } +} + +/** + * Supported compiler arguments. + */ +struct Arguments +{ + private bool assembler_; + private string output_; + private string[] inFiles_; + + @property string[] inFiles() @nogc nothrow pure @safe + { + return this.inFiles_; + } + + /** + * Returns: Whether to generate assembly instead of an object file. + */ + @property bool assembler() const @nogc nothrow pure @safe + { + return this.assembler_; + } + + /** + * Returns: Output file. + */ + @property string output() const @nogc nothrow pure @safe + { + return this.output_; + } + + /** + * Parse command line arguments. + * + * The first argument is expected to be the program name (and it is + * ignored). + * + * Params: + * arguments = Command line arguments. + * + * Returns: Parsed arguments or an error. + */ + static SumType!(ArgumentError, Arguments) parse(string[] arguments) + @nogc nothrow pure @safe + { + if (!arguments.empty) + { + arguments.popFront; + } + alias ReturnType = typeof(return); + + return parseArguments(arguments).match!( + (Arguments parsed) { + if (parsed.inFiles.empty) + { + return ReturnType(ArgumentError(ArgumentError.Type.noInput)); + } + return ReturnType(parsed); + }, + (ArgumentError argumentError) => ReturnType(argumentError) + ); + } + + private static SumType!(ArgumentError, Arguments) parseArguments(ref string[] arguments) + @nogc nothrow pure @safe + { + Arguments parsed; + + while (!arguments.empty) + { + if (arguments.front == "-s") + { + parsed.assembler_ = true; + } + else if (arguments.front == "-o") + { + if (arguments.empty) + { + return typeof(return)(ArgumentError( + ArgumentError.Type.expectedOutputFile, + arguments.front + )); + } + arguments.popFront; + parsed.output_ = arguments.front; + } + else if (arguments.front == "--") + { + arguments.popFront; + parsed.inFiles_ = arguments; + break; + } + else if (!arguments.front.startsWith("-")) + { + parsed.inFiles_ = arguments; + break; + } + arguments.popFront; + } + return typeof(return)(parsed); + } +}