diff options
author | Carlos Maniero <carlos@maniero.me> | 2023-05-08 23:53:42 -0300 |
---|---|---|
committer | Johnny Richard <johnny@johnnyrichard.com> | 2023-05-09 21:46:51 +0200 |
commit | 35425aa5837543e4cc3fc82266dc2ae429cb2779 (patch) | |
tree | 1d7dec87dc17785162c20206371818f5e28fd99b | |
parent | 3842de0e22d72075f06bd8cc44b8744e86c21725 (diff) |
parser: Ensure the expression types
When assign a variable or returning a value it now ensures that the
expression matches the expected type.
To make this possible a %result_type% field was added to ast_node_t and
this field is used whenever to make the comparison.
Signed-off-by: Carlos Maniero <carlos@maniero.me>
Signed-off-by: Johnny Richard <johnny@johnnyrichard.com>
-rw-r--r-- | src/ast.c | 25 | ||||
-rw-r--r-- | src/ast.h | 14 | ||||
-rw-r--r-- | src/parser.c | 61 | ||||
-rw-r--r-- | src/scope.c | 1 | ||||
-rw-r--r-- | test/parser_test.c | 15 |
5 files changed, 99 insertions, 17 deletions
@@ -73,6 +73,7 @@ ast_node_new_return_stmt(ast_node_t *argument) ast_node_t *node = ast_node_new(); *node = (ast_node_t){ + .result_type = TYPE_VOID, .kind = AST_RETURN_STMT, .data = { .return_stmt = { .argument = argument } }, }; @@ -106,6 +107,7 @@ ast_node_new_literal_integer(uint32_t number) *node = (ast_node_t){ .kind = AST_LITERAL, + .result_type = TYPE_I32, .data = { .literal = { .kind = AST_LITERAL_INTEGER, @@ -124,6 +126,7 @@ ast_node_new_literal_bool(bool boolean) *node = (ast_node_t){ .kind = AST_LITERAL, + .result_type = TYPE_BOOL, .data = { .literal = { .kind = AST_LITERAL_BOOL, @@ -136,12 +139,13 @@ ast_node_new_literal_bool(bool boolean) } ast_node_t * -ast_node_new_binary_operation(ast_binary_operation_kind_t kind, ast_node_t *left, ast_node_t *right) +ast_node_new_binary_operation(ast_binary_operation_kind_t kind, ast_node_t *left, ast_node_t *right, type_t result_type) { ast_node_t *node = ast_node_new(); *node = (ast_node_t){ .kind = AST_BINARY_OPERATION, + .result_type = result_type, .data = { .binary_operation = { .kind = kind, @@ -161,6 +165,7 @@ ast_node_new_variable_declaration(string_view_t variable_name, type_t type, ast_ *node = (ast_node_t){ .kind = AST_VARIABLE_DECLARATION, + .result_type = TYPE_VOID, .data = { .variable_declaration = { .identifier = { .name = variable_name }, @@ -180,6 +185,7 @@ ast_node_new_variable_assignment(ast_identifier_t *identifier, ast_node_t *expre *node = (ast_node_t){ .kind = AST_VARIABLE_ASSIGNMENT, + .result_type = TYPE_VOID, .data = { .variable_assignment = { .identifier = identifier, @@ -192,14 +198,29 @@ ast_node_new_variable_assignment(ast_identifier_t *identifier, ast_node_t *expre } ast_node_t * -ast_node_new_variable(ast_identifier_t *identifier) +ast_node_new_variable(ast_identifier_t *identifier, type_t result_type) { ast_node_t *node = ast_node_new(); *node = (ast_node_t){ .kind = AST_VARIABLE, + .result_type = result_type, .data = { .variable = { .identifier = identifier } }, }; return node; } + +char * +ast_type_to_str(type_t type) +{ + switch (type) { + case TYPE_I32: + return "i32"; + case TYPE_BOOL: + return "bool"; + case TYPE_VOID: + return "void"; + } + assert(false); +} @@ -23,7 +23,8 @@ typedef enum { TYPE_I32, - TYPE_BOOL + TYPE_BOOL, + TYPE_VOID, } type_t; typedef struct ast_node_t ast_node_t; @@ -124,8 +125,12 @@ typedef struct ast_node_t { ast_node_kind_t kind; ast_node_data_t data; + type_t result_type; } ast_node_t; +char * +ast_type_to_str(type_t type); + ast_node_t * ast_node_new(void); @@ -136,7 +141,10 @@ void ast_node_destroy(ast_node_t *node); ast_node_t * -ast_node_new_binary_operation(ast_binary_operation_kind_t kind, ast_node_t *left, ast_node_t *right); +ast_node_new_binary_operation(ast_binary_operation_kind_t kind, + ast_node_t *left, + ast_node_t *right, + type_t result_type); ast_node_t * ast_node_new_function_declaration(string_view_t function_name, type_t return_type, vector_t *body); @@ -154,7 +162,7 @@ ast_node_t * ast_node_new_literal_bool(bool boolean); ast_node_t * -ast_node_new_variable(ast_identifier_t *identifier); +ast_node_new_variable(ast_identifier_t *identifier, type_t result_type); ast_node_t * ast_node_new_variable_assignment(ast_identifier_t *identifier, ast_node_t *expression); diff --git a/src/parser.c b/src/parser.c index 22313a6..7a22f16 100644 --- a/src/parser.c +++ b/src/parser.c @@ -27,6 +27,9 @@ #include "scope.h" #include "vector.h" +static bool +parser_expression_matches_the_expected_type(parser_t *parser, ast_node_t *expression, type_t type, token_t token); + void parser_init(parser_t *parser, lexer_t *lexer, scope_t *scope) { @@ -169,7 +172,8 @@ parser_parse_factor(parser_t *parser) return NULL; } - return ast_node_new_variable(&var_node->data.variable_declaration.identifier); + return ast_node_new_variable(&var_node->data.variable_declaration.identifier, + var_node->data.variable_declaration.type); } default: { parser_error_t error; @@ -204,7 +208,7 @@ parser_parse_term(parser_t *parser) return NULL; } - node = ast_node_new_binary_operation(token_to_binary_operation_kind(&token), left, right); + node = ast_node_new_binary_operation(token_to_binary_operation_kind(&token), left, right, TYPE_I32); lexer_peek_next_token(parser->lexer, &token); } @@ -242,7 +246,7 @@ parser_parse_expression(parser_t *parser) return NULL; } - node = ast_node_new_binary_operation(token_to_binary_operation_kind(&token), left, right); + node = ast_node_new_binary_operation(token_to_binary_operation_kind(&token), left, right, TYPE_I32); lexer_peek_next_token(parser->lexer, &token); } @@ -251,23 +255,29 @@ parser_parse_expression(parser_t *parser) } static ast_node_t * -parser_parse_return_stmt(parser_t *parser) +parser_parse_return_stmt(parser_t *parser, type_t result_type) { - if (!drop_expected_token(parser, TOKEN_KEYWORD_RETURN)) { + token_t token; + if (!expected_token(&token, parser, TOKEN_KEYWORD_RETURN)) { return NULL; } - ast_node_t *argument_token = parser_parse_expression(parser); - if (argument_token == NULL) { + ast_node_t *expression = parser_parse_expression(parser); + if (expression == NULL) { return NULL; } if (!drop_expected_token(parser, TOKEN_SEMICOLON)) { - ast_node_destroy(argument_token); + ast_node_destroy(expression); + return NULL; + } + + if (!parser_expression_matches_the_expected_type(parser, expression, result_type, token)) { + ast_node_destroy(expression); return NULL; } - return ast_node_new_return_stmt(argument_token); + return ast_node_new_return_stmt(expression); } static ast_node_t * @@ -307,9 +317,31 @@ parser_parse_variable_assignment(parser_t *parser) assert(variable_declaration_node->kind == AST_VARIABLE_DECLARATION); + if (!parser_expression_matches_the_expected_type( + parser, expression, variable_declaration_node->data.variable_declaration.type, variable_token)) { + ast_node_destroy(expression); + return false; + } + return ast_node_new_variable_assignment(&variable_declaration_node->data.variable_declaration.identifier, expression); } +static bool +parser_expression_matches_the_expected_type(parser_t *parser, ast_node_t *expression, type_t type, token_t token) +{ + if (expression->result_type != type) { + parser_error_t error; + error.token = token; + sprintf(error.message, + "incompatible types: expected '%s', actual '%s'.", + ast_type_to_str(type), + ast_type_to_str(expression->result_type)); + parser->errors[parser->errors_len++] = error; + return false; + } + return true; +} + static ast_node_t * parser_parse_variable_declaration(parser_t *parser) { @@ -348,6 +380,11 @@ parser_parse_variable_declaration(parser_t *parser) return NULL; } + if (!parser_expression_matches_the_expected_type(parser, expression, type, variable_name)) { + ast_node_destroy(expression); + return NULL; + } + ast_node_t *node = ast_node_new_variable_declaration(variable_name.value, type, expression); scope_push(parser->scope, &node->data.variable_declaration.identifier, node); @@ -426,7 +463,7 @@ parser_ensure_function_return_statement(parser_t *parser, vector_t *body, token_ } static vector_t * -parser_parse_block_declarations(parser_t *parser) +parser_parse_block_declarations(parser_t *parser, type_t result_type) { if (!drop_expected_token(parser, TOKEN_OCURLY)) { return NULL; @@ -441,7 +478,7 @@ parser_parse_block_declarations(parser_t *parser) while (!is_block_end(parser)) { if (is_next_statement_return(parser)) { - ast_node_t *return_node = parser_parse_return_stmt(parser); + ast_node_t *return_node = parser_parse_return_stmt(parser, result_type); if (return_node == NULL) { scope_leave(parser->scope); @@ -529,7 +566,7 @@ parser_parse_function_declaration(parser_t *parser) return NULL; } - vector_t *body = parser_parse_block_declarations(parser); + vector_t *body = parser_parse_block_declarations(parser, return_type); if (body == NULL) { return NULL; diff --git a/src/scope.c b/src/scope.c index 6338e60..5b0eb56 100644 --- a/src/scope.c +++ b/src/scope.c @@ -115,6 +115,7 @@ scope_get(scope_t *scope, string_view_t name) return NULL; } + void scope_push(scope_t *scope, ast_identifier_t *identifier, ast_node_t *node) { diff --git a/test/parser_test.c b/test/parser_test.c index 12b2cac..1371453 100644 --- a/test/parser_test.c +++ b/test/parser_test.c @@ -268,6 +268,21 @@ test_parse_basic_syntax_errors(const MunitParameter params[], void *user_data_or assert_parser_error("fn main(): beff { return 42; }", "type 'beff' is not defined"); assert_parser_error("fn main(): i32 { return b; }", "identifier 'b' not defined"); assert_parser_error("fn main(): i32 { b = 1; return b; }", "trying to assign 'b' before defining it."); + assert_parser_error("fn main(): i32 { let b: bool = 3; return 1; }", + "incompatible types: expected 'bool', actual 'i32'."); + assert_parser_error("fn main(): i32 { let b: i32 = true; return 1; }", + "incompatible types: expected 'i32', actual 'bool'."); + assert_parser_error("fn main(): i32 { let b: i32 = 1; b = true; return 1; }", + "incompatible types: expected 'i32', actual 'bool'."); + assert_parser_error("fn main(): i32 { let b: bool = true; let c: i32 = b; return 1; }", + "incompatible types: expected 'i32', actual 'bool'."); + assert_parser_error("fn main(): i32 { let b: i32 = 1; let c: bool = b; return 1; }", + "incompatible types: expected 'bool', actual 'i32'."); + assert_parser_error("fn main(): i32 { let b: bool = 1; b = 42; return 1; }", + "incompatible types: expected 'bool', actual 'i32'."); + assert_parser_error("fn main(): i32 { return true; }", "incompatible types: expected 'i32', actual 'bool'."); + assert_parser_error("fn main(): i32 { let a: bool = true; return a; }", + "incompatible types: expected 'i32', actual 'bool'."); // FIXME: once function calls are implemented, this error should inform that // neither a variable or function call was found. assert_parser_error("fn main(): i32 { oxi 42; }", "unexpected token 'TOKEN_NAME' value='oxi'"); |