Compare commits

..

12 Commits

Author SHA1 Message Date
seeseemelk ec896495a3 Fix infinite loop bug 2026-04-29 14:40:06 +02:00
seeseemelk eb4b0495f2 Working on parser refactor 2026-04-29 14:36:42 +02:00
seeseemelk 1f40c8f5ee Refactor tests a bit more 2026-04-29 13:25:41 +02:00
seeseemelk 98d58a2169 Refactor tests 2026-04-29 13:09:14 +02:00
seeseemelk f0621a8076 Refactor parser 2026-04-29 11:53:26 +02:00
seeseemelk 84747028f5 Ensure alias and import can be mixed 2026-04-29 11:46:02 +02:00
seeseemelk f90cad2b96 Use proper public keyword 2026-04-29 11:43:14 +02:00
seeseemelk e09bd72441 Update ast interface 2026-04-29 11:24:42 +02:00
seeseemelk 9035cc639c Add alias to ast 2026-04-29 11:18:40 +02:00
seeseemelk 3288efdfd7 Refactor test interface 2026-04-29 10:59:06 +02:00
seeseemelk 34b7939f76 Refactor parser to C11 and update build configuration 2026-04-29 10:38:34 +02:00
seeseemelk 15714393c3 Refactor parser to use Token in AST and update tests 2026-04-29 10:35:12 +02:00
29 changed files with 590 additions and 265 deletions
+5
View File
@@ -40,3 +40,8 @@ the agent to update the implementation.
When creating a commit, make sure that both the user's and the agent's modifications
are included in the commit.
Only create a commit when specifically asked for that. Never assume implicitly that the
user wants you to create a commit.
Even if they asked you to create a commit in an earlier task, it does not mean that
you should also create a commit in a later task.
+60 -7
View File
@@ -5,29 +5,82 @@
#define AST_H
#include "bool.h"
#include "token.h"
#include <stddef.h>
typedef struct {
/* @brief The name of the module being imported. */
char* module_name;
/** @brief The name of the module being imported. */
const char* module_name;
/* @brief Whether the import is public or not. */
/** @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.
*/
typedef struct TypeExpression TypeExpression;
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.
*/
typedef struct {
/** @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. */
char* name;
/** @brief The name of the module. */
const char* name;
/* @brief The list of imports in the module. */
/** @brief The list of imports in the module. */
ImportDeclaration* imports;
/* @brief The number of imports in the module. */
/** @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
+2 -2
View File
@@ -1,4 +1,4 @@
V0_SRC := v0/main.c v0/util.c v0/token.c v0/parser.c v0/log.c
V0_SRC := v0/main.c v0/util.c v0/token.c v0/parser.c v0/log.c v0/str.c
# 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"`.
@@ -11,7 +11,7 @@ V0_TEST_OBJ := $(patsubst v0/%.c,v0/bin/%.o,$(V0_TEST))
V0_SRC_DEPS := $(V0_SRC_OBJ:.o=.d)
V0_TEST_DEPS := $(V0_TEST_OBJ:.o=.d)
CFLAGS += -Werror -Wall -pedantic -std=c89
CFLAGS += -Werror -Wall -pedantic -std=c11
v0/bin/c2: $(V0_SRC_OBJ)
$(CC) $(CFLAGS) -o $@ $^
+2 -2
View File
@@ -20,7 +20,7 @@ void log_error(const char* msg) {
}
}
void log_on_line(Location* loc, int to_column, const char* msg, ...) {
void log_on_line(Location* loc, const char* msg, ...) {
/* Declarations first for C89 */
char* line_prefix = NULL;
char* formatted_msg = NULL;
@@ -36,7 +36,7 @@ void log_on_line(Location* loc, int to_column, const char* msg, ...) {
line_prefix = format_string("%d| ", loc->line);
if (!line_prefix) goto cleanup;
caret_len = to_column - loc->column_start + 1;
caret_len = loc->column_end - loc->column_start + 1;
if (caret_len < 1) caret_len = 1;
/* Format the message */
+1 -2
View File
@@ -28,10 +28,9 @@ void log_error(const char* msg);
* It additionally supports the `%S` format specifier, which can be used to format a `String` structure from `string.h`.
*
* @param loc The location where the error occurred.
* @param to_column The column number where the error ends.
* @param msg The error message to log. This can contain format specifiers like printf, and the additional arguments will be formatted into the message.
* @param ... Additional arguments to format into the error message.
*/
void log_on_line(Location* loc, int to_column, const char* msg, ...);
void log_on_line(Location* loc, const char* msg, ...);
#endif
+245 -74
View File
@@ -4,110 +4,281 @@
#include <string.h>
#include <stdio.h>
Module* parser_parse(TokenStream* ts) {
/* Declarations first for C89 */
Token t;
Module* module;
ImportDeclaration* new_imports;
int is_public;
// /**
// * 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;
t = tokenstream_next(ts);
if (t.token != TOKEN_MODULE) {
log_on_line(&t.location, t.location.column_end, "expected 'module' keyword");
return NULL;
// 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);
}
t = tokenstream_next(ts);
if (t.token != TOKEN_IDENTIFIER) {
log_on_line(&t.location, t.location.column_end, "expected module name");
return NULL;
/**
* 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;
}
module = (Module*)malloc(sizeof(Module));
if (module == NULL) {
fprintf(stderr, "Out of memory\n");
exit(1);
/**
* @copilot todo
*/
static bool parser_expect(Parser* p, TokenType token, const char* msg) {
if (parser_accept(p, token)) {
return true;
}
module->name = NULL;
module->imports = NULL;
module->import_count = 0;
module->name = (char*)malloc(t.text.length + 1);
if (module->name == NULL) {
fprintf(stderr, "Out of memory\n");
exit(1);
log_on_line(&p->token.location, msg);
return false;
}
memcpy(module->name, t.text.data, t.text.length);
module->name[t.text.length] = '\0';
t = tokenstream_next(ts);
if (t.token != TOKEN_SEMICOLON) {
log_on_line(&t.location, t.location.column_end, "expected ';' after module name");
parser_free(module);
return NULL;
/**
* @copilot todo add docs
*/
static bool parser_peek(Parser* p, TokenType token) {
if (p->token.token == token) {
return true;
}
return false;
}
module->imports = NULL;
module->import_count = 0;
while (1) {
t = tokenstream_next(ts);
if (t.token != TOKEN_IMPORT) {
break;
/**
* @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;
}
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;
t = tokenstream_next(ts);
is_public = 0;
if (t.token == TOKEN_IDENTIFIER && strncmp(t.text.data, "public", t.text.length) == 0) {
is_public = 1;
t = tokenstream_next(ts);
/**
* 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;
}
if (t.token != TOKEN_IDENTIFIER) {
log_on_line(&t.location, t.location.column_end, "expected module name to import");
parser_free(module);
return NULL;
/**
* Parses the "module" keyword
*/
static bool parse_declaration_module(Parser* p, Module* module) {
if (!parser_expect(p, TOKEN_MODULE, "expected keyword 'module'")) {
return false;
}
module->imports[module->import_count].module_name = (char*)malloc(t.text.length + 1);
if (!module->imports[module->import_count].module_name) {
fprintf(stderr, "Out of memory\n");
exit(1);
if (!parser_require(p, TOKEN_IDENTIFIER, "expected module identifier")) {
return false;
}
memcpy(module->imports[module->import_count].module_name, t.text.data, t.text.length);
module->imports[module->import_count].module_name[t.text.length] = '\0';
module->imports[module->import_count].is_public = is_public;
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];
t = tokenstream_next(ts);
if (t.token != TOKEN_SEMICOLON) {
log_on_line(&t.location, t.location.column_end, "expected ';' after import");
parser_free(module);
return NULL;
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) {
/* C89: declare index variable before the loop */
size_t i;
for (i = 0; i < module->import_count; i++) {
free(module->imports[i].module_name);
for(size_t i = 0; i < module->import_count; i++) {
free((void*)module->imports[i].module_name);
}
free(module->imports);
}
free(module->name);
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);
}
+11
View File
@@ -0,0 +1,11 @@
#include "str.h"
#include <string.h>
#include <stdlib.h>
char* string_copy(String string) {
char* str = malloc(string.length + 1);
memcpy(str, string.data, string.length);
str[string.length] = '\0';
return str;
}
+10
View File
@@ -14,4 +14,14 @@ typedef struct {
size_t length;
} String;
/**
* Creates a copy of a string.
*
* Note that this copy has to be freed afterwards.
*
* @param string The string to copy.
* @returns A null-terminated copy of the string.
*/
char* string_copy(String string);
#endif
+100 -65
View File
@@ -1,9 +1,12 @@
#include "test.h"
#include "util.h"
#include "parser.h"
#include <setjmp.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "util.h"
static jmp_buf s_testJmp;
static const char* s_failMsg;
@@ -11,27 +14,14 @@ static char* s_logOutput = NULL;
static const char* s_currentTestName = NULL;
static char* s_testSource = NULL;
static Module* s_currentModule = NULL;
static TokenStream* s_currentTokenStream = NULL;
void fail(const char* msg) {
s_failMsg = msg;
longjmp(s_testJmp, 1);
}
void assert_not_null(void* ptr, const char* msg) {
if (ptr == NULL) {
fail(msg);
}
}
void assert_str(const char* expected, const char* actual, const char* msg) {
if (expected == NULL || actual == NULL || strcmp(expected, actual) != 0) {
fail(msg);
}
}
void assert_log(const char* expected, const char* msg) {
assert_str(expected, s_logOutput, msg);
}
char* read_file_content(const char* filepath) {
FILE* f;
long size;
@@ -53,6 +43,59 @@ char* read_file_content(const char* filepath) {
return content;
}
void assert_not_null(void* ptr, const char* msg) {
if (ptr == NULL) {
fail(msg);
}
}
void assert_string(const char* expected, String actual, const char* msg) {
if (expected == NULL || actual.data == NULL || strlen(expected) != actual.length || strncmp(expected, actual.data, actual.length) != 0) {
fail(msg);
}
}
void assert_str(const char* expected, const char* actual, const char* msg) {
if (expected == NULL || actual == NULL || strcmp(expected, actual) != 0) {
fail(msg);
}
}
TokenStream* test_get_tokenstream(void) {
if (s_currentTokenStream == NULL) {
char* filepath = NULL;
filepath = format_string("v0/tests/%s.c2", s_currentTestName);
if (!filepath) {
fail("out of memory");
return NULL;
}
if (s_testSource) free(s_testSource);
s_testSource = read_file_content(filepath);
if (!s_testSource) {
puts(filepath);
fail("could not read test source file");
free(filepath);
return NULL;
}
s_currentTokenStream = tokenstream_open(filepath, s_testSource);
free(filepath);
}
return s_currentTokenStream;
}
Module* test_get_ast(void) {
if (s_currentModule == NULL) {
s_currentModule = parser_parse(test_get_tokenstream());
}
return s_currentModule;
}
void assert_log(const char* expected, const char* msg) {
assert_str(expected, s_logOutput, msg);
}
void assert_log_file(const char* msg) {
char* filepath = format_string("v0/tests/%s.log", s_currentTestName);
const char* generate;
@@ -109,28 +152,6 @@ void assert_false(bool condition, const char* msg) {
}
}
TokenStream* tokenstream_get_test(void) {
char* filepath = NULL;
TokenStream* ts = NULL;
filepath = format_string("v0/tests/%s.c2", s_currentTestName);
if (!filepath) {
fail("out of memory");
return NULL;
}
if (s_testSource) free(s_testSource);
s_testSource = read_file_content(filepath);
if (!s_testSource) {
fail("could not read test source file");
free(filepath);
return NULL;
}
ts = tokenstream_open(filepath, s_testSource);
free(filepath);
return ts;
}
static void log_append(const char* msg) {
size_t oldLen = s_logOutput ? strlen(s_logOutput) : 0;
size_t newLen = oldLen + strlen(msg) + 1;
@@ -164,35 +185,37 @@ typedef struct {
static int s_totalTests;
static int s_greenTests;
#define TEST(name) {#name, name},
static TestCase s_tests[] = {
{"tokenstream_open_fail", test_tokenstream_open_fail},
{"tokenstream_simple_keyword", test_tokenstream_simple_keyword},
{"tokenstream_keywords_and_symbols", test_tokenstream_keywords_and_symbols},
{"tokenstream_parentheses_and_brackets", test_tokenstream_parentheses_and_brackets},
{"tokenstream_comma", test_tokenstream_comma},
{"tokenstream_whitespace_ignored", test_tokenstream_whitespace_ignored},
{"tokenstream_void_function_signature", test_tokenstream_void_function_signature},
{"tokenstream_unknown_token", test_tokenstream_unknown_token},
{"tokenstream_info", test_tokenstream_info},
{"parser_module_name", test_parser_module_name},
{"parser_bad_module_name", test_parser_bad_module_name},
{"parser_missing_semicolon_module", test_parser_missing_semicolon_module},
{"parser_missing_semicolon_import", test_parser_missing_semicolon_import},
{"parser_bad_import_name", test_parser_bad_import_name},
{"parser_imports", test_parser_imports},
{"parser_public_imports", test_parser_public_imports},
{"log_error", test_log_error},
{"log_on_line", test_log_on_line},
{"log_on_line_variadic", test_log_on_line_variadic},
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)
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_public_imports)
TEST(test_tokenstream_comma)
TEST(test_tokenstream_info)
TEST(test_tokenstream_keywords_and_symbols)
TEST(test_tokenstream_open_fail)
TEST(test_tokenstream_parentheses_and_brackets)
TEST(test_tokenstream_simple_keyword)
TEST(test_tokenstream_unknown_token)
TEST(test_tokenstream_void_function_signature)
TEST(test_tokenstream_whitespace_ignored)
};
int main(int argc, char** argv) {
/* Declarations first for C89 */
const char** failedTests;
int failedCount;
int i;
int j;
(void)argc;
(void)argv;
@@ -200,14 +223,16 @@ int main(int argc, char** argv) {
s_totalTests = sizeof(s_tests) / sizeof(s_tests[0]);
s_greenTests = 0;
/* Allocate failed tests array dynamically to avoid VLAs */
// Allocate failed tests array dynamically to avoid VLAs
failedTests = (const char**)malloc((s_totalTests + 1) * sizeof(const char*));
failedCount = 0;
for (i = 0; i < s_totalTests; i++) {
s_currentTestName = s_tests[i].name;
for (int i = 0; i < s_totalTests; i++) {
// Add 5 to strip the 'test_' prefix.
s_currentTestName = s_tests[i].name + 5;
log_set_output(log_append);
printf("%s...", s_tests[i].name);
fflush(stdout);
s_failMsg = NULL;
if (setjmp(s_testJmp) == 0) {
@@ -223,6 +248,16 @@ int main(int argc, char** argv) {
printf(" [FAIL]: %s\n", s_failMsg ? s_failMsg : "");
failedTests[failedCount++] = s_tests[i].name;
}
if (s_currentModule) {
parser_free(s_currentModule);
s_currentModule = NULL;
}
if (s_currentTokenStream) {
tokenstream_close(s_currentTokenStream);
s_currentTokenStream = NULL;
}
fflush(stdout);
}
if (s_testSource) free(s_testSource);
@@ -230,7 +265,7 @@ int main(int argc, char** argv) {
if (failedCount > 0) {
printf("\nFailed tests:\n");
for (j = 0; j < failedCount; j++) {
for (int j = 0; j < failedCount; j++) {
printf(" - %s\n", failedTests[j]);
}
}
+23 -1
View File
@@ -5,6 +5,7 @@
#define TEST_H
#include "token.h"
#include "ast.h"
typedef void (*Test)(void);
@@ -35,6 +36,17 @@ void assert_not_null(void* ptr, const char* msg);
*/
void assert_str(const char* expected, const char* actual, const char* msg);
/**
* Asserts that a string has the expected value.
*
* Calls `fail` if the assertion does not hold.
*
* @param expected The expected value. This is typically a string literal.
* @param actual The actual value. This is typically an expression.
* @param msg The message to print if these do not match.
*/
void assert_string(const char* expected, String actual, const char* msg);
/**
* Asserts that the logged output matches the expected value.
*/
@@ -66,7 +78,17 @@ void assert_false(bool condition, const char* msg);
/**
* Get the token stream used for this test.
* It reads from the `v0/tests/xyz.c2` file, where xyz is the test name.
*
* At the end of the test, the tokenstream will be freed automatically by the test harness.
*/
TokenStream* tokenstream_get_test(void);
TokenStream* test_get_tokenstream(void);
/**
* Gets a parsed module for the this test.
* It reads from the `v0/tests/xyz.c2` file, where xyz is the test name.
*
* At the end of the test, the AST will be freed automatically by the test harness.
*/
Module* test_get_ast(void);
#endif
+2 -2
View File
@@ -34,7 +34,7 @@ static void test_log_on_line(void) {
loc.column_start = 12;
loc.column_end = 13;
log_on_line(&loc, 13, "unexpected token");
log_on_line(&loc, "unexpected token");
assert_log_file("expected formatted error message");
}
@@ -47,6 +47,6 @@ static void test_log_on_line_variadic(void) {
loc.column_start = 12;
loc.column_end = 13;
log_on_line(&loc, 13, "unexpected token '%c'", 'x');
log_on_line(&loc, "unexpected token '%c'", 'x');
assert_log_file("expected formatted error message with variadic args");
}
+50 -41
View File
@@ -1,86 +1,95 @@
#include "test.h"
#include "parser.h"
#include <string.h>
#include <stdlib.h>
static void test_parser_module_name(void) {
TokenStream* ts = tokenstream_open("test.c", "module my_module;");
Module* m = parser_parse(ts);
Module* m = test_get_ast();
assert_not_null(m, "expected module to be parsed");
assert_str("my_module", m->name, "expected name 'my_module'");
parser_free(m);
tokenstream_close(ts);
}
static void test_parser_bad_module_name(void) {
TokenStream* ts = tokenstream_get_test();
Module* m = parser_parse(ts);
test_get_ast();
assert_log_file("expected error to be logged for bad module name");
parser_free(m);
tokenstream_close(ts);
}
static void test_parser_missing_semicolon_module(void) {
TokenStream* ts = tokenstream_get_test();
Module* m = parser_parse(ts);
test_get_ast();
assert_log_file("expected error for missing semicolon");
parser_free(m);
tokenstream_close(ts);
}
static void test_parser_missing_semicolon_import(void) {
TokenStream* ts = tokenstream_get_test();
Module* m = parser_parse(ts);
test_get_ast();
assert_log_file("expected error for missing semicolon");
parser_free(m);
tokenstream_close(ts);
}
static void test_parser_bad_import_name(void) {
TokenStream* ts = tokenstream_get_test();
Module* m = parser_parse(ts);
test_get_ast();
assert_log_file("expected error for bad import name");
parser_free(m);
tokenstream_close(ts);
}
static void test_parser_imports(void) {
TokenStream* ts = tokenstream_open("test.c", "module my_module; import other_module;");
Module* m = parser_parse(ts);
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, m->import_count, "expected one import");
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");
parser_free(m);
tokenstream_close(ts);
}
static void test_parser_public_imports(void) {
TokenStream* ts = tokenstream_open("test.c", "module my_module; import public other_module;");
Module* m = parser_parse(ts);
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, m->import_count, "expected one import");
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");
parser_free(m);
tokenstream_close(ts);
}
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'");
}
+10 -44
View File
@@ -9,23 +9,19 @@ static void test_tokenstream_open_fail(void) {
}
static void test_tokenstream_simple_keyword(void) {
TokenStream* ts;
TokenStream* ts = test_get_tokenstream();
Token t;
Token eof;
ts = tokenstream_open("test.c", "module");
t = tokenstream_next(ts);
if (t.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
eof = tokenstream_next(ts);
if (eof.token != TOKEN_EOF) fail("expected EOF");
tokenstream_close(ts);
}
static void test_tokenstream_keywords_and_symbols(void) {
TokenStream* ts = tokenstream_open("test.c", "module main; import stdio;");
TokenStream* ts = test_get_tokenstream();
if (tokenstream_next(ts).token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
if (tokenstream_next(ts).token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER (main)");
@@ -34,24 +30,20 @@ static void test_tokenstream_keywords_and_symbols(void) {
if (tokenstream_next(ts).token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER (stdio)");
if (tokenstream_next(ts).token != TOKEN_SEMICOLON) fail("expected TOKEN_SEMICOLON");
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
tokenstream_close(ts);
}
static void test_tokenstream_parentheses_and_brackets(void) {
TokenStream* ts = tokenstream_open("test.c", "()[]");
TokenStream* ts = test_get_tokenstream();
if (tokenstream_next(ts).token != TOKEN_PARENT_OPEN) fail("expected TOKEN_PARENT_OPEN");
if (tokenstream_next(ts).token != TOKEN_PARENT_CLOSE) fail("expected TOKEN_PARENT_CLOSE");
if (tokenstream_next(ts).token != TOKEN_BRACKET_OPEN) fail("expected TOKEN_BRACKET_OPEN");
if (tokenstream_next(ts).token != TOKEN_BRACKET_CLOSE) fail("expected TOKEN_BRACKET_CLOSE");
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
tokenstream_close(ts);
}
static void test_tokenstream_comma(void) {
TokenStream* ts = tokenstream_open("test.c", "a,b,c");
TokenStream* ts = test_get_tokenstream();
if (tokenstream_next(ts).token != TOKEN_IDENTIFIER) fail("expected a");
if (tokenstream_next(ts).token != TOKEN_COMMA) fail("expected comma");
@@ -59,75 +51,49 @@ static void test_tokenstream_comma(void) {
if (tokenstream_next(ts).token != TOKEN_COMMA) fail("expected comma");
if (tokenstream_next(ts).token != TOKEN_IDENTIFIER) fail("expected c");
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
tokenstream_close(ts);
}
static void test_tokenstream_whitespace_ignored(void) {
TokenStream* ts = tokenstream_open("test.c", " module \n\t import ; ");
TokenStream* ts = test_get_tokenstream();
if (tokenstream_next(ts).token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
if (tokenstream_next(ts).token != TOKEN_IMPORT) fail("expected TOKEN_IMPORT");
if (tokenstream_next(ts).token != TOKEN_SEMICOLON) fail("expected TOKEN_SEMICOLON");
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
tokenstream_close(ts);
}
static void test_tokenstream_void_function_signature(void) {
TokenStream* ts = tokenstream_open("test.c", "void main()");
TokenStream* ts = test_get_tokenstream();
if (tokenstream_next(ts).token != TOKEN_VOID) fail("expected TOKEN_VOID");
if (tokenstream_next(ts).token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER");
if (tokenstream_next(ts).token != TOKEN_PARENT_OPEN) fail("expected TOKEN_PARENT_OPEN");
if (tokenstream_next(ts).token != TOKEN_PARENT_CLOSE) fail("expected TOKEN_PARENT_CLOSE");
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
tokenstream_close(ts);
}
static void test_tokenstream_unknown_token(void) {
TokenStream* ts = tokenstream_get_test();
TokenStream* ts = test_get_tokenstream();
if (tokenstream_next(ts).token != TOKEN_UNKNOWN) fail("expected TOKEN_UNKNOWN");
assert_log_file("expected error message for unknown token");
tokenstream_close(ts);
}
static void test_tokenstream_info(void) {
TokenStream* ts;
TokenStream* ts = test_get_tokenstream();
Token t1;
Token t2;
char* buf1;
char* buf2;
ts = tokenstream_open("test.c", "module main;");
t1 = tokenstream_next(ts);
if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
buf1 = malloc((size_t)t1.text.length + 1);
if (!buf1) fail("out of memory");
memcpy(buf1, t1.text.data, t1.text.length);
buf1[t1.text.length] = '\0';
assert_str("module", buf1, "info: expected 'module'");
assert_string("module", t1.text, "info: expected 'module'");
if (t1.location.line != 1) fail("expected line 1");
if (t1.location.column_start != 1) fail("expected column 1");
t2 = tokenstream_next(ts);
if (t2.token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER");
buf2 = malloc((size_t)t2.text.length + 1);
if (!buf2) { free(buf1); fail("out of memory"); }
memcpy(buf2, t2.text.data, t2.text.length);
buf2[t2.text.length] = '\0';
assert_str("main", buf2, "info: expected 'main'");
assert_string("main", t2.text, "info: expected 'main'");
if (t2.location.line != 1) fail("expected line 1");
if (t2.location.column_start != 8) fail("expected column 8");
free(buf1);
free(buf2);
tokenstream_close(ts);
}
+9
View File
@@ -0,0 +1,9 @@
module mymodule;
import foo;
alias myalias = int32[];
import bar;
alias otheralias = int32;
+3
View File
@@ -0,0 +1,3 @@
module mymodule;
alias myalias = int32[];
+3
View File
@@ -0,0 +1,3 @@
module mymodule;
alias myalias = int32;
+2
View File
@@ -0,0 +1,2 @@
module my_module;
import other_module;
+1
View File
@@ -0,0 +1 @@
module my_module;
+3
View File
@@ -0,0 +1,3 @@
module my_module;
public import other_module;
+1
View File
@@ -0,0 +1 @@
a,b,c
+1
View File
@@ -0,0 +1 @@
module main;
@@ -0,0 +1 @@
module main; import stdio;
@@ -0,0 +1 @@
()[]
+1
View File
@@ -0,0 +1 @@
module
@@ -0,0 +1 @@
void main()
@@ -0,0 +1,2 @@
module
import ;
+4 -2
View File
@@ -26,10 +26,11 @@ typedef struct {
const char* keyword;
TokenType token;
} KeywordMap;
static const KeywordMap keywords[] = {
{"module", TOKEN_MODULE},
{"import", TOKEN_IMPORT},
{"alias", TOKEN_ALIAS},
{"public", TOKEN_PUBLIC},
{"void", TOKEN_VOID},
};
@@ -220,6 +221,7 @@ Token tokenstream_next(TokenStream* ts) {
case ']': return create_token(ts, TOKEN_BRACKET_CLOSE, 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_ASSIGN, start_text, 1, start_line, start_column, line_start);
}
/* Keywords and identifiers */
@@ -239,6 +241,6 @@ Token tokenstream_next(TokenStream* ts) {
/* Unknown character */
t = create_token(ts, TOKEN_UNKNOWN, start_text, 1, start_line, start_column, line_start);
log_on_line(&t.location, t.location.column_end, "unexpected token '%c'", c);
log_on_line(&t.location, "unexpected token '%c'", c);
return t;
}
+4
View File
@@ -14,6 +14,8 @@ typedef enum {
TOKEN_MODULE,
TOKEN_IMPORT,
TOKEN_SEMICOLON,
TOKEN_ALIAS,
TOKEN_PUBLIC,
/* Symbols */
TOKEN_PARENT_OPEN,
@@ -21,9 +23,11 @@ typedef enum {
TOKEN_BRACKET_OPEN,
TOKEN_BRACKET_CLOSE,
TOKEN_COMMA,
TOKEN_ASSIGN,
/* Primitives */
TOKEN_VOID,
TOKEN_BUILTIN_TYPE,
/* Variable */
TOKEN_IDENTIFIER,
+9
View File
@@ -0,0 +1,9 @@
/**
* Contains runtime information about types.
*/
#ifndef TYPES_H
#define TYPES_H
#endif