Print test summary

This commit is contained in:
Eugen Wissner 2024-02-28 16:18:39 +01:00
parent 03a72fc583
commit 4d46fc6b4d
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
18 changed files with 890 additions and 706 deletions

View File

@ -12,11 +12,14 @@ add_executable(elnsh shell/main.cpp
shell/interactive.cpp include/elna/interactive.hpp shell/interactive.cpp include/elna/interactive.hpp
shell/history.cpp include/elna/history.hpp shell/history.cpp include/elna/history.hpp
shell/state.cpp include/elna/state.hpp shell/state.cpp include/elna/state.hpp
shell/result.cpp include/elna/result.hpp
) )
target_include_directories(elnsh PRIVATE include) target_include_directories(elnsh PRIVATE include)
add_library(elna add_library(elna
source/lexer.cpp include/elna/lexer.hpp source/lexer.cpp include/elna/lexer.hpp
source/result.cpp include/elna/result.hpp
source/riscv.cpp include/elna/riscv.hpp
source/ir.cpp include/elna/ir.hpp
include/elna/parser.hpp
) )
target_include_directories(elna PRIVATE include) target_include_directories(elna PRIVATE include)

View File

@ -4,87 +4,16 @@ 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/*.eln'].flat_map do |test|
build = Pathname.new 'build'
test_basename = Pathname.new(test).basename('')
[build + 'riscv' + test_basename].map { |path| path.sub_ext('').to_path }
end
SOURCES = FileList['source/**/*.d'] SOURCES = FileList['source/**/*.d']
directory 'build' directory 'build/riscv'
CLEAN.include 'build' CLEAN.include 'build'
CLEAN.include '.dub' CLEAN.include '.dub'
rule(/build\/riscv\/[^\/\.]+$/ => ->(file) { test_for_out(file, '.o') }) do |t|
sh '/opt/riscv/bin/riscv32-unknown-elf-ld',
'-o', t.name,
'-L/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/',
'-L/opt/riscv/riscv32-unknown-elf/lib',
'/opt/riscv/riscv32-unknown-elf/lib/crt0.o',
'/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtbegin.o',
t.source,
'--start-group', '-lgcc', '-lc', '-lgloss', '--end-group',
'/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtend.o'
end
rule(/build\/riscv\/.+\.o$/ => ->(file) { test_for_object(file, '.eln') }) do |t|
Pathname.new(t.name).dirname.mkpath
sh BINARY, '-o', t.name, t.source
end
file BINARY => SOURCES do |t| file BINARY => SOURCES do |t|
sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc') sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc')
end end
task default: 'build/riscv'
task default: BINARY 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
def test_for_object(out_file, extension)
test_source = Pathname
.new(out_file)
.sub_ext(extension)
.sub(/^build\/[[:alpha:]]+\//, 'tests/')
.to_path
[test_source, BINARY]
end
def test_for_out(out_file, extension)
Pathname
.new(out_file)
.sub_ext(extension)
.to_path
end

View File

@ -5,5 +5,7 @@
"name": "elna", "name": "elna",
"targetType": "executable", "targetType": "executable",
"targetPath": "build/bin", "targetPath": "build/bin",
"mainSourceFile": "source/main.d" "mainSourceFile": "source/main.d",
"libs": ["elna", "stdc++"],
"lflags": ["-Lbuild"]
} }

95
include/elna/ir.hpp Normal file
View File

@ -0,0 +1,95 @@
#pragma once
#include "elna/parser.hpp"
#include <cstddef>
#include <cstdint>
namespace elna::ir
{
class Node;
class Definition;
class Operand;
class BinaryExpression;
class Variable;
class VariableDeclaration;
class Number;
struct IRVisitor
{
virtual void visit(Node *) = 0;
virtual void visit(Definition *) = 0;
virtual void visit(Operand *) = 0;
virtual void visit(BinaryExpression *) = 0;
virtual void visit(Variable *) = 0;
virtual void visit(Number *) = 0;
};
/**
* AST node.
*/
class Node
{
public:
virtual void accept(IRVisitor *) = 0;
};
/**
* Definition.
*/
class Definition : public Node
{
public:
BinaryExpression **statements;
std::size_t statementsLength;
Operand *result;
virtual void accept(IRVisitor *visitor) override;
};
class Statement : public Node
{
};
class Operand : public Node
{
public:
virtual void accept(IRVisitor *visitor) override;
};
class Number : public Operand
{
public:
std::int32_t value;
virtual void accept(IRVisitor *visitor) override;
};
class Variable : public Operand
{
public:
std::size_t counter;
virtual void accept(IRVisitor *visitor) override;
};
class BinaryExpression : public Statement
{
public:
Operand *lhs, *rhs;
BinaryOperator _operator;
BinaryExpression(Operand *lhs, Operand *rhs, BinaryOperator _operator);
virtual void accept(IRVisitor *visitor) override;
};
class BangExpression : public Statement
{
Operand *operand;
public:
BangExpression(Operand *operand);
virtual void accept(IRVisitor *visitor) override;
};
}

View File

@ -14,10 +14,10 @@ namespace elna
class const_iterator class const_iterator
{ {
std::string::const_iterator m_buffer; std::string::const_iterator m_buffer;
source_position m_position; Position m_position;
const_iterator(std::string::const_iterator buffer, const_iterator(std::string::const_iterator buffer,
const source_position position = source_position()); const Position position = Position());
public: public:
using iterator_category = std::forward_iterator_tag; using iterator_category = std::forward_iterator_tag;
@ -26,7 +26,7 @@ namespace elna
using pointer = const value_type *; using pointer = const value_type *;
using reference = const value_type&; using reference = const value_type&;
const source_position& position() const noexcept; const Position& position() const noexcept;
reference operator*() const noexcept; reference operator*() const noexcept;
pointer operator->() const noexcept; pointer operator->() const noexcept;
@ -43,38 +43,61 @@ namespace elna
const_iterator end() const; const_iterator end() const;
private: private:
const std::string& m_buffer; const std::string m_buffer;
}; };
/** /**
* Union type representing a single token. * Union type representing a single token.
*/ */
struct token struct Token
{ {
/** /**
* Token type. * Token type.
*/ */
enum class type enum Type : std::uint16_t
{ {
word, TOKEN_NUMBER = 0,
TOKEN_OPERATOR = 1,
TOKEN_LET = 2,
TOKEN_IDENTIFIER = 3,
TOKEN_EQUALS = 4,
TOKEN_VAR = 5,
TOKEN_SEMICOLON = 6,
TOKEN_LEFT_PAREN = 7,
TOKEN_RIGHT_PAREN = 8,
TOKEN_BANG = 9,
TOKEN_DOT = 10,
TOKEN_COMMA = 11,
}; };
/** /**
* Type of the token value. * Type of the token value.
*/ */
using value = std::string; union Value
{
std::int32_t number;
const char *identifier;
};
token(const type of, source::const_iterator begin, source::const_iterator end); Token(Type of, Position position);
Token(Type of, std::int32_t value, Position position);
Token(Type of, const char *value, Position position);
Token(const Token& that);
Token(Token&& that);
~Token();
type of() const noexcept; Token& operator=(const Token& that);
const value& identifier() const noexcept; Token& operator=(Token&& that);
const source_position& position() const noexcept;
Type of() const noexcept;
const char *identifier() const noexcept;
std::int32_t number() const noexcept;
const Position& position() const noexcept;
private: private:
std::string m_value; Type m_type;
source_position m_position; Value m_value;
type m_type; Position m_position;
}; };
/** /**
@ -82,5 +105,5 @@ namespace elna
* *
* \return Tokens or error. * \return Tokens or error.
*/ */
result<std::vector<token>> lex(const std::string& buffer); Token *lex(const char *buffer, CompileError *compile_error, std::size_t *length);
} }

10
include/elna/parser.hpp Normal file
View File

@ -0,0 +1,10 @@
#pragma once
namespace elna
{
enum class BinaryOperator
{
sum,
subtraction
};
}

View File

