From 0704284726179924501e480db4cf6b87f239b0e0 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Wed, 29 Apr 2026 21:39:48 +0200 Subject: [PATCH] Can parse variables --- specs/VARIABLES.md | 4 +- v0/parser.c | 93 +++++++++++++++++++++++++++++- v0/test.c | 2 +- v0/test_parser.c | 20 ++++--- v0/tests/parser_variable_simple.c2 | 4 ++ 5 files changed, 108 insertions(+), 15 deletions(-) create mode 100644 v0/tests/parser_variable_simple.c2 diff --git a/specs/VARIABLES.md b/specs/VARIABLES.md index f3956a0..1ff5a37 100644 --- a/specs/VARIABLES.md +++ b/specs/VARIABLES.md @@ -6,10 +6,10 @@ Global variables can be defined as such: ```c2 // Defines a global variable called my_var. -int32 my_var; +i32 my_var; // Defines a const variable. -const int32 my_var; +const i32 my_var; // Defines a global variable whose type is determined automatically. // The value will be determined at runtime. diff --git a/v0/parser.c b/v0/parser.c index 3b42985..a93e4ed 100644 --- a/v0/parser.c +++ b/v0/parser.c @@ -264,6 +264,57 @@ static bool parse_alias_declaration(Parser* p, Module* module, bool is_public) { return true; } +/** + * Parses a variable declaration. + * + * @param p The parser state. + * @param module The module to add the variable to. + * @param is_public Whether the variable is public. + * @param is_static Whether the variable is static. + * @param is_const Whether the variable is constant. + * @return true if successful, false otherwise. + */ +static bool parse_variable_declaration(Parser* p, Module* module, bool is_public, bool is_static, bool is_const) { + module->variable_count++; + module->variables = realloc(module->variables, sizeof(VariableDeclaration) * module->variable_count); + + VariableDeclaration* var = &module->variables[module->variable_count - 1]; + memset(var, 0, sizeof(VariableDeclaration)); + var->is_public = is_public; + var->is_static = is_static; + var->is_const = is_const; + + if (!parse_type_expression(p, &var->type)) { + return false; + } + + if (!parser_require(p, TOKEN_IDENTIFIER, "expected variable identifier")) { + return false; + } + var->name = parser_to_text(p); + + if (!parser_expect(p, TOKEN_SEMICOLON, "expected ';' after variable declaration")) { + return false; + } + + return true; +} + +/** + * Checks if the current token is a primitive type. + * + * The token will not be consumed by this function, so the caller can decide how to handle it if it is a primitive type. + * + * @param p The parser state. + * @return true if the current token is a primitive type, false otherwise. + */ +static bool parser_accept_primitive(Parser* p) { + return parser_peek(p, TOKEN_I8) || parser_peek(p, TOKEN_I16) || + parser_peek(p, TOKEN_I32) || parser_peek(p, TOKEN_I64) || + parser_peek(p, TOKEN_U8) || parser_peek(p, TOKEN_U16) || + parser_peek(p, TOKEN_U32) || parser_peek(p, TOKEN_U64); +} + Module* parser_parse(TokenStream* ts) { Parser* p = malloc(sizeof(Parser)); p->ts = ts; @@ -277,25 +328,53 @@ Module* parser_parse(TokenStream* ts) { while (!parser_peek(p, TOKEN_EOF)) { bool is_public = false; + bool is_static = false; + bool is_const = false; bool terminal = false; - do { + + while (!terminal) { if (parser_accept(p, TOKEN_IMPORT)) { + if (is_static) { + log_on_line(&p->token.location, "import declarations cannot be static or const"); + goto fail; + } + if (is_const) { + log_on_line(&p->token.location, "import declarations cannot be static or const"); + goto fail; + } if (!parse_import_declaration(p, module, is_public)) { goto fail; } terminal = true; } else if (parser_accept(p, TOKEN_ALIAS)) { + if (is_static) { + log_on_line(&p->token.location, "alias declarations cannot be static or const"); + goto fail; + } + if (is_const) { + log_on_line(&p->token.location, "alias declarations cannot be static or const"); + goto fail; + } if (!parse_alias_declaration(p, module, is_public)) { goto fail; } terminal = true; } else if (parser_accept(p, TOKEN_PUBLIC)) { is_public = true; + } else if (parser_accept(p, TOKEN_STATIC)) { + is_static = true; + } else if (parser_accept(p, TOKEN_CONST)) { + is_const = true; + } else if (parser_accept_primitive(p)) { + if (!parse_variable_declaration(p, module, is_public, is_static, is_const)) { + goto fail; + } + terminal = true; } else { log_on_line(&p->token.location, "unexpected token"); goto fail; } - } while (!terminal); + } } free(p); @@ -333,6 +412,14 @@ void parser_free(Module* module) { free(module->aliases); } + if (module->variables != NULL) { + for(size_t i = 0; i < module->variable_count; i++) { + free(module->variables[i].name); + free_type_expression(&module->variables[i].type); + } + free(module->variables); + } + free(module->name); - free(module); + free(module); } diff --git a/v0/test.c b/v0/test.c index 1fe1fa9..5c53f70 100644 --- a/v0/test.c +++ b/v0/test.c @@ -201,7 +201,6 @@ static TestCase s_tests[] = { TEST(test_log_error) TEST(test_log_on_line_variadic) TEST(test_log_on_line) - TEST(test_parser_alias_and_import_mix) TEST(test_parser_alias_array) TEST(test_parser_alias_simple) TEST(test_parser_bad_import_name) @@ -211,6 +210,7 @@ static TestCase s_tests[] = { TEST(test_parser_missing_semicolon_module) TEST(test_parser_module_name) TEST(test_parser_public_imports) + TEST(test_parser_variable_simple) TEST(test_tokenstream_comma) TEST(test_tokenstream_info) TEST(test_tokenstream_keywords_and_symbols) diff --git a/v0/test_parser.c b/v0/test_parser.c index da99cf3..ec950d2 100644 --- a/v0/test_parser.c +++ b/v0/test_parser.c @@ -81,15 +81,17 @@ static void test_parser_alias_array(void) { assert_true(valueType->builtin.isSigned, "expected signed"); } -static void test_parser_alias_and_import_mix(void) { +static void test_parser_variable_simple(void) { Module* m = test_get_ast(); - + assert_not_null(m, "expected module to be parsed"); - assert_int(2, (int)m->import_count, "expected 2 imports"); - assert_int(2, (int)m->alias_count, "expected 2 aliases"); - - assert_str("foo", m->imports[0].module_name, "expected import 1 name 'foo'"); - assert_str("bar", m->imports[1].module_name, "expected import 2 name 'bar'"); - assert_str("myalias", m->aliases[0].name, "expected alias 1 name 'myalias'"); - assert_str("otheralias", m->aliases[1].name, "expected alias 2 name 'otheralias'"); + assert_int(1, (int)m->variable_count, "expected correct number of variables"); + VariableDeclaration var = m->variables[0]; + assert_str("my_var", var.name, "expected correct variable name"); + assert_int(TYPE_EXPRESSION_BUILTIN, var.type.tag, "expected correct type tag"); + assert_int(32, var.type.builtin.bitSize, "expected bitSize 32"); + assert_true(var.type.builtin.isSigned, "expected signed"); + assert_false(var.is_const, "expected not const"); + assert_false(var.is_static, "expected not static"); } + diff --git a/v0/tests/parser_variable_simple.c2 b/v0/tests/parser_variable_simple.c2 new file mode 100644 index 0000000..3ef4b2b --- /dev/null +++ b/v0/tests/parser_variable_simple.c2 @@ -0,0 +1,4 @@ +module my_module; + +// Defines a global variable called my_var. +i32 my_var;