From 8c46189b2024a5e067802a275209682bcb99d514 Mon Sep 17 00:00:00 2001 From: bog Date: Wed, 20 Sep 2023 13:31:35 +0200 Subject: [PATCH] ADD: builtin type literals (int, float, bool, string). --- .gitignore | 4 + Makefile | 13 +++ doc/grammar.bnf | 3 + meson.build | 84 ++++++++++++++++ src/Compiler.cpp | 69 +++++++++++++ src/Compiler.hpp | 26 +++++ src/Constant.cpp | 43 ++++++++ src/Constant.hpp | 34 +++++++ src/Lexer.cpp | 254 +++++++++++++++++++++++++++++++++++++++++++++++ src/Lexer.hpp | 47 +++++++++ src/Loc.cpp | 14 +++ src/Loc.hpp | 39 ++++++++ src/Module.cpp | 48 +++++++++ src/Module.hpp | 36 +++++++ src/Node.cpp | 55 ++++++++++ src/Node.hpp | 40 ++++++++ src/Parser.cpp | 151 ++++++++++++++++++++++++++++ src/Parser.hpp | 38 +++++++ src/Program.cpp | 73 ++++++++++++++ src/Program.hpp | 41 ++++++++ src/VM.cpp | 80 +++++++++++++++ src/VM.hpp | 36 +++++++ src/commons.hpp | 29 ++++++ src/conf.in.hpp | 6 ++ src/main.cpp | 56 +++++++++++ src/opcodes.hpp | 13 +++ src/types.hpp | 13 +++ tests/Lexer.cpp | 65 ++++++++++++ tests/Parser.cpp | 31 ++++++ tests/main.cpp | 2 + 30 files changed, 1443 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 doc/grammar.bnf create mode 100644 meson.build create mode 100644 src/Compiler.cpp create mode 100644 src/Compiler.hpp create mode 100644 src/Constant.cpp create mode 100644 src/Constant.hpp create mode 100644 src/Lexer.cpp create mode 100644 src/Lexer.hpp create mode 100644 src/Loc.cpp create mode 100644 src/Loc.hpp create mode 100644 src/Module.cpp create mode 100644 src/Module.hpp create mode 100644 src/Node.cpp create mode 100644 src/Node.hpp create mode 100644 src/Parser.cpp create mode 100644 src/Parser.hpp create mode 100644 src/Program.cpp create mode 100644 src/Program.hpp create mode 100644 src/VM.cpp create mode 100644 src/VM.hpp create mode 100644 src/commons.hpp create mode 100644 src/conf.in.hpp create mode 100644 src/main.cpp create mode 100644 src/opcodes.hpp create mode 100644 src/types.hpp create mode 100644 tests/Lexer.cpp create mode 100644 tests/Parser.cpp create mode 100644 tests/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e705fa --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build +*~* +*\#* +.cache \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..96f986d --- /dev/null +++ b/Makefile @@ -0,0 +1,13 @@ +DIR=build + +.PHONY: build tests libs + +build: + meson setup $(DIR) + meson compile -C $(DIR) + +tests: build + $(DIR)/fakir-tests + +install: tests + meson install -C $(DIR) diff --git a/doc/grammar.bnf b/doc/grammar.bnf new file mode 100644 index 0000000..a6be922 --- /dev/null +++ b/doc/grammar.bnf @@ -0,0 +1,3 @@ +MODULE ::= EXPR* +EXPR ::= +| int | float | bool | string diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..6b3d430 --- /dev/null +++ b/meson.build @@ -0,0 +1,84 @@ +project('fakir', + 'cpp', + version: '0.0.0', + default_options: [ + 'warning_level=3', + 'cpp_std=c++17', + 'prefix=/usr', + ]) + +# CONFIGURATION +# ============= +conf = configuration_data() +conf.set('version', meson.project_version()) + +configure_file(input: 'src/conf.in.hpp', + output: 'conf.hpp', + configuration: conf) + +# CORE LIB +# ======== +fakir_cpp = [ + 'src/Node.cpp', + 'src/Loc.cpp', + + 'src/Lexer.cpp', + 'src/Parser.cpp', + 'src/Constant.cpp', + + 'src/Compiler.cpp', + 'src/VM.cpp', + 'src/Program.cpp', + 'src/Module.cpp', +] + +fakir_hpp = [ + 'src/Node.hpp', + 'src/Loc.hpp', + 'src/Lexer.hpp', + 'src/Parser.hpp', + 'src/Constant.hpp', + + 'src/Compiler.hpp', + 'src/VM.hpp', + 'src/Program.hpp', + 'src/Module.hpp', + + 'src/opcodes.hpp', + 'src/commons.hpp', + 'src/types.hpp', +] + +fakir_lib = shared_library('fakir', + sources: fakir_cpp, + install: true) + +install_headers(fakir_hpp, subdir: 'fakir') + +fakir_dep = declare_dependency( + link_with: fakir_lib +) + +# COMPILER +# ======== +executable('fakir', + sources: [ + 'src/main.cpp', + ], + dependencies: [ + fakir_dep + ], + install: true) + +# TESTS +# ===== +executable('fakir-tests', + sources: [ + 'tests/main.cpp', + 'tests/Lexer.cpp', + 'tests/Parser.cpp', + ], + dependencies: [ + fakir_dep, + dependency('catch2') + ]) diff --git a/src/Compiler.cpp b/src/Compiler.cpp new file mode 100644 index 0000000..e57182d --- /dev/null +++ b/src/Compiler.cpp @@ -0,0 +1,69 @@ +#include "Compiler.hpp" + +namespace fk +{ + /*explicit*/ Compiler::Compiler() + { + } + + /*virtual*/ Compiler::~Compiler() + { + } + + std::shared_ptr Compiler::compile(std::shared_ptr node) + { + auto prog = std::make_shared(); + compile(node, prog); + return prog; + } + + void Compiler::compile(std::shared_ptr node, + std::shared_ptr prog) + { + switch (node->type()) + { + case NODE_MODULE: { + for (size_t i=0; isize(); i++) + { + compile(node->child(i), prog); + prog->add(OP_POP); + } + } break; + + case NODE_INT: { + prog->load_const(std::make_shared(TYPE_INT, + stoi(node->repr()), + node->loc())); + } break; + + case NODE_FLOAT: { + prog->load_const(std::make_shared(TYPE_FLOAT, + stof(node->repr()), + node->loc())); + } break; + + case NODE_BOOL: { + prog->load_const(std::make_shared(TYPE_BOOL, + node->repr() == "true", + node->loc())); + } break; + + case NODE_STRING: { + std::string str = node->repr(); + prog->load_const(std::make_shared(TYPE_STRING, + str.substr(1, str.size() - 2), + node->loc())); + } break; + + default: { + std::stringstream ss; + ss << "cannot compile expression '" + << (NodeTypeStr[node->type()] + strlen("NODE_")) + << "'"; + + node->loc().error(LOG_ERROR, ss.str()); + } break; + } + } + +} diff --git a/src/Compiler.hpp b/src/Compiler.hpp new file mode 100644 index 0000000..ea1406a --- /dev/null +++ b/src/Compiler.hpp @@ -0,0 +1,26 @@ +#ifndef fk_COMPILER_HPP +#define fk_COMPILER_HPP + +#include "commons.hpp" +#include "Program.hpp" +#include "Node.hpp" + +namespace fk +{ + FK_ERROR(compile_error); + + class Compiler + { + public: + explicit Compiler(); + virtual ~Compiler(); + + std::shared_ptr compile(std::shared_ptr node); + + private: + void compile(std::shared_ptr node, + std::shared_ptr prog); + }; +} + +#endif diff --git a/src/Constant.cpp b/src/Constant.cpp new file mode 100644 index 0000000..73d6218 --- /dev/null +++ b/src/Constant.cpp @@ -0,0 +1,43 @@ +#include "Constant.hpp" + +namespace fk +{ + /*explicit*/ Constant::Constant(Type type, constant_t value, Loc const& loc) + : m_type { type } + , m_value { value } + , m_loc { loc } + { + } + + /*virtual*/ Constant::~Constant() + { + } + + std::string Constant::string() const + { + switch (m_type) + { + case TYPE_INT: return std::to_string(std::get(m_value)); + case TYPE_FLOAT: return std::to_string(std::get(m_value)); + case TYPE_BOOL: return std::get(m_value) ? "true" : "false"; + case TYPE_STRING: return std::get(m_value); + + default: { + std::stringstream ss; + ss << "cannot stringify '" + << (TypeStr[m_type] + strlen("TYPE_")) + << "'"; + + m_loc.error(LOG_ERROR, ss.str()); + + } break; + } + + return ""; + } + + bool Constant::equals(Constant const& rhs) const + { + return m_type == rhs.m_type && m_value == rhs.m_value; + } +} diff --git a/src/Constant.hpp b/src/Constant.hpp new file mode 100644 index 0000000..fadf224 --- /dev/null +++ b/src/Constant.hpp @@ -0,0 +1,34 @@ +#ifndef fk_CONSTANT_HPP +#define fk_CONSTANT_HPP + +#include "commons.hpp" +#include "types.hpp" +#include "Loc.hpp" + +namespace fk +{ + FK_ERROR(constant_error); + + using constant_t = std::variant; + + class Constant + { + public: + explicit Constant(Type type, constant_t value, Loc const& loc); + virtual ~Constant(); + + Type type() const { return m_type; } + constant_t value() const { return m_value; } + Loc loc() const { return m_loc; } + + std::string string() const; + bool equals(Constant const& rhs) const; + + private: + Type m_type; + constant_t m_value; + Loc m_loc; + }; +} + +#endif diff --git a/src/Lexer.cpp b/src/Lexer.cpp new file mode 100644 index 0000000..301f93c --- /dev/null +++ b/src/Lexer.cpp @@ -0,0 +1,254 @@ +#include "Lexer.hpp" +#include "src/Loc.hpp" + +namespace fk +{ + /*explicit*/ Lexer::Lexer(Loc const& loc) + : m_loc { loc } + { + std::vector> text = { + {NODE_BOOL, "true", true}, + {NODE_BOOL, "false", true} + }; + + for (auto txt: text) + { + m_scanners.push_back(std::bind(&Lexer::scan_text, + this, + std::get<1>(txt), + std::get<0>(txt), + std::get<2>(txt))); + } + + m_scanners.push_back(std::bind(&Lexer::scan_int, this)); + m_scanners.push_back(std::bind(&Lexer::scan_float, this)); + m_scanners.push_back(std::bind(&Lexer::scan_string, this)); + } + + /*virtual*/ Lexer::~Lexer() + { + } + + void Lexer::scan(std::string const& source) + { + m_source = source; + m_cursor = 0; + } + + std::shared_ptr Lexer::next() + { + std::optional best_info; + skip_spaces(); + + while (m_cursor < m_source.size() + && m_source[m_cursor] == ';') + { + while (m_cursor < m_source.size() + && m_source[m_cursor] != '\n') + { + m_cursor++; + } + + skip_spaces(); + } + + for (auto const& scanner: m_scanners) + { + auto info = scanner(); + + if (info + && (best_info == std::nullopt + || info->cursor > best_info->cursor)) + { + best_info = info; + } + } + + if (best_info) + { + m_cursor = best_info->cursor; + return std::make_shared(best_info->type, + best_info->repr, + m_loc); + } + + if (m_cursor < m_source.size()) + { + std::string text; + + while (m_cursor < m_source.size() + && !std::isspace(m_source[m_cursor])) + { + text += m_source[m_cursor]; + m_cursor++; + } + + m_loc.error(LOG_ERROR, + "unexpected end near '" + text + "'"); + } + + return nullptr; + } + + void Lexer::skip_spaces() + { + while (m_cursor < m_source.size() + && std::isspace(m_source[m_cursor])) + { + if (m_source[m_cursor] == '\n') + { + m_loc = Loc {m_loc.path(), m_loc.line() + 1}; + } + + m_cursor++; + } + } + + std::optional Lexer::scan_int() + { + size_t cursor = m_cursor; + std::string repr; + + if (m_source[cursor] == '-') + { + repr += "-"; + cursor++; + } + + while (cursor < m_source.size() + && std::isdigit(m_source[cursor])) + { + repr += m_source[cursor]; + cursor++; + } + + if (repr.empty() || repr.back() == '-') + { + return std::nullopt; + } + + return ScanInfo { + cursor, + NODE_INT, + repr + }; + } + + std::optional Lexer::scan_float() + { + size_t cursor = m_cursor; + std::string repr; + + if (m_source[cursor] == '-') + { + repr += "-"; + cursor++; + } + + while (cursor < m_source.size() + && std::isdigit(m_source[cursor])) + { + repr += m_source[cursor]; + cursor++; + } + + if (m_source[cursor] == '.') + { + repr += "."; + cursor++; + } + else + { + return std::nullopt; + } + + while (cursor < m_source.size() + && std::isdigit(m_source[cursor])) + { + repr += m_source[cursor]; + cursor++; + } + + if (repr.empty() || repr.back() == '-' || repr == ".") + { + return std::nullopt; + } + + if (repr.front() == '.') + { + repr = "0" + repr; + } + + if (repr.back() == '.') + { + repr += "0"; + } + + return ScanInfo { + cursor, + NODE_FLOAT, + repr + }; + } + + std::optional Lexer::scan_string() + { + size_t cursor = m_cursor; + std::string repr = "'"; + + if (cursor > m_source.size() + || m_source[cursor] != '\'') + { + return std::nullopt; + } + + cursor++; + + while (cursor < m_source.size() + && m_source[cursor] != '\'') + { + repr += m_source[cursor]; + cursor++; + } + + if (cursor < m_source.size() + && m_source[cursor] == '\'') + { + repr += "'"; + cursor++; + + return ScanInfo { + cursor, + NODE_STRING, + repr + }; + } + + return std::nullopt; + } + + std::optional Lexer::scan_text(std::string const& text, + NodeType type, + bool has_value) + { + if (m_cursor + text.size() > m_source.size()) + { + return std::nullopt; + } + + for (size_t i=0; i()>; + + class Lexer + { + public: + explicit Lexer(Loc const& loc); + virtual ~Lexer(); + + void scan(std::string const& source); + std::shared_ptr next(); + + private: + Loc m_loc; + std::string m_source; + size_t m_cursor = 0; + std::vector m_scanners; + std::vector m_separators; + + void skip_spaces(); + + std::optional scan_int(); + std::optional scan_float(); + std::optional scan_string(); + std::optional scan_text(std::string const& text, + NodeType type, + bool has_value); + }; +} + +#endif diff --git a/src/Loc.cpp b/src/Loc.cpp new file mode 100644 index 0000000..c3bf186 --- /dev/null +++ b/src/Loc.cpp @@ -0,0 +1,14 @@ +#include "Loc.hpp" + +namespace fk +{ + /*explicit*/ Loc::Loc(std::filesystem::path path, int line) + : m_path { path } + , m_line { line } + { + } + + /*virtual*/ Loc::~Loc() + { + } +} diff --git a/src/Loc.hpp b/src/Loc.hpp new file mode 100644 index 0000000..93d7058 --- /dev/null +++ b/src/Loc.hpp @@ -0,0 +1,39 @@ +#ifndef fk_LOC_HPP +#define fk_LOC_HPP + +#include "commons.hpp" + +#define LOG_TYPES(G) \ + G(LOG_ERROR) + +namespace fk +{ + FK_ENUM(LogType, LOG_TYPES); + + class Loc + { + public: + explicit Loc(std::filesystem::path path, int line=0); + virtual ~Loc(); + + std::filesystem::path path() const { return m_path; } + int line() const { return m_line; } + + template + void error(LogType type, std::string const& what) const; + + private: + std::filesystem::path m_path; + int m_line; + }; + + template + void Loc::error(LogType type, std::string const& what) const + { + throw T {m_path.string() + ":" + std::to_string(m_line) + + " " + (LogTypeStr[type] + strlen("LOG_")) + " " + what}; + } + +} + +#endif diff --git a/src/Module.cpp b/src/Module.cpp new file mode 100644 index 0000000..90904e0 --- /dev/null +++ b/src/Module.cpp @@ -0,0 +1,48 @@ +#include "Module.hpp" +#include "Loc.hpp" + +namespace fk +{ + /*explicit*/ Module::Module(std::filesystem::path source_path) + : m_source_path { source_path } + { + } + + /*virtual*/ Module::~Module() + { + } + + void Module::build() + { + m_source = load_sources(); + m_ast = m_parser->parse(m_source); + + auto program = m_compiler->compile(m_ast); + m_vm->mount(program); + m_vm->run(); + } + + std::string Module::load_sources() + { + std::ifstream file { m_source_path }; + Loc loc { m_source_path }; + std::string source; + + if (!file) + { + std::stringstream ss; + ss << "cannot load module '" << m_source_path.string() << "'"; + loc.error(LOG_ERROR, ss.str()); + } + + std::string line; + source = ""; + + while (std::getline(file, line)) + { + source += line + (file.eof() ? "" : "\n"); + } + + return source; + } +} diff --git a/src/Module.hpp b/src/Module.hpp new file mode 100644 index 0000000..5504840 --- /dev/null +++ b/src/Module.hpp @@ -0,0 +1,36 @@ +#ifndef fk_MODULE_HPP +#define fk_MODULE_HPP + +#include "commons.hpp" +#include "Lexer.hpp" +#include "Parser.hpp" +#include "Compiler.hpp" +#include "VM.hpp" + +namespace fk +{ + FK_ERROR(module_error); + + class Module + { + public: + explicit Module(std::filesystem::path source_path); + virtual ~Module(); + + void build(); + + private: + std::filesystem::path m_source_path; + std::string m_source; + Loc m_loc {m_source_path}; + std::shared_ptr m_lexer = std::make_shared(m_loc); + std::shared_ptr m_parser = std::make_shared(*m_lexer); + std::shared_ptr m_compiler = std::make_shared(); + std::shared_ptr m_ast; + std::shared_ptr m_vm = std::make_shared(); + + std::string load_sources(); + }; +} + +#endif diff --git a/src/Node.cpp b/src/Node.cpp new file mode 100644 index 0000000..05b6dfd --- /dev/null +++ b/src/Node.cpp @@ -0,0 +1,55 @@ +#include "Node.hpp" + +namespace fk +{ + /*explicit*/ Node::Node(NodeType type, + std::string const& repr, + Loc const& loc) + : m_type { type } + , m_repr { repr } + , m_loc { loc } + { + } + + /*virtual*/ Node::~Node() + { + } + + void Node::add_child(std::shared_ptr child) + { + m_children.push_back(child); + } + + std::shared_ptr Node::child(size_t index) const + { + assert(index < size()); + return m_children.at(index); + } + + std::string Node::string() const + { + std::stringstream ss; + ss << (NodeTypeStr[m_type] + strlen("NODE_")); + + if (m_repr.empty() == false) + { + ss << "[" << m_repr << "]"; + } + + if (size() > 0) + { + ss << "("; + std::string sep; + + for (auto child: m_children) + { + ss << sep << child->string(); + sep = ","; + } + + ss << ")"; + } + + return ss.str(); + } +} diff --git a/src/Node.hpp b/src/Node.hpp new file mode 100644 index 0000000..c19d43b --- /dev/null +++ b/src/Node.hpp @@ -0,0 +1,40 @@ +#ifndef fk_NODE_HPP +#define fk_NODE_HPP + +#include "commons.hpp" +#include "Loc.hpp" + +#define NODE_TYPES(G) \ + G(NODE_MODULE), G(NODE_INT), G(NODE_FLOAT), G(NODE_BOOL), G(NODE_STRING), + +namespace fk +{ + FK_ENUM(NodeType, NODE_TYPES); + + class Node + { + public: + explicit Node(NodeType type, + std::string const& repr, + Loc const& loc); + virtual ~Node(); + + NodeType type() const { return m_type; } + std::string repr() const { return m_repr; } + Loc loc() const { return m_loc; } + size_t size() const { return m_children.size(); } + + void add_child(std::shared_ptr child); + std::shared_ptr child(size_t index) const; + + std::string string() const; + + private: + NodeType m_type; + std::string m_repr; + Loc m_loc; + std::vector> m_children; + }; +} + +#endif diff --git a/src/Parser.cpp b/src/Parser.cpp new file mode 100644 index 0000000..23d3107 --- /dev/null +++ b/src/Parser.cpp @@ -0,0 +1,151 @@ +#include "Parser.hpp" +#include "src/Loc.hpp" + +namespace fk +{ + /*explicit*/ Parser::Parser(Lexer& lexer) + : m_lexer { lexer } + { + } + + /*virtual*/ Parser::~Parser() + { + } + + std::shared_ptr Parser::parse(std::string const& source) + { + m_lexer.scan(source); + std::shared_ptr tok; + m_tokens.clear(); + m_cursor = 0; + + while ( (tok=m_lexer.next()) ) + { + m_tokens.push_back(tok); + } + + if (m_tokens.empty()) + { + Loc loc {"???"}; + auto node = std::make_shared(NODE_MODULE, "", loc); + return node; + } + + return parse_module(); + } + + std::shared_ptr Parser::parse_module() + { + auto node = make_node(NODE_MODULE); + + while (m_cursor < m_tokens.size()) + { + node->add_child(parse_expr()); + } + + return node; + } + + std::shared_ptr Parser::parse_expr() + { + if (type_any({NODE_INT, NODE_FLOAT, NODE_BOOL, NODE_STRING})) + { + return consume(); + } + + std::stringstream ss; + ss << "unknown expression '" + << (NodeTypeStr[m_tokens[m_cursor]->type()] + strlen("NODE_")) + << "'"; + + loc().error(LOG_ERROR, ss.str()); + + return nullptr; + } + + std::shared_ptr Parser::make_node(NodeType type) + { + return std::make_shared(type, "", loc()); + } + + Loc Parser::loc() const + { + if (m_cursor < m_tokens.size()) + { + return m_tokens[m_cursor]->loc(); + } + else if (m_tokens.empty() == false) + { + return m_tokens.back()->loc(); + } + else + { + Loc loc {"???"}; + return loc; + } + } + + bool Parser::type_is(NodeType type) const + { + return type_all({type}); + } + + bool Parser::type_isnt(NodeType type) const + { + return !type_is(type); + } + + bool Parser::type_all(std::vector types) const + { + for (size_t i=0; i= m_tokens.size() + || m_tokens[m_cursor + i]->type() != types[i]) + { + return false; + } + } + + return true; + } + + bool Parser::type_any(std::vector types) const + { + for (size_t i=0; itype() == types[i]) + { + return true; + } + } + + return false; + } + + std::shared_ptr Parser::consume() + { + assert(m_cursor < m_tokens.size()); + + auto node = m_tokens[m_cursor]; + m_cursor++; + return node; + } + + std::shared_ptr Parser::consume(NodeType type) + { + if (type_isnt(type)) + { + std::stringstream ss; + ss << "expected '" + << (NodeTypeStr[type] + strlen("NODE_")) + << "', got '" + << (NodeTypeStr[m_tokens[m_cursor]->type()] + strlen("NODE_")) + << "'"; + + loc().error(LOG_ERROR, ss.str()); + } + + return consume(); + } +} diff --git a/src/Parser.hpp b/src/Parser.hpp new file mode 100644 index 0000000..6e23596 --- /dev/null +++ b/src/Parser.hpp @@ -0,0 +1,38 @@ +#ifndef fk_PARSER_HPP +#define fk_PARSER_HPP + +#include "commons.hpp" +#include "Lexer.hpp" + +namespace fk +{ + FK_ERROR(syntax_error); + + class Parser + { + public: + explicit Parser(Lexer& lexer); + virtual ~Parser(); + + std::shared_ptr parse(std::string const& source); + private: + Lexer& m_lexer; + std::vector> m_tokens; + size_t m_cursor = 0; + + std::shared_ptr parse_module(); + std::shared_ptr parse_expr(); + + std::shared_ptr make_node(NodeType type); + Loc loc() const; + bool type_is(NodeType type) const; + bool type_isnt(NodeType type) const; + bool type_all(std::vector types) const; + bool type_any(std::vector types) const; + + std::shared_ptr consume(); + std::shared_ptr consume(NodeType type); + }; +} + +#endif diff --git a/src/Program.cpp b/src/Program.cpp new file mode 100644 index 0000000..45ad0cf --- /dev/null +++ b/src/Program.cpp @@ -0,0 +1,73 @@ +#include "Program.hpp" + +namespace fk +{ + /*explicit*/ Program::Program() + { + } + + /*virtual*/ Program::~Program() + { + } + + size_t Program::add(Opcode opcode, addr_t param) + { + m_instrs.push_back({opcode, param}); + return m_instrs.size() - 1; + } + + Instr Program::instr(size_t index) const + { + assert(index < size()); + return m_instrs[index]; + } + + size_t Program::add(std::shared_ptr constant) + { + for (size_t i=0; iequals(*constant)) + { + return i; + } + } + + m_consts.push_back(constant); + return m_consts.size() - 1; + } + + size_t Program::load_const(std::shared_ptr constant) + { + size_t addr = add(constant); + return add(OP_LOAD_CONST, addr); + } + + std::string Program::string() const + { + std::stringstream ss; + ss << std::setw(2) << std::left; + + ss << "======== ByteCode ========" << std::endl; + for (size_t i=0; istring() << std::endl; + } + + return ss.str(); + } +} diff --git a/src/Program.hpp b/src/Program.hpp new file mode 100644 index 0000000..6a7a8be --- /dev/null +++ b/src/Program.hpp @@ -0,0 +1,41 @@ +#ifndef fk_PROGRAM_HPP +#define fk_PROGRAM_HPP + +#include "commons.hpp" +#include "opcodes.hpp" +#include "Constant.hpp" + +namespace fk +{ + struct Instr { + Opcode opcode; + addr_t param; + }; + + constexpr addr_t NO_PARAM = -1; + + class Program + { + public: + explicit Program(); + virtual ~Program(); + + size_t size() const { return m_instrs.size(); } + size_t const_size() const { return m_consts.size(); } + + size_t add(Opcode opcode, addr_t param=NO_PARAM); + Instr instr(size_t index) const; + + size_t add(std::shared_ptr constant); + + size_t load_const(std::shared_ptr constant); + + std::string string() const; + + private: + std::vector m_instrs; + std::vector> m_consts; + }; +} + +#endif diff --git a/src/VM.cpp b/src/VM.cpp new file mode 100644 index 0000000..8ba3275 --- /dev/null +++ b/src/VM.cpp @@ -0,0 +1,80 @@ +#include "VM.hpp" + +namespace fk +{ + /*explicit*/ VM::VM() + { + } + + /*virtual*/ VM::~VM() + { + } + + void VM::mount(std::shared_ptr program) + { + Frame frame; + frame.program = program; + m_frames.push_back(frame); + } + + void VM::run() + { + while (m_pc < frame().program->size()) + { + Instr instr = frame().program->instr(m_pc); + + switch (instr.opcode) + { + + case OP_LOAD_CONST: { + push(instr.param); + m_pc++; + } break; + + case OP_POP: { + pop(); + m_pc++; + } break; + + default: { + std::cerr << "cannot run unknown opcode '" + << (OpcodeStr[instr.opcode] + strlen("OP_")) + << "'"<< std::endl; + abort(); + } break; + } + } + } + + std::string VM::string() const + { + std::stringstream ss; + ss << "======== VM Stack ========" << "\n"; + ss << std::setw(8) << std::left; + + for (size_t i=0; i program; + }; + + class VM + { + public: + explicit VM(); + virtual ~VM(); + + Frame frame() const { return m_frames.back(); } + + void mount(std::shared_ptr program); + void run(); + + std::string string() const; + private: + addr_t m_pc = 0; + std::vector m_frames; + std::vector m_stack; + + void push(addr_t addr); + addr_t top(); + addr_t pop(); + }; +} + +#endif diff --git a/src/commons.hpp b/src/commons.hpp new file mode 100644 index 0000000..9baf180 --- /dev/null +++ b/src/commons.hpp @@ -0,0 +1,29 @@ +#ifndef fk_COMMONS_HPP +#define fk_COMMONS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using addr_t = size_t; + +#define FK_GEN_ENUM(X) X +#define FK_GEN_STRING(X) #X + +#define FK_ENUM(PREFIX, DEFINITION) \ + enum PREFIX { DEFINITION(FK_GEN_ENUM) }; \ + constexpr char const* PREFIX ## Str [] = { DEFINITION(FK_GEN_STRING) } + +#define FK_ERROR(NAME) \ + struct NAME : public std::runtime_error { \ + NAME (std::string const& what) : std::runtime_error(what ) {} \ + } + +#endif diff --git a/src/conf.in.hpp b/src/conf.in.hpp new file mode 100644 index 0000000..d3f8c8e --- /dev/null +++ b/src/conf.in.hpp @@ -0,0 +1,6 @@ +#ifndef fk_CONF_HPP +#define fk_CONF_HPP + +#define FK_VERSION "@version@" + +#endif diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..7f6ad34 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,56 @@ +#include +#include +#include "commons.hpp" +#include "Module.hpp" + +int main(int argc, char** argv) +{ + while (true) + { + static struct option options[] = { + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; + + int opt_index = 0; + + int c = getopt_long(argc, argv, "h", options, &opt_index); + + if (c == -1) + { + break; + } + + switch (c) + { + case 'h': { + std::stringstream ss; + ss << std::setw(8) << std::left; + ss << "Usage: fakir " << std::endl + << std::endl; + ss << "[OPTIONS]" << std::endl; + ss << "-h, --help" << "\t" << "show this message." << std::endl; + std::cout << ss.str() << std::endl; + return 0; + } break; + + default: break; + } + } + + std::vector sources; + + while (optind < argc) + { + sources.push_back(argv[optind]); + optind++; + } + + if (sources.size()) + { + fk::Module mod {sources[0]}; + mod.build(); + } + + return 0; +} diff --git a/src/opcodes.hpp b/src/opcodes.hpp new file mode 100644 index 0000000..4e22c96 --- /dev/null +++ b/src/opcodes.hpp @@ -0,0 +1,13 @@ +#ifndef fk_OPCODES_HPP +#define fk_OPCODES_HPP + +#define OPCODES(G) G(OP_LOAD_CONST), G(OP_POP) + +#include "commons.hpp" + +namespace fk +{ + FK_ENUM(Opcode, OPCODES); +} + +#endif diff --git a/src/types.hpp b/src/types.hpp new file mode 100644 index 0000000..f99ce2a --- /dev/null +++ b/src/types.hpp @@ -0,0 +1,13 @@ +#ifndef fk_TYPES_HPP +#define fk_TYPES_HPP + +#include "commons.hpp" + +#define TYPES(G) G(TYPE_INT), G(TYPE_FLOAT), G(TYPE_BOOL), G(TYPE_STRING) + +namespace fk +{ + FK_ENUM(Type, TYPES); +} + +#endif diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp new file mode 100644 index 0000000..19a4f61 --- /dev/null +++ b/tests/Lexer.cpp @@ -0,0 +1,65 @@ +#include +#include "../src/Lexer.hpp" + +class LexerTest +{ +public: + explicit LexerTest() {} + virtual ~LexerTest() {} + + void test_next(std::string const& oracle) + { + auto node = m_lexer.next(); + REQUIRE(node); + REQUIRE(oracle == node->string()); + } + + void test_end() + { + auto node = m_lexer.next(); + REQUIRE(nullptr == node); + } + +protected: + fk::Loc m_loc {"tests/lexer"}; + fk::Lexer m_lexer { m_loc }; +}; + +TEST_CASE_METHOD(LexerTest, "Lexer_int") +{ + m_lexer.scan(" 34 7 -296"); + + test_next("INT[34]"); + test_next("INT[7]"); + test_next("INT[-296]"); + test_end(); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_float") +{ + m_lexer.scan(" .34 7.3 -296. "); + + test_next("FLOAT[0.34]"); + test_next("FLOAT[7.3]"); + test_next("FLOAT[-296.0]"); + test_end(); +} + + +TEST_CASE_METHOD(LexerTest, "Lexer_bool") +{ + m_lexer.scan(" true false "); + + test_next("BOOL[true]"); + test_next("BOOL[false]"); + test_end(); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_string") +{ + m_lexer.scan(" 'true ' 'false' "); + + test_next("STRING['true ']"); + test_next("STRING['false']"); + test_end(); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp new file mode 100644 index 0000000..2763ad8 --- /dev/null +++ b/tests/Parser.cpp @@ -0,0 +1,31 @@ +#include +#include "../src/Parser.hpp" + +class ParserTest +{ +public: + explicit ParserTest() {} + virtual ~ParserTest() {} + + void test_parse(std::string const& oracle, std::string const& source) + { + auto node = m_parser.parse(source); + REQUIRE(oracle == node->string()); + } + +protected: + fk::Loc m_loc {"tests/parser"}; + fk::Lexer m_lexer { m_loc }; + fk::Parser m_parser { m_lexer }; +}; + +TEST_CASE_METHOD(ParserTest, "Parser_empty") +{ + test_parse("MODULE", ""); +} + +TEST_CASE_METHOD(ParserTest, "Parser_builtins") +{ + test_parse("MODULE(INT[4],FLOAT[3.0],BOOL[false],STRING['salut'])", + "4 3. false 'salut'"); +} diff --git a/tests/main.cpp b/tests/main.cpp new file mode 100644 index 0000000..4ed06df --- /dev/null +++ b/tests/main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include