@ -8,7 +8,7 @@ namespace elna
/** /**
* Position in the source text. * Position in the source text.
*/ */
struct source_position struct Position
{ {
/// Line. /// Line.
std::size_t line = 1; std::size_t line = 1;
@ -20,18 +20,18 @@ namespace elna
/** /**
* A compilation error consists of an error message and position. * A compilation error consists of an error message and position.
*/ */
struct compile_error struct CompileError
{ {
private: private:
char const *message; char const *message;
source_position position; Position position;
public: public:
/** /**
* @param message Error text. * @param message Error text.
* @param position Error position in the source text. * @param position Error position in the source text.
*/ */
compile_error(char const *message, const source_position position) noexcept; CompileError(char const *message, const Position position) noexcept;
/// Error text. /// Error text.
const char *what() const noexcept; const char *what() const noexcept;
@ -44,5 +44,19 @@ namespace elna
}; };
template<typename T> template<typename T>
using result = boost::outcome_v2::result<T, compile_error>; using result = boost::outcome_v2::result<T, CompileError>;
enum class Target
{
text,
high20,
lower12i
};
struct Reference
{
const char* name;
size_t offset;
Target target;
};
} }

148
include/elna/riscv.hpp Normal file
View File

@ -0,0 +1,148 @@
#pragma once
#include <cstdint>
#include "elna/ir.hpp"
#include "elna/result.hpp"
namespace elna
{
enum class XRegister : std::uint8_t
{
zero = 0,
ra = 1,
sp = 2,
gp = 3,
tp = 4,
t0 = 5,
t1 = 6,
t2 = 7,
s0 = 8,
s1 = 9,
a0 = 10,
a1 = 11,
a2 = 12,
a3 = 13,
a4 = 14,
a5 = 15,
a6 = 16,
a7 = 17,
s2 = 18,
s3 = 19,
s4 = 20,
s5 = 21,
s6 = 22,
s7 = 23,
s8 = 24,
s9 = 25,
s10 = 26,
s11 = 27,
t3 = 28,
t4 = 29,
t5 = 30,
t6 = 31,
};
enum class Funct3 : std::uint8_t
{
addi = 0b000,
slti = 0b001,
sltiu = 0b011,
andi = 0b111,
ori = 0b110,
xori = 0b100,
slli = 0b000,
srli = 0b101,
srai = 0b101,
add = 0b000,
slt = 0b010,
sltu = 0b011,
_and = 0b111,
_or = 0b110,
_xor = 0b100,
sll = 0b001,
srl = 0b101,
sub = 0b000,
sra = 0b101,
beq = 0b000,
bne = 0b001,
blt = 0b100,
bltu = 0b110,
bge = 0b101,
bgeu = 0b111,
fence = 0b000,
fenceI = 0b001,
csrrw = 0b001,
csrrs = 0b010,
csrrc = 0b011,
csrrwi = 0b101,
csrrsi = 0b110,
csrrci = 0b111,
priv = 0b000,
sb = 0b000,
sh = 0b001,
sw = 0b010,
lb = 0b000,
lh = 0b001,
lw = 0b010,
lbu = 0b100,
lhu = 0b101,
jalr = 0b000,
};
enum class Funct12 : std::uint8_t
{
ecall = 0b000000000000,
ebreak = 0b000000000001,
};
enum class Funct7 : std::uint8_t
{
none = 0,
sub = 0b0100000
};
enum class BaseOpcode : std::uint8_t
{
opImm = 0b0010011,
lui = 0b0110111,
auipc = 0b0010111,
op = 0b0110011,
jal = 0b1101111,
jalr = 0b1100111,
branch = 0b1100011,
load = 0b0000011,
store = 0b0100011,
miscMem = 0b0001111,
system = 0b1110011,
};
struct Instruction
{
Instruction(BaseOpcode opcode);
Instruction& i(XRegister rd, Funct3 funct3, XRegister rs1, std::uint32_t immediate);
Instruction& s(std::uint32_t imm1, Funct3 funct3, XRegister rs1, XRegister rs2);
Instruction& r(XRegister rd, Funct3 funct3, XRegister rs1, XRegister rs2, Funct7 funct7 = Funct7::none);
Instruction& u(XRegister rd, std::uint32_t imm);
std::uint8_t *encode();
private:
std::uint32_t instruction{ 0 };
};
class RiscVVisitor : public ir::IRVisitor
{
Instruction *instructions;
std::size_t instructionsLength;
bool registerInUse;
std::uint32_t variableCounter = 1;
Reference references[3];
virtual void visit(ir::Node *) override;
virtual void visit(ir::Definition *definition) override;
virtual void visit(ir::Operand *operand) override;
virtual void visit(ir::Variable *variable) override;
virtual void visit(ir::Number *number) override;
virtual void visit(ir::BinaryExpression *expression) override;
};
}

View File

@ -2,6 +2,7 @@ module elna.backend;
import core.stdc.stdio; import core.stdc.stdio;
import core.stdc.stdlib; import core.stdc.stdlib;
import core.stdc.string;
import elna.elf; import elna.elf;
import elna.ir; import elna.ir;
import elna.extended; import elna.extended;
@ -17,7 +18,7 @@ import tanya.container.array;
import tanya.container.string; import tanya.container.string;
import tanya.memory.allocator; import tanya.memory.allocator;
private Nullable!String readSource(string source) @nogc private char* readSource(string source) @nogc
{ {
enum size_t bufferSize = 255; enum size_t bufferSize = 255;
auto sourceFilename = String(source); auto sourceFilename = String(source);
@ -25,31 +26,39 @@ private Nullable!String readSource(string source) @nogc
return readFile(sourceFilename).match!( return readFile(sourceFilename).match!(
(ErrorCode errorCode) { (ErrorCode errorCode) {
perror(sourceFilename.toStringz); perror(sourceFilename.toStringz);
return Nullable!String(); return null;
}, },
(Array!ubyte contents) => nullable(String(cast(char[]) contents.get)) (Array!ubyte contents) {
char* cString = cast(char*) malloc(contents.length + 1);
memcpy(cString, contents.get.ptr, contents.length);
cString[contents.length] = '\0';
return cString;
}
); );
} }
int generate(string inFile, ref String outputFilename) @nogc int generate(string inFile, ref String outputFilename) @nogc
{ {
auto sourceText = readSource(inFile); auto sourceText = readSource(inFile);
if (sourceText.isNull) if (sourceText is null)
{ {
return 3; return 3;
} }
auto tokens = lex(sourceText.get.get); CompileError compileError = void;
if (!tokens.valid) size_t tokensCount;
auto tokens = lex(sourceText, &compileError, &tokensCount);
free(sourceText);
if (tokens is null)
{ {
auto compileError = tokens.error.get; printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.what);
printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.message.ptr);
return 1; return 1;
} }
auto ast = parse(tokens.result); auto ast = parse(tokens[0 .. tokensCount]);
if (!ast.valid) if (!ast.valid)
{ {
auto compileError = ast.error.get; compileError = ast.error.get;
printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.message.ptr); printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.what);
return 2; return 2;
} }
auto transformVisitor = cast(TransformVisitor) malloc(__traits(classInstanceSize, TransformVisitor)); auto transformVisitor = cast(TransformVisitor) malloc(__traits(classInstanceSize, TransformVisitor));
@ -85,18 +94,18 @@ int generate(string inFile, ref String outputFilename) @nogc
final switch (reference.target) final switch (reference.target)
{ {
case Reference.Target.text: case Target.text:
relocationEntry.r_info = R_RISCV_CALL; relocationEntry.r_info = R_RISCV_CALL;
break; break;
case Reference.Target.high20: case Target.high20:
relocationEntry.r_info = R_RISCV_HI20; relocationEntry.r_info = R_RISCV_HI20;
break; break;
case Reference.Target.lower12i: case Target.lower12i:
relocationEntry.r_info = R_RISCV_LO12_I; relocationEntry.r_info = R_RISCV_LO12_I;
break; break;
} }
elf.relocate(reference.name, relocationEntry, relocationSub); elf.relocate(String(reference.name[0 .. strlen(reference.name)]), relocationEntry, relocationSub);
} }
elf.finish(); elf.finish();

View File

