Compare commits

..

14 Commits

Author SHA1 Message Date
seeseemelk b6d0a78d06 Add integration test 2026-05-01 09:44:46 +02:00
seeseemelk 3bdccf2000 Add integration test framework 2026-04-30 22:21:08 +02:00
seeseemelk 177fb971e4 Rename AST structures to Tree and relocate freeing logic 2026-04-30 21:46:15 +02:00
seeseemelk ea55dedd07 Refactor AST and Parser into modular subdirectories
- Split ast.h into granular headers in v0/ast/
- Split parser.c into modular implementation files in v0/parser/
- Move and rename parser tests to v0/parser/test_*.c
- Update build system (include.mk) with modular sub-makefiles
- Maintain v0/ast.h and v0/parser.h as umbrella headers
2026-04-30 21:23:07 +02:00
seeseemelk 4bd66ea216 More variable stuff 2026-04-30 20:25:53 +02:00
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
seeseemelk e2d8e385f0 Add basic var tokens 2026-04-29 20:28:52 +02:00
seeseemelk 76f9168c5f Fix docs 2026-04-29 20:21:52 +02:00
seeseemelk 1ab021561e Fix bad test 2026-04-29 20:20:16 +02:00
seeseemelk f260e02efa Refactor parser 2026-04-29 20:15:05 +02:00
seeseemelk 1c5d49d682 Fix valgrind errors 2026-04-29 19:41:00 +02:00
seeseemelk cc25563cd2 Cleanup 2026-04-29 19:23:59 +02:00
seeseemelk 323a599399 Build with debug symbols 2026-04-29 18:53:02 +02:00
48 changed files with 996 additions and 495 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
+8 -2
View File
@@ -1,6 +1,6 @@
.PHONY: all test clean .PHONY: all test clean
all: c2 test all: c2 test integration-test
c2: v0/bin/c2 c2: v0/bin/c2
cp $< $@ cp $< $@
@@ -12,4 +12,10 @@ generate_golden::
clean:: clean::
rm -f c2 rm -f c2
include v0/include.mk include v0/include.mk
integration-test: v0/bin/c2 v0/bin/test_integration
./v0/bin/test_integration
v0/bin/test_integration: v0/test_integration.c
$(CC) $(CFLAGS) -o $@ $<
+16
View File
@@ -13,5 +13,21 @@ In order to run the tests, run `make test`.
## Versioning ## Versioning
The current version is v0. Its source code lives in the `v0` directory. The current version is v0. Its source code lives in the `v0` directory.
## Testing
### Unit Tests
Run unit tests with:
```bash
make test
```
### Integration Tests
Integration tests compare the compiler output with expected C files.
To add a new integration test, create a new directory under `v0/integration_tests/` with `input.c2` and `expected.c` files.
Run integration tests with:
```bash
make integration-test
```
## Languages Specifications ## Languages Specifications
See the specs directory for information on the actual language syntax. See the specs directory for information on the actual language syntax.
+1
View File
@@ -0,0 +1 @@
Hello, world
+24
View File
@@ -0,0 +1,24 @@
# Variables
Variables can be defined in the global scope, in structs and classes, and in functions.
## Global variables
Global variables can be defined as such:
```c2
// Defines a global variable called my_var.
i32 my_var;
// Defines a const variable.
const i32 my_var;
// Defines a global variable whose type is determined automatically.
// The value will be determined at runtime.
var my_var = 123;
// Defines a const variable whose type is determined automatically.
const my_var = 123;
// Defines a global variable whose initial value is computed at compile-time.
// If it cannot be computed at compile-time, an error is thrown.
static my_var = 123;
```
+7 -72
View File
@@ -4,83 +4,18 @@
#ifndef AST_H #ifndef AST_H
#define AST_H #define AST_H
#include "bool.h" #include "ast/expression.h"
#include "token.h" #include "ast/declaration.h"
#include "ast/module.h"
#include <stddef.h>
typedef struct {
/** @brief The name of the module being imported. */
const char* module_name;
/** @brief Whether the import is public or not. */
bool is_public;
} ImportDeclaration;
typedef enum {
TYPE_EXPRESSION_BUILTIN,
TYPE_EXPRESSION_ARRAY
} TypeExpressionTag;
/** /**
* An expression that evaluates to a type. * Frees a module and all its children.
*/ */
typedef struct TypeExpression TypeExpression; void ast_free_module(ModuleTree* module);
struct TypeExpression{
/** @brief defines which entry in the union is valid */
TypeExpressionTag tag;
union {
/** @brief Evaluates to an array of the given type. */
struct {
/** @brief A pointer to the type of the elements stored in the array. */
TypeExpression* array;
} array;
/** @brief Evaluates to a builtin integer type.*/
struct {
/**
* @brief The number of bits in the integer.
* Typical values are 8, 16, 32, and 64.
*/
int bitSize;
/** @brief `true` if the type is signed, `false` if it's unsigned. */
bool isSigned;
} builtin;
};
};
/** /**
* A declaration that aliases one type to another. * Frees a type expression.
*/ */
typedef struct { void ast_free_type(TypeTree* type);
/** @brief The name of the alias. */
const char* name;
/** @brief The value of the alias. */
TypeExpression value;
/** @brief Whether the import is public or not. */
bool is_public;
} AliasDeclaration;
/**
* The top-level model.
* Every file matches an entire Module.
*/
typedef struct {
/** @brief The name of the module. */
const char* name;
/** @brief The list of imports in the module. */
ImportDeclaration* imports;
/** @brief The number of imports in the module. */
size_t import_count;
/** @brief The list of aliases in the module. */
AliasDeclaration* aliases;
/** @brief The number of aliases in the module. */
size_t alias_count;
} Module;
#endif #endif
+49
View File
@@ -0,0 +1,49 @@
#ifndef AST_DECLARATION_H
#define AST_DECLARATION_H
#include "expression.h"
#include "../bool.h"
typedef struct {
/** @brief The name of the module being imported. */
char* module_name;
/** @brief Whether the import is public or not. */
bool is_public;
} ImportTree;
/**
* A declaration that aliases one type to another.
*/
typedef struct {
/** @brief The name of the alias. */
const char* name;
/** @brief The value of the alias. */
TypeTree value;
} AliasTree;
/**
* 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. */
TypeTree type;
/** @brief The optional initializer expression. */
ExpressionTree* initializer;
/** @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;
} VariableTree;
#endif
+9
View File
@@ -0,0 +1,9 @@
#include "expression.h"
#include <stdlib.h>
void ast_free_type(TypeTree* expr) {
if (expr->tag == TYPE_TREE_ARRAY) {
ast_free_type(expr->array.array);
free(expr->array.array);
}
}
+52
View File
@@ -0,0 +1,52 @@
#ifndef AST_EXPRESSION_H
#define AST_EXPRESSION_H
#include "../bool.h"
typedef enum {
EXPRESSION_TREE_INTEGER,
EXPRESSION_TREE_STRING,
EXPRESSION_TREE_BOOLEAN
} ExpressionTreeTag;
typedef struct {
ExpressionTreeTag tag;
union {
int integer;
const char* string;
bool boolean;
};
} ExpressionTree;
typedef enum {
TYPE_TREE_BUILTIN,
TYPE_TREE_ARRAY
} TypeTreeTag;
/**
* An expression that evaluates to a type.
*/
typedef struct TypeTree TypeTree;
struct TypeTree {
/** @brief defines which entry in the union is valid */
TypeTreeTag tag;
union {
/** @brief Evaluates to an array of the given type. */
struct {
/** @brief A pointer to the type of the elements stored in the array. */
TypeTree* array;
} array;
/** @brief Evaluates to a builtin integer type.*/
struct {
/**
* @brief The number of bits in the integer.
* Typical values are 8, 16, 32, and 64.
*/
int bitSize;
/** @brief `true` if the type is signed, `false` if it's unsigned. */
bool isSigned;
} builtin;
};
};
#endif
+3
View File
@@ -0,0 +1,3 @@
# There are currently no .c files in the ast directory.
# This file is provided for future consistency.
AST_SRC := v0/ast/module.c v0/ast/expression.c
+43
View File
@@ -0,0 +1,43 @@
#include "module.h"
#include "expression.h"
#include <stdlib.h>
void ast_free_type(TypeTree* type);
void ast_free_module(ModuleTree* module) {
if (module == NULL) {
return;
}
if (module->imports != NULL) {
for(size_t i = 0; i < module->import_count; i++) {
free(module->imports[i].module_name);
}
free(module->imports);
}
if (module->aliases != NULL) {
for(size_t i = 0; i < module->alias_count; i++) {
free((void*)module->aliases[i].name);
ast_free_type(&module->aliases[i].value);
}
free(module->aliases);
}
if (module->variables != NULL) {
for(size_t i = 0; i < module->variable_count; i++) {
free(module->variables[i].name);
ast_free_type(&module->variables[i].type);
if (module->variables[i].initializer) {
if (module->variables[i].initializer->tag == EXPRESSION_TREE_STRING) {
free((void*)module->variables[i].initializer->string);
}
free(module->variables[i].initializer);
}
}
free(module->variables);
}
free(module->name);
free(module);
}
+34
View File
@@ -0,0 +1,34 @@
#ifndef AST_MODULE_H
#define AST_MODULE_H
#include "declaration.h"
#include <stddef.h>
/**
* The top-level model.
* Every file matches an entire Module.
*/
typedef struct {
/** @brief The name of the module. */
char* name;
/** @brief The list of imports in the module. */
ImportTree* imports;
/** @brief The number of imports in the module. */
size_t import_count;
/** @brief The list of aliases in the module. */
AliasTree* aliases;
/** @brief The number of aliases in the module. */
size_t alias_count;
/** @brief The list of variables in the module. */
VariableTree* variables;
/** @brief The number of variables in the module. */
size_t variable_count;
} ModuleTree;
#endif
+5 -2
View File
@@ -1,4 +1,7 @@
V0_SRC := v0/main.c v0/util.c v0/token.c v0/parser.c v0/log.c v0/str.c include v0/ast/include.mk
include v0/parser/include.mk
V0_SRC := v0/main.c v0/util.c v0/token.c $(AST_SRC) $(PARSER_SRC) v0/log.c v0/str.c
# V0_TEST must only include `v0/test.c` itself, as all other test Csource files are # V0_TEST must only include `v0/test.c` itself, as all other test Csource files are
# included directly into `v0/test.c` using `#include "test_xyz.c"`. # included directly into `v0/test.c` using `#include "test_xyz.c"`.
@@ -11,7 +14,7 @@ V0_TEST_OBJ := $(patsubst v0/%.c,v0/bin/%.o,$(V0_TEST))
V0_SRC_DEPS := $(V0_SRC_OBJ:.o=.d) V0_SRC_DEPS := $(V0_SRC_OBJ:.o=.d)
V0_TEST_DEPS := $(V0_TEST_OBJ:.o=.d) V0_TEST_DEPS := $(V0_TEST_OBJ:.o=.d)
CFLAGS += -Werror -Wall -pedantic -std=c11 CFLAGS += -Werror -Wall -pedantic -std=c11 -g
v0/bin/c2: $(V0_SRC_OBJ) v0/bin/c2: $(V0_SRC_OBJ)
$(CC) $(CFLAGS) -o $@ $^ $(CC) $(CFLAGS) -o $@ $^
+4
View File
@@ -0,0 +1,4 @@
#include <stdint.h>
// u32 simple:x
static uint32_t v_6simple_1x = 123;
+2
View File
@@ -0,0 +1,2 @@
module simple;
u32 x = 123;
-284
View File
@@ -1,284 +0,0 @@
#include "parser.h"
#include "log.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
// /**
// * Parses an import declaration.
// *
// * @param ts The token stream to parse from.
// * @param module The module being parsed.
// * @returns true on success, false on failure.
// */
// static bool parse_import(TokenStream* ts, Module* module) {
// ImportDeclaration* new_imports = realloc(module->imports, (module->import_count + 1) * sizeof(ImportDeclaration));
// if (!new_imports) {
// fprintf(stderr, "Out of memory\n");
// exit(1);
// }
// module->imports = new_imports;
// Token t = tokenstream_next(ts);
// bool is_public = false;
// if (t.token == TOKEN_PUBLIC) {
// is_public = true;
// t = tokenstream_next(ts);
// }
// if (t.token != TOKEN_IDENTIFIER) {
// log_on_line(&t.location, t.location.column_end, "expected module name to import");
// return false;
// }
// char* name = (char*)malloc(t.text.length + 1);
// memcpy(name, t.text.data, t.text.length);
// name[t.text.length] = '\0';
// module->imports[module->import_count] = (ImportDeclaration){ .module_name = name, .is_public = is_public };
// module->import_count++;
// t = tokenstream_next(ts);
// if (t.token != TOKEN_SEMICOLON) {
// log_on_line(&t.location, t.location.column_end, "expected ';' after import");
// return false;
// }
// return true;
// }
// /**
// * Parses an alias declaration.
// *
// * @param ts The token stream to parse from.
// * @param module The module being parsed.
// * @returns true on success, false on failure.
// */
// static bool parse_alias(TokenStream* ts, Module* module) {
// AliasDeclaration* new_aliases = realloc(module->aliases, (module->alias_count + 1) * sizeof(AliasDeclaration));
// if (!new_aliases) {
// fprintf(stderr, "Out of memory\n");
// exit(1);
// }
// module->aliases = new_aliases;
// Token t = tokenstream_next(ts);
// if (t.token != TOKEN_IDENTIFIER) {
// log_on_line(&t.location, t.location.column_end, "expected alias name");
// return false;
// }
// char* name = (char*)malloc(t.text.length + 1);
// memcpy(name, t.text.data, t.text.length);
// name[t.text.length] = '\0';
// AliasDeclaration alias;
// alias.name = name;
// t = tokenstream_next(ts);
// if (t.token != TOKEN_ASSIGN) {
// log_on_line(&t.location, t.location.column_end, "expected '='");
// return false;
// }
// t = tokenstream_next(ts);
// TypeExpression type;
// if (t.token == TOKEN_IDENTIFIER && strncmp(t.text.data, "int32", t.text.length) == 0) {
// type = (TypeExpression){ .tag = TYPE_EXPRESSION_BUILTIN, .builtin = { .bitSize = 32, .isSigned = true } };
// t = tokenstream_next(ts);
// if (t.token == TOKEN_BRACKET_OPEN) {
// t = tokenstream_next(ts);
// if (t.token != TOKEN_BRACKET_CLOSE) {
// log_on_line(&t.location, t.location.column_end, "expected ']'");
// return false;
// }
// TypeExpression* inner = malloc(sizeof(TypeExpression));
// *inner = type;
// type = (TypeExpression){ .tag = TYPE_EXPRESSION_ARRAY, .array = { .array = inner } };
// t = tokenstream_next(ts);
// }
// } else {
// log_on_line(&t.location, t.location.column_end, "expected type");
// return false;
// }
// alias.value = type;
// module->aliases[module->alias_count] = alias;
// module->alias_count++;
// if (t.token != TOKEN_SEMICOLON) {
// log_on_line(&t.location, t.location.column_end, "expected ';'");
// return false;
// }
// return true;
// }
typedef struct {
TokenStream* ts;
Token token;
} Parser;
/**
* Reads a new token into p->token.
*/
static void parser_next_token(Parser* p) {
p->token = tokenstream_next(p->ts);
}
/**
* Reads a new token if the current token is equal to the expected token.
*
* If they are equal, it continues to the next token.
*
* @param p
* @param token The expected token.
* @returns `true` if the current token matches the expected, `false` if it does not.
*/
static bool parser_accept(Parser* p, TokenType token) {
if (p->token.token == token) {
parser_next_token(p);
return true;
}
return false;
}
/**
* @copilot todo
*/
static bool parser_expect(Parser* p, TokenType token, const char* msg) {
if (parser_accept(p, token)) {
return true;
}
log_on_line(&p->token.location, msg);
return false;
}
/**
* @copilot todo add docs
*/
static bool parser_peek(Parser* p, TokenType token) {
if (p->token.token == token) {
return true;
}
return false;
}
/**
* @copilot todo add docs
*/
static bool parser_require(Parser* p, TokenType token, const char* msg) {
if (parser_peek(p, token)) {
return true;
}
log_on_line(&p->token.location, msg);
return false;
}
/**
* Converts the current token to a string.
* @copilot add proper docs
*/
static char* parser_to_text(Parser* p) {
char* str = string_copy(p->token.text);
parser_next_token(p);
return str;
}
/**
* Parses the "module" keyword
*/
static bool parse_declaration_module(Parser* p, Module* module) {
if (!parser_expect(p, TOKEN_MODULE, "expected keyword 'module'")) {
return false;
}
if (!parser_require(p, TOKEN_IDENTIFIER, "expected module identifier")) {
return false;
}
module->name = parser_to_text(p);
return parser_expect(p, TOKEN_SEMICOLON, "expected ';' after module name");
}
static bool parse_import(Parser* p, Module* module, bool is_public) {
module->import_count++;
module->imports = realloc(module->imports, sizeof(ImportDeclaration) * module->import_count);
ImportDeclaration* import = &module->imports[module->import_count - 1];
if (!parser_expect(p, TOKEN_IDENTIFIER, "expected module identifier")) {
return false;
}
import->module_name = parser_to_text(p);
import->is_public = is_public;
return true;
}
static bool parse_alias(Parser* p, Module* module, bool is_public) {
// @copilot implement
return true;
}
Module* parser_parse(TokenStream* ts) {
Parser* p = malloc(sizeof(Parser));
p->ts = ts;
parser_next_token(p);
Module* module = malloc(sizeof(Module));
if (!parse_declaration_module(p, module)) {
goto fail;
}
while (!parser_peek(p, TOKEN_EOF)) {
bool is_public = false;
bool terminal = false;
do {
if (parser_accept(p, TOKEN_IMPORT)) {
if (!parse_import(p, module, is_public)) {
goto fail;
}
terminal = true;
} else if (parser_accept(p, TOKEN_ALIAS)) {
if (!parse_alias(p, module, is_public)) {
goto fail;
}
terminal = true;
} else if (parser_accept(p, TOKEN_PUBLIC)) {
is_public = true;
} else {
log_on_line(&p->token.location, "unexpected token");
goto fail;
}
} while (!terminal);
}
return module;
fail:
free(module);
return NULL;
}
void free_type_expression(TypeExpression* expr) {
if (expr->tag == TYPE_EXPRESSION_ARRAY) {
free_type_expression(expr->array.array);
free(expr->array.array);
}
}
void parser_free(Module* module) {
if (module == NULL) return;
if (module->imports != NULL) {
for(size_t i = 0; i < module->import_count; i++) {
free((void*)module->imports[i].module_name);
}
free(module->imports);
}
if (module->aliases != NULL) {
for(size_t i = 0; i < module->alias_count; i++) {
free((void*)module->aliases[i].name);
free_type_expression(&module->aliases[i].value);
}
free(module->aliases);
}
free((void*)module->name);
free(module);
}
+1 -8
View File
@@ -10,13 +10,6 @@
* @param ts The TokenStream to read. * @param ts The TokenStream to read.
* @returns The parsed module. * @returns The parsed module.
*/ */
Module* parser_parse(TokenStream* ts); ModuleTree* parser_parse(TokenStream* ts);
/**
* Frees the parsed AST.
*
* @param module The AST return by parser_parse.
*/
void parser_free(Module* module);
#endif #endif
+52
View File
@@ -0,0 +1,52 @@
#include "internal.h"
#include "../str.h"
#include "../log.h"
#include <stdlib.h>
void parser_next_token(Parser* p) {
p->token = tokenstream_next(p->ts);
}
bool parser_accept(Parser* p, TokenType token) {
if (p->token.token == token) {
parser_next_token(p);
return true;
}
return false;
}
bool parser_expect(Parser* p, TokenType token, const char* msg) {
if (parser_accept(p, token)) {
return true;
}
log_on_line(&p->token.location, msg);
return false;
}
bool parser_peek(Parser* p, TokenType token) {
if (p->token.token == token) {
return true;
}
return false;
}
bool parser_require(Parser* p, TokenType token, const char* msg) {
if (parser_peek(p, token)) {
return true;
}
log_on_line(&p->token.location, msg);
return false;
}
char* parser_to_text(Parser* p) {
char* str = string_copy(p->token.text);
parser_next_token(p);
return str;
}
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);
}
+87
View File
@@ -0,0 +1,87 @@
#include "internal.h"
#include <stdlib.h>
#include <string.h>
bool parse_import_declaration(Parser* p, ModuleTree* module, bool is_public) {
module->import_count++;
module->imports = realloc(module->imports, sizeof(ImportTree) * module->import_count);
ImportTree* import = &module->imports[module->import_count - 1];
memset(import, 0, sizeof(ImportTree));
import->is_public = is_public;
if (!parser_require(p, TOKEN_IDENTIFIER, "expected module identifier")) {
return false;
}
import->module_name = parser_to_text(p);
if (!parser_expect(p, TOKEN_SEMICOLON, "expected ';' after import")) {
return false;
}
return true;
}
bool parse_alias_declaration(Parser* p, ModuleTree* module, bool is_public) {
(void)is_public;
module->alias_count++;
module->aliases = realloc(module->aliases, sizeof(AliasTree) * module->alias_count);
AliasTree* alias = &module->aliases[module->alias_count - 1];
memset(alias, 0, sizeof(AliasTree));
if (!parser_require(p, TOKEN_IDENTIFIER, "expected alias identifier")) {
return false;
}
alias->name = parser_to_text(p);
if (!parser_expect(p, TOKEN_ASSIGN, "expected '=' after alias name")) {
return false;
}
if (!parse_type_expression(p, &alias->value)) {
return false;
}
if (!parser_expect(p, TOKEN_SEMICOLON, "expected ';' after alias declaration")) {
return false;
}
return true;
}
bool parse_variable_declaration(Parser* p, ModuleTree* module, bool is_public, bool is_static, bool is_const) {
module->variable_count++;
module->variables = realloc(module->variables, sizeof(VariableTree) * module->variable_count);
VariableTree* var = &module->variables[module->variable_count - 1];
memset(var, 0, sizeof(VariableTree));
var->is_public = is_public;
var->is_static = is_static;
var->is_const = is_const;
if (parser_accept_primitive(p)) {
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_accept(p, TOKEN_ASSIGN)) {
var->initializer = malloc(sizeof(ExpressionTree));
if (!parse_expression(p, var->initializer)) {
return false;
}
}
if (!parser_expect(p, TOKEN_SEMICOLON, "expected ';' after variable declaration")) {
return false;
}
return true;
}
+98
View File
@@ -0,0 +1,98 @@
#include "internal.h"
#include "../log.h"
#include <stdlib.h>
bool parse_primitive_type_expression(Parser* p, TypeTree* expr) {
if (parser_accept(p, TOKEN_U8)) {
expr->tag = TYPE_TREE_BUILTIN;
expr->builtin.bitSize = 8;
expr->builtin.isSigned = false;
return true;
} else if (parser_accept(p, TOKEN_U16)) {
expr->tag = TYPE_TREE_BUILTIN;
expr->builtin.bitSize = 16;
expr->builtin.isSigned = false;
return true;
} else if (parser_accept(p, TOKEN_U32)) {
expr->tag = TYPE_TREE_BUILTIN;
expr->builtin.bitSize = 32;
expr->builtin.isSigned = false;
return true;
} else if (parser_accept(p, TOKEN_U64)) {
expr->tag = TYPE_TREE_BUILTIN;
expr->builtin.bitSize = 64;
expr->builtin.isSigned = false;
return true;
} else if (parser_accept(p, TOKEN_I8)) {
expr->tag = TYPE_TREE_BUILTIN;
expr->builtin.bitSize = 8;
expr->builtin.isSigned = true;
return true;
} else if (parser_accept(p, TOKEN_I16)) {
expr->tag = TYPE_TREE_BUILTIN;
expr->builtin.bitSize = 16;
expr->builtin.isSigned = true;
return true;
} else if (parser_accept(p, TOKEN_I32)) {
expr->tag = TYPE_TREE_BUILTIN;
expr->builtin.bitSize = 32;
expr->builtin.isSigned = true;
return true;
} else if (parser_accept(p, TOKEN_I64)) {
expr->tag = TYPE_TREE_BUILTIN;
expr->builtin.bitSize = 64;
expr->builtin.isSigned = true;
return true;
} else {
log_on_line(&p->token.location, "expected type expression");
return false;
}
}
bool parse_array_type_expression(Parser* p, TypeTree* expr) {
TypeTree elementType;
if (!parse_primitive_type_expression(p, &elementType)) {
return false;
}
if (parser_accept(p, TOKEN_BRACKET_OPEN)) {
expr->tag = TYPE_TREE_ARRAY;
expr->array.array = malloc(sizeof(TypeTree));
*expr->array.array = elementType;
if (!parser_expect(p, TOKEN_BRACKET_CLOSE, "expected ']' to end array type")) {
return false;
}
} else {
*expr = elementType;
return true;
}
return true;
}
bool parse_type_expression(Parser* p, TypeTree* expr) {
return parse_array_type_expression(p, expr);
}
bool parse_expression(Parser* p, ExpressionTree* expr) {
if (parser_peek(p, TOKEN_INTEGER)) {
expr->tag = EXPRESSION_TREE_INTEGER;
expr->integer = atoi(p->token.text.data);
parser_next_token(p);
return true;
} else if (parser_peek(p, TOKEN_STRING)) {
expr->tag = EXPRESSION_TREE_STRING;
expr->string = parser_to_text(p);
return true;
} else if (parser_accept(p, TOKEN_TRUE)) {
expr->tag = EXPRESSION_TREE_BOOLEAN;
expr->boolean = true;
return true;
} else if (parser_accept(p, TOKEN_FALSE)) {
expr->tag = EXPRESSION_TREE_BOOLEAN;
expr->boolean = false;
return true;
}
log_on_line(&p->token.location, "expected expression");
return false;
}
+1
View File
@@ -0,0 +1 @@
PARSER_SRC := v0/parser/core.c v0/parser/expression.c v0/parser/declaration.c v0/parser/module.c
+36
View File
@@ -0,0 +1,36 @@
#ifndef PARSER_INTERNAL_H
#define PARSER_INTERNAL_H
#include "../parser.h"
#include "../token.h"
#include "../ast.h"
typedef struct {
TokenStream* ts;
Token token;
} Parser;
// Core functions
void parser_next_token(Parser* p);
bool parser_accept(Parser* p, TokenType token);
bool parser_expect(Parser* p, TokenType token, const char* msg);
bool parser_peek(Parser* p, TokenType token);
bool parser_require(Parser* p, TokenType token, const char* msg);
char* parser_to_text(Parser* p);
bool parser_accept_primitive(Parser* p);
// Base parsing (expressions, types)
bool parse_primitive_type_expression(Parser* p, TypeTree* expr);
bool parse_array_type_expression(Parser* p, TypeTree* expr);
bool parse_type_expression(Parser* p, TypeTree* expr);
bool parse_expression(Parser* p, ExpressionTree* expr);
// Declaration parsing
bool parse_import_declaration(Parser* p, ModuleTree* module, bool is_public);
bool parse_alias_declaration(Parser* p, ModuleTree* module, bool is_public);
bool parse_variable_declaration(Parser* p, ModuleTree* module, bool is_public, bool is_static, bool is_const);
// Module parsing
bool parse_module_declaration(Parser* p, ModuleTree* module);
#endif
+87
View File
@@ -0,0 +1,87 @@
#include "internal.h"
#include "../log.h"
#include <stdlib.h>
#include <string.h>
bool parse_module_declaration(Parser* p, ModuleTree* module) {
if (!parser_expect(p, TOKEN_MODULE, "expected keyword 'module'")) {
return false;
}
if (!parser_require(p, TOKEN_IDENTIFIER, "expected module identifier")) {
return false;
}
module->name = parser_to_text(p);
return parser_expect(p, TOKEN_SEMICOLON, "expected ';' after module name");
}
ModuleTree* parser_parse(TokenStream* ts) {
Parser* p = malloc(sizeof(Parser));
p->ts = ts;
parser_next_token(p);
ModuleTree* module = malloc(sizeof(ModuleTree));
memset(module, 0, sizeof(ModuleTree));
if (!parse_module_declaration(p, module)) {
goto fail;
}
while (!parser_peek(p, TOKEN_EOF)) {
bool is_public = false;
bool is_static = false;
bool is_const = false;
bool terminal = false;
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(p, TOKEN_VAR) || 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;
}
}
}
free(p);
return module;
fail:
free(p);
ast_free_module(module);
return NULL;
}
+8
View File
@@ -0,0 +1,8 @@
#include "../test.h"
#include "../parser.h"
// Currently core utilities are tested indirectly through other parser tests.
// Placeholder for future explicit core utility tests.
static void test_parser_core_placeholder(void) {
// No-op
}
+89
View File
@@ -0,0 +1,89 @@
#include "../test.h"
#include "../parser.h"
#include <string.h>
#include <stdlib.h>
static void test_parser_missing_semicolon_import(void) {
test_get_ast();
assert_log_file("expected error for missing semicolon");
}
static void test_parser_bad_import_name(void) {
test_get_ast();
assert_log_file("expected error for bad import name");
}
static void test_parser_imports(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_str("my_module", m->name, "expected name 'my_module'");
assert_not_null(m->imports, "expected imports to be parsed");
assert_int(1, (int)m->import_count, "expected one import");
assert_str("other_module", m->imports[0].module_name, "expected import name 'other_module'");
assert_false(m->imports[0].is_public, "expected import to not be public");
}
static void test_parser_public_imports(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_str("my_module", m->name, "expected name 'my_module'");
assert_not_null(m->imports, "expected imports to be parsed");
assert_int(1, (int)m->import_count, "expected one import");
assert_str("other_module", m->imports[0].module_name, "expected import name 'other_module'");
assert_true(m->imports[0].is_public, "expected import to be public");
}
static void test_parser_alias_simple(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->alias_count, "expected correct number of aliases");
AliasTree alias = m->aliases[0];
assert_str("myalias", alias.name, "expected correct alias name");
}
static void test_parser_variable_simple(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->variable_count, "expected correct number of variables");
VariableTree var = m->variables[0];
assert_str("my_var", var.name, "expected correct variable name");
assert_false(var.is_const, "expected not const");
assert_false(var.is_static, "expected not static");
}
static void test_parser_variable_const(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->variable_count, "expected correct number of variables");
VariableTree var = m->variables[0];
assert_str("my_const", var.name, "expected correct variable name");
assert_true(var.is_const, "expected const");
assert_false(var.is_static, "expected not static");
}
static void test_parser_variable_static(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->variable_count, "expected correct number of variables");
VariableTree var = m->variables[0];
assert_str("my_static", var.name, "expected correct variable name");
assert_false(var.is_const, "expected not const");
assert_true(var.is_static, "expected static");
}
static void test_parser_multiple_vars(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(2, (int)m->variable_count, "expected correct number of variables");
assert_str("var1", m->variables[0].name, "expected first variable name 'var1'");
assert_str("var2", m->variables[1].name, "expected second variable name 'var2'");
}
+52
View File
@@ -0,0 +1,52 @@
#include "../test.h"
#include "../parser.h"
#include <string.h>
#include <stdlib.h>
static void test_parser_alias_simple_type(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->alias_count, "expected correct number of aliases");
AliasTree alias = m->aliases[0];
assert_int(TYPE_TREE_BUILTIN, alias.value.tag, "expected correct alias tag");
assert_int(32, alias.value.builtin.bitSize, "expected bitSize 32");
assert_true(alias.value.builtin.isSigned, "expected signed");
}
static void test_parser_alias_array(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->alias_count, "expected correct number of aliases");
AliasTree alias = m->aliases[0];
assert_int(TYPE_TREE_ARRAY, alias.value.tag, "expected correct alias tag");
TypeTree* valueType = alias.value.array.array;
assert_not_null(valueType, "expected pointer to array type");
assert_int(TYPE_TREE_BUILTIN, valueType->tag, "expected correct type tag");
assert_int(32, valueType->builtin.bitSize, "expected bitSize 32");
assert_true(valueType->builtin.isSigned, "expected signed");
}
static void test_parser_variable_init(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->variable_count, "expected 1 variable");
VariableTree* var = &m->variables[0];
assert_str("x", var->name, "expected variable name 'x'");
assert_not_null(var->initializer, "expected variable to have an initializer");
assert_int(EXPRESSION_TREE_INTEGER, var->initializer->tag, "expected integer initializer");
assert_int(123, var->initializer->integer, "expected value 123");
}
static void test_parser_variable_simple_type(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->variable_count, "expected correct number of variables");
VariableTree var = m->variables[0];
assert_int(TYPE_TREE_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");
}
+21
View File
@@ -0,0 +1,21 @@
#include "../test.h"
#include "../parser.h"
#include <string.h>
#include <stdlib.h>
static void test_parser_module_name(void) {
ModuleTree* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_str("my_module", m->name, "expected name 'my_module'");
}
static void test_parser_bad_module_name(void) {
test_get_ast();
assert_log_file("expected error to be logged for bad module name");
}
static void test_parser_missing_semicolon_module(void) {
test_get_ast();
assert_log_file("expected error for missing semicolon");
}
+46 -19
View File
@@ -9,16 +9,21 @@
#include <stdlib.h> #include <stdlib.h>
static jmp_buf s_testJmp; static jmp_buf s_testJmp;
static const char* s_failMsg; static char s_failMsg[1024];
static char* s_logOutput = NULL; static char* s_logOutput = NULL;
static const char* s_currentTestName = NULL; static const char* s_currentTestName = NULL;
static char* s_testSource = NULL; static char* s_testSource = NULL;
static Module* s_currentModule = NULL; static ModuleTree* s_currentModule = NULL;
static TokenStream* s_currentTokenStream = NULL; static TokenStream* s_currentTokenStream = NULL;
void fail(const char* msg) { void fail(const char* msg) {
s_failMsg = msg; if (msg) {
strncpy(s_failMsg, msg, sizeof(s_failMsg) - 1);
s_failMsg[sizeof(s_failMsg) - 1] = '\0';
} else {
s_failMsg[0] = '\0';
}
longjmp(s_testJmp, 1); longjmp(s_testJmp, 1);
} }
@@ -75,8 +80,8 @@ TokenStream* test_get_tokenstream(void) {
s_testSource = read_file_content(filepath); s_testSource = read_file_content(filepath);
if (!s_testSource) { if (!s_testSource) {
puts(filepath); puts(filepath);
fail("could not read test source file");
free(filepath); free(filepath);
fail("could not read test source file");
return NULL; return NULL;
} }
s_currentTokenStream = tokenstream_open(filepath, s_testSource); s_currentTokenStream = tokenstream_open(filepath, s_testSource);
@@ -85,7 +90,7 @@ TokenStream* test_get_tokenstream(void) {
return s_currentTokenStream; return s_currentTokenStream;
} }
Module* test_get_ast(void) { ModuleTree* test_get_ast(void) {
if (s_currentModule == NULL) { if (s_currentModule == NULL) {
s_currentModule = parser_parse(test_get_tokenstream()); s_currentModule = parser_parse(test_get_tokenstream());
} }
@@ -108,24 +113,29 @@ void assert_log_file(const char* msg) {
if (generate && strcmp(generate, "1") == 0) { if (generate && strcmp(generate, "1") == 0) {
FILE* f = fopen(filepath, "w"); FILE* f = fopen(filepath, "w");
if (!f) { if (!f) {
free(filepath);
fail("could not open golden file for writing"); fail("could not open golden file for writing");
return; return;
} }
fputs(s_logOutput ? s_logOutput : "", f); fputs(s_logOutput ? s_logOutput : "", f);
fclose(f); fclose(f);
free(filepath);
return; return;
} }
content = read_file_content(filepath); content = read_file_content(filepath);
if (!content) { if (!content) {
fail("could not open golden file for reading");
free(filepath); free(filepath);
fail("could not open golden file for reading");
return; return;
} }
assert_str(content, s_logOutput, msg); bool match = strcmp(content, s_logOutput ? s_logOutput : "") == 0;
free(content); free(content);
free(filepath); free(filepath);
if (!match) {
fail(msg);
}
} }
void assert_int(int expected, int actual, const char* msg) { void assert_int(int expected, int actual, const char* msg) {
@@ -179,7 +189,10 @@ typedef struct {
} TestCase; } TestCase;
#include "test_token.c" #include "test_token.c"
#include "test_parser.c" #include "parser/test_module.c"
#include "parser/test_declaration.c"
#include "parser/test_expression.c"
#include "parser/test_core.c"
#include "test_log.c" #include "test_log.c"
static int s_totalTests; static int s_totalTests;
@@ -191,21 +204,29 @@ 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_simple)
TEST(test_parser_bad_import_name)
TEST(test_parser_bad_module_name)
TEST(test_parser_imports)
TEST(test_parser_missing_semicolon_import)
TEST(test_parser_missing_semicolon_module)
TEST(test_parser_module_name) TEST(test_parser_module_name)
TEST(test_parser_bad_module_name)
TEST(test_parser_missing_semicolon_module)
TEST(test_parser_missing_semicolon_import)
TEST(test_parser_bad_import_name)
TEST(test_parser_imports)
TEST(test_parser_public_imports) TEST(test_parser_public_imports)
TEST(test_parser_alias_simple)
TEST(test_parser_alias_simple_type)
TEST(test_parser_alias_array)
TEST(test_parser_variable_simple)
TEST(test_parser_variable_simple_type)
TEST(test_parser_variable_const)
TEST(test_parser_variable_init)
TEST(test_parser_variable_static)
TEST(test_parser_multiple_vars)
TEST(test_parser_core_placeholder)
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)
TEST(test_tokenstream_open_fail) TEST(test_tokenstream_open_fail)
TEST(test_tokenstream_parentheses_and_brackets) TEST(test_tokenstream_parentheses_and_brackets)
TEST(test_tokenstream_primitive_types)
TEST(test_tokenstream_simple_keyword) TEST(test_tokenstream_simple_keyword)
TEST(test_tokenstream_unknown_token) TEST(test_tokenstream_unknown_token)
TEST(test_tokenstream_void_function_signature) TEST(test_tokenstream_void_function_signature)
@@ -233,7 +254,7 @@ int main(int argc, char** argv) {
log_set_output(log_append); log_set_output(log_append);
printf("%s...", s_tests[i].name); printf("%s...", s_tests[i].name);
fflush(stdout); fflush(stdout);
s_failMsg = NULL; s_failMsg[0] = '\0';
if (setjmp(s_testJmp) == 0) { if (setjmp(s_testJmp) == 0) {
log_clear(); log_clear();
@@ -245,12 +266,18 @@ int main(int argc, char** argv) {
printf(" [OK]\n"); printf(" [OK]\n");
s_greenTests++; s_greenTests++;
} else { } else {
printf(" [FAIL]: %s\n", s_failMsg ? s_failMsg : ""); printf(" [FAIL]: %s\n", s_failMsg[0] ? s_failMsg : "");
failedTests[failedCount++] = s_tests[i].name; failedTests[failedCount++] = s_tests[i].name;
// Log output on failure
if (s_logOutput && s_logOutput[0]) {
printf("%s\n", s_logOutput);
}
} }
// Free AST and TokenStream after each test
if (s_currentModule) { if (s_currentModule) {
parser_free(s_currentModule); ast_free_module(s_currentModule);
s_currentModule = NULL; s_currentModule = NULL;
} }
if (s_currentTokenStream) { if (s_currentTokenStream) {
+1 -1
View File
@@ -89,6 +89,6 @@ TokenStream* test_get_tokenstream(void);
* *
* At the end of the test, the AST will be freed automatically by the test harness. * At the end of the test, the AST will be freed automatically by the test harness.
*/ */
Module* test_get_ast(void); ModuleTree* test_get_ast(void);
#endif #endif
+63
View File
@@ -0,0 +1,63 @@
#define _DEFAULT_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
int run_test(const char* dir_name) {
char cmd[2048];
char input_path[1024];
char expected_path[1024];
snprintf(input_path, sizeof(input_path), "v0/integration_tests/%s/input.c2", dir_name);
snprintf(expected_path, sizeof(expected_path), "v0/integration_tests/%s/expected.c", dir_name);
if (snprintf(cmd, sizeof(cmd), "./v0/bin/c2 %s > actual.c", input_path) >= sizeof(cmd)) {
printf("Command buffer too small for %s\n", dir_name);
return 1;
}
if (system(cmd) != 0) {
printf("Failed to run compiler for %s\n", dir_name);
return 1;
}
if (snprintf(cmd, sizeof(cmd), "diff -u %s actual.c", expected_path) >= sizeof(cmd)) {
printf("Command buffer too small for %s\n", dir_name);
return 1;
}
if (system(cmd) != 0) {
printf("Test %s failed: Output mismatch\n", dir_name);
return 1;
}
printf("Test %s passed\n", dir_name);
return 0;
}
int main() {
DIR* d = opendir("v0/integration_tests");
if (!d) {
perror("opendir");
return 1;
}
struct dirent* dir;
int passed = 0;
int failed = 0;
while ((dir = readdir(d)) != NULL) {
if (dir->d_type == DT_DIR && strcmp(dir->d_name, ".") != 0 && strcmp(dir->d_name, "..") != 0) {
if (run_test(dir->d_name) == 0) {
passed++;
} else {
failed++;
}
}
}
closedir(d);
printf("\nTotal tests: %d, Passed: %d, Failed: %d\n", passed + failed, passed, failed);
return failed > 0 ? 1 : 0;
}
-95
View File
@@ -1,95 +0,0 @@
#include "test.h"
#include "parser.h"
#include <string.h>
#include <stdlib.h>
static void test_parser_module_name(void) {
Module* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_str("my_module", m->name, "expected name 'my_module'");
}
static void test_parser_bad_module_name(void) {
test_get_ast();
assert_log_file("expected error to be logged for bad module name");
}
static void test_parser_missing_semicolon_module(void) {
test_get_ast();
assert_log_file("expected error for missing semicolon");
}
static void test_parser_missing_semicolon_import(void) {
test_get_ast();
assert_log_file("expected error for missing semicolon");
}
static void test_parser_bad_import_name(void) {
test_get_ast();
assert_log_file("expected error for bad import name");
}
static void test_parser_imports(void) {
Module* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_str("my_module", m->name, "expected name 'my_module'");
assert_not_null(m->imports, "expected imports to be parsed");
assert_int(1, (int)m->import_count, "expected one import");
assert_str("other_module", m->imports[0].module_name, "expected import name 'other_module'");
assert_false(m->imports[0].is_public, "expected import to not be public");
}
static void test_parser_public_imports(void) {
Module* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_str("my_module", m->name, "expected name 'my_module'");
assert_not_null(m->imports, "expected imports to be parsed");
assert_int(1, (int)m->import_count, "expected one import");
assert_str("other_module", m->imports[0].module_name, "expected import name 'other_module'");
assert_true(m->imports[0].is_public, "expected import to be public");
}
static void test_parser_alias_simple(void) {
Module* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->alias_count, "expected correct number of aliases");
AliasDeclaration alias = m->aliases[0];
assert_str("myalias", alias.name, "expected correct alias name");
assert_int(TYPE_EXPRESSION_BUILTIN, alias.value.tag, "expected correct alias tag");
assert_int(32, alias.value.builtin.bitSize, "expected bitSize 32");
assert_true(alias.value.builtin.isSigned, "expected signed");
}
static void test_parser_alias_array(void) {
Module* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_int(1, (int)m->alias_count, "expected correct number of aliases");
AliasDeclaration alias = m->aliases[0];
assert_str("myalias", alias.name, "expected correct alias name");
assert_int(TYPE_EXPRESSION_ARRAY, alias.value.tag, "expected correct alias tag");
TypeExpression* valueType = alias.value.array.array;
assert_not_null(valueType, "expected pointer to array type");
assert_int(TYPE_EXPRESSION_BUILTIN, valueType->tag, "expected correct type tag");
assert_int(32, valueType->builtin.bitSize, "expected bitSize 32");
assert_true(valueType->builtin.isSigned, "expected signed");
}
static void test_parser_alias_and_import_mix(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'");
}
+14
View File
@@ -97,3 +97,17 @@ static void test_tokenstream_info(void) {
if (t2.location.line != 1) fail("expected line 1"); if (t2.location.line != 1) fail("expected line 1");
if (t2.location.column_start != 8) fail("expected column 8"); if (t2.location.column_start != 8) fail("expected column 8");
} }
static void test_tokenstream_primitive_types(void) {
TokenStream* ts = test_get_tokenstream();
if (tokenstream_next(ts).token != TOKEN_I8) fail("expected TOKEN_I8");
if (tokenstream_next(ts).token != TOKEN_I16) fail("expected TOKEN_I16");
if (tokenstream_next(ts).token != TOKEN_I32) fail("expected TOKEN_I32");
if (tokenstream_next(ts).token != TOKEN_I64) fail("expected TOKEN_I64");
if (tokenstream_next(ts).token != TOKEN_U8) fail("expected TOKEN_U8");
if (tokenstream_next(ts).token != TOKEN_U16) fail("expected TOKEN_U16");
if (tokenstream_next(ts).token != TOKEN_U32) fail("expected TOKEN_U32");
if (tokenstream_next(ts).token != TOKEN_U64) fail("expected TOKEN_U64");
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
}
+2 -2
View File
@@ -2,8 +2,8 @@ module mymodule;
import foo; import foo;
alias myalias = int32[]; alias myalias = i32[];
import bar; import bar;
alias otheralias = int32; alias otheralias = i32;
+1 -1
View File
@@ -1,3 +1,3 @@
module mymodule; module mymodule;
alias myalias = int32[]; alias myalias = i32[];
+1 -1
View File
@@ -1,3 +1,3 @@
module mymodule; module mymodule;
alias myalias = int32; alias myalias = i32;
+3
View File
@@ -0,0 +1,3 @@
module mymodule;
alias myalias = i32;
+1
View File
@@ -1 +1,2 @@
module mymodule;
import ; import ;
+3 -3
View File
@@ -1,4 +1,4 @@
--- v0/tests/parser_bad_import_name.c2 --- --- v0/tests/parser_bad_import_name.c2 ---
1| import ; 2| import ;
^^^^^^ ^
expected 'module' keyword expected module identifier
+1 -1
View File
@@ -1,4 +1,4 @@
--- v0/tests/parser_bad_module_name.c2 --- --- v0/tests/parser_bad_module_name.c2 ---
1| import other_module; 1| import other_module;
^^^^^^ ^^^^^^
expected 'module' keyword expected keyword 'module'
+4
View File
@@ -0,0 +1,4 @@
module test_multiple_vars;
i32 var1;
i32 var2;
+3
View File
@@ -0,0 +1,3 @@
module test_const_var;
const i32 my_const;
+2
View File
@@ -0,0 +1,2 @@
module mymodule;
var x = 123;
+4
View File
@@ -0,0 +1,4 @@
module my_module;
// Defines a global variable called my_var.
i32 my_var;
+4
View File
@@ -0,0 +1,4 @@
module my_module;
// Defines a global variable called my_var.
i32 my_var;
+3
View File
@@ -0,0 +1,3 @@
module test_static_var;
static i32 my_static;
+1
View File
@@ -0,0 +1 @@
i8 i16 i32 i64 u8 u16 u32 u64
+33
View File
@@ -31,7 +31,21 @@ static const KeywordMap keywords[] = {
{"import", TOKEN_IMPORT}, {"import", TOKEN_IMPORT},
{"alias", TOKEN_ALIAS}, {"alias", TOKEN_ALIAS},
{"public", TOKEN_PUBLIC}, {"public", TOKEN_PUBLIC},
{"var", TOKEN_VAR},
{"const", TOKEN_CONST},
{"static", TOKEN_STATIC},
{"void", TOKEN_VOID}, {"void", TOKEN_VOID},
{"i8", TOKEN_I8},
{"i16", TOKEN_I16},
{"i32", TOKEN_I32},
{"i64", TOKEN_I64},
{"u8", TOKEN_U8},
{"u16", TOKEN_U16},
{"u32", TOKEN_U32},
{"u64", TOKEN_U64},
{"true", TOKEN_TRUE},
{"false", TOKEN_FALSE},
}; };
/** /**
@@ -222,8 +236,27 @@ Token tokenstream_next(TokenStream* ts) {
case ',': return create_token(ts, TOKEN_COMMA, start_text, 1, start_line, start_column, line_start); case ',': return create_token(ts, TOKEN_COMMA, start_text, 1, start_line, start_column, line_start);
case ';': return create_token(ts, TOKEN_SEMICOLON, start_text, 1, start_line, start_column, line_start); case ';': return create_token(ts, TOKEN_SEMICOLON, start_text, 1, start_line, start_column, line_start);
case '=': return create_token(ts, TOKEN_ASSIGN, start_text, 1, start_line, start_column, line_start); case '=': return create_token(ts, TOKEN_ASSIGN, start_text, 1, start_line, start_column, line_start);
case '"': {
size_t len = 0;
const char* start = &ts->code[ts->pos];
while (peek_char(ts) != '"' && peek_char(ts) != '\0') {
read_char(ts);
len++;
}
if (peek_char(ts) == '"') read_char(ts);
return create_token(ts, TOKEN_STRING, start, len, start_line, start_column + 1, line_start);
}
} }
if (isdigit(c)) {
size_t len = 1;
while (isdigit(peek_char(ts))) {
read_char(ts);
len++;
}
return create_token(ts, TOKEN_INTEGER, start_text, len, start_line, start_column, line_start);
}
/* Keywords and identifiers */ /* Keywords and identifiers */
if (is_identifier_start(c)) { if (is_identifier_start(c)) {
/* Declarations first for C89 */ /* Declarations first for C89 */
+15 -1
View File
@@ -16,6 +16,9 @@ typedef enum {
TOKEN_SEMICOLON, TOKEN_SEMICOLON,
TOKEN_ALIAS, TOKEN_ALIAS,
TOKEN_PUBLIC, TOKEN_PUBLIC,
TOKEN_VAR,
TOKEN_CONST,
TOKEN_STATIC,
/* Symbols */ /* Symbols */
TOKEN_PARENT_OPEN, TOKEN_PARENT_OPEN,
@@ -27,7 +30,18 @@ typedef enum {
/* Primitives */ /* Primitives */
TOKEN_VOID, TOKEN_VOID,
TOKEN_BUILTIN_TYPE, TOKEN_I8,
TOKEN_I16,
TOKEN_I32,
TOKEN_I64,
TOKEN_U8,
TOKEN_U16,
TOKEN_U32,
TOKEN_U64,
TOKEN_STRING,
TOKEN_INTEGER,
TOKEN_TRUE,
TOKEN_FALSE,
/* Variable */ /* Variable */
TOKEN_IDENTIFIER, TOKEN_IDENTIFIER,