diff --git a/doc/grammar.bnf b/doc/grammar.bnf index 8754905..e02a3e8 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -2,7 +2,14 @@ PROG ::= (INSTR (EOI+ INSTR)*)? INSTR ::= EXPR | assert EXPR -| assert_static_fail EXPR +| assert_static_fail INSTR +| VARDECL +| CONSTDECL +| ASSIGN + +VARDECL ::= let_mut ident assign EXPR +CONSTDECL ::= let ident assign EXPR +ASSIGN ::= ident assign EXPR EXPR ::= IMP @@ -19,4 +26,4 @@ FACTOR ::= UNOP ((mul | div | mod) UNOP)* UNOP ::= (add | sub | not)? POW POW ::= GROUP (pow GROUP)? GROUP ::= BASE | opar EXPR cpar -BASE ::= int | bool +BASE ::= int | bool | ident diff --git a/lib/Compiler.cpp b/lib/Compiler.cpp index 02f8fc0..7ad4230 100644 --- a/lib/Compiler.cpp +++ b/lib/Compiler.cpp @@ -35,8 +35,9 @@ namespace roza try { - StaticPass static_pass {m_log}; + StaticPass static_pass {m_log, m_sym}; static_pass.check_children(root); + compile_children(root, prog); failed = true; @@ -52,6 +53,33 @@ namespace roza } break; + case NODE_IDENT: { + SymEntry const& entry = m_sym.find(root->repr()); + prog->push_instr(OP_LOAD_GLOBAL, entry.addr); + } break; + + case NODE_VARDECL: { + std::string name = root->child(0)->repr(); + compile_node(root->child(1), prog); + int addr = m_sym.declare_mut(name, root->child(1)); + prog->push_instr(OP_STORE_GLOBAL, addr); + + } break; + + case NODE_CONSTDECL: { + std::string name = root->child(0)->repr(); + compile_node(root->child(1), prog); + int addr = m_sym.declare(name, root->child(1)); + prog->push_instr(OP_STORE_GLOBAL, addr); + + } break; + + case NODE_ASSIGN: { + int addr = m_sym.find(root->child(0)->repr()).addr; + compile_node(root->child(1), prog); + prog->push_instr(OP_STORE_GLOBAL, addr); + } break; + case NODE_INT: { auto value = std::make_shared(std::stoi(root->repr()), root->loc()); prog->push_instr(OP_PUSH_CONST, prog->push_value(value)); diff --git a/lib/Compiler.hpp b/lib/Compiler.hpp index fd47e5a..89bec7a 100644 --- a/lib/Compiler.hpp +++ b/lib/Compiler.hpp @@ -6,6 +6,7 @@ #include "Node.hpp" #include "opcodes.hpp" #include "StatusLog.hpp" +#include "SymTable.hpp" namespace roza { @@ -21,6 +22,7 @@ namespace roza private: StatusLog& m_log; + SymTable m_sym; }; } diff --git a/lib/Lexer.cpp b/lib/Lexer.cpp index b0fbf73..46c09d3 100644 --- a/lib/Lexer.cpp +++ b/lib/Lexer.cpp @@ -23,9 +23,12 @@ namespace roza {"^", NODE_POW, false}, {"(", NODE_OPAR, false}, {")", NODE_CPAR, false}, + {"=", NODE_ASSIGN, false}, }; std::vector> keywords = { + {"let!", NODE_LET_MUT, false}, + {"let", NODE_LET, false}, {"true", NODE_BOOL, true}, {"false", NODE_BOOL, true}, {"and", NODE_AND, false}, @@ -35,7 +38,6 @@ namespace roza {"assert", NODE_ASSERT, false}, }; - m_scanners.push_back(std::bind(&Lexer::scan_int, this)); for (auto const& entry: keywords) @@ -54,6 +56,7 @@ namespace roza std::get<2>(entry))); } + m_scanners.push_back(std::bind(&Lexer::scan_ident, this)); } /*virtual*/ Lexer::~Lexer() @@ -225,4 +228,42 @@ namespace roza }; } + ScanInfo Lexer::scan_ident() const + { + size_t cursor = m_cursor; + std::string value; + + auto is_ident = [](size_t pos, char c){ + bool other = false; + + if (pos > 0) + { + other = + std::isdigit(c); + } + + return c == '_' || std::isalpha(c) || other; + }; + + size_t pos = 0; + + while (cursor < m_source.size() + && is_ident(pos, m_source[cursor])) + { + value += m_source[cursor]; + cursor++; + pos++; + } + + if (value.empty() == false) + { + return ScanInfo { + std::make_shared(NODE_IDENT, value, loc()), + cursor + }; + } + + return ScanInfo { + }; + } } diff --git a/lib/Lexer.hpp b/lib/Lexer.hpp index 9ecc8fd..ba2391e 100644 --- a/lib/Lexer.hpp +++ b/lib/Lexer.hpp @@ -48,6 +48,8 @@ namespace roza ScanInfo scan_keyword(std::string const& keyword, NodeType type, bool value=false) const; + + ScanInfo scan_ident() const; }; } diff --git a/lib/Node.hpp b/lib/Node.hpp index 35657ab..176ad41 100644 --- a/lib/Node.hpp +++ b/lib/Node.hpp @@ -11,7 +11,9 @@ G(NODE_CPAR), G(NODE_UADD), G(NODE_USUB), G(NODE_BOOL), \ G(NODE_AND), G(NODE_OR), G(NODE_NOT), G(NODE_IMP), \ G(NODE_EQ), G(NODE_NE), G(NODE_LT), G(NODE_LE), G(NODE_GT), \ - G(NODE_GE), G(NODE_ASSERT), G(NODE_ASSERT_STATIC_FAIL) + G(NODE_GE), G(NODE_ASSERT), G(NODE_ASSERT_STATIC_FAIL), \ + G(NODE_IDENT), G(NODE_ASSIGN), G(NODE_LET), G(NODE_LET_MUT), \ + G(NODE_VARDECL), G(NODE_CONSTDECL) namespace roza { diff --git a/lib/Parser.cpp b/lib/Parser.cpp index 953e3f9..48049d5 100644 --- a/lib/Parser.cpp +++ b/lib/Parser.cpp @@ -94,6 +94,12 @@ namespace roza return ret; } + void Parser::ensure(NodeType type) + { + consume_or_nullptr(type); + consume_all(type); + } + void Parser::next() { m_cursor++; @@ -121,22 +127,88 @@ namespace roza { consume_all(NODE_EOI); - std::shared_ptr root; - - if (type_is(NODE_ASSERT) - || type_is(NODE_ASSERT_STATIC_FAIL)) + if (type_is(NODE_ASSERT)) { - root = consume(); + auto root = consume(); root->add_child(parse_expr()); + + ensure(NODE_EOI); + return root; + } + else if (type_is(NODE_ASSERT_STATIC_FAIL)) + { + auto root = consume(); + root->add_child(parse_instr()); + return root; + } + else if (type_is(NODE_LET)) + { + auto root = parse_constdecl(); + ensure(NODE_EOI); + return root; + } + else if (type_is(NODE_LET_MUT)) + { + auto root = parse_vardecl(); + ensure(NODE_EOI); + return root; + } + else if (type_is(NODE_IDENT) && type_is(NODE_ASSIGN, 1)) + { + auto root = parse_assign(); + ensure(NODE_EOI); + return root; } else { - root = parse_expr(); + auto root = parse_expr(); + ensure(NODE_EOI); + return root; } + m_log.fatal(node()->loc(), + std::string() + + "unknown instruction '" + + std::string(NodeTypeStr[node()->type()]) + .substr(std::string("NODE_").size()) + + "'"); - consume_or_nullptr(NODE_EOI); - consume_all(NODE_EOI); + return nullptr; + } + + std::shared_ptr Parser::parse_vardecl() + { + consume(NODE_LET_MUT); + auto root = std::make_shared(NODE_VARDECL, "", node()->loc()); + auto ident = consume(NODE_IDENT); + consume(NODE_ASSIGN); + auto expr = parse_expr(); + root->add_child(ident); + root->add_child(expr); + + return root; + } + + std::shared_ptr Parser::parse_constdecl() + { + consume(NODE_LET); + auto root = std::make_shared(NODE_CONSTDECL, "", node()->loc()); + auto ident = consume(NODE_IDENT); + consume(NODE_ASSIGN); + auto expr = parse_expr(); + root->add_child(ident); + root->add_child(expr); + + return root; + } + + std::shared_ptr Parser::parse_assign() + { + auto lhs = consume(NODE_IDENT); + auto root = consume(NODE_ASSIGN); + + root->add_child(lhs); + root->add_child(parse_expr()); return root; } @@ -320,13 +392,14 @@ namespace roza std::shared_ptr Parser::parse_base() { if (type_is(NODE_INT) - || type_is(NODE_BOOL)) + || type_is(NODE_BOOL) + || type_is(NODE_IDENT)) { return consume(); } m_log.fatal(node()->loc(), - "unknown node '" + "cannot parse unknown node '" + node()->string() + "'"); diff --git a/lib/Parser.hpp b/lib/Parser.hpp index 22e155e..d402379 100644 --- a/lib/Parser.hpp +++ b/lib/Parser.hpp @@ -27,10 +27,16 @@ namespace roza std::shared_ptr consume(NodeType type); void consume_all(NodeType type); std::shared_ptr consume(); + void ensure(NodeType type); void next(); std::shared_ptr parse_prog(); std::shared_ptr parse_instr(); + + std::shared_ptr parse_vardecl(); + std::shared_ptr parse_constdecl(); + std::shared_ptr parse_assign(); + std::shared_ptr parse_expr(); std::shared_ptr parse_imp(); std::shared_ptr parse_or(); diff --git a/lib/StaticPass.cpp b/lib/StaticPass.cpp index 2e11121..e55c079 100644 --- a/lib/StaticPass.cpp +++ b/lib/StaticPass.cpp @@ -5,7 +5,13 @@ namespace roza { /*explicit*/ StaticPass::StaticPass(StatusLog& log) + : StaticPass (log, SymTable {}) + { + } + + /*explicit*/ StaticPass::StaticPass(StatusLog& log, SymTable const& sym_table) : m_log { log } + , m_sym { SymTable(sym_table) } { } @@ -16,9 +22,35 @@ namespace roza void StaticPass::check(std::shared_ptr root) { TypeResolver resolver {m_log}; + assert(root); switch (root->type()) { + case NODE_CONSTDECL: { + check(root->child(1)); + m_sym.declare(root->child(0)->repr(), root->child(1)); + } break; + + case NODE_VARDECL: { + check(root->child(1)); + m_sym.declare_mut(root->child(0)->repr(), root->child(1)); + } break; + + case NODE_ASSIGN: { + auto const& entry = m_sym.find(root->child(0)->repr()); + + if (!entry.is_mut) + { + m_log.fatal(root->child(0)->loc(), + root->child(0)->repr() + " is not mutable"); + } + + auto lhs = resolver.find(entry.node, m_sym); + auto rhs = resolver.find(root->child(1), m_sym); + check_types(root, lhs, rhs); + } break; + + case NODE_IDENT: case NODE_ASSERT_STATIC_FAIL: case NODE_INT: case NODE_BOOL: @@ -27,8 +59,8 @@ namespace roza case NODE_EQ: case NODE_NE: { check_children(root); - auto lhs = resolver.find(root->child(0)); - auto rhs = resolver.find(root->child(1)); + auto lhs = resolver.find(root->child(0), m_sym); + auto rhs = resolver.find(root->child(1), m_sym); check_types(root, lhs, rhs); } break; @@ -36,8 +68,8 @@ namespace roza case NODE_OR: case NODE_AND: { check_children(root); - auto lhs = resolver.find(root->child(0)); - auto rhs = resolver.find(root->child(1)); + auto lhs = resolver.find(root->child(0), m_sym); + auto rhs = resolver.find(root->child(1), m_sym); check_types(root, lhs, std::make_shared(TY_BOOL)); check_types(root, lhs, rhs); } break; @@ -45,7 +77,7 @@ namespace roza case NODE_ASSERT: case NODE_NOT: { check_children(root); - auto lhs = resolver.find(root->child(0)); + auto lhs = resolver.find(root->child(0), m_sym); check_types(root, lhs, std::make_shared(TY_BOOL)); } break; @@ -60,8 +92,9 @@ namespace roza case NODE_MOD: case NODE_POW: { check_children(root); - auto lhs = resolver.find(root->child(0)); - auto rhs = resolver.find(root->child(1)); + auto lhs = resolver.find(root->child(0), m_sym); + auto rhs = resolver.find(root->child(1), m_sym); + check_types(root, lhs, std::make_shared(TY_INT)); check_types(root, lhs, rhs); } break; @@ -69,7 +102,7 @@ namespace roza case NODE_UADD: case NODE_USUB: { check_children(root); - auto lhs = resolver.find(root->child(0)); + auto lhs = resolver.find(root->child(0), m_sym); check_types(root, lhs, std::make_shared(TY_INT)); } break; @@ -94,6 +127,9 @@ namespace roza std::shared_ptr lhs, std::shared_ptr rhs) { + assert(lhs); + assert(rhs); + if (!lhs->equals(*rhs)) { m_log.fatal(root->loc(), diff --git a/lib/StaticPass.hpp b/lib/StaticPass.hpp index 5697b6c..3c38104 100644 --- a/lib/StaticPass.hpp +++ b/lib/StaticPass.hpp @@ -5,6 +5,7 @@ #include "StatusLog.hpp" #include "Node.hpp" #include "Type.hpp" +#include "SymTable.hpp" namespace roza { @@ -12,6 +13,7 @@ namespace roza { public: explicit StaticPass(StatusLog& log); + explicit StaticPass(StatusLog& log, SymTable const& sym_table); virtual ~StaticPass(); void check(std::shared_ptr root); @@ -19,6 +21,7 @@ namespace roza private: StatusLog& m_log; + SymTable m_sym; void check_types(std::shared_ptr root, std::shared_ptr lhs, diff --git a/lib/SymTable.cpp b/lib/SymTable.cpp new file mode 100644 index 0000000..b246e96 --- /dev/null +++ b/lib/SymTable.cpp @@ -0,0 +1,69 @@ +#include "SymTable.hpp" + +namespace roza +{ + /*static*/ int SymTable::addr = 0; + + /*explicit*/ SymTable::SymTable() + { + } + + /*explicit*/ SymTable::SymTable(SymTable const& sym_table) + { + m_scope = sym_table.m_scope; + + for (auto const& entry: sym_table.m_entries) + { + m_entries[entry.first] = entry.second; + } + } + + /*virtual*/ SymTable::~SymTable() + { + } + + int SymTable::declare(std::string const& name, std::shared_ptr node) + { + assert(!exists(name)); + + m_entries.insert({name, SymEntry { + SymTable::addr++, + m_scope, + node, + false + }}); + + return SymTable::addr - 1; + } + + int SymTable::declare_mut(std::string const& name, std::shared_ptr node) + { + assert(!exists(name)); + + m_entries.insert({name, SymEntry { + SymTable::addr++, + m_scope, + node, + true + }}); + + return SymTable::addr - 1; + } + + SymEntry& SymTable::find(std::string const& name) + { + assert(exists(name)); + return m_entries[name]; + } + + SymEntry const& SymTable::find(std::string const& name) const + { + assert(exists(name)); + return m_entries.at(name); + } + + bool SymTable::exists(std::string const& name) const + { + return m_entries.find(name) != std::end(m_entries); + } +} diff --git a/lib/SymTable.hpp b/lib/SymTable.hpp new file mode 100644 index 0000000..dbf1385 --- /dev/null +++ b/lib/SymTable.hpp @@ -0,0 +1,39 @@ +#ifndef roza_SYMTABLE_HPP +#define roza_SYMTABLE_HPP + +#include "commons.hpp" +#include "Node.hpp" + +namespace roza +{ + struct SymEntry { + int addr; + int scope; + std::shared_ptr node; + bool is_mut; + }; + + class SymTable + { + public: + explicit SymTable(); + explicit SymTable(SymTable const& sym_table); + + virtual ~SymTable(); + + int declare(std::string const& name, std::shared_ptr node); + int declare_mut(std::string const& name, std::shared_ptr node); + + SymEntry& find(std::string const& name); + SymEntry const& find(std::string const& name) const; + + bool exists(std::string const& name) const; + + private: + static int addr; + std::unordered_map m_entries; + int m_scope = 0; + }; +} + +#endif diff --git a/lib/TypeResolver.cpp b/lib/TypeResolver.cpp index 9e5216b..d80ed5d 100644 --- a/lib/TypeResolver.cpp +++ b/lib/TypeResolver.cpp @@ -11,12 +11,24 @@ namespace roza { } - std::shared_ptr TypeResolver::find(std::shared_ptr root) + std::shared_ptr TypeResolver::find(std::shared_ptr root, + SymTable const& sym) { switch (root->type()) { case NODE_PROG: { - return find(root->child(root->size() - 1)); + return find(root->child(root->size() - 1), sym); + } break; + + case NODE_IDENT: { + std::string name = root->repr(); + SymEntry const& entry = sym.find(name); + return find(entry.node, sym); + } break; + + case NODE_CONSTDECL: + case NODE_VARDECL: { + auto ty = find(root->child(1), sym); } break; case NODE_INT: { @@ -45,7 +57,7 @@ namespace roza case NODE_POW: case NODE_UADD: case NODE_USUB:{ - return find(root->child(0)); + return find(root->child(0), sym); } break; default: diff --git a/lib/TypeResolver.hpp b/lib/TypeResolver.hpp index e3d7045..919002b 100644 --- a/lib/TypeResolver.hpp +++ b/lib/TypeResolver.hpp @@ -5,6 +5,7 @@ #include "Type.hpp" #include "StatusLog.hpp" #include "Node.hpp" +#include "SymTable.hpp" namespace roza { @@ -14,7 +15,7 @@ namespace roza explicit TypeResolver(StatusLog& log); virtual ~TypeResolver(); - std::shared_ptr find(std::shared_ptr root); + std::shared_ptr find(std::shared_ptr root, SymTable const& sym); private: StatusLog& m_log; }; diff --git a/lib/VM.cpp b/lib/VM.cpp index 103df2b..fca21b3 100644 --- a/lib/VM.cpp +++ b/lib/VM.cpp @@ -19,6 +19,23 @@ namespace roza { switch (program->opcode(m_pc)) { + case OP_LOAD_GLOBAL: { + int addr = *program->param(m_pc); + auto value = m_globals[addr]; + push(program->push_value(value)); + + m_pc++; + } break; + + case OP_STORE_GLOBAL: { + auto value = program->value(pop()); + int addr = *program->param(m_pc); + + m_globals[addr] = value; + + m_pc++; + } break; + case OP_ASSERT: { auto value = program->value(pop()); diff --git a/lib/VM.hpp b/lib/VM.hpp index e981fe5..334d4ef 100644 --- a/lib/VM.hpp +++ b/lib/VM.hpp @@ -27,6 +27,7 @@ namespace roza StatusLog& m_log; std::vector m_stack; std::shared_ptr m_last_program; + std::unordered_map> m_globals; size_t m_pc = 0; void push(param_t param); diff --git a/lib/opcodes.hpp b/lib/opcodes.hpp index 055339c..0c2ceef 100644 --- a/lib/opcodes.hpp +++ b/lib/opcodes.hpp @@ -8,7 +8,8 @@ G(OP_POP), \ G(OP_IADD), G(OP_ISUB), G(OP_IMUL), G(OP_IDIV), G(OP_IMOD), G(OP_IPOW), \ G(OP_MOD), G(OP_IUADD), G(OP_IUSUB), G(OP_AND), G(OP_OR), G(OP_NOT), \ - G(OP_IMP), G(OP_EQ), G(OP_ILT), G(OP_IGT), G(OP_ASSERT) + G(OP_IMP), G(OP_EQ), G(OP_ILT), G(OP_IGT), G(OP_ASSERT), G(OP_STORE_GLOBAL), \ + G(OP_LOAD_GLOBAL) namespace roza { diff --git a/meson.build b/meson.build index 9457ff8..aaa641a 100644 --- a/meson.build +++ b/meson.build @@ -22,6 +22,7 @@ roza_lib = static_library( 'lib/Type.cpp', 'lib/Value.cpp', 'lib/TypeResolver.cpp', + 'lib/SymTable.cpp', ] ) diff --git a/roza_tests/test_var.roza b/roza_tests/test_var.roza new file mode 100644 index 0000000..6cde31d --- /dev/null +++ b/roza_tests/test_var.roza @@ -0,0 +1,21 @@ +let! a = 34 + +assert 34 == a +assert 102 == 3 * a + +a = 9 + +a = a + 1 + +assert 11 == a + 1 + +assert_static_fail a and true +assert_static_fail a = false + +let b = 7 + +assert 7 == b +assert 42 == 6 * b +assert 17 == a + b + +assert_static_fail b = 4 \ No newline at end of file diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index 72047be..684b72a 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -58,7 +58,6 @@ TEST_CASE_METHOD(LexerTest, "Lexer_int_arith") TEST_CASE_METHOD(LexerTest, "Lexer_keywords") { - REQUIRE_THROWS(m_lexer.scan(" andor ")); REQUIRE_NOTHROW(m_lexer.scan(" and+ ")); REQUIRE_NOTHROW(m_lexer.scan(" (and) ")); } @@ -94,3 +93,13 @@ TEST_CASE_METHOD(LexerTest, "Lexer_asserts") REQUIRE("ASSERT_STATIC_FAIL" == get_str(1)); REQUIRE("" == get_str(2)); } + +TEST_CASE_METHOD(LexerTest, "Lexer_declarations") +{ + m_lexer.scan(" let let! hello = "); + REQUIRE("LET" == get_str(0)); + REQUIRE("LET_MUT" == get_str(1)); + REQUIRE("IDENT[hello]" == get_str(2)); + REQUIRE("ASSIGN" == get_str(3)); + REQUIRE("" == get_str(4)); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp index 03b2819..69fbab2 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -110,3 +110,15 @@ TEST_CASE_METHOD(ParserTest, "Parser_assertions") test_node("PROG(ASSERT_STATIC_FAIL(EQ(ADD(INT[1],INT[1]),INT[2])))", "assert_static_fail 1 + 1 == 2"); } + +TEST_CASE_METHOD(ParserTest, "Parser_vardecl") +{ + test_node("PROG(VARDECL(IDENT[x],INT[34]))", + "let! x = 34"); + + test_node("PROG(CONSTDECL(IDENT[x],INT[34]))", + "let x = 34"); + + test_node("PROG(CONSTDECL(IDENT[_coucou],MUL(INT[6],INT[7])))", + "let _coucou = 6 * 7"); +} diff --git a/tests/TypeResolver.cpp b/tests/TypeResolver.cpp index 83ea838..19b71eb 100644 --- a/tests/TypeResolver.cpp +++ b/tests/TypeResolver.cpp @@ -16,11 +16,12 @@ public: roza::StatusLog log; roza::Lexer lexer {log, loc}; roza::Parser parser {lexer, log}; + roza::SymTable sym; roza::TypeResolver resolver {log}; lexer.scan(source); auto node = parser.parse(); - return resolver.find(node); + return resolver.find(node, sym); } void test_ty(std::string const& source, std::shared_ptr ty)