@ -1,5 +1,6 @@
module elna.ir; module elna.ir;
import core.stdc.stdlib;
import parser = elna.parser; import parser = elna.parser;
import tanya.container.array; import tanya.container.array;
import tanya.container.hashtable; import tanya.container.hashtable;
@ -14,7 +15,6 @@ struct ASTMapping
{ {
alias Node = .Node; alias Node = .Node;
alias Definition = .Definition; alias Definition = .Definition;
alias VariableDeclaration = .VariableDeclaration;
alias Statement = .Operand; alias Statement = .Operand;
alias BangStatement = .Operand; alias BangStatement = .Operand;
alias Block = .Definition; alias Block = .Definition;
@ -28,20 +28,21 @@ struct ASTMapping
/** /**
* IR visitor. * IR visitor.
*/ */
interface IRVisitor extern(C++, "elna", "ir")
abstract class IRVisitor
{ {
abstract void visit(Node) @nogc; abstract void visit(Node) @nogc;
abstract void visit(Definition) @nogc; abstract void visit(Definition) @nogc;
abstract void visit(Operand) @nogc; abstract void visit(Operand) @nogc;
abstract void visit(BinaryExpression) @nogc; abstract void visit(BinaryExpression) @nogc;
abstract void visit(Variable) @nogc; abstract void visit(Variable) @nogc;
abstract void visit(VariableDeclaration) @nogc;
abstract void visit(Number) @nogc; abstract void visit(Number) @nogc;
} }
/** /**
* AST node. * AST node.
*/ */
extern(C++, "elna", "ir")
abstract class Node abstract class Node
{ {
abstract void accept(IRVisitor) @nogc; abstract void accept(IRVisitor) @nogc;
@ -50,99 +51,69 @@ abstract class Node
/** /**
* Definition. * Definition.
*/ */
extern(C++, "elna", "ir")
class Definition : Node class Definition : Node
{ {
char[] identifier; BinaryExpression* statements;
Array!BinaryExpression statements; size_t statementsLength;
Array!VariableDeclaration variableDeclarations;
Operand result; Operand result;
override void accept(IRVisitor visitor) @nogc override void accept(IRVisitor visitor) @nogc;
{
visitor.visit(this);
}
} }
extern(C++, "elna", "ir")
abstract class Statement : Node abstract class Statement : Node
{ {
} }
extern(C++, "elna", "ir")
abstract class Operand : Node abstract class Operand : Node
{ {
override void accept(IRVisitor visitor) @nogc override void accept(IRVisitor visitor) @nogc;
{
visitor.visit(this);
}
} }
extern(C++, "elna", "ir")
class Number : Operand class Number : Operand
{ {
int value; int value;
override void accept(IRVisitor visitor) @nogc override void accept(IRVisitor visitor) @nogc;
{
visitor.visit(this);
}
} }
extern(C++, "elna", "ir")
class Variable : Operand class Variable : Operand
{ {
size_t counter; size_t counter;
override void accept(IRVisitor visitor) @nogc override void accept(IRVisitor visitor) @nogc;
{
visitor.visit(this);
}
}
class VariableDeclaration : Node
{
String identifier;
override void accept(IRVisitor visitor) @nogc
{
visitor.visit(this);
}
} }
extern(C++, "elna", "ir")
class BinaryExpression : Statement class BinaryExpression : Statement
{ {
Operand lhs, rhs; Operand lhs, rhs;
BinaryOperator operator; BinaryOperator operator;
this(Operand lhs, Operand rhs, BinaryOperator operator) this(Operand lhs, Operand rhs, BinaryOperator operator) @nogc;
@nogc
{
this.lhs = lhs;
this.rhs = rhs;
this.operator = operator;
}
override void accept(IRVisitor visitor) @nogc override void accept(IRVisitor visitor) @nogc;
{
visitor.visit(this);
}
} }
extern(C++, "elna", "ir")
class BangExpression : Statement class BangExpression : Statement
{ {
Operand operand; Operand operand;
this(Operand operand) this(Operand operand);
{
this.operand = operand;
}
override void accept(IRVisitor visitor) @nogc override void accept(IRVisitor visitor) @nogc;
{
visitor.visit(this);
}
} }
final class TransformVisitor : parser.ParserVisitor!ASTMapping final class TransformVisitor : parser.ParserVisitor!ASTMapping
{ {
private HashTable!(String, int) constants; private HashTable!(String, int) constants;
private Array!BinaryExpression statements; private BinaryExpression* statements;
private size_t statementsLength;
ASTMapping.Node visit(parser.Node node) @nogc ASTMapping.Node visit(parser.Node node) @nogc
{ {
@ -154,11 +125,6 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
assert(false, "Not implemented"); assert(false, "Not implemented");
} }
ASTMapping.VariableDeclaration visit(parser.VariableDeclaration declaration) @nogc
{
assert(false, "Not implemented");
}
ASTMapping.BangStatement visit(parser.BangStatement statement) @nogc ASTMapping.BangStatement visit(parser.BangStatement statement) @nogc
{ {
return statement.expression.accept(this); return statement.expression.accept(this);
@ -171,8 +137,7 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
target.result = block.statement.accept(this); target.result = block.statement.accept(this);
target.statements = this.statements; target.statements = this.statements;
target.statementsLength = this.statementsLength;
target.variableDeclarations = transformVariableDeclarations(block.variableDeclarations);
return target; return target;
} }
@ -217,10 +182,12 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
binaryExpression.rhs.accept(this), binaryExpression.rhs.accept(this),
binaryExpression.operator binaryExpression.operator
); );
statements.insertBack(target); this.statements = cast(BinaryExpression*)
realloc(this.statements, (this.statementsLength + 1) * BinaryExpression.sizeof);
this.statements[this.statementsLength++] = target;
auto newVariable = defaultAllocator.make!Variable; auto newVariable = defaultAllocator.make!Variable;
newVariable.counter = statements.length; newVariable.counter = this.statementsLength;
return newVariable; return newVariable;
} }
@ -250,19 +217,4 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping
return constants; return constants;
} }
Array!VariableDeclaration transformVariableDeclarations(ref Array!(parser.VariableDeclaration) variableDeclarations)
@nogc
{
typeof(return) variables;
foreach (ref variableDeclaration; variableDeclarations)
{
auto newDeclaration = defaultAllocator.make!VariableDeclaration;
newDeclaration.identifier = variableDeclaration.identifier;
variables.insertBack(newDeclaration);
}
return variables;
}
} }

View File

