summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCarlos Maniero <carlos@maniero.me>2023-05-08 23:53:42 -0300
committerJohnny Richard <johnny@johnnyrichard.com>2023-05-09 21:46:51 +0200
commit35425aa5837543e4cc3fc82266dc2ae429cb2779 (patch)
tree1d7dec87dc17785162c20206371818f5e28fd99b
parent3842de0e22d72075f06bd8cc44b8744e86c21725 (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.c25
-rw-r--r--src/ast.h14
-rw-r--r--src/parser.c61
-rw-r--r--src/scope.c1
-rw-r--r--test/parser_test.c15
5 files changed, 99 insertions, 17 deletions
diff --git a/src/ast.c b/src/ast.c
index 8a46034..4fd5a3f 100644
--- a/src/ast.c
+++ b/src/ast.c
@@ -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);
+}
diff --git a/src/ast.h b/src/ast.h
index 80148dc..f91632c 100644
--- a/src/ast.h
+++ b/src/ast.h
@@ -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'");