From d72660e6ded526c1104a768c7fe38dc8fb20526b Mon Sep 17 00:00:00 2001 From: bog Date: Fri, 1 Sep 2023 01:22:51 +0200 Subject: [PATCH] ADD: if-then-else statement. --- doc/grammar.bnf | 4 ++ lib/Compiler.cpp | 45 +++++++++++++++ lib/Lexer.cpp | 4 ++ lib/Node.hpp | 21 +++---- lib/Parser.cpp | 55 +++++++++++++++++++ lib/Parser.hpp | 3 + lib/Program.cpp | 6 ++ lib/Program.hpp | 2 + lib/StaticPass.cpp | 14 +++++ lib/SymTable.cpp | 119 ++++++++++++++++++++++++++++++++-------- lib/SymTable.hpp | 7 ++- lib/VM.cpp | 18 ++++++ lib/commons.hpp | 1 + lib/opcodes.hpp | 2 +- roza_tests/test_if.roza | 92 +++++++++++++++++++++++++++++++ tests/Lexer.cpp | 9 +++ tests/Parser.cpp | 15 +++++ 17 files changed, 381 insertions(+), 36 deletions(-) create mode 100644 roza_tests/test_if.roza diff --git a/doc/grammar.bnf b/doc/grammar.bnf index e02a3e8..1d8b4f4 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -6,10 +6,14 @@ INSTR ::= EXPR | VARDECL | CONSTDECL | ASSIGN +| IF VARDECL ::= let_mut ident assign EXPR CONSTDECL ::= let ident assign EXPR ASSIGN ::= ident assign EXPR +IF ::= if EXPR THEN (else (IF | ELSE))? +THEN ::= INSTR* +ELSE ::= INSTR* EXPR ::= IMP diff --git a/lib/Compiler.cpp b/lib/Compiler.cpp index 7ad4230..624a8e9 100644 --- a/lib/Compiler.cpp +++ b/lib/Compiler.cpp @@ -25,6 +25,51 @@ namespace roza { switch (root->type()) { + + case NODE_IF: { + m_sym.enter_scope(); + + std::vector to_end; + + std::function)> + f = [&](std::shared_ptr n){ + auto cond = n->child(0); + auto then = n->child(1); + + compile_node(cond, prog); + size_t cond_addr = prog->size(); + prog->push_instr(OP_BRF, 0); + + compile_node(then, prog); + + to_end.push_back(prog->size()); + prog->push_instr(OP_BR, 0); + + prog->set_param(cond_addr, prog->size()); + + if (n->size() > 2) + { + auto next = n->child(2); + compile_node(next, prog); + } + }; + + f(root); + + for (auto addr: to_end) + { + prog->set_param(addr, prog->size()); + } + + m_sym.leave_scope(); + + } break; + + case NODE_ELSE: + case NODE_THEN: { + compile_children(root, prog); + } break; + case NODE_ASSERT: { compile_children(root, prog); prog->push_instr(OP_ASSERT); diff --git a/lib/Lexer.cpp b/lib/Lexer.cpp index 46c09d3..f831f7b 100644 --- a/lib/Lexer.cpp +++ b/lib/Lexer.cpp @@ -8,6 +8,7 @@ namespace roza , m_loc { loc } { std::vector> texts = { + {";", NODE_EOI, false}, {"==", NODE_EQ, false}, {"!=", NODE_NE, false}, {"<=", NODE_LE, false}, @@ -27,6 +28,9 @@ namespace roza }; std::vector> keywords = { + {"if", NODE_IF, false}, + {"else", NODE_ELSE, false}, + {"end", NODE_END, false}, {"let!", NODE_LET_MUT, false}, {"let", NODE_LET, false}, {"true", NODE_BOOL, true}, diff --git a/lib/Node.hpp b/lib/Node.hpp index 176ad41..d341802 100644 --- a/lib/Node.hpp +++ b/lib/Node.hpp @@ -4,16 +4,17 @@ #include "commons.hpp" #include "SrcLoc.hpp" -#define NODE_TYPE(G) \ - G(NODE_PROG), G(NODE_INSTR), G(NODE_EOI), \ - G(NODE_INT), G(NODE_ADD), G(NODE_SUB), G(NODE_MUL), \ - G(NODE_DIV), G(NODE_MOD), G(NODE_POW), G(NODE_OPAR), \ - 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_IDENT), G(NODE_ASSIGN), G(NODE_LET), G(NODE_LET_MUT), \ - G(NODE_VARDECL), G(NODE_CONSTDECL) +#define NODE_TYPE(G) \ + G(NODE_PROG), G(NODE_INSTR), G(NODE_EOI), \ + G(NODE_INT), G(NODE_ADD), G(NODE_SUB), G(NODE_MUL), \ + G(NODE_DIV), G(NODE_MOD), G(NODE_POW), G(NODE_OPAR), \ + 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_IDENT), G(NODE_ASSIGN), G(NODE_LET), G(NODE_LET_MUT), \ + G(NODE_VARDECL), G(NODE_CONSTDECL), G(NODE_IF), G(NODE_ELSE), \ + G(NODE_THEN), G(NODE_END) namespace roza { diff --git a/lib/Parser.cpp b/lib/Parser.cpp index 48049d5..483b6a5 100644 --- a/lib/Parser.cpp +++ b/lib/Parser.cpp @@ -159,6 +159,12 @@ namespace roza ensure(NODE_EOI); return root; } + else if (type_is(NODE_IF)) + { + auto root = parse_if(); + consume_all(NODE_EOI); + return root; + } else { auto root = parse_expr(); @@ -213,6 +219,55 @@ namespace roza return root; } + std::shared_ptr Parser::parse_if() + { + auto root = consume(NODE_IF); + root->add_child(parse_expr()); + root->add_child(parse_then()); + + if (type_is(NODE_ELSE) + && type_is(NODE_IF, 1)) + { + consume(NODE_ELSE); + root->add_child(parse_if()); + return root; + } + + if (type_is(NODE_ELSE)) + { + root->add_child(parse_else()); + } + + consume(NODE_END); + + return root; + } + + std::shared_ptr Parser::parse_then() + { + auto root = std::make_shared(NODE_THEN, "", node()->loc()); + + while (!type_is(NODE_ELSE) + && !type_is(NODE_END)) + { + root->add_child(parse_instr()); + } + + return root; + } + + std::shared_ptr Parser::parse_else() + { + auto root = consume(NODE_ELSE); + + while (!type_is(NODE_END)) + { + root->add_child(parse_instr()); + } + + return root; + } + std::shared_ptr Parser::parse_expr() { return parse_imp(); diff --git a/lib/Parser.hpp b/lib/Parser.hpp index d402379..07e27e2 100644 --- a/lib/Parser.hpp +++ b/lib/Parser.hpp @@ -36,6 +36,9 @@ namespace roza std::shared_ptr parse_vardecl(); std::shared_ptr parse_constdecl(); std::shared_ptr parse_assign(); + std::shared_ptr parse_if(); + std::shared_ptr parse_then(); + std::shared_ptr parse_else(); std::shared_ptr parse_expr(); std::shared_ptr parse_imp(); diff --git a/lib/Program.cpp b/lib/Program.cpp index e7af3f4..728d924 100644 --- a/lib/Program.cpp +++ b/lib/Program.cpp @@ -45,6 +45,12 @@ namespace roza return index < m_values.size(); } + void Program::set_param(size_t index, param_t param) + { + assert(index < m_instrs.size()); + m_instrs[index].param = param; + } + std::string Program::string() const { std::stringstream ss; diff --git a/lib/Program.hpp b/lib/Program.hpp index f662023..32ff91f 100644 --- a/lib/Program.hpp +++ b/lib/Program.hpp @@ -30,6 +30,8 @@ namespace roza std::shared_ptr value(size_t index) const; bool has_value(size_t index) const; + void set_param(size_t index, param_t param); + std::string string() const; private: diff --git a/lib/StaticPass.cpp b/lib/StaticPass.cpp index e55c079..2845531 100644 --- a/lib/StaticPass.cpp +++ b/lib/StaticPass.cpp @@ -26,6 +26,20 @@ namespace roza switch (root->type()) { + case NODE_IF: { + m_sym.enter_scope(); + auto cond_type = resolver.find(root->child(0), m_sym); + check_types(root, std::make_shared(TY_BOOL), cond_type); + check_children(root); + m_sym.leave_scope(); + } break; + + case NODE_THEN: + case NODE_ELSE: { + check_children(root); + } break; + + case NODE_CONSTDECL: { check(root->child(1)); m_sym.declare(root->child(0)->repr(), root->child(1)); diff --git a/lib/SymTable.cpp b/lib/SymTable.cpp index b246e96..15e5530 100644 --- a/lib/SymTable.cpp +++ b/lib/SymTable.cpp @@ -11,59 +11,130 @@ namespace roza /*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; - } + m_entries = sym_table.m_entries; } /*virtual*/ SymTable::~SymTable() { } + void SymTable::enter_scope() + { + m_scope++; + } + + void SymTable::leave_scope() + { + auto iter = std::begin(m_entries); + + while (iter != std::end(m_entries)) + { + if (iter->scope >= m_scope) + { + iter = m_entries.erase(iter); + } + else + { + iter++; + } + } + + m_scope--; + } + int SymTable::declare(std::string const& name, std::shared_ptr node) { - assert(!exists(name)); + assert(!exists_in_scope(name)); - m_entries.insert({name, SymEntry { - SymTable::addr++, - m_scope, - node, - false - }}); + m_entries.push_back(SymEntry { + name, + 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)); + int addr = declare(name, node); + m_entries.back().is_mut = true; - m_entries.insert({name, SymEntry { - SymTable::addr++, - m_scope, - node, - true - }}); - - return SymTable::addr - 1; + return addr; } SymEntry& SymTable::find(std::string const& name) { assert(exists(name)); - return m_entries[name]; + + size_t idx = 0; + int scope = -1; + + size_t i = 0; + + for (auto const& entry: m_entries) + { + if (entry.name == name && entry.scope > scope) + { + idx = i; + scope = entry.scope; + } + + i++; + } + + assert(scope >= 0); + + return m_entries[idx]; } SymEntry const& SymTable::find(std::string const& name) const { assert(exists(name)); - return m_entries.at(name); + + size_t idx = 0; + int scope = -1; + + size_t i = 0; + + for (auto const& entry: m_entries) + { + if (entry.name == name && entry.scope > scope) + { + idx = i; + scope = entry.scope; + } + + i++; + } + + assert(scope >= 0); + + return m_entries[idx]; } bool SymTable::exists(std::string const& name) const { - return m_entries.find(name) != std::end(m_entries); + for (auto const& entry: m_entries) + { + if (entry.name == name) { return true; } + } + + return false; + } + + bool SymTable::exists_in_scope(std::string const& name) const + { + for (auto const& entry: m_entries) + { + if (entry.name == name && entry.scope >= m_scope) + { + return true; + } + } + + return false; } } diff --git a/lib/SymTable.hpp b/lib/SymTable.hpp index dbf1385..61b99de 100644 --- a/lib/SymTable.hpp +++ b/lib/SymTable.hpp @@ -7,6 +7,7 @@ namespace roza { struct SymEntry { + std::string name; int addr; int scope; std::shared_ptr node; @@ -21,6 +22,9 @@ namespace roza virtual ~SymTable(); + void enter_scope(); + void leave_scope(); + int declare(std::string const& name, std::shared_ptr node); int declare_mut(std::string const& name, std::shared_ptr node); @@ -28,10 +32,11 @@ namespace roza SymEntry const& find(std::string const& name) const; bool exists(std::string const& name) const; + bool exists_in_scope(std::string const& name) const; private: static int addr; - std::unordered_map m_entries; + std::vector m_entries; int m_scope = 0; }; } diff --git a/lib/VM.cpp b/lib/VM.cpp index fca21b3..bff580c 100644 --- a/lib/VM.cpp +++ b/lib/VM.cpp @@ -19,6 +19,24 @@ namespace roza { switch (program->opcode(m_pc)) { + + case OP_BRF: { + auto value = program->value(pop()); + + if (!value->as_bool()) + { + m_pc = *program->param(m_pc); + } + else + { + m_pc++; + } + } break; + + case OP_BR: { + m_pc = *program->param(m_pc); + } break; + case OP_LOAD_GLOBAL: { int addr = *program->param(m_pc); auto value = m_globals[addr]; diff --git a/lib/commons.hpp b/lib/commons.hpp index 8ef7074..91d5a75 100644 --- a/lib/commons.hpp +++ b/lib/commons.hpp @@ -1,6 +1,7 @@ #ifndef roza_COMMONS_HPP #define roza_COMMONS_HPP +#include #include #include #include diff --git a/lib/opcodes.hpp b/lib/opcodes.hpp index 0c2ceef..ae97cc1 100644 --- a/lib/opcodes.hpp +++ b/lib/opcodes.hpp @@ -9,7 +9,7 @@ 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_STORE_GLOBAL), \ - G(OP_LOAD_GLOBAL) + G(OP_LOAD_GLOBAL), G(OP_BRF), G(OP_BR) namespace roza { diff --git a/roza_tests/test_if.roza b/roza_tests/test_if.roza new file mode 100644 index 0000000..fdd2585 --- /dev/null +++ b/roza_tests/test_if.roza @@ -0,0 +1,92 @@ +let! a = 0 + +if true + a = 7 +end + +assert 7 == a + +if false + a = 9 +end + +assert 7 == a + +if true + a = 0 +else + a = 1 +end + +assert 0 == a + +if false + a = 0 +else + a = 1 +end + +assert 1 == a + +if true + a = 1 +else if true + a = 2 +else if true + a = 3 +else + a = 4 +end + +assert 1 == a + +if false + a = 1 +else if true + a = 2 +else if true + a = 3 +else + a = 4 +end + +assert 2 == a + +if false + a = 1 +else if false + a = 2 +else if true + a = 3 +else + a = 4 +end + +assert 3 == a + +if false + a = 1 +else if false + a = 2 +else if false + a = 3 +else + a = 4 +end + +assert 4 == a + +let! b = 54 +let! c = 33 + +if true + let! b = 18 + c = 7 + assert 18 == b +end + +assert 54 == b +assert 7 == c + + +assert_static_fail if 4 0; end diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index 684b72a..af81f4f 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -103,3 +103,12 @@ TEST_CASE_METHOD(LexerTest, "Lexer_declarations") REQUIRE("ASSIGN" == get_str(3)); REQUIRE("" == get_str(4)); } + +TEST_CASE_METHOD(LexerTest, "Lexer_if") +{ + m_lexer.scan(" if else end "); + REQUIRE("IF" == get_str(0)); + REQUIRE("ELSE" == get_str(1)); + REQUIRE("END" == get_str(2)); + REQUIRE("" == get_str(3)); +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp index 69fbab2..abb8d82 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -122,3 +122,18 @@ TEST_CASE_METHOD(ParserTest, "Parser_vardecl") test_node("PROG(CONSTDECL(IDENT[_coucou],MUL(INT[6],INT[7])))", "let _coucou = 6 * 7"); } + +TEST_CASE_METHOD(ParserTest, "Parser_if") +{ + test_node("PROG(IF(BOOL[true],THEN(ASSIGN(IDENT[x],INT[4]))))", + "if true x = 4; end"); + + test_node("PROG(IF(BOOL[true],THEN(INT[1]),ELSE(INT[4])))", + "if true 1; else 4; end"); + + test_node("PROG(IF(BOOL[true],THEN(INT[0])," + "IF(BOOL[false],THEN(INT[1])," + "IF(BOOL[true],THEN(INT[2])," + "ELSE(INT[3])))))", + "if true 0; else if false 1; else if true 2; else 3; end"); +}