@ -8,28 +8,29 @@ import std.range;
import tanya.container.array; import tanya.container.array;
import tanya.container.string; import tanya.container.string;
extern(C++, "elna")
struct Token struct Token
{ {
enum Type enum Type : ushort
{ {
number, number = 0,
operator, operator = 1,
let, let = 2,
identifier, identifier = 3,
equals, equals = 4,
var, var = 5,
semicolon, semicolon = 6,
leftParen, leftParen = 7,
rightParen, rightParen = 8,
bang, bang = 9,
dot, dot = 10,
comma, comma = 11,
} }
union Value union Value
{ {
int number; int number;
String identifier; const(char)* identifier;
} }
private Type type; private Type type;
@ -38,62 +39,21 @@ struct Token
@disable this(); @disable this();
this(Type type, Position position) @nogc nothrow pure @safe this(Type type, Position position) @nogc nothrow pure @safe;
{ this(Type type, int value, Position position) @nogc nothrow pure @trusted;
this.type = type; this(Type type, const(char)* value, Position position) @nogc nothrow;
this.position_ = position;
}
this(Type type, int value, Position position) @nogc nothrow pure @trusted
in (type == Type.number)
{
this(type, position);
this.value_.number = value;
}
this()(Type type, auto ref String value, Position position)
@nogc nothrow pure @trusted
in (type == Type.identifier || type == Type.operator)
{
this(type, position);
this.value_.identifier = value;
}
/** /**
* Params: * Returns: Expected token type.
* type = Expected type.
*
* Returns: Whether this token is of the expected type.
*/ */
bool ofType(Type type) const @nogc nothrow pure @safe Type of() const @nogc nothrow pure @safe;
{ const(char)* identifier() const @nogc nothrow pure;
return this.type == type; int number() const @nogc nothrow pure;
}
@property auto value(Type type)() @nogc nothrow pure @trusted
in (ofType(type))
{
static if (type == Type.number)
{
return this.value_.number;
}
else static if (type == Type.identifier || type == Type.operator)
{
return this.value_.identifier;
}
else
{
static assert(false, "This type doesn't have a value");
}
}
/** /**
* Returns: The token position in the source text. * Returns: The token position in the source text.
*/ */
@property const(Position) position() const @nogc nothrow pure @safe @property const(Position) position() const @nogc nothrow pure @safe;
{
return this.position_;
}
} }
/** /**
@ -101,10 +61,10 @@ struct Token
*/ */
struct Source struct Source
{ {
char[] buffer; const(char)* buffer;
Position position; Position position;
this(char[] buffer) @nogc nothrow pure @safe this(const(char)* buffer) @nogc nothrow pure @safe
{ {
this.buffer = buffer; this.buffer = buffer;
} }
@ -113,7 +73,7 @@ struct Source
bool empty() @nogc nothrow pure @safe bool empty() @nogc nothrow pure @safe
{ {
return this.length == 0; return this.buffer is null || this.buffer[0] == '\0';
} }
char front() @nogc nothrow pure @safe char front() @nogc nothrow pure @safe
@ -122,133 +82,36 @@ struct Source
return this.buffer[0]; return this.buffer[0];
} }
void popFront() @nogc nothrow pure @safe void popFront() @nogc nothrow pure
in (!empty) in (!empty)
{ {
this.buffer = buffer[1 .. $]; ++this.buffer;
++this.position.column; ++this.position.column;
} }
void breakLine() @nogc nothrow pure @safe void breakLine() @nogc nothrow pure
in (!empty) in (!empty)
{ {
this.buffer = buffer[1 .. $]; ++this.buffer;
++this.position.line; ++this.position.line;
this.position.column = 1; this.position.column = 1;
} }
@property size_t length() const @nogc nothrow pure @safe @property size_t length() const @nogc nothrow pure
{ {
return this.buffer.length; return strlen(this.buffer);
} }
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 const(char)[] opSlice(size_t i, size_t j) @nogc nothrow pure
in
{
assert(i <= j);
assert(j <= length);
}
do
{ {
return this.buffer[i .. j]; return this.buffer[i .. j];
} }
} }
Result!(Array!Token) lex(char[] buffer) @nogc extern(C++, "elna")
{ Token* lex(const(char)* buffer, CompileError* compileError, size_t* length) @nogc;
Array!Token tokens;
auto source = Source(buffer);
while (!source.empty)
{
if (source.front == ' ')
{
source.popFront;
}
else if (source.front >= '0' && source.front <= '9') // Multi-digit.
{
tokens.insertBack(Token(Token.Type.number, source.front - '0', source.position));
source.popFront;
}
else if (source.front == '=')
{
tokens.insertBack(Token(Token.Type.equals, source.position));
source.popFront;
}
else if (source.front == '(')
{
tokens.insertBack(Token(Token.Type.leftParen, source.position));
source.popFront;
}
else if (source.front == ')')
{
tokens.insertBack(Token(Token.Type.rightParen, source.position));
source.popFront;
}
else if (source.front == ';')
{
tokens.insertBack(Token(Token.Type.semicolon, source.position));
source.popFront;
}
else if (source.front == ',')
{
tokens.insertBack(Token(Token.Type.comma, source.position));
source.popFront;
}
else if (source.front == '!')
{
tokens.insertBack(Token(Token.Type.bang, source.position));
source.popFront;
}
else if (source.front == '.')
{
tokens.insertBack(Token(Token.Type.dot, source.position));
source.popFront;
}
else if (isalpha(source.front))
{
size_t i = 1;
while (i < source.length && isalpha(source[i]))
{
++i;
}
if (source[0 .. i] == "const")
{
tokens.insertBack(Token(Token.Type.let, source.position));
}
else if (source[0 .. i] == "var")
{
tokens.insertBack(Token(Token.Type.var, source.position));
}
else
{
auto identifier = String(source[0 .. i]);
tokens.insertBack(Token(Token.Type.identifier, identifier, source.position));
}
source.popFrontN(i);
}
else if (source.front == '+' || source.front == '-')
{
String operator;
operator.insertBack(source.front);
tokens.insertBack(Token(Token.Type.operator, operator, source.position));
source.popFront;
}
else if (source.front == '\n')
{
source.breakLine;
}
else
{
return typeof(return)("Unexptected next character", source.position);
}
}
return typeof(return)(tokens);
}

View File

