Implement multiplication

This commit is contained in:
Eugen Wissner 2024-03-03 13:11:39 +01:00
parent 223f54d38d
commit 632dc53b53
Signed by: belka
GPG Key ID: A27FDC1E8EE902C0
25 changed files with 343 additions and 1989 deletions

View File

@ -5,6 +5,9 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set(CMAKE_CXX_STANDARD 17)
find_package(Boost COMPONENTS program_options REQUIRED)
include_directories(${Boost_INCLUDE_DIR})
add_executable(tester tests/runner.cpp include/elna/tester.hpp)
target_include_directories(tester PRIVATE include)
@ -15,7 +18,7 @@ add_executable(elnsh shell/main.cpp
)
target_include_directories(elnsh PRIVATE include)
add_library(elna
add_executable(elna source/main.cpp
source/lexer.cpp include/elna/lexer.hpp
source/result.cpp include/elna/result.hpp
source/riscv.cpp include/elna/riscv.hpp
@ -24,3 +27,4 @@ add_library(elna
source/cl.cpp include/elna/cl.hpp
)
target_include_directories(elna PRIVATE include)
target_link_libraries(elna LINK_PUBLIC ${Boost_LIBRARIES})

View File

@ -1,19 +0,0 @@
require 'pathname'
require 'rake/clean'
require 'open3'
DFLAGS = ['--warn-no-deprecated', '-L/usr/lib64/gcc-12']
BINARY = 'build/bin/elna'
SOURCES = FileList['source/**/*.d']
directory 'build/riscv'
CLEAN.include 'build'
CLEAN.include '.dub'
file BINARY => SOURCES do |t|
sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc')
end
task default: 'build/riscv'
task default: BINARY

30
TODO
View File

@ -1,4 +1,23 @@
# Completion
# Compiler
- Separate headers and sources for the compiler, shell and tests.
- Make writing ELF independent from the code generation.
- Argument parsing.
- Whitespaces are checked twice, in the source class and by lexing.
- Replace pointer and length with vectors and strings.
- Choose one name for runner.cpp and tester.hpp.
- Move argument handling into the cl module.
- Catch exceptions thrown by the argument parser and print them normally.
- cmake clean target.
- Division.
- Expressions contain one or more terms. Currently it parses another expression on the right side.
- Multiple factors.
# Shell
- Persist the history.
- Replace hard coded ANSI codes with constants or functions.
## Completion
- Configure fzf to show only the current directory files
- Support multiple selections for insertion in fzf.
@ -7,17 +26,12 @@
- Home directory expansion.
- Show files in the PATH if starting at the beginning of the prompt
# Appearance
## Appearance
- Add a bar with additional information under the prompt (edit_bar), like the hostname.
- Show current time in the prompt.
# Input
## Input
- DEL handling.
- Starting long running process and killing it with Ctrl-C kills the shell.
# Other
- Persist the history.
- Replace hard coded ANSI codes with constants or functions.

View File

@ -1,11 +0,0 @@
{
"dependencies": {
"tanya": "~>0.19.0"
},
"name": "elna",
"targetType": "executable",
"targetPath": "build/bin",
"mainSourceFile": "source/main.d",
"libs": ["elna", "stdc++"],
"lflags": ["-Lbuild"]
}

View File

@ -1,7 +1,9 @@
#pragma once
#include <filesystem>
namespace elna
{
char *readSource(const char *source);
int compile(const char *inFile, const char *outputFilename);
int compile(const std::filesystem::path& in_file, const std::filesystem::path& out_file);
}

View File

@ -1,10 +1,13 @@
#pragma once
#include <cstdint>
#include <string>
#include "elna/result.hpp"
namespace elna
{
namespace lex
{
/**
* Range over the source text that keeps track of the current position.
@ -14,10 +17,10 @@ namespace elna
class const_iterator
{
std::string::const_iterator m_buffer;
Position m_position;
elna::source::position m_position;
const_iterator(std::string::const_iterator buffer,
const Position position = Position());
const elna::source::position start_position = elna::source::position());
public:
using iterator_category = std::forward_iterator_tag;
@ -26,7 +29,7 @@ namespace elna
using pointer = const value_type *;
using reference = const value_type&;
const Position& position() const noexcept;
const elna::source::position& position() const noexcept;
reference operator*() const noexcept;
pointer operator->() const noexcept;
@ -54,20 +57,21 @@ namespace elna
/**
* Token type.
*/
enum Type : std::uint16_t
enum class type : std::uint16_t
{
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,
number = 0,
term_operator = 1,
let = 2,
identifier = 3,
equals = 4,
var = 5,
semicolon = 6,
left_paren = 7,
right_paren = 8,
bang = 9,
dot = 10,
comma = 11,
factor_operator = 12,
};
/**
@ -79,9 +83,9 @@ namespace elna
const char *identifier;
};
Token(Type of, Position position);
Token(Type of, std::int32_t value, Position position);
Token(Type of, const char *value, Position position);
Token(type of, elna::source::position position);
Token(type of, std::int32_t value, elna::source::position position);
Token(type of, const char *value, elna::source::position position);
Token(const Token& that);
Token(Token&& that);
~Token();
@ -89,21 +93,23 @@ namespace elna
Token& operator=(const Token& that);
Token& operator=(Token&& that);
Type of() const noexcept;
type of() const noexcept;
const char *identifier() const noexcept;
std::int32_t number() const noexcept;
const Position& position() const noexcept;
const elna::source::position& position() const noexcept;
private:
Type m_type;
type m_type;
Value m_value;
Position m_position;
elna::source::position m_position;
};
/**
* Split the source into tokens.
*
* \param buffer Source text.
* \return Tokens or error.
*/
Token *lex(const char *buffer, CompileError *compile_error, std::size_t *length);
elna::source::result<std::vector<Token>> lex(const char *buffer);
}
}

View File

@ -9,7 +9,9 @@ namespace elna
enum class BinaryOperator
{
sum,
subtraction
subtraction,
multiplication,
division
};
class Node;
@ -112,12 +114,12 @@ namespace elna
virtual void accept(ParserVisitor *visitor) override;
};
Expression *parseFactor(Token **tokens, std::size_t *length);
Expression *parseTerm(Token **tokens, std::size_t *length);
Expression *parseExpression(Token **tokens, std::size_t *length);
Definition *parseDefinition(Token **tokens, std::size_t *length);
Statement *parseStatement(Token **tokens, std::size_t *length);
Definition **parseDefinitions(Token **tokens, std::size_t *length, std::size_t *resultLength);
Block *parseBlock(Token **tokens, std::size_t *length);
Block *parse(Token *tokenStream, std::size_t length);
Expression *parseFactor(lex::Token **tokens, std::size_t *length);
Expression *parseTerm(lex::Token **tokens, std::size_t *length);
Expression *parseExpression(lex::Token **tokens, std::size_t *length);
Definition *parseDefinition(lex::Token **tokens, std::size_t *length);
Statement *parseStatement(lex::Token **tokens, std::size_t *length);
Definition **parseDefinitions(lex::Token **tokens, std::size_t *length, std::size_t *resultLength);
Block *parseBlock(lex::Token **tokens, std::size_t *length);
Block *parse(lex::Token *tokenStream, std::size_t length);
}

