From e8e4906cfca9c96f91c29005b7eed17262495ac7 Mon Sep 17 00:00:00 2001 From: bog Date: Sun, 10 Sep 2023 00:03:28 +0200 Subject: [PATCH] ADD: foreign functions. --- core/core.cpp | 6 ++ doc/grammar.bnf | 4 ++ lib/Code.cpp | 13 +++++ lib/Code.hpp | 22 +++++++ lib/Compiler.cpp | 80 +++++++++++++++++++++++++ lib/Compiler.hpp | 31 ++++++++++ lib/Function.cpp | 19 ++++++ lib/Function.hpp | 26 +++++++++ lib/Lexer.cpp | 100 +++++++++++++++++++++++++++++--- lib/Lexer.hpp | 8 ++- lib/Loader.cpp | 50 ++++++++++++++++ lib/Loader.hpp | 27 +++++++++ lib/Node.hpp | 7 ++- lib/Opcodes.hpp | 19 ++++++ lib/Parser.cpp | 127 +++++++++++++++++++++++++++++++++++++++- lib/Parser.hpp | 15 +++++ lib/Program.cpp | 61 ++++++++++++++++++++ lib/Program.hpp | 41 +++++++++++++ lib/StaticPass.cpp | 37 ++++++++++++ lib/StaticPass.hpp | 24 ++++++++ lib/SymTable.cpp | 64 ++++++++++++++++++++ lib/SymTable.hpp | 43 ++++++++++++++ lib/Type.hpp | 10 +++- lib/VM.cpp | 141 +++++++++++++++++++++++++++++++++++++++++++++ lib/VM.hpp | 41 +++++++++++++ lib/Value.cpp | 55 ++++++++++++++++++ lib/Value.hpp | 15 +++++ lib/commons.hpp | 1 + meson.build | 19 +++++- src/main.cpp | 52 ++++++++++++----- tests/Lexer.cpp | 15 +++++ tests/Parser.cpp | 21 ++++++- 32 files changed, 1162 insertions(+), 32 deletions(-) create mode 100644 core/core.cpp create mode 100644 doc/grammar.bnf create mode 100644 lib/Code.cpp create mode 100644 lib/Code.hpp create mode 100644 lib/Compiler.cpp create mode 100644 lib/Compiler.hpp create mode 100644 lib/Function.cpp create mode 100644 lib/Function.hpp create mode 100644 lib/Loader.cpp create mode 100644 lib/Loader.hpp create mode 100644 lib/Opcodes.hpp create mode 100644 lib/Program.cpp create mode 100644 lib/Program.hpp create mode 100644 lib/StaticPass.cpp create mode 100644 lib/StaticPass.hpp create mode 100644 lib/SymTable.cpp create mode 100644 lib/SymTable.hpp create mode 100644 lib/VM.cpp create mode 100644 lib/VM.hpp diff --git a/core/core.cpp b/core/core.cpp new file mode 100644 index 0000000..50befc8 --- /dev/null +++ b/core/core.cpp @@ -0,0 +1,6 @@ +#include "lib/Function.hpp" +#include "lib/Loader.hpp" + +extern "C" void lib(jk::Loader& loader) +{ +} diff --git a/doc/grammar.bnf b/doc/grammar.bnf new file mode 100644 index 0000000..8f35903 --- /dev/null +++ b/doc/grammar.bnf @@ -0,0 +1,4 @@ +PROG ::= FUNCALL* +EXPR ::= LITERAL | FUNCALL +FUNCALL ::= opar ident EXPR* cpar +LITERAL ::= int | ident diff --git a/lib/Code.cpp b/lib/Code.cpp new file mode 100644 index 0000000..125f13c --- /dev/null +++ b/lib/Code.cpp @@ -0,0 +1,13 @@ +#include "Code.hpp" + +namespace jk +{ + /*explicit*/ Code::Code(foreign_t foreign) + : m_foreign { foreign } + { + } + + /*virtual*/ Code::~Code() + { + } +} diff --git a/lib/Code.hpp b/lib/Code.hpp new file mode 100644 index 0000000..654c79e --- /dev/null +++ b/lib/Code.hpp @@ -0,0 +1,22 @@ +#ifndef jk_CODE_HPP +#define jk_CODE_HPP + +#include "commons.hpp" +#include "Function.hpp" + +namespace jk +{ + class Code + { + public: + explicit Code(foreign_t foreign); + virtual ~Code(); + + foreign_t foreign() const { return m_foreign; } + + private: + foreign_t m_foreign; + }; +} + +#endif diff --git a/lib/Compiler.cpp b/lib/Compiler.cpp new file mode 100644 index 0000000..0891381 --- /dev/null +++ b/lib/Compiler.cpp @@ -0,0 +1,80 @@ +#include "Compiler.hpp" +#include "lib/Opcodes.hpp" +#include "Code.hpp" + +namespace jk +{ + /*explicit*/ Compiler::Compiler(std::shared_ptr sym, + Logger& logger) + : m_sym { sym } + , m_logger(logger) + { + } + + /*virtual*/ Compiler::~Compiler() + { + } + + void Compiler::compile(std::shared_ptr node, + std::shared_ptr program) + { + switch (node->type()) + { + case NODE_PROG: { + for (size_t i=0; isize(); i++) + { + compile(node->child(i).lock(), program); + } + } break; + + case NODE_FUNCALL: { + for (size_t i=0; isize(); i++) + { + compile(node->child(i).lock(), program); + } + + program->push_instr(OPCODE_CALL, node->size() - 1); + } break; + + case NODE_IDENT: { + std::string ident = node->repr(); + auto sym = m_sym->find(ident); + + if (!sym) + { + m_logger.log(LOG_ERROR, node->loc(), + std::string() + + "'" + + ident + + "' is undefined"); + } + + OpcodeType op_load = OPCODE_LOAD; + + if (sym->is_global) + { + op_load = OPCODE_LOAD_GLOBAL; + } + + if (sym->type->type() == TYPE_FUNCTION) + { + program->push_instr(op_load, sym->addr); + } + + } break; + + case NODE_INT: { + auto value = Value::make_int(std::stoi(node->repr())); + size_t addr = program->push_constant(value); + program->push_instr(OPCODE_PUSH_CONST, addr); + } break; + + default: + std::cerr << "cannot compile unknown node '" + << NodeTypeStr[node->type()] << "'" << std::endl; + abort(); + + } + } + +} diff --git a/lib/Compiler.hpp b/lib/Compiler.hpp new file mode 100644 index 0000000..cbeec58 --- /dev/null +++ b/lib/Compiler.hpp @@ -0,0 +1,31 @@ +#ifndef jk_COMPILER_HPP +#define jk_COMPILER_HPP + +#include "commons.hpp" +#include "SymTable.hpp" +#include "Logger.hpp" +#include "Node.hpp" +#include "Program.hpp" +#include "VM.hpp" + +namespace jk +{ + JK_ERROR(compile_error); + + class Compiler + { + public: + explicit Compiler(std::shared_ptr sym, + Logger& logger); + virtual ~Compiler(); + + void compile(std::shared_ptr node, + std::shared_ptr program); + + private: + std::shared_ptr m_sym; + Logger& m_logger; + }; +} + +#endif diff --git a/lib/Function.cpp b/lib/Function.cpp new file mode 100644 index 0000000..ae6a9f8 --- /dev/null +++ b/lib/Function.cpp @@ -0,0 +1,19 @@ +#include "Function.hpp" +#include "Value.hpp" + +namespace jk +{ + /*explicit*/ Function::Function(foreign_t foreign) + : m_foreign { foreign } + { + } + + /*virtual*/ Function::~Function() + { + } + + value_t Function::call(std::vector const& args) + { + return m_foreign(args); + } +} diff --git a/lib/Function.hpp b/lib/Function.hpp new file mode 100644 index 0000000..4fda67f --- /dev/null +++ b/lib/Function.hpp @@ -0,0 +1,26 @@ +#ifndef jk_FUNCTION_HPP +#define jk_FUNCTION_HPP + +#include "commons.hpp" + +namespace jk +{ + class Value; + + using value_t = std::shared_ptr; + using foreign_t = std::function)>; + + class Function + { + public: + explicit Function(foreign_t foreign); + virtual ~Function(); + + value_t call(std::vector const& args); + + private: + foreign_t m_foreign; + }; +} + +#endif diff --git a/lib/Lexer.cpp b/lib/Lexer.cpp index 0262818..7e4b020 100644 --- a/lib/Lexer.cpp +++ b/lib/Lexer.cpp @@ -6,6 +6,21 @@ namespace jk : m_logger { logger } , m_loc { loc } { + std::vector> texts = { + {NODE_OPAR, "(", false}, + {NODE_CPAR, ")", false} + }; + + for (auto text: texts) + { + m_scanners.push_back(std::bind(&Lexer::scan_text, + this, + std::get<0>(text), + std::get<1>(text), + std::get<2>(text))); + } + + m_scanners.push_back(std::bind(&Lexer::scan_ident, this)); m_scanners.push_back(std::bind(&Lexer::scan_int, this)); } @@ -24,10 +39,10 @@ namespace jk skip_spaces(); while (more(m_cursor) - && current(m_cursor) == '#') + && at(m_cursor) == '#') { while (more(m_cursor) - && current(m_cursor) != '\n') + && at(m_cursor) != '\n') { m_cursor++; } @@ -60,9 +75,9 @@ namespace jk std::string text; while (more(m_cursor) - && !std::isspace(current(m_cursor))) + && !std::isspace(at(m_cursor))) { - text += current(m_cursor); + text += at(m_cursor); m_cursor++; } @@ -79,7 +94,7 @@ namespace jk return index < m_source.size(); } - char Lexer::current(size_t index) const + char Lexer::at(size_t index) const { assert(more(index)); @@ -89,9 +104,9 @@ namespace jk void Lexer::skip_spaces() { while (more(m_cursor) - && std::isspace(current(m_cursor))) + && std::isspace(at(m_cursor))) { - if (current(m_cursor) == '\n') + if (at(m_cursor) == '\n') { m_loc = Loc { m_loc.path(), @@ -110,9 +125,9 @@ namespace jk std::string repr; while (more(cursor) - && std::isdigit(current(cursor))) + && std::isdigit(at(cursor))) { - repr += current(cursor); + repr += at(cursor); cursor++; } @@ -127,4 +142,71 @@ namespace jk return std::nullopt; } + + std::optional Lexer::scan_text(NodeType type, + std::string const& text, + bool has_value) const + { + if (m_cursor + text.size() > m_source.size()) + { + return std::nullopt; + } + + for (size_t i=0; i Lexer::scan_ident() const + { + auto car = [](char c){ + return std::isalpha(c) + || c == '_' + || c == '-' + || c == '?' + || c == '!' + || c == '/'; + }; + + auto cdr = [car](char c){ + return car(c) + || std::isdigit(c) + ; + }; + + size_t cursor = m_cursor; + std::string repr; + + if (!more(cursor) + || !car(at(cursor))) + { + return std::nullopt; + } + + repr += at(cursor); + cursor++; + + while (more(cursor) + && cdr(at(cursor))) + { + repr += at(cursor); + cursor++; + } + + return ScanInfo { + cursor, + NODE_IDENT, + repr + }; + } } diff --git a/lib/Lexer.hpp b/lib/Lexer.hpp index 14cf035..67bea27 100644 --- a/lib/Lexer.hpp +++ b/lib/Lexer.hpp @@ -23,6 +23,8 @@ namespace jk explicit Lexer(Logger& logger, Loc const& loc); virtual ~Lexer(); + Loc loc() const { return m_loc; } + void scan(std::string const& source); std::shared_ptr next(); @@ -34,11 +36,15 @@ namespace jk std::vector m_scanners; bool more(size_t index) const; - char current(size_t index) const; + char at(size_t index) const; void skip_spaces(); std::optional scan_int() const; + std::optional scan_text(NodeType type, + std::string const& text, + bool has_value) const; + std::optional scan_ident() const; }; } diff --git a/lib/Loader.cpp b/lib/Loader.cpp new file mode 100644 index 0000000..c71c01d --- /dev/null +++ b/lib/Loader.cpp @@ -0,0 +1,50 @@ +#include "Loader.hpp" +#include "lib/Function.hpp" +#include "lib/config.in.hpp" +#include + +namespace jk +{ + /*explicit*/ Loader::Loader(std::shared_ptr vm, + std::shared_ptr sym) + : m_vm { vm } + , m_sym { sym } + { + } + + /*virtual*/ Loader::~Loader() + { + } + + void Loader::load() + { + for (auto entry: std::filesystem::directory_iterator(JOKO_LIBDIR)) + { + if (entry.path().extension() == ".so") + { + void* handle = dlopen(entry.path().string().c_str(), RTLD_LAZY); + assert(handle); + typedef void(*func)(Loader&); + func f = (func) dlsym(handle, "lib"); + assert(f); + + f(*this); + } + } + } + + void Loader::declare(std::string const& name, foreign_t body) + { + auto fun = std::make_shared(body); + + auto fun_val = Value::make_function(fun); + size_t addr = m_vm->push_heap(fun_val); + + auto ref = Value::make_ref(addr); + size_t global_addr = m_sym->declare_global(name, + fun_val->type().lock(), + Loc {"global", 1, 1}); + m_vm->set_global(global_addr, ref); + } + +} diff --git a/lib/Loader.hpp b/lib/Loader.hpp new file mode 100644 index 0000000..f042ef0 --- /dev/null +++ b/lib/Loader.hpp @@ -0,0 +1,27 @@ +#ifndef jk_LOADER_HPP +#define jk_LOADER_HPP + +#include "commons.hpp" +#include "VM.hpp" +#include "SymTable.hpp" +#include "Function.hpp" + +namespace jk +{ + class Loader + { + public: + explicit Loader(std::shared_ptr vm, + std::shared_ptr sym); + virtual ~Loader(); + + void load(); + void declare(std::string const& name, foreign_t body); + + private: + std::shared_ptr m_vm; + std::shared_ptr m_sym; + }; +} + +#endif diff --git a/lib/Node.hpp b/lib/Node.hpp index 97da022..e37eddf 100644 --- a/lib/Node.hpp +++ b/lib/Node.hpp @@ -6,7 +6,12 @@ #define NODE_TYPE(G) \ G(NODE_PROG), \ - G(NODE_INT) + G(NODE_INT), \ + G(NODE_OPAR), \ + G(NODE_CPAR), \ + G(NODE_IDENT), \ + G(NODE_FUNCALL), + namespace jk { diff --git a/lib/Opcodes.hpp b/lib/Opcodes.hpp new file mode 100644 index 0000000..fb8d941 --- /dev/null +++ b/lib/Opcodes.hpp @@ -0,0 +1,19 @@ +#ifndef jk_OPCODES_HPP +#define jk_OPCODES_HPP + +#include "commons.hpp" + +#define OPCODES(G) \ + G(OPCODE_PUSH_CONST), \ + G(OPCODE_CALL), \ + G(OPCODE_LOAD), \ + G(OPCODE_STORE), \ + G(OPCODE_LOAD_GLOBAL), \ + G(OPCODE_MK_FUNCTION) + +namespace jk +{ + JK_ENUM(Opcode, OPCODES); +} + +#endif diff --git a/lib/Parser.cpp b/lib/Parser.cpp index 2a67b3b..3e46a9f 100644 --- a/lib/Parser.cpp +++ b/lib/Parser.cpp @@ -12,8 +12,131 @@ namespace jk { } - std::shared_ptr Parser::parse(std::string const&) + std::shared_ptr Parser::parse(std::string const& source) { - return nullptr; + std::shared_ptr tok; + m_cursor = 0; + m_lexer->scan(source); + + while ( (tok=m_lexer->next()) ) + { + m_tokens.push_back(tok); + } + + if (m_tokens.empty()) + { + return std::make_shared(NODE_PROG, "", + Loc {m_lexer->loc().path(), 1, 1}); + } + + return parse_prog(); + } + + std::shared_ptr Parser::current() const + { + assert(m_cursor < m_tokens.size()); + return m_tokens[m_cursor]; + } + + Loc Parser::loc() const + { + return current()->loc(); + } + + std::shared_ptr Parser::consume() + { + assert(m_cursor < m_tokens.size()); + + auto tok = m_tokens[m_cursor]; + m_cursor++; + return tok; + } + + std::shared_ptr Parser::consume(NodeType type) + { + assert(m_cursor < m_tokens.size()); + + if (m_tokens[m_cursor]->type() != type) + { + std::stringstream ss; + ss << "expected '" + << std::string(NodeTypeStr[type]) + .substr(std::string("NODE_").size()) + << "', got '" + << std::string(NodeTypeStr[m_tokens[m_cursor]->type()]) + .substr(std::string("NODE_").size()) + << "'"; + + m_logger.log(LOG_ERROR, + m_tokens[m_cursor]->loc(), + ss.str()); + } + + return consume(); + } + + bool Parser::type_is(NodeType type, int lookahead) const + { + if (m_cursor + lookahead >= m_tokens.size()) + { + return false; + } + + return m_tokens[m_cursor + lookahead]->type() == type; + } + + std::shared_ptr Parser::parse_prog() + { + auto root = std::make_shared(NODE_PROG, "", loc()); + + while (m_cursor < m_tokens.size()) + { + root->add_child(parse_funcall()); + } + + return root; + } + + std::shared_ptr Parser::parse_expr() + { + if (type_is(NODE_OPAR)) + { + return parse_funcall(); + } + + return parse_literal(); + } + + std::shared_ptr Parser::parse_funcall() + { + auto root = std::make_shared(NODE_FUNCALL, "", loc()); + consume(NODE_OPAR); + + while (!type_is(NODE_CPAR)) + { + root->add_child(parse_expr()); + } + + consume(NODE_CPAR); + + return root; + } + + std::shared_ptr Parser::parse_literal() + { + if (type_is(NODE_INT) + || type_is(NODE_IDENT)) + { + return consume(); + } + + m_logger.log(LOG_ERROR, + loc(), + std::string() + + "unexpected token '" + + current()->repr() + + "'"); + + abort(); } } diff --git a/lib/Parser.hpp b/lib/Parser.hpp index d316f24..93d3b41 100644 --- a/lib/Parser.hpp +++ b/lib/Parser.hpp @@ -7,6 +7,8 @@ namespace jk { + JK_ERROR(syntax_error); + class Parser { public: @@ -18,6 +20,19 @@ namespace jk private: Logger& m_logger; std::shared_ptr m_lexer; + std::vector> m_tokens; + size_t m_cursor; + + std::shared_ptr current() const; + Loc loc() const; + std::shared_ptr consume(); + std::shared_ptr consume(NodeType type); + bool type_is(NodeType type, int lookahead=0) const; + + std::shared_ptr parse_prog(); + std::shared_ptr parse_expr(); + std::shared_ptr parse_funcall(); + std::shared_ptr parse_literal(); }; } diff --git a/lib/Program.cpp b/lib/Program.cpp new file mode 100644 index 0000000..46b8bf7 --- /dev/null +++ b/lib/Program.cpp @@ -0,0 +1,61 @@ +#include "Program.hpp" + +namespace jk +{ + /*explicit*/ Program::Program() + { + } + + /*virtual*/ Program::~Program() + { + } + + Instr Program::get(size_t index) const + { + assert(index < size()); + return m_instrs[index]; + } + + void Program::push_instr(OpcodeType opcode, + std::optional param /*= std::nullopt*/) + { + m_instrs.push_back(Instr { opcode, param }); + } + + std::string Program::string() const + { + std::stringstream ss; + size_t addr = 0; + + for (auto const& instr: m_instrs) + { + ss << addr << "\t" + << std::string(OpcodeTypeStr[instr.opcode]) + .substr(std::string("OPCODE_").size()); + + if (instr.param) + { + ss << "\t" << *instr.param; + } + + ss << std::endl; + + addr++; + } + + return ss.str(); + } + + size_t Program::push_constant(std::shared_ptr value) + { + m_constants.push_back(value); + return m_constants.size() - 1; + } + + std::shared_ptr Program::constant(size_t index) const + { + assert(index < m_constants.size()); + return m_constants[index]; + } + +} diff --git a/lib/Program.hpp b/lib/Program.hpp new file mode 100644 index 0000000..e286350 --- /dev/null +++ b/lib/Program.hpp @@ -0,0 +1,41 @@ +#ifndef jk_PROGRAM_HPP +#define jk_PROGRAM_HPP + +#include "commons.hpp" +#include "Opcodes.hpp" +#include "Value.hpp" + +namespace jk +{ + using param_t = size_t; + + struct Instr { + OpcodeType opcode; + std::optional param; + }; + + class Program + { + public: + explicit Program(); + virtual ~Program(); + + size_t size() const { return m_instrs.size(); } + + Instr get(size_t index) const; + + void push_instr(OpcodeType opcode, + std::optional param = std::nullopt); + + size_t push_constant(std::shared_ptr value); + std::shared_ptr constant(size_t index) const; + + std::string string() const; + + private: + std::vector m_instrs; + std::vector> m_constants; + }; +} + +#endif diff --git a/lib/StaticPass.cpp b/lib/StaticPass.cpp new file mode 100644 index 0000000..8de75cd --- /dev/null +++ b/lib/StaticPass.cpp @@ -0,0 +1,37 @@ +#include "StaticPass.hpp" + +namespace jk +{ + /*explicit*/ StaticPass::StaticPass(std::shared_ptr sym, + Logger& logger) + : m_sym { sym } + , m_logger { logger } + { + } + + /*virtual*/ StaticPass::~StaticPass() + { + } + + void StaticPass::pass(std::shared_ptr node) + { + switch (node->type()) + { + case NODE_PROG: { + for (size_t i=0; isize(); i++) + { + pass(node->child(i).lock()); + } + } break; + + case NODE_FUNCALL: { + + } break; + + default: + std::cerr << "cannot static_pass unknown node '" + << NodeTypeStr[node->type()] << "'" << std::endl; + abort(); + } + } +} diff --git a/lib/StaticPass.hpp b/lib/StaticPass.hpp new file mode 100644 index 0000000..3910b73 --- /dev/null +++ b/lib/StaticPass.hpp @@ -0,0 +1,24 @@ +#ifndef jk_STATICPASS_HPP +#define jk_STATICPASS_HPP + +#include "commons.hpp" +#include "SymTable.hpp" +#include "Node.hpp" + +namespace jk +{ + class StaticPass + { + public: + explicit StaticPass(std::shared_ptr sym, Logger& logger); + virtual ~StaticPass(); + + void pass(std::shared_ptr node); + + private: + std::shared_ptr m_sym; + Logger& m_logger; + }; +} + +#endif diff --git a/lib/SymTable.cpp b/lib/SymTable.cpp new file mode 100644 index 0000000..70603a1 --- /dev/null +++ b/lib/SymTable.cpp @@ -0,0 +1,64 @@ +#include "SymTable.hpp" + +namespace jk +{ + /*explicit*/ SymTable::SymTable(Logger& logger) + : m_logger { logger } + { + } + + /*virtual*/ SymTable::~SymTable() + { + } + + size_t + SymTable::declare(std::string const& name, + std::shared_ptr type, + Loc const& loc) + { + if (find(name)) + { + m_logger.log(LOG_ERROR, + loc, + std::string() + + "'" + + name + + "' is already defined"); + } + + size_t id = m_syms.size(); + + m_syms.push_back(Sym { + name, + type, + loc, + false, + id + }); + + return id; + } + + size_t + SymTable::declare_global(std::string const& name, + std::shared_ptr type, + Loc const& loc) + { + size_t addr = declare(name, type, loc); + m_syms[addr].is_global = true; + return addr; + } + + std::optional SymTable::find(std::string const& name) const + { + for (auto const& sym: m_syms) + { + if (sym.name == name) + { + return sym; + } + } + + return std::nullopt; + } +} diff --git a/lib/SymTable.hpp b/lib/SymTable.hpp new file mode 100644 index 0000000..0696ca4 --- /dev/null +++ b/lib/SymTable.hpp @@ -0,0 +1,43 @@ +#ifndef jk_SYMTABLE_HPP +#define jk_SYMTABLE_HPP + +#include "commons.hpp" +#include "Type.hpp" +#include "Logger.hpp" + +namespace jk +{ + struct Sym { + std::string name; + std::shared_ptr type; + Loc loc; + bool is_global = false; + size_t addr; + }; + + JK_ERROR(symbolic_error); + + class SymTable + { + public: + explicit SymTable(Logger& logger); + virtual ~SymTable(); + + size_t declare(std::string const& name, + std::shared_ptr type, + Loc const& loc); + + size_t declare_global(std::string const& name, + std::shared_ptr type, + Loc const& loc); + + std::optional find(std::string const& name) const; + + private: + Logger& m_logger; + std::vector m_syms; + size_t m_addr = 0; + }; +} + +#endif diff --git a/lib/Type.hpp b/lib/Type.hpp index 3f49eb5..efdb58c 100644 --- a/lib/Type.hpp +++ b/lib/Type.hpp @@ -3,9 +3,13 @@ #include "commons.hpp" -#define TYPE_TYPE(G) \ - G(TYPE_NIL), \ - G(TYPE_INT) +#define TYPE_TYPE(G) \ + G(TYPE_NIL), \ + G(TYPE_INT), \ + G(TYPE_FUNCTION), \ + G(TYPE_CODE), \ + G(TYPE_REF) + namespace jk { diff --git a/lib/VM.cpp b/lib/VM.cpp new file mode 100644 index 0000000..b7bab75 --- /dev/null +++ b/lib/VM.cpp @@ -0,0 +1,141 @@ +#include "VM.hpp" +#include "Function.hpp" +#include "Code.hpp" +#include "lib/Opcodes.hpp" + +namespace jk +{ + /*explicit*/ VM::VM() + { + } + + /*virtual*/ VM::~VM() + { + } + + void VM::execute(std::shared_ptr program) + { + Frame frame; + frame.program = program; + m_frames.push_back(frame); + + while (m_pc < m_frames.back().program->size()) + { + Instr instr = m_frames.back().program->get(m_pc); + + switch (instr.opcode) + { + case OPCODE_MK_FUNCTION: { + auto code = program->constant(pop()); + + auto function = std::make_shared + (code->as_code()->foreign()); + + auto value = Value::make_function(function); + + size_t addr = push_heap(value); + auto ref = Value::make_ref(addr); + + push(program->push_constant(ref)); + + m_pc++; + + } break; + + case OPCODE_PUSH_CONST: { + push(*instr.param); + m_pc++; + } break; + + case OPCODE_LOAD: { + auto value = m_frames.back().locals[*instr.param]; + push(program->push_constant(value)); + m_pc++; + } break; + + case OPCODE_STORE: { + auto idx = pop(); + + m_frames.back().locals[*instr.param] = + program->constant(idx); + + m_pc++; + } break; + + case OPCODE_LOAD_GLOBAL: { + auto value = m_globals[*instr.param]; + push(program->push_constant(value)); + m_pc++; + } break; + + case OPCODE_CALL: { + std::vector> args; + + for (size_t i=0; i<*instr.param; i++) + { + args.insert(std::begin(args), program->constant(pop())); + } + + auto ref_val = program->constant(pop()); + auto ref = ref_val->as_ref(); + auto fun_val = heap(ref); + auto fun = fun_val->as_function(); + + auto result = fun->call(args); + + push(program->push_constant(result)); + + m_pc++; + } break; + + default: + std::cerr << "cannot execute unknown opcode " + << OpcodeTypeStr[instr.opcode] + << std::endl; + abort(); + } + } + } + + std::string VM::string() const + { + std::stringstream ss; + + for (size_t i=0; i value) + { + m_heap.push_back(value); + return m_heap.size() - 1; + } + + std::shared_ptr VM::heap(size_t addr) + { + assert(addr < m_heap.size()); + return m_heap[addr]; + } + + void VM::set_global(size_t addr, std::shared_ptr value) + { + m_globals[addr] = value; + } + + void VM::push(param_t param) + { + m_stack.push_back(param); + } + + param_t VM::pop() + { + auto param = m_stack.back(); + m_stack.pop_back(); + return param; + } + +} diff --git a/lib/VM.hpp b/lib/VM.hpp new file mode 100644 index 0000000..90a6bdd --- /dev/null +++ b/lib/VM.hpp @@ -0,0 +1,41 @@ +#ifndef jk_VM_HPP +#define jk_VM_HPP + +#include "commons.hpp" +#include "Program.hpp" + +namespace jk +{ + struct Frame { + std::shared_ptr program; + std::unordered_map> locals; + }; + + class VM + { + public: + explicit VM(); + virtual ~VM(); + + void execute(std::shared_ptr program); + + std::string string() const; + + size_t push_heap(std::shared_ptr value); + std::shared_ptr heap(size_t addr); + + void set_global(size_t addr, std::shared_ptr value); + + private: + size_t m_pc = 0; + std::vector m_frames; + std::vector m_stack; + std::vector> m_heap; + std::unordered_map> m_globals; + + void push(param_t param); + param_t pop(); + }; +} + +#endif diff --git a/lib/Value.cpp b/lib/Value.cpp index c3547f8..b72e204 100644 --- a/lib/Value.cpp +++ b/lib/Value.cpp @@ -1,5 +1,7 @@ #include "Value.hpp" #include "Type.hpp" +#include "Function.hpp" +#include "Code.hpp" namespace jk { @@ -18,8 +20,61 @@ namespace jk return value; } + /*static*/ std::shared_ptr + Value::make_function(std::shared_ptr val) + { + auto value = std::make_shared(); + value->m_type = std::make_shared(TYPE_FUNCTION); + value->m_function_val = val; + return value; + } + + /*static*/ std::shared_ptr + Value::make_code(std::shared_ptr val) + { + auto value = std::make_shared(); + value->m_type = std::make_shared(TYPE_CODE); + value->m_code_val = val; + return value; + } + + /*static*/ std::shared_ptr + Value::make_ref(size_t val) + { + auto value = std::make_shared(); + value->m_type = std::make_shared(TYPE_REF); + value->m_ref_val = val; + return value; + } + + std::shared_ptr Value::as_function() const + { + return m_function_val; + } + + std::shared_ptr Value::as_code() const + { + return m_code_val; + } + std::weak_ptr Value::type() const { return m_type; } + + std::string Value::string() const + { + switch (m_type->type()) + { + case TYPE_NIL: return ""; + case TYPE_INT: return std::to_string(*m_int_val); + + default: + std::cerr << "cannot stringify value '" + << TypeTypeStr[m_type->type()] + << "'" + << std::endl; + abort(); + } + } } diff --git a/lib/Value.hpp b/lib/Value.hpp index 44cc922..25436ca 100644 --- a/lib/Value.hpp +++ b/lib/Value.hpp @@ -6,23 +6,38 @@ namespace jk { class Type; + class Function; + class Code; class Value { public: static std::shared_ptr make_nil(); static std::shared_ptr make_int(int val); + static std::shared_ptr make_function(std::shared_ptr + val); + static std::shared_ptr make_code(std::shared_ptr val); + static std::shared_ptr make_ref(size_t val); explicit Value() = default; virtual ~Value() = default; int as_int() const { return *m_int_val; } + size_t as_ref() const { return *m_ref_val; } + + std::shared_ptr as_function() const; + std::shared_ptr as_code() const; std::weak_ptr type() const; + std::string string() const; + private: std::shared_ptr m_type; std::optional m_int_val; + std::shared_ptr m_function_val; + std::shared_ptr m_code_val; + std::optional m_ref_val; }; } diff --git a/lib/commons.hpp b/lib/commons.hpp index 69fe580..f918fb6 100644 --- a/lib/commons.hpp +++ b/lib/commons.hpp @@ -14,5 +14,6 @@ #include #include "mutils.hpp" +#include "config.hpp" #endif diff --git a/meson.build b/meson.build index 74c1b18..7fa8c68 100644 --- a/meson.build +++ b/meson.build @@ -32,13 +32,30 @@ joko_lib = static_library( 'lib/Type.cpp', 'lib/Value.cpp', - + 'lib/Function.cpp', + 'lib/Code.cpp', + 'lib/SymTable.cpp', + 'lib/StaticPass.cpp', + 'lib/Compiler.cpp', + 'lib/Program.cpp', + 'lib/VM.cpp', + 'lib/Loader.cpp', ], dependencies: [ ]) joko_dep = declare_dependency(link_with: joko_lib) + +joko_core_lib = shared_library('joko-core', + sources: [ + 'core/core.cpp' + ], + dependencies: [ + joko_dep + ], + install: true, + install_dir: joko_libdir) executable('joko', sources: [ 'src/main.cpp', diff --git a/src/main.cpp b/src/main.cpp index ea82c86..d3315b6 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -2,6 +2,13 @@ #include #include "config.hpp" #include "../lib/Lexer.hpp" +#include "../lib/Parser.hpp" +#include "../lib/SymTable.hpp" +#include "../lib/StaticPass.hpp" +#include "../lib/Compiler.hpp" +#include "../lib/VM.hpp" +#include "lib/Value.hpp" +#include "../lib/Loader.hpp" int main(int argc, char** argv) { @@ -64,29 +71,46 @@ int main(int argc, char** argv) source += line + (file.eof() ? "" : "\n"); } - jk::Lexer lexer {logger, loc}; - lexer.scan(source); + auto lexer = std::make_shared(logger, loc); + auto parser = std::make_shared(logger, lexer); + auto ast = parser->parse(source); if (debug_mode) { - std::cout << "--- tokens ---" << std::endl; - std::shared_ptr tok; + std::cout << "--- ast ---" << std::endl; + std::cout << ast->string() << std::endl; + } - std::string sep; - while ( (tok = lexer.next()) ) - { - std::cout << sep << tok->string(); - sep = " "; - } + auto sym = std::make_shared(logger); - std::cout << std::endl; + auto static_pass = std::make_shared(sym, logger); + static_pass->pass(ast); + + auto compiler = std::make_shared(sym, logger); + auto program = std::make_shared(); + auto vm = std::make_shared(); + auto loader = std::make_shared(vm, sym); + loader->load(); + + compiler->compile(ast, program); + + if (debug_mode) + { + std::cout << "--- program ---" << std::endl; + std::cout << program->string() << std::endl; + } + + + vm->execute(program); + + if (debug_mode) + { + std::cout << "--- stack ---" << std::endl; + std::cout << vm->string() << std::endl; } return 0; } - - - return 0; } diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index de204af..5a957fd 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -50,3 +50,18 @@ TEST_CASE_METHOD(LexerTest, "Lexer_error") lexer->scan(" ยง "); REQUIRE_THROWS_AS(lexer->next(), jk::lexical_error); } + +TEST_CASE_METHOD(LexerTest, "Lexer_funcall") +{ + auto lexer = jk::Factory(m_logger, "tests/lexer").make_lexer(); + lexer->scan(" )( salut salut! salut? _salut -salut salut/monde"); + test_next(*lexer, "CPAR"); + test_next(*lexer, "OPAR"); + test_next(*lexer, "IDENT[salut]"); + test_next(*lexer, "IDENT[salut!]"); + test_next(*lexer, "IDENT[salut?]"); + test_next(*lexer, "IDENT[_salut]"); + test_next(*lexer, "IDENT[-salut]"); + test_next(*lexer, "IDENT[salut/monde]"); + test_end(*lexer); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp index fd87b66..33db916 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -8,10 +8,29 @@ public: explicit ParserTest() {} virtual ~ParserTest() {} + void test_parser(std::string const& oracle, std::string const& source) + { + auto parser = jk::Factory(m_logger, "tests/parser").make_parser(); + auto root = parser->parse(source); + + REQUIRE(oracle == root->string()); + } + protected: jk::Logger m_logger; }; -TEST_CASE_METHOD(ParserTest, "Parser_") +TEST_CASE_METHOD(ParserTest, "Parser_funcall") { + test_parser("PROG", + " "); + + test_parser("PROG(FUNCALL(IDENT[hello]))", + " (hello) "); + + test_parser("PROG(FUNCALL(IDENT[hello],INT[1],INT[2]))", + " (hello 1 2) "); + + test_parser("PROG(FUNCALL(IDENT[hello],INT[1],INT[2],IDENT[bim!]))", + " (hello 1 2 bim!) "); }