@ -1,11 +1,12 @@
module elna.parser; module elna.parser;
import core.stdc.string;
import elna.lexer; import elna.lexer;
import elna.result; import elna.result;
import tanya.container.array; import tanya.container.array;
import tanya.container.string; import tanya.container.string;
import tanya.memory.allocator; import tanya.memory.allocator;
import std.array;
/** /**
* Parser visitor. * Parser visitor.
@ -14,7 +15,6 @@ interface ParserVisitor(Mapping)
{ {
Mapping.Node visit(Node) @nogc; Mapping.Node visit(Node) @nogc;
Mapping.Definition visit(Definition) @nogc; Mapping.Definition visit(Definition) @nogc;
Mapping.VariableDeclaration visit(VariableDeclaration) @nogc;
Mapping.Statement visit(Statement) @nogc; Mapping.Statement visit(Statement) @nogc;
Mapping.BangStatement visit(BangStatement) @nogc; Mapping.BangStatement visit(BangStatement) @nogc;
Mapping.Block visit(Block) @nogc; Mapping.Block visit(Block) @nogc;
@ -49,19 +49,6 @@ class Definition : Node
} }
} }
/**
* Variable declaration.
*/
class VariableDeclaration : Node
{
String identifier;
Mapping.VariableDeclaration accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
{
return visitor.visit(this);
}
}
abstract class Statement : Node abstract class Statement : Node
{ {
Mapping.Statement accept(Mapping)(ParserVisitor!Mapping visitor) @nogc Mapping.Statement accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
@ -83,7 +70,6 @@ class BangStatement : Statement
class Block : Node class Block : Node
{ {
Array!Definition definitions; Array!Definition definitions;
Array!VariableDeclaration variableDeclarations;
Statement statement; Statement statement;
Mapping.Block accept(Mapping)(ParserVisitor!Mapping visitor) @nogc Mapping.Block accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
@ -120,6 +106,7 @@ class Variable : Expression
} }
} }
extern(C++, "elna")
enum BinaryOperator enum BinaryOperator
{ {
sum, sum,
@ -155,24 +142,24 @@ class BinaryExpression : Expression
} }
} }
private Result!Expression parseFactor(ref Array!Token.Range tokens) @nogc private Result!Expression parseFactor(ref Token[] tokens) @nogc
in (!tokens.empty, "Expected factor, got end of stream") in (!tokens.empty, "Expected factor, got end of stream")
{ {
if (tokens.front.ofType(Token.Type.identifier)) if (tokens.front.of() == Token.Type.identifier)
{ {
auto variable = defaultAllocator.make!Variable; auto variable = defaultAllocator.make!Variable;
variable.identifier = tokens.front.value!(Token.Type.identifier); variable.identifier = tokens.front.identifier()[0 .. strlen(tokens.front.identifier())];
tokens.popFront; tokens.popFront;
return Result!Expression(variable); return Result!Expression(variable);
} }
else if (tokens.front.ofType(Token.Type.number)) else if (tokens.front.of() == Token.Type.number)
{ {
auto number = defaultAllocator.make!Number; auto number = defaultAllocator.make!Number;
number.value = tokens.front.value!(Token.Type.number); number.value = tokens.front.number();
tokens.popFront; tokens.popFront;
return Result!Expression(number); return Result!Expression(number);
} }
else if (tokens.front.ofType(Token.Type.leftParen)) else if (tokens.front.of() == Token.Type.leftParen)
{ {
tokens.popFront; tokens.popFront;
@ -184,20 +171,20 @@ in (!tokens.empty, "Expected factor, got end of stream")
return Result!Expression("Expected a factor", tokens.front.position); return Result!Expression("Expected a factor", tokens.front.position);
} }
private Result!Expression parseTerm(ref Array!(Token).Range tokens) @nogc private Result!Expression parseTerm(ref Token[] tokens) @nogc
{ {
return parseFactor(tokens); return parseFactor(tokens);
} }
private Result!Expression parseExpression(ref Array!(Token).Range tokens) @nogc private Result!Expression parseExpression(ref Token[] tokens) @nogc
in (!tokens.empty, "Expected expression, got end of stream") in (!tokens.empty, "Expected expression, got end of stream")
{ {
auto term = parseTerm(tokens); auto term = parseTerm(tokens);
if (!term.valid || tokens.empty || !tokens.front.ofType(Token.Type.operator)) if (!term.valid || tokens.empty || tokens.front.of() != Token.Type.operator)
{ {
return term; return term;
} }
auto operator = tokens.front.value!(Token.Type.operator); auto operator = String(tokens.front.identifier()[0 .. strlen(tokens.front.identifier())]);
tokens.popFront; tokens.popFront;
auto expression = parseExpression(tokens); auto expression = parseExpression(tokens);
@ -215,19 +202,19 @@ in (!tokens.empty, "Expected expression, got end of stream")
} }
} }
private Result!Definition parseDefinition(ref Array!Token.Range tokens) @nogc private Result!Definition parseDefinition(ref Token[] tokens) @nogc
in (!tokens.empty, "Expected definition, got end of stream") in (!tokens.empty, "Expected definition, got end of stream")
{ {
auto definition = defaultAllocator.make!Definition; auto definition = defaultAllocator.make!Definition;
definition.identifier = tokens.front.value!(Token.Type.identifier); // Copy. definition.identifier = tokens.front.identifier()[0 .. strlen(tokens.front.identifier())]; // Copy.
tokens.popFront(); tokens.popFront();
tokens.popFront(); // Skip the equals sign. tokens.popFront(); // Skip the equals sign.
if (tokens.front.ofType(Token.Type.number)) if (tokens.front.of() == Token.Type.number)
{ {
auto number = defaultAllocator.make!Number; auto number = defaultAllocator.make!Number;
number.value = tokens.front.value!(Token.Type.number); number.value = tokens.front.number();
definition.number = number; definition.number = number;
tokens.popFront; tokens.popFront;
return Result!Definition(definition); return Result!Definition(definition);
@ -235,10 +222,10 @@ in (!tokens.empty, "Expected definition, got end of stream")
return Result!Definition("Expected a number", tokens.front.position); return Result!Definition("Expected a number", tokens.front.position);
} }
private Result!Statement parseStatement(ref Array!Token.Range tokens) @nogc private Result!Statement parseStatement(ref Token[] tokens) @nogc
in (!tokens.empty, "Expected block, got end of stream") in (!tokens.empty, "Expected block, got end of stream")
{ {
if (tokens.front.ofType(Token.Type.bang)) if (tokens.front.of() == Token.Type.bang)
{ {
tokens.popFront; tokens.popFront;
auto statement = defaultAllocator.make!BangStatement; auto statement = defaultAllocator.make!BangStatement;
@ -256,7 +243,7 @@ in (!tokens.empty, "Expected block, got end of stream")
return Result!Statement("Expected ! statement", tokens.front.position); return Result!Statement("Expected ! statement", tokens.front.position);
} }
private Result!(Array!Definition) parseDefinitions(ref Array!Token.Range tokens) @nogc private Result!(Array!Definition) parseDefinitions(ref Token[] tokens) @nogc
in (!tokens.empty, "Expected definition, got end of stream") in (!tokens.empty, "Expected definition, got end of stream")
{ {
tokens.popFront; // Skip const. tokens.popFront; // Skip const.
@ -271,11 +258,11 @@ in (!tokens.empty, "Expected definition, got end of stream")
return typeof(return)(definition.error.get); return typeof(return)(definition.error.get);
} }
definitions.insertBack(definition.result); definitions.insertBack(definition.result);
if (tokens.front.ofType(Token.Type.semicolon)) if (tokens.front.of() == Token.Type.semicolon)
{ {
break; break;
} }
if (tokens.front.ofType(Token.Type.comma)) if (tokens.front.of() == Token.Type.comma)
{ {
tokens.popFront; tokens.popFront;
} }
@ -284,49 +271,11 @@ in (!tokens.empty, "Expected definition, got end of stream")
return typeof(return)(definitions); return typeof(return)(definitions);
} }
private Result!(Array!VariableDeclaration) parseVariableDeclarations(ref Array!Token.Range tokens) @nogc private Result!Block parseBlock(ref Token[] tokens) @nogc
in (!tokens.empty, "Expected variable declarations, got end of stream")
{
tokens.popFront; // Skip var.
Array!VariableDeclaration variableDeclarations;
while (!tokens.empty)
{
auto currentToken = tokens.front;
if (currentToken.ofType(Token.Type.identifier))
{
auto variableDeclaration = defaultAllocator.make!VariableDeclaration;
variableDeclaration.identifier = currentToken.value!(Token.Type.identifier);
variableDeclarations.insertBack(variableDeclaration);
tokens.popFront;
}
else
{
return typeof(return)("Expected variable name", tokens.front.position);
}
if (tokens.empty)
{
return typeof(return)("Expected \";\" or \",\" name", currentToken.position);
}
if (tokens.front.ofType(Token.Type.semicolon))
{
break;
}
if (tokens.front.ofType(Token.Type.comma))
{
tokens.popFront;
}
}
return typeof(return)(variableDeclarations);
}
private Result!Block parseBlock(ref Array!Token.Range tokens) @nogc
in (!tokens.empty, "Expected block, got end of stream") in (!tokens.empty, "Expected block, got end of stream")
{ {
auto block = defaultAllocator.make!Block; auto block = defaultAllocator.make!Block;
if (tokens.front.ofType(Token.Type.let)) if (tokens.front.of() == Token.Type.let)
{ {
auto constDefinitions = parseDefinitions(tokens); auto constDefinitions = parseDefinitions(tokens);
if (constDefinitions.valid) if (constDefinitions.valid)
@ -339,19 +288,6 @@ in (!tokens.empty, "Expected block, got end of stream")
} }
tokens.popFront; tokens.popFront;
} }
if (tokens.front.ofType(Token.Type.var))
{
auto variableDeclarations = parseVariableDeclarations(tokens);
if (variableDeclarations.valid)
{
block.variableDeclarations = variableDeclarations.result;
}
else
{
return Result!Block(variableDeclarations.error.get);
}
tokens.popFront;
}
auto statement = parseStatement(tokens); auto statement = parseStatement(tokens);
if (statement.valid) if (statement.valid)
{ {
@ -365,7 +301,7 @@ in (!tokens.empty, "Expected block, got end of stream")
return Result!Block(block); return Result!Block(block);
} }
Result!Block parse(ref Array!Token tokenStream) @nogc Result!Block parse(Token[] tokenStream) @nogc
{ {
auto tokens = tokenStream[]; auto tokens = tokenStream[];
return parseBlock(tokens); return parseBlock(tokens);

View File

@ -7,6 +7,7 @@ import tanya.container.string;
/** /**
* Position in the source text. * Position in the source text.
*/ */
extern(C++, "elna")
struct Position struct Position
{ {
/// Line. /// Line.
@ -16,9 +17,10 @@ struct Position
size_t column = 1; size_t column = 1;
} }
extern(C++, "elna")
struct CompileError struct CompileError
{ {
private string message_; private const(char)* message_;
private Position position_; private Position position_;
@ -29,29 +31,16 @@ struct CompileError
* message = Error text. * message = Error text.
* position = Error position in the source text. * position = Error position in the source text.
*/ */
this(string message, Position position) @nogc nothrow pure @safe this(const(char)* message, const Position position) @nogc nothrow pure @safe;
{
this.message_ = message;
this.position_ = position;
}
/// Error text. /// Error text.
@property string message() const @nogc nothrow pure @safe @property const(char)* what() const @nogc nothrow pure @safe;
{
return this.message_;
}
/// Error line in the source text. /// Error line in the source text.
@property size_t line() const @nogc nothrow pure @safe @property size_t line() const @nogc nothrow pure @safe;
{
return this.position_.line;
}
/// Error column in the source text. /// Error column in the source text.
@property size_t column() const @nogc nothrow pure @safe @property size_t column() const @nogc nothrow pure @safe;
{
return this.position_.column;
}
} }
struct Result(T) struct Result(T)
@ -65,7 +54,7 @@ struct Result(T)
this.error = typeof(this.error).init; this.error = typeof(this.error).init;
} }
this(string message, Position position) this(const(char)* message, Position position)
{ {
this.result = T.init; this.result = T.init;
this.error = CompileError(message, position); this.error = CompileError(message, position);
@ -85,16 +74,18 @@ struct Result(T)
} }
} }
struct Reference extern(C++, "elna")
enum Target
{ {
enum Target
{
text, text,
high20, high20,
lower12i lower12i
} }
String name; extern(C++, "elna")
struct Reference
{
const(char)* name;
size_t offset; size_t offset;
Target target; Target target;
} }

View File