View File

@ -1,50 +1,92 @@
#pragma once
#include <cstddef>
#include <boost/outcome.hpp>
#include <variant>
#include <vector>
#include <forward_list>
namespace elna
{
/**
* Position in the source text.
*/
struct Position
namespace source
{
/// Line.
std::size_t line = 1;
/// Column.
std::size_t column = 1;
};
/**
* A compilation error consists of an error message and position.
*/
struct CompileError
{
private:
char const *message;
Position position;
public:
/**
* @param message Error text.
* @param position Error position in the source text.
* Position in the source text.
*/
CompileError(char const *message, const Position position) noexcept;
struct position
{
/// Line.
std::size_t line = 1;
/// Error text.
const char *what() const noexcept;
/// Column.
std::size_t column = 1;
};
/// Error line in the source text.
std::size_t line() const noexcept;
/**
* A compilation error consists of an error message and position.
*/
struct error
{
private:
char const *message;
source::position position;
/// Error column in the source text.
std::size_t column() const noexcept;
};
public:
/**
* \param message Error text.
* \param position Error position in the source text.
*/
error(char const *message, const source::position position) noexcept;
template<typename T>
using result = boost::outcome_v2::result<T, CompileError>;
/// Error text.
const char *what() const noexcept;
/// Error line in the source text.
std::size_t line() const noexcept;
/// Error column in the source text.
std::size_t column() const noexcept;
};
template<typename T>
struct result
{
using E = std::forward_list<source::error>;
template<typename... Args>
explicit result(Args&&... arguments)
: payload(std::forward<Args>(arguments)...)
{
}
explicit result(const char *message, const source::position position)
: payload(E{ source::error(message, position) })
{
}
bool has_errors() const noexcept
{
return std::holds_alternative<E>(payload);
}
bool is_success() const noexcept
{
return std::holds_alternative<T>(payload);
}
T& success()
{
return std::get<T>(payload);
}
E& errors()
{
return std::get<E>(payload);
}
private:
std::variant<T, E> payload;
};
}
enum class Target
{
@ -64,8 +106,7 @@ namespace elna
{
Symbol(const char *name);
const char *name;
unsigned char *text;
std::size_t length;
std::vector<std::byte> text;
Reference symbols[3];
};
}

