diff --git a/doc/grammar.bnf b/doc/grammar.bnf index 1981507..89f7b96 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -1,11 +1,15 @@ PROG ::= (INSTR (EOI INSTR)*)? INSTR ::= EXPR -EXPR ::= TERM +EXPR ::= IMP + +IMP ::= OR (imp OR)? +OR ::= AND (or AND)* +AND ::= TERM (and TERM)* TERM ::= FACTOR ((add | sub) FACTOR)* FACTOR ::= UNOP ((mul | div | mod) UNOP)* -UNOP ::= (add | sub)? POW +UNOP ::= (add | sub | not)? POW POW ::= GROUP (pow GROUP)? GROUP ::= BASE | opar EXPR cpar -BASE ::= int +BASE ::= int | bool diff --git a/lib/Compiler.cpp b/lib/Compiler.cpp index a882290..fb638d5 100644 --- a/lib/Compiler.cpp +++ b/lib/Compiler.cpp @@ -28,6 +28,31 @@ namespace roza prog->push_instr(OP_PUSH_CONST, prog->push_value(value)); } break; + case NODE_BOOL: { + auto value = std::make_shared(root->repr() == "true", root->loc()); + prog->push_instr(OP_PUSH_CONST, prog->push_value(value)); + } break; + + case NODE_IMP: { + compile_children(root, prog); + prog->push_instr(OP_IMP); + } break; + + case NODE_AND: { + compile_children(root, prog); + prog->push_instr(OP_AND); + } break; + + case NODE_OR: { + compile_children(root, prog); + prog->push_instr(OP_OR); + } break; + + case NODE_NOT: { + compile_children(root, prog); + prog->push_instr(OP_NOT); + } break; + case NODE_ADD: { compile_children(root, prog); prog->push_instr(OP_IADD); diff --git a/lib/Lexer.cpp b/lib/Lexer.cpp index cb2725d..4f47fcc 100644 --- a/lib/Lexer.cpp +++ b/lib/Lexer.cpp @@ -7,15 +7,44 @@ namespace roza : m_log { log } , m_loc { loc } { + std::vector> texts = { + {"=>", NODE_IMP, false}, + {"+", NODE_ADD, false}, + {"-", NODE_SUB, false}, + {"*", NODE_MUL, false}, + {"/", NODE_DIV, false}, + {"%", NODE_MOD, false}, + {"^", NODE_POW, false}, + {"(", NODE_OPAR, false}, + {")", NODE_CPAR, false}, + }; + + std::vector> keywords = { + {"true", NODE_BOOL, true}, + {"false", NODE_BOOL, true}, + {"and", NODE_AND, false}, + {"or", NODE_OR, false}, + {"not", NODE_NOT, false}, + }; + + m_scanners.push_back(std::bind(&Lexer::scan_int, this)); - m_scanners.push_back(std::bind(&Lexer::scan_text, this, "+", NODE_ADD, false)); - m_scanners.push_back(std::bind(&Lexer::scan_text, this, "-", NODE_SUB, false)); - m_scanners.push_back(std::bind(&Lexer::scan_text, this, "*", NODE_MUL, false)); - m_scanners.push_back(std::bind(&Lexer::scan_text, this, "/", NODE_DIV, false)); - m_scanners.push_back(std::bind(&Lexer::scan_text, this, "%", NODE_MOD, false)); - m_scanners.push_back(std::bind(&Lexer::scan_text, this, "^", NODE_POW, false)); - m_scanners.push_back(std::bind(&Lexer::scan_text, this, "(", NODE_OPAR, false)); - m_scanners.push_back(std::bind(&Lexer::scan_text, this, ")", NODE_CPAR, false)); + + for (auto const& entry: keywords) + { + m_scanners.push_back(std::bind(&Lexer::scan_keyword, this, + std::get<0>(entry), + std::get<1>(entry), + std::get<2>(entry))); + } + + for (auto const& entry: texts) + { + m_scanners.push_back(std::bind(&Lexer::scan_text, this, + std::get<0>(entry), + std::get<1>(entry), + std::get<2>(entry))); + } } @@ -118,6 +147,18 @@ namespace roza return m_nodes.at(index); } + bool Lexer::is_sep(size_t index) const + { + if (index >= m_source.size()) + { + return true; + } + + char c = m_source[index]; + + return !std::isalnum(c); + } + ScanInfo Lexer::scan_int() const { size_t cursor = m_cursor; @@ -160,4 +201,20 @@ namespace roza m_cursor + text.size() }; } + + ScanInfo Lexer::scan_keyword(std::string const& keyword, + NodeType type, + bool value) const + { + auto info = scan_text(keyword, type, value); + + if (is_sep(info.cursor)) + { + return info; + } + + return ScanInfo { + }; + } + } diff --git a/lib/Lexer.hpp b/lib/Lexer.hpp index f60c278..9ecc8fd 100644 --- a/lib/Lexer.hpp +++ b/lib/Lexer.hpp @@ -38,10 +38,16 @@ namespace roza std::vector> m_nodes; std::vector m_scanners; + bool is_sep(size_t index) const; + ScanInfo scan_int() const; ScanInfo scan_text(std::string const& text, NodeType type, bool value=false) const; + + ScanInfo scan_keyword(std::string const& keyword, + NodeType type, + bool value=false) const; }; } diff --git a/lib/Node.hpp b/lib/Node.hpp index 8cd6acd..b7f6b49 100644 --- a/lib/Node.hpp +++ b/lib/Node.hpp @@ -8,7 +8,8 @@ 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_CPAR), G(NODE_UADD), G(NODE_USUB), G(NODE_BOOL), \ + G(NODE_AND), G(NODE_OR), G(NODE_NOT), G(NODE_IMP) namespace roza { diff --git a/lib/Parser.cpp b/lib/Parser.cpp index d77ed7c..21ab5cd 100644 --- a/lib/Parser.cpp +++ b/lib/Parser.cpp @@ -126,7 +126,52 @@ namespace roza std::shared_ptr Parser::parse_expr() { - return parse_term(); + return parse_imp(); + } + + std::shared_ptr Parser::parse_imp() + { + auto lhs = parse_or(); + + if (type_is(NODE_IMP)) + { + auto root = consume(); + root->add_child(lhs); + root->add_child(parse_or()); + lhs = root; + } + + return lhs; + } + + std::shared_ptr Parser::parse_or() + { + auto lhs = parse_and(); + + while (type_is(NODE_OR)) + { + auto root = consume(); + root->add_child(lhs); + root->add_child(parse_and()); + lhs = root; + } + + return lhs; + } + + std::shared_ptr Parser::parse_and() + { + auto lhs = parse_term(); + + while (type_is(NODE_AND)) + { + auto root = consume(); + root->add_child(lhs); + root->add_child(parse_term()); + lhs = root; + } + + return lhs; } std::shared_ptr Parser::parse_term() @@ -181,6 +226,15 @@ namespace roza return root; } + if (type_is(NODE_NOT)) + { + auto root = std::make_shared(NODE_NOT, "", m_lexer.loc()); + next(); + + root->add_child(parse_pow()); + return root; + } + return parse_pow(); } @@ -214,7 +268,18 @@ namespace roza std::shared_ptr Parser::parse_base() { - return parse_int(); + if (type_is(NODE_INT) + || type_is(NODE_BOOL)) + { + return consume(); + } + + m_log.fatal(node()->loc(), + "unknown node '" + + node()->string() + + "'"); + + return nullptr; } std::shared_ptr Parser::parse_int() diff --git a/lib/Parser.hpp b/lib/Parser.hpp index e7d50c0..95f396d 100644 --- a/lib/Parser.hpp +++ b/lib/Parser.hpp @@ -32,6 +32,9 @@ namespace roza std::shared_ptr parse_prog(); std::shared_ptr parse_instr(); std::shared_ptr parse_expr(); + std::shared_ptr parse_imp(); + std::shared_ptr parse_or(); + std::shared_ptr parse_and(); std::shared_ptr parse_term(); std::shared_ptr parse_factor(); std::shared_ptr parse_unop(); diff --git a/lib/StaticPass.cpp b/lib/StaticPass.cpp index 6121632..1123e36 100644 --- a/lib/StaticPass.cpp +++ b/lib/StaticPass.cpp @@ -19,7 +19,25 @@ namespace roza switch (root->type()) { - case NODE_INT: break; + case NODE_INT: + case NODE_BOOL: + break; + + case NODE_IMP: + case NODE_OR: + case NODE_AND: { + check_children(root); + auto lhs = resolver.find(root->child(0)); + auto rhs = resolver.find(root->child(1)); + check_types(root, lhs, std::make_shared(TY_BOOL)); + check_types(root, lhs, rhs); + } break; + + case NODE_NOT: { + check_children(root); + auto lhs = resolver.find(root->child(0)); + check_types(root, lhs, std::make_shared(TY_BOOL)); + } break; case NODE_ADD: case NODE_SUB: @@ -27,35 +45,74 @@ namespace roza case NODE_DIV: case NODE_MOD: case NODE_POW: { + check_children(root); auto lhs = resolver.find(root->child(0)); auto rhs = resolver.find(root->child(1)); - - if (!lhs->equals(*rhs)) - { - m_log.fatal(root->loc(), - std::string() - + "type mismatch, expected '" - + lhs->string() - + "', got '" - + rhs->string() - + "'"); - } - + check_types(root, lhs, std::make_shared(TY_INT)); + check_types(root, lhs, rhs); } break; case NODE_UADD: case NODE_USUB: { + check_children(root); } break; case NODE_PROG: { - for (size_t i=0; isize(); i++) - { - check(root->child(i)); - } + check_children(root); } break; default: m_log.fatal(root->loc(), "cannot check node '" + root->string() + "'"); } } + + void StaticPass::check_children(std::shared_ptr root) + { + for (size_t i=0; isize(); i++) + { + check(root->child(i)); + } + } + + void StaticPass::check_types(std::shared_ptr root, + std::shared_ptr lhs, + std::shared_ptr rhs) + { + if (!lhs->equals(*rhs)) + { + m_log.fatal(root->loc(), + std::string() + + "type mismatch, expected '" + + lhs->string() + + "', got '" + + rhs->string() + + "'"); + } + } + + void StaticPass::check_types(std::shared_ptr root, + std::shared_ptr lhs, + std::vector> const& rhs) + { + for (auto const& ty: rhs) + { + if (lhs->equals(*ty)) + { + return; + } + } + + std::stringstream ss; + + ss << "type mismatch, got '" << lhs->string() << "'"; + ss << "candidates are:" << std::endl; + + for (auto ty: rhs) + { + ss << "\t-> " << ty->string() << std::endl; + } + + m_log.fatal(root->loc(), ss.str()); + } + } diff --git a/lib/StaticPass.hpp b/lib/StaticPass.hpp index e62b0bf..5697b6c 100644 --- a/lib/StaticPass.hpp +++ b/lib/StaticPass.hpp @@ -4,6 +4,7 @@ #include "commons.hpp" #include "StatusLog.hpp" #include "Node.hpp" +#include "Type.hpp" namespace roza { @@ -14,9 +15,18 @@ namespace roza virtual ~StaticPass(); void check(std::shared_ptr root); + void check_children(std::shared_ptr root); private: StatusLog& m_log; + + void check_types(std::shared_ptr root, + std::shared_ptr lhs, + std::shared_ptr rhs); + + void check_types(std::shared_ptr root, + std::shared_ptr lhs, + std::vector> const& rhs); }; } diff --git a/lib/Type.hpp b/lib/Type.hpp index 28280da..efbbb6d 100644 --- a/lib/Type.hpp +++ b/lib/Type.hpp @@ -4,7 +4,7 @@ #include "commons.hpp" #define BASE_TYPE(G) \ - G(TY_INT) + G(TY_INT), G(TY_BOOL) namespace roza { diff --git a/lib/TypeResolver.cpp b/lib/TypeResolver.cpp index 8a0c213..bff5878 100644 --- a/lib/TypeResolver.cpp +++ b/lib/TypeResolver.cpp @@ -23,6 +23,14 @@ namespace roza return std::make_shared(BaseType::TY_INT); } break; + case NODE_IMP: + case NODE_AND: + case NODE_OR: + case NODE_NOT: + case NODE_BOOL: { + return std::make_shared(BaseType::TY_BOOL); + } break; + case NODE_ADD: case NODE_SUB: case NODE_MUL: diff --git a/lib/VM.cpp b/lib/VM.cpp index cefcf82..b7484d7 100644 --- a/lib/VM.cpp +++ b/lib/VM.cpp @@ -25,6 +25,37 @@ namespace roza m_pc++; } break; + case OP_IMP: { + apply_binop(program, [](auto lhs, auto rhs){ + bool a = lhs->as_bool(); + bool b = rhs->as_bool(); + + return std::make_shared(!a || b, + lhs->loc()); + }); + } break; + + case OP_AND: { + apply_binop(program, [](auto lhs, auto rhs){ + return std::make_shared(lhs->as_bool() && rhs->as_bool(), + lhs->loc()); + }); + } break; + + case OP_OR: { + apply_binop(program, [](auto lhs, auto rhs){ + return std::make_shared(lhs->as_bool() || rhs->as_bool(), + lhs->loc()); + }); + } break; + + case OP_NOT: { + apply_unop(program, [](auto lhs){ + return std::make_shared(!lhs->as_bool(), + lhs->loc()); + }); + } break; + case OP_IADD: { apply_binop(program, [](auto lhs, auto rhs){ return std::make_shared(lhs->as_int() + rhs->as_int(), @@ -62,7 +93,8 @@ namespace roza case OP_IPOW: { apply_binop(program, [](auto lhs, auto rhs){ - return std::make_shared(std::pow(lhs->as_int(), rhs->as_int()), + return std::make_shared(static_cast(std::pow(lhs->as_int(), + rhs->as_int())), lhs->loc()); }); } break; diff --git a/lib/Value.cpp b/lib/Value.cpp index 51d5648..6661bdf 100644 --- a/lib/Value.cpp +++ b/lib/Value.cpp @@ -9,6 +9,13 @@ namespace roza { } + /*explicit*/ Value::Value(bool value, SrcLoc loc) + : m_type { std::make_shared(BaseType::TY_BOOL) } + , m_bool_val { value } + , m_loc { loc } + { + } + /*virtual*/ Value::~Value() { } @@ -20,6 +27,11 @@ namespace roza return std::to_string(m_int_val); } + if (m_type->equals(TY_BOOL)) + { + return m_bool_val ? "true" : "false"; + } + assert("cannot stringify unknown value " && 0); } } diff --git a/lib/Value.hpp b/lib/Value.hpp index 16521da..2e15f1a 100644 --- a/lib/Value.hpp +++ b/lib/Value.hpp @@ -11,16 +11,21 @@ namespace roza { public: explicit Value(int value, SrcLoc loc); + explicit Value(bool value, SrcLoc loc); virtual ~Value(); SrcLoc loc() const { return m_loc; } + int as_int() const { return m_int_val; } + bool as_bool() const { return m_bool_val; } + std::shared_ptr type() const { return m_type; } std::string string() const; private: std::shared_ptr m_type; int m_int_val; + bool m_bool_val; SrcLoc m_loc; }; } diff --git a/lib/opcodes.hpp b/lib/opcodes.hpp index 5664fd0..dd0348a 100644 --- a/lib/opcodes.hpp +++ b/lib/opcodes.hpp @@ -7,7 +7,8 @@ G(OP_PUSH_CONST), \ 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_MOD), G(OP_IUADD), G(OP_IUSUB), G(OP_AND), G(OP_OR), G(OP_NOT), \ + G(OP_IMP) namespace roza { diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index 2c0cb6c..3c59794 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -55,3 +55,25 @@ TEST_CASE_METHOD(LexerTest, "Lexer_int_arith") REQUIRE("CPAR" == get_str(7)); REQUIRE("" == get_str(8)); } + +TEST_CASE_METHOD(LexerTest, "Lexer_keywords") +{ + REQUIRE_THROWS(m_lexer.scan(" andor ")); + REQUIRE_NOTHROW(m_lexer.scan(" and+ ")); + REQUIRE_NOTHROW(m_lexer.scan(" (and) ")); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_bool") +{ + SECTION("nominal cases") + { + m_lexer.scan("and or not true false =>"); + REQUIRE("AND" == get_str(0)); + REQUIRE("OR" == get_str(1)); + REQUIRE("NOT" == get_str(2)); + REQUIRE("BOOL[true]" == get_str(3)); + REQUIRE("BOOL[false]" == get_str(4)); + REQUIRE("IMP" == get_str(5)); + REQUIRE("" == get_str(6)); + } +} diff --git a/tests/Parser.cpp b/tests/Parser.cpp index 938564d..d794ec8 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -70,3 +70,24 @@ TEST_CASE_METHOD(ParserTest, "Parser_int_arith") test_node("PROG(USUB(POW(INT[2],INT[7])))", " -2^7 "); } + +TEST_CASE_METHOD(ParserTest, "Parser_bool") +{ + test_node("PROG(BOOL[true])", + "true"); + + test_node("PROG(AND(BOOL[true],BOOL[false]))", + "true and false"); + + test_node("PROG(OR(BOOL[true],BOOL[false]))", + "true or false"); + + test_node("PROG(NOT(BOOL[false]))", + "not false"); + + test_node("PROG(NOT(OR(BOOL[false],BOOL[true])))", + "not (false or true)"); + + test_node("PROG(IMP(BOOL[true],BOOL[false]))", + "true => false"); +} diff --git a/tests/StaticPass.cpp b/tests/StaticPass.cpp index 52e7a2a..d713a1b 100644 --- a/tests/StaticPass.cpp +++ b/tests/StaticPass.cpp @@ -33,4 +33,5 @@ protected: TEST_CASE_METHOD(StaticPassTest, "StaticPass_integer") { test_ok(" 43 "); + test_ko(" 43 + 7 * true"); }