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_load_source(&module, argv[1]);
module_compile(&module);
int status = module_compile(&module);
module_free(&module);
errors_free();
return status;
}
return 0;

View File

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

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

View File

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

View File

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

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_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);

View File

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

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_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);

View File

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

View File

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

View File

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

View File

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

View File

@ -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; 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);
}
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;
}

View File

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

View File

@ -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
);
}

View File

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

View File

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

View File

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