diff --git a/doc/roza.bnf b/doc/roza.bnf index ac9fcd6..f918b08 100644 --- a/doc/roza.bnf +++ b/doc/roza.bnf @@ -1,6 +1,10 @@ MOD ::= EXPR* EXPR ::= | ASSERT -| BUILTIN +| EQNE ASSERT ::= assert EXPR +EQNE ::= +| BUILTIN +| BUILTIN eq BUILTIN +| BUILTIN ne BUILTIN BUILTIN ::= num | bool | str diff --git a/lib/compiler.c b/lib/compiler.c index bbaf08e..a8b0455 100644 --- a/lib/compiler.c +++ b/lib/compiler.c @@ -64,6 +64,19 @@ void compiler_run(compiler_t* compiler, node_t* node) mod_push_instr(compiler->mod, OP_ASSERT, RZ_NO_PARAM); } break; + case NODE_NE: + case NODE_EQ: { + assert(node->children.size == 2); + + for (size_t i=0; ichildren.size; i++) + { + compiler_run(compiler, node_child(node, i)); + } + + mod_push_instr(compiler->mod, + node->type == NODE_EQ ? OP_EQ : OP_NE, RZ_NO_PARAM); + } break; + default: { fprintf(stderr, "Cannot compile unknown node '%s'", NodeTypeStr[node->type]); diff --git a/lib/lexer.c b/lib/lexer.c index 7bb5543..d0bd370 100644 --- a/lib/lexer.c +++ b/lib/lexer.c @@ -1,10 +1,16 @@ #include "lexer.h" #include "lib/commons.h" -#define RZ_KEYWORD(KW, NODE, VAL) \ - { \ - node_t* kw = lexer_try_new_keyword(lexer, KW, NODE, VAL); \ - if (kw) { lexer_skip_spaces(lexer); return kw; } \ +#define RZ_KEYWORD(KW, NODE, VAL) \ + { \ + node_t* kw = lexer_try_new_keyword(lexer, KW, NODE, VAL, 1); \ + if (kw) { lexer_skip_spaces(lexer); return kw; } \ + } + +#define RZ_TEXT(KW, NODE, VAL) \ + { \ + node_t* kw = lexer_try_new_keyword(lexer, KW, NODE, VAL, 0); \ + if (kw) { lexer_skip_spaces(lexer); return kw; } \ } void lexer_init(lexer_t* lexer, char const* source, err_t* err) @@ -30,6 +36,11 @@ node_t* lexer_try_new_next(lexer_t* lexer) lexer_skip_spaces(lexer); + // Text + // ==== + RZ_TEXT("==", NODE_EQ, 0); + RZ_TEXT("!=", NODE_NE, 0); + // Keywords // ======== RZ_KEYWORD("true", NODE_BOOL, 1); @@ -156,7 +167,8 @@ void lexer_skip_spaces(lexer_t* lexer) } node_t* lexer_try_new_keyword(lexer_t* lexer, char* kw, - NodeType type, int has_value) + NodeType type, int has_value, + int is_kw) { assert(lexer); assert(kw); @@ -182,7 +194,7 @@ node_t* lexer_try_new_keyword(lexer_t* lexer, char* kw, int next_idx = lexer->cursor + len; if (next_idx < strlen(lexer->source) - && !isspace(lexer->source[next_idx])) + && (is_kw && !lexer_is_sep(lexer, next_idx))) { return NULL; } @@ -272,3 +284,23 @@ node_t* lexer_try_new_str(lexer_t* lexer) return tok; } + +int lexer_is_sep(lexer_t* lexer, size_t idx) +{ + assert(lexer); + + if (idx >= strlen(lexer->source)) + { + return 1; + } + + char c = lexer->source[idx]; + + if (isspace(c)) + { + return 1; + } + + return c == '=' + || c == '!'; +} diff --git a/lib/lexer.h b/lib/lexer.h index 1ec7f29..4034b1b 100644 --- a/lib/lexer.h +++ b/lib/lexer.h @@ -20,8 +20,11 @@ NodeType lexer_peek(lexer_t* lexer, int lookahead); void lexer_skip_spaces(lexer_t* lexer); node_t* lexer_try_new_keyword(lexer_t* lexer, char* kw, - NodeType type, int has_value); + NodeType type, int has_value, + int is_kw); node_t* lexer_try_new_str(lexer_t* lexer); +int lexer_is_sep(lexer_t* lexer, size_t idx); + #endif diff --git a/lib/loader.c b/lib/loader.c index dd9268c..120edaf 100644 --- a/lib/loader.c +++ b/lib/loader.c @@ -1,4 +1,5 @@ #include "lexer.h" +#include "lib/commons.h" #include "parser.h" #include "loader.h" #include "compiler.h" @@ -63,6 +64,15 @@ int loader_ldfile(loader_t* loader, char const* path, int debug) node_t* node = parser_try_new_tree(&parser); + if (debug) + { + char msg[RZ_STR_LIMIT]; + node_str(node, msg, RZ_STR_LIMIT); + + printf("\n======== AST ========\n"); + printf("%s\n", msg); + } + if (node) { mod_t mod; @@ -102,7 +112,7 @@ int loader_ldfile(loader_t* loader, char const* path, int debug) // execute { vm_t vm; - vm_init(&vm, &err); + vm_init(&vm, &tysy, &err); int status = vm_exec_mod(&vm, &mod); diff --git a/lib/node.h b/lib/node.h index 9ed6c9e..0f0c81a 100644 --- a/lib/node.h +++ b/lib/node.h @@ -6,7 +6,8 @@ #define NODE_TYPE(G) \ G(NODE_MOD), \ G(NODE_NUM), G(NODE_BOOL), G(NODE_STR), \ - G(NODE_ASSERT) + G(NODE_ASSERT), \ + G(NODE_EQ), G(NODE_NE) RZ_ENUM_H(NodeType, NODE_TYPE); diff --git a/lib/opcodes.h b/lib/opcodes.h index cea0b73..88d93ec 100644 --- a/lib/opcodes.h +++ b/lib/opcodes.h @@ -5,7 +5,8 @@ #define OPCODES(G) \ G(OP_ASSERT), \ - G(OP_PUSH), G(OP_POP) + G(OP_PUSH), G(OP_POP), \ + G(OP_EQ), G(OP_NE) RZ_ENUM_H(Opcode, OPCODES); diff --git a/lib/parser.c b/lib/parser.c index 77a3541..684de5a 100644 --- a/lib/parser.c +++ b/lib/parser.c @@ -61,7 +61,7 @@ node_t* parser_try_new_expr(parser_t* parser) return parser_try_new_assert(parser); } - return parser_try_new_builtin(parser); + return parser_try_new_eqne(parser); } node_t* parser_try_new_assert(parser_t* parser) @@ -77,6 +77,33 @@ node_t* parser_try_new_assert(parser_t* parser) return node; } +node_t* parser_try_new_eqne(parser_t* parser) +{ + assert(parser); + node_t* lhs = parser_try_new_builtin(parser); + + while (1) + { + NodeType next_type = lexer_peek(parser->lexer, 1); + + if (next_type == NODE_EQ || next_type == NODE_NE) + { + node_t* node = lexer_try_new_next(parser->lexer); + + node_add_new_child(node, lhs); + node_add_new_child(node, parser_try_new_builtin(parser)); + + lhs = node; + } + else + { + break; + } + } + + return lhs; +} + node_t* parser_try_new_builtin(parser_t* parser) { assert(parser); diff --git a/lib/parser.h b/lib/parser.h index f300764..52ad5ba 100644 --- a/lib/parser.h +++ b/lib/parser.h @@ -17,6 +17,7 @@ node_t* parser_try_new_tree(parser_t* parser); node_t* parser_try_new_mod(parser_t* parser); node_t* parser_try_new_expr(parser_t* parser); node_t* parser_try_new_assert(parser_t* parser); +node_t* parser_try_new_eqne(parser_t* parser); node_t* parser_try_new_builtin(parser_t* parser); node_t* parser_try_new_consume(parser_t* parser, NodeType type); diff --git a/lib/prepass.c b/lib/prepass.c index 1159a4b..2ee845b 100644 --- a/lib/prepass.c +++ b/lib/prepass.c @@ -24,7 +24,9 @@ void prepass_run(prepass_t* prepass, node_t* node) case NODE_NUM: case NODE_STR: case NODE_BOOL: - case NODE_ASSERT:{ + case NODE_ASSERT: + case NODE_EQ: + case NODE_NE: { for (size_t i=0; ichildren.size; i++) { prepass_run(prepass, (node_t*) node->children.data[i]); diff --git a/lib/vm.c b/lib/vm.c index 15b4875..56b991b 100644 --- a/lib/vm.c +++ b/lib/vm.c @@ -1,8 +1,9 @@ #include "vm.h" #include "lib/commons.h" +#include "lib/mod.h" #include "lib/type.h" -void vm_init(vm_t* vm, err_t* err) +void vm_init(vm_t* vm, tysy_t* tysy, err_t* err) { assert(vm); @@ -10,6 +11,7 @@ void vm_init(vm_t* vm, err_t* err) vm->bp = 0; vm->sp = 0; + vm->tysy = tysy; vm->err = err; } @@ -18,6 +20,24 @@ void vm_free(vm_t* vm) assert(vm); } +void vm_push_value(vm_t* vm, mod_t* mod, value_t* value) +{ + assert(vm); + assert(value); + + param_t param = mod_push_new_value(mod, value); + vm_push(vm, param); +} + +void vm_push(vm_t* vm, param_t param) +{ + assert(vm); + assert(vm->sp + 1 < RZ_STACK_LIMIT); + + vm->stack[vm->sp] = param; + vm->sp++; +} + param_t vm_pop(vm_t* vm) { assert(vm); @@ -111,6 +131,54 @@ int vm_exec_instr(vm_t* vm, mod_t* mod, Opcode op, param_t param) } break; + case OP_NE: + case OP_EQ: { + int l = vm_pop(vm); + int r = vm_pop(vm); + assert(l != -1); + assert(r != -1); + + value_t* rhs = mod->values.data[l]; + value_t* lhs = mod->values.data[r]; + + int same_type = lhs->type->kind == rhs->type->kind; + int type = lhs->type->kind; + + int eq = 0; + + if (same_type) + { + if (type == TYPE_NUM) + { + eq = lhs->value.num == rhs->value.num; + } + else if (type == TYPE_BOOL) + { + eq = lhs->value.bool == rhs->value.bool; + } + else if (type == TYPE_STR) + { + eq = strcmp(lhs->value.str, rhs->value.str) == 0; + } + else + { + fprintf(stderr, "Cannot test equality on unknown type '%s'.\n", + TypeKindStr[type]); + abort(); + } + } + + value_t* res = tysy_new_bool(vm->tysy, + (op == OP_EQ + ? (same_type && eq) + : (!same_type || !eq)), + rhs->line); + + vm_push_value(vm, mod, res); + + vm->pc++; + } break; + default: { fprintf(stderr, "Cannot execute unknown opcode '%s'.\n", OpcodeStr[op]); diff --git a/lib/vm.h b/lib/vm.h index 9660207..af410ff 100644 --- a/lib/vm.h +++ b/lib/vm.h @@ -5,8 +5,10 @@ #include "opcodes.h" #include "mod.h" #include "err.h" +#include "tysy.h" typedef struct { + tysy_t* tysy; param_t stack[RZ_STACK_LIMIT]; size_t pc; size_t bp; @@ -15,9 +17,11 @@ typedef struct { err_t* err; } vm_t; -void vm_init(vm_t* vm, err_t* err); +void vm_init(vm_t* vm, tysy_t* tysy, err_t* err); void vm_free(vm_t* vm); +void vm_push_value(vm_t* vm, mod_t* mod, value_t* value); +void vm_push(vm_t* vm, param_t param); param_t vm_pop(vm_t* vm); size_t vm_stack_str(vm_t* vm, char* buffer, size_t size); diff --git a/tests/acceptances/cmp.roza b/tests/acceptances/cmp.roza new file mode 100644 index 0000000..edb7507 --- /dev/null +++ b/tests/acceptances/cmp.roza @@ -0,0 +1,13 @@ +assert 5 == 5 +assert 3 != 5 + +assert true == true +assert false == false +assert false != true + +assert "hello" == "hello" +assert "world" != "hello" + +assert 7 != "hello" +assert false != 23 +assert "bim" != true diff --git a/tests/units/lexer.c b/tests/units/lexer.c index 855540f..183f875 100644 --- a/tests/units/lexer.c +++ b/tests/units/lexer.c @@ -8,13 +8,13 @@ static void test_lexer_ok(char const* oracle, char const* source) lexer_init(&lex, source, NULL); node_t* tok = lexer_try_new_next(&lex); - cr_assert(tok != NULL); + cr_assert(tok != NULL, "error -> %s", source); size_t const SZ = 512; char str[SZ]; node_str(tok, str, SZ); - cr_assert_str_eq(str, oracle); + cr_assert_str_eq(str, oracle, "error -> %s", source); node_free(tok); lexer_free(&lex); @@ -30,7 +30,7 @@ static void test_lexer_ko(char const* source) node_t* tok = lexer_try_new_next(&lex); - cr_assert(tok == NULL); + cr_assert(tok == NULL, "error -> %s", source); cr_assert(err.size > 0); err_free(&err); @@ -48,7 +48,7 @@ static void test_lexer(char const* source, size_t n, ...) for (size_t i=0; i %s", source); size_t const SZ = 256; char tok_str[SZ]; @@ -109,3 +109,19 @@ Test(lexer, str) { Test(lexer, assert) { test_lexer("assert", 1, "ASSERT"); } + +Test(lexer, cmp) { + test_lexer("== !=", 2, + "EQ", + "NE"); + + test_lexer("false==true", 3, + "BOOL[false]", + "EQ", + "BOOL[true]"); + + test_lexer("false!=true", 3, + "BOOL[false]", + "NE", + "BOOL[true]"); +} diff --git a/tests/units/parser.c b/tests/units/parser.c index 3a34166..25c7f3b 100644 --- a/tests/units/parser.c +++ b/tests/units/parser.c @@ -15,7 +15,7 @@ static void test_parser_ok(char const* oracle, char const* source) node_t* node = parser_try_new_tree(&parser); - cr_assert(NULL != node); + cr_assert(NULL != node, "error -> %s", source); size_t const SZ = 256; char msg[SZ]; @@ -71,3 +71,8 @@ Test(parser, assert) { test_parser_ok("MOD(ASSERT(BOOL[false]))", " assert false "); test_parser_ok("MOD(ASSERT(BOOL[true]))", " assert true "); } + +Test(parser, eqne) { + test_parser_ok("MOD(EQ(NUM[3],NUM[3]))", " 3 == 3 "); + test_parser_ok("MOD(NE(NUM[3],NUM[3]))", " 3 != 3 "); +}