Compare commits
12 Commits
| 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
|
When creating a commit, make sure that both the user's and the agent's modifications
|
||||||
are included in the commit.
|
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
|
#define AST_H
|
||||||
|
|
||||||
#include "bool.h"
|
#include "bool.h"
|
||||||
|
#include "token.h"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/* @brief The name of the module being imported. */
|
/** @brief The name of the module being imported. */
|
||||||
char* module_name;
|
const char* module_name;
|
||||||
|
|
||||||
/* @brief Whether the import is public or not. */
|
/** @brief Whether the import is public or not. */
|
||||||
bool is_public;
|
bool is_public;
|
||||||
} ImportDeclaration;
|
} 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.
|
* The top-level model.
|
||||||
* Every file matches an entire Module.
|
* Every file matches an entire Module.
|
||||||
*/
|
*/
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/* @brief The name of the module. */
|
/** @brief The name of the module. */
|
||||||
char* name;
|
const char* name;
|
||||||
|
|
||||||
/* @brief The list of imports in the module. */
|
/** @brief The list of imports in the module. */
|
||||||
ImportDeclaration* imports;
|
ImportDeclaration* imports;
|
||||||
|
|
||||||
/* @brief The number of imports in the module. */
|
/** @brief The number of imports in the module. */
|
||||||
size_t import_count;
|
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;
|
} Module;
|
||||||
|
|
||||||
#endif
|
#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
|
# 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"`.
|
# 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_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=c89
|
CFLAGS += -Werror -Wall -pedantic -std=c11
|
||||||
|
|
||||||
v0/bin/c2: $(V0_SRC_OBJ)
|
v0/bin/c2: $(V0_SRC_OBJ)
|
||||||
$(CC) $(CFLAGS) -o $@ $^
|
$(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 */
|
/* Declarations first for C89 */
|
||||||
char* line_prefix = NULL;
|
char* line_prefix = NULL;
|
||||||
char* formatted_msg = 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);
|
line_prefix = format_string("%d| ", loc->line);
|
||||||
if (!line_prefix) goto cleanup;
|
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;
|
if (caret_len < 1) caret_len = 1;
|
||||||
|
|
||||||
/* Format the message */
|
/* 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`.
|
* 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 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 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.
|
* @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
|
#endif
|
||||||
|
|||||||
+261
-90
@@ -4,110 +4,281 @@
|
|||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdio.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) {
|
Module* parser_parse(TokenStream* ts) {
|
||||||
/* Declarations first for C89 */
|
Parser* p = malloc(sizeof(Parser));
|
||||||
Token t;
|
p->ts = ts;
|
||||||
Module* module;
|
parser_next_token(p);
|
||||||
ImportDeclaration* new_imports;
|
|
||||||
int is_public;
|
|
||||||
|
|
||||||
t = tokenstream_next(ts);
|
Module* module = malloc(sizeof(Module));
|
||||||
if (t.token != TOKEN_MODULE) {
|
if (!parse_declaration_module(p, module)) {
|
||||||
log_on_line(&t.location, t.location.column_end, "expected 'module' keyword");
|
goto fail;
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
t = tokenstream_next(ts);
|
|
||||||
if (t.token != TOKEN_IDENTIFIER) {
|
|
||||||
log_on_line(&t.location, t.location.column_end, "expected module name");
|
|
||||||
return NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module = (Module*)malloc(sizeof(Module));
|
while (!parser_peek(p, TOKEN_EOF)) {
|
||||||
if (module == NULL) {
|
bool is_public = false;
|
||||||
fprintf(stderr, "Out of memory\n");
|
bool terminal = false;
|
||||||
exit(1);
|
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);
|
||||||
}
|
}
|
||||||
module->name = NULL;
|
|
||||||
module->imports = NULL;
|
|
||||||
module->import_count = 0;
|
|
||||||
|
|
||||||
module->name = (char*)malloc(t.text.length + 1);
|
return module;
|
||||||
if (module->name == NULL) {
|
fail:
|
||||||
fprintf(stderr, "Out of memory\n");
|
free(module);
|
||||||
exit(1);
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
memcpy(module->name, t.text.data, t.text.length);
|
void free_type_expression(TypeExpression* expr) {
|
||||||
module->name[t.text.length] = '\0';
|
if (expr->tag == TYPE_EXPRESSION_ARRAY) {
|
||||||
|
free_type_expression(expr->array.array);
|
||||||
t = tokenstream_next(ts);
|
free(expr->array.array);
|
||||||
if (t.token != TOKEN_SEMICOLON) {
|
}
|
||||||
log_on_line(&t.location, t.location.column_end, "expected ';' after module name");
|
|
||||||
parser_free(module);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
module->imports = NULL;
|
|
||||||
module->import_count = 0;
|
|
||||||
|
|
||||||
while (1) {
|
|
||||||
t = tokenstream_next(ts);
|
|
||||||
if (t.token != TOKEN_IMPORT) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (t.token != TOKEN_IDENTIFIER) {
|
|
||||||
log_on_line(&t.location, t.location.column_end, "expected module name to import");
|
|
||||||
parser_free(module);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
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->import_count++;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return module;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void parser_free(Module* module) {
|
void parser_free(Module* module) {
|
||||||
if (module == NULL) return;
|
if (module == NULL) return;
|
||||||
if (module->imports != NULL) {
|
if (module->imports != NULL) {
|
||||||
/* C89: declare index variable before the loop */
|
for(size_t i = 0; i < module->import_count; i++) {
|
||||||
size_t i;
|
free((void*)module->imports[i].module_name);
|
||||||
for (i = 0; i < module->import_count; i++) {
|
|
||||||
free(module->imports[i].module_name);
|
|
||||||
}
|
}
|
||||||
free(module->imports);
|
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);
|
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;
|
size_t length;
|
||||||
} String;
|
} 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
|
#endif
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#include "test.h"
|
#include "test.h"
|
||||||
|
|
||||||
|
#include "util.h"
|
||||||
|
#include "parser.h"
|
||||||
|
|
||||||
#include <setjmp.h>
|
#include <setjmp.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include "util.h"
|
|
||||||
|
|
||||||
static jmp_buf s_testJmp;
|
static jmp_buf s_testJmp;
|
||||||
static const char* s_failMsg;
|
static const char* s_failMsg;
|
||||||
@@ -11,27 +14,14 @@ 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 TokenStream* s_currentTokenStream = NULL;
|
||||||
|
|
||||||
void fail(const char* msg) {
|
void fail(const char* msg) {
|
||||||
s_failMsg = msg;
|
s_failMsg = msg;
|
||||||
longjmp(s_testJmp, 1);
|
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) {
|
char* read_file_content(const char* filepath) {
|
||||||
FILE* f;
|
FILE* f;
|
||||||
long size;
|
long size;
|
||||||
@@ -53,6 +43,59 @@ char* read_file_content(const char* filepath) {
|
|||||||
return content;
|
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) {
|
void assert_log_file(const char* msg) {
|
||||||
char* filepath = format_string("v0/tests/%s.log", s_currentTestName);
|
char* filepath = format_string("v0/tests/%s.log", s_currentTestName);
|
||||||
const char* generate;
|
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) {
|
static void log_append(const char* msg) {
|
||||||
size_t oldLen = s_logOutput ? strlen(s_logOutput) : 0;
|
size_t oldLen = s_logOutput ? strlen(s_logOutput) : 0;
|
||||||
size_t newLen = oldLen + strlen(msg) + 1;
|
size_t newLen = oldLen + strlen(msg) + 1;
|
||||||
@@ -164,35 +185,37 @@ typedef struct {
|
|||||||
static int s_totalTests;
|
static int s_totalTests;
|
||||||
static int s_greenTests;
|
static int s_greenTests;
|
||||||
|
|
||||||
|
#define TEST(name) {#name, name},
|
||||||
|
|
||||||
static TestCase s_tests[] = {
|
static TestCase s_tests[] = {
|
||||||
{"tokenstream_open_fail", test_tokenstream_open_fail},
|
TEST(test_log_error)
|
||||||
{"tokenstream_simple_keyword", test_tokenstream_simple_keyword},
|
TEST(test_log_on_line_variadic)
|
||||||
{"tokenstream_keywords_and_symbols", test_tokenstream_keywords_and_symbols},
|
TEST(test_log_on_line)
|
||||||
{"tokenstream_parentheses_and_brackets", test_tokenstream_parentheses_and_brackets},
|
TEST(test_parser_alias_and_import_mix)
|
||||||
{"tokenstream_comma", test_tokenstream_comma},
|
TEST(test_parser_alias_array)
|
||||||
{"tokenstream_whitespace_ignored", test_tokenstream_whitespace_ignored},
|
TEST(test_parser_alias_simple)
|
||||||
{"tokenstream_void_function_signature", test_tokenstream_void_function_signature},
|
TEST(test_parser_bad_import_name)
|
||||||
{"tokenstream_unknown_token", test_tokenstream_unknown_token},
|
TEST(test_parser_bad_module_name)
|
||||||
{"tokenstream_info", test_tokenstream_info},
|
TEST(test_parser_imports)
|
||||||
{"parser_module_name", test_parser_module_name},
|
TEST(test_parser_missing_semicolon_import)
|
||||||
{"parser_bad_module_name", test_parser_bad_module_name},
|
TEST(test_parser_missing_semicolon_module)
|
||||||
{"parser_missing_semicolon_module", test_parser_missing_semicolon_module},
|
TEST(test_parser_module_name)
|
||||||
{"parser_missing_semicolon_import", test_parser_missing_semicolon_import},
|
TEST(test_parser_public_imports)
|
||||||
{"parser_bad_import_name", test_parser_bad_import_name},
|
TEST(test_tokenstream_comma)
|
||||||
{"parser_imports", test_parser_imports},
|
TEST(test_tokenstream_info)
|
||||||
{"parser_public_imports", test_parser_public_imports},
|
TEST(test_tokenstream_keywords_and_symbols)
|
||||||
{"log_error", test_log_error},
|
TEST(test_tokenstream_open_fail)
|
||||||
{"log_on_line", test_log_on_line},
|
TEST(test_tokenstream_parentheses_and_brackets)
|
||||||
{"log_on_line_variadic", test_log_on_line_variadic},
|
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) {
|
int main(int argc, char** argv) {
|
||||||
/* Declarations first for C89 */
|
|
||||||
const char** failedTests;
|
const char** failedTests;
|
||||||
int failedCount;
|
int failedCount;
|
||||||
int i;
|
|
||||||
int j;
|
|
||||||
|
|
||||||
(void)argc;
|
(void)argc;
|
||||||
(void)argv;
|
(void)argv;
|
||||||
@@ -200,14 +223,16 @@ int main(int argc, char** argv) {
|
|||||||
s_totalTests = sizeof(s_tests) / sizeof(s_tests[0]);
|
s_totalTests = sizeof(s_tests) / sizeof(s_tests[0]);
|
||||||
s_greenTests = 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*));
|
failedTests = (const char**)malloc((s_totalTests + 1) * sizeof(const char*));
|
||||||
failedCount = 0;
|
failedCount = 0;
|
||||||
|
|
||||||
for (i = 0; i < s_totalTests; i++) {
|
for (int i = 0; i < s_totalTests; i++) {
|
||||||
s_currentTestName = s_tests[i].name;
|
// Add 5 to strip the 'test_' prefix.
|
||||||
|
s_currentTestName = s_tests[i].name + 5;
|
||||||
log_set_output(log_append);
|
log_set_output(log_append);
|
||||||
printf("%s...", s_tests[i].name);
|
printf("%s...", s_tests[i].name);
|
||||||
|
fflush(stdout);
|
||||||
s_failMsg = NULL;
|
s_failMsg = NULL;
|
||||||
|
|
||||||
if (setjmp(s_testJmp) == 0) {
|
if (setjmp(s_testJmp) == 0) {
|
||||||
@@ -223,6 +248,16 @@ int main(int argc, char** argv) {
|
|||||||
printf(" [FAIL]: %s\n", s_failMsg ? s_failMsg : "");
|
printf(" [FAIL]: %s\n", s_failMsg ? s_failMsg : "");
|
||||||
failedTests[failedCount++] = s_tests[i].name;
|
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);
|
if (s_testSource) free(s_testSource);
|
||||||
@@ -230,7 +265,7 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
if (failedCount > 0) {
|
if (failedCount > 0) {
|
||||||
printf("\nFailed tests:\n");
|
printf("\nFailed tests:\n");
|
||||||
for (j = 0; j < failedCount; j++) {
|
for (int j = 0; j < failedCount; j++) {
|
||||||
printf(" - %s\n", failedTests[j]);
|
printf(" - %s\n", failedTests[j]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#define TEST_H
|
#define TEST_H
|
||||||
|
|
||||||
#include "token.h"
|
#include "token.h"
|
||||||
|
#include "ast.h"
|
||||||
|
|
||||||
typedef void (*Test)(void);
|
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);
|
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.
|
* 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.
|
* Get the token stream used for this test.
|
||||||
* It reads from the `v0/tests/xyz.c2` file, where xyz is the test name.
|
* 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
|
#endif
|
||||||
|
|||||||
+2
-2
@@ -34,7 +34,7 @@ static void test_log_on_line(void) {
|
|||||||
loc.column_start = 12;
|
loc.column_start = 12;
|
||||||
loc.column_end = 13;
|
loc.column_end = 13;
|
||||||
|
|
||||||
log_on_line(&loc, 13, "unexpected token");
|
log_on_line(&loc, "unexpected token");
|
||||||
assert_log_file("expected formatted error message");
|
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_start = 12;
|
||||||
loc.column_end = 13;
|
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");
|
assert_log_file("expected formatted error message with variadic args");
|
||||||
}
|
}
|
||||||
|
|||||||
+56
-47
@@ -1,86 +1,95 @@
|
|||||||
#include "test.h"
|
#include "test.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
static void test_parser_module_name(void) {
|
static void test_parser_module_name(void) {
|
||||||
TokenStream* ts = tokenstream_open("test.c", "module my_module;");
|
Module* m = test_get_ast();
|
||||||
Module* m = parser_parse(ts);
|
|
||||||
|
|
||||||
assert_not_null(m, "expected module to be parsed");
|
assert_not_null(m, "expected module to be parsed");
|
||||||
assert_str("my_module", m->name, "expected name 'my_module'");
|
assert_str("my_module", m->name, "expected name 'my_module'");
|
||||||
|
|
||||||
parser_free(m);
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_parser_bad_module_name(void) {
|
static void test_parser_bad_module_name(void) {
|
||||||
TokenStream* ts = tokenstream_get_test();
|
test_get_ast();
|
||||||
Module* m = parser_parse(ts);
|
|
||||||
|
|
||||||
assert_log_file("expected error to be logged for bad module name");
|
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) {
|
static void test_parser_missing_semicolon_module(void) {
|
||||||
TokenStream* ts = tokenstream_get_test();
|
test_get_ast();
|
||||||
Module* m = parser_parse(ts);
|
|
||||||
|
|
||||||
assert_log_file("expected error for missing semicolon");
|
assert_log_file("expected error for missing semicolon");
|
||||||
|
|
||||||
parser_free(m);
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_parser_missing_semicolon_import(void) {
|
static void test_parser_missing_semicolon_import(void) {
|
||||||
TokenStream* ts = tokenstream_get_test();
|
test_get_ast();
|
||||||
Module* m = parser_parse(ts);
|
|
||||||
|
|
||||||
assert_log_file("expected error for missing semicolon");
|
assert_log_file("expected error for missing semicolon");
|
||||||
|
|
||||||
parser_free(m);
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_parser_bad_import_name(void) {
|
static void test_parser_bad_import_name(void) {
|
||||||
TokenStream* ts = tokenstream_get_test();
|
test_get_ast();
|
||||||
Module* m = parser_parse(ts);
|
|
||||||
|
|
||||||
assert_log_file("expected error for bad import name");
|
assert_log_file("expected error for bad import name");
|
||||||
|
|
||||||
parser_free(m);
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_parser_imports(void) {
|
static void test_parser_imports(void) {
|
||||||
TokenStream* ts = tokenstream_open("test.c", "module my_module; import other_module;");
|
Module* m = test_get_ast();
|
||||||
Module* m = parser_parse(ts);
|
|
||||||
|
|
||||||
assert_not_null(m, "expected module to be parsed");
|
assert_not_null(m, "expected module to be parsed");
|
||||||
assert_str("my_module", m->name, "expected name 'my_module'");
|
assert_str("my_module", m->name, "expected name 'my_module'");
|
||||||
|
|
||||||
assert_not_null(m->imports, "expected imports to be parsed");
|
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_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");
|
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) {
|
static void test_parser_public_imports(void) {
|
||||||
TokenStream* ts = tokenstream_open("test.c", "module my_module; import public other_module;");
|
Module* m = test_get_ast();
|
||||||
Module* m = parser_parse(ts);
|
|
||||||
|
|
||||||
assert_not_null(m, "expected module to be parsed");
|
assert_not_null(m, "expected module to be parsed");
|
||||||
assert_str("my_module", m->name, "expected name 'my_module'");
|
assert_str("my_module", m->name, "expected name 'my_module'");
|
||||||
|
|
||||||
assert_not_null(m->imports, "expected imports to be parsed");
|
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_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");
|
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'");
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-45
@@ -9,23 +9,19 @@ static void test_tokenstream_open_fail(void) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static void test_tokenstream_simple_keyword(void) {
|
static void test_tokenstream_simple_keyword(void) {
|
||||||
TokenStream* ts;
|
TokenStream* ts = test_get_tokenstream();
|
||||||
Token t;
|
Token t;
|
||||||
Token eof;
|
Token eof;
|
||||||
|
|
||||||
ts = tokenstream_open("test.c", "module");
|
|
||||||
|
|
||||||
t = tokenstream_next(ts);
|
t = tokenstream_next(ts);
|
||||||
if (t.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
|
if (t.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
|
||||||
|
|
||||||
eof = tokenstream_next(ts);
|
eof = tokenstream_next(ts);
|
||||||
if (eof.token != TOKEN_EOF) fail("expected EOF");
|
if (eof.token != TOKEN_EOF) fail("expected EOF");
|
||||||
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_tokenstream_keywords_and_symbols(void) {
|
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_MODULE) fail("expected TOKEN_MODULE");
|
||||||
if (tokenstream_next(ts).token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER (main)");
|
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_IDENTIFIER) fail("expected TOKEN_IDENTIFIER (stdio)");
|
||||||
if (tokenstream_next(ts).token != TOKEN_SEMICOLON) fail("expected TOKEN_SEMICOLON");
|
if (tokenstream_next(ts).token != TOKEN_SEMICOLON) fail("expected TOKEN_SEMICOLON");
|
||||||
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
||||||
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_tokenstream_parentheses_and_brackets(void) {
|
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_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_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_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_BRACKET_CLOSE) fail("expected TOKEN_BRACKET_CLOSE");
|
||||||
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
||||||
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_tokenstream_comma(void) {
|
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_IDENTIFIER) fail("expected a");
|
||||||
if (tokenstream_next(ts).token != TOKEN_COMMA) fail("expected comma");
|
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_COMMA) fail("expected comma");
|
||||||
if (tokenstream_next(ts).token != TOKEN_IDENTIFIER) fail("expected c");
|
if (tokenstream_next(ts).token != TOKEN_IDENTIFIER) fail("expected c");
|
||||||
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
||||||
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_tokenstream_whitespace_ignored(void) {
|
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_MODULE) fail("expected TOKEN_MODULE");
|
||||||
if (tokenstream_next(ts).token != TOKEN_IMPORT) fail("expected TOKEN_IMPORT");
|
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_SEMICOLON) fail("expected TOKEN_SEMICOLON");
|
||||||
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
||||||
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_tokenstream_void_function_signature(void) {
|
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_VOID) fail("expected TOKEN_VOID");
|
||||||
if (tokenstream_next(ts).token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER");
|
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_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_PARENT_CLOSE) fail("expected TOKEN_PARENT_CLOSE");
|
||||||
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
if (tokenstream_next(ts).token != TOKEN_EOF) fail("expected EOF");
|
||||||
|
|
||||||
tokenstream_close(ts);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_tokenstream_unknown_token(void) {
|
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");
|
if (tokenstream_next(ts).token != TOKEN_UNKNOWN) fail("expected TOKEN_UNKNOWN");
|
||||||
|
|
||||||
assert_log_file("expected error message for unknown token");
|
|
||||||
|
|
||||||
tokenstream_close(ts);
|
assert_log_file("expected error message for unknown token");
|
||||||
}
|
}
|
||||||
|
|
||||||
static void test_tokenstream_info(void) {
|
static void test_tokenstream_info(void) {
|
||||||
TokenStream* ts;
|
TokenStream* ts = test_get_tokenstream();
|
||||||
Token t1;
|
Token t1;
|
||||||
Token t2;
|
Token t2;
|
||||||
char* buf1;
|
|
||||||
char* buf2;
|
|
||||||
|
|
||||||
ts = tokenstream_open("test.c", "module main;");
|
|
||||||
|
|
||||||
t1 = tokenstream_next(ts);
|
t1 = tokenstream_next(ts);
|
||||||
if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
|
if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
|
||||||
|
assert_string("module", t1.text, "info: expected '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'");
|
|
||||||
if (t1.location.line != 1) fail("expected line 1");
|
if (t1.location.line != 1) fail("expected line 1");
|
||||||
if (t1.location.column_start != 1) fail("expected column 1");
|
if (t1.location.column_start != 1) fail("expected column 1");
|
||||||
|
|
||||||
t2 = tokenstream_next(ts);
|
t2 = tokenstream_next(ts);
|
||||||
if (t2.token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER");
|
if (t2.token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER");
|
||||||
|
assert_string("main", t2.text, "info: expected 'main'");
|
||||||
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'");
|
|
||||||
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");
|
||||||
|
|
||||||
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;
|
const char* keyword;
|
||||||
TokenType token;
|
TokenType token;
|
||||||
} KeywordMap;
|
} KeywordMap;
|
||||||
|
|
||||||
static const KeywordMap keywords[] = {
|
static const KeywordMap keywords[] = {
|
||||||
{"module", TOKEN_MODULE},
|
{"module", TOKEN_MODULE},
|
||||||
{"import", TOKEN_IMPORT},
|
{"import", TOKEN_IMPORT},
|
||||||
|
{"alias", TOKEN_ALIAS},
|
||||||
|
{"public", TOKEN_PUBLIC},
|
||||||
{"void", TOKEN_VOID},
|
{"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_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_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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Keywords and identifiers */
|
/* Keywords and identifiers */
|
||||||
@@ -239,6 +241,6 @@ Token tokenstream_next(TokenStream* ts) {
|
|||||||
|
|
||||||
/* Unknown character */
|
/* Unknown character */
|
||||||
t = create_token(ts, TOKEN_UNKNOWN, start_text, 1, start_line, start_column, line_start);
|
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;
|
return t;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ typedef enum {
|
|||||||
TOKEN_MODULE,
|
TOKEN_MODULE,
|
||||||
TOKEN_IMPORT,
|
TOKEN_IMPORT,
|
||||||
TOKEN_SEMICOLON,
|
TOKEN_SEMICOLON,
|
||||||
|
TOKEN_ALIAS,
|
||||||
|
TOKEN_PUBLIC,
|
||||||
|
|
||||||
/* Symbols */
|
/* Symbols */
|
||||||
TOKEN_PARENT_OPEN,
|
TOKEN_PARENT_OPEN,
|
||||||
@@ -21,9 +23,11 @@ typedef enum {
|
|||||||
TOKEN_BRACKET_OPEN,
|
TOKEN_BRACKET_OPEN,
|
||||||
TOKEN_BRACKET_CLOSE,
|
TOKEN_BRACKET_CLOSE,
|
||||||
TOKEN_COMMA,
|
TOKEN_COMMA,
|
||||||
|
TOKEN_ASSIGN,
|
||||||
|
|
||||||
/* Primitives */
|
/* Primitives */
|
||||||
TOKEN_VOID,
|
TOKEN_VOID,
|
||||||
|
TOKEN_BUILTIN_TYPE,
|
||||||
|
|
||||||
/* Variable */
|
/* Variable */
|
||||||
TOKEN_IDENTIFIER,
|
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