Grow stack automatically
This commit is contained in:
parent
df2494e145
commit
86d579e8d5
5
CMakeLists.txt
Normal file
5
CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.21)
|
||||
project(Elna)
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
add_executable(tester tests/runner.cpp)
|
8
README
8
README
@ -35,3 +35,11 @@ factor = ident | number | "(" expression ")";
|
||||
"!" - Write a line.
|
||||
"?" - Read user input.
|
||||
"odd" - The only function, returns whether a number is odd.
|
||||
|
||||
# Build and test
|
||||
|
||||
```sh
|
||||
cmake -B build
|
||||
make -C build
|
||||
./build/bin/tester
|
||||
```
|
||||
|
33
Rakefile
33
Rakefile
@ -39,40 +39,9 @@ file BINARY => SOURCES do |t|
|
||||
sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc')
|
||||
end
|
||||
|
||||
task default: TESTS
|
||||
task default: BINARY
|
||||
|
||||
desc 'Run all tests and check the results'
|
||||
task test: TESTS
|
||||
task test: BINARY do
|
||||
TESTS.each do |test|
|
||||
expected = Pathname
|
||||
.new(test)
|
||||
.sub_ext('.txt')
|
||||
.sub(/^build\/[[:alpha:]]+\//, 'tests/expectations/')
|
||||
.to_path
|
||||
|
||||
puts "Running #{test}"
|
||||
if test.include? '/riscv/'
|
||||
spike = [
|
||||
'/opt/riscv/bin/spike',
|
||||
'--isa=RV32IMAC',
|
||||
'/opt/riscv/riscv32-unknown-elf/bin/pk',
|
||||
test
|
||||
]
|
||||
diff = ['diff', '-Nur', '--color', expected, '-']
|
||||
tail = ['tail', '-n', '1']
|
||||
|
||||
last_stdout, wait_threads = Open3.pipeline_r spike, tail, diff
|
||||
else
|
||||
raise 'Unsupported test platform'
|
||||
end
|
||||
print last_stdout.read
|
||||
last_stdout.close
|
||||
|
||||
fail unless wait_threads.last.value.exitstatus.zero?
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Run unittest blocks'
|
||||
task unittest: SOURCES do |t|
|
||||
sh('dub', 'test', '--compiler=gdc-12')
|
||||
|
8
TODO
Normal file
8
TODO
Normal file
@ -0,0 +1,8 @@
|
||||
- Mark all classes as extern(C++) to interface with C++.
|
||||
- Some nodes assert(false, "Not implemented") because their logic is handled
|
||||
in the parent nodes. Move this logic to the nodes themselves.
|
||||
- Implement multiplication and division.
|
||||
- Split tester.cpp into a header and implementation files.
|
||||
- The generate function in backend.d contains the whole compilation logic.
|
||||
It's not a backend task.
|
||||
- Output assembly.
|
146
source/elna/ir.d
146
source/elna/ir.d
@ -15,34 +15,35 @@ struct ASTMapping
|
||||
alias Node = .Node;
|
||||
alias Definition = .Definition;
|
||||
alias VariableDeclaration = .VariableDeclaration;
|
||||
alias Statement = Array!(.Statement);
|
||||
alias BangStatement = .Expression;
|
||||
alias Statement = .Operand;
|
||||
alias BangStatement = .Operand;
|
||||
alias Block = .Definition;
|
||||
alias Expression = .Expression;
|
||||
alias Expression = .Operand;
|
||||
alias Number = .Number;
|
||||
alias Variable = .Variable;
|
||||
alias BinaryExpression = .BinaryExpression;
|
||||
alias Variable = .Number;
|
||||
alias BinaryExpression = .Variable;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* IR visitor.
|
||||
*/
|
||||
extern(C++)
|
||||
interface IRVisitor
|
||||
{
|
||||
abstract void visit(Node) @nogc;
|
||||
abstract void visit(Definition) @nogc;
|
||||
abstract void visit(Expression) @nogc;
|
||||
abstract void visit(Statement) @nogc;
|
||||
abstract void visit(Operand) @nogc;
|
||||
abstract void visit(BinaryExpression) @nogc;
|
||||
abstract void visit(Variable) @nogc;
|
||||
abstract void visit(VariableDeclaration) @nogc;
|
||||
abstract void visit(Number) @nogc;
|
||||
abstract void visit(BinaryExpression) @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
* AST node.
|
||||
*/
|
||||
extern(C++)
|
||||
abstract class Node
|
||||
{
|
||||
abstract void accept(IRVisitor) @nogc;
|
||||
@ -51,11 +52,13 @@ abstract class Node
|
||||
/**
|
||||
* Definition.
|
||||
*/
|
||||
extern(C++)
|
||||
class Definition : Node
|
||||
{
|
||||
char[] identifier;
|
||||
Array!Statement statements;
|
||||
Array!BinaryExpression statements;
|
||||
Array!VariableDeclaration variableDeclarations;
|
||||
Operand result;
|
||||
|
||||
override void accept(IRVisitor visitor) @nogc
|
||||
{
|
||||
@ -63,17 +66,13 @@ class Definition : Node
|
||||
}
|
||||
}
|
||||
|
||||
class Statement : Node
|
||||
extern(C++)
|
||||
abstract class Statement : Node
|
||||
{
|
||||
BinaryExpression expression;
|
||||
|
||||
override void accept(IRVisitor visitor) @nogc
|
||||
{
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Expression : Node
|
||||
extern(C++)
|
||||
abstract class Operand : Node
|
||||
{
|
||||
override void accept(IRVisitor visitor) @nogc
|
||||
{
|
||||
@ -81,7 +80,8 @@ abstract class Expression : Node
|
||||
}
|
||||
}
|
||||
|
||||
class Number : Expression
|
||||
extern(C++)
|
||||
class Number : Operand
|
||||
{
|
||||
int value;
|
||||
|
||||
@ -91,7 +91,8 @@ class Number : Expression
|
||||
}
|
||||
}
|
||||
|
||||
class Variable : Expression
|
||||
extern(C++)
|
||||
class Variable : Operand
|
||||
{
|
||||
size_t counter;
|
||||
|
||||
@ -101,6 +102,7 @@ class Variable : Expression
|
||||
}
|
||||
}
|
||||
|
||||
extern(C++)
|
||||
class VariableDeclaration : Node
|
||||
{
|
||||
String identifier;
|
||||
@ -111,12 +113,13 @@ class VariableDeclaration : Node
|
||||
}
|
||||
}
|
||||
|
||||
class BinaryExpression : Node
|
||||
extern(C++)
|
||||
class BinaryExpression : Statement
|
||||
{
|
||||
Expression lhs, rhs;
|
||||
Operand lhs, rhs;
|
||||
BinaryOperator operator;
|
||||
|
||||
this(Expression lhs, Expression rhs, BinaryOperator operator)
|
||||
this(Operand lhs, Operand rhs, BinaryOperator operator)
|
||||
@nogc
|
||||
{
|
||||
this.lhs = lhs;
|
||||
@ -130,9 +133,26 @@ class BinaryExpression : Node
|
||||
}
|
||||
}
|
||||
|
||||
extern(C++)
|
||||
class BangExpression : Statement
|
||||
{
|
||||
Operand operand;
|
||||
|
||||
this(Operand operand)
|
||||
{
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
override void accept(IRVisitor visitor) @nogc
|
||||
{
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
final class TransformVisitor : parser.ParserVisitor!ASTMapping
|
||||
{
|
||||
private HashTable!(String, int) constants;
|
||||
private Array!BinaryExpression statements;
|
||||
|
||||
ASTMapping.Node visit(parser.Node node) @nogc
|
||||
{
|
||||
@ -151,7 +171,7 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
|
||||
|
||||
ASTMapping.BangStatement visit(parser.BangStatement statement) @nogc
|
||||
{
|
||||
assert(false, "Not implemented");
|
||||
return statement.expression.accept(this);
|
||||
}
|
||||
|
||||
ASTMapping.Block visit(parser.Block block) @nogc
|
||||
@ -159,7 +179,9 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
|
||||
auto target = defaultAllocator.make!Definition;
|
||||
this.constants = transformConstants(block.definitions);
|
||||
|
||||
target.statements = block.statement.accept(this);
|
||||
target.result = block.statement.accept(this);
|
||||
target.statements = this.statements;
|
||||
|
||||
target.variableDeclarations = transformVariableDeclarations(block.variableDeclarations);
|
||||
|
||||
return target;
|
||||
@ -167,41 +189,45 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
|
||||
|
||||
ASTMapping.Expression visit(parser.Expression expression) @nogc
|
||||
{
|
||||
assert(false, "Not implemented");
|
||||
if ((cast(parser.Number) expression) !is null)
|
||||
{
|
||||
return (cast(parser.Number) expression).accept(this);
|
||||
}
|
||||
if ((cast(parser.Variable) expression) !is null)
|
||||
{
|
||||
return (cast(parser.Variable) expression).accept(this);
|
||||
}
|
||||
else if ((cast(parser.BinaryExpression) expression) !is null)
|
||||
{
|
||||
return (cast(parser.BinaryExpression) expression).accept(this);
|
||||
}
|
||||
assert(false, "Invalid expression type");
|
||||
}
|
||||
|
||||
ASTMapping.Number visit(parser.Number number) @nogc
|
||||
{
|
||||
assert(false, "Not implemented");
|
||||
auto numberExpression = defaultAllocator.make!Number;
|
||||
numberExpression.value = number.value;
|
||||
|
||||
return numberExpression;
|
||||
}
|
||||
|
||||
ASTMapping.Variable visit(parser.Variable variable) @nogc
|
||||
{
|
||||
assert(false, "Not implemented");
|
||||
auto numberExpression = defaultAllocator.make!Number;
|
||||
numberExpression.value = this.constants[variable.identifier];
|
||||
|
||||
return numberExpression;
|
||||
}
|
||||
|
||||
ASTMapping.BinaryExpression visit(parser.BinaryExpression) @nogc
|
||||
{
|
||||
assert(false, "Not implemented");
|
||||
}
|
||||
|
||||
private Number transformNumber(parser.Number number) @nogc
|
||||
{
|
||||
return defaultAllocator.make!Number(number.value);
|
||||
}
|
||||
|
||||
private Variable binaryExpression(parser.BinaryExpression binaryExpression,
|
||||
ref Array!Statement statements) @nogc
|
||||
ASTMapping.BinaryExpression visit(parser.BinaryExpression binaryExpression) @nogc
|
||||
{
|
||||
auto target = defaultAllocator.make!BinaryExpression(
|
||||
expression(binaryExpression.lhs, statements),
|
||||
expression(binaryExpression.rhs, statements),
|
||||
binaryExpression.lhs.accept(this),
|
||||
binaryExpression.rhs.accept(this),
|
||||
binaryExpression.operator
|
||||
);
|
||||
|
||||
auto newStatement = defaultAllocator.make!Statement;
|
||||
newStatement.expression = target;
|
||||
statements.insertBack(newStatement);
|
||||
statements.insertBack(target);
|
||||
|
||||
auto newVariable = defaultAllocator.make!Variable;
|
||||
newVariable.counter = statements.length;
|
||||
@ -209,38 +235,18 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
|
||||
return newVariable;
|
||||
}
|
||||
|
||||
private Expression expression(parser.Expression expression,
|
||||
ref Array!Statement statements) @nogc
|
||||
private Number transformNumber(parser.Number number) @nogc
|
||||
{
|
||||
if ((cast(parser.Number) expression) !is null)
|
||||
{
|
||||
auto numberExpression = defaultAllocator.make!Number;
|
||||
numberExpression.value = (cast(parser.Number) expression).value;
|
||||
|
||||
return numberExpression;
|
||||
}
|
||||
if ((cast(parser.Variable) expression) !is null)
|
||||
{
|
||||
auto numberExpression = defaultAllocator.make!Number;
|
||||
numberExpression.value = this.constants[(cast(parser.Variable) expression).identifier];
|
||||
|
||||
return numberExpression;
|
||||
}
|
||||
else if ((cast(parser.BinaryExpression) expression) !is null)
|
||||
{
|
||||
return binaryExpression(cast(parser.BinaryExpression) expression, statements);
|
||||
}
|
||||
return null;
|
||||
return defaultAllocator.make!Number(number.value);
|
||||
}
|
||||
|
||||
override Array!Statement visit(parser.Statement statement) @nogc
|
||||
override Operand visit(parser.Statement statement) @nogc
|
||||
{
|
||||
typeof(return) statements;
|
||||
if ((cast(parser.BangStatement) statement) !is null)
|
||||
{
|
||||
expression((cast(parser.BangStatement) statement).expression, statements);
|
||||
return (cast(parser.BangStatement) statement).accept(this);
|
||||
}
|
||||
return statements;
|
||||
assert(false, "Invalid statement type");
|
||||
}
|
||||
|
||||
private HashTable!(String, int) transformConstants(ref Array!(parser.Definition) definitions) @nogc
|
||||
|
@ -8,6 +8,7 @@ import std.range;
|
||||
import tanya.container.array;
|
||||
import tanya.container.string;
|
||||
|
||||
extern(C++)
|
||||
struct Token
|
||||
{
|
||||
enum Type
|
||||
@ -71,7 +72,7 @@ struct Token
|
||||
}
|
||||
|
||||
@property auto value(Type type)() @nogc nothrow pure @trusted
|
||||
in (ofType(type))
|
||||
in (ofType(type), "Expected type: " ~ type.stringof)
|
||||
{
|
||||
static if (type == Type.number)
|
||||
{
|
||||
@ -99,14 +100,17 @@ struct Token
|
||||
/**
|
||||
* Range over the source text that keeps track of the current position.
|
||||
*/
|
||||
extern(C++)
|
||||
struct Source
|
||||
{
|
||||
char[] buffer;
|
||||
char* buffer_;
|
||||
size_t length_;
|
||||
Position position;
|
||||
|
||||
this(char[] buffer) @nogc nothrow pure @safe
|
||||
this(char* buffer, const size_t length) @nogc nothrow pure
|
||||
{
|
||||
this.buffer = buffer;
|
||||
this.buffer_ = buffer;
|
||||
this.length_ = length;
|
||||
}
|
||||
|
||||
@disable this();
|
||||
@ -116,54 +120,50 @@ struct Source
|
||||
return this.length == 0;
|
||||
}
|
||||
|
||||
char front() @nogc nothrow pure @safe
|
||||
char front() @nogc nothrow pure
|
||||
in (!empty)
|
||||
{
|
||||
return this.buffer[0];
|
||||
return this.buffer_[0];
|
||||
}
|
||||
|
||||
void popFront() @nogc nothrow pure @safe
|
||||
void popFront() @nogc nothrow pure
|
||||
in (!empty)
|
||||
{
|
||||
this.buffer = buffer[1 .. $];
|
||||
++this.buffer_;
|
||||
--this.length_;
|
||||
++this.position.column;
|
||||
}
|
||||
|
||||
void breakLine() @nogc nothrow pure @safe
|
||||
void breakLine() @nogc nothrow pure
|
||||
in (!empty)
|
||||
{
|
||||
this.buffer = buffer[1 .. $];
|
||||
++this.buffer_;
|
||||
--this.length_;
|
||||
++this.position.line;
|
||||
this.position.column = 1;
|
||||
}
|
||||
|
||||
@property size_t length() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.buffer.length;
|
||||
return this.length_;
|
||||
}
|
||||
|
||||
char opIndex(size_t index) @nogc nothrow pure @safe
|
||||
char opIndex(size_t index) @nogc nothrow pure
|
||||
in (index < length)
|
||||
{
|
||||
return this.buffer[index];
|
||||
return this.buffer_[index];
|
||||
}
|
||||
|
||||
char[] opSlice(size_t i, size_t j) @nogc nothrow pure @safe
|
||||
in
|
||||
char* buffer() @nogc nothrow pure
|
||||
{
|
||||
assert(i <= j);
|
||||
assert(j <= length);
|
||||
}
|
||||
do
|
||||
{
|
||||
return this.buffer[i .. j];
|
||||
return this.buffer_;
|
||||
}
|
||||
}
|
||||
|
||||
Result!(Array!Token) lex(char[] buffer) @nogc
|
||||
{
|
||||
Array!Token tokens;
|
||||
auto source = Source(buffer);
|
||||
auto source = Source(buffer.ptr, buffer.length);
|
||||
|
||||
while (!source.empty)
|
||||
{
|
||||
@ -218,17 +218,17 @@ Result!(Array!Token) lex(char[] buffer) @nogc
|
||||
{
|
||||
++i;
|
||||
}
|
||||
if (source[0 .. i] == "const")
|
||||
if (source.buffer[0 .. i] == "const")
|
||||
{
|
||||
tokens.insertBack(Token(Token.Type.let, source.position));
|
||||
}
|
||||
else if (source[0 .. i] == "var")
|
||||
else if (source.buffer[0 .. i] == "var")
|
||||
{
|
||||
tokens.insertBack(Token(Token.Type.var, source.position));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto identifier = String(source[0 .. i]);
|
||||
auto identifier = String(source.buffer[0 .. i]);
|
||||
tokens.insertBack(Token(Token.Type.identifier, identifier, source.position));
|
||||
}
|
||||
source.popFrontN(i);
|
||||
|
@ -1,5 +1,6 @@
|
||||
module elna.riscv;
|
||||
|
||||
import core.stdc.stdlib;
|
||||
import elna.extended;
|
||||
import elna.ir;
|
||||
import elna.result;
|
||||
@ -7,7 +8,6 @@ import std.algorithm;
|
||||
import std.typecons;
|
||||
import tanya.container.array;
|
||||
import tanya.container.string;
|
||||
import tanya.memory.allocator;
|
||||
|
||||
enum XRegister : ubyte
|
||||
{
|
||||
@ -140,14 +140,14 @@ struct Instruction
|
||||
return this;
|
||||
}
|
||||
|
||||
ref Instruction s(uint imm1, Funct3 funct3, XRegister rs1, XRegister rs2, uint imm2 = 0)
|
||||
ref Instruction s(uint imm1, Funct3 funct3, XRegister rs1, XRegister rs2)
|
||||
return scope @nogc
|
||||
{
|
||||
this.instruction |= (imm1 << 7)
|
||||
this.instruction |= ((imm1 & 0b11111) << 7)
|
||||
| (funct3 << 12)
|
||||
| (rs1 << 15)
|
||||
| (rs2 << 20)
|
||||
| (imm2 << 25);
|
||||
| ((imm1 & 0b111111100000) << 20);
|
||||
|
||||
return this;
|
||||
}
|
||||
@ -178,6 +178,7 @@ struct Instruction
|
||||
}
|
||||
}
|
||||
|
||||
extern(C++)
|
||||
class RiscVVisitor : IRVisitor
|
||||
{
|
||||
Array!Instruction instructions;
|
||||
@ -191,22 +192,24 @@ class RiscVVisitor : IRVisitor
|
||||
|
||||
override void visit(Definition definition) @nogc
|
||||
{
|
||||
const uint stackSize = cast(uint) (definition.statements.length * 4 + 12);
|
||||
|
||||
// Prologue.
|
||||
this.instructions.insertBack(
|
||||
Instruction(BaseOpcode.opImm)
|
||||
.i(XRegister.sp, Funct3.addi, XRegister.sp, cast(uint) -32)
|
||||
.i(XRegister.sp, Funct3.addi, XRegister.sp, -stackSize)
|
||||
);
|
||||
this.instructions.insertBack(
|
||||
Instruction(BaseOpcode.store)
|
||||
.s(28, Funct3.sw, XRegister.sp, XRegister.s0)
|
||||
.s(stackSize - 4, Funct3.sw, XRegister.sp, XRegister.s0)
|
||||
);
|
||||
this.instructions.insertBack(
|
||||
Instruction(BaseOpcode.store)
|
||||
.s(24, Funct3.sw, XRegister.sp, XRegister.ra)
|
||||
.s(stackSize - 8, Funct3.sw, XRegister.sp, XRegister.ra)
|
||||
);
|
||||
this.instructions.insertBack(
|
||||
Instruction(BaseOpcode.opImm)
|
||||
.i(XRegister.s0, Funct3.addi, XRegister.sp, 32)
|
||||
.i(XRegister.s0, Funct3.addi, XRegister.sp, stackSize)
|
||||
);
|
||||
|
||||
foreach (statement; definition.statements[])
|
||||
@ -217,6 +220,10 @@ class RiscVVisitor : IRVisitor
|
||||
{
|
||||
variableDeclaration.accept(this);
|
||||
}
|
||||
this.registerInUse = true;
|
||||
definition.result.accept(this);
|
||||
this.registerInUse = false;
|
||||
|
||||
// Print the result.
|
||||
this.instructions.insertBack(
|
||||
Instruction(BaseOpcode.opImm)
|
||||
@ -247,15 +254,15 @@ class RiscVVisitor : IRVisitor
|
||||
// Epilogue.
|
||||
this.instructions.insertBack(
|
||||
Instruction(BaseOpcode.load)
|
||||
.i(XRegister.s0, Funct3.lw, XRegister.sp, 28)
|
||||
.i(XRegister.s0, Funct3.lw, XRegister.sp, stackSize - 4)
|
||||
);
|
||||
this.instructions.insertBack(
|
||||
Instruction(BaseOpcode.load)
|
||||
.i(XRegister.ra, Funct3.lw, XRegister.sp, 24)
|
||||
.i(XRegister.ra, Funct3.lw, XRegister.sp, stackSize - 8)
|
||||
);
|
||||
this.instructions.insertBack(
|
||||
Instruction(BaseOpcode.opImm)
|
||||
.i(XRegister.sp, Funct3.addi, XRegister.sp, 32)
|
||||
.i(XRegister.sp, Funct3.addi, XRegister.sp, stackSize)
|
||||
);
|
||||
this.instructions.insertBack(
|
||||
Instruction(BaseOpcode.jalr)
|
||||
@ -263,13 +270,16 @@ class RiscVVisitor : IRVisitor
|
||||
);
|
||||
}
|
||||
|
||||
override void visit(Expression) @nogc
|
||||
override void visit(Operand operand) @nogc
|
||||
{
|
||||
}
|
||||
|
||||
override void visit(Statement statement) @nogc
|
||||
{
|
||||
statement.expression.accept(this);
|
||||
if ((cast(Variable) operand) !is null)
|
||||
{
|
||||
return (cast(Variable) operand).accept(this);
|
||||
}
|
||||
if ((cast(Number) operand) !is null)
|
||||
{
|
||||
return (cast(Number) operand).accept(this);
|
||||
}
|
||||
}
|
||||
|
||||
override void visit(Variable variable) @nogc
|
||||
@ -334,10 +344,12 @@ Symbol writeNext(Definition ast) @nogc
|
||||
{
|
||||
Array!Instruction instructions;
|
||||
Array!Reference references;
|
||||
auto visitor = defaultAllocator.make!RiscVVisitor;
|
||||
auto visitor = cast(RiscVVisitor) malloc(__traits(classInstanceSize, RiscVVisitor));
|
||||
(cast(void*) visitor)[0 .. __traits(classInstanceSize, RiscVVisitor)] = __traits(initSymbol, RiscVVisitor)[];
|
||||
scope (exit)
|
||||
{
|
||||
defaultAllocator.dispose(visitor);
|
||||
visitor.__xdtor();
|
||||
free(cast(void*) visitor);
|
||||
}
|
||||
visitor.visit(ast);
|
||||
|
||||
|
1
tests/7_member_sum.eln
Normal file
1
tests/7_member_sum.eln
Normal file
@ -0,0 +1 @@
|
||||
! 3 + 4 + 5 + 1 + 2 + 4 + 3
|
1
tests/expectations/7_member_sum.txt
Normal file
1
tests/expectations/7_member_sum.txt
Normal file
@ -0,0 +1 @@
|
||||
22
|
1
tests/expectations/print_number.txt
Normal file
1
tests/expectations/print_number.txt
Normal file
@ -0,0 +1 @@
|
||||
3
|
2
tests/print_number.eln
Normal file
2
tests/print_number.eln
Normal file
@ -0,0 +1,2 @@
|
||||
! 3
|
||||
.
|
103
tests/runner.cpp
Executable file
103
tests/runner.cpp
Executable file
@ -0,0 +1,103 @@
|
||||
#include <boost/process.hpp>
|
||||
|
||||
#include <iostream>
|
||||
#include <filesystem>
|
||||
|
||||
class test_results final
|
||||
{
|
||||
std::uint32_t m_total{ 0 };
|
||||
std::uint32_t m_passed{ 0 };
|
||||
|
||||
public:
|
||||
test_results() = default;
|
||||
|
||||
std::uint32_t total() const noexcept
|
||||
{
|
||||
return m_total;
|
||||
}
|
||||
|
||||
std::uint32_t passed() const noexcept
|
||||
{
|
||||
return m_passed;
|
||||
}
|
||||
|
||||
std::uint32_t failed() const noexcept
|
||||
{
|
||||
return m_total - m_passed;
|
||||
}
|
||||
|
||||
int exit_code() const noexcept
|
||||
{
|
||||
return m_total == m_passed ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
}
|
||||
|
||||
void add_exit_code(const int exit_code) noexcept
|
||||
{
|
||||
++m_total;
|
||||
if (exit_code == 0)
|
||||
{
|
||||
++m_passed;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
int run_test(const std::filesystem::directory_entry& test_entry)
|
||||
{
|
||||
const std::filesystem::path test_filename = test_entry.path().filename();
|
||||
|
||||
std::filesystem::path test_binary = "build/riscv" / test_filename;
|
||||
test_binary.replace_extension();
|
||||
|
||||
std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename;
|
||||
expectation_path.replace_extension(".txt");
|
||||
|
||||
std::cout << "Running " << test_binary << std::endl;
|
||||
|
||||
boost::process::pipe pipe_stream;
|
||||
boost::process::child vm(
|
||||
"/opt/riscv/bin/spike", "--isa=RV32IMAC",
|
||||
"/opt/riscv/riscv32-unknown-elf/bin/pk",
|
||||
test_binary.string(),
|
||||
boost::process::std_out > pipe_stream
|
||||
);
|
||||
boost::process::child diff(
|
||||
"/usr/bin/diff", "-Nur", "--color",
|
||||
expectation_path.string(), "-",
|
||||
boost::process::std_in < pipe_stream
|
||||
);
|
||||
vm.wait();
|
||||
diff.wait();
|
||||
|
||||
return diff.exit_code();
|
||||
}
|
||||
|
||||
test_results run_in_path(const std::filesystem::path test_directory)
|
||||
{
|
||||
test_results results;
|
||||
|
||||
for (const auto& test_entry : std::filesystem::directory_iterator(test_directory))
|
||||
{
|
||||
if (test_entry.path().extension() != ".eln" || !test_entry.is_regular_file())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
results.add_exit_code(run_test(test_entry));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
boost::process::system("rake");
|
||||
|
||||
std::cout << "Run all tests and check the results" << std::endl;
|
||||
std::filesystem::path test_directory{ "tests" };
|
||||
const auto results = run_in_path(test_directory);
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << results.total() << " tests run, "
|
||||
<< results.passed() << " passed, "
|
||||
<< results.failed() << " failed." << std::endl;
|
||||
|
||||
return results.exit_code();
|
||||
}
|
Loading…
Reference in New Issue
Block a user