Extend the tester to compile sources
This commit is contained in:
154
source/elna/arguments.d
Normal file
154
source/elna/arguments.d
Normal file
@ -0,0 +1,154 @@
|
||||
/**
|
||||
* Argument parsing.
|
||||
*/
|
||||
module elna.arguments;
|
||||
|
||||
import std.algorithm;
|
||||
import std.range;
|
||||
import std.sumtype;
|
||||
|
||||
struct ArgumentError
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
expectedOutputFile,
|
||||
noInput,
|
||||
superfluousArguments,
|
||||
}
|
||||
|
||||
private Type type_;
|
||||
private string argument_;
|
||||
|
||||
@property Type type() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.type_;
|
||||
}
|
||||
|
||||
@property string argument() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.argument_;
|
||||
}
|
||||
|
||||
void toString(OR)(OR range)
|
||||
if (isOutputRage!OR)
|
||||
{
|
||||
final switch (Type)
|
||||
{
|
||||
case Type.expectedOutputFile:
|
||||
put(range, "Expected an output filename after -o");
|
||||
break;
|
||||
case Type.noInput:
|
||||
put(range, "No input files specified");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Supported compiler arguments.
|
||||
*/
|
||||
struct Arguments
|
||||
{
|
||||
private bool assembler_;
|
||||
private string output_;
|
||||
private string inFile_;
|
||||
|
||||
@property string inFile() @nogc nothrow pure @safe
|
||||
{
|
||||
return this.inFile_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Whether to generate assembly instead of an object file.
|
||||
*/
|
||||
@property bool assembler() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.assembler_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Output file.
|
||||
*/
|
||||
@property string output() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.output_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse command line arguments.
|
||||
*
|
||||
* The first argument is expected to be the program name (and it is
|
||||
* ignored).
|
||||
*
|
||||
* Params:
|
||||
* arguments = Command line arguments.
|
||||
*
|
||||
* Returns: Parsed arguments or an error.
|
||||
*/
|
||||
static SumType!(ArgumentError, Arguments) parse(string[] arguments)
|
||||
@nogc nothrow pure @safe
|
||||
{
|
||||
if (!arguments.empty)
|
||||
{
|
||||
arguments.popFront;
|
||||
}
|
||||
alias ReturnType = typeof(return);
|
||||
|
||||
return parseArguments(arguments).match!(
|
||||
(Arguments parsed) {
|
||||
if (parsed.inFile is null)
|
||||
{
|
||||
return ReturnType(ArgumentError(ArgumentError.Type.noInput));
|
||||
}
|
||||
else if (!arguments.empty)
|
||||
{
|
||||
return ReturnType(ArgumentError(
|
||||
ArgumentError.Type.superfluousArguments,
|
||||
arguments.front
|
||||
));
|
||||
}
|
||||
return ReturnType(parsed);
|
||||
},
|
||||
(ArgumentError argumentError) => ReturnType(argumentError)
|
||||
);
|
||||
}
|
||||
|
||||
private static SumType!(ArgumentError, Arguments) parseArguments(ref string[] arguments)
|
||||
@nogc nothrow pure @safe
|
||||
{
|
||||
Arguments parsed;
|
||||
|
||||
while (!arguments.empty)
|
||||
{
|
||||
if (arguments.front == "-s")
|
||||
{
|
||||
parsed.assembler_ = true;
|
||||
}
|
||||
else if (arguments.front == "-o")
|
||||
{
|
||||
if (arguments.empty)
|
||||
{
|
||||
return typeof(return)(ArgumentError(
|
||||
ArgumentError.Type.expectedOutputFile,
|
||||
arguments.front
|
||||
));
|
||||
}
|
||||
arguments.popFront;
|
||||
parsed.output_ = arguments.front;
|
||||
}
|
||||
else if (arguments.front == "--")
|
||||
{
|
||||
arguments.popFront;
|
||||
parsed.inFile_ = arguments.front;
|
||||
arguments.popFront;
|
||||
break;
|
||||
}
|
||||
else if (!arguments.front.startsWith("-"))
|
||||
{
|
||||
parsed.inFile_ = arguments.front;
|
||||
}
|
||||
arguments.popFront;
|
||||
}
|
||||
return typeof(return)(parsed);
|
||||
}
|
||||
}
|
105
source/elna/backend.d
Normal file
105
source/elna/backend.d
Normal file
@ -0,0 +1,105 @@
|
||||
module elna.backend;
|
||||
|
||||
import core.stdc.stdio;
|
||||
import core.stdc.stdlib;
|
||||
import elna.elf;
|
||||
import elna.ir;
|
||||
import elna.extended;
|
||||
import elna.riscv;
|
||||
import elna.lexer;
|
||||
import elna.parser;
|
||||
import elna.result;
|
||||
import std.algorithm;
|
||||
import std.sumtype;
|
||||
import std.typecons;
|
||||
import tanya.os.error;
|
||||
import tanya.container.array;
|
||||
import tanya.container.string;
|
||||
import tanya.memory.allocator;
|
||||
|
||||
private Nullable!String readSource(string source) @nogc
|
||||
{
|
||||
enum size_t bufferSize = 255;
|
||||
auto sourceFilename = String(source);
|
||||
|
||||
return readFile(sourceFilename).match!(
|
||||
(ErrorCode errorCode) {
|
||||
perror(sourceFilename.toStringz);
|
||||
return Nullable!String();
|
||||
},
|
||||
(Array!ubyte contents) => nullable(String(cast(char[]) contents.get))
|
||||
);
|
||||
}
|
||||
|
||||
int generate(string inFile, ref String outputFilename) @nogc
|
||||
{
|
||||
auto sourceText = readSource(inFile);
|
||||
if (sourceText.isNull)
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
auto tokens = lex(sourceText.get.get);
|
||||
if (!tokens.valid)
|
||||
{
|
||||
auto compileError = tokens.error.get;
|
||||
printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.message.ptr);
|
||||
return 1;
|
||||
}
|
||||
auto ast = parse(tokens.result);
|
||||
if (!ast.valid)
|
||||
{
|
||||
auto compileError = ast.error.get;
|
||||
printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.message.ptr);
|
||||
return 2;
|
||||
}
|
||||
auto transformVisitor = cast(TransformVisitor) malloc(__traits(classInstanceSize, TransformVisitor));
|
||||
(cast(void*) transformVisitor)[0 .. __traits(classInstanceSize, TransformVisitor)] = __traits(initSymbol, TransformVisitor)[];
|
||||
|
||||
auto ir = transformVisitor.visit(ast.result);
|
||||
|
||||
transformVisitor.__xdtor();
|
||||
free(cast(void*) transformVisitor);
|
||||
|
||||
auto handle = File.open(outputFilename.toStringz, BitFlags!(File.Mode)(File.Mode.truncate));
|
||||
if (!handle.valid)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
auto program = writeNext(ir);
|
||||
auto elf = Elf!ELFCLASS32(move(handle));
|
||||
auto readOnlyData = Array!ubyte(cast(const(ubyte)[]) "%d\n".ptr[0 .. 4]); // With \0.
|
||||
|
||||
elf.addReadOnlyData(String(".CL0"), readOnlyData);
|
||||
elf.addCode(program.name, program.text);
|
||||
|
||||
elf.addExternSymbol(String("printf"));
|
||||
foreach (ref reference; program.symbols)
|
||||
{
|
||||
elf.Rela relocationEntry = {
|
||||
r_offset: cast(elf.Addr) reference.offset
|
||||
};
|
||||
elf.Rela relocationSub = {
|
||||
r_offset: cast(elf.Addr) reference.offset,
|
||||
r_info: R_RISCV_RELAX
|
||||
};
|
||||
|
||||
final switch (reference.target)
|
||||
{
|
||||
case Reference.Target.text:
|
||||
relocationEntry.r_info = R_RISCV_CALL;
|
||||
break;
|
||||
case Reference.Target.high20:
|
||||
relocationEntry.r_info = R_RISCV_HI20;
|
||||
break;
|
||||
case Reference.Target.lower12i:
|
||||
relocationEntry.r_info = R_RISCV_LO12_I;
|
||||
break;
|
||||
}
|
||||
|
||||
elf.relocate(reference.name, relocationEntry, relocationSub);
|
||||
}
|
||||
|
||||
elf.finish();
|
||||
|
||||
return 0;
|
||||
}
|
1060
source/elna/elf.d
Normal file
1060
source/elna/elf.d
Normal file
File diff suppressed because it is too large
Load Diff
335
source/elna/extended.d
Normal file
335
source/elna/extended.d
Normal file
@ -0,0 +1,335 @@
|
||||
/**
|
||||
* File I/O that can be moved into more generic library when and if finished.
|
||||
*/
|
||||
module elna.extended;
|
||||
|
||||
import core.stdc.errno;
|
||||
import core.stdc.stdio;
|
||||
import std.sumtype;
|
||||
import std.typecons;
|
||||
import tanya.os.error;
|
||||
import tanya.container.array;
|
||||
import tanya.container.string;
|
||||
|
||||
/**
|
||||
* File handle abstraction.
|
||||
*/
|
||||
struct File
|
||||
{
|
||||
/// Plattform dependent file type.
|
||||
alias Handle = FILE*;
|
||||
|
||||
/// Uninitialized file handle value.
|
||||
enum Handle invalid = null;
|
||||
|
||||
/**
|
||||
* Relative position.
|
||||
*/
|
||||
enum Whence
|
||||
{
|
||||
/// Relative to the start of the file.
|
||||
set = SEEK_SET,
|
||||
/// Relative to the current cursor position.
|
||||
currentt = SEEK_CUR,
|
||||
/// Relative from the end of the file.
|
||||
end = SEEK_END,
|
||||
}
|
||||
|
||||
/**
|
||||
* File open modes.
|
||||
*/
|
||||
enum Mode
|
||||
{
|
||||
/// Open the file for reading.
|
||||
read = 1 << 0,
|
||||
/// Open the file for writing. The stream is positioned at the beginning
|
||||
/// of the file.
|
||||
write = 1 << 1,
|
||||
/// Open the file for writing and remove its contents.
|
||||
truncate = 1 << 2,
|
||||
/// Open the file for writing. The stream is positioned at the end of
|
||||
/// the file.
|
||||
append = 1 << 3,
|
||||
}
|
||||
|
||||
private enum Status
|
||||
{
|
||||
invalid,
|
||||
owned,
|
||||
borrowed,
|
||||
}
|
||||
|
||||
private union Storage
|
||||
{
|
||||
Handle handle;
|
||||
ErrorCode errorCode;
|
||||
}
|
||||
private Storage storage;
|
||||
private Status status = Status.invalid;
|
||||
|
||||
@disable this(scope return ref File f);
|
||||
@disable this();
|
||||
|
||||
/**
|
||||
* Closes the file.
|
||||
*/
|
||||
~this() @nogc nothrow
|
||||
{
|
||||
if (this.status == Status.owned)
|
||||
{
|
||||
fclose(this.storage.handle);
|
||||
}
|
||||
this.storage.handle = invalid;
|
||||
this.status = Status.invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct the object with the given system handle. The won't be claused
|
||||
* in the descructor if this constructor is used.
|
||||
*
|
||||
* Params:
|
||||
* handle = File handle to be wrapped by this structure.
|
||||
*/
|
||||
this(Handle handle) @nogc nothrow pure @safe
|
||||
{
|
||||
this.storage.handle = handle;
|
||||
this.status = Status.borrowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Plattform dependent file handle.
|
||||
*/
|
||||
@property Handle handle() @nogc nothrow pure @trusted
|
||||
{
|
||||
return valid ? this.storage.handle : invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: An error code if an error has occurred.
|
||||
*/
|
||||
@property ErrorCode errorCode() @nogc nothrow pure @safe
|
||||
{
|
||||
return valid ? ErrorCode() : this.storage.errorCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Whether a valid, opened file is represented.
|
||||
*/
|
||||
@property bool valid() @nogc nothrow pure @safe
|
||||
{
|
||||
return this.status != Status.invalid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transfers the file into invalid state.
|
||||
*
|
||||
* Returns: The old file handle.
|
||||
*/
|
||||
Handle reset() @nogc nothrow pure @safe
|
||||
{
|
||||
if (!valid)
|
||||
{
|
||||
return invalid;
|
||||
}
|
||||
auto oldHandle = handle;
|
||||
|
||||
this.status = Status.invalid;
|
||||
this.storage.errorCode = ErrorCode();
|
||||
|
||||
return oldHandle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets stream position in the file.
|
||||
*
|
||||
* Params:
|
||||
* offset = File offset.
|
||||
* whence = File position to add the offset to.
|
||||
*
|
||||
* Returns: Error code if any.
|
||||
*/
|
||||
ErrorCode seek(size_t offset, Whence whence) @nogc nothrow
|
||||
{
|
||||
if (!valid)
|
||||
{
|
||||
return ErrorCode(ErrorCode.ErrorNo.badDescriptor);
|
||||
}
|
||||
if (fseek(this.storage.handle, offset, whence))
|
||||
{
|
||||
return ErrorCode(cast(ErrorCode.ErrorNo) errno);
|
||||
}
|
||||
return ErrorCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: Current offset or an error.
|
||||
*/
|
||||
SumType!(ErrorCode, size_t) tell() @nogc nothrow
|
||||
{
|
||||
if (!valid)
|
||||
{
|
||||
return typeof(return)(ErrorCode(ErrorCode.ErrorNo.badDescriptor));
|
||||
}
|
||||
auto result = ftell(this.storage.handle);
|
||||
|
||||
if (result < 0)
|
||||
{
|
||||
return typeof(return)(ErrorCode(cast(ErrorCode.ErrorNo) errno));
|
||||
}
|
||||
return typeof(return)(cast(size_t) result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* buffer = Destination buffer.
|
||||
*
|
||||
* Returns: Bytes read. $(D_PSYMBOL ErrorCode.ErrorNo.success) means that
|
||||
* while reading the file an unknown error has occurred.
|
||||
*/
|
||||
SumType!(ErrorCode, size_t) read(ubyte[] buffer) @nogc nothrow
|
||||
{
|
||||
if (!valid)
|
||||
{
|
||||
return typeof(return)(ErrorCode(ErrorCode.ErrorNo.badDescriptor));
|
||||
}
|
||||
const bytesRead = fread(buffer.ptr, 1, buffer.length, this.storage.handle);
|
||||
if (bytesRead == buffer.length || eof())
|
||||
{
|
||||
return typeof(return)(bytesRead);
|
||||
}
|
||||
return typeof(return)(ErrorCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* buffer = Source buffer.
|
||||
*
|
||||
* Returns: Bytes written. $(D_PSYMBOL ErrorCode.ErrorNo.success) means that
|
||||
* while reading the file an unknown error has occurred.
|
||||
*/
|
||||
SumType!(ErrorCode, size_t) write(const(ubyte)[] buffer) @nogc nothrow
|
||||
{
|
||||
if (!valid)
|
||||
{
|
||||
return typeof(return)(ErrorCode(ErrorCode.ErrorNo.badDescriptor));
|
||||
}
|
||||
const bytesWritten = fwrite(buffer.ptr, 1, buffer.length, this.storage.handle);
|
||||
if (bytesWritten == buffer.length)
|
||||
{
|
||||
return typeof(return)(buffer.length);
|
||||
}
|
||||
return typeof(return)(ErrorCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: EOF status of the file.
|
||||
*/
|
||||
bool eof() @nogc nothrow
|
||||
{
|
||||
return valid && feof(this.storage.handle) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a file object that will be closed in the destructor.
|
||||
*
|
||||
* Params:
|
||||
* filename = The file to open.
|
||||
*
|
||||
* Returns: Opened file or an error.
|
||||
*/
|
||||
static File open(const(char)* filename, BitFlags!Mode mode) @nogc nothrow
|
||||
{
|
||||
char[3] modeBuffer = "\0\0\0";
|
||||
|
||||
if (mode.truncate)
|
||||
{
|
||||
modeBuffer[0] = 'w';
|
||||
if (mode.read)
|
||||
{
|
||||
modeBuffer[1] = '+';
|
||||
}
|
||||
}
|
||||
else if (mode.append)
|
||||
{
|
||||
modeBuffer[0] = 'a';
|
||||
if (mode.read)
|
||||
{
|
||||
modeBuffer[1] = '+';
|
||||
}
|
||||
}
|
||||
else if (mode.read)
|
||||
{
|
||||
modeBuffer[0] = 'r';
|
||||
if (mode.write)
|
||||
{
|
||||
modeBuffer[1] = '+';
|
||||
}
|
||||
}
|
||||
|
||||
auto newHandle = fopen(filename, modeBuffer.ptr);
|
||||
auto newFile = File(newHandle);
|
||||
|
||||
if (newHandle is null)
|
||||
{
|
||||
newFile.status = Status.invalid;
|
||||
newFile.storage.errorCode = ErrorCode(cast(ErrorCode.ErrorNo) errno);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (mode == BitFlags!Mode(Mode.write))
|
||||
{
|
||||
rewind(newHandle);
|
||||
}
|
||||
newFile.status = Status.owned;
|
||||
}
|
||||
|
||||
return newFile;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the whole file and returns its contents.
|
||||
*
|
||||
* Params:
|
||||
* sourceFilename = Source filename.
|
||||
*
|
||||
* Returns: File contents or an error.
|
||||
*
|
||||
* See_Also: $(D_PSYMBOL File.read)
|
||||
*/
|
||||
SumType!(ErrorCode, Array!ubyte) readFile(String sourceFilename) @nogc
|
||||
{
|
||||
enum size_t bufferSize = 255;
|
||||
auto sourceFile = File.open(sourceFilename.toStringz, BitFlags!(File.Mode)(File.Mode.read));
|
||||
|
||||
if (!sourceFile.valid)
|
||||
{
|
||||
return typeof(return)(sourceFile.errorCode);
|
||||
}
|
||||
Array!ubyte sourceText;
|
||||
size_t totalRead;
|
||||
size_t bytesRead;
|
||||
do
|
||||
{
|
||||
sourceText.length = sourceText.length + bufferSize;
|
||||
const readStatus = sourceFile
|
||||
.read(sourceText[totalRead .. $].get)
|
||||
.match!(
|
||||
(ErrorCode errorCode) => nullable(errorCode),
|
||||
(size_t bytesRead_) {
|
||||
bytesRead = bytesRead_;
|
||||
return Nullable!ErrorCode();
|
||||
}
|
||||
);
|
||||
if (!readStatus.isNull)
|
||||
{
|
||||
return typeof(return)(readStatus.get);
|
||||
}
|
||||
totalRead += bytesRead;
|
||||
}
|
||||
while (bytesRead == bufferSize);
|
||||
|
||||
sourceText.length = totalRead;
|
||||
|
||||
return typeof(return)(sourceText);
|
||||
}
|
268
source/elna/ir.d
Normal file
268
source/elna/ir.d
Normal file
@ -0,0 +1,268 @@
|
||||
module elna.ir;
|
||||
|
||||
import parser = elna.parser;
|
||||
import tanya.container.array;
|
||||
import tanya.container.hashtable;
|
||||
import tanya.container.string;
|
||||
import tanya.memory.allocator;
|
||||
public import elna.parser : BinaryOperator;
|
||||
|
||||
/**
|
||||
* Mapping between the parser and IR AST.
|
||||
*/
|
||||
struct ASTMapping
|
||||
{
|
||||
alias Node = .Node;
|
||||
alias Definition = .Definition;
|
||||
alias VariableDeclaration = .VariableDeclaration;
|
||||
alias Statement = .Operand;
|
||||
alias BangStatement = .Operand;
|
||||
alias Block = .Definition;
|
||||
alias Expression = .Operand;
|
||||
alias Number = .Number;
|
||||
alias Variable = .Number;
|
||||
alias BinaryExpression = .Variable;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* IR visitor.
|
||||
*/
|
||||
interface 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.
|
||||
*/
|
||||
abstract class Node
|
||||
{
|
||||
abstract void accept(IRVisitor) @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Definition.
|
||||
*/
|
||||
class Definition : Node
|
||||
{
|
||||
char[] identifier;
|
||||
Array!BinaryExpression statements;
|
||||
Array!VariableDeclaration variableDeclarations;
|
||||
Operand result;
|
||||
|
||||
override void accept(IRVisitor visitor) @nogc
|
||||
{
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Statement : Node
|
||||
{
|
||||
}
|
||||
|
||||
abstract class Operand : Node
|
||||
{
|
||||
override void accept(IRVisitor visitor) @nogc
|
||||
{
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
class Number : Operand
|
||||
{
|
||||
int value;
|
||||
|
||||
override void accept(IRVisitor visitor) @nogc
|
||||
{
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
override void accept(IRVisitor visitor) @nogc
|
||||
{
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
class BangExpression : Statement
|
||||
{
|
||||
Operand operand;
|
||||
|
||||
this(Operand operand)
|
||||
{
|
||||
this.operand = operand;
|
||||
}
|
||||
|
||||
override void accept(IRVisitor visitor) @nogc
|
||||
{
|
||||
visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
final class TransformVisitor : parser.ParserVisitor!ASTMapping
|
||||
{
|
||||
private HashTable!(String, int) constants;
|
||||
private Array!BinaryExpression statements;
|
||||
|
||||
ASTMapping.Node visit(parser.Node node) @nogc
|
||||
{
|
||||
assert(false, "Not implemented");
|
||||
}
|
||||
|
||||
ASTMapping.Definition visit(parser.Definition definition) @nogc
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
ASTMapping.Block visit(parser.Block block) @nogc
|
||||
{
|
||||
auto target = defaultAllocator.make!Definition;
|
||||
this.constants = transformConstants(block.definitions);
|
||||
|
||||
target.result = block.statement.accept(this);
|
||||
target.statements = this.statements;
|
||||
|
||||
target.variableDeclarations = transformVariableDeclarations(block.variableDeclarations);
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
ASTMapping.Expression visit(parser.Expression expression) @nogc
|
||||
{
|
||||
if ((cast(parser.Number) expression) !is null)
|
||||
{
|
||||
return (cast(parser.Number) expression).accept(this);
|
||||
}
|
||||
if ((cast(parser.Variable) expression) !is null)
|
||||
{
|
||||
return (cast(parser.Variable) expression).accept(this);
|
||||
}
|
||||
else if ((cast(parser.BinaryExpression) expression) !is null)
|
||||
{
|
||||
return (cast(parser.BinaryExpression) expression).accept(this);
|
||||
}
|
||||
assert(false, "Invalid expression type");
|
||||
}
|
||||
|
||||
ASTMapping.Number visit(parser.Number number) @nogc
|
||||
{
|
||||
auto numberExpression = defaultAllocator.make!Number;
|
||||
numberExpression.value = number.value;
|
||||
|
||||
return numberExpression;
|
||||
}
|
||||
|
||||
ASTMapping.Variable visit(parser.Variable variable) @nogc
|
||||
{
|
||||
auto numberExpression = defaultAllocator.make!Number;
|
||||
numberExpression.value = this.constants[variable.identifier];
|
||||
|
||||
return numberExpression;
|
||||
}
|
||||
|
||||
ASTMapping.BinaryExpression visit(parser.BinaryExpression binaryExpression) @nogc
|
||||
{
|
||||
auto target = defaultAllocator.make!BinaryExpression(
|
||||
binaryExpression.lhs.accept(this),
|
||||
binaryExpression.rhs.accept(this),
|
||||
binaryExpression.operator
|
||||
);
|
||||
statements.insertBack(target);
|
||||
|
||||
auto newVariable = defaultAllocator.make!Variable;
|
||||
newVariable.counter = statements.length;
|
||||
|
||||
return newVariable;
|
||||
}
|
||||
|
||||
private Number transformNumber(parser.Number number) @nogc
|
||||
{
|
||||
return defaultAllocator.make!Number(number.value);
|
||||
}
|
||||
|
||||
override Operand visit(parser.Statement statement) @nogc
|
||||
{
|
||||
if ((cast(parser.BangStatement) statement) !is null)
|
||||
{
|
||||
return (cast(parser.BangStatement) statement).accept(this);
|
||||
}
|
||||
assert(false, "Invalid statement type");
|
||||
}
|
||||
|
||||
private HashTable!(String, int) transformConstants(ref Array!(parser.Definition) definitions) @nogc
|
||||
{
|
||||
typeof(return) constants;
|
||||
|
||||
foreach (definition; definitions[])
|
||||
{
|
||||
constants[definition.identifier] = definition.number.value;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
254
source/elna/lexer.d
Normal file
254
source/elna/lexer.d
Normal file
@ -0,0 +1,254 @@
|
||||
module elna.lexer;
|
||||
|
||||
import core.stdc.stdlib;
|
||||
import core.stdc.ctype;
|
||||
import core.stdc.string;
|
||||
import elna.result;
|
||||
import std.range;
|
||||
import tanya.container.array;
|
||||
import tanya.container.string;
|
||||
|
||||
struct Token
|
||||
{
|
||||
enum Type
|
||||
{
|
||||
number,
|
||||
operator,
|
||||
let,
|
||||
identifier,
|
||||
equals,
|
||||
var,
|
||||
semicolon,
|
||||
leftParen,
|
||||
rightParen,
|
||||
bang,
|
||||
dot,
|
||||
comma,
|
||||
}
|
||||
|
||||
union Value
|
||||
{
|
||||
int number;
|
||||
String identifier;
|
||||
}
|
||||
|
||||
private Type type;
|
||||
private Value value_;
|
||||
private Position position_;
|
||||
|
||||
@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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* type = Expected type.
|
||||
*
|
||||
* Returns: Whether this token is of the expected 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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns: The token position in the source text.
|
||||
*/
|
||||
@property const(Position) position() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.position_;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Range over the source text that keeps track of the current position.
|
||||
*/
|
||||
struct Source
|
||||
{
|
||||
char[] buffer;
|
||||
Position position;
|
||||
|
||||
this(char[] buffer) @nogc nothrow pure @safe
|
||||
{
|
||||
this.buffer = buffer;
|
||||
}
|
||||
|
||||
@disable this();
|
||||
|
||||
bool empty() @nogc nothrow pure @safe
|
||||
{
|
||||
return this.length == 0;
|
||||
}
|
||||
|
||||
char front() @nogc nothrow pure @safe
|
||||
in (!empty)
|
||||
{
|
||||
return this.buffer[0];
|
||||
}
|
||||
|
||||
void popFront() @nogc nothrow pure @safe
|
||||
in (!empty)
|
||||
{
|
||||
this.buffer = buffer[1 .. $];
|
||||
++this.position.column;
|
||||
}
|
||||
|
||||
void breakLine() @nogc nothrow pure @safe
|
||||
in (!empty)
|
||||
{
|
||||
this.buffer = buffer[1 .. $];
|
||||
++this.position.line;
|
||||
this.position.column = 1;
|
||||
}
|
||||
|
||||
@property size_t length() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.buffer.length;
|
||||
}
|
||||
|
||||
char opIndex(size_t index) @nogc nothrow pure @safe
|
||||
in (index < length)
|
||||
{
|
||||
return this.buffer[index];
|
||||
}
|
||||
|
||||
char[] opSlice(size_t i, size_t j) @nogc nothrow pure @safe
|
||||
in
|
||||
{
|
||||
assert(i <= j);
|
||||
assert(j <= length);
|
||||
}
|
||||
do
|
||||
{
|
||||
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);
|
||||
}
|
372
source/elna/parser.d
Normal file
372
source/elna/parser.d
Normal file
@ -0,0 +1,372 @@
|
||||
module elna.parser;
|
||||
|
||||
import elna.lexer;
|
||||
import elna.result;
|
||||
import tanya.container.array;
|
||||
import tanya.container.string;
|
||||
import tanya.memory.allocator;
|
||||
|
||||
|
||||
/**
|
||||
* Parser visitor.
|
||||
*/
|
||||
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;
|
||||
Mapping.Expression visit(Expression) @nogc;
|
||||
Mapping.Number visit(Number) @nogc;
|
||||
Mapping.Variable visit(Variable) @nogc;
|
||||
Mapping.BinaryExpression visit(BinaryExpression) @nogc;
|
||||
}
|
||||
|
||||
/**
|
||||
* AST node.
|
||||
*/
|
||||
abstract class Node
|
||||
{
|
||||
Mapping.Node accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
|
||||
{
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constant definition.
|
||||
*/
|
||||
class Definition : Node
|
||||
{
|
||||
Number number;
|
||||
String identifier;
|
||||
|
||||
Mapping.Definition accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
|
||||
{
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
class BangStatement : Statement
|
||||
{
|
||||
Expression expression;
|
||||
|
||||
Mapping.BangStatement accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
|
||||
{
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
class Block : Node
|
||||
{
|
||||
Array!Definition definitions;
|
||||
Array!VariableDeclaration variableDeclarations;
|
||||
Statement statement;
|
||||
|
||||
Mapping.Block accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
|
||||
{
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Expression : Node
|
||||
{
|
||||
Mapping.Expression accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
|
||||
{
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
class Number : Expression
|
||||
{
|
||||
int value;
|
||||
|
||||
Mapping.Number accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
|
||||
{
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
class Variable : Expression
|
||||
{
|
||||
String identifier;
|
||||
|
||||
Mapping.Variable accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
|
||||
{
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
enum BinaryOperator
|
||||
{
|
||||
sum,
|
||||
subtraction
|
||||
}
|
||||
|
||||
class BinaryExpression : Expression
|
||||
{
|
||||
Expression lhs, rhs;
|
||||
BinaryOperator operator;
|
||||
|
||||
this(Expression lhs, Expression rhs, String operator) @nogc
|
||||
{
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
if (operator == "+")
|
||||
{
|
||||
this.operator = BinaryOperator.sum;
|
||||
}
|
||||
else if (operator == "-")
|
||||
{
|
||||
this.operator = BinaryOperator.subtraction;
|
||||
}
|
||||
else
|
||||
{
|
||||
assert(false, "Invalid binary operator");
|
||||
}
|
||||
}
|
||||
|
||||
Mapping.BinaryExpression accept(Mapping)(ParserVisitor!Mapping visitor) @nogc
|
||||
{
|
||||
return visitor.visit(this);
|
||||
}
|
||||
}
|
||||
|
||||
private Result!Expression parseFactor(ref Array!Token.Range tokens) @nogc
|
||||
in (!tokens.empty, "Expected factor, got end of stream")
|
||||
{
|
||||
if (tokens.front.ofType(Token.Type.identifier))
|
||||
{
|
||||
auto variable = defaultAllocator.make!Variable;
|
||||
variable.identifier = tokens.front.value!(Token.Type.identifier);
|
||||
tokens.popFront;
|
||||
return Result!Expression(variable);
|
||||
}
|
||||
else if (tokens.front.ofType(Token.Type.number))
|
||||
{
|
||||
auto number = defaultAllocator.make!Number;
|
||||
number.value = tokens.front.value!(Token.Type.number);
|
||||
tokens.popFront;
|
||||
return Result!Expression(number);
|
||||
}
|
||||
else if (tokens.front.ofType(Token.Type.leftParen))
|
||||
{
|
||||
tokens.popFront;
|
||||
|
||||
auto expression = parseExpression(tokens);
|
||||
|
||||
tokens.popFront;
|
||||
return expression;
|
||||
}
|
||||
return Result!Expression("Expected a factor", tokens.front.position);
|
||||
}
|
||||
|
||||
private Result!Expression parseTerm(ref Array!(Token).Range tokens) @nogc
|
||||
{
|
||||
return parseFactor(tokens);
|
||||
}
|
||||
|
||||
private Result!Expression parseExpression(ref Array!(Token).Range 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))
|
||||
{
|
||||
return term;
|
||||
}
|
||||
auto operator = tokens.front.value!(Token.Type.operator);
|
||||
tokens.popFront;
|
||||
|
||||
auto expression = parseExpression(tokens);
|
||||
|
||||
if (expression.valid)
|
||||
{
|
||||
auto binaryExpression = defaultAllocator
|
||||
.make!BinaryExpression(term.result, expression.result, operator);
|
||||
|
||||
return Result!Expression(binaryExpression);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result!Expression("Expected right-hand side to be an expression", tokens.front.position);
|
||||
}
|
||||
}
|
||||
|
||||
private Result!Definition parseDefinition(ref Array!Token.Range 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.
|
||||
|
||||
tokens.popFront();
|
||||
tokens.popFront(); // Skip the equals sign.
|
||||
|
||||
if (tokens.front.ofType(Token.Type.number))
|
||||
{
|
||||
auto number = defaultAllocator.make!Number;
|
||||
number.value = tokens.front.value!(Token.Type.number);
|
||||
definition.number = number;
|
||||
tokens.popFront;
|
||||
return Result!Definition(definition);
|
||||
}
|
||||
return Result!Definition("Expected a number", tokens.front.position);
|
||||
}
|
||||
|
||||
private Result!Statement parseStatement(ref Array!Token.Range tokens) @nogc
|
||||
in (!tokens.empty, "Expected block, got end of stream")
|
||||
{
|
||||
if (tokens.front.ofType(Token.Type.bang))
|
||||
{
|
||||
tokens.popFront;
|
||||
auto statement = defaultAllocator.make!BangStatement;
|
||||
auto expression = parseExpression(tokens);
|
||||
if (expression.valid)
|
||||
{
|
||||
statement.expression = expression.result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result!Statement(expression.error.get);
|
||||
}
|
||||
return Result!Statement(statement);
|
||||
}
|
||||
return Result!Statement("Expected ! statement", tokens.front.position);
|
||||
}
|
||||
|
||||
private Result!(Array!Definition) parseDefinitions(ref Array!Token.Range tokens) @nogc
|
||||
in (!tokens.empty, "Expected definition, got end of stream")
|
||||
{
|
||||
tokens.popFront; // Skip const.
|
||||
|
||||
Array!Definition definitions;
|
||||
|
||||
while (!tokens.empty)
|
||||
{
|
||||
auto definition = parseDefinition(tokens);
|
||||
if (!definition.valid)
|
||||
{
|
||||
return typeof(return)(definition.error.get);
|
||||
}
|
||||
definitions.insertBack(definition.result);
|
||||
if (tokens.front.ofType(Token.Type.semicolon))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (tokens.front.ofType(Token.Type.comma))
|
||||
{
|
||||
tokens.popFront;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
in (!tokens.empty, "Expected block, got end of stream")
|
||||
{
|
||||
auto block = defaultAllocator.make!Block;
|
||||
if (tokens.front.ofType(Token.Type.let))
|
||||
{
|
||||
auto constDefinitions = parseDefinitions(tokens);
|
||||
if (constDefinitions.valid)
|
||||
{
|
||||
block.definitions = constDefinitions.result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result!Block(constDefinitions.error.get);
|
||||
}
|
||||
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)
|
||||
{
|
||||
block.statement = statement.result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Result!Block(statement.error.get);
|
||||
}
|
||||
|
||||
return Result!Block(block);
|
||||
}
|
||||
|
||||
Result!Block parse(ref Array!Token tokenStream) @nogc
|
||||
{
|
||||
auto tokens = tokenStream[];
|
||||
return parseBlock(tokens);
|
||||
}
|
107
source/elna/result.d
Normal file
107
source/elna/result.d
Normal file
@ -0,0 +1,107 @@
|
||||
module elna.result;
|
||||
|
||||
import std.typecons;
|
||||
import tanya.container.array;
|
||||
import tanya.container.string;
|
||||
|
||||
/**
|
||||
* Position in the source text.
|
||||
*/
|
||||
struct Position
|
||||
{
|
||||
/// Line.
|
||||
size_t line = 1;
|
||||
|
||||
/// Column.
|
||||
size_t column = 1;
|
||||
}
|
||||
|
||||
struct CompileError
|
||||
{
|
||||
private string message_;
|
||||
|
||||
private Position position_;
|
||||
|
||||
@disable this();
|
||||
|
||||
/**
|
||||
* Params:
|
||||
* 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;
|
||||
}
|
||||
|
||||
/// Error text.
|
||||
@property string message() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.message_;
|
||||
}
|
||||
|
||||
/// Error line in the source text.
|
||||
@property size_t line() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.position_.line;
|
||||
}
|
||||
|
||||
/// Error column in the source text.
|
||||
@property size_t column() const @nogc nothrow pure @safe
|
||||
{
|
||||
return this.position_.column;
|
||||
}
|
||||
}
|
||||
|
||||
struct Result(T)
|
||||
{
|
||||
Nullable!CompileError error;
|
||||
T result;
|
||||
|
||||
this(T result)
|
||||
{
|
||||
this.result = result;
|
||||
this.error = typeof(this.error).init;
|
||||
}
|
||||
|
||||
this(string message, Position position)
|
||||
{
|
||||
this.result = T.init;
|
||||
this.error = CompileError(message, position);
|
||||
}
|
||||
|
||||
this(CompileError compileError)
|
||||
{
|
||||
this.result = null;
|
||||
this.error = compileError;
|
||||
}
|
||||
|
||||
@disable this();
|
||||
|
||||
@property bool valid() const
|
||||
{
|
||||
return error.isNull;
|
||||
}
|
||||
}
|
||||
|
||||
struct Reference
|
||||
{
|
||||
enum Target
|
||||
{
|
||||
text,
|
||||
high20,
|
||||
lower12i
|
||||
}
|
||||
|
||||
String name;
|
||||
size_t offset;
|
||||
Target target;
|
||||
}
|
||||
|
||||
struct Symbol
|
||||
{
|
||||
String name;
|
||||
Array!ubyte text;
|
||||
Array!Reference symbols;
|
||||
}
|
363
source/elna/riscv.d
Normal file
363
source/elna/riscv.d
Normal file
@ -0,0 +1,363 @@
|
||||
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;
|
||||
|
||||
enum XRegister : ubyte
|
||||
{
|
||||
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 Funct3 : ubyte
|
||||
{
|
||||
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 Funct12 : ubyte
|
||||
{
|
||||
ecall = 0b000000000000,
|
||||
ebreak = 0b000000000001,
|
||||
}
|
||||
|
||||
enum Funct7 : ubyte
|
||||
{
|
||||
none = 0,
|
||||
sub = 0b0100000
|
||||
}
|
||||
|
||||
enum BaseOpcode : ubyte
|
||||
{
|
||||
opImm = 0b0010011,
|
||||
lui = 0b0110111,
|
||||
auipc = 0b0010111,
|
||||
op = 0b0110011,
|
||||
jal = 0b1101111,
|
||||
jalr = 0b1100111,
|
||||
branch = 0b1100011,
|
||||
load = 0b0000011,
|
||||
store = 0b0100011,
|
||||
miscMem = 0b0001111,
|
||||
system = 0b1110011,
|
||||
}
|
||||
|
||||
struct Instruction
|
||||
{
|
||||
private uint instruction;
|
||||
|
||||
this(BaseOpcode opcode) @nogc
|
||||
{
|
||||
this.instruction = opcode;
|
||||
}
|
||||
@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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ref Instruction u(XRegister rd, uint imm)
|
||||
return scope @nogc
|
||||
{
|
||||
this.instruction |= (rd << 7) | (imm << 12);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
ubyte* encode() return scope @nogc
|
||||
{
|
||||
return cast(ubyte*) (&this.instruction);
|
||||
}
|
||||
}
|
||||
|
||||
class RiscVVisitor : IRVisitor
|
||||
{
|
||||
Array!Instruction instructions;
|
||||
bool registerInUse;
|
||||
uint variableCounter = 1;
|
||||
Array!Reference 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;
|
||||
}
|
||||
}
|
||||
|
||||
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.text.insertBack(instruction.encode[0 .. uint.sizeof]);
|
||||
}
|
||||
return program;
|
||||
}
|
125
source/lexer.cpp
Normal file
125
source/lexer.cpp
Normal file
@ -0,0 +1,125 @@
|
||||
#include "elna/lexer.hpp"
|
||||
|
||||
namespace elna
|
||||
{
|
||||
source::source(const std::string& buffer)
|
||||
: m_buffer(buffer)
|
||||
{
|
||||
}
|
||||
|
||||
source::const_iterator source::begin() const
|
||||
{
|
||||
return source::const_iterator(std::cbegin(m_buffer));
|
||||
}
|
||||
|
||||
source::const_iterator source::end() const
|
||||
{
|
||||
source_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)
|
||||
: m_buffer(buffer), m_position(start_position)
|
||||
{
|
||||
}
|
||||
|
||||
const source_position& source::const_iterator::position() const noexcept
|
||||
{
|
||||
return this->m_position;
|
||||
}
|
||||
|
||||
source::const_iterator::reference source::const_iterator::operator*() const noexcept
|
||||
{
|
||||
return *m_buffer;
|
||||
}
|
||||
|
||||
source::const_iterator::pointer source::const_iterator::operator->() const noexcept
|
||||
{
|
||||
return m_buffer.base();
|
||||
}
|
||||
|
||||
source::const_iterator& source::const_iterator::operator++()
|
||||
{
|
||||
if (*this->m_buffer == '\n')
|
||||
{
|
||||
this->m_position.column = 1;
|
||||
++this->m_position.line;
|
||||
}
|
||||
else
|
||||
{
|
||||
++this->m_position.column;
|
||||
}
|
||||
std::advance(this->m_buffer, 1);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
source::const_iterator& source::const_iterator::operator++(int)
|
||||
{
|
||||
auto tmp = *this;
|
||||
++(*this);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool source::const_iterator::operator==(const source::const_iterator& that) const noexcept
|
||||
{
|
||||
return this->m_buffer == that.m_buffer;
|
||||
}
|
||||
|
||||
bool source::const_iterator::operator!=(const source::const_iterator& that) const noexcept
|
||||
{
|
||||
return !(*this == that);
|
||||
}
|
||||
|
||||
token::token(const type of, source::const_iterator begin, source::const_iterator end)
|
||||
: m_type(of), m_value(begin, end)
|
||||
{
|
||||
}
|
||||
|
||||
token::type token::of() const noexcept
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
const token::value& token::identifier() const noexcept
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
const source_position& token::position() const noexcept
|
||||
{
|
||||
return m_position;
|
||||
}
|
||||
|
||||
result<std::vector<token>> lex(const std::string& buffer)
|
||||
{
|
||||
std::vector<token> tokens;
|
||||
source input{ buffer };
|
||||
|
||||
for (auto iterator = input.begin(); iterator != input.end();)
|
||||
{
|
||||
if (*iterator == ' ' || *iterator == '\n')
|
||||
{
|
||||
}
|
||||
else if (std::isgraph(*iterator))
|
||||
{
|
||||
auto current_position = iterator;
|
||||
do
|
||||
{
|
||||
++current_position;
|
||||
}
|
||||
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;
|
||||
continue;
|
||||
}
|
||||
++iterator;
|
||||
}
|
||||
|
||||
return tokens;
|
||||
}
|
||||
}
|
33
source/main.d
Normal file
33
source/main.d
Normal file
@ -0,0 +1,33 @@
|
||||
import elna.backend;
|
||||
import elna.ir;
|
||||
import elna.arguments;
|
||||
import std.path;
|
||||
import std.sumtype;
|
||||
import tanya.container.string;
|
||||
import tanya.memory.allocator;
|
||||
import tanya.memory.mmappool;
|
||||
|
||||
int main(string[] args)
|
||||
{
|
||||
defaultAllocator = MmapPool.instance;
|
||||
|
||||
return Arguments.parse(args).match!(
|
||||
(ArgumentError argumentError) => 4,
|
||||
(Arguments arguments) {
|
||||
String outputFilename;
|
||||
if (arguments.output is null)
|
||||
{
|
||||
outputFilename = arguments
|
||||
.inFile
|
||||
.baseName
|
||||
.withExtension("o");
|
||||
}
|
||||
else
|
||||
{
|
||||
outputFilename = String(arguments.output);
|
||||
}
|
||||
|
||||
return generate(arguments.inFile, outputFilename);
|
||||
}
|
||||
);
|
||||
}
|
Reference in New Issue
Block a user