View File

@ -87,6 +87,14 @@ namespace elna
lbu = 0b100,
lhu = 0b101,
jalr = 0b000,
mul = 000,
mulh = 001,
mulhsu = 010,
mulhu = 011,
div = 100,
divu = 101,
rem = 110,
remu = 111
};
enum class Funct12 : std::uint8_t
@ -98,7 +106,8 @@ namespace elna
enum class Funct7 : std::uint8_t
{
none = 0,
sub = 0b0100000
sub = 0b0100000,
muldiv = 0b0000001
};
enum class BaseOpcode : std::uint8_t
@ -124,7 +133,9 @@ namespace elna
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();
const std::byte *cbegin();
const std::byte *cend();
private:
std::uint32_t instruction{ 0 };

View File

@ -24,7 +24,7 @@ namespace elna
return result;
}
int compile(const char *inFile, const char *outputFilename)
int compile(const std::filesystem::path& in_file, const std::filesystem::path& out_file)
{
ELFIO::elfio writer;
@ -35,21 +35,23 @@ namespace elna
writer.set_type(ELFIO::ET_REL);
writer.set_machine(ELFIO::EM_RISCV);
auto sourceText = readSource(inFile);
auto sourceText = readSource(in_file.c_str());
if (sourceText == nullptr)
{
return 3;
}
CompileError *compileError = nullptr;
size_t tokensCount{ 0 };
auto tokens = lex(sourceText, compileError, &tokensCount);
auto lex_result = lex::lex(sourceText);
free(sourceText);
if (tokens == nullptr)
if (lex_result.has_errors())
{
printf("%lu:%lu: %s\n", compileError->line(), compileError->column(), compileError->what());
for (const auto& compile_error : lex_result.errors())
{
printf("%lu:%lu: %s\n", compile_error.line(), compile_error.column(), compile_error.what());
}
return 1;
}
auto ast = parse(tokens, tokensCount);
auto ast = parse(lex_result.success().data(), tokensCount);
if (ast == nullptr)
{
return 2;
@ -61,7 +63,7 @@ namespace elna
text_sec->set_type(ELFIO::SHT_PROGBITS);
text_sec->set_flags(ELFIO::SHF_ALLOC | ELFIO::SHF_EXECINSTR);
text_sec->set_addr_align(0x1);
text_sec->set_data(reinterpret_cast<const char *>(program.text), program.length);
text_sec->set_data(reinterpret_cast<const char *>(program.text.data()), program.text.size());
// Create string table section
ELFIO::section* str_sec = writer.sections.add(".strtab");
@ -91,7 +93,7 @@ namespace elna
ELFIO::symbol_section_accessor syma(writer, sym_sec);
auto label_sym = syma.add_symbol(stra, ".CL0", 0x00000000, strlen("%d\n") + 1,
ELFIO::STB_LOCAL, ELFIO::STT_NOTYPE, 0, ro_sec->get_index());
syma.add_symbol(stra, program.name, 0x00000000, program.length,
syma.add_symbol(stra, program.name, 0x00000000, program.text.size(),
ELFIO::STB_GLOBAL, ELFIO::STT_FUNC, 0, text_sec->get_index());
auto printf_sym = syma.add_symbol(stra, "printf", 0x00000000, 0,
ELFIO::STB_GLOBAL, ELFIO::STT_NOTYPE, 0, ELFIO::SHN_UNDEF);
@ -115,21 +117,8 @@ namespace elna
rela.add_entry(program.symbols[2].offset, printf_sym, 18 /* ELFIO::R_RISCV_CALL */);
rela.add_entry(program.symbols[2].offset, printf_sym, 51 /* ELFIO::R_RISCV_RELAX */);
// Another method to add the same relocation entry at one step is:
// rela.add_entry( stra, "msg",
// syma, 29, 0,
// ELF_ST_INFO( STB_GLOBAL, STT_OBJECT ), 0,
// text_sec->get_index(),
// place_to_adjust, (unsigned char)R_386_RELATIVE );
/* We don't use local symbols here. There is no need to rearrange them.
// But, for the completeness, we do this just prior 'save'
syma.arrange_local_symbols([&](ELFIO::Elf_Xword first, ELFIO::Elf_Xword second) {
rela.swap_symbols( first, second );
}); */
// Create ELF object file
writer.save(outputFilename);
writer.save(out_file);
return 0;
}

View File

@ -1,93 +0,0 @@
module elna.backend;
import core.stdc.stdio;
import core.stdc.stdlib;
import core.stdc.string;
import elna.elf;
import elna.extended;
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;
extern(C++, "elna")
Symbol writeNext(Block ast) @nogc;
extern(C++, "elna")
char* readSource(const(char)* source) @nogc;
extern(C++, "elna")
int compile(const(char)* inFile, const(char)* outputFilename) @nogc;
int generate(const(char)* inFile, const(char)* outputFilename) @nogc
{
auto sourceText = readSource(inFile);
if (sourceText is null)
{
return 3;
}
CompileError compileError = void;
size_t tokensCount;
auto tokens = lex(sourceText, &compileError, &tokensCount);
free(sourceText);
if (tokens is null)
{
printf("%lu:%lu: %s\n", compileError.line, compileError.column, compileError.what);
return 1;
}
auto ast = parse(tokens, tokensCount);
if (ast is null)
{
return 2;
}
auto handle = File.open(outputFilename, BitFlags!(File.Mode)(File.Mode.truncate));
if (!handle.valid)
{
return 1;
}
auto program = writeNext(ast);
auto elf = Elf!ELFCLASS32(move(handle));
auto readOnlyData = Array!ubyte(cast(const(ubyte)[]) "%d\n".ptr[0 .. 4]); // With \0.
auto text = Array!ubyte(program.text[0 .. program.length]);
elf.addReadOnlyData(String(".CL0"), readOnlyData);
elf.addCode(String(program.name[0 .. strlen(program.name)]), 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 Target.text:
relocationEntry.r_info = R_RISCV_CALL;
break;
case Target.high20:
relocationEntry.r_info = R_RISCV_HI20;
break;
case Target.lower12i:
relocationEntry.r_info = R_RISCV_LO12_I;
break;
}
elf.relocate(String(reference.name[0 .. strlen(reference.name)]), relocationEntry, relocationSub);
}
elf.finish();
return 0;
}

File diff suppressed because it is too large Load Diff

View File

@ -1,335 +0,0 @@
/**
* 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(const(char)* sourceFilename) @nogc
{
enum size_t bufferSize = 255;
auto sourceFile = File.open(sourceFilename, 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);
}

View File

@ -1,55 +0,0 @@
module elna.lexer;
import core.stdc.string;
import elna.result;
extern(C++, "elna")
struct Token
{
enum Type : ushort
{
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;
const(char)* identifier;
}
private Type type;
private Value value_;
private Position position_;
@disable this();
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;
/**
* Returns: Expected token type.
*/
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;
}
extern(C++, "elna")
Token* lex(const(char)* buffer, CompileError* compileError, size_t* length) @nogc;