@ -1,14 +1,12 @@
module elna.riscv; module elna.riscv;
import core.stdc.stdlib; import core.stdc.stdlib;
import elna.extended;
import elna.ir; import elna.ir;
import elna.result; import elna.result;
import std.algorithm;
import std.typecons;
import tanya.container.array; import tanya.container.array;
import tanya.container.string; import tanya.container.string;
extern(C++, "elna")
enum XRegister : ubyte enum XRegister : ubyte
{ {
zero = 0, zero = 0,
@ -45,6 +43,7 @@ enum XRegister : ubyte
t6 = 31, t6 = 31,
} }
extern(C++, "elna")
enum Funct3 : ubyte enum Funct3 : ubyte
{ {
addi = 0b000, addi = 0b000,
@ -92,18 +91,21 @@ enum Funct3 : ubyte
jalr = 0b000, jalr = 0b000,
} }
extern(C++, "elna")
enum Funct12 : ubyte enum Funct12 : ubyte
{ {
ecall = 0b000000000000, ecall = 0b000000000000,
ebreak = 0b000000000001, ebreak = 0b000000000001,
} }
extern(C++, "elna")
enum Funct7 : ubyte enum Funct7 : ubyte
{ {
none = 0, none = 0,
sub = 0b0100000 sub = 0b0100000
} }
extern(C++, "elna")
enum BaseOpcode : ubyte enum BaseOpcode : ubyte
{ {
opImm = 0b0010011, opImm = 0b0010011,
@ -119,243 +121,61 @@ enum BaseOpcode : ubyte
system = 0b1110011, system = 0b1110011,
} }
extern(C++, "elna")
struct Instruction struct Instruction
{ {
private uint instruction; private uint instruction;
this(BaseOpcode opcode) @nogc this(BaseOpcode opcode) @nogc;
{
this.instruction = opcode;
}
@disable this(); @disable this();
ref Instruction i(XRegister rd, Funct3 funct3, XRegister rs1, uint immediate) ref Instruction i(XRegister rd, Funct3 funct3, XRegister rs1, uint immediate)
return scope @nogc return scope @nogc;
{
this.instruction |= (rd << 7)
| (funct3 << 12)
| (rs1 << 15)
| (immediate << 20);
return this;
}
ref Instruction s(uint imm1, Funct3 funct3, XRegister rs1, XRegister rs2) ref Instruction s(uint imm1, Funct3 funct3, XRegister rs1, XRegister rs2)
return scope @nogc return scope @nogc;
{
this.instruction |= ((imm1 & 0b11111) << 7)
| (funct3 << 12)
| (rs1 << 15)
| (rs2 << 20)
| ((imm1 & 0b111111100000) << 20);
return this;
}
ref Instruction r(XRegister rd, Funct3 funct3, XRegister rs1, XRegister rs2, Funct7 funct7 = Funct7.none) ref Instruction r(XRegister rd, Funct3 funct3, XRegister rs1, XRegister rs2, Funct7 funct7 = Funct7.none)
return scope @nogc return scope @nogc;
{
this.instruction |= (rd << 7)
| (funct3 << 12)
| (rs1 << 15)
| (rs2 << 20)
| (funct7 << 25);
return this;
}
ref Instruction u(XRegister rd, uint imm) ref Instruction u(XRegister rd, uint imm)
return scope @nogc return scope @nogc;
{
this.instruction |= (rd << 7) | (imm << 12);
return this; ubyte* encode() return scope @nogc;
}
ubyte* encode() return scope @nogc
{
return cast(ubyte*) (&this.instruction);
}
} }
extern(C++, "elna")
class RiscVVisitor : IRVisitor class RiscVVisitor : IRVisitor
{ {
Array!Instruction instructions; Instruction *instructions;
size_t instructionsLength;
bool registerInUse; bool registerInUse;
uint variableCounter = 1; uint variableCounter = 1;
Array!Reference references; Reference[3] references;
override void visit(Node) @nogc override void visit(Node) @nogc;
{ override void visit(Definition definition) @nogc;
} override void visit(Operand operand) @nogc;
override void visit(Variable variable) @nogc;
override void visit(Definition definition) @nogc override void visit(Number number) @nogc;
{ override void visit(BinaryExpression expression) @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, -stackSize)
);
this.instructions.insertBack(
Instruction(BaseOpcode.store)
.s(stackSize - 4, Funct3.sw, XRegister.sp, XRegister.s0)
);
this.instructions.insertBack(
Instruction(BaseOpcode.store)
.s(stackSize - 8, Funct3.sw, XRegister.sp, XRegister.ra)
);
this.instructions.insertBack(
Instruction(BaseOpcode.opImm)
.i(XRegister.s0, Funct3.addi, XRegister.sp, stackSize)
);
foreach (statement; definition.statements[])
{
statement.accept(this);
}
foreach (variableDeclaration; definition.variableDeclarations[])
{
variableDeclaration.accept(this);
}
this.registerInUse = true;
definition.result.accept(this);
this.registerInUse = false;
// Print the result.
this.instructions.insertBack(
Instruction(BaseOpcode.opImm)
.i(XRegister.a1, Funct3.addi, XRegister.a0, 0)
);
this.references.insertBack(Reference(String(".CL0"), instructions.length * 4, Reference.Target.high20));
this.instructions.insertBack(
Instruction(BaseOpcode.lui).u(XRegister.a5, 0)
);
this.references.insertBack(Reference(String(".CL0"), instructions.length * 4, Reference.Target.lower12i));
this.instructions.insertBack(
Instruction(BaseOpcode.opImm).i(XRegister.a0, Funct3.addi, XRegister.a5, 0)
);
this.references.insertBack(Reference(String("printf"), instructions.length * 4, Reference.Target.text));
this.instructions.insertBack(
Instruction(BaseOpcode.auipc).u(XRegister.ra, 0)
);
this.instructions.insertBack(
Instruction(BaseOpcode.jalr)
.i(XRegister.ra, Funct3.jalr, XRegister.ra, 0)
);
// Set the return value (0).
this.instructions.insertBack(
Instruction(BaseOpcode.op)
.r(XRegister.a0, Funct3.and, XRegister.zero, XRegister.zero)
);
// Epilogue.
this.instructions.insertBack(
Instruction(BaseOpcode.load)
.i(XRegister.s0, Funct3.lw, XRegister.sp, stackSize - 4)
);
this.instructions.insertBack(
Instruction(BaseOpcode.load)
.i(XRegister.ra, Funct3.lw, XRegister.sp, stackSize - 8)
);
this.instructions.insertBack(
Instruction(BaseOpcode.opImm)
.i(XRegister.sp, Funct3.addi, XRegister.sp, stackSize)
);
this.instructions.insertBack(
Instruction(BaseOpcode.jalr)
.i(XRegister.zero, Funct3.jalr, XRegister.ra, 0)
);
}
override void visit(Operand operand) @nogc
{
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
{
const freeRegister = this.registerInUse ? XRegister.a0 : XRegister.t0;
// movl -x(%rbp), %eax; where x is a number.
this.instructions.insertBack(
Instruction(BaseOpcode.load)
.i(freeRegister, Funct3.lw, XRegister.sp,
cast(byte) (variable.counter * 4))
);
}
override void visit(VariableDeclaration) @nogc
{
}
override void visit(Number number) @nogc
{
const freeRegister = this.registerInUse ? XRegister.a0 : XRegister.t0;
this.instructions.insertBack(
Instruction(BaseOpcode.opImm) // movl $x, %eax; where $x is a number.
.i(freeRegister, Funct3.addi, XRegister.zero, number.value)
);
}
override void visit(BinaryExpression expression) @nogc
{
this.registerInUse = true;
expression.lhs.accept(this);
this.registerInUse = false;
expression.rhs.accept(this);
// Calculate the result and assign it to a variable on the stack.
final switch (expression.operator)
{
case BinaryOperator.sum:
this.instructions.insertBack(
Instruction(BaseOpcode.op)
.r(XRegister.a0, Funct3.add, XRegister.a0, XRegister.t0)
);
break;
case BinaryOperator.subtraction:
this.instructions.insertBack(
Instruction(BaseOpcode.op)
.r(XRegister.a0, Funct3.sub, XRegister.a0, XRegister.t0, Funct7.sub)
);
break;
}
this.instructions.insertBack( // movl %eax, -x(%rbp); where x is a number.
Instruction(BaseOpcode.store)
.s(cast(uint) (this.variableCounter * 4), Funct3.sw, XRegister.sp, XRegister.a0)
);
++this.variableCounter;
}
} }
Symbol writeNext(Definition ast) @nogc Symbol writeNext(Definition ast) @nogc
{ {
Array!Instruction instructions; Array!Instruction instructions;
Array!Reference references;
auto visitor = cast(RiscVVisitor) malloc(__traits(classInstanceSize, RiscVVisitor)); auto visitor = cast(RiscVVisitor) malloc(__traits(classInstanceSize, RiscVVisitor));
(cast(void*) visitor)[0 .. __traits(classInstanceSize, RiscVVisitor)] = __traits(initSymbol, RiscVVisitor)[]; (cast(void*) visitor)[0 .. __traits(classInstanceSize, RiscVVisitor)] = __traits(initSymbol, RiscVVisitor)[];
scope (exit) scope (exit)
{ {
visitor.__xdtor();
free(cast(void*) visitor); free(cast(void*) visitor);
} }
visitor.visit(ast); visitor.visit(ast);
auto program = Symbol(String("main")); auto program = Symbol(String("main"));
program.symbols = move(visitor.references); program.symbols = Array!Reference(visitor.references[]);
foreach (ref instruction; visitor.instructions) foreach (ref instruction; visitor.instructions[0 .. visitor.instructionsLength])
{ {
program.text.insertBack(instruction.encode[0 .. uint.sizeof]); program.text.insertBack(instruction.encode[0 .. uint.sizeof]);
} }

53
source/ir.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "elna/ir.hpp"
namespace elna::ir
{
/**
* AST node.
*/
void Node::accept(IRVisitor *)
{
}
void Definition::accept(IRVisitor *visitor)
{
visitor->visit(this);
}
void Operand::accept(IRVisitor *visitor)
{
visitor->visit(this);
}
void Number::accept(IRVisitor *visitor)
{
visitor->visit(this);
}
void Variable::accept(IRVisitor *visitor)
{
visitor->visit(this);
}
BinaryExpression::BinaryExpression(Operand *lhs, Operand *rhs, BinaryOperator _operator)
{
this->lhs = lhs;
this->rhs = rhs;
this->_operator = _operator;
}
void BinaryExpression::accept(IRVisitor *visitor)
{
visitor->visit(this);
}
BangExpression::BangExpression(Operand *operand)
{
this->operand = operand;
}
void BangExpression::accept(IRVisitor *visitor)
{
visitor->visit(this);
}
}

View File

@ -14,18 +14,18 @@ namespace elna
source::const_iterator source::end() const source::const_iterator source::end() const
{ {
source_position end_position{ 0, 0 }; Position end_position{ 0, 0 };
return source::const_iterator(std::cend(m_buffer), end_position); return source::const_iterator(std::cend(m_buffer), end_position);
} }
source::const_iterator::const_iterator(std::string::const_iterator buffer, source::const_iterator::const_iterator(std::string::const_iterator buffer,
const source_position start_position) const Position start_position)
: m_buffer(buffer), m_position(start_position) : m_buffer(buffer), m_position(start_position)
{ {
} }
const source_position& source::const_iterator::position() const noexcept const Position& source::const_iterator::position() const noexcept
{ {
return this->m_position; return this->m_position;
} }
@ -73,29 +73,109 @@ namespace elna
return !(*this == that); return !(*this == that);
} }
token::token(const type of, source::const_iterator begin, source::const_iterator end) Token::Token(const Type of, const char *value, Position position)
: m_type(of), m_value(begin, end) : m_type(of), m_position(position)
{
std::size_t value_length = strlen(value);
char *buffer = reinterpret_cast<char *>(malloc(value_length + 1));
std::memcpy(buffer, value, value_length);
buffer[value_length] = 0;
m_value.identifier = buffer;
}
Token::Token(const Type of, std::int32_t number, Position position)
: m_type(of), m_position(position)
{
m_value.number = number;
}
Token::Token(const Type of, Position position)
: m_type(of), m_position(position)
{ {
} }
token::type token::of() const noexcept Token::Token(const Token& that)
: m_type(that.of()), m_position(that.position())
{
*this = that;
}
Token::Token(Token&& that)
: m_type(that.of()), m_position(that.position())
{
*this = std::move(that);
}
Token::~Token()
{
if (m_type == TOKEN_IDENTIFIER || m_type == TOKEN_OPERATOR)
{
std::free(const_cast<char*>(m_value.identifier));
}
}
Token& Token::operator=(const Token& that)
{
m_type = that.of();
m_position = that.position();
if (that.of() == TOKEN_IDENTIFIER || that.of() == TOKEN_OPERATOR)
{
std::size_t value_length = strlen(that.identifier());
char *buffer = reinterpret_cast<char *>(malloc(value_length + 1));
std::memcpy(buffer, that.identifier(), value_length);
buffer[value_length] = 0;
m_value.identifier = buffer;
}
else if (that.of() == TOKEN_NUMBER)
{
m_value.number = that.number();
}
return *this;
}
Token& Token::operator=(Token&& that)
{
m_type = that.of();
m_position = that.position();
if (that.of() == TOKEN_IDENTIFIER || that.of() == TOKEN_OPERATOR)
{
m_value.identifier = that.identifier();
that.m_value.identifier = nullptr;
}
else if (that.of() == TOKEN_NUMBER)
{
m_value.number = that.number();
}
return *this;
}
Token::Type Token::of() const noexcept
{ {
return m_type; return m_type;
} }
const token::value& token::identifier() const noexcept const char *Token::identifier() const noexcept
{ {
return m_value; return m_value.identifier;
} }
const source_position& token::position() const noexcept std::int32_t Token::number() const noexcept
{
return m_value.number;
}
const Position& Token::position() const noexcept
{ {
return m_position; return m_position;
} }
result<std::vector<token>> lex(const std::string& buffer) Token *lex(const char *buffer, CompileError *compile_error, std::size_t *length)
{ {
std::vector<token> tokens; std::vector<Token> tokens;
source input{ buffer }; source input{ buffer };
for (auto iterator = input.begin(); iterator != input.end();) for (auto iterator = input.begin(); iterator != input.end();)
@ -103,23 +183,88 @@ namespace elna
if (*iterator == ' ' || *iterator == '\n') if (*iterator == ' ' || *iterator == '\n')
{ {
} }
else if (std::isgraph(*iterator)) else if (std::isdigit(*iterator))
{ {
auto current_position = iterator; tokens.emplace_back(
do Token::TOKEN_NUMBER,
{ static_cast<std::int32_t>(*iterator - '0'),
++current_position; iterator.position()
);
} }
while (current_position != input.end() && std::isgraph(*current_position)); else if (*iterator == '=')
token new_token{ token::type::word, iterator, current_position }; {
tokens.emplace_back(Token::TOKEN_EQUALS, iterator.position());
tokens.push_back(new_token); }
iterator = current_position; else if (*iterator == '(')
{
tokens.emplace_back(Token::TOKEN_LEFT_PAREN, iterator.position());
}
else if (*iterator == ')')
{
tokens.emplace_back(Token::TOKEN_RIGHT_PAREN, iterator.position());
}
else if (*iterator == ';')
{
tokens.emplace_back(Token::TOKEN_SEMICOLON, iterator.position());
}
else if (*iterator == ',')
{
tokens.emplace_back(Token::TOKEN_COMMA, iterator.position());
}
else if (*iterator == '!')
{
tokens.emplace_back(Token::TOKEN_BANG, iterator.position());
}
else if (*iterator == '.')
{
tokens.emplace_back(Token::TOKEN_DOT, iterator.position());
}
else if (std::isalpha(*iterator))
{
std::string word;
auto i = iterator;
while (i != input.end() && std::isalpha(*i))
{
word.push_back(*i);
++i;
}
if (word == "const")
{
tokens.emplace_back(Token::TOKEN_LET, iterator.position());
}
else if (word == "var")
{
tokens.emplace_back(Token::TOKEN_VAR, iterator.position());
}
else
{
tokens.emplace_back(Token::TOKEN_IDENTIFIER, word.c_str(), iterator.position());
}
iterator = i;
continue; continue;
} }
else if (*iterator == '+' || *iterator == '-')
{
std::string _operator{ *iterator };
tokens.emplace_back(Token::TOKEN_OPERATOR, _operator.c_str(), iterator.position());
}
else
{
*compile_error = CompileError("Unexpected next character", iterator.position());
return nullptr;
}
++iterator; ++iterator;
} }
Token *target = reinterpret_cast<Token *>(malloc(tokens.size() * sizeof(Token) + sizeof(Token)));
int i = 0;
for (auto& token : tokens)
{
target[i] = std::move(token);
++i;
}
*length = i;
return tokens; return target;
} }
} }

