Compare commits
12 Commits
146aa4d9d1
...
ec896495a3
| Author | SHA1 | Date | |
|---|---|---|---|
| ec896495a3 | |||
| eb4b0495f2 | |||
| 1f40c8f5ee | |||
| 98d58a2169 | |||
| f0621a8076 | |||
| 84747028f5 | |||
| f90cad2b96 | |||
| e09bd72441 | |||
| 9035cc639c | |||
| 3288efdfd7 | |||
| 34b7939f76 | |||
| 15714393c3 |
@@ -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.
|
||||
|
||||
@@ -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
@@ -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 C–source 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 $@ $^
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
module mymodule;
|
||||
|
||||
import foo;
|
||||
|
||||
alias myalias = int32[];
|
||||
|
||||
import bar;
|
||||
|
||||
alias otheralias = int32;
|
||||
@@ -0,0 +1,3 @@
|
||||
module mymodule;
|
||||
|
||||
alias myalias = int32[];
|
||||
@@ -0,0 +1,3 @@
|
||||
module mymodule;
|
||||
|
||||
alias myalias = int32;
|
||||
@@ -0,0 +1,2 @@
|
||||
module my_module;
|
||||
import other_module;
|
||||
@@ -0,0 +1 @@
|
||||
module my_module;
|
||||
@@ -0,0 +1,3 @@
|
||||
module my_module;
|
||||
|
||||
public import other_module;
|
||||
@@ -0,0 +1 @@
|
||||
a,b,c
|
||||
@@ -0,0 +1 @@
|
||||
module main;
|
||||
@@ -0,0 +1 @@
|
||||
module main; import stdio;
|
||||
@@ -0,0 +1 @@
|
||||
()[]
|
||||
@@ -0,0 +1 @@
|
||||
module
|
||||
@@ -0,0 +1 @@
|
||||
void main()
|
||||
@@ -0,0 +1,2 @@
|
||||
module
|
||||
import ;
|
||||
+4
-2
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Contains runtime information about types.
|
||||
*/
|
||||
#ifndef TYPES_H
|
||||
#define TYPES_H
|
||||
|
||||
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user