View File

@ -1,110 +0,0 @@
module elna.parser;
import core.stdc.string;
import core.stdc.stdlib;
import elna.lexer;
import elna.result;
import tanya.memory.allocator;
import std.array;
/**
* Parser visitor.
*/
extern(C++, "elna")
abstract class ParserVisitor
{
abstract void visit(Node) @nogc;
abstract void visit(Definition) @nogc;
abstract void visit(BangStatement) @nogc;
abstract void visit(Block) @nogc;
abstract void visit(Expression) @nogc;
abstract void visit(BinaryExpression) @nogc;
abstract void visit(Variable) @nogc;
abstract void visit(Number) @nogc;
}
/**
* AST node.
*/
extern(C++, "elna")
abstract class Node
{
abstract void accept(ParserVisitor visitor) @nogc;
}
extern(C++, "elna")
abstract class Statement : Node
{
}
/**
* Constant definition.
*/
extern(C++, "elna")
class Definition : Node
{
Number number;
const(char)* identifier;
override void accept(ParserVisitor visitor) @nogc;
}
extern(C++, "elna")
class BangStatement : Statement
{
Expression expression;
override void accept(ParserVisitor visitor) @nogc;
}
extern(C++, "elna")
class Block : Node
{
Definition* definitions;
size_t definitionsLength;
Statement statement;
override void accept(ParserVisitor visitor) @nogc;
}
extern(C++, "elna")
abstract class Expression : Node
{
override void accept(ParserVisitor visitor) @nogc;
}
extern(C++, "elna")
class Number : Expression
{
int value;
override void accept(ParserVisitor visitor) @nogc;
}
extern(C++, "elna")
class Variable : Expression
{
const(char)* identifier;
override void accept(ParserVisitor visitor) @nogc;
}
extern(C++, "elna")
enum BinaryOperator
{
sum,
subtraction
}
extern(C++, "elna")
class BinaryExpression : Expression
{
Expression lhs, rhs;
BinaryOperator operator;
this(Expression lhs, Expression rhs, ubyte operator) @nogc;
override void accept(ParserVisitor visitor) @nogc;
}
extern(C++, "elna")
Block parse(Token* tokenStream, size_t length) @nogc;

