From 4d46fc6b4d814f65debdf9108cd7cdd318f8f79d Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Wed, 28 Feb 2024 16:18:39 +0100 Subject: [PATCH] Print test summary --- CMakeLists.txt | 5 +- Rakefile | 75 +----------- dub.json | 4 +- include/elna/ir.hpp | 95 +++++++++++++++ include/elna/lexer.hpp | 57 ++++++--- include/elna/parser.hpp | 10 ++ include/elna/result.hpp | 24 +++- include/elna/riscv.hpp | 148 +++++++++++++++++++++++ source/elna/backend.d | 39 +++--- source/elna/ir.d | 104 +++++----------- source/elna/lexer.d | 209 ++++++-------------------------- source/elna/parser.d | 114 ++++-------------- source/elna/result.d | 45 +++---- source/elna/riscv.d | 228 ++++------------------------------- source/ir.cpp | 53 ++++++++ source/lexer.cpp | 187 ++++++++++++++++++++++++---- {shell => source}/result.cpp | 8 +- source/riscv.cpp | 191 +++++++++++++++++++++++++++++ 18 files changed, 890 insertions(+), 706 deletions(-) create mode 100644 include/elna/ir.hpp create mode 100644 include/elna/parser.hpp create mode 100644 include/elna/riscv.hpp create mode 100644 source/ir.cpp rename {shell => source}/result.cpp (51%) create mode 100644 source/riscv.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8a2223e..eac0f7f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,11 +12,14 @@ add_executable(elnsh shell/main.cpp shell/interactive.cpp include/elna/interactive.hpp shell/history.cpp include/elna/history.hpp shell/state.cpp include/elna/state.hpp - shell/result.cpp include/elna/result.hpp ) target_include_directories(elnsh PRIVATE include) add_library(elna 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) diff --git a/Rakefile b/Rakefile index c623b05..773d560 100644 --- a/Rakefile +++ b/Rakefile @@ -4,87 +4,16 @@ require 'open3' DFLAGS = ['--warn-no-deprecated', '-L/usr/lib64/gcc-12'] 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'] -directory 'build' +directory 'build/riscv' CLEAN.include 'build' 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| sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc') end +task default: 'build/riscv' 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 diff --git a/dub.json b/dub.json index 743f619..2e76980 100644 --- a/dub.json +++ b/dub.json @@ -5,5 +5,7 @@ "name": "elna", "targetType": "executable", "targetPath": "build/bin", - "mainSourceFile": "source/main.d" + "mainSourceFile": "source/main.d", + "libs": ["elna", "stdc++"], + "lflags": ["-Lbuild"] } diff --git a/include/elna/ir.hpp b/include/elna/ir.hpp new file mode 100644 index 0000000..d758a73 --- /dev/null +++ b/include/elna/ir.hpp @@ -0,0 +1,95 @@ +#pragma once + +#include "elna/parser.hpp" +#include +#include + +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; + }; +} diff --git a/include/elna/lexer.hpp b/include/elna/lexer.hpp index b9435d4..09ab8a6 100644 --- a/include/elna/lexer.hpp +++ b/include/elna/lexer.hpp @@ -14,10 +14,10 @@ namespace elna class const_iterator { std::string::const_iterator m_buffer; - source_position m_position; + Position m_position; const_iterator(std::string::const_iterator buffer, - const source_position position = source_position()); + const Position position = Position()); public: using iterator_category = std::forward_iterator_tag; @@ -26,7 +26,7 @@ namespace elna using pointer = const value_type *; using reference = const value_type&; - const source_position& position() const noexcept; + const Position& position() const noexcept; reference operator*() const noexcept; pointer operator->() const noexcept; @@ -43,38 +43,61 @@ namespace elna const_iterator end() const; private: - const std::string& m_buffer; + const std::string m_buffer; }; /** * Union type representing a single token. */ - struct token + struct Token { /** * 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. */ - 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; - const value& identifier() const noexcept; - const source_position& position() const noexcept; + Token& operator=(const Token& that); + Token& operator=(Token&& that); + + Type of() const noexcept; + const char *identifier() const noexcept; + std::int32_t number() const noexcept; + const Position& position() const noexcept; private: - std::string m_value; - source_position m_position; - type m_type; + Type m_type; + Value m_value; + Position m_position; }; /** @@ -82,5 +105,5 @@ namespace elna * * \return Tokens or error. */ - result> lex(const std::string& buffer); + Token *lex(const char *buffer, CompileError *compile_error, std::size_t *length); } diff --git a/include/elna/parser.hpp b/include/elna/parser.hpp new file mode 100644 index 0000000..322277d --- /dev/null +++ b/include/elna/parser.hpp @@ -0,0 +1,10 @@ +#pragma once + +namespace elna +{ + enum class BinaryOperator + { + sum, + subtraction + }; +} diff --git a/include/elna/result.hpp b/include/elna/result.hpp index 078b500..72dc647 100644 --- a/include/elna/result.hpp +++ b/include/elna/result.hpp @@ -8,7 +8,7 @@ namespace elna /** * Position in the source text. */ - struct source_position + struct Position { /// Line. std::size_t line = 1; @@ -20,18 +20,18 @@ namespace elna /** * A compilation error consists of an error message and position. */ - struct compile_error + struct CompileError { private: char const *message; - source_position position; + Position position; public: /** * @param message Error 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. const char *what() const noexcept; @@ -44,5 +44,19 @@ namespace elna }; template - using result = boost::outcome_v2::result; + using result = boost::outcome_v2::result; + + enum class Target + { + text, + high20, + lower12i + }; + + struct Reference + { + const char* name; + size_t offset; + Target target; + }; } diff --git a/include/elna/riscv.hpp b/include/elna/riscv.hpp new file mode 100644 index 0000000..5a01bf9 --- /dev/null +++ b/include/elna/riscv.hpp @@ -0,0 +1,148 @@ +#pragma once + +#include +#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; + }; +} diff --git a/source/elna/backend.d b/source/elna/backend.d index ec4ed7a..e2d67ac 100644 --- a/source/elna/backend.d +++ b/source/elna/backend.d @@ -2,6 +2,7 @@ module elna.backend; import core.stdc.stdio; import core.stdc.stdlib; +import core.stdc.string; import elna.elf; import elna.ir; import elna.extended; @@ -17,7 +18,7 @@ import tanya.container.array; import tanya.container.string; import tanya.memory.allocator; -private Nullable!String readSource(string source) @nogc +private char* readSource(string source) @nogc { enum size_t bufferSize = 255; auto sourceFilename = String(source); @@ -25,31 +26,39 @@ private Nullable!String readSource(string source) @nogc return readFile(sourceFilename).match!( (ErrorCode errorCode) { 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 { auto sourceText = readSource(inFile); - if (sourceText.isNull) + if (sourceText is null) { return 3; } - auto tokens = lex(sourceText.get.get); - if (!tokens.valid) + CompileError compileError = void; + 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.message.ptr); + printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.what); return 1; } - auto ast = parse(tokens.result); + auto ast = parse(tokens[0 .. tokensCount]); if (!ast.valid) { - auto compileError = ast.error.get; - printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.message.ptr); + compileError = ast.error.get; + printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.what); return 2; } auto transformVisitor = cast(TransformVisitor) malloc(__traits(classInstanceSize, TransformVisitor)); @@ -85,18 +94,18 @@ int generate(string inFile, ref String outputFilename) @nogc final switch (reference.target) { - case Reference.Target.text: + case Target.text: relocationEntry.r_info = R_RISCV_CALL; break; - case Reference.Target.high20: + case Target.high20: relocationEntry.r_info = R_RISCV_HI20; break; - case Reference.Target.lower12i: + case Target.lower12i: relocationEntry.r_info = R_RISCV_LO12_I; break; } - elf.relocate(reference.name, relocationEntry, relocationSub); + elf.relocate(String(reference.name[0 .. strlen(reference.name)]), relocationEntry, relocationSub); } elf.finish(); diff --git a/source/elna/ir.d b/source/elna/ir.d index 41d834c..4ff2288 100644 --- a/source/elna/ir.d +++ b/source/elna/ir.d @@ -1,5 +1,6 @@ module elna.ir; +import core.stdc.stdlib; import parser = elna.parser; import tanya.container.array; import tanya.container.hashtable; @@ -14,7 +15,6 @@ struct ASTMapping { alias Node = .Node; alias Definition = .Definition; - alias VariableDeclaration = .VariableDeclaration; alias Statement = .Operand; alias BangStatement = .Operand; alias Block = .Definition; @@ -28,20 +28,21 @@ struct ASTMapping /** * IR visitor. */ -interface IRVisitor +extern(C++, "elna", "ir") +abstract class IRVisitor { abstract void visit(Node) @nogc; abstract void visit(Definition) @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; } /** * AST node. */ +extern(C++, "elna", "ir") abstract class Node { abstract void accept(IRVisitor) @nogc; @@ -50,99 +51,69 @@ abstract class Node /** * Definition. */ +extern(C++, "elna", "ir") class Definition : Node { - char[] identifier; - Array!BinaryExpression statements; - Array!VariableDeclaration variableDeclarations; + BinaryExpression* statements; + size_t statementsLength; Operand result; - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } + override void accept(IRVisitor visitor) @nogc; } +extern(C++, "elna", "ir") abstract class Statement : Node { } +extern(C++, "elna", "ir") abstract class Operand : Node { - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } + override void accept(IRVisitor visitor) @nogc; } +extern(C++, "elna", "ir") class Number : Operand { int value; - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } + override void accept(IRVisitor visitor) @nogc; } +extern(C++, "elna", "ir") class Variable : Operand { size_t counter; - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } -} - -class VariableDeclaration : Node -{ - String identifier; - - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } + override void accept(IRVisitor visitor) @nogc; } +extern(C++, "elna", "ir") class BinaryExpression : Statement { Operand lhs, rhs; BinaryOperator operator; - this(Operand lhs, Operand rhs, BinaryOperator operator) - @nogc - { - this.lhs = lhs; - this.rhs = rhs; - this.operator = operator; - } + this(Operand lhs, Operand rhs, BinaryOperator operator) @nogc; - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } + override void accept(IRVisitor visitor) @nogc; } +extern(C++, "elna", "ir") class BangExpression : Statement { Operand operand; - this(Operand operand) - { - this.operand = operand; - } + this(Operand operand); - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } + override void accept(IRVisitor visitor) @nogc; } final class TransformVisitor : parser.ParserVisitor!ASTMapping { private HashTable!(String, int) constants; - private Array!BinaryExpression statements; + private BinaryExpression* statements; + private size_t statementsLength; ASTMapping.Node visit(parser.Node node) @nogc { @@ -154,11 +125,6 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping assert(false, "Not implemented"); } - ASTMapping.VariableDeclaration visit(parser.VariableDeclaration declaration) @nogc - { - assert(false, "Not implemented"); - } - ASTMapping.BangStatement visit(parser.BangStatement statement) @nogc { return statement.expression.accept(this); @@ -171,8 +137,7 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping target.result = block.statement.accept(this); target.statements = this.statements; - - target.variableDeclarations = transformVariableDeclarations(block.variableDeclarations); + target.statementsLength = this.statementsLength; return target; } @@ -217,10 +182,12 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping binaryExpression.rhs.accept(this), 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; - newVariable.counter = statements.length; + newVariable.counter = this.statementsLength; return newVariable; } @@ -250,19 +217,4 @@ final class TransformVisitor : parser.ParserVisitor!ASTMapping 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; - } } diff --git a/source/elna/lexer.d b/source/elna/lexer.d index 83fb873..45a641c 100644 --- a/source/elna/lexer.d +++ b/source/elna/lexer.d @@ -8,28 +8,29 @@ import std.range; import tanya.container.array; import tanya.container.string; +extern(C++, "elna") struct Token { - enum Type + enum Type : ushort { - number, - operator, - let, - identifier, - equals, - var, - semicolon, - leftParen, - rightParen, - bang, - dot, - comma, + number = 0, + operator = 1, + let = 2, + identifier = 3, + equals = 4, + var = 5, + semicolon = 6, + leftParen = 7, + rightParen = 8, + bang = 9, + dot = 10, + comma = 11, } union Value { int number; - String identifier; + const(char)* identifier; } private Type type; @@ -38,62 +39,21 @@ struct Token @disable this(); - this(Type type, Position position) @nogc nothrow pure @safe - { - this.type = type; - 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; - } + this(Type type, Position position) @nogc nothrow pure @safe; + this(Type type, int value, Position position) @nogc nothrow pure @trusted; + this(Type type, const(char)* value, Position position) @nogc nothrow; /** - * Params: - * type = Expected type. - * - * Returns: Whether this token is of the expected type. + * Returns: Expected token type. */ - bool ofType(Type type) const @nogc nothrow pure @safe - { - return this.type == type; - } - - @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"); - } - } + Type of() const @nogc nothrow pure @safe; + const(char)* identifier() const @nogc nothrow pure; + int number() const @nogc nothrow pure; /** * Returns: The token position in the source text. */ - @property const(Position) position() const @nogc nothrow pure @safe - { - return this.position_; - } + @property const(Position) position() const @nogc nothrow pure @safe; } /** @@ -101,10 +61,10 @@ struct Token */ struct Source { - char[] buffer; + const(char)* buffer; Position position; - this(char[] buffer) @nogc nothrow pure @safe + this(const(char)* buffer) @nogc nothrow pure @safe { this.buffer = buffer; } @@ -113,7 +73,7 @@ struct Source 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 @@ -122,133 +82,36 @@ struct Source return this.buffer[0]; } - void popFront() @nogc nothrow pure @safe + void popFront() @nogc nothrow pure in (!empty) { - this.buffer = buffer[1 .. $]; + ++this.buffer; ++this.position.column; } - void breakLine() @nogc nothrow pure @safe + void breakLine() @nogc nothrow pure in (!empty) { - this.buffer = buffer[1 .. $]; + ++this.buffer; ++this.position.line; 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 - in (index < length) + char opIndex(size_t index) @nogc nothrow pure { return this.buffer[index]; } - char[] opSlice(size_t i, size_t j) @nogc nothrow pure @safe - in - { - assert(i <= j); - assert(j <= length); - } - do + const(char)[] opSlice(size_t i, size_t j) @nogc nothrow pure { return this.buffer[i .. j]; } } -Result!(Array!Token) lex(char[] buffer) @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); -} +extern(C++, "elna") +Token* lex(const(char)* buffer, CompileError* compileError, size_t* length) @nogc; diff --git a/source/elna/parser.d b/source/elna/parser.d index c0bf048..18e31bd 100644 --- a/source/elna/parser.d +++ b/source/elna/parser.d @@ -1,11 +1,12 @@ module elna.parser; +import core.stdc.string; import elna.lexer; import elna.result; import tanya.container.array; import tanya.container.string; import tanya.memory.allocator; - +import std.array; /** * Parser visitor. @@ -14,7 +15,6 @@ interface ParserVisitor(Mapping) { Mapping.Node visit(Node) @nogc; Mapping.Definition visit(Definition) @nogc; - Mapping.VariableDeclaration visit(VariableDeclaration) @nogc; Mapping.Statement visit(Statement) @nogc; Mapping.BangStatement visit(BangStatement) @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 { Mapping.Statement accept(Mapping)(ParserVisitor!Mapping visitor) @nogc @@ -83,7 +70,6 @@ class BangStatement : Statement class Block : Node { Array!Definition definitions; - Array!VariableDeclaration variableDeclarations; Statement statement; Mapping.Block accept(Mapping)(ParserVisitor!Mapping visitor) @nogc @@ -120,6 +106,7 @@ class Variable : Expression } } +extern(C++, "elna") enum BinaryOperator { 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") { - if (tokens.front.ofType(Token.Type.identifier)) + if (tokens.front.of() == Token.Type.identifier) { 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; 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; - number.value = tokens.front.value!(Token.Type.number); + number.value = tokens.front.number(); tokens.popFront; return Result!Expression(number); } - else if (tokens.front.ofType(Token.Type.leftParen)) + else if (tokens.front.of() == Token.Type.leftParen) { tokens.popFront; @@ -184,20 +171,20 @@ in (!tokens.empty, "Expected factor, got end of stream") 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); } -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") { 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; } - auto operator = tokens.front.value!(Token.Type.operator); + auto operator = String(tokens.front.identifier()[0 .. strlen(tokens.front.identifier())]); tokens.popFront; 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") { 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(); // Skip the equals sign. - if (tokens.front.ofType(Token.Type.number)) + if (tokens.front.of() == Token.Type.number) { auto number = defaultAllocator.make!Number; - number.value = tokens.front.value!(Token.Type.number); + number.value = tokens.front.number(); definition.number = number; tokens.popFront; 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); } -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") { - if (tokens.front.ofType(Token.Type.bang)) + if (tokens.front.of() == Token.Type.bang) { tokens.popFront; 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); } -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") { tokens.popFront; // Skip const. @@ -271,11 +258,11 @@ in (!tokens.empty, "Expected definition, got end of stream") return typeof(return)(definition.error.get); } definitions.insertBack(definition.result); - if (tokens.front.ofType(Token.Type.semicolon)) + if (tokens.front.of() == Token.Type.semicolon) { break; } - if (tokens.front.ofType(Token.Type.comma)) + if (tokens.front.of() == Token.Type.comma) { tokens.popFront; } @@ -284,49 +271,11 @@ in (!tokens.empty, "Expected definition, got end of stream") return typeof(return)(definitions); } -private Result!(Array!VariableDeclaration) parseVariableDeclarations(ref Array!Token.Range 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 +private Result!Block parseBlock(ref Token[] tokens) @nogc in (!tokens.empty, "Expected block, got end of stream") { auto block = defaultAllocator.make!Block; - if (tokens.front.ofType(Token.Type.let)) + if (tokens.front.of() == Token.Type.let) { auto constDefinitions = parseDefinitions(tokens); if (constDefinitions.valid) @@ -339,19 +288,6 @@ in (!tokens.empty, "Expected block, got end of stream") } 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); if (statement.valid) { @@ -365,7 +301,7 @@ in (!tokens.empty, "Expected block, got end of stream") return Result!Block(block); } -Result!Block parse(ref Array!Token tokenStream) @nogc +Result!Block parse(Token[] tokenStream) @nogc { auto tokens = tokenStream[]; return parseBlock(tokens); diff --git a/source/elna/result.d b/source/elna/result.d index 9427147..f35f68e 100644 --- a/source/elna/result.d +++ b/source/elna/result.d @@ -7,6 +7,7 @@ import tanya.container.string; /** * Position in the source text. */ +extern(C++, "elna") struct Position { /// Line. @@ -16,9 +17,10 @@ struct Position size_t column = 1; } +extern(C++, "elna") struct CompileError { - private string message_; + private const(char)* message_; private Position position_; @@ -29,29 +31,16 @@ struct CompileError * message = Error text. * position = Error position in the source text. */ - this(string message, Position position) @nogc nothrow pure @safe - { - this.message_ = message; - this.position_ = position; - } + this(const(char)* message, const Position position) @nogc nothrow pure @safe; /// Error text. - @property string message() const @nogc nothrow pure @safe - { - return this.message_; - } + @property const(char)* what() const @nogc nothrow pure @safe; /// Error line in the source text. - @property size_t line() const @nogc nothrow pure @safe - { - return this.position_.line; - } + @property size_t line() const @nogc nothrow pure @safe; /// Error column in the source text. - @property size_t column() const @nogc nothrow pure @safe - { - return this.position_.column; - } + @property size_t column() const @nogc nothrow pure @safe; } struct Result(T) @@ -65,7 +54,7 @@ struct Result(T) this.error = typeof(this.error).init; } - this(string message, Position position) + this(const(char)* message, Position position) { this.result = T.init; this.error = CompileError(message, position); @@ -85,16 +74,18 @@ struct Result(T) } } +extern(C++, "elna") +enum Target +{ + text, + high20, + lower12i +} + +extern(C++, "elna") struct Reference { - enum Target - { - text, - high20, - lower12i - } - - String name; + const(char)* name; size_t offset; Target target; } diff --git a/source/elna/riscv.d b/source/elna/riscv.d index bf3f2a5..2719f48 100644 --- a/source/elna/riscv.d +++ b/source/elna/riscv.d @@ -1,14 +1,12 @@ module elna.riscv; import core.stdc.stdlib; -import elna.extended; import elna.ir; import elna.result; -import std.algorithm; -import std.typecons; import tanya.container.array; import tanya.container.string; +extern(C++, "elna") enum XRegister : ubyte { zero = 0, @@ -45,6 +43,7 @@ enum XRegister : ubyte t6 = 31, } +extern(C++, "elna") enum Funct3 : ubyte { addi = 0b000, @@ -92,18 +91,21 @@ enum Funct3 : ubyte jalr = 0b000, } +extern(C++, "elna") enum Funct12 : ubyte { ecall = 0b000000000000, ebreak = 0b000000000001, } +extern(C++, "elna") enum Funct7 : ubyte { none = 0, sub = 0b0100000 } +extern(C++, "elna") enum BaseOpcode : ubyte { opImm = 0b0010011, @@ -119,243 +121,61 @@ enum BaseOpcode : ubyte system = 0b1110011, } +extern(C++, "elna") struct Instruction { private uint instruction; - this(BaseOpcode opcode) @nogc - { - this.instruction = opcode; - } + this(BaseOpcode opcode) @nogc; @disable this(); ref Instruction i(XRegister rd, Funct3 funct3, XRegister rs1, uint immediate) - return scope @nogc - { - this.instruction |= (rd << 7) - | (funct3 << 12) - | (rs1 << 15) - | (immediate << 20); - - return this; - } + return scope @nogc; ref Instruction s(uint imm1, Funct3 funct3, XRegister rs1, XRegister rs2) - return scope @nogc - { - this.instruction |= ((imm1 & 0b11111) << 7) - | (funct3 << 12) - | (rs1 << 15) - | (rs2 << 20) - | ((imm1 & 0b111111100000) << 20); - - return this; - } + return scope @nogc; ref Instruction r(XRegister rd, Funct3 funct3, XRegister rs1, XRegister rs2, Funct7 funct7 = Funct7.none) - return scope @nogc - { - this.instruction |= (rd << 7) - | (funct3 << 12) - | (rs1 << 15) - | (rs2 << 20) - | (funct7 << 25); - - return this; - } + return scope @nogc; ref Instruction u(XRegister rd, uint imm) - return scope @nogc - { - this.instruction |= (rd << 7) | (imm << 12); + return scope @nogc; - return this; - } - - ubyte* encode() return scope @nogc - { - return cast(ubyte*) (&this.instruction); - } + ubyte* encode() return scope @nogc; } +extern(C++, "elna") class RiscVVisitor : IRVisitor { - Array!Instruction instructions; + Instruction *instructions; + size_t instructionsLength; bool registerInUse; uint variableCounter = 1; - Array!Reference references; + Reference[3] references; - override void visit(Node) @nogc - { - } - - 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, -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; - } + 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(Number number) @nogc; + override void visit(BinaryExpression expression) @nogc; } Symbol writeNext(Definition ast) @nogc { Array!Instruction instructions; - Array!Reference references; auto visitor = cast(RiscVVisitor) malloc(__traits(classInstanceSize, RiscVVisitor)); (cast(void*) visitor)[0 .. __traits(classInstanceSize, RiscVVisitor)] = __traits(initSymbol, RiscVVisitor)[]; scope (exit) { - visitor.__xdtor(); free(cast(void*) visitor); } visitor.visit(ast); auto program = Symbol(String("main")); - program.symbols = move(visitor.references); - foreach (ref instruction; visitor.instructions) + program.symbols = Array!Reference(visitor.references[]); + foreach (ref instruction; visitor.instructions[0 .. visitor.instructionsLength]) { program.text.insertBack(instruction.encode[0 .. uint.sizeof]); } diff --git a/source/ir.cpp b/source/ir.cpp new file mode 100644 index 0000000..f7ae9ff --- /dev/null +++ b/source/ir.cpp @@ -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); + } +} diff --git a/source/lexer.cpp b/source/lexer.cpp index b246ab6..00de021 100644 --- a/source/lexer.cpp +++ b/source/lexer.cpp @@ -14,18 +14,18 @@ namespace elna 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); } 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) { } - const source_position& source::const_iterator::position() const noexcept + const Position& source::const_iterator::position() const noexcept { return this->m_position; } @@ -73,29 +73,109 @@ namespace elna return !(*this == that); } - token::token(const type of, source::const_iterator begin, source::const_iterator end) - : m_type(of), m_value(begin, end) + Token::Token(const Type of, const char *value, Position position) + : m_type(of), m_position(position) + { + std::size_t value_length = strlen(value); + char *buffer = reinterpret_cast(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(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(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; } - 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; } - result> lex(const std::string& buffer) + Token *lex(const char *buffer, CompileError *compile_error, std::size_t *length) { - std::vector tokens; + std::vector tokens; source input{ buffer }; for (auto iterator = input.begin(); iterator != input.end();) @@ -103,23 +183,88 @@ namespace elna if (*iterator == ' ' || *iterator == '\n') { } - else if (std::isgraph(*iterator)) + else if (std::isdigit(*iterator)) { - auto current_position = iterator; - do + tokens.emplace_back( + Token::TOKEN_NUMBER, + static_cast(*iterator - '0'), + iterator.position() + ); + } + else if (*iterator == '=') + { + tokens.emplace_back(Token::TOKEN_EQUALS, iterator.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)) { - ++current_position; + word.push_back(*i); + ++i; } - while (current_position != input.end() && std::isgraph(*current_position)); - token new_token{ token::type::word, iterator, current_position }; - - tokens.push_back(new_token); - iterator = current_position; + 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; } + 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; } + Token *target = reinterpret_cast(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; } } diff --git a/shell/result.cpp b/source/result.cpp similarity index 51% rename from shell/result.cpp rename to source/result.cpp index 8054eb3..c027c70 100644 --- a/shell/result.cpp +++ b/source/result.cpp @@ -2,23 +2,23 @@ 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->position = position; } - char const *compile_error::what() const noexcept + char const *CompileError::what() const noexcept { return this->message; } - std::size_t compile_error::line() const noexcept + std::size_t CompileError::line() const noexcept { return this->position.line; } - std::size_t compile_error::column() const noexcept + std::size_t CompileError::column() const noexcept { return this->position.column; } diff --git a/source/riscv.cpp b/source/riscv.cpp new file mode 100644 index 0000000..c8d434f --- /dev/null +++ b/source/riscv.cpp @@ -0,0 +1,191 @@ +#include "elna/parser.hpp" +#include "elna/riscv.hpp" +#include + +namespace elna +{ + Instruction::Instruction(BaseOpcode opcode) + { + this->instruction = static_cast::type>(opcode); + } + + Instruction& Instruction::i(XRegister rd, Funct3 funct3, XRegister rs1, std::uint32_t immediate) + { + this->instruction |= (static_cast::type>(rd) << 7) + | (static_cast::type>(funct3) << 12) + | (static_cast::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::type>(funct3) << 12) + | (static_cast::type>(rs1) << 15) + | (static_cast::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::type>(rd) << 7) + | (static_cast::type>(funct3) << 12) + | (static_cast::type>(rs1) << 15) + | (static_cast::type>(rs2) << 20) + | (static_cast::type>(funct7) << 25); + + return *this; + } + + Instruction& Instruction::u(XRegister rd, std::uint32_t imm) + { + this->instruction |= (static_cast::type>(rd) << 7) | (imm << 12); + + return *this; + } + + std::uint8_t *Instruction::encode() + { + return reinterpret_cast(&this->instruction); + } + + void RiscVVisitor::visit(ir::Node *) + { + } + + void RiscVVisitor::visit(ir::Definition *definition) + { + const uint stackSize = static_cast(definition->statementsLength * 4 + 12); + + this->instructionsLength += 4; + this->instructions = reinterpret_cast( + 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( + 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(operand) != nullptr) + { + return dynamic_cast(operand)->accept(this); + } + if (dynamic_cast(operand) != nullptr) + { + return dynamic_cast(operand)->accept(this); + } + } + + void RiscVVisitor::visit(ir::Variable *variable) + { + const auto freeRegister = this->registerInUse ? XRegister::a0 : XRegister::t0; + + ++this->instructionsLength; + this->instructions = reinterpret_cast( + 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(variable->counter * 4)); + } + + void RiscVVisitor::visit(ir::Number *number) + { + const auto freeRegister = this->registerInUse ? XRegister::a0 : XRegister::t0; + + ++this->instructionsLength; + this->instructions = reinterpret_cast( + 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( + 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(this->variableCounter * 4), Funct3::sw, XRegister::sp, XRegister::a0); + + ++this->variableCounter; + } +}