Add an argument parser

This commit is contained in:
Eugen Wissner 2022-06-05 23:43:45 +02:00
parent 5490f6ce1c
commit aa6b2504bd
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
2 changed files with 172 additions and 17 deletions

View File

@ -4,8 +4,12 @@ require 'open3'
DFLAGS = ['--warn-no-deprecated', '-L/usr/lib64/gcc-12'] DFLAGS = ['--warn-no-deprecated', '-L/usr/lib64/gcc-12']
BINARY = 'build/bin/elna' BINARY = 'build/bin/elna'
TESTS = FileList['tests/*.elna'] TESTS = FileList['tests/*.elna'].flat_map do |test|
.map { |test| (Pathname.new('build') + test).sub_ext('').to_path } 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'] SOURCES = FileList['source/**/*.d']
directory 'build' directory 'build'
@ -13,22 +17,24 @@ directory 'build'
CLEAN.include 'build' CLEAN.include 'build'
CLEAN.include '.dub' 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 Pathname.new(t.name).dirname.mkpath
sh BINARY, t.source sh BINARY, t.source
sh 'gcc', '-o', t.name, "#{t.name}.o"
# Open3.pipeline [BINARY, t.source], ['gcc', '-x', 'assembler', '-o', t.name, '-']
end end
file BINARY => SOURCES do |t| file BINARY => SOURCES do |t|
sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc-12') sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc-12')
end end
file 'build/tests/sample' => BINARY do |t|
sh t.source
sh 'gcc', '-o', t.name, 'build/tests/sample.o'
end
task default: BINARY task default: BINARY
desc 'Run all tests and check the results' desc 'Run all tests and check the results'
@ -38,7 +44,7 @@ task test: BINARY do
expected = Pathname expected = Pathname
.new(test) .new(test)
.sub_ext('.txt') .sub_ext('.txt')
.sub(/^build\/tests\//, 'tests/expectations/') .sub(/^build\/[[:alpha:]]+\//, 'tests/expectations/')
.read .read
.to_i .to_i
@ -48,10 +54,6 @@ task test: BINARY do
fail "#{test}: Expected #{expected}, got #{actual}" unless expected == actual fail "#{test}: Expected #{expected}, got #{actual}" unless expected == actual
end end
# system './build/tests/sample'
# actual = $?.exitstatus
# fail "./build/tests/sample: Expected 3, got #{actual}" unless 3 == actual
end end
desc 'Run unittest blocks' desc 'Run unittest blocks'
@ -59,11 +61,18 @@ task unittest: SOURCES do |t|
sh('dub', 'test', '--compiler=gdc-12') sh('dub', 'test', '--compiler=gdc-12')
end end
def test_for_out(out_file) def test_for_object(out_file)
test_source = Pathname test_source = Pathname
.new(out_file) .new(out_file)
.sub_ext('.elna') .sub_ext('.elna')
.sub(/^build\//, '') .sub(/^build\/[[:alpha:]]+\//, 'tests/')
.to_path .to_path
[test_source, BINARY] [test_source, BINARY]
end end
def test_for_out(out_file)
Pathname
.new(out_file)
.sub_ext('.o')
.to_path
end

146
source/elna/arguments.d Normal file
View File

@ -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);
}
}