View File

@ -1,101 +0,0 @@
module elna.result;
import std.typecons;
import tanya.container.array;
import tanya.container.string;
/**
* Position in the source text.
*/
extern(C++, "elna")
struct Position
{
/// Line.
size_t line = 1;
/// Column.
size_t column = 1;
}
extern(C++, "elna")
struct CompileError
{
private const(char)* message_;
private Position position_;
@disable this();
/**
* Params:
* message = Error text.
* position = Error position in the source text.
*/
this(const(char)* message, const Position position) @nogc nothrow pure @safe;
/// Error text.
@property const(char)* what() const @nogc nothrow pure @safe;
/// Error line in the source text.
@property size_t line() const @nogc nothrow pure @safe;
/// Error column in the source text.
@property size_t column() const @nogc nothrow pure @safe;
}
struct Result(T)
{
Nullable!CompileError error;
T result;
this(T result)
{
this.result = result;
this.error = typeof(this.error).init;
}
this(const(char)* 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;
}
}
extern(C++, "elna")
enum Target
{
text,
high20,
lower12i
}
extern(C++, "elna")
struct Reference
{
const(char)* name;
size_t offset;
Target target;
}
extern(C++, "elna")
struct Symbol
{
this(const(char)* name) @nogc;
const(char)* name;
ubyte* text;
size_t length;
Reference[3] symbols;
}

View File

