From e8b10498cf7b499097359fc03601e5a999b2724f Mon Sep 17 00:00:00 2001 From: bog Date: Mon, 1 Apr 2024 07:20:42 +0200 Subject: [PATCH] :sparkles: assert expression :white_check_mark: acceptance tests. --- cli/main.c | 3 ++- doc/grammar.bnf | 5 ++++- features/int.sk | 8 ++++++++ features/run.sh | 30 ++++++++++++++++++++++++++++ lib/include/lexer.h | 6 +++++- lib/include/module.h | 2 +- lib/include/node.h | 2 +- lib/include/parser.h | 1 + lib/include/prog.h | 2 +- lib/include/state.h | 2 +- lib/include/token.h | 3 ++- lib/include/value.h | 6 +++++- lib/src/compiler.c | 7 ++++++- lib/src/exec.c | 46 ++++++++++++++++++++++++++++++++++++++++++- lib/src/lexer.c | 47 ++++++++++++++++++++++++++++++++++++++++++++ lib/src/module.c | 16 ++++++++++++--- lib/src/parser.c | 42 +++++++++++++++++++++++++++++++++++++++ lib/src/state.c | 25 ++++++++++++++--------- lib/src/value.c | 22 ++++++++++++++++++++- tests/lexer.h | 15 ++++++++++++-- tests/parser.h | 13 +++++++++++- 21 files changed, 276 insertions(+), 27 deletions(-) create mode 100644 features/int.sk create mode 100755 features/run.sh diff --git a/cli/main.c b/cli/main.c index e5f9958..20762b5 100644 --- a/cli/main.c +++ b/cli/main.c @@ -11,10 +11,11 @@ int main(int argc, char** argv) module_init(&module); module_load_source(&module, argv[1]); - module_compile(&module); + int status = module_compile(&module); module_free(&module); errors_free(); + return status; } return 0; diff --git a/doc/grammar.bnf b/doc/grammar.bnf index a067bb9..f8abd01 100644 --- a/doc/grammar.bnf +++ b/doc/grammar.bnf @@ -1,5 +1,8 @@ ROOT ::= EXPR* -EXPR ::= TERM +EXPR ::= +| TERM +| ASSERT +ASSERT ::= assert EXPR eq EXPR TERM ::= FACTOR ((add|sub) FACTOR)* FACTOR ::= USUB ((mul|div|mod) USUB)* USUB ::= sub* POW diff --git a/features/int.sk b/features/int.sk new file mode 100644 index 0000000..7fa8bb9 --- /dev/null +++ b/features/int.sk @@ -0,0 +1,8 @@ +assert 32 eq 32 + +assert 1 + 1 eq 2 +assert 6 * 7 eq 42 +assert 1 + 2 * 3 eq 7 +assert (1 + 2) * 3 eq 9 +assert -2^3 eq -8 +assert 32 % 7 eq 4 diff --git a/features/run.sh b/features/run.sh new file mode 100755 index 0000000..7a0f04b --- /dev/null +++ b/features/run.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +TOTAL=0 +FAILED=0 + +for file in $(find . -name "*.sk") +do + OUTPUT=$(skopy $file 2>&1) + RET=$? + + echo -en "\e[35m$file\e[0m ... " + + if [ $RET -eq 0 ] + then + echo -e "\e[32mpassed\e[0m" + else + echo -e "\e[31mfailed\e[0m" + echo "$OUTPUT" + FAILED=$(($FAILED + 1)) + fi + + TOTAL=$(($TOTAL + 1)) +done + +if [ $FAILED -eq 0 ] +then + echo -e "\e[32m=== All tests passed ===\e[0m" +else + echo -e "\e[31m=== Some tests failed ===\e[0m" +fi diff --git a/lib/include/lexer.h b/lib/include/lexer.h index 1c4e236..1d80b90 100644 --- a/lib/include/lexer.h +++ b/lib/include/lexer.h @@ -21,6 +21,7 @@ void lexer_init(struct lexer* self, char const* source); void lexer_free(struct lexer* self); void lexer_skip_spaces(struct lexer* self); +bool lexer_is_sep(struct lexer* self, size_t index); bool lexer_next_is(struct lexer* self, TokenKind kind); @@ -30,5 +31,8 @@ struct token* lexer_try_scan_int(struct lexer* self); struct token* lexer_try_scan_text(struct lexer* self, char const* text, TokenKind kind); - +struct token* lexer_try_scan_keyword(struct lexer* self, + char const* keyword, + TokenKind kind, + char const* value); #endif diff --git a/lib/include/module.h b/lib/include/module.h index 51a2797..41eff89 100644 --- a/lib/include/module.h +++ b/lib/include/module.h @@ -16,6 +16,6 @@ void module_free(struct module* self); void module_load_source(struct module* self, char const* path); -void module_compile(struct module* self); +int module_compile(struct module* self); #endif diff --git a/lib/include/node.h b/lib/include/node.h index 002754e..0857517 100644 --- a/lib/include/node.h +++ b/lib/include/node.h @@ -8,7 +8,7 @@ G(NODE_ROOT), \ G(NODE_INT), \ G(NODE_ADD), G(NODE_SUB), G(NODE_MUL),\ G(NODE_DIV), G(NODE_POW), G(NODE_MOD),\ -G(NODE_USUB) +G(NODE_USUB), G(NODE_ASSERT_EQ) SK_ENUM_H(NodeKind, NODE_KIND); diff --git a/lib/include/parser.h b/lib/include/parser.h index 34d757e..ec79eb2 100644 --- a/lib/include/parser.h +++ b/lib/include/parser.h @@ -19,6 +19,7 @@ struct node* parser_try(struct parser* self, struct node* parser_try_parse(struct parser* self); struct node* parser_try_root(struct parser* self); struct node* parser_try_expr(struct parser* self); +struct node* parser_try_assert(struct parser* self); struct node* parser_try_term(struct parser* self); struct node* parser_try_factor(struct parser* self); struct node* parser_try_usub(struct parser* self); diff --git a/lib/include/prog.h b/lib/include/prog.h index e4a81f7..fe14679 100644 --- a/lib/include/prog.h +++ b/lib/include/prog.h @@ -9,7 +9,7 @@ G(OP_PUSH), \ G(OP_ADD), G(OP_SUB), G(OP_MUL), \ G(OP_DIV), G(OP_MOD), G(OP_POW), \ -G(OP_USUB) +G(OP_USUB), G(OP_ASSERT_EQ) SK_ENUM_H(Opcode, OPCODE); diff --git a/lib/include/state.h b/lib/include/state.h index 4a0b9ef..f185d3a 100644 --- a/lib/include/state.h +++ b/lib/include/state.h @@ -44,7 +44,7 @@ struct value* state_try_get_value(struct state* self, SK value); SK state_pop(struct state* self); -SK state_push_int(struct state* self, int integer); +SK state_push_int(struct state* self, int integer, int line); SK state_add(struct state* self); SK state_sub(struct state* self); diff --git a/lib/include/token.h b/lib/include/token.h index 464ce3f..ae26819 100644 --- a/lib/include/token.h +++ b/lib/include/token.h @@ -8,7 +8,8 @@ G(TOKEN_ROOT), \ G(TOKEN_INT), \ G(TOKEN_ADD), G(TOKEN_SUB), \ G(TOKEN_MUL), G(TOKEN_DIV), G(TOKEN_MOD), \ -G(TOKEN_POW), G(TOKEN_OPAR), G(TOKEN_CPAR) +G(TOKEN_POW), G(TOKEN_OPAR), G(TOKEN_CPAR), \ +G(TOKEN_ASSERT), G(TOKEN_ASSERT_EQ) SK_ENUM_H(TokenKind, TOKEN_KIND); diff --git a/lib/include/value.h b/lib/include/value.h index b22d1d5..414638c 100644 --- a/lib/include/value.h +++ b/lib/include/value.h @@ -18,14 +18,18 @@ struct value { TypeKind type; union val val; + int line; }; void value_init(struct value* self, TypeKind type, - union val val); + union val val, + int line); void value_free(struct value* self); +bool value_equals(struct value* self, struct value* rhs); + void value_str(struct value* self, struct str* dest); #endif diff --git a/lib/src/compiler.c b/lib/src/compiler.c index fa1ef14..e98a6d4 100644 --- a/lib/src/compiler.c +++ b/lib/src/compiler.c @@ -29,11 +29,16 @@ void compiler_compile(struct compiler* self, } } break; + case NODE_ASSERT_EQ: { + compiler_compile_children(self, node, prog); + prog_add_instr(prog, OP_ASSERT_EQ, SK_NO_PARAM); + } break; + case NODE_INT: { struct value* value = malloc(sizeof(struct value)); union val val; val.integer = atoi(node->token->value); - value_init(value, TYPE_INT, val); + value_init(value, TYPE_INT, val, node->token->line); size_t idx = prog_add_constant(prog, value); prog_add_instr(prog, OP_PUSH, idx); diff --git a/lib/src/exec.c b/lib/src/exec.c index 0cac7cd..7af42db 100644 --- a/lib/src/exec.c +++ b/lib/src/exec.c @@ -17,17 +17,61 @@ void exec_execute(struct exec* self, { while (self->pc < prog->opcodes.size) { + if (!errors_ok()) + { + return; + } + Opcode opcode = (size_t) prog->opcodes.data[self->pc]; size_t param = (size_t) prog->params.data[self->pc]; switch (opcode) { + case OP_ASSERT_EQ: { + SK rhs = state_pop(state); + SK lhs = state_pop(state); + struct value* left = + state_try_get_value(state, lhs); + struct value* right = + state_try_get_value(state, rhs); + + bool equals = value_equals( + left, right + ); + + if (!equals) + { + struct str lstr; + str_init(&lstr); + value_str(left, &lstr); + + struct str rstr; + str_init(&rstr); + value_str(right, &rstr); + + errors_push(left->line, + "assertion failed" + "\n\texpected: %s" + "\n\tgot: %s", + rstr.value, lstr.value); + + str_free(&lstr); + str_free(&rstr); + } + + self->pc++; + } break; + case OP_PUSH: { struct value* constant = prog->constants.data[param]; switch (constant->type) { case TYPE_INT: { - state_push_int(state, constant->val.integer); + state_push_int( + state, + constant->val.integer, + constant->line + ); } break; default: { fprintf(stderr, "cannot push value '%s'\n", diff --git a/lib/src/lexer.c b/lib/src/lexer.c index 0fdd01c..d2d951b 100644 --- a/lib/src/lexer.c +++ b/lib/src/lexer.c @@ -6,6 +6,12 @@ if ( (tok=lexer_try_scan_text(self, TXT, TOK)) )\ return tok;\ } +#define SK_SCAN_KEYWORD(TXT, TOK, VAL) \ +if ( (tok=lexer_try_scan_keyword(self, TXT, TOK, VAL)) )\ +{\ + return tok;\ +} + void lexer_init(struct lexer* self, char const* source) { assert(self); @@ -114,6 +120,8 @@ struct token* lexer_try_new_next(struct lexer* self) SK_SCAN_TEXT("%", TOKEN_MOD); SK_SCAN_TEXT("(", TOKEN_OPAR); SK_SCAN_TEXT(")", TOKEN_CPAR); + SK_SCAN_KEYWORD("assert", TOKEN_ASSERT, NULL); + SK_SCAN_KEYWORD("eq", TOKEN_ASSERT_EQ, NULL); if (self->context.cursor < self->len) { @@ -199,3 +207,42 @@ struct token* lexer_try_scan_text(struct lexer* self, self->context.cursor += txt_sz; return tok; } + +struct token* lexer_try_scan_keyword(struct lexer* self, + char const* keyword, + TokenKind kind, + char const* value) +{ + assert(self); + assert(keyword); + + size_t txt_sz = strlen(keyword); + + if (self->context.cursor + txt_sz > self->len) + { + return NULL; + } + + for (size_t i=0; isource[self->context.cursor + i] + != keyword[i]) + { + return NULL; + } + } + + if ( + (self->context.cursor > 0 + && !lexer_is_sep(self, self->context.cursor - 1)) + || (self->context.cursor + txt_sz <= self->len + && !lexer_is_sep(self, self->context.cursor + txt_sz))) + { + return NULL; + } + + struct token* tok = malloc(sizeof(struct token)); + token_init(tok, kind, value ? value : "", self->context.line); + self->context.cursor += txt_sz; + return tok; +} diff --git a/lib/src/module.c b/lib/src/module.c index 2f45dbf..d9b34d7 100644 --- a/lib/src/module.c +++ b/lib/src/module.c @@ -35,14 +35,14 @@ void module_load_source(struct module* self, fclose(file); } -void module_compile(struct module* self) +int module_compile(struct module* self) { assert(self); struct parser parser; parser_init(&parser, self->source.value); struct node* root = parser_try_parse(&parser); - + if (!errors_ok()) { errors_dump(); @@ -62,10 +62,16 @@ void module_compile(struct module* self) exec_execute(&exec, &state, &self->prog); + if (!errors_ok()) + { + errors_dump(); + goto free_state; + } + if (state_has_top(&state)) { struct value* value = state_try_get_value( - &state, + &state, state_top(&state) ); @@ -79,6 +85,7 @@ void module_compile(struct module* self) } // Free +free_state: state_free(&state); exec_free(&exec); compiler_free(&compiler); @@ -86,4 +93,7 @@ free_node: node_free(root); free(root); parser_free(&parser); + + + return errors_ok() ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/lib/src/parser.c b/lib/src/parser.c index b36f22a..ffa4b55 100644 --- a/lib/src/parser.c +++ b/lib/src/parser.c @@ -68,9 +68,51 @@ struct node* parser_try_root(struct parser* self) struct node* parser_try_expr(struct parser* self) { + if (lexer_next_is(&self->lexer, TOKEN_ASSERT)) + { + return SK_TRY(parser_try_assert); + } + return SK_TRY(parser_try_term); } +struct node* parser_try_assert(struct parser* self) +{ + assert(self); + + if (!lexer_next_is(&self->lexer, TOKEN_ASSERT)) + { + return NULL; + } + + struct token* tok = lexer_try_new_next(&self->lexer); + + struct node* lhs = SK_TRY(parser_try_expr); + if (!lhs) { return NULL; } + + if (!lexer_next_is(&self->lexer, TOKEN_ASSERT_EQ)) + { + token_free(tok); + free(tok); + return NULL; + } + + lexer_consume_next(&self->lexer); + struct node* rhs = SK_TRY(parser_try_expr); + if (!rhs) + { + token_free(tok); + free(tok); + return NULL; + } + + struct node* node = malloc(sizeof(struct node)); + node_init(node, NODE_ASSERT_EQ, tok); + node_push_new_child(node, lhs); + node_push_new_child(node, rhs); + return node; +} + struct node* parser_try_term(struct parser* self) { assert(self); diff --git a/lib/src/state.c b/lib/src/state.c index fba4a0d..1985d47 100644 --- a/lib/src/state.c +++ b/lib/src/state.c @@ -107,14 +107,14 @@ SK state_pop(struct state* self) return value; } -SK state_push_int(struct state* self, int integer) +SK state_push_int(struct state* self, int integer, int line) { assert(self); struct value* value = malloc(sizeof(struct value)); union val val; val.integer = integer; - value_init(value, TYPE_INT, val); + value_init(value, TYPE_INT, val, line); struct frame* frame = state_frame(self); struct local* local = malloc(sizeof(struct local)); @@ -137,7 +137,8 @@ SK state_add(struct state* self) return state_push_int( self, - left->val.integer + right->val.integer + left->val.integer + right->val.integer, + left->line ); } @@ -151,7 +152,8 @@ SK state_sub(struct state* self) return state_push_int( self, - left->val.integer - right->val.integer + left->val.integer - right->val.integer, + left->line ); } @@ -163,7 +165,8 @@ SK state_usub(struct state* self) return state_push_int( self, - -left->val.integer + -left->val.integer, + left->line ); } @@ -177,7 +180,8 @@ SK state_mul(struct state* self) return state_push_int( self, - left->val.integer * right->val.integer + left->val.integer * right->val.integer, + left->line ); } @@ -191,7 +195,8 @@ SK state_div(struct state* self) return state_push_int( self, - left->val.integer / right->val.integer + left->val.integer / right->val.integer, + left->line ); } @@ -205,7 +210,8 @@ SK state_mod(struct state* self) return state_push_int( self, - fmod(left->val.integer, right->val.integer) + fmod(left->val.integer, right->val.integer), + left->line ); } @@ -219,6 +225,7 @@ SK state_pow(struct state* self) return state_push_int( self, - powf(left->val.integer, right->val.integer) + powf(left->val.integer, right->val.integer), + left->line ); } diff --git a/lib/src/value.c b/lib/src/value.c index d7f5959..e3d29b0 100644 --- a/lib/src/value.c +++ b/lib/src/value.c @@ -4,11 +4,13 @@ SK_ENUM_C(TypeKind, TYPE_KIND); void value_init(struct value* self, TypeKind type, - union val val) + union val val, + int line) { assert(self); self->type = type; self->val = val; + self->line = line; } void value_free(struct value* self) @@ -16,6 +18,24 @@ void value_free(struct value* self) assert(self); } +bool value_equals(struct value* self, struct value* rhs) +{ + struct str left; + str_init(&left); + value_str(self, &left); + + struct str right; + str_init(&right); + value_str(rhs, &right); + + bool result = strcmp(left.value, right.value) == 0; + + str_free(&left); + str_free(&right); + + return result; +} + void value_str(struct value* self, struct str* dest) { assert(self); diff --git a/tests/lexer.h b/tests/lexer.h index 445fb0c..58228fb 100644 --- a/tests/lexer.h +++ b/tests/lexer.h @@ -38,22 +38,33 @@ static void test_lexer(char const* source, int count, ...) static void test_lexer_int() { - test_lexer(" 4 -23 720 ", 3, + test_lexer(" 4 -23 720 ", 3, "INT[4]", "INT[-23]", "INT[720]" ); - test_lexer("+-*/^%()", 8, + test_lexer("+-*/^%()", 8, "ADD", "SUB", "MUL", "DIV", "POW", "MOD", "OPAR", "CPAR" ); } +static void test_lexer_assert() +{ + test_lexer("assert 2 eq 1", 4, + "ASSERT", + "INT[2]", + "ASSERT_EQ", + "INT[1]" + ); +} + void register_lexer() { CU_pSuite suite = CU_add_suite("Lexer", 0, 0); CU_add_test(suite, "Integers", test_lexer_int); + CU_add_test(suite, "Assertions", test_lexer_assert); } #endif diff --git a/tests/parser.h b/tests/parser.h index 6b99020..4fc313a 100644 --- a/tests/parser.h +++ b/tests/parser.h @@ -15,7 +15,11 @@ static void test_parser(char const* oracle, char const* source) struct str str; str_init(&str); node_str(node, &str); - CU_ASSERT_STRING_EQUAL(oracle, str.value); + if (strcmp(oracle, str.value) != 0) + { + fprintf(stderr, "\n%s <> %s\n", oracle, str.value); + } + CU_ASSERT_STRING_EQUAL_FATAL(oracle, str.value); str_free(&str); node_free(node); @@ -38,10 +42,17 @@ static void test_parser_int() test_parser("ROOT(USUB(INT[32]))", " -(32) "); } +static void test_parser_assert() +{ + test_parser("ROOT(ASSERT_EQ(INT[32],INT[12]))", + " assert 32 eq 12 "); +} + void register_parser() { CU_pSuite suite = CU_add_suite("Parser", 0, 0); CU_add_test(suite, "Integers", test_parser_int); + CU_add_test(suite, "Assertions", test_parser_assert); } #endif