From ad29dbdc14fff9e3ab5182653ef15bdfad1d4b74 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Thu, 22 Feb 2024 21:29:25 +0100 Subject: [PATCH] Create a minimal interactive shell --- .gitignore | 5 +- CMakeLists.txt | 18 +- README | 45 - Rakefile | 64 -- TODO | 23 +- dub.json | 9 - include/elna/history.hpp | 28 + include/elna/interactive.hpp | 80 ++ include/elna/lexer.hpp | 85 ++ include/elna/result.hpp | 48 ++ include/elna/state.hpp | 160 ++++ include/elna/tester.hpp | 22 + shell/history.cpp | 49 ++ shell/interactive.cpp | 254 ++++++ shell/lexer.cpp | 124 +++ shell/main.cpp | 16 + shell/result.cpp | 25 + shell/state.cpp | 214 +++++ source/elna/arguments.d | 154 ---- source/elna/backend.d | 100 --- source/elna/elf.d | 1060 ------------------------ source/elna/extended.d | 335 -------- source/elna/ir.d | 278 ------- source/elna/lexer.d | 254 ------ source/elna/parser.d | 372 --------- source/elna/result.d | 107 --- source/elna/riscv.d | 364 -------- source/main.d | 33 - tests/7_member_sum.eln | 1 - tests/const_list.eln | 3 - tests/expectations/7_member_sum.txt | 1 - tests/expectations/const_list.txt | 1 - tests/expectations/left_nested_sum.txt | 1 - tests/expectations/print_number.txt | 1 - tests/expectations/subtraction.txt | 1 - tests/expectations/sum.txt | 1 - tests/expectations/sums.txt | 1 - tests/left_nested_sum.eln | 2 - tests/print_number.eln | 2 - tests/runner.cpp | 109 ++- tests/subtraction.eln | 2 - tests/sum.eln | 2 - tests/sums.eln | 2 - 43 files changed, 1192 insertions(+), 3264 deletions(-) delete mode 100644 README delete mode 100644 Rakefile delete mode 100644 dub.json create mode 100644 include/elna/history.hpp create mode 100644 include/elna/interactive.hpp create mode 100644 include/elna/lexer.hpp create mode 100644 include/elna/result.hpp create mode 100644 include/elna/state.hpp create mode 100644 include/elna/tester.hpp create mode 100644 shell/history.cpp create mode 100644 shell/interactive.cpp create mode 100644 shell/lexer.cpp create mode 100644 shell/main.cpp create mode 100644 shell/result.cpp create mode 100644 shell/state.cpp delete mode 100644 source/elna/arguments.d delete mode 100644 source/elna/backend.d delete mode 100644 source/elna/elf.d delete mode 100644 source/elna/extended.d delete mode 100644 source/elna/ir.d delete mode 100644 source/elna/lexer.d delete mode 100644 source/elna/parser.d delete mode 100644 source/elna/result.d delete mode 100644 source/elna/riscv.d delete mode 100644 source/main.d delete mode 100644 tests/7_member_sum.eln delete mode 100644 tests/const_list.eln delete mode 100644 tests/expectations/7_member_sum.txt delete mode 100644 tests/expectations/const_list.txt delete mode 100644 tests/expectations/left_nested_sum.txt delete mode 100644 tests/expectations/print_number.txt delete mode 100644 tests/expectations/subtraction.txt delete mode 100644 tests/expectations/sum.txt delete mode 100644 tests/expectations/sums.txt delete mode 100644 tests/left_nested_sum.eln delete mode 100644 tests/print_number.eln delete mode 100644 tests/subtraction.eln delete mode 100644 tests/sum.eln delete mode 100644 tests/sums.eln diff --git a/.gitignore b/.gitignore index d0d201a..a65d6bb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ -/.dub/ -/dub.selections.json /build/ +.cache/ +CMakeFiles/ +CMakeCache.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 66c650c..d316f83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,21 @@ cmake_minimum_required(VERSION 3.21) project(Elna) +set(CMAKE_EXPORT_COMPILE_COMMANDS 1) set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) -add_executable(tester tests/runner.cpp) + +find_package(Boost COMPONENTS filesystem REQUIRED) + +add_executable(tester tests/runner.cpp include/elna/tester.hpp) +target_include_directories(tester PRIVATE include) + +add_executable(elnsh shell/main.cpp + shell/interactive.cpp include/elna/interactive.hpp + shell/history.cpp include/elna/history.hpp + shell/state.cpp include/elna/state.hpp + shell/lexer.cpp include/elna/lexer.hpp + shell/result.cpp include/elna/result.hpp) +target_include_directories(elnsh PRIVATE include) +target_link_libraries(elnsh PUBLIC + ${Boost_FILESYSTEM_LIBRARY} +) diff --git a/README b/README deleted file mode 100644 index e496ae7..0000000 --- a/README +++ /dev/null @@ -1,45 +0,0 @@ -# Elna programming language - -Elna compiles simple mathematical operations to machine code. -The compiled program returns the result of the operation. - -## File extension - -.elna - -## Grammar PL/0 - -program = block "." ; - -block = [ "const" ident "=" number {"," ident "=" number} ";"] - [ "var" ident {"," ident} ";"] - { "procedure" ident ";" block ";" } statement ; - -statement = [ ident ":=" expression | "call" ident - | "?" ident | "!" expression - | "begin" statement {";" statement } "end" - | "if" condition "then" statement - | "while" condition "do" statement ]; - -condition = "odd" expression | - expression ("="|"#"|"<"|"<="|">"|">=") expression ; - -expression = [ "+"|"-"] term { ("+"|"-") term}; - -term = factor {("*"|"/") factor}; - -factor = ident | number | "(" expression ")"; - -## Operations - -"!" - Write a line. -"?" - Read user input. -"odd" - The only function, returns whether a number is odd. - -# Build and test - -```sh -cmake -B build -make -C build -./build/bin/tester -``` diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 7670654..0000000 --- a/Rakefile +++ /dev/null @@ -1,64 +0,0 @@ -require 'pathname' -require 'rake/clean' -require 'open3' - -DFLAGS = ['--warn-no-deprecated', '-L/usr/lib64/gcc-12'] -BINARY = 'build/bin/elna' -TESTS = FileList['tests/*.eln'].flat_map do |test| - build = Pathname.new 'build' - test_basename = Pathname.new(test).basename('') - - [build + 'riscv' + test_basename].map { |path| path.sub_ext('').to_path } -end - -SOURCES = FileList['source/**/*.d'] - -directory 'build' - -CLEAN.include 'build' -CLEAN.include '.dub' - -rule(/build\/riscv\/[^\/\.]+$/ => ->(file) { test_for_out(file, '.o') }) do |t| - sh '/opt/riscv/bin/riscv32-unknown-elf-ld', - '-o', t.name, - '-L/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/', - '-L/opt/riscv/riscv32-unknown-elf/lib', - '/opt/riscv/riscv32-unknown-elf/lib/crt0.o', - '/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtbegin.o', - t.source, - '--start-group', '-lgcc', '-lc', '-lgloss', '--end-group', - '/opt/riscv/lib/gcc/riscv32-unknown-elf/13.2.0/crtend.o' -end - -rule(/build\/riscv\/.+\.o$/ => ->(file) { test_for_object(file, '.eln') }) do |t| - Pathname.new(t.name).dirname.mkpath - sh BINARY, '-o', t.name, t.source -end - -file BINARY => SOURCES do |t| - sh({ 'DFLAGS' => (DFLAGS * ' ') }, 'dub', 'build', '--compiler=gdc') -end - -task default: TESTS -task default: BINARY - -desc 'Run unittest blocks' -task unittest: SOURCES do |t| - sh('dub', 'test', '--compiler=gdc-12') -end - -def test_for_object(out_file, extension) - test_source = Pathname - .new(out_file) - .sub_ext(extension) - .sub(/^build\/[[:alpha:]]+\//, 'tests/') - .to_path - [test_source, BINARY] -end - -def test_for_out(out_file, extension) - Pathname - .new(out_file) - .sub_ext(extension) - .to_path -end diff --git a/TODO b/TODO index e202e19..5b2a1dc 100644 --- a/TODO +++ b/TODO @@ -1,8 +1,15 @@ -- Mark all classes as extern(C++) to interface with C++. -- Some nodes assert(false, "Not implemented") because their logic is handled - in the parent nodes. Move this logic to the nodes themselves. -- Implement multiplication and division. -- Split tester.cpp into a header and implementation files. -- The generate function in backend.d contains the whole compilation logic. - It's not a backend task. -- Output assembly. +- After inserting autocompletion move the cursor to the end of the insertion. +- Support multiple selections for insertion in fzf. +- Vi mode. +- Don't hardcode fzf binary path. +- Persist the history. +- Send the word under the cursor to fzf as initial input. +- Replace hard coded ANSI codes with constants or functions. +- Show the hostname in the prompt. +- Crash if the cd directory doesn't exist. +- Home directory expansion. +- Paste pastes only the first character. +- When we read the key and an error occures how it should be handled? +- Kitty protocol works with UTF8 code points for escape sequences with modifiers. + They can be wider than an unsigned char. +- Add a bar with additional information under the prompt (edit_bar). diff --git a/dub.json b/dub.json deleted file mode 100644 index 743f619..0000000 --- a/dub.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "dependencies": { - "tanya": "~>0.19.0" - }, - "name": "elna", - "targetType": "executable", - "targetPath": "build/bin", - "mainSourceFile": "source/main.d" -} diff --git a/include/elna/history.hpp b/include/elna/history.hpp new file mode 100644 index 0000000..c7fb3d4 --- /dev/null +++ b/include/elna/history.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include + +namespace elna +{ + struct editor_history : public boost::noncopyable + { + using const_iterator = std::list::const_iterator; + + editor_history(); + + void push(const std::string& entry); + void clear(); + + const_iterator next() noexcept; + const_iterator prev() noexcept; + const_iterator cbegin() const noexcept; + const_iterator cend() const noexcept; + const_iterator current() const noexcept; + + private: + std::list commands; + std::list::const_iterator current_pointer; + }; +} diff --git a/include/elna/interactive.hpp b/include/elna/interactive.hpp new file mode 100644 index 0000000..f5c1fb3 --- /dev/null +++ b/include/elna/interactive.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include +#include + +#include "elna/lexer.hpp" +#include "elna/state.hpp" +#include "elna/history.hpp" + +namespace elna +{ + /** + * Runtime exception for non recoverable errors. + */ + struct interactive_exception : public std::runtime_error + { + /** + * Constructor. + */ + interactive_exception(); + }; + + /** + * Main loop. + */ + void loop(); + + /** + * Reads the next line. + * Returns no value upon receiving end of file. + * + * \param history Shell history. + * \return The read line. + */ + std::optional read_line(editor_history& history); + + /** + * Runs a built-in or a command. + * + * \param line The command and arguments. + * \return Whether the input shoud continued (no exit requested). + */ + bool execute(const std::vector& line); + + /** + * Runs the binary specified in its argument. + * + * \param program Program name in PATH. + * \param arguments Command arguments. + */ + void launch(const std::string& program, const std::vector& arguments); + + /** + * Enables the raw mode. + * + * \return Whether the operation was successful. + */ + bool enable_raw_mode(); + + /** + * Disables the raw mode. + */ + void disable_raw_mode(); + + /** + * Reads a key. + * + * \return Read character. + */ + key read_key(); + + /** + * Calls autocompletion. + * + * \return Selected item. + */ + std::string complete(); +} diff --git a/include/elna/lexer.hpp b/include/elna/lexer.hpp new file mode 100644 index 0000000..159cb8f --- /dev/null +++ b/include/elna/lexer.hpp @@ -0,0 +1,85 @@ +#pragma once + +#include + +#include "elna/result.hpp" + +namespace elna +{ + /** + * Range over the source text that keeps track of the current position. + */ + struct source + { + class const_iterator + { + std::string::const_iterator m_buffer; + Position m_position; + + const_iterator(std::string::const_iterator buffer, const Position& position); + + public: + using iterator_category = std::forward_iterator_tag; + using difference_type = ptrdiff_t; + using value_type = char; + using pointer = const value_type *; + using reference = const value_type&; + + const Position& position() const noexcept; + + reference operator*() const noexcept; + pointer operator->() const noexcept; + const_iterator& operator++(); + const_iterator& operator++(int); + bool operator==(const const_iterator& that) const noexcept; + bool operator!=(const const_iterator& that) const noexcept; + + friend source; + }; + + source(const std::string& buffer); + const_iterator begin() const; + const_iterator end() const; + + private: + const std::string& m_buffer; + }; + + /** + * Union type representing a single token. + */ + struct token + { + /** + * Token type. + */ + enum class type + { + word, + }; + + + /** + * Type of the token value. + */ + using value = std::string; + + token(const type of, source::const_iterator begin, source::const_iterator end); + + type of() const noexcept; + const value& identifier() const noexcept; + const Position& position() const noexcept; + + private: + std::string m_value; + Position m_position; + type m_type; + }; + + /** + * Split the source into tokens. + * + * \return Tokens or error. + */ + result> lex(const std::string& buffer); +} diff --git a/include/elna/result.hpp b/include/elna/result.hpp new file mode 100644 index 0000000..b14f5dd --- /dev/null +++ b/include/elna/result.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include + +namespace elna +{ + /** + * Position in the source text. + */ + struct Position + { + /// 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. + */ + CompileError(char const *message, Position position) noexcept; + + /// Error text. + char const *message() 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 + using result = boost::outcome_v2::result; +} diff --git a/include/elna/state.hpp b/include/elna/state.hpp new file mode 100644 index 0000000..b0284cd --- /dev/null +++ b/include/elna/state.hpp @@ -0,0 +1,160 @@ +#include +#include +#include +#include + +namespace elna +{ + constexpr const char *erase_line = "\x1b[2K"; + constexpr const char *start_kitty_keybaord = "\x1b[>1u"; + constexpr const char *end_kitty_keybaord = "\x1b[ store; + std::uint8_t modifiers; + + public: + key(); + explicit key(const unsigned char c, const std::uint8_t modifiers = 0); + explicit key(const unsigned char c, const modifier modifiers); + explicit key(const special_key control, const std::uint8_t modifiers = 0); + explicit key(const special_key control, const modifier modifiers); + + bool operator==(const unsigned char c) const; + friend bool operator==(const unsigned char c, const key& that); + bool operator==(const special_key control) const; + friend bool operator==(const special_key control, const key& that); + bool operator==(const key& that) const; + bool operator!=(const unsigned char c) const; + friend bool operator!=(const unsigned char c, const key& that); + bool operator!=(const special_key control) const; + friend bool operator!=(const special_key control, const key& that); + bool operator!=(const key& that) const; + + unsigned char character() const; + special_key control() const; + bool modified(const std::uint8_t modifiers) const noexcept; + bool modified(const modifier modifiers) const noexcept; + + bool is_character() const noexcept; + bool is_control() const noexcept; + bool empty() const noexcept; + }; + + /** + * The action should be performed after updating the editor state. + */ + enum class action + { + redraw, ///< Redraw everything. + write, ///< Write a single character. + finalize, ///< Finalize command input. + move, ///< Move the cursor. + ignore, ///< Do nothing. + complete, ///< Complete the input. + history, ///< Navigate the history. + }; + + /** + * The line editor with its state. + */ + class editor_state + { + std::string input; + std::string prompt = "> "; + std::size_t position{ 1 }; + + public: + editor_state(); + + /** + * Returns the current input gathered by the lin editor. + * + * \return User input. + */ + const std::string& command_line() const noexcept; + + /** + * Processes the next keypress by changing the line editor state. + * + * \param key Pressed key. + * \return Action to do after the key was pressed. + */ + action process_keypress(const key& press); + + /** + * Clears editors working area and writes drawing sequences into the + * buffer. + * + * \param T Output range for bytes. + * \param output Output buffer. + */ + template + void draw(T output) const + { + std::string code; + + std::copy(erase_line, erase_line + strlen(erase_line), output); + + code = cursor_to_column(); + std::copy(std::cbegin(code), std::cend(code), output); + + std::copy(std::cbegin(this->prompt), std::cend(this->prompt), output); + std::copy(std::cbegin(this->input), std::cend(this->input), output); + + code = cursor_to_column(absolute_position()); + std::copy(std::cbegin(code), std::cend(code), output); + } + + /** + * Computes the position in the terminal line. + * + * \return Position in the terminal line. + */ + std::size_t absolute_position() const noexcept; + + /** + * Inserts text at cursor position. + * + * \param text Text to insert. + */ + void insert(const std::string& text); + + /** + * Replaces the user input with the given string. + * + * \param text New user input string. + */ + void load(const std::string& text); + }; +} diff --git a/include/elna/tester.hpp b/include/elna/tester.hpp new file mode 100644 index 0000000..ee34351 --- /dev/null +++ b/include/elna/tester.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +namespace elna +{ + class test_results final + { + std::uint32_t m_total{ 0 }; + std::uint32_t m_passed{ 0 }; + + public: + test_results() = default; + + std::uint32_t total() const noexcept; + std::uint32_t passed() const noexcept; + std::uint32_t failed() const noexcept; + + int exit_code() const noexcept; + void add_exit_code(const int exit_code) noexcept; + }; +} diff --git a/shell/history.cpp b/shell/history.cpp new file mode 100644 index 0000000..bb58dd4 --- /dev/null +++ b/shell/history.cpp @@ -0,0 +1,49 @@ +#include "elna/history.hpp" + +namespace elna +{ + editor_history::editor_history() + : commands{}, current_pointer(commands.cend()) + { + } + + void editor_history::push(const std::string& entry) + { + commands.push_front(entry); + current_pointer = commands.cbegin(); + } + + void editor_history::clear() + { + commands.clear(); + current_pointer = commands.cend(); + } + + editor_history::const_iterator editor_history::prev() noexcept + { + if (this->current_pointer != cbegin()) + { + this->current_pointer = std::prev(this->current_pointer); + } + return this->current_pointer; + } + + editor_history::const_iterator editor_history::next() noexcept + { + if (this->current_pointer != cend()) + { + this->current_pointer = std::next(this->current_pointer); + } + return this->current_pointer; + } + + editor_history::const_iterator editor_history::cbegin() const noexcept + { + return this->commands.cbegin(); + } + + editor_history::const_iterator editor_history::cend() const noexcept + { + return this->commands.cend(); + } +} diff --git a/shell/interactive.cpp b/shell/interactive.cpp new file mode 100644 index 0000000..22af464 --- /dev/null +++ b/shell/interactive.cpp @@ -0,0 +1,254 @@ +#include +#include +#include + +#include "elna/interactive.hpp" + +namespace elna +{ + static termios original_termios; + + interactive_exception::interactive_exception() + : runtime_error("read") + { + } + + template + static std::pair read_number() + { + T position{ 0 }; + unsigned char c{ 0 }; + + while (read(STDIN_FILENO, &c, 1) == 1 && std::isdigit(c)) + { + position = position * 10 + (c - '0'); + } + return std::make_pair(position, c); + } + + void loop() + { + editor_history history; + do + { + auto line = read_line(history); + + if (!line.has_value()) + { + write(STDOUT_FILENO, "\r\n", 2); + break; + } + history.push(line.value().command_line()); + auto tokens = lex(line.value().command_line()); + + if (!execute(tokens.assume_value())) + { + break; + } + } + while (true); + } + + std::optional read_line(editor_history& history) + { + editor_state state; + std::string buffer; + + state.draw(std::back_inserter(buffer)); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + + while (true) + { + buffer.clear(); + auto pressed_key = read_key(); + + if (pressed_key.empty()) + { + continue; + } + switch (state.process_keypress(pressed_key)) + { + case action::finalize: + if (pressed_key == key('d', modifier::ctrl)) + { + return std::optional(); + } + else if (pressed_key == '\r') + { + write(STDOUT_FILENO, "\r\n", 2); + return std::make_optional(state); + } + break; + case action::complete: + write(STDOUT_FILENO, "\x1b[1E\x1b[2K", 8); + state.insert(complete()); + write(STDOUT_FILENO, "\x1b[1A", 4); + state.draw(std::back_inserter(buffer)); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + break; + case action::redraw: + state.draw(std::back_inserter(buffer)); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + break; + case action::write: + write(STDOUT_FILENO, &pressed_key, 1); + break; + case action::move: + buffer = cursor_to_column(state.absolute_position()); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + break; + case action::ignore: + break; + case action::history: + editor_history::const_iterator history_iterator = history.cend(); + + if (pressed_key == key(special_key::arrow_up, modifier::ctrl)) + { + history_iterator = history.next(); + } + else if (pressed_key == key(special_key::arrow_down, modifier::ctrl)) + { + history_iterator = history.prev(); + } + if (history_iterator != history.cend()) + { + state.load(*history_iterator); + state.draw(std::back_inserter(buffer)); + write(STDOUT_FILENO, buffer.data(), buffer.size()); + } + break; + } + } + } + + key read_key() + { + char c{ 0 }; + + if (read(STDIN_FILENO, &c, 1) == -1 && errno != EAGAIN) + { + throw interactive_exception(); + } + if (c != '\x1b') + { + return key(c); + } + if (read(STDIN_FILENO, &c, 1) != 1 || c != '[') + { + return key(); + } + auto [number, last_char] = read_number(); + std::uint8_t modifiers{ 0 }; + + if (last_char == ';') + { + auto modifier_response = read_number(); + modifiers = modifier_response.first; + last_char = modifier_response.second; + } + if (number == 0 || number == 1) + { + switch (last_char) + { + case 'A': + return key(special_key::arrow_up, modifiers); + case 'B': + return key(special_key::arrow_down, modifiers); + case 'C': + return key(special_key::arrow_right, modifiers); + case 'D': + return key(special_key::arrow_left, modifiers); + case 'H': + return key(special_key::home_key, modifiers); + case 'F': + return key(special_key::end_key, modifiers); + } + } + else if (last_char == '~') + { + switch (number) + { + case 5: + return key(special_key::page_up, modifiers); + case 6: + return key(special_key::page_up, modifiers); + case 7: + return key(special_key::home_key, modifiers); + case 8: + return key(special_key::end_key, modifiers); + } + } + else if (last_char == 'u') + { + return key(number, modifiers); + } + return key(); + } + + bool execute(const std::vector& tokens) + { + if (tokens.empty()) + { + return true; + } + std::string program = tokens.front().identifier(); + if (program == "exit") + { + return false; + } + else if (program == "cd") + { + std::filesystem::current_path(tokens[1].identifier()); + + return true; + } + std::vector arguments; + for (auto argument = tokens.cbegin() + 1; argument != std::cend(tokens); ++argument) + { + arguments.push_back(argument->identifier()); + } + launch(program, arguments); + return true; + } + + void launch(const std::string& program, const std::vector& arguments) + { + boost::process::system(boost::process::search_path(program), arguments); + } + + bool enable_raw_mode() + { + if (tcgetattr(STDIN_FILENO, &original_termios) == -1) + { + return false; + } + termios raw = original_termios; + + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + raw.c_oflag &= ~(OPOST); + raw.c_cflag |= CS8; + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + + write(STDOUT_FILENO, start_kitty_keybaord, strlen(start_kitty_keybaord)); + + return tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw) != -1; + } + + void disable_raw_mode() + { + write(STDOUT_FILENO, end_kitty_keybaord, strlen(end_kitty_keybaord)); + tcsetattr(STDIN_FILENO, TCSAFLUSH, &original_termios); + } + + std::string complete() + { + boost::process::ipstream output; + boost::process::system("/usr/bin/fzf", "--height=10", "--layout=reverse", + boost::process::std_out > output); + + std::string selections; + std::getline(output, selections, '\n'); + + return selections; + } +} diff --git a/shell/lexer.cpp b/shell/lexer.cpp new file mode 100644 index 0000000..e7ff7fc --- /dev/null +++ b/shell/lexer.cpp @@ -0,0 +1,124 @@ +#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), Position()); + } + + source::const_iterator source::end() const + { + 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) + : m_buffer(buffer), m_position(start_position) + { + } + + const 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 Position& token::position() const noexcept + { + return m_position; + } + + result> lex(const std::string& buffer) + { + std::vector 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; + } +} diff --git a/shell/main.cpp b/shell/main.cpp new file mode 100644 index 0000000..19b1892 --- /dev/null +++ b/shell/main.cpp @@ -0,0 +1,16 @@ +#include +#include +#include "elna/interactive.hpp" + +int main() +{ + if (!elna::enable_raw_mode()) + { + std::perror("tcsetattr"); + return EXIT_FAILURE; + } + std::atexit(elna::disable_raw_mode); + elna::loop(); + + return EXIT_SUCCESS; +} diff --git a/shell/result.cpp b/shell/result.cpp new file mode 100644 index 0000000..8531512 --- /dev/null +++ b/shell/result.cpp @@ -0,0 +1,25 @@ +#include "elna/result.hpp" + +namespace elna +{ + CompileError::CompileError(char const *message, Position position) noexcept + { + this->message_ = message; + this->position_ = position; + } + + char const *CompileError::message() const noexcept + { + return this->message_; + } + + std::size_t CompileError::line() const noexcept + { + return this->position_.line; + } + + std::size_t CompileError::column() const noexcept + { + return this->position_.column; + } +} diff --git a/shell/state.cpp b/shell/state.cpp new file mode 100644 index 0000000..61b41c8 --- /dev/null +++ b/shell/state.cpp @@ -0,0 +1,214 @@ +#include "elna/state.hpp" + +namespace elna +{ + std::string cursor_to_column(const std::uint16_t position) + { + std::string code = std::to_string(position); + code.insert(0, "\x1b["); + code.push_back('G'); + + return code; + } + + key::key() + : store(static_cast(0x1b)) + { + } + + key::key(const unsigned char c, const std::uint8_t modifiers) + : store(c), modifiers(modifiers == 0 ? 0 : modifiers - 1) + { + } + + key::key(const unsigned char c, const modifier modifiers) + : store(c), modifiers(static_cast(modifiers)) + { + } + + key::key(const special_key control, const std::uint8_t modifiers) + : store(control), modifiers(modifiers == 0 ? 0 : modifiers - 1) + { + } + + key::key(const special_key control, const modifier modifiers) + : store(control), modifiers(static_cast(modifiers)) + { + } + + unsigned char key::character() const + { + return std::get(this->store); + } + + special_key key::control() const + { + return std::get(this->store); + } + + bool key::modified(const std::uint8_t modifiers) const noexcept + { + return this->modifiers == modifiers; + } + + bool key::modified(const modifier modifiers) const noexcept + { + return this->modifiers == static_cast(modifiers); + } + + bool key::operator==(const unsigned char c) const + { + return std::holds_alternative(this->store) + && std::get(this->store) == c; + } + + bool key::operator==(const special_key control) const + { + return std::holds_alternative(this->store) + && std::get(this->store) == control; + } + + bool key::operator==(const key& that) const + { + return this->store == that.store; + } + + bool key::operator!=(const unsigned char c) const + { + return !(*this == c); + } + + bool key::operator!=(const special_key control) const + { + return !(*this == control); + } + + bool key::operator!=(const key& that) const + { + return !(*this == that); + } + + bool key::is_character() const noexcept + { + return std::holds_alternative(this->store); + } + + bool key::is_control() const noexcept + { + return std::holds_alternative(this->store); + } + + bool key::empty() const noexcept + { + return *this == '\x1b'; + } + + bool operator==(const special_key control, const key& that) + { + return that == control; + } + + bool operator==(const char c, const key& that) + { + return that == c; + } + + bool operator!=(const special_key control, const key& that) + { + return that != control; + } + + bool operator!=(const char c, const key& that) + { + return that != c; + } + + editor_state::editor_state() + { + this->input.reserve(1024); + } + + void editor_state::load(const std::string& text) + { + this->input = text; + } + + const std::string& editor_state::command_line() const noexcept + { + return this->input; + } + + std::size_t editor_state::absolute_position() const noexcept + { + return this->prompt.size() + this->position; + } + + void editor_state::insert(const std::string& text) + { + this->input.insert(std::end(this->input), std::cbegin(text), std::cend(text)); + } + + action editor_state::process_keypress(const key& press) + { + if (press.is_control()) + { + switch (press.control()) + { + case special_key::arrow_left: + if (this->position > 1) + { + --this->position; + } + return action::move; + case special_key::arrow_right: + if (this->position + 1 < this->input.size() + this->prompt.size()) + { + ++this->position; + } + return action::move; + case special_key::arrow_up: + case special_key::arrow_down: + return action::history; + case special_key::home_key: + this->position = 1; + return action::move; + case special_key::end_key: + this->position = this->input.size() + 1; + return action::move; + default: + return action::ignore; + } + } + else if (press.modified(modifier::ctrl)) + { + switch (press.character()) + { + case 'd': + return action::finalize; + case 't': + return action::complete; + default: + return action::ignore; + } + } + else + { + switch (press.character()) + { + case 127: // Backspace. + if (this->position <= this->input.size() + 1 && this->position > 1) + { + --this->position; + this->input.erase(this->position - 1, 1); + } + return action::redraw; + case '\r': + return action::finalize; + default: + ++this->position; + this->input.push_back(press.character()); + return action::write; + } + } + } +} diff --git a/source/elna/arguments.d b/source/elna/arguments.d deleted file mode 100644 index 1ad928a..0000000 --- a/source/elna/arguments.d +++ /dev/null @@ -1,154 +0,0 @@ -/** - * 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); - } -} diff --git a/source/elna/backend.d b/source/elna/backend.d deleted file mode 100644 index 995efda..0000000 --- a/source/elna/backend.d +++ /dev/null @@ -1,100 +0,0 @@ -module elna.backend; - -import core.stdc.stdio; -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 = defaultAllocator.make!TransformVisitor(); - auto ir = transformVisitor.visit(ast.result); - defaultAllocator.dispose(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; -} diff --git a/source/elna/elf.d b/source/elna/elf.d deleted file mode 100644 index 81dd7da..0000000 --- a/source/elna/elf.d +++ /dev/null @@ -1,1060 +0,0 @@ -module elna.elf; - -import elna.extended; -import elna.result; -import std.algorithm; -import tanya.container.array; -import tanya.container.hashtable; -import tanya.container.string; - -/// Unsigned program address. -alias Elf64_Addr = ulong; -/// Unsigned file offset. -alias Elf64_Off = ulong; -/// Unsigned medium integer. -alias Elf64_Half = ushort; -/// Unsigned integer. -alias Elf64_Word = uint; -/// Signed integer. -alias Elf64_Sword = int; -/// Unsigned long integer. -alias Elf64_Xword = ulong; -/// Signed long integer. -alias Elf64_Sxword = long; - -/// Unsigned program address. -alias Elf32_Addr = uint; -/// Unsigned file offset. -alias Elf32_Off = uint; -/// Unsigned medium integer. -alias Elf32_Half = ushort; -/// Unsigned integer. -alias Elf32_Word = uint; -/// Signed integer. -alias Elf32_Sword = int; - -enum : size_t -{ - /// File identification. - EI_MAG0 = 0, - /// File identification. - EI_MAG1 = 1, - /// File identification. - EI_MAG2 = 2, - /// File identification. - EI_MAG3 = 3, - /// File class. - EI_CLASS = 4, - /// Data encoding. - EI_DATA = 5, - /// File version. - EI_VERSION = 6, - /// Start of padding bytes. - EI_PAD = 7, - /// Size of e_ident[] - EI_NIDENT = 16 -} - -enum : ubyte -{ - /// e_ident[EI_MAG0]. - ELFMAG0 = 0x7f, - /// e_ident[EI_MAG1]. - ELFMAG1 = 'E', - /// e_ident[EI_MAG2]. - ELFMAG2 = 'L', - /// e_ident[EI_MAG3]. - ELFMAG3 = 'F' -} - -/** - * File header. - */ -struct Elf64_Ehdr -{ - /// ELF identification. - ubyte[EI_NIDENT] e_ident; - /// Object file type. - Elf64_Half e_type; - /// Machine type. - Elf64_Half e_machine; - /// Object file version - Elf64_Word e_version; - /// Entry point address. - Elf64_Addr e_entry; - /// Program header offset. - Elf64_Off e_phoff; - /// Section header offset. - Elf64_Off e_shoff; - /// Processor-specific flags. - Elf64_Word e_flags; - /// ELF header size. - Elf64_Half e_ehsize; - /// Size of program header entry. - Elf64_Half e_phentsize; - /// Number of program header entries. - Elf64_Half e_phnum; - /// Size of section header entry. - Elf64_Half e_shentsize; - /// Number of section header entries. - Elf64_Half e_shnum; - /// Section name string table index. - Elf64_Half e_shstrndx; -} - -/** - * File header. - */ -struct Elf32_Ehdr { - /// ELF identification. - ubyte[EI_NIDENT] e_ident; - /// Object file type. - Elf32_Half e_type; - /// Machine type. - Elf32_Half e_machine; - /// Object file version - Elf32_Word e_version; - /// Entry point address. - Elf32_Addr e_entry; - /// Program header offset. - Elf32_Off e_phoff; - /// Section header offset. - Elf32_Off e_shoff; - /// Processor-specific flags. - Elf32_Word e_flags; - /// ELF header size. - Elf32_Half e_ehsize; - /// Size of program header entry. - Elf32_Half e_phentsize; - /// Number of program header entries. - Elf32_Half e_phnum; - /// Size of section header entry. - Elf32_Half e_shentsize; - /// Number of section header entries. - Elf32_Half e_shnum; - /// Section name string table index. - Elf32_Half e_shstrndx; -} - -/** - * Section header. - */ -struct Elf64_Shdr -{ - /// Section name. - Elf64_Word sh_name; - /// Section type. - Elf64_Word sh_type; - /// Section attributes. - Elf64_Xword sh_flags; - /// Virtual address in memory. - Elf64_Addr sh_addr; - /// Offset in file. - Elf64_Off sh_offset; - /// Size of section. - Elf64_Xword sh_size; - /// Link to other section. - Elf64_Word sh_link; - /// Miscellaneous information. - Elf64_Word sh_info; - /// Address alignment boundary. - Elf64_Xword sh_addralign; - /// Size of entries, if section has table. - Elf64_Xword sh_entsize; -} - -/** - * Section header. - */ -struct Elf32_Shdr -{ - /// Section name. - Elf32_Word sh_name; - /// Section type. - Elf32_Word sh_type; - /// Section attributes. - Elf32_Word sh_flags; - /// Virtual address in memory. - Elf32_Addr sh_addr; - /// Offset in file. - Elf32_Off sh_offset; - /// Size of section. - Elf32_Word sh_size; - /// Link to other section. - Elf32_Word sh_link; - /// Miscellaneous information. - Elf32_Word sh_info; - /// Address alignment boundary. - Elf32_Word sh_addralign; - /// Size of entries, if section has table. - Elf32_Word sh_entsize; -} - -/** - * Symbol table entry. - */ -struct Elf64_Sym -{ - /// Symbol name. - Elf64_Word st_name; - /// Type and Binding attributes. - ubyte st_info; - /// Reserved. - ubyte st_other; - /// Section table index. - Elf64_Half st_shndx; - /// Symbol value. - Elf64_Addr st_value; - /// Size of object (e.g., common). - Elf64_Xword st_size; -} - -/** - * Relocation entry. - */ -struct Elf64_Rel -{ - /// Address of reference. - Elf64_Addr r_offset; - /// Symbol index and type of relocation. - Elf64_Xword r_info; -} - -/** - * Relocation entry with explicit addend. - */ -struct Elf64_Rela -{ - /// Address of reference. - Elf64_Addr r_offset; - /// Symbol index and type of relocation. - Elf64_Xword r_info; - /// Constant part of expression. - Elf64_Sxword r_addend; -} - -/** - * Symbol table entry. - */ -struct Elf32_Sym -{ - /// Symbol name. - Elf32_Word st_name; - /// Symbol value. - Elf32_Addr st_value; - /// Size of object (e.g., common). - Elf32_Word st_size; - /// Type and Binding attributes. - ubyte st_info; - /// Reserved. - ubyte st_other; - /// Section table index. - Elf32_Half st_shndx; -} - -/** - * Relocation entry. - */ -struct Elf32_Rel -{ - /// Address of reference. - Elf32_Addr r_offset; - /// Symbol index and type of relocation. - Elf32_Word r_info; -} - -/** - * Relocation entry with explicit addend. - */ -struct Elf32_Rela -{ - /// Address of reference. - Elf32_Addr r_offset; - /// Symbol index and type of relocation. - Elf32_Word r_info; - /// Constant part of expression. - Elf32_Sword r_addend; -} - -/// Section Types, sh_type. -enum : Elf64_Word -{ - /// Marks an unused section header. - SHT_NULL = 0, - /// Contains information defined by the program. - SHT_PROGBITS = 1, - /// Contains a linker symbol table. - SHT_SYMTAB = 2, - /// Contains a string table. - SHT_STRTAB = 3, - /// Contains “Rela” type relocation entries. - SHT_RELA = 4, - /// Contains a symbol hash table - SHT_HASH = 5, - /// Contains dynamic linking tables - SHT_DYNAMIC = 6, - /// Contains note information - SHT_NOTE = 7, - /// Contains uninitialized space; does not occupy any space in the file. - SHT_NOBITS = 8, - /// Contains "Rel" type relocation entries. - SHT_REL = 9, - /// Reserved. - SHT_SHLIB = 10, - /// Contains a dynamic loader symbol table. - SHT_DYNSYM = 11, - /// Environment-specific use. - SHT_LOOS = 0x60000000, - SHT_HIOS = 0x6FFFFFFF, - /// Processor-specific use. - SHT_LOPROC = 0x70000000, - SHT_HIPROC = 0x7FFFFFFF, -} - -/** - * Section Attributes, sh_flags. - */ -enum : Elf64_Xword -{ - /// Section contains writable data. - SHF_WRITE = 0x1, - /// Section is allocated in memory image of program. - SHF_ALLOC = 0x2, - /// Section contains executable instructions. - SHF_EXECINSTR = 0x4, - /// Environment-specific use. - SHF_MASKOS = 0x0F000000, - /// Processor-specific use. - SHF_MASKPROC = 0xF0000000, -} - -ubyte ELF64_R_SYM(Elf64_Xword i) @nogc nothrow pure @safe -{ - return cast(ubyte) (i >> 32); -} - -Elf64_Xword ELF64_R_TYPE(Elf64_Xword i) @nogc nothrow pure @safe -{ - return i & 0xffffffffL; -} - -Elf64_Xword ELF64_R_INFO(Elf64_Xword s, Elf64_Xword t) @nogc nothrow pure @safe -{ - return (s << 32) + (t & 0xffffffffL); -} - -ubyte ELF32_ST_BIND(ubyte i) @nogc nothrow pure @safe -{ - return i >> 4; -} - -ubyte ELF32_ST_TYPE(ubyte i) @nogc nothrow pure @safe -{ - return i & 0xf; -} - -ubyte ELF32_ST_INFO(Elf32_Word b, ubyte t) @nogc nothrow pure @safe -{ - return cast(ubyte) ((b << 4) + (t & 0xf)); -} - -Elf32_Word ELF32_R_SYM(Elf32_Word i) @nogc nothrow pure @safe -{ - return i >> 8; -} - -ubyte ELF32_R_TYPE(Elf32_Word i) @nogc nothrow pure @safe -{ - return cast(ubyte) i; -} - -Elf32_Word ELF32_R_INFO(Elf32_Word s, Elf32_Word t) @nogc nothrow pure @safe -{ - return (s << 8) + t; -} - -enum : uint -{ - /// Not visible outside the object file. - STB_LOCAL = 0, - /// Global symbol, visible to all object files. - STB_GLOBAL = 1, - /// Global scope, but with lower precedence than global symbols. - STB_WEAK = 2, - /// Environment-specific use. - STB_LOOS = 10, - STB_HIOS = 12, - /// Processor-specific use. - STB_LOPROC = 13, - STB_HIPROC = 15, -} - -enum : uint -{ - /// No type specified (e.g., an absolute symbol). - STT_NOTYPE = 0, - /// Data object. - STT_OBJECT = 1, - /// Function entry point. - STT_FUNC = 2, - /// Symbol is associated with a section. - STT_SECTION = 3, - /// Source file associated with the object file. - STT_FILE = 4, - /// Environment-specific use. - STT_LOOS = 10, - STT_HIOS = 12, - /// Processor-specific use. - STT_LOPROC = 13, - STT_HIPROC = 15, -} - -/// Special Section Indices. -enum : ushort -{ - /// Used to mark an undefined or meaningless section reference. - SHN_UNDEF = 0, - /// This value specifies the lower bound of the range of reserved indexes. - SHN_LORESERVE = 0xff00, - /// Processor-specific use. - SHN_LOPROC = 0xFF00, - SHN_HIPROC = 0xFF1F, - /// Environment-specific use. - SHN_LOOS = 0xFF20, - SHN_HIOS = 0xFF3F, - /// Indicates that the corresponding reference is an absolute value. - SHN_ABS = 0xFFF1, - /** - * Indicates a symbol that has been declared as a common block (Fortran - * COMMON or C tentative declaration). - */ - SHN_COMMON = 0xFFF2, -} - -/** - * Object File Classes, e_ident[EI_CLASS]. - */ -enum : ubyte -{ - /// Invalid class. - ELFCLASSNONE = 0, - /// 32-bit objects. - ELFCLASS32 = 1, - /// 64-bit objects. - ELFCLASS64 = 2 -} - -enum : ubyte { - /// Invalid version. - EV_NONE = 0, - /// Current version. - EV_CURRENT = 1 -} - -/** - * Data Encodings, e_ident[EI_DATA]. - */ -enum : ubyte -{ - /// Object file data structures are little-endian. - ELFDATA2LSB = 1, - /// Object file data structures are big-endian. - ELFDATA2MSB = 2, -} - -/** - * Operating System and ABI Identifiers, e_ident[EI_OSABI]. - */ -enum EI_OSABI : ubyte -{ - /// System V ABI. - ELFOSABI_SYSV = 0, - /// HP-UX operating system. - ELFOSABI_HPUX = 1, - /// Standalone (embedded) application. - ELFOSABI_STANDALONE = 255, -} - -enum : Elf64_Half -{ - ET_NONE = 0, /// No file type. - ET_REL = 1, /// Relocatable object file. - ET_EXEC = 2, /// Executable file. - ET_DYN = 3, /// Shared object file. - ET_CORE = 4, /// Core file. - ET_LOOS = 0xFE00, /// Environment-specific use. - ET_HIOS = 0xFEFF, - ET_LOPROC = 0xFF00, /// Processor-specific use. - ET_HIPROC = 0xFFFF, -} - -enum : ubyte -{ - R_RISCV_NONE = 0, - /// 32-bit relocation. - R_RISCV_32 = 1, - /// 64-bit relocation. - R_RISCV_64 = 2, - /// Relocation against a local symbol in a shared object. - R_RISCV_RELATIVE = 3, - /// Must be in executable; not allowed in shared library. - R_RISCV_COPY = 4, - /// Indicates the symbol associated with a PLT entry. - R_RISCV_JUMP_SLOT = 5, - R_RISCV_TLS_DTPMOD32 = 6, - R_RISCV_TLS_DTPMOD64 = 7, - R_RISCV_TLS_DTPREL32 = 8, - R_RISCV_TLS_DTPREL64 = 9, - R_RISCV_TLS_TPREL32 = 10, - R_RISCV_TLS_TPREL64 = 11, - /// 12-bit PC-relative branch offset. - R_RISCV_BRANCH = 16, - /// 20-bit PC-relative jump offset. - R_RISCV_JAL = 17, - /// 32-bit PC-relative function call, macros `call`, `tail`. - R_RISCV_CALL = 18, - /// 32-bit PC-relative function call, macros `call`, `tail` (PIC). - R_RISCV_CALL_PLT = 19, - /// High 20 bits of 32-bit PC-relative GOT access, `%got_pcrel_hi(symbol)`. - R_RISCV_GOT_HI20 = 20, - /// High 20 bits of 32-bit PC-relative TLS IE GOT access, macro `la.tls.ie`. - R_RISCV_TLS_GOT_HI20 = 21, - /// High 20 bits of 32-bit PC-relative TLS GD GOT reference, macro `la.tls.gd`. - R_RISCV_TLS_GD_HI20 = 22, - /// High 20 bits of 32-bit PC-relative reference, `%pcrel_hi(symbol)`. - R_RISCV_PCREL_HI20 = 23, - /// Low 12 bits of a 32-bit PC-relative, `%pcrel_lo(address of %pcrel_hi)`, the addend must be 0. - R_RISCV_PCREL_LO12_I = 24, - /// Low 12 bits of a 32-bit PC-relative, `%pcrel_lo(address of %pcrel_hi)`, the addend must be 0. - R_RISCV_PCREL_LO12_S = 25, - /// High 20 bits of 32-bit absolute address, `%hi(symbol)`. - R_RISCV_HI20 = 26, - /// Low 12 bits of 32-bit absolute address, `%lo(symbol)`. - R_RISCV_LO12_I = 27, - /// Low 12 bits of 32-bit absolute address, `%lo(symbol)`. - R_RISCV_LO12_S = 28, - /// High 20 bits of TLS LE thread pointer offset, `%tprel_hi(symbol)`. - R_RISCV_TPREL_HI20 = 29, - /// Low 12 bits of TLS LE thread pointer offset, `%tprel_lo(symbol)`. - R_RISCV_TPREL_LO12_I = 30, - /// Low 12 bits of TLS LE thread pointer offset, `%tprel_lo(symbol)`. - R_RISCV_TPREL_LO12_S = 31, - /// TLS LE thread pointer usage, `%tprel_add(symbol)`. - R_RISCV_TPREL_ADD = 32, - /// 8-bit label addition. - R_RISCV_ADD8 = 33, - /// 16-bit label addition. - R_RISCV_ADD16 = 34, - /// 32-bit label addition. - R_RISCV_ADD32 = 35, - /// 64-bit label addition. - R_RISCV_ADD64 = 36, - /// 8-bit label subtraction. - R_RISCV_SUB8 = 37, - /// 16-bit label subtraction. - R_RISCV_SUB16 = 38, - /// 32-bit label subtraction. - R_RISCV_SUB32 = 39, - /// 64-bit label subtraction. - R_RISCV_SUB64 = 40, - /// GNU {Cpp} vtable hierarchy. - R_RISCV_GNU_VTINHERIT = 41, - /// GNU {Cpp} vtable member usage. - R_RISCV_GNU_VTENTRY = 42, - /// Alignment statement. - R_RISCV_ALIGN = 43, - /// 8-bit PC-relative branch offset. - R_RISCV_RVC_BRANCH = 44, - /// 11-bit PC-relative jump offset. - R_RISCV_RVC_JUMP = 45, - /// High 6 bits of 18-bit absolute address. - R_RISCV_RVC_LUI = 46, - /// Instruction can be relaxed, paired with a normal relocation at the same address. - R_RISCV_RELAX = 51, - /// Local label subtraction. - R_RISCV_SUB6 = 52, - /// Local label assignment. - R_RISCV_SET6 = 53, - /// Local label assignment. - R_RISCV_SET8 = 54, - /// Local label assignment. - R_RISCV_SET16 = 55, - /// Local label assignment. - R_RISCV_SET32 = 56, - /// 32-bit PC relative. - R_RISCV_32_PCREL = 57, - /// Relocation against a local ifunc symbol in a shared object. - R_RISCV_IRELATIVE = 58 -} - -auto pad(ubyte elfClass)(size_t value) @nogc -{ - static if (elfClass == ELFCLASS32) - { - return cast(Elf32_Word) (value / 4 + 1) * 4; - } - else static if (elfClass == ELFCLASS64) - { - return cast(Elf64_Xword) (value / 8 + 1) * 8; - } - else - { - static assert(false, "Invalid ELF class"); - } -} - -private struct Relocation(Sym, Rel) -{ - Sym symbol; - Array!Rel relocations; -} - -struct Elf(ubyte elfClass) -{ - static if (elfClass == ELFCLASS32) - { - alias Addr = Elf32_Addr; - alias Off = Elf32_Off; - alias Half = Elf32_Half; - alias Word = Elf32_Word; - alias Sword = Elf32_Sword; - alias Xword = Elf32_Word; - alias Sxword = Elf32_Sword; - - alias Ehdr = Elf32_Ehdr; - alias Shdr = Elf32_Shdr; - alias Rel = Elf32_Rel; - alias Rela = Elf32_Rela; - alias Sym = Elf32_Sym; - - alias R_SYM = ELF32_R_SYM; - alias R_TYPE = ELF32_R_TYPE; - alias R_INFO = ELF32_R_INFO; - alias ST_BIND = ELF32_ST_BIND; - alias ST_TYPE = ELF32_ST_TYPE; - alias ST_INFO = ELF32_ST_INFO; - } - else static if (elfClass == ELFCLASS64) - { - alias Addr = Elf64_Addr; - alias Off = Elf64_Off; - alias Half = Elf64_Half; - alias Word = Elf64_Word; - alias Sword = Elf64_Sword; - alias Xword = Elf64_Xword; - alias Sxword = Elf64_Sxword; - - alias Ehdr = Elf64_Ehdr; - alias Shdr = Elf64_Shdr; - alias Rel = Elf64_Rel; - alias Rela = Elf64_Rela; - alias Sym = Elf64_Sym; - - alias R_SYM = ELF64_R_SYM; - alias R_TYPE = ELF64_R_TYPE; - alias R_INFO = ELF64_R_INFO; - alias ST_BIND = ELF64_ST_BIND; - alias ST_TYPE = ELF64_ST_TYPE; - alias ST_INFO = ELF64_ST_INFO; - } - else - { - static assert(false, "Invalid ELF class"); - } - - private alias Relocation = .Relocation!(Sym, Rela); - - private Array!Shdr sectionHeaders; - private Off currentOffset = Elf32_Ehdr.sizeof; - static immutable char[52] sections = - "\0.symtab\0.strtab\0.shstrtab\0.text\0.rodata\0.rela.text\0"; - private String strings; - private File output; - private Array!ubyte readOnly; - - private HashTable!(String, Relocation) symbolTable; - - private enum HeaderName - { - text = 0x1b, - roData = 0x21, - string_ = 0x09, - headerString = 0x11, - symbol = 0x01, - rela = 0x29 - } - - static Elf opCall(File output) @nogc - { - Elf elf = Elf.init; - - elf.initializeSectionHeaders(); - elf.output = move(output); - - elf.output.seek(Ehdr.sizeof, File.Whence.set); - - elf.makeTextHeader(); - elf.makeRoDataHeader(); - elf.makeSymbolHeader(); - elf.makeRelaHeader(); - elf.makeStringHeader!(HeaderName.string_)(); - elf.makeStringHeader!(HeaderName.headerString)(); - - return elf; - } - - @disable this(this); - - void finish() @nogc - { - writeRoDataTable(); - writeSymbolTable(); - writeStringTables(); - - // End writing data, start writing headers. - - output.write((cast(ubyte*) this.sectionHeaders.get)[0 .. Shdr.sizeof * this.sectionHeaders.length]); - - writeFileHeader(); - } - - private Sym initializeSymbols() @nogc - { - // Zero symbol - Sym symbol; - symbol.st_name = 0; // Word - symbol.st_value = 0; // Addr - symbol.st_size = 0; // Word - symbol.st_info = 0; // char - symbol.st_other = 0; // char - symbol.st_shndx = 0; // Half word - - return symbol; - } - - private void makeStringHeader(HeaderName position)() @nogc - { - Shdr table; - - table.sh_name = position; - table.sh_type = SHT_STRTAB; - table.sh_flags = 0; - table.sh_addr = 0; - table.sh_offset = 0; - table.sh_size = 0; - table.sh_link = SHN_UNDEF; - table.sh_info = 0; - table.sh_addralign = 1; - table.sh_entsize = 0; - - this.sectionHeaders.insertBack(table); - } - - private void writeStringTables() @nogc - { - auto stringIndex = findHeader!(HeaderName.string_); - assert(stringIndex != -1); - - this.sectionHeaders[stringIndex].sh_offset = this.currentOffset; - this.sectionHeaders[stringIndex].sh_size = cast(Word) strings.length; - - output.write(cast(ubyte[]) this.strings.toStringz[0 .. this.strings.length + 1]); - this.currentOffset += this.strings.length + 1; - - auto headerStringIndex = findHeader!(HeaderName.headerString); - assert(stringIndex != -1); - - this.sectionHeaders[headerStringIndex].sh_offset = this.currentOffset; - this.sectionHeaders[headerStringIndex].sh_size = cast(Word) sections.length; - - output.write(cast(const(ubyte)[]) this.sections); - this.currentOffset += this.sections.length; - auto alignment = pad!ELFCLASS32(this.strings.length + 1 + this.sections.length); - const(ubyte)[4] padding = 0; - output.write(padding[0 .. alignment - this.strings.length - 1 - this.sections.length]); - this.currentOffset += alignment - this.strings.length - 1 - this.sections.length; - } - - private void makeSymbolHeader() @nogc - { - Shdr symbolTableHeader; - - symbolTableHeader.sh_name = HeaderName.symbol; - symbolTableHeader.sh_type = SHT_SYMTAB; - symbolTableHeader.sh_flags = 0; - symbolTableHeader.sh_addr = 0; - symbolTableHeader.sh_offset = 0; - symbolTableHeader.sh_size = 0; - // String table used by entries in this section. - symbolTableHeader.sh_link = 0; - symbolTableHeader.sh_info = 0; - symbolTableHeader.sh_addralign = 4; - symbolTableHeader.sh_entsize = Sym.sizeof; - - this.sectionHeaders.insertBack(symbolTableHeader); - } - - private void writeSymbolTable() @nogc - { - const index = findHeader!(HeaderName.symbol)(); - const stringIndex = findHeader!(HeaderName.string_)(); - const relaIndex = findHeader!(HeaderName.rela); - const textIndex = findHeader!(HeaderName.text)(); - - assert(index != -1); - assert(stringIndex != -1); - assert(relaIndex != -1); - assert(textIndex != -1); - - this.sectionHeaders[index].sh_offset = this.currentOffset; - this.sectionHeaders[index].sh_link = cast(Word) stringIndex; - this.sectionHeaders[index].sh_size = cast(Word) ((1 + symbolTable.length) * Sym.sizeof); - - this.sectionHeaders[relaIndex].sh_link = cast(Word) index; - this.sectionHeaders[relaIndex].sh_info = cast(Word) textIndex; - this.sectionHeaders[relaIndex].sh_offset = this.sectionHeaders[index].sh_offset - + this.sectionHeaders[index].sh_size; - - auto initialSymbol = initializeSymbols(); - output.write((cast(ubyte*) &initialSymbol)[0 .. Sym.sizeof]); - this.currentOffset += Sym.sizeof; - - int i = 1; - Array!Relocation symbols = Array!Relocation(this.symbolTable.byValue()); - auto rightRange = symbols[].partition!(symbol => ST_BIND(symbol.symbol.st_info) != STB_GLOBAL); - - // Greater than last local symbol. - this.sectionHeaders[index].sh_info = cast(Word) (symbols.length - rightRange.length + 1); - - foreach (ref symbol; symbols[]) - { - this.output.seek(this.sectionHeaders[relaIndex].sh_offset + this.sectionHeaders[relaIndex].sh_size, - File.Whence.set); - - if (!symbol.relocations.empty) - { - foreach (ref relocation; symbol.relocations[]) - { - relocation.r_info = R_INFO(i, R_TYPE(relocation.r_info)); - } - this.sectionHeaders[relaIndex].sh_flags = SHF_ALLOC; - const size = cast(Word) (Rela.sizeof * symbol.relocations.length); - - this.output.write((cast(ubyte*) symbol.relocations.get)[0 .. size]); - this.sectionHeaders[relaIndex].sh_size += size; - this.currentOffset += size; - } - - this.output.seek(this.sectionHeaders[index].sh_offset + i * Sym.sizeof, File.Whence.set); - output.write((cast(ubyte*) &symbol)[0 .. Sym.sizeof]); - this.currentOffset += Sym.sizeof; - ++i; - } - this.output.seek(0, File.Whence.end); - } - - void addCode(String name, ref Array!ubyte text) - @nogc - { - this.output.write(text.get); - - auto textHeaderIndex = findHeader!(HeaderName.text)(); - assert(textHeaderIndex != -1); - - this.strings.insertBack("\0"); - - Sym symbol; - // Main function - symbol.st_name = cast(Word) this.strings.length; - symbol.st_value = 0; - symbol.st_size = cast(Word) text.length; - symbol.st_info = ST_INFO(STB_GLOBAL, STT_FUNC); - symbol.st_other = 0; // char - // .text header index, half word - symbol.st_shndx = cast(Half) textHeaderIndex; - this.symbolTable[name] = Relocation(symbol); - - this.strings.insertBack(name[]); - - this.sectionHeaders[textHeaderIndex].sh_size += text.length; - this.currentOffset += text.length; - } - - void addReadOnlyData(String name, ref Array!ubyte data) @nogc - { - auto roDataIndex = findHeader!(HeaderName.roData)(); - assert(roDataIndex != -1); - - this.strings.insertBack("\0"); - - Sym symbol; - // Main function - symbol.st_name = cast(Word) this.strings.length; - symbol.st_value = 0; - symbol.st_size = cast(Word) data.length; - symbol.st_info = ST_INFO(STB_LOCAL, STT_NOTYPE); - symbol.st_other = 0; // char - // .text header index, half word - symbol.st_shndx = cast(Half) roDataIndex; - this.symbolTable[name] = Relocation(symbol); - - this.strings.insertBack(name[]); - this.readOnly.insertBack(data[]); - } - - void addExternSymbol(String name) @nogc - { - Sym usedSymbolEntry; - - this.strings.insertBack("\0"); - usedSymbolEntry.st_name = cast(Word) this.strings.length; - usedSymbolEntry.st_value = 0; - usedSymbolEntry.st_size = 0; - usedSymbolEntry.st_info = ST_INFO(STB_GLOBAL, STT_NOTYPE); - usedSymbolEntry.st_other = 0; - usedSymbolEntry.st_shndx = SHN_UNDEF; - - this.strings.insertBack(name[]); - this.strings.insertBack("\0"); - this.symbolTable[name] = Relocation(usedSymbolEntry); - } - - void relocate(String name, Rela[] usedSymbols...) @nogc - { - foreach (usedSymbol; usedSymbols) - { - Rela relocationEntry = usedSymbol; - - relocationEntry.r_info = usedSymbol.r_info; - this.symbolTable[name].relocations.insertBack(relocationEntry); - } - } - - private ptrdiff_t findHeader(HeaderName position)() - { - return countUntil!(header => header.sh_name == position)(this.sectionHeaders[]); - } - - private void makeTextHeader() @nogc - { - Shdr textHeader; - - textHeader.sh_name = HeaderName.text; - textHeader.sh_type = SHT_PROGBITS; - textHeader.sh_flags = SHF_EXECINSTR | SHF_ALLOC; - textHeader.sh_addr = 0; - textHeader.sh_offset = this.currentOffset; - textHeader.sh_size = 0; - textHeader.sh_link = SHN_UNDEF; - textHeader.sh_info = 0; - textHeader.sh_addralign = 1; - textHeader.sh_entsize = 0; - - this.sectionHeaders.insertBack(textHeader); - } - - private void initializeSectionHeaders() @nogc - { - Shdr table; - - table.sh_name = 0; - table.sh_type = SHT_NULL; - table.sh_flags = 0; - table.sh_addr = 0; - table.sh_offset = 0; - table.sh_size = 0; - table.sh_link = SHN_UNDEF; - table.sh_info = 0; - table.sh_addralign = 0; - table.sh_entsize = 0; - - this.sectionHeaders.insertBack(table); - } - - private void writeFileHeader() @nogc - { - Ehdr fileHeader; - auto headerStringIndex = findHeader!(HeaderName.headerString)(); - - assert(headerStringIndex != -1); - - // Magic number. - fileHeader.e_ident[0] = '\x7f'; - fileHeader.e_ident[1] = 'E'; - fileHeader.e_ident[2] = 'L'; - fileHeader.e_ident[3] = 'F'; - - fileHeader.e_ident[4] = ELFCLASS32; - fileHeader.e_ident[5] = ELFDATA2LSB; - fileHeader.e_ident[6] = EV_CURRENT; - fileHeader.e_ident[7] = EI_OSABI.ELFOSABI_SYSV; - fileHeader.e_ident[8] = 0; - - fileHeader.e_type = ET_REL; - fileHeader.e_machine = 0xf3; // EM_RISCV - fileHeader.e_version = EV_CURRENT; - fileHeader.e_entry = 0; - fileHeader.e_phoff = 0; - fileHeader.e_shoff = this.currentOffset; - fileHeader.e_flags = 0; - fileHeader.e_ehsize = Elf32_Ehdr.sizeof; - fileHeader.e_phentsize = 0; - fileHeader.e_phnum = 0; - fileHeader.e_shentsize = Elf32_Shdr.sizeof; - fileHeader.e_shnum = cast(Elf32_Half) this.sectionHeaders.length; - - // String table is the last one - fileHeader.e_shstrndx = cast(Half) headerStringIndex; - - output.seek(0, File.Whence.set); - output.write((cast(ubyte*) &fileHeader)[0 .. fileHeader.sizeof]); - } - - private void makeRoDataHeader() @nogc - { - Shdr table; - - table.sh_name = HeaderName.roData; - table.sh_type = SHT_PROGBITS; - table.sh_flags = SHF_ALLOC; - table.sh_addr = 0; - table.sh_offset = 0; - table.sh_size = 0; - table.sh_link = SHN_UNDEF; - table.sh_info = 0; - table.sh_addralign = 4; - table.sh_entsize = 0; - - this.sectionHeaders.insertBack(table); - } - - private void writeRoDataTable() @nogc - { - auto index = findHeader!(HeaderName.roData)(); - assert(index != -1); - - this.sectionHeaders[index].sh_offset = this.currentOffset; - this.sectionHeaders[index].sh_size = cast(Xword) this.readOnly.length; - - output.write(this.readOnly.get); - this.currentOffset += this.readOnly.length; - } - - private void makeRelaHeader() @nogc - { - Shdr table; - - table.sh_name = HeaderName.rela; - table.sh_type = SHT_RELA; - table.sh_flags = 0; - table.sh_addr = 0; - table.sh_offset = 0; - table.sh_size = 0; - table.sh_link = SHN_UNDEF; - table.sh_info = 0; - table.sh_addralign = 4; - table.sh_entsize = Rela.sizeof; - - this.sectionHeaders.insertBack(table); - } -} diff --git a/source/elna/extended.d b/source/elna/extended.d deleted file mode 100644 index ff06a0c..0000000 --- a/source/elna/extended.d +++ /dev/null @@ -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(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); -} diff --git a/source/elna/ir.d b/source/elna/ir.d deleted file mode 100644 index 13582ba..0000000 --- a/source/elna/ir.d +++ /dev/null @@ -1,278 +0,0 @@ -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. -*/ -extern(C++) -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. - */ -extern(C++) -abstract class Node -{ - abstract void accept(IRVisitor) @nogc; -} - -/** - * Definition. - */ -extern(C++) -class Definition : Node -{ - char[] identifier; - Array!BinaryExpression statements; - Array!VariableDeclaration variableDeclarations; - Operand result; - - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } -} - -extern(C++) -abstract class Statement : Node -{ -} - -extern(C++) -abstract class Operand : Node -{ - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } -} - -extern(C++) -class Number : Operand -{ - int value; - - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } -} - -extern(C++) -class Variable : Operand -{ - size_t counter; - - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } -} - -extern(C++) -class VariableDeclaration : Node -{ - String identifier; - - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } -} - -extern(C++) -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); - } -} - -extern(C++) -class BangExpression : Statement -{ - Operand operand; - - this(Operand operand) - { - this.operand = operand; - } - - override void accept(IRVisitor visitor) @nogc - { - visitor.visit(this); - } -} - -final class TransformVisitor : parser.ParserVisitor!ASTMapping -{ - private HashTable!(String, int) constants; - private Array!BinaryExpression statements; - - ASTMapping.Node visit(parser.Node node) @nogc - { - 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; - } -} diff --git a/source/elna/lexer.d b/source/elna/lexer.d deleted file mode 100644 index a38a0ee..0000000 --- a/source/elna/lexer.d +++ /dev/null @@ -1,254 +0,0 @@ -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; - -extern(C++) -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), "Expected type: " ~ type.stringof) - { - 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. - */ -extern(C++) -struct Source -{ - char* buffer_; - size_t length_; - Position position; - - this(char* buffer, const size_t length) @nogc nothrow pure - { - this.buffer_ = buffer; - this.length_ = length; - } - - @disable this(); - - bool empty() @nogc nothrow pure @safe - { - return this.length == 0; - } - - char front() @nogc nothrow pure - in (!empty) - { - return this.buffer_[0]; - } - - void popFront() @nogc nothrow pure - in (!empty) - { - ++this.buffer_; - --this.length_; - ++this.position.column; - } - - void breakLine() @nogc nothrow pure - in (!empty) - { - ++this.buffer_; - --this.length_; - ++this.position.line; - this.position.column = 1; - } - - @property size_t length() const @nogc nothrow pure @safe - { - return this.length_; - } - - char opIndex(size_t index) @nogc nothrow pure - in (index < length) - { - return this.buffer_[index]; - } - - char* buffer() @nogc nothrow pure - { - return this.buffer_; - } -} - -Result!(Array!Token) lex(char[] buffer) @nogc -{ - Array!Token tokens; - auto source = Source(buffer.ptr, buffer.length); - - 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.buffer[0 .. i] == "const") - { - tokens.insertBack(Token(Token.Type.let, source.position)); - } - else if (source.buffer[0 .. i] == "var") - { - tokens.insertBack(Token(Token.Type.var, source.position)); - } - else - { - auto identifier = String(source.buffer[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); -} diff --git a/source/elna/parser.d b/source/elna/parser.d deleted file mode 100644 index c0bf048..0000000 --- a/source/elna/parser.d +++ /dev/null @@ -1,372 +0,0 @@ -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); -} diff --git a/source/elna/result.d b/source/elna/result.d deleted file mode 100644 index 9427147..0000000 --- a/source/elna/result.d +++ /dev/null @@ -1,107 +0,0 @@ -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; -} diff --git a/source/elna/riscv.d b/source/elna/riscv.d deleted file mode 100644 index a41b7cf..0000000 --- a/source/elna/riscv.d +++ /dev/null @@ -1,364 +0,0 @@ -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))[0 .. uint.sizeof]; - } -} - -extern(C++) -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); - } - return program; -} diff --git a/source/main.d b/source/main.d deleted file mode 100644 index dcb9fa0..0000000 --- a/source/main.d +++ /dev/null @@ -1,33 +0,0 @@ -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); - } - ); -} diff --git a/tests/7_member_sum.eln b/tests/7_member_sum.eln deleted file mode 100644 index a54a60a..0000000 --- a/tests/7_member_sum.eln +++ /dev/null @@ -1 +0,0 @@ -! 3 + 4 + 5 + 1 + 2 + 4 + 3 diff --git a/tests/const_list.eln b/tests/const_list.eln deleted file mode 100644 index 375627f..0000000 --- a/tests/const_list.eln +++ /dev/null @@ -1,3 +0,0 @@ -const a = 1, b = 2; -! a + b -. diff --git a/tests/expectations/7_member_sum.txt b/tests/expectations/7_member_sum.txt deleted file mode 100644 index 2bd5a0a..0000000 --- a/tests/expectations/7_member_sum.txt +++ /dev/null @@ -1 +0,0 @@ -22 diff --git a/tests/expectations/const_list.txt b/tests/expectations/const_list.txt deleted file mode 100644 index 00750ed..0000000 --- a/tests/expectations/const_list.txt +++ /dev/null @@ -1 +0,0 @@ -3 diff --git a/tests/expectations/left_nested_sum.txt b/tests/expectations/left_nested_sum.txt deleted file mode 100644 index 45a4fb7..0000000 --- a/tests/expectations/left_nested_sum.txt +++ /dev/null @@ -1 +0,0 @@ -8 diff --git a/tests/expectations/print_number.txt b/tests/expectations/print_number.txt deleted file mode 100644 index 00750ed..0000000 --- a/tests/expectations/print_number.txt +++ /dev/null @@ -1 +0,0 @@ -3 diff --git a/tests/expectations/subtraction.txt b/tests/expectations/subtraction.txt deleted file mode 100644 index d00491f..0000000 --- a/tests/expectations/subtraction.txt +++ /dev/null @@ -1 +0,0 @@ -1 diff --git a/tests/expectations/sum.txt b/tests/expectations/sum.txt deleted file mode 100644 index 45a4fb7..0000000 --- a/tests/expectations/sum.txt +++ /dev/null @@ -1 +0,0 @@ -8 diff --git a/tests/expectations/sums.txt b/tests/expectations/sums.txt deleted file mode 100644 index 45a4fb7..0000000 --- a/tests/expectations/sums.txt +++ /dev/null @@ -1 +0,0 @@ -8 diff --git a/tests/left_nested_sum.eln b/tests/left_nested_sum.eln deleted file mode 100644 index aae2a77..0000000 --- a/tests/left_nested_sum.eln +++ /dev/null @@ -1,2 +0,0 @@ -! (3 + 4) + 1 -. diff --git a/tests/print_number.eln b/tests/print_number.eln deleted file mode 100644 index 51b5ca0..0000000 --- a/tests/print_number.eln +++ /dev/null @@ -1,2 +0,0 @@ -! 3 -. diff --git a/tests/runner.cpp b/tests/runner.cpp index 2c15737..fcb06ab 100755 --- a/tests/runner.cpp +++ b/tests/runner.cpp @@ -1,37 +1,32 @@ #include -#include #include +#include +#include "elna/tester.hpp" -class test_results final +namespace elna { - std::uint32_t m_total{ 0 }; - std::uint32_t m_passed{ 0 }; - -public: - test_results() = default; - - std::uint32_t total() const noexcept + std::uint32_t test_results::total() const noexcept { return m_total; } - std::uint32_t passed() const noexcept + std::uint32_t test_results::passed() const noexcept { return m_passed; } - std::uint32_t failed() const noexcept + std::uint32_t test_results::failed() const noexcept { return m_total - m_passed; } - int exit_code() const noexcept + int test_results::exit_code() const noexcept { return m_total == m_passed ? EXIT_SUCCESS : EXIT_FAILURE; } - void add_exit_code(const int exit_code) noexcept + void test_results::add_exit_code(const int exit_code) noexcept { ++m_total; if (exit_code == 0) @@ -39,52 +34,52 @@ public: ++m_passed; } } -}; -int run_test(const std::filesystem::directory_entry& test_entry) -{ - const std::filesystem::path test_filename = test_entry.path().filename(); - - std::filesystem::path test_binary = "build/riscv" / test_filename; - test_binary.replace_extension(); - - std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename; - expectation_path.replace_extension(".txt"); - - std::cout << "Running " << test_binary << std::endl; - - boost::process::pipe pipe_stream; - boost::process::child vm( - "/opt/riscv/bin/spike", "--isa=RV32IMAC", - "/opt/riscv/riscv32-unknown-elf/bin/pk", - test_binary.string(), - boost::process::std_out > pipe_stream - ); - boost::process::child diff( - "/usr/bin/diff", "-Nur", "--color", - expectation_path.string(), "-", - boost::process::std_in < pipe_stream - ); - vm.wait(); - diff.wait(); - - return diff.exit_code(); -} - -test_results run_in_path(const std::filesystem::path test_directory) -{ - test_results results; - - for (const auto& test_entry : std::filesystem::directory_iterator(test_directory)) + static int run_test(const std::filesystem::directory_entry& test_entry) { - if (test_entry.path().extension() != ".eln" || !test_entry.is_regular_file()) - { - continue; - } - results.add_exit_code(run_test(test_entry)); + const std::filesystem::path test_filename = test_entry.path().filename(); + + std::filesystem::path test_binary = "build/riscv" / test_filename; + test_binary.replace_extension(); + + std::filesystem::path expectation_path = test_entry.path().parent_path() / "expectations" / test_filename; + expectation_path.replace_extension(".txt"); + + std::cout << "Running " << test_binary << std::endl; + + boost::process::pipe pipe_stream; + boost::process::child vm( + "/opt/riscv/bin/spike", "--isa=RV32IMAC", + "/opt/riscv/riscv32-unknown-elf/bin/pk", + test_binary.string(), + boost::process::std_out > pipe_stream + ); + boost::process::child diff( + "/usr/bin/diff", "-Nur", "--color", + expectation_path.string(), "-", + boost::process::std_in < pipe_stream + ); + vm.wait(); + diff.wait(); + + return diff.exit_code(); } - return results; -} + + static test_results run_in_path(const std::filesystem::path test_directory) + { + test_results results; + + for (const auto& test_entry : std::filesystem::directory_iterator(test_directory)) + { + if (test_entry.path().extension() != ".eln" || !test_entry.is_regular_file()) + { + continue; + } + results.add_exit_code(run_test(test_entry)); + } + return results; + } +}; int main() { @@ -92,7 +87,7 @@ int main() std::cout << "Run all tests and check the results" << std::endl; std::filesystem::path test_directory{ "tests" }; - const auto results = run_in_path(test_directory); + const auto results = elna::run_in_path(test_directory); std::cout << std::endl; std::cout << results.total() << " tests run, " diff --git a/tests/subtraction.eln b/tests/subtraction.eln deleted file mode 100644 index 00917c6..0000000 --- a/tests/subtraction.eln +++ /dev/null @@ -1,2 +0,0 @@ -! 5 - 4 -. diff --git a/tests/sum.eln b/tests/sum.eln deleted file mode 100644 index 6b4e720..0000000 --- a/tests/sum.eln +++ /dev/null @@ -1,2 +0,0 @@ -! 1 + 7 -. diff --git a/tests/sums.eln b/tests/sums.eln deleted file mode 100644 index 59586e9..0000000 --- a/tests/sums.eln +++ /dev/null @@ -1,2 +0,0 @@ -! 1 + (3 + 4) -.