@ -1,7 +1,15 @@
#include "elna/lexer.hpp"
#include <cstring>
namespace elna
{
namespace lex
{
using source_position = elna::source::position;
using source_error = elna::source::error;
using source_result = elna::source::result<std::vector<Token>>;
source::source(const std::string& buffer)
: m_buffer(buffer)
{
@ -14,18 +22,18 @@ namespace elna
source::const_iterator source::end() const
{
Position end_position{ 0, 0 };
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 Position start_position)
const source_position start_position)
: m_buffer(buffer), m_position(start_position)
{
}
const Position& source::const_iterator::position() const noexcept
const source_position& source::const_iterator::position() const noexcept
{
return this->m_position;
}
@ -73,7 +81,7 @@ namespace elna
return !(*this == that);
}
Token::Token(const Type of, const char *value, Position position)
Token::Token(const type of, const char *value, source_position position)
: m_type(of), m_position(position)
{
std::size_t value_length = strlen(value);
@ -85,13 +93,13 @@ namespace elna
m_value.identifier = buffer;
}
Token::Token(const Type of, std::int32_t number, Position position)
Token::Token(const type of, std::int32_t number, source_position position)
: m_type(of), m_position(position)
{
m_value.number = number;
}
Token::Token(const Type of, Position position)
Token::Token(const type of, source_position position)
: m_type(of), m_position(position)
{
}
@ -110,7 +118,7 @@ namespace elna
Token::~Token()
{
if (m_type == TOKEN_IDENTIFIER || m_type == TOKEN_OPERATOR)
if (m_type == type::identifier || m_type == type::term_operator || m_type == type::factor_operator)
{
std::free(const_cast<char*>(m_value.identifier));
}
@ -120,7 +128,7 @@ namespace elna
{
m_type = that.of();
m_position = that.position();
if (that.of() == TOKEN_IDENTIFIER || that.of() == TOKEN_OPERATOR)
if (that.of() == type::identifier || that.of() == type::term_operator || m_type == type::factor_operator)
{
std::size_t value_length = strlen(that.identifier());
char *buffer = reinterpret_cast<char *>(malloc(value_length + 1));
@ -130,7 +138,7 @@ namespace elna
m_value.identifier = buffer;
}
else if (that.of() == TOKEN_NUMBER)
else if (that.of() == type::number)
{
m_value.number = that.number();
}
@ -141,19 +149,19 @@ namespace elna
{
m_type = that.of();
m_position = that.position();
if (that.of() == TOKEN_IDENTIFIER || that.of() == TOKEN_OPERATOR)
if (that.of() == type::identifier || that.of() == type::term_operator || that.of() == type::factor_operator)
{
m_value.identifier = that.identifier();
that.m_value.identifier = nullptr;
}
else if (that.of() == TOKEN_NUMBER)
else if (that.of() == type::number)
{
m_value.number = that.number();
}
return *this;
}
Token::Type Token::of() const noexcept
Token::type Token::of() const noexcept
{
return m_type;
}
@ -168,12 +176,12 @@ namespace elna
return m_value.number;
}
const Position& Token::position() const noexcept
const source_position& Token::position() const noexcept
{
return m_position;
}
Token *lex(const char *buffer, CompileError *compile_error, std::size_t *length)
source_result lex(const char *buffer)
{
std::vector<Token> tokens;
source input{ buffer };
@ -186,38 +194,38 @@ namespace elna
else if (std::isdigit(*iterator))
{
tokens.emplace_back(
Token::TOKEN_NUMBER,
Token::type::number,
static_cast<std::int32_t>(*iterator - '0'),
iterator.position()
);
}
else if (*iterator == '=')
{
tokens.emplace_back(Token::TOKEN_EQUALS, iterator.position());
tokens.emplace_back(Token::type::equals, iterator.position());
}
else if (*iterator == '(')
{
tokens.emplace_back(Token::TOKEN_LEFT_PAREN, iterator.position());
tokens.emplace_back(Token::type::left_paren, iterator.position());
}
else if (*iterator == ')')
{
tokens.emplace_back(Token::TOKEN_RIGHT_PAREN, iterator.position());
tokens.emplace_back(Token::type::right_paren, iterator.position());
}
else if (*iterator == ';')
{
tokens.emplace_back(Token::TOKEN_SEMICOLON, iterator.position());
tokens.emplace_back(Token::type::semicolon, iterator.position());
}
else if (*iterator == ',')
{
tokens.emplace_back(Token::TOKEN_COMMA, iterator.position());
tokens.emplace_back(Token::type::comma, iterator.position());
}
else if (*iterator == '!')
{
tokens.emplace_back(Token::TOKEN_BANG, iterator.position());
tokens.emplace_back(Token::type::bang, iterator.position());
}
else if (*iterator == '.')
{
tokens.emplace_back(Token::TOKEN_DOT, iterator.position());
tokens.emplace_back(Token::type::dot, iterator.position());
}
else if (std::isalpha(*iterator))
{
@ -230,15 +238,15 @@ namespace elna
}
if (word == "const")
{
tokens.emplace_back(Token::TOKEN_LET, iterator.position());
tokens.emplace_back(Token::type::let, iterator.position());
}
else if (word == "var")
{
tokens.emplace_back(Token::TOKEN_VAR, iterator.position());
tokens.emplace_back(Token::type::var, iterator.position());
}
else
{
tokens.emplace_back(Token::TOKEN_IDENTIFIER, word.c_str(), iterator.position());
tokens.emplace_back(Token::type::identifier, word.c_str(), iterator.position());
}
iterator = i;
continue;
@ -247,24 +255,21 @@ namespace elna
{
std::string _operator{ *iterator };
tokens.emplace_back(Token::TOKEN_OPERATOR, _operator.c_str(), iterator.position());
tokens.emplace_back(Token::type::term_operator, _operator.c_str(), iterator.position());
}
else if (*iterator == '*' || *iterator == '/')
{
std::string _operator{ *iterator };
tokens.emplace_back(Token::type::factor_operator, _operator.c_str(), iterator.position());
}
else
{
*compile_error = CompileError("Unexpected next character", iterator.position());
return nullptr;
return source_result("Unexpected next character", iterator.position());
}
++iterator;
}
Token *target = reinterpret_cast<Token *>(malloc(tokens.size() * sizeof(Token) + sizeof(Token)));
int i = 0;
for (auto& token : tokens)
{
target[i] = std::move(token);
++i;
}
*length = i;
return target;
return source_result(tokens);
}
}
}

47
source/main.cpp Normal file
View File

@ -0,0 +1,47 @@
#include "elna/cl.hpp"
#include <filesystem>
#include <iostream>
#include <boost/program_options.hpp>
int main(int argc, char **argv)
{
boost::program_options::options_description visible{ "Allowed options" };
boost::program_options::options_description description;
boost::program_options::positional_options_description positional;
boost::program_options::variables_map variables_map;
visible.add_options()
("help", "Print this help message")
("output,o", boost::program_options::value<std::filesystem::path>(), "Output object file")
;
description.add_options()
("input", boost::program_options::value<std::filesystem::path>()->required())
;
description.add(visible);
positional.add("input", 1);
auto parsed = boost::program_options::command_line_parser(argc, argv)
.options(description).positional(positional)
.run();
boost::program_options::store(parsed, variables_map);
boost::program_options::notify(variables_map);
if (variables_map.count("help"))
{
std::cout << description << std::endl;
return 0;
}
const auto in_file = variables_map["input"].as<std::filesystem::path>();
std::filesystem::path out_file;
if (variables_map.count("output"))
{
out_file = variables_map["output"].as<std::filesystem::path>();
}
else
{
out_file = in_file.filename().replace_extension(".o");
}
return elna::compile(in_file, out_file);
}

View File

