assert expression acceptance tests.

main
bog 2024-04-01 07:20:42 +02:00
parent 77fda3a432
commit e8b10498cf
21 changed files with 276 additions and 27 deletions

View File

@ -11,10 +11,11 @@ int main(int argc, char** argv)
module_init(&module); module_init(&module);
module_load_source(&module, argv[1]); module_load_source(&module, argv[1]);
module_compile(&module); int status = module_compile(&module);
module_free(&module); module_free(&module);
errors_free(); errors_free();
return status;
} }
return 0; return 0;

View File

@ -1,5 +1,8 @@
ROOT ::= EXPR* ROOT ::= EXPR*
EXPR ::= TERM EXPR ::=
| TERM
| ASSERT
ASSERT ::= assert EXPR eq EXPR
TERM ::= FACTOR ((add|sub) FACTOR)* TERM ::= FACTOR ((add|sub) FACTOR)*
FACTOR ::= USUB ((mul|div|mod) USUB)* FACTOR ::= USUB ((mul|div|mod) USUB)*
USUB ::= sub* POW USUB ::= sub* POW

8
features/int.sk Normal file
View File

@ -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

30
features/run.sh Executable file
View File

@ -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

View File

@ -21,6 +21,7 @@ void lexer_init(struct lexer* self, char const* source);
void lexer_free(struct lexer* self); void lexer_free(struct lexer* self);
void lexer_skip_spaces(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); 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, struct token* lexer_try_scan_text(struct lexer* self,
char const* text, char const* text,
TokenKind kind); TokenKind kind);
struct token* lexer_try_scan_keyword(struct lexer* self,
char const* keyword,
TokenKind kind,
char const* value);
#endif #endif

View File

@ -16,6 +16,6 @@ void module_free(struct module* self);
void module_load_source(struct module* self, void module_load_source(struct module* self,
char const* path); char const* path);
void module_compile(struct module* self); int module_compile(struct module* self);
#endif #endif

View File

@ -8,7 +8,7 @@ G(NODE_ROOT), \
G(NODE_INT), \ G(NODE_INT), \
G(NODE_ADD), G(NODE_SUB), G(NODE_MUL),\ G(NODE_ADD), G(NODE_SUB), G(NODE_MUL),\
G(NODE_DIV), G(NODE_POW), G(NODE_MOD),\ 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); SK_ENUM_H(NodeKind, NODE_KIND);

View File

