diff --git a/doc/grammar.bnf b/doc/grammar.bnf index 699445f..8754905 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -1,5 +1,9 @@ -PROG ::= (INSTR (EOI INSTR)*)? +PROG ::= (INSTR (EOI+ INSTR)*)? + INSTR ::= EXPR +| assert EXPR +| assert_static_fail EXPR + EXPR ::= IMP IMP ::= OR (imp OR)? diff --git a/lib/Compiler.cpp b/lib/Compiler.cpp index 43a8f5f..02f8fc0 100644 --- a/lib/Compiler.cpp +++ b/lib/Compiler.cpp @@ -1,5 +1,7 @@ #include "Compiler.hpp" +#include "lib/Node.hpp" #include "lib/opcodes.hpp" +#include "StaticPass.hpp" namespace roza { @@ -23,6 +25,33 @@ namespace roza { switch (root->type()) { + case NODE_ASSERT: { + compile_children(root, prog); + prog->push_instr(OP_ASSERT); + } break; + + case NODE_ASSERT_STATIC_FAIL: { + bool failed = false; + + try + { + StaticPass static_pass {m_log}; + static_pass.check_children(root); + compile_children(root, prog); + + failed = true; + } + catch(...) + { + } + + if (failed) + { + m_log.fatal(root->loc(), "static assertion failed"); + } + + } 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/Lexer.cpp b/lib/Lexer.cpp index b21ebbf..b0fbf73 100644 --- a/lib/Lexer.cpp +++ b/lib/Lexer.cpp @@ -31,6 +31,8 @@ namespace roza {"and", NODE_AND, false}, {"or", NODE_OR, false}, {"not", NODE_NOT, false}, + {"assert_static_fail", NODE_ASSERT_STATIC_FAIL, false}, + {"assert", NODE_ASSERT, false}, }; diff --git a/lib/Node.hpp b/lib/Node.hpp index 6026ba1..35657ab 100644 --- a/lib/Node.hpp +++ b/lib/Node.hpp @@ -9,9 +9,9 @@ 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_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_GE), G(NODE_ASSERT), G(NODE_ASSERT_STATIC_FAIL) namespace roza { diff --git a/lib/Parser.cpp b/lib/Parser.cpp index 43165e5..953e3f9 100644 --- a/lib/Parser.cpp +++ b/lib/Parser.cpp @@ -1,5 +1,6 @@ #include "Parser.hpp" #include "lib/Node.hpp" +#include namespace roza { @@ -67,6 +68,7 @@ namespace roza + "', got '" + NodeTypeStr[type()] + "'"); + return nullptr; } @@ -118,7 +120,21 @@ namespace roza std::shared_ptr Parser::parse_instr() { consume_all(NODE_EOI); - auto root = parse_expr(); + + std::shared_ptr root; + + if (type_is(NODE_ASSERT) + || type_is(NODE_ASSERT_STATIC_FAIL)) + { + root = consume(); + root->add_child(parse_expr()); + } + else + { + root = parse_expr(); + } + + consume_or_nullptr(NODE_EOI); consume_all(NODE_EOI); diff --git a/lib/StaticPass.cpp b/lib/StaticPass.cpp index a38c702..2e11121 100644 --- a/lib/StaticPass.cpp +++ b/lib/StaticPass.cpp @@ -19,6 +19,7 @@ namespace roza switch (root->type()) { + case NODE_ASSERT_STATIC_FAIL: case NODE_INT: case NODE_BOOL: break; @@ -41,6 +42,7 @@ namespace roza check_types(root, lhs, rhs); } break; + case NODE_ASSERT: case NODE_NOT: { check_children(root); auto lhs = resolver.find(root->child(0)); @@ -67,6 +69,8 @@ namespace roza case NODE_UADD: case NODE_USUB: { check_children(root); + auto lhs = resolver.find(root->child(0)); + check_types(root, lhs, std::make_shared(TY_INT)); } break; case NODE_PROG: { diff --git a/lib/VM.cpp b/lib/VM.cpp index a5de8a9..103df2b 100644 --- a/lib/VM.cpp +++ b/lib/VM.cpp @@ -19,6 +19,18 @@ namespace roza { switch (program->opcode(m_pc)) { + case OP_ASSERT: { + auto value = program->value(pop()); + + if (!value->as_bool()) + { + m_log.fatal(value->loc(), "assertion failed"); + } + + m_pc++; + + } break; + case OP_PUSH_CONST: { param_t param = *program->param(m_pc); push(param); diff --git a/lib/opcodes.hpp b/lib/opcodes.hpp index 6efdc66..055339c 100644 --- a/lib/opcodes.hpp +++ b/lib/opcodes.hpp @@ -8,7 +8,7 @@ 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_IMP), G(OP_EQ), G(OP_ILT), G(OP_IGT), G(OP_ASSERT) namespace roza { diff --git a/roza_tests/run.sh b/roza_tests/run.sh new file mode 100755 index 0000000..e3dca5b --- /dev/null +++ b/roza_tests/run.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +OK=0 +KO=0 +TOTAL=0 + +for file in $(find . -name "test_*.roza" | sort) +do + roza $file + RET="$?" + NAME=$(basename "$file" | cut -d '.' -f1) + + echo -en "\e[34m$NAME...\e[0m" + + if [ "$RET" == 0 ] + then + echo -e " \e[32mok\e[0m" + OK=$(($OK + 1)) + else + echo -e " \e[31mko\e[0m" + KO=$(($KO + 1)) + fi + + TOTAL=$(($TOTAL + 1)) +done + +if [ $OK -eq $TOTAL ] +then + echo -e "\e[32m=== all tests passed ($OK) ===\e[0m" + exit 0 +else + echo -e "\e[31m=== some tests failed ($KO) ===\e[0m" + exit -1 +fi diff --git a/roza_tests/test_bool.roza b/roza_tests/test_bool.roza new file mode 100644 index 0000000..36d5783 --- /dev/null +++ b/roza_tests/test_bool.roza @@ -0,0 +1,30 @@ +assert true == true +assert false == false + +assert true == (true and true) +assert false == (true and false) +assert false == (false and true) +assert false == (false and false) + +assert true == (true or true) +assert true == (true or false) +assert true == (false or true) +assert false == (false or false) + +assert false == not true +assert true == not false + +assert true == (true => true) +assert false == (true => false) +assert true == (false => true) +assert true == (false => false) + +assert (false or true) == (not (true and false)) + +assert_static_fail 1 and true +assert_static_fail false and 7 +assert_static_fail 1 or true +assert_static_fail false or 7 +assert_static_fail not 9 +assert_static_fail 3 => true +assert_static_fail true => 7 diff --git a/roza_tests/test_int.roza b/roza_tests/test_int.roza new file mode 100644 index 0000000..4d50a09 --- /dev/null +++ b/roza_tests/test_int.roza @@ -0,0 +1,54 @@ +assert 5 == 5 +assert 3 != 5 +assert -9 == -10 + 1 +assert -4 == 3 - 7 +assert 4 == 7 - 3 +assert 21 == 3 * 7 +assert 3 == 21 / 7 +assert 2 == 21 / 8 +assert 3 == 7 % 4 +assert 128 == 2 ^ 7 +assert -128 == -2 ^ 7 +assert 3 == +3 +assert 0 - 7 == -7 + +assert 7 == 1 + 2 * 3 +assert 9 == (1 + 2) * 3 + +assert true == 5 < 6 +assert false == 5 < 5 +assert false == 5 < 4 +assert true == 5 <= 6 +assert true == 5 <= 5 +assert false == 5 <= 4 +assert false == 5 > 6 +assert false == 5 > 5 +assert true == 5 > 4 +assert false == 5 >= 6 +assert true == 5 >= 5 +assert true == 5 >= 4 + +assert_static_fail 5 + true +assert_static_fail 5 - true +assert_static_fail 5 * true +assert_static_fail 5 / true +assert_static_fail 5 % true +assert_static_fail 5 ^ true +assert_static_fail -true +assert_static_fail +true + +assert_static_fail true + 9 +assert_static_fail true - (4 + 1) +assert_static_fail true * 7 +assert_static_fail true / 3 +assert_static_fail true % 15 +assert_static_fail true ^ 2 + +assert_static_fail true > 1 +assert_static_fail 2 > false +assert_static_fail true < 1 +assert_static_fail 2 < false +assert_static_fail true >= 1 +assert_static_fail 2 >= false +assert_static_fail true <= 1 +assert_static_fail 2 <= false diff --git a/src/main.cpp b/src/main.cpp index 9fa81de..89ba862 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -33,51 +33,61 @@ int main(int argc, char** argv) if (args.inputs().size() > 0) { - auto source = loader.load(args.inputs()[0]); - roza::SrcLoc loc {args.inputs()[0]}; - roza::StatusLog log; - - auto lexer = std::make_shared(log, loc); - auto parser = std::make_shared(*lexer, log); - auto static_pass = std::make_shared(log); - auto compiler = std::make_shared(log); - auto vm = std::make_shared(log); - - lexer->scan(source); - - if (args.get("--tokens")) + try { - std::cout << "Tokens:" << std::endl; - for (size_t i=0; isize(); i++) + auto source = loader.load(args.inputs()[0]); + roza::SrcLoc loc {args.inputs()[0]}; + roza::StatusLog log; + + auto lexer = std::make_shared(log, loc); + auto parser = std::make_shared(*lexer, log); + auto static_pass = std::make_shared(log); + auto compiler = std::make_shared(log); + auto vm = std::make_shared(log); + + lexer->scan(source); + + if (args.get("--tokens")) { - std::cout << "\t" << lexer->get_or_nullptr(i)->string() << std::endl; + std::cout << "Tokens:" << std::endl; + for (size_t i=0; isize(); i++) + { + std::cout << "\t" << lexer->get_or_nullptr(i)->string() << std::endl; + } } + + auto root = parser->parse(); + + if (args.get("--ast")) + { + std::cout << "AST:" << std::endl; + std::cout << root->string() << std::endl; + } + + static_pass->check(root); + auto prog = compiler->compile(root); + + if (args.get("--code")) + { + std::cout << "Bytecode:" << std::endl; + std::cout << prog->string() << std::endl; + } + + vm->exec(prog); + + if (args.get("--stack")) + { + std::cout << vm->string() << std::endl; + } + + return 0; } - - auto root = parser->parse(); - - if (args.get("--ast")) + catch(std::exception const& err) { - std::cout << "AST:" << std::endl; - std::cout << root->string() << std::endl; + std::cerr << err.what() << std::endl; + return -1; } - static_pass->check(root); - auto prog = compiler->compile(root); - - if (args.get("--code")) - { - std::cout << "Bytecode:" << std::endl; - std::cout << prog->string() << std::endl; - } - - vm->exec(prog); - - if (args.get("--stack")) - { - std::cout << vm->string() << std::endl; - } + return 0; } - - return 0; } diff --git a/tests/Lexer.cpp b/tests/Lexer.cpp index 33e2a8c..72047be 100644 --- a/tests/Lexer.cpp +++ b/tests/Lexer.cpp @@ -65,30 +65,32 @@ TEST_CASE_METHOD(LexerTest, "Lexer_keywords") 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)); - } + 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)); } TEST_CASE_METHOD(LexerTest, "Lexer_comparisons") { - SECTION("nominal cases") - { - m_lexer.scan("== != < > <= >="); - REQUIRE("EQ" == get_str(0)); - REQUIRE("NE" == get_str(1)); - REQUIRE("LT" == get_str(2)); - REQUIRE("GT" == get_str(3)); - REQUIRE("LE" == get_str(4)); - REQUIRE("GE" == get_str(5)); - REQUIRE("" == get_str(6)); - } + m_lexer.scan("== != < > <= >="); + REQUIRE("EQ" == get_str(0)); + REQUIRE("NE" == get_str(1)); + REQUIRE("LT" == get_str(2)); + REQUIRE("GT" == get_str(3)); + REQUIRE("LE" == get_str(4)); + REQUIRE("GE" == get_str(5)); + REQUIRE("" == get_str(6)); +} + +TEST_CASE_METHOD(LexerTest, "Lexer_asserts") +{ + m_lexer.scan(" assert assert_static_fail "); + REQUIRE("ASSERT" == get_str(0)); + REQUIRE("ASSERT_STATIC_FAIL" == get_str(1)); + REQUIRE("" == get_str(2)); } diff --git a/tests/Parser.cpp b/tests/Parser.cpp index 3e59547..03b2819 100644 --- a/tests/Parser.cpp +++ b/tests/Parser.cpp @@ -100,3 +100,13 @@ TEST_CASE_METHOD(ParserTest, "Parser_comparisons") test_node("PROG(EQ(LE(INT[5],INT[3]),BOOL[false]))", "5 <= 3 == false"); } + + +TEST_CASE_METHOD(ParserTest, "Parser_assertions") +{ + test_node("PROG(ASSERT(EQ(ADD(INT[1],INT[1]),INT[2])))", + "assert 1 + 1 == 2"); + + test_node("PROG(ASSERT_STATIC_FAIL(EQ(ADD(INT[1],INT[1]),INT[2])))", + "assert_static_fail 1 + 1 == 2"); +}