@ -1,15 +0,0 @@
import elna.backend;
import std.path;
import std.sumtype;
import tanya.container.string;
import tanya.memory.allocator;
import tanya.memory.mmappool;
extern(C)
int main(int argc, char** args)
{
defaultAllocator = MmapPool.instance;
// return generate(args[3], args[2]);
return compile(args[3], args[2]);
}

View File

@ -40,17 +40,22 @@ namespace elna
this->lhs = lhs;
this->rhs = rhs;
if (_operator == '+')
switch (_operator)
{
this->_operator = BinaryOperator::sum;
}
else if (_operator == '-')
{
this->_operator = BinaryOperator::subtraction;
}
else
{
throw std::logic_error("Invalid binary operator");
case '+':
this->_operator = BinaryOperator::sum;
break;
case '-':
this->_operator = BinaryOperator::subtraction;
break;
case '*':
this->_operator = BinaryOperator::multiplication;
break;
case '/':
this->_operator = BinaryOperator::division;
break;
default:
throw std::logic_error("Invalid binary operator");
}
}
@ -64,14 +69,14 @@ namespace elna
visitor->visit(this);
}
Block *parse(Token *tokenStream, std::size_t length)
Block *parse(lex::Token *tokenStream, std::size_t length)
{
return parseBlock(&tokenStream, &length);
}
Expression *parseFactor(Token **tokens, size_t *length)
Expression *parseFactor(lex::Token **tokens, size_t *length)
{
if ((*tokens)[0].of() == Token::TOKEN_IDENTIFIER)
if ((*tokens)[0].of() == lex::Token::type::identifier)
{
auto variable = new Variable();
variable->identifier = (*tokens)[0].identifier();
@ -79,7 +84,7 @@ namespace elna
--(*length);
return variable;
}
else if ((*tokens)[0].of() == Token::TOKEN_NUMBER)
else if ((*tokens)[0].of() == lex::Token::Token::type::number)
{
auto number = new Number();
number->value = (*tokens)[0].number();
@ -87,7 +92,7 @@ namespace elna
--(*length);
return number;
}
else if ((*tokens)[0].of() == Token::TOKEN_LEFT_PAREN)
else if ((*tokens)[0].of() == lex::Token::type::left_paren)
{
++(*tokens);
--(*length);
@ -102,15 +107,29 @@ namespace elna
return nullptr;
}
Expression *parseTerm(Token **tokens, size_t *length)
Expression *parseTerm(lex::Token **tokens, size_t *length)
{
return parseFactor(tokens, length);
auto lhs = parseFactor(tokens, length);
if (lhs == nullptr || *length == 0 || (*tokens)[0].of() != lex::Token::type::factor_operator)
{
return lhs;
}
auto _operator = (*tokens)[0].identifier()[0];
++(*tokens);
--(*length);
auto rhs = parseFactor(tokens, length);
if (rhs != nullptr)
{
return new BinaryExpression(lhs, rhs, _operator);
}
return nullptr;
}
Expression *parseExpression(Token **tokens, size_t *length)
Expression *parseExpression(lex::Token **tokens, size_t *length)
{
auto term = parseTerm(tokens, length);
if (term == nullptr || *length == 0 || (*tokens)[0].of() != Token::TOKEN_OPERATOR)
if (term == nullptr || *length == 0 || (*tokens)[0].of() != lex::Token::type::term_operator)
{
return term;
}
@ -122,17 +141,12 @@ namespace elna
if (expression != nullptr)
{
auto binaryExpression = new BinaryExpression(term, expression, _operator);
return binaryExpression;
}
else
{
return nullptr;
return new BinaryExpression(term, expression, _operator);
}
return nullptr;
}
Definition *parseDefinition(Token **tokens, size_t *length)
Definition *parseDefinition(lex::Token **tokens, size_t *length)
{
auto definition = new Definition();
definition->identifier = (*tokens)[0].identifier(); // Copy.
@ -141,7 +155,7 @@ namespace elna
++(*tokens); // Skip the equals sign.
*length -= 2;
if ((*tokens)[0].of() == Token::TOKEN_NUMBER)
if ((*tokens)[0].of() == lex::Token::type::number)
{
auto number = new Number();
number->value = (*tokens)[0].number();
@ -153,9 +167,9 @@ namespace elna
return nullptr;
}
Statement *parseStatement(Token **tokens, std::size_t *length)
Statement *parseStatement(lex::Token **tokens, std::size_t *length)
{
if ((*tokens)[0].of() == Token::TOKEN_BANG)
if ((*tokens)[0].of() == lex::Token::type::bang)
{
++(*tokens);
--(*length);
@ -174,7 +188,7 @@ namespace elna
return nullptr;
}
Definition **parseDefinitions(Token **tokens, size_t *length, size_t *resultLength)
Definition **parseDefinitions(lex::Token **tokens, size_t *length, size_t *resultLength)
{
++(*tokens); // Skip const.
--(*length);
@ -193,11 +207,11 @@ namespace elna
realloc(definitions, (*resultLength + 1) * sizeof(Definition*)));
definitions[(*resultLength)++] = definition;
if ((*tokens)[0].of() == Token::TOKEN_SEMICOLON)
if ((*tokens)[0].of() == lex::Token::type::semicolon)
{
break;
}
if ((*tokens)[0].of() == Token::TOKEN_COMMA)
if ((*tokens)[0].of() == lex::Token::type::comma)
{
++(*tokens);
--(*length);
@ -207,10 +221,10 @@ namespace elna
return definitions;
}
Block *parseBlock(Token **tokens, std::size_t *length)
Block *parseBlock(lex::Token **tokens, std::size_t *length)
{
auto block = new Block();
if ((*tokens)[0].of() == Token::TOKEN_LET)
if ((*tokens)[0].of() == lex::Token::type::let)
{
size_t length_ = 0;
auto constDefinitions = parseDefinitions(tokens, length, &length_);

View File

@ -2,26 +2,29 @@
namespace elna
{
CompileError::CompileError(const char *message, const Position position) noexcept
namespace source
{
error::error(const char *message, const source::position position) noexcept
{
this->message = message;
this->position = position;
}
char const *CompileError::what() const noexcept
char const *error::what() const noexcept
{
return this->message;
}
std::size_t CompileError::line() const noexcept
std::size_t error::line() const noexcept
{
return this->position.line;
}
std::size_t CompileError::column() const noexcept
std::size_t error::column() const noexcept
{
return this->position.column;
}
}
Symbol::Symbol(const char *name)
{

View File

@ -1,7 +1,7 @@
#include "elna/parser.hpp"
#include "elna/riscv.hpp"
#include <memory>
#include <type_traits>
#include <cstring>
namespace elna
{
@ -49,9 +49,14 @@ namespace elna
return *this;
}
std::uint8_t *Instruction::encode()
const std::byte *Instruction::cbegin()
{
return reinterpret_cast<std::uint8_t *>(&this->instruction);
return reinterpret_cast<std::byte *>(&this->instruction);
}
const std::byte *Instruction::cend()
{
return reinterpret_cast<std::byte *>(&this->instruction) + sizeof(this->instruction);
}
void RiscVVisitor::visit(Node *)
@ -154,7 +159,7 @@ namespace elna
std::size_t i = 0;
for (; i < constCount; ++i)
{
if (strcmp(variable->identifier, constNames[i]) == 0)
if (std::strcmp(variable->identifier, constNames[i]) == 0)
{
break;
}
@ -218,6 +223,10 @@ namespace elna
this->instructions[instructionsLength - 1] = Instruction(BaseOpcode::op)
.r(lhs_register, Funct3::sub, XRegister::a0, XRegister::t0, Funct7::sub);
break;
case BinaryOperator::multiplication:
this->instructions[instructionsLength - 1] = Instruction(BaseOpcode::op)
.r(lhs_register, Funct3::mul, XRegister::a0, XRegister::t0, Funct7::muldiv);
break;
}
}
@ -232,11 +241,10 @@ namespace elna
{
program.symbols[i] = visitor->references[i];
}
program.text = reinterpret_cast<unsigned char *>(malloc(sizeof(std::uint32_t) * visitor->instructionsLength));
for (std::size_t i = 0; i < visitor->instructionsLength; ++i)
{
memcpy(program.text + program.length, visitor->instructions[i].encode(), sizeof(std::uint32_t));
program.length += sizeof(std::uint32_t);
program.text.insert(program.text.cend(),
visitor->instructions[i].cbegin(), visitor->instructions[i].cend());
}
return program;
}

View File

@ -0,0 +1 @@
10

1
tests/multiply.eln Normal file
View File

@ -0,0 +1 @@
! 5 * 2

View File

@ -35,9 +35,14 @@ namespace elna
}
}
static std::filesystem::path in_build_directory()
{
return "build/riscv";
}
static std::string in_build_directory(const std::filesystem::path& path)
{
return "build/riscv" / path;
return in_build_directory() / path;
}
static int build_test(const std::filesystem::directory_entry& test_entry)
@ -136,7 +141,7 @@ namespace elna
int main()
{
boost::process::system("rake");
std::filesystem::create_directory(elna::in_build_directory());
std::cout << "Run all tests and check the results" << std::endl;
std::filesystem::path test_directory{ "tests" };