View File

@ -2,23 +2,23 @@
namespace elna namespace elna
{ {
compile_error::compile_error(const char *message, const source_position position) noexcept CompileError::CompileError(const char *message, const Position position) noexcept
{ {
this->message = message; this->message = message;
this->position = position; this->position = position;
} }
char const *compile_error::what() const noexcept char const *CompileError::what() const noexcept
{ {
return this->message; return this->message;
} }
std::size_t compile_error::line() const noexcept std::size_t CompileError::line() const noexcept
{ {
return this->position.line; return this->position.line;
} }
std::size_t compile_error::column() const noexcept std::size_t CompileError::column() const noexcept
{ {
return this->position.column; return this->position.column;
} }

191
source/riscv.cpp Normal file
View File

@ -0,0 +1,191 @@
#include "elna/parser.hpp"
#include "elna/riscv.hpp"
#include <type_traits>
namespace elna
{
Instruction::Instruction(BaseOpcode opcode)
{
this->instruction = static_cast<std::underlying_type<BaseOpcode>::type>(opcode);
}
Instruction& Instruction::i(XRegister rd, Funct3 funct3, XRegister rs1, std::uint32_t immediate)
{
this->instruction |= (static_cast<std::underlying_type<XRegister>::type>(rd) << 7)
| (static_cast<std::underlying_type<Funct3>::type>(funct3) << 12)
| (static_cast<std::underlying_type<XRegister>::type>(rs1) << 15)
| (immediate << 20);
return *this;
}
Instruction& Instruction::s(std::uint32_t imm1, Funct3 funct3, XRegister rs1, XRegister rs2)
{
this->instruction |= ((imm1 & 0b11111) << 7)
| (static_cast<std::underlying_type<Funct3>::type>(funct3) << 12)
| (static_cast<std::underlying_type<XRegister>::type>(rs1) << 15)
| (static_cast<std::underlying_type<XRegister>::type>(rs2) << 20)
| ((imm1 & 0b111111100000) << 20);
return *this;
}
Instruction& Instruction::r(XRegister rd, Funct3 funct3, XRegister rs1, XRegister rs2, Funct7 funct7)
{
this->instruction |= (static_cast<std::underlying_type<XRegister>::type>(rd) << 7)
| (static_cast<std::underlying_type<Funct3>::type>(funct3) << 12)
| (static_cast<std::underlying_type<XRegister>::type>(rs1) << 15)
| (static_cast<std::underlying_type<XRegister>::type>(rs2) << 20)
| (static_cast<std::underlying_type<Funct7>::type>(funct7) << 25);
return *this;
}
Instruction& Instruction::u(XRegister rd, std::uint32_t imm)
{
this->instruction |= (static_cast<std::underlying_type<XRegister>::type>(rd) << 7) | (imm << 12);
return *this;
}
std::uint8_t *Instruction::encode()
{
return reinterpret_cast<std::uint8_t *>(&this->instruction);
}
void RiscVVisitor::visit(ir::Node *)
{
}
void RiscVVisitor::visit(ir::Definition *definition)
{
const uint stackSize = static_cast<std::uint32_t>(definition->statementsLength * 4 + 12);
this->instructionsLength += 4;
this->instructions = reinterpret_cast<Instruction *>(
realloc(this->instructions, this->instructionsLength * sizeof(Instruction)));
// Prologue.
this->instructions[instructionsLength - 4] = Instruction(BaseOpcode::opImm)
.i(XRegister::sp, Funct3::addi, XRegister::sp, -stackSize);
this->instructions[instructionsLength - 3] = Instruction(BaseOpcode::store)
.s(stackSize - 4, Funct3::sw, XRegister::sp, XRegister::s0);
this->instructions[instructionsLength - 2] = Instruction(BaseOpcode::store)
.s(stackSize - 8, Funct3::sw, XRegister::sp, XRegister::ra);
this->instructions[instructionsLength - 1] = Instruction(BaseOpcode::opImm)
.i(XRegister::s0, Funct3::addi, XRegister::sp, stackSize);
for (std::size_t i = 0; i < definition->statementsLength; ++i)
{
definition->statements[i]->accept(this);
}
this->registerInUse = true;
definition->result->accept(this);
this->registerInUse = false;
this->instructions = reinterpret_cast<Instruction*>(
realloc(this->instructions, (this->instructionsLength + 10) * sizeof(Instruction)));
// Print the result.
this->instructions[instructionsLength++] = Instruction(BaseOpcode::opImm)
.i(XRegister::a1, Funct3::addi, XRegister::a0, 0);
this->references[0] = Reference();
this->references[0].name = ".CL0";
this->references[0].offset = instructionsLength * 4;
this->references[0].target = Target::high20;
this->instructions[instructionsLength++] = Instruction(BaseOpcode::lui).u(XRegister::a5, 0);
this->references[1] = Reference();
this->references[1].name = ".CL0";
this->references[1].offset = instructionsLength * 4;
this->references[1].target = Target::lower12i;
this->instructions[instructionsLength++] = Instruction(BaseOpcode::opImm)
.i(XRegister::a0, Funct3::addi, XRegister::a5, 0);
this->references[2] = Reference();
this->references[2].name = "printf";
this->references[2].offset = instructionsLength * 4;
this->references[2].target = Target::text;
this->instructions[instructionsLength++] = Instruction(BaseOpcode::auipc).u(XRegister::ra, 0);
this->instructions[instructionsLength++] = Instruction(BaseOpcode::jalr)
.i(XRegister::ra, Funct3::jalr, XRegister::ra, 0);
// Set the return value (0).
this->instructions[instructionsLength++] = Instruction(BaseOpcode::op)
.r(XRegister::a0, Funct3::_and, XRegister::zero, XRegister::zero);
// Epilogue.
this->instructions[instructionsLength++] = Instruction(BaseOpcode::load)
.i(XRegister::s0, Funct3::lw, XRegister::sp, stackSize - 4);
this->instructions[instructionsLength++] = Instruction(BaseOpcode::load)
.i(XRegister::ra, Funct3::lw, XRegister::sp, stackSize - 8);
this->instructions[instructionsLength++] = Instruction(BaseOpcode::opImm)
.i(XRegister::sp, Funct3::addi, XRegister::sp, stackSize);
this->instructions[instructionsLength++] = Instruction(BaseOpcode::jalr)
.i(XRegister::zero, Funct3::jalr, XRegister::ra, 0);
}
void RiscVVisitor::visit(ir::Operand *operand)
{
if (dynamic_cast<ir::Variable *>(operand) != nullptr)
{
return dynamic_cast<ir::Variable *>(operand)->accept(this);
}
if (dynamic_cast<ir::Number *>(operand) != nullptr)
{
return dynamic_cast<ir::Number *>(operand)->accept(this);
}
}
void RiscVVisitor::visit(ir::Variable *variable)
{
const auto freeRegister = this->registerInUse ? XRegister::a0 : XRegister::t0;
++this->instructionsLength;
this->instructions = reinterpret_cast<Instruction *>(
realloc(this->instructions, this->instructionsLength * sizeof(Instruction)));
// movl -x(%rbp), %eax; where x is a number.
this->instructions[instructionsLength - 1] = Instruction(BaseOpcode::load)
.i(freeRegister, Funct3::lw, XRegister::sp,
static_cast<std::int8_t>(variable->counter * 4));
}
void RiscVVisitor::visit(ir::Number *number)
{
const auto freeRegister = this->registerInUse ? XRegister::a0 : XRegister::t0;
++this->instructionsLength;
this->instructions = reinterpret_cast<Instruction *>(
realloc(this->instructions, this->instructionsLength * sizeof(Instruction)));
this->instructions[this->instructionsLength - 1] =
Instruction(BaseOpcode::opImm) // movl $x, %eax; where $x is a number.
.i(freeRegister, Funct3::addi, XRegister::zero, number->value);
}
void RiscVVisitor::visit(ir::BinaryExpression *expression)
{
this->registerInUse = true;
expression->lhs->accept(this);
this->registerInUse = false;
expression->rhs->accept(this);
this->instructionsLength += 2;
this->instructions = reinterpret_cast<Instruction *>(
realloc(this->instructions, this->instructionsLength * sizeof(Instruction)));
// Calculate the result and assign it to a variable on the stack.
switch (expression->_operator)
{
case BinaryOperator::sum:
this->instructions[instructionsLength - 2] = Instruction(BaseOpcode::op)
.r(XRegister::a0, Funct3::add, XRegister::a0, XRegister::t0);
break;
case BinaryOperator::subtraction:
this->instructions[instructionsLength - 2] = Instruction(BaseOpcode::op)
.r(XRegister::a0, Funct3::sub, XRegister::a0, XRegister::t0, Funct7::sub);
break;
}
this->instructions[instructionsLength - 1] = // movl %eax, -x(%rbp); where x is a number.
Instruction(BaseOpcode::store)
.s(static_cast<std::uint32_t>(this->variableCounter * 4), Funct3::sw, XRegister::sp, XRegister::a0);
++this->variableCounter;
}
}