diff --git a/doc/grammar.bnf b/doc/grammar.bnf index 370828b..a7e193b 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -1,2 +1,6 @@ MODULE ::= EXPR* -EXPR ::= bool +EXPR ::= + bool +| ident +| VARDECL +VARDECL ::= opar decl ident EXPR cpar diff --git a/meson.build b/meson.build index 3d2b511..8a8e406 100644 --- a/meson.build +++ b/meson.build @@ -24,6 +24,7 @@ grino_src = static_library('grino', 'src/Program.cpp', 'src/VM.cpp', 'src/Value.cpp', + 'src/SymTable.cpp', ]) grino_dep = declare_dependency(link_with: grino_src) diff --git a/src/Compiler.cpp b/src/Compiler.cpp index 1c8c955..74beece 100644 --- a/src/Compiler.cpp +++ b/src/Compiler.cpp @@ -1,10 +1,13 @@ #include "Compiler.hpp" #include "Program.hpp" +#include "SymTable.hpp" +#include "src/opcodes.hpp" namespace grino { - /*explicit*/ Compiler::Compiler(Logger& logger) + /*explicit*/ Compiler::Compiler(Logger& logger, SymTable& sym) : m_logger { logger } + , m_sym { sym } { } @@ -21,6 +24,7 @@ namespace grino for (size_t i=0; isize(); i++) { compile(node->child(i).lock(), program); + program.push_instr(OPCODE_POP); } } break; @@ -32,6 +36,31 @@ namespace grino program.push_constant(value)); } break; + case NODE_VARDECL: { + std::string ident = node->child(0).lock()->repr(); + auto expr = node->child(1).lock(); + size_t address = get_local_address(); + + compile(expr, program); + + m_sym.declare(node->loc(), ident, address); + program.push_instr(OPCODE_STORE_LOCAL, address); + } break; + + case NODE_IDENT: { + std::string ident = node->repr(); + auto entry = m_sym.find(ident); + + if (entry == std::nullopt) + { + std::stringstream ss; + ss << "undefined variable '" << ident << "'"; + m_logger.log(LOG_ERROR, node->loc(), ss.str()); + } + + program.push_instr(OPCODE_LOAD_LOCAL, entry->addr); + } break; + default: std::cerr << "cannot compile node '" << GRINO_TRIM(NodeTypeStr[node->type()], "NODE_") @@ -39,4 +68,11 @@ namespace grino abort(); } } + + size_t Compiler::get_local_address() + { + static size_t addr = 0; + addr++; + return addr - 1; + } } diff --git a/src/Compiler.hpp b/src/Compiler.hpp index a5b25bb..166c7fa 100644 --- a/src/Compiler.hpp +++ b/src/Compiler.hpp @@ -4,15 +4,19 @@ #include "commons.hpp" #include "Logger.hpp" #include "Node.hpp" +#include "src/mutils.hpp" namespace grino { class Program; + class SymTable; + + GRINO_ERROR(compile_error); class Compiler { public: - explicit Compiler(Logger& logger); + explicit Compiler(Logger& logger, SymTable& sym); virtual ~Compiler(); void compile(std::shared_ptr node, @@ -20,6 +24,9 @@ namespace grino private: Logger& m_logger; + SymTable& m_sym; + + size_t get_local_address(); }; } diff --git a/src/Lexer.cpp b/src/Lexer.cpp index e3eb81c..f028f40 100644 --- a/src/Lexer.cpp +++ b/src/Lexer.cpp @@ -7,8 +7,14 @@ namespace grino : m_logger { logger } , m_loc {source_path, 1} { + add_text(NODE_OPAR, "(", false); + add_text(NODE_CPAR, ")", false); + add_text(NODE_DECL, "$", false); + add_keyword(NODE_BOOL, "true", true); add_keyword(NODE_BOOL, "false", true); + + m_scanners.push_back(std::bind(&Lexer::scan_ident, this)); } /*virtual*/ Lexer::~Lexer() @@ -125,6 +131,7 @@ namespace grino text, has_value)); if (text.size() == 1) { + m_separators.push_back(text[0]); } } @@ -141,7 +148,7 @@ namespace grino std::string const& text, bool has_value) { - if (!has_more(m_cursor + text.size())) + if (m_cursor + text.size() > m_source.size()) { return std::nullopt; } @@ -191,4 +198,28 @@ namespace grino }; } + std::optional Lexer::scan_ident() + { + size_t cursor = m_cursor; + + std::string repr; + + while (has_more(cursor) + && !is_sep(cursor)) + { + repr += at(cursor); + cursor++; + } + + if (repr.empty() == false) + { + return ScanInfo { + cursor, + NODE_IDENT, + repr + }; + } + + return std::nullopt; + } } diff --git a/src/Lexer.hpp b/src/Lexer.hpp index a46d02c..5f82807 100644 --- a/src/Lexer.hpp +++ b/src/Lexer.hpp @@ -57,6 +57,7 @@ namespace grino std::string const& text, bool has_value); + std::optional scan_ident(); }; } diff --git a/src/Node.hpp b/src/Node.hpp index 24a2f1a..7b7fb52 100644 --- a/src/Node.hpp +++ b/src/Node.hpp @@ -4,9 +4,14 @@ #include "commons.hpp" #include "Loc.hpp" -#define NODE_TYPE(G) \ - G(NODE_MODULE), \ - G(NODE_BOOL), +#define NODE_TYPE(G) \ + G(NODE_MODULE), \ + G(NODE_BOOL), \ + G(NODE_VARDECL), \ + G(NODE_IDENT), \ + G(NODE_OPAR), \ + G(NODE_CPAR), \ + G(NODE_DECL), namespace grino { diff --git a/src/Parser.cpp b/src/Parser.cpp index f8ee5f0..68d81a2 100644 --- a/src/Parser.cpp +++ b/src/Parser.cpp @@ -97,9 +97,15 @@ namespace grino } } + std::shared_ptr Parser::make_node(NodeType type, + std::string const& repr) + { + return std::make_shared(type, repr, loc()); + } + std::shared_ptr Parser::parse_module() { - auto node = std::make_shared(NODE_MODULE, "", loc()); + auto node = make_node(NODE_MODULE); while (m_cursor < m_tokens.size()) { @@ -111,7 +117,13 @@ namespace grino std::shared_ptr Parser::parse_expr() { - if (type_is(NODE_BOOL)) + if (type_is(NODE_OPAR)) + { + return parse_vardecl(); + } + + if (type_is(NODE_IDENT) + || type_is(NODE_BOOL)) { return consume(); } @@ -126,4 +138,17 @@ namespace grino return nullptr; } + std::shared_ptr Parser::parse_vardecl() + { + auto node = make_node(NODE_VARDECL); + consume(NODE_OPAR); + consume(NODE_DECL); + + node->add_child(consume(NODE_IDENT)); + node->add_child(parse_expr()); + + consume(NODE_CPAR); + + return node; + } } diff --git a/src/Parser.hpp b/src/Parser.hpp index 7a9bef1..fe89d0a 100644 --- a/src/Parser.hpp +++ b/src/Parser.hpp @@ -29,8 +29,12 @@ namespace grino bool type_is(std::vector const& types) const; Loc loc() const; + std::shared_ptr make_node(NodeType type, + std::string const& repr=""); + std::shared_ptr parse_module(); std::shared_ptr parse_expr(); + std::shared_ptr parse_vardecl(); }; } diff --git a/src/SymTable.cpp b/src/SymTable.cpp new file mode 100644 index 0000000..a220e7c --- /dev/null +++ b/src/SymTable.cpp @@ -0,0 +1,55 @@ +#include "SymTable.hpp" + +namespace grino +{ + /*explicit*/ SymTable::SymTable(Logger& logger) + : m_logger { logger } + { + } + + /*virtual*/ SymTable::~SymTable() + { + } + + void SymTable::declare(Loc const& loc, std::string const& name, size_t addr) + { + if (find(name)) + { + m_logger.log(LOG_ERROR, loc, "'" + + name + + "' already defined"); + } + + SymEntry entry; + entry.addr = addr; + entry.name = name; + entry.is_global = false; + + m_entries.push_back(entry); + } + + std::optional SymTable::find(std::string const& name) + { + for (size_t i=0; i find(std::string const& name); + + std::string string() const; + + private: + Logger& m_logger; + std::vector m_entries; + }; +} + +#endif diff --git a/src/VM.cpp b/src/VM.cpp index c6bdd8f..dc76f03 100644 --- a/src/VM.cpp +++ b/src/VM.cpp @@ -6,6 +6,7 @@ namespace grino /*explicit*/ VM::VM(Logger& logger) : m_logger { logger } { + m_frames.push_back(Frame {}); } /*virtual*/ VM::~VM() @@ -29,6 +30,24 @@ namespace grino m_pc++; } break; + case OPCODE_POP: { + pop(); + m_pc++; + } break; + + case OPCODE_STORE_LOCAL: { + size_t addr = *instr.param; + auto value = program.constant(top()); + set_local(addr, value); + m_pc++; + } break; + + case OPCODE_LOAD_LOCAL: { + size_t addr = *instr.param; + push(program.push_constant(local(addr))); + m_pc++; + } break; + default: std::cerr << "cannot execute unknown opcode " << OpcodeTypeStr[instr.opcode] @@ -72,4 +91,22 @@ namespace grino return addr; } + + size_t VM::top() + { + size_t addr = m_stack[m_sp - 1]; + return addr; + } + + std::shared_ptr VM::local(size_t addr) const + { + return m_frames.back().locals.at(addr); + } + + void VM::set_local(size_t addr, + std::shared_ptr value) + { + m_frames.back().locals[addr] = value; + } + } diff --git a/src/VM.hpp b/src/VM.hpp index 2da3dc7..86b9ca8 100644 --- a/src/VM.hpp +++ b/src/VM.hpp @@ -11,6 +11,10 @@ namespace grino GRINO_ERROR(execution_error); + struct Frame { + std::unordered_map> locals; + }; + class VM { public: @@ -23,6 +27,7 @@ namespace grino private: Logger& m_logger; std::array m_stack; + std::vector m_frames; size_t m_sp; /* stack pointer */ size_t m_bp; /* base pointer */ @@ -30,6 +35,10 @@ namespace grino void push(size_t addr); size_t pop(); + size_t top(); + std::shared_ptr local(size_t addr) const; + void set_local(size_t addr, + std::shared_ptr value); }; } diff --git a/src/main.cpp b/src/main.cpp index b7eb340..e05604d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -8,6 +8,7 @@ #include "Program.hpp" #include "VM.hpp" #include "Logger.hpp" +#include "src/SymTable.hpp" int main(int argc, char** argv) { @@ -73,8 +74,9 @@ int main(int argc, char** argv) std::cout << "--- ast ---" << std::endl; std::cout << ast->string() << std::endl; } + grino::SymTable sym_table {logger}; - grino::Compiler compiler {logger}; + grino::Compiler compiler {logger, sym_table}; grino::Program program; compiler.compile(ast, program); diff --git a/src/opcodes.hpp b/src/opcodes.hpp index 29399bd..d20c591 100644 --- a/src/opcodes.hpp +++ b/src/opcodes.hpp @@ -4,8 +4,11 @@ #include "commons.hpp" #include "src/mutils.hpp" -#define OPCODES(G) \ - G(OPCODE_LOAD_CONST) +#define OPCODES(G) \ + G(OPCODE_LOAD_CONST), \ + G(OPCODE_POP), \ + G(OPCODE_LOAD_LOCAL), \ + G(OPCODE_STORE_LOCAL) namespace grino { diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index b866e94..642f3c0 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -24,13 +24,6 @@ protected: grino::Logger m_logger; }; -TEST_CASE_METHOD(LexerTest, "Lexer_errors") -{ - grino::Lexer lexer {m_logger, "tests/lexer"}; - lexer.scan(" ยง "); - REQUIRE_THROWS_AS(lexer.next(), grino::lexical_error); -} - TEST_CASE_METHOD(LexerTest, "Lexer_booleans") { grino::Lexer lexer {m_logger, "tests/lexer"}; @@ -62,3 +55,25 @@ TEST_CASE_METHOD(LexerTest, "Lexer_comments") test_end(lexer); } + +TEST_CASE_METHOD(LexerTest, "Lexer_vardecl") +{ + grino::Lexer lexer {m_logger, "tests/lexer"}; + + lexer.scan(" $ () coucou) "); + test_next(lexer, "DECL"); + test_next(lexer, "OPAR"); + test_next(lexer, "CPAR"); + test_next(lexer, "IDENT[coucou]"); + test_next(lexer, "CPAR"); + test_end(lexer); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_no_end_space") +{ + grino::Lexer lexer {m_logger, "tests/lexer"}; + + lexer.scan(")"); + test_next(lexer, "CPAR"); + test_end(lexer); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp index ca74ed0..d0914ed 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -10,14 +10,15 @@ public: void test_parse(std::string const& oracle, std::string const& source) { - auto root = m_parser.parse(source); + grino::Logger logger; + grino::Lexer lexer {logger, "tests/parser"}; + grino::Parser parser {logger, lexer}; + + auto root = parser.parse(source); REQUIRE(oracle == root->string()); } protected: - grino::Logger m_logger; - grino::Lexer m_lexer {m_logger, "tests/parser"}; - grino::Parser m_parser {m_logger, m_lexer}; }; TEST_CASE_METHOD(ParserTest, "Parser_empty") @@ -29,3 +30,12 @@ TEST_CASE_METHOD(ParserTest, "Parser_booleans") { test_parse("MODULE(BOOL[true],BOOL[false])", "true false"); } + +TEST_CASE_METHOD(ParserTest, "Parser_vardecl") +{ + test_parse("MODULE(VARDECL(IDENT[hello],BOOL[false]))", + "($ hello false)"); + + test_parse("MODULE(VARDECL(IDENT[hello],IDENT[world]))", + "($ hello world)"); +}