@ -19,6 +19,7 @@ struct node* parser_try(struct parser* self,
struct node* parser_try_parse(struct parser* self); struct node* parser_try_parse(struct parser* self);
struct node* parser_try_root(struct parser* self); struct node* parser_try_root(struct parser* self);
struct node* parser_try_expr(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_term(struct parser* self);
struct node* parser_try_factor(struct parser* self); struct node* parser_try_factor(struct parser* self);
struct node* parser_try_usub(struct parser* self); struct node* parser_try_usub(struct parser* self);

View File

@ -9,7 +9,7 @@
G(OP_PUSH), \ G(OP_PUSH), \
G(OP_ADD), G(OP_SUB), G(OP_MUL), \ G(OP_ADD), G(OP_SUB), G(OP_MUL), \
G(OP_DIV), G(OP_MOD), G(OP_POW), \ G(OP_DIV), G(OP_MOD), G(OP_POW), \
G(OP_USUB) G(OP_USUB), G(OP_ASSERT_EQ)
SK_ENUM_H(Opcode, OPCODE); SK_ENUM_H(Opcode, OPCODE);

View File

@ -44,7 +44,7 @@ struct value* state_try_get_value(struct state* self, SK value);
SK state_pop(struct state* self); 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_add(struct state* self);
SK state_sub(struct state* self); SK state_sub(struct state* self);

View File

@ -8,7 +8,8 @@ G(TOKEN_ROOT), \
G(TOKEN_INT), \ G(TOKEN_INT), \
G(TOKEN_ADD), G(TOKEN_SUB), \ G(TOKEN_ADD), G(TOKEN_SUB), \
G(TOKEN_MUL), G(TOKEN_DIV), G(TOKEN_MOD), \ 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); SK_ENUM_H(TokenKind, TOKEN_KIND);

View File

@ -18,14 +18,18 @@ struct value
{ {
TypeKind type; TypeKind type;
union val val; union val val;
int line;
}; };
void value_init(struct value* self, void value_init(struct value* self,
TypeKind type, TypeKind type,
union val val); union val val,
int line);
void value_free(struct value* self); void value_free(struct value* self);
bool value_equals(struct value* self, struct value* rhs);
void value_str(struct value* self, struct str* dest); void value_str(struct value* self, struct str* dest);
#endif #endif

View File

@ -29,11 +29,16 @@ void compiler_compile(struct compiler* self,
} }
} break; } 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: { case NODE_INT: {
struct value* value = malloc(sizeof(struct value)); struct value* value = malloc(sizeof(struct value));
union val val; union val val;
val.integer = atoi(node->token->value); 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); size_t idx = prog_add_constant(prog, value);
prog_add_instr(prog, OP_PUSH, idx); prog_add_instr(prog, OP_PUSH, idx);

View File

@ -17,17 +17,61 @@ void exec_execute(struct exec* self,
{ {
while (self->pc < prog->opcodes.size) while (self->pc < prog->opcodes.size)
{ {
if (!errors_ok())
{
return;
}
Opcode opcode = (size_t) prog->opcodes.data[self->pc]; Opcode opcode = (size_t) prog->opcodes.data[self->pc];
size_t param = (size_t) prog->params.data[self->pc]; size_t param = (size_t) prog->params.data[self->pc];
switch (opcode) 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: { case OP_PUSH: {
struct value* constant = prog->constants.data[param]; struct value* constant = prog->constants.data[param];
switch (constant->type) switch (constant->type)
{ {
case TYPE_INT: { case TYPE_INT: {
state_push_int(state, constant->val.integer); state_push_int(
state,
constant->val.integer,
constant->line
);
} break; } break;
default: { default: {
fprintf(stderr, "cannot push value '%s'\n", fprintf(stderr, "cannot push value '%s'\n",

View File

@ -6,6 +6,12 @@ if ( (tok=lexer_try_scan_text(self, TXT, TOK)) )\
return 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) void lexer_init(struct lexer* self, char const* source)
{ {
assert(self); assert(self);
@ -114,6 +120,8 @@ struct token* lexer_try_new_next(struct lexer* self)
SK_SCAN_TEXT("%", TOKEN_MOD); SK_SCAN_TEXT("%", TOKEN_MOD);
SK_SCAN_TEXT("(", TOKEN_OPAR); SK_SCAN_TEXT("(", TOKEN_OPAR);
SK_SCAN_TEXT(")", TOKEN_CPAR); 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) if (self->context.cursor < self->len)
{ {
@ -199,3 +207,42 @@ struct token* lexer_try_scan_text(struct lexer* self,
self->context.cursor += txt_sz; self->context.cursor += txt_sz;
return tok; 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; i<txt_sz; i++)
{
if (self->source[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;
}

View File

@ -35,14 +35,14 @@ void module_load_source(struct module* self,
fclose(file); fclose(file);
} }
void module_compile(struct module* self) int module_compile(struct module* self)
{ {
assert(self); assert(self);
struct parser parser; struct parser parser;
parser_init(&parser, self->source.value); parser_init(&parser, self->source.value);
struct node* root = parser_try_parse(&parser); struct node* root = parser_try_parse(&parser);
if (!errors_ok()) if (!errors_ok())
{ {
errors_dump(); errors_dump();
@ -62,10 +62,16 @@ void module_compile(struct module* self)
exec_execute(&exec, &state, &self->prog); exec_execute(&exec, &state, &self->prog);
if (!errors_ok())
{
errors_dump();
goto free_state;
}
if (state_has_top(&state)) if (state_has_top(&state))
{ {
struct value* value = state_try_get_value( struct value* value = state_try_get_value(
&state, &state,
state_top(&state) state_top(&state)
); );
@ -79,6 +85,7 @@ void module_compile(struct module* self)
} }
// Free // Free
free_state:
state_free(&state); state_free(&state);
exec_free(&exec); exec_free(&exec);
compiler_free(&compiler); compiler_free(&compiler);
@ -86,4 +93,7 @@ free_node:
node_free(root); node_free(root);
free(root); free(root);
parser_free(&parser); parser_free(&parser);
return errors_ok() ? EXIT_SUCCESS : EXIT_FAILURE;
} }

View File

@ -68,9 +68,51 @@ struct node* parser_try_root(struct parser* self)
struct node* parser_try_expr(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); 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) struct node* parser_try_term(struct parser* self)
{ {
assert(self); assert(self);

View File

@ -107,14 +107,14 @@ SK state_pop(struct state* self)
return value; return value;
} }
SK state_push_int(struct state* self, int integer) SK state_push_int(struct state* self, int integer, int line)
{ {
assert(self); assert(self);
struct value* value = malloc(sizeof(struct value)); struct value* value = malloc(sizeof(struct value));
union val val; union val val;
val.integer = integer; val.integer = integer;
value_init(value, TYPE_INT, val); value_init(value, TYPE_INT, val, line);
struct frame* frame = state_frame(self); struct frame* frame = state_frame(self);
struct local* local = malloc(sizeof(struct local)); struct local* local = malloc(sizeof(struct local));
@ -137,7 +137,8 @@ SK state_add(struct state* self)
return state_push_int( return state_push_int(
self, 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( return state_push_int(
self, 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( return state_push_int(
self, self,
-left->val.integer -left->val.integer,
left->line
); );
} }
@ -177,7 +180,8 @@ SK state_mul(struct state* self)
return state_push_int( return state_push_int(
self, 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( return state_push_int(
self, 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( return state_push_int(
self, 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( return state_push_int(
self, self,
powf(left->val.integer, right->val.integer) powf(left->val.integer, right->val.integer),
left->line
); );
} }

View File

@ -4,11 +4,13 @@ SK_ENUM_C(TypeKind, TYPE_KIND);
void value_init(struct value* self, void value_init(struct value* self,
TypeKind type, TypeKind type,
union val val) union val val,
int line)
{ {
assert(self); assert(self);
self->type = type; self->type = type;
self->val = val; self->val = val;
self->line = line;
} }
void value_free(struct value* self) void value_free(struct value* self)
@ -16,6 +18,24 @@ void value_free(struct value* self)
assert(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) void value_str(struct value* self, struct str* dest)
{ {
assert(self); assert(self);

View File

@ -38,22 +38,33 @@ static void test_lexer(char const* source, int count, ...)
static void test_lexer_int() static void test_lexer_int()
{ {
test_lexer(" 4 -23 720 ", 3, test_lexer(" 4 -23 720 ", 3,
"INT[4]", "INT[4]",
"INT[-23]", "INT[-23]",
"INT[720]" "INT[720]"
); );
test_lexer("+-*/^%()", 8, test_lexer("+-*/^%()", 8,
"ADD", "SUB", "MUL", "DIV", "ADD", "SUB", "MUL", "DIV",
"POW", "MOD", "OPAR", "CPAR" "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() void register_lexer()
{ {
CU_pSuite suite = CU_add_suite("Lexer", 0, 0); CU_pSuite suite = CU_add_suite("Lexer", 0, 0);
CU_add_test(suite, "Integers", test_lexer_int); CU_add_test(suite, "Integers", test_lexer_int);
CU_add_test(suite, "Assertions", test_lexer_assert);
} }
#endif #endif

View File

@ -15,7 +15,11 @@ static void test_parser(char const* oracle, char const* source)
struct str str; struct str str;
str_init(&str); str_init(&str);
node_str(node, &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); str_free(&str);
node_free(node); node_free(node);
@ -38,10 +42,17 @@ static void test_parser_int()
test_parser("ROOT(USUB(INT[32]))", " -(32) "); 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() void register_parser()
{ {
CU_pSuite suite = CU_add_suite("Parser", 0, 0); CU_pSuite suite = CU_add_suite("Parser", 0, 0);
CU_add_test(suite, "Integers", test_parser_int); CU_add_test(suite, "Integers", test_parser_int);
CU_add_test(suite, "Assertions", test_parser_assert);
} }
#endif #endif