Compare commits

...

2 Commits

Author SHA1 Message Date
seeseemelk 0704284726 Can parse variables 2026-04-29 21:39:48 +02:00
seeseemelk 94ae665a0a Add initial variable work 2026-04-29 21:20:52 +02:00
7 changed files with 136 additions and 18 deletions
+2 -3
View File
@@ -26,9 +26,8 @@ There will be no `test_buffer.h`. Instead, `test.c` will directly
Every syntax error path identified in the parser MUST have a corresponding test. Every syntax error path identified in the parser MUST have a corresponding test.
## Language Syntax ## Language Syntax
Since this is a compiler for a new language, do not assume anything Since this is a compiler for a new language, do not assume anything of its syntax.
of its syntax. Always check the `specs` directory to see examples and documentation about the language.
Always check the `specs` directory.
If there is anything unclear, ask the user for clarification. If there is anything unclear, ask the user for clarification.
It is certainly possible that there are contradictions in the It is certainly possible that there are contradictions in the
+2 -2
View File
@@ -6,10 +6,10 @@ Global variables can be defined as such:
```c2 ```c2
// Defines a global variable called my_var. // Defines a global variable called my_var.
int32 my_var; i32 my_var;
// Defines a const variable. // Defines a const variable.
const int32 my_var; const i32 my_var;
// Defines a global variable whose type is determined automatically. // Defines a global variable whose type is determined automatically.
// The value will be determined at runtime. // The value will be determined at runtime.
+26
View File
@@ -62,6 +62,26 @@ typedef struct {
bool is_public; bool is_public;
} AliasDeclaration; } AliasDeclaration;
/**
* A declaration of a variable, which may be a constant or not, and may be static or not.
*/
typedef struct {
/** @brief The name of the variable. */
char* name;
/** @brief The type of the variable. */
TypeExpression type;
/** @brief Whether the variable is public or not. */
bool is_public;
/** @brief Whether the variable is static or not. */
bool is_static;
/** @brief Whether the variable is a constant or not. */
bool is_const;
} VariableDeclaration;
/** /**
* The top-level model. * The top-level model.
* Every file matches an entire Module. * Every file matches an entire Module.
@@ -81,6 +101,12 @@ typedef struct {
/** @brief The number of aliases in the module. */ /** @brief The number of aliases in the module. */
size_t alias_count; size_t alias_count;
/** @brief The list of variables in the module. */
VariableDeclaration* variables;
/** @brief The number of variables in the module. */
size_t variable_count;
} Module; } Module;
#endif #endif
+90 -3
View File
@@ -264,6 +264,57 @@ static bool parse_alias_declaration(Parser* p, Module* module, bool is_public) {
return true; 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) { Module* parser_parse(TokenStream* ts) {
Parser* p = malloc(sizeof(Parser)); Parser* p = malloc(sizeof(Parser));
p->ts = ts; p->ts = ts;
@@ -277,25 +328,53 @@ Module* parser_parse(TokenStream* ts) {
while (!parser_peek(p, TOKEN_EOF)) { while (!parser_peek(p, TOKEN_EOF)) {
bool is_public = false; bool is_public = false;
bool is_static = false;
bool is_const = false;
bool terminal = false; bool terminal = false;
do {
while (!terminal) {
if (parser_accept(p, TOKEN_IMPORT)) { 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)) { if (!parse_import_declaration(p, module, is_public)) {
goto fail; goto fail;
} }
terminal = true; terminal = true;
} else if (parser_accept(p, TOKEN_ALIAS)) { } 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)) { if (!parse_alias_declaration(p, module, is_public)) {
goto fail; goto fail;
} }
terminal = true; terminal = true;
} else if (parser_accept(p, TOKEN_PUBLIC)) { } else if (parser_accept(p, TOKEN_PUBLIC)) {
is_public = true; 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 { } else {
log_on_line(&p->token.location, "unexpected token"); log_on_line(&p->token.location, "unexpected token");
goto fail; goto fail;
} }
} while (!terminal); }
} }
free(p); free(p);
@@ -333,6 +412,14 @@ void parser_free(Module* module) {
free(module->aliases); 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->name);
free(module); free(module);
} }
+1 -1
View File
@@ -201,7 +201,6 @@ static TestCase s_tests[] = {
TEST(test_log_error) TEST(test_log_error)
TEST(test_log_on_line_variadic) TEST(test_log_on_line_variadic)
TEST(test_log_on_line) TEST(test_log_on_line)
TEST(test_parser_alias_and_import_mix)
TEST(test_parser_alias_array) TEST(test_parser_alias_array)
TEST(test_parser_alias_simple) TEST(test_parser_alias_simple)
TEST(test_parser_bad_import_name) TEST(test_parser_bad_import_name)
@@ -211,6 +210,7 @@ static TestCase s_tests[] = {
TEST(test_parser_missing_semicolon_module) TEST(test_parser_missing_semicolon_module)
TEST(test_parser_module_name) TEST(test_parser_module_name)
TEST(test_parser_public_imports) TEST(test_parser_public_imports)
TEST(test_parser_variable_simple)
TEST(test_tokenstream_comma) TEST(test_tokenstream_comma)
TEST(test_tokenstream_info) TEST(test_tokenstream_info)
TEST(test_tokenstream_keywords_and_symbols) TEST(test_tokenstream_keywords_and_symbols)
+11 -9
View File
@@ -81,15 +81,17 @@ static void test_parser_alias_array(void) {
assert_true(valueType->builtin.isSigned, "expected signed"); 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(); Module* m = test_get_ast();
assert_not_null(m, "expected module to be parsed"); assert_not_null(m, "expected module to be parsed");
assert_int(2, (int)m->import_count, "expected 2 imports"); assert_int(1, (int)m->variable_count, "expected correct number of variables");
assert_int(2, (int)m->alias_count, "expected 2 aliases"); VariableDeclaration var = m->variables[0];
assert_str("my_var", var.name, "expected correct variable name");
assert_str("foo", m->imports[0].module_name, "expected import 1 name 'foo'"); assert_int(TYPE_EXPRESSION_BUILTIN, var.type.tag, "expected correct type tag");
assert_str("bar", m->imports[1].module_name, "expected import 2 name 'bar'"); assert_int(32, var.type.builtin.bitSize, "expected bitSize 32");
assert_str("myalias", m->aliases[0].name, "expected alias 1 name 'myalias'"); assert_true(var.type.builtin.isSigned, "expected signed");
assert_str("otheralias", m->aliases[1].name, "expected alias 2 name 'otheralias'"); assert_false(var.is_const, "expected not const");
assert_false(var.is_static, "expected not static");
} }
+4
View File
@@ -0,0 +1,4 @@
module my_module;
// Defines a global variable called my_var.
i32 my_var;