Convert codebase to C89 compatibility and update test scripts

This commit is contained in:
2026-04-29 10:20:30 +02:00
parent 189c21667b
commit 146aa4d9d1
14 changed files with 287 additions and 192 deletions
+11 -11
View File
@@ -4,15 +4,15 @@
#ifndef AST_H #ifndef AST_H
#define AST_H #define AST_H
#include <stdbool.h> #include "bool.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; 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;
/** /**
@@ -20,14 +20,14 @@ typedef struct {
* 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; 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;
} Module; } Module;
#endif #endif
+10
View File
@@ -0,0 +1,10 @@
/* Minimal boolean type for C89 compatibility */
#ifndef BOOL_H
#define BOOL_H
typedef int bool;
#define true 1
#define false 0
#endif
+11 -1
View File
@@ -11,6 +11,8 @@ 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
v0/bin/c2: $(V0_SRC_OBJ) v0/bin/c2: $(V0_SRC_OBJ)
$(CC) $(CFLAGS) -o $@ $^ $(CC) $(CFLAGS) -o $@ $^
@@ -19,8 +21,16 @@ V0_SRC_OBJ_NO_MAIN := $(filter-out v0/bin/main.o,$(V0_SRC_OBJ))
v0/bin/test: $(V0_SRC_OBJ_NO_MAIN) $(V0_TEST_OBJ) v0/bin/test: $(V0_SRC_OBJ_NO_MAIN) $(V0_TEST_OBJ)
$(CC) $(CFLAGS) -o $@ $^ $(CC) $(CFLAGS) -o $@ $^
# Only run tests under valgrind on Linux. On macOS (Darwin) valgrind is
# typically unavailable or unsupported, so run the test binary directly.
ifeq ($(shell uname -s),Linux)
TEST_CMD := valgrind --quiet --leak-check=full --error-exitcode=1 v0/bin/test
else
TEST_CMD := v0/bin/test
endif
test:: v0/bin/test test:: v0/bin/test
valgrind --quiet --leak-check=full --error-exitcode=1 v0/bin/test $(TEST_CMD)
generate_golden:: v0/bin/test generate_golden:: v0/bin/test
GENERATE_GOLDEN=1 v0/bin/test GENERATE_GOLDEN=1 v0/bin/test
+9 -9
View File
@@ -9,20 +9,20 @@
#include <stddef.h> #include <stddef.h>
typedef struct { typedef struct {
/// @brief The name of the file where the token was found. /* @brief The name of the file where the token was found. */
char* filename; char* filename;
/// @brief The entire line of text where the token was found. /* @brief The entire line of text where the token was found. */
String line_text; String line_text;
/// @brief The line number where the token was found. /* @brief The line number where the token was found. */
int line; int line;
/// @brief The starting column number where the token was found. /* @brief The starting column number where the token was found. */
int column_start; int column_start;
/// @brief The ending column number where the token was found. /* @brief The ending column number where the token was found. */
int column_end; int column_end;
} Location; } Location;
#endif #endif
+21 -15
View File
@@ -21,25 +21,31 @@ void log_error(const char* msg) {
} }
void log_on_line(Location* loc, int to_column, const char* msg, ...) { void log_on_line(Location* loc, int to_column, const char* msg, ...) {
/* Declarations first for C89 */
char* line_prefix = NULL; char* line_prefix = NULL;
char* formatted_msg = NULL; char* formatted_msg = NULL;
char* header = NULL; char* header = NULL;
char* buffer = NULL; char* buffer = NULL;
va_list args;
int caret_len;
char* p;
int i1, i2;
size_t i3;
size_t total_size;
line_prefix = format_string("%d| ", loc->line); line_prefix = format_string("%d| ", loc->line);
if (!line_prefix) goto cleanup; if (!line_prefix) goto cleanup;
int caret_len = to_column - loc->column_start + 1; caret_len = to_column - loc->column_start + 1;
if (caret_len < 1) caret_len = 1; if (caret_len < 1) caret_len = 1;
// Format the message /* Format the message */
va_list args;
va_start(args, msg); va_start(args, msg);
formatted_msg = format_string_va(msg, args); formatted_msg = format_string_va(msg, args);
va_end(args); va_end(args);
if (!formatted_msg) goto cleanup; if (!formatted_msg) goto cleanup;
// Header logic /* Header logic */
if (loc->filename && loc->filename[0] != '\0') { if (loc->filename && loc->filename[0] != '\0') {
header = format_string("--- %s ---\n", loc->filename); header = format_string("--- %s ---\n", loc->filename);
} else { } else {
@@ -47,26 +53,26 @@ void log_on_line(Location* loc, int to_column, const char* msg, ...) {
} }
if (!header) goto cleanup; if (!header) goto cleanup;
size_t total_size = strlen(header) + 20 + total_size = strlen(header) + 20 +
strlen(line_prefix) + loc->line_text.length + 2 + // line| text\n strlen(line_prefix) + loc->line_text.length + 2 + /* line| text\n */
strlen(line_prefix) + loc->column_start - 1 + caret_len + 2 + // indent + ^^\n strlen(line_prefix) + loc->column_start - 1 + caret_len + 2 + /* indent + ^^\n */
strlen(line_prefix) + 3 + strlen(formatted_msg) + 2 + // indent + msg\n strlen(line_prefix) + 3 + strlen(formatted_msg) + 2 + /* indent + msg\n */
10; 10;
buffer = (char*)malloc(total_size); buffer = (char*)malloc(total_size);
if (!buffer) goto cleanup; if (!buffer) goto cleanup;
char* p = buffer; p = buffer;
p += sprintf(p, "%s", header); p += sprintf(p, "%s", header);
p += sprintf(p, "%s%.*s\n", line_prefix, (int)loc->line_text.length, loc->line_text.data); p += sprintf(p, "%s%.*s\n", line_prefix, (int)loc->line_text.length, loc->line_text.data);
// Caret line /* Caret line */
for (int i = 0; i < (int)(strlen(line_prefix) + loc->column_start - 1); i++) *p++ = ' '; for (i1 = 0; i1 < (int)(strlen(line_prefix) + loc->column_start - 1); i1++) *p++ = ' ';
for (int i = 0; i < caret_len; i++) *p++ = '^'; for (i2 = 0; i2 < caret_len; i2++) *p++ = '^';
*p++ = '\n'; *p++ = '\n';
// Message line /* Message line */
for (size_t i = 0; i < strlen(line_prefix); i++) *p++ = ' '; for (i3 = 0; i3 < strlen(line_prefix); i3++) *p++ = ' ';
p += sprintf(p, "%s\n", formatted_msg); p += sprintf(p, "%s\n", formatted_msg);
*p = '\0'; *p = '\0';
+3 -2
View File
@@ -1,5 +1,6 @@
#include <stdio.h> #include <stdio.h>
int main(int argc, char** argv) { int main(int argc, char** argv) {
puts("Hello, world"); puts("Hello, world");
} return 0;
}
+39 -31
View File
@@ -5,26 +5,32 @@
#include <stdio.h> #include <stdio.h>
Module* parser_parse(TokenStream* ts) { Module* parser_parse(TokenStream* ts) {
Token t = tokenstream_next(ts); /* Declarations first for C89 */
Token t;
Module* module;
ImportDeclaration* new_imports;
int is_public;
t = tokenstream_next(ts);
if (t.token != TOKEN_MODULE) { if (t.token != TOKEN_MODULE) {
log_on_line(&t.location, t.location.column_end, "expected 'module' keyword"); log_on_line(&t.location, t.location.column_end, "expected 'module' keyword");
return NULL; return NULL;
} }
t = tokenstream_next(ts); t = tokenstream_next(ts);
if (t.token != TOKEN_IDENTIFIER) { if (t.token != TOKEN_IDENTIFIER) {
log_on_line(&t.location, t.location.column_end, "expected module name"); log_on_line(&t.location, t.location.column_end, "expected module name");
return NULL; return NULL;
} }
Module* module = (Module*)malloc(sizeof(Module)); module = (Module*)malloc(sizeof(Module));
if (module == NULL) { if (module == NULL) {
fprintf(stderr, "Out of memory\n"); fprintf(stderr, "Out of memory\n");
exit(1); exit(1);
} }
module->name = NULL; module->name = NULL;
module->imports = NULL; module->imports = NULL;
module->import_count = 0; module->import_count = 0;
module->name = (char*)malloc(t.text.length + 1); module->name = (char*)malloc(t.text.length + 1);
if (module->name == NULL) { if (module->name == NULL) {
@@ -51,19 +57,19 @@ Module* parser_parse(TokenStream* ts) {
break; break;
} }
ImportDeclaration* new_imports = realloc(module->imports, (module->import_count + 1) * sizeof(ImportDeclaration)); new_imports = realloc(module->imports, (module->import_count + 1) * sizeof(ImportDeclaration));
if (!new_imports) { if (!new_imports) {
fprintf(stderr, "Out of memory\n"); fprintf(stderr, "Out of memory\n");
exit(1); exit(1);
} }
module->imports = new_imports; module->imports = new_imports;
t = tokenstream_next(ts); t = tokenstream_next(ts);
bool is_public = false; is_public = 0;
if (t.token == TOKEN_IDENTIFIER && strncmp(t.text.data, "public", t.text.length) == 0) { if (t.token == TOKEN_IDENTIFIER && strncmp(t.text.data, "public", t.text.length) == 0) {
is_public = true; is_public = 1;
t = tokenstream_next(ts); t = tokenstream_next(ts);
} }
if (t.token != TOKEN_IDENTIFIER) { if (t.token != TOKEN_IDENTIFIER) {
log_on_line(&t.location, t.location.column_end, "expected module name to import"); log_on_line(&t.location, t.location.column_end, "expected module name to import");
@@ -94,12 +100,14 @@ Module* parser_parse(TokenStream* ts) {
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) {
for (size_t i = 0; i < module->import_count; i++) { /* C89: declare index variable before the loop */
free(module->imports[i].module_name); size_t i;
} for (i = 0; i < module->import_count; i++) {
free(module->imports); free(module->imports[i].module_name);
} }
free(module->imports);
}
free(module->name); free(module->name);
free(module); free(module);
} }
+30 -14
View File
@@ -33,12 +33,16 @@ void assert_log(const char* expected, const char* msg) {
} }
char* read_file_content(const char* filepath) { char* read_file_content(const char* filepath) {
FILE* f = fopen(filepath, "r"); FILE* f;
long size;
char* content;
f = fopen(filepath, "r");
if (!f) return NULL; if (!f) return NULL;
fseek(f, 0, SEEK_END); fseek(f, 0, SEEK_END);
long size = ftell(f); size = ftell(f);
fseek(f, 0, SEEK_SET); fseek(f, 0, SEEK_SET);
char* content = malloc(size + 1); content = malloc(size + 1);
if (!content) { if (!content) {
fclose(f); fclose(f);
return NULL; return NULL;
@@ -51,12 +55,13 @@ char* read_file_content(const char* filepath) {
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;
char* content;
if (!filepath) { if (!filepath) {
fail("out of memory"); fail("out of memory");
return; return;
} }
generate = getenv("GENERATE_GOLDEN");
const char* generate = getenv("GENERATE_GOLDEN");
if (generate && strcmp(generate, "1") == 0) { if (generate && strcmp(generate, "1") == 0) {
FILE* f = fopen(filepath, "w"); FILE* f = fopen(filepath, "w");
if (!f) { if (!f) {
@@ -68,7 +73,7 @@ void assert_log_file(const char* msg) {
return; return;
} }
char* content = read_file_content(filepath); content = read_file_content(filepath);
if (!content) { if (!content) {
fail("could not open golden file for reading"); fail("could not open golden file for reading");
free(filepath); free(filepath);
@@ -105,7 +110,10 @@ void assert_false(bool condition, const char* msg) {
} }
TokenStream* tokenstream_get_test(void) { TokenStream* tokenstream_get_test(void) {
char* filepath = format_string("v0/tests/%s.c2", s_currentTestName); char* filepath = NULL;
TokenStream* ts = NULL;
filepath = format_string("v0/tests/%s.c2", s_currentTestName);
if (!filepath) { if (!filepath) {
fail("out of memory"); fail("out of memory");
return NULL; return NULL;
@@ -118,7 +126,7 @@ TokenStream* tokenstream_get_test(void) {
free(filepath); free(filepath);
return NULL; return NULL;
} }
TokenStream* ts = tokenstream_open(filepath, s_testSource); ts = tokenstream_open(filepath, s_testSource);
free(filepath); free(filepath);
return ts; return ts;
} }
@@ -139,7 +147,7 @@ static void log_append(const char* msg) {
} }
} }
static void log_clear() { static void log_clear(void) {
free(s_logOutput); free(s_logOutput);
s_logOutput = NULL; s_logOutput = NULL;
} }
@@ -180,16 +188,23 @@ static TestCase s_tests[] = {
int main(int argc, char** argv) { int main(int argc, char** argv) {
/* Declarations first for C89 */
const char** failedTests;
int failedCount;
int i;
int j;
(void)argc; (void)argc;
(void)argv; (void)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;
const char* failedTests[s_totalTests + 1]; /* Allocate failed tests array dynamically to avoid VLAs */
int failedCount = 0; failedTests = (const char**)malloc((s_totalTests + 1) * sizeof(const char*));
failedCount = 0;
for (int i = 0; i < s_totalTests; i++) { for (i = 0; i < s_totalTests; i++) {
s_currentTestName = s_tests[i].name; s_currentTestName = s_tests[i].name;
log_set_output(log_append); log_set_output(log_append);
printf("%s...", s_tests[i].name); printf("%s...", s_tests[i].name);
@@ -215,11 +230,12 @@ int main(int argc, char** argv) {
if (failedCount > 0) { if (failedCount > 0) {
printf("\nFailed tests:\n"); printf("\nFailed tests:\n");
for (int i = 0; i < failedCount; i++) { for (j = 0; j < failedCount; j++) {
printf(" - %s\n", failedTests[i]); printf(" - %s\n", failedTests[j]);
} }
} }
printf("\n%d/%d tests passed.\n", s_greenTests, s_totalTests); printf("\n%d/%d tests passed.\n", s_greenTests, s_totalTests);
free(failedTests);
return failedCount > 0 ? 1 : 0; return failedCount > 0 ? 1 : 0;
} }
+2 -2
View File
@@ -6,8 +6,6 @@
#include "token.h" #include "token.h"
#include <stdbool.h>
typedef void (*Test)(void); typedef void (*Test)(void);
/** /**
@@ -56,6 +54,8 @@ void assert_int(int expected, int actual, const char* msg);
/** /**
* Asserts that a condition is true. * Asserts that a condition is true.
*/ */
#include "bool.h"
void assert_true(bool condition, const char* msg); void assert_true(bool condition, const char* msg);
/** /**
+15 -15
View File
@@ -20,32 +20,32 @@ static void test_log_error(void) {
assert_str("test error message", s_lastLoggedError, "expected 'test error message'"); assert_str("test error message", s_lastLoggedError, "expected 'test error message'");
log_set_output(NULL); // Reset to default log_set_output(NULL);
free(s_lastLoggedError); free(s_lastLoggedError);
s_lastLoggedError = NULL; s_lastLoggedError = NULL;
} }
static void test_log_on_line(void) { static void test_log_on_line(void) {
Location loc = { Location loc;
.filename = "v0/tests/log_on_line.c2", loc.filename = "v0/tests/log_on_line.c2";
.line_text = { "int main() []", 13 }, loc.line_text.data = "int main() []";
.line = 1, loc.line_text.length = 13;
.column_start = 12, loc.line = 1;
.column_end = 13 loc.column_start = 12;
}; loc.column_end = 13;
log_on_line(&loc, 13, "unexpected token"); log_on_line(&loc, 13, "unexpected token");
assert_log_file("expected formatted error message"); assert_log_file("expected formatted error message");
} }
static void test_log_on_line_variadic(void) { static void test_log_on_line_variadic(void) {
Location loc = { Location loc;
.filename = "v0/tests/log_on_line_variadic.c2", loc.filename = "v0/tests/log_on_line_variadic.c2";
.line_text = { "int main() []", 13 }, loc.line_text.data = "int main() []";
.line = 1, loc.line_text.length = 13;
.column_start = 12, loc.line = 1;
.column_end = 13 loc.column_start = 12;
}; loc.column_end = 13;
log_on_line(&loc, 13, "unexpected token '%c'", 'x'); log_on_line(&loc, 13, "unexpected token '%c'", 'x');
assert_log_file("expected formatted error message with variadic args"); assert_log_file("expected formatted error message with variadic args");
+29 -19
View File
@@ -9,15 +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_open("test.c", "module"); TokenStream* ts;
Token t;
Token eof;
Token t = tokenstream_next(ts); ts = tokenstream_open("test.c", "module");
if (t.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
Token eof = tokenstream_next(ts); t = tokenstream_next(ts);
if (eof.token != TOKEN_EOF) fail("expected EOF"); if (t.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
tokenstream_close(ts); eof = tokenstream_next(ts);
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) {
@@ -93,29 +97,35 @@ static void test_tokenstream_unknown_token(void) {
} }
static void test_tokenstream_info(void) { static void test_tokenstream_info(void) {
TokenStream* ts = tokenstream_open("test.c", "module main;"); TokenStream* ts;
Token t1;
Token t2;
char* buf1;
char* buf2;
Token t1 = tokenstream_next(ts); ts = tokenstream_open("test.c", "module main;");
if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
t1 = tokenstream_next(ts);
char* buf1 = malloc((size_t)t1.text.length + 1); if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
buf1 = malloc((size_t)t1.text.length + 1);
if (!buf1) fail("out of memory"); if (!buf1) fail("out of memory");
memcpy(buf1, t1.text.data, t1.text.length); memcpy(buf1, t1.text.data, t1.text.length);
buf1[t1.text.length] = '\0'; buf1[t1.text.length] = '\0';
assert_str("module", buf1, "info: expected 'module'"); 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");
Token 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");
char* buf2 = malloc((size_t)t2.text.length + 1); buf2 = malloc((size_t)t2.text.length + 1);
if (!buf2) { free(buf1); fail("out of memory"); } if (!buf2) { free(buf1); fail("out of memory"); }
memcpy(buf2, t2.text.data, t2.text.length); memcpy(buf2, t2.text.data, t2.text.length);
buf2[t2.text.length] = '\0'; buf2[t2.text.length] = '\0';
assert_str("main", buf2, "info: expected 'main'"); 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(buf1);
free(buf2); free(buf2);
+65 -50
View File
@@ -5,17 +5,17 @@
#include <string.h> #include <string.h>
struct TokenStream { struct TokenStream {
char* filename; char* filename;
const char* code; const char* code;
size_t pos; size_t pos;
int line; int line;
int column; int column;
const char* line_start; const char* line_start;
// End of last non-EOF token /* End of last non-EOF token */
int last_line; int last_line;
int last_column_end; int last_column_end;
const char* last_line_start; const char* last_line_start;
}; };
/** /**
@@ -38,8 +38,9 @@ static const KeywordMap keywords[] = {
* Returns TOKEN_IDENTIFIER if not found. * Returns TOKEN_IDENTIFIER if not found.
*/ */
static TokenType lookup_keyword(const char* str, size_t length) { static TokenType lookup_keyword(const char* str, size_t length) {
int count = sizeof(keywords) / sizeof(keywords[0]); int count = sizeof(keywords) / sizeof(keywords[0]);
for (int i = 0; i < count; i++) { int i;
for (i = 0; i < count; i++) {
if (strlen(keywords[i].keyword) == length && if (strlen(keywords[i].keyword) == length &&
strncmp(keywords[i].keyword, str, length) == 0) { strncmp(keywords[i].keyword, str, length) == 0) {
return keywords[i].token; return keywords[i].token;
@@ -117,27 +118,31 @@ static Token create_token(TokenStream* ts, TokenType type, const char* text, siz
} }
TokenStream* tokenstream_open(const char* filename, const char* code) { TokenStream* tokenstream_open(const char* filename, const char* code) {
if (code == NULL) return NULL; /* Declarations first for C89 */
TokenStream* ts;
const char* name_src;
TokenStream* ts = (TokenStream*)malloc(sizeof(struct TokenStream)); if (code == NULL) return NULL;
if (ts == NULL) {
return NULL;
}
const char* name_src = filename ? filename : "unknown"; ts = (TokenStream*)malloc(sizeof(struct TokenStream));
if (ts == NULL) {
return NULL;
}
name_src = filename ? filename : "unknown";
ts->filename = malloc(strlen(name_src) + 1); ts->filename = malloc(strlen(name_src) + 1);
if (ts->filename) { if (ts->filename) {
memcpy(ts->filename, name_src, strlen(name_src) + 1); memcpy(ts->filename, name_src, strlen(name_src) + 1);
} }
ts->code = code; ts->code = code;
ts->pos = 0; ts->pos = 0;
ts->line = 1; ts->line = 1;
ts->column = 1; ts->column = 1;
ts->line_start = code; ts->line_start = code;
ts->last_line = 1; ts->last_line = 1;
ts->last_column_end = 0; ts->last_column_end = 0;
ts->last_line_start = code; ts->last_line_start = code;
return ts; return ts;
} }
void tokenstream_close(TokenStream* ts) { void tokenstream_close(TokenStream* ts) {
@@ -147,16 +152,22 @@ void tokenstream_close(TokenStream* ts) {
} }
Token tokenstream_next(TokenStream* ts) { Token tokenstream_next(TokenStream* ts) {
if (ts == NULL) { /* Declarations first for C89 */
Token t = {0}; char c;
t.token = TOKEN_EOF; int start_line;
return t; int start_column;
} const char* line_start;
const char* start_text;
Token t;
char c; if (ts == NULL) {
Token t = {0};
t.token = TOKEN_EOF;
return t;
}
/* Skip whitespace and comments */ /* Skip whitespace and comments */
while ((c = peek_char(ts)) != '\0') { while ((c = peek_char(ts)) != '\0') {
if (isspace(c)) { if (isspace(c)) {
read_char(ts); read_char(ts);
continue; continue;
@@ -194,12 +205,12 @@ Token tokenstream_next(TokenStream* ts) {
return t; return t;
} }
int start_line = ts->line; start_line = ts->line;
int start_column = ts->column; start_column = ts->column;
const char* line_start = ts->line_start; line_start = ts->line_start;
const char* start_text = &ts->code[ts->pos]; start_text = &ts->code[ts->pos];
c = read_char(ts); c = read_char(ts);
/* Single-character tokens */ /* Single-character tokens */
switch (c) { switch (c) {
@@ -212,18 +223,22 @@ Token tokenstream_next(TokenStream* ts) {
} }
/* Keywords and identifiers */ /* Keywords and identifiers */
if (is_identifier_start(c)) { if (is_identifier_start(c)) {
size_t length = 1; /* Declarations first for C89 */
while (is_identifier_part(peek_char(ts))) { size_t length;
read_char(ts); TokenType type;
length++;
} length = 1;
TokenType type = lookup_keyword(start_text, length); while (is_identifier_part(peek_char(ts))) {
return create_token(ts, type, start_text, length, start_line, start_column, line_start); read_char(ts);
} length++;
}
type = lookup_keyword(start_text, length);
return create_token(ts, type, start_text, length, start_line, start_column, line_start);
}
/* Unknown character */ /* Unknown character */
Token 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, t.location.column_end, "unexpected token '%c'", c);
return t; return t;
} }
+18 -18
View File
@@ -10,41 +10,41 @@
* A list of all possible tokens. * A list of all possible tokens.
*/ */
typedef enum { typedef enum {
// Keywords /* Keywords */
TOKEN_MODULE, TOKEN_MODULE,
TOKEN_IMPORT, TOKEN_IMPORT,
TOKEN_SEMICOLON, TOKEN_SEMICOLON,
// Symbols /* Symbols */
TOKEN_PARENT_OPEN, TOKEN_PARENT_OPEN,
TOKEN_PARENT_CLOSE, TOKEN_PARENT_CLOSE,
TOKEN_BRACKET_OPEN, TOKEN_BRACKET_OPEN,
TOKEN_BRACKET_CLOSE, TOKEN_BRACKET_CLOSE,
TOKEN_COMMA, TOKEN_COMMA,
// Primitives /* Primitives */
TOKEN_VOID, TOKEN_VOID,
// Variable /* Variable */
TOKEN_IDENTIFIER, TOKEN_IDENTIFIER,
// Others /* Others */
TOKEN_EOF, TOKEN_EOF,
TOKEN_UNKNOWN, TOKEN_UNKNOWN
} TokenType; } TokenType;
/** /**
* Holds additional information about a token. * Holds additional information about a token.
*/ */
typedef struct { typedef struct {
/// @brief The actual token. /* @brief The actual token. */
TokenType token; TokenType token;
/// @brief The textual representation of a token. /* @brief The textual representation of a token. */
String text; String text;
/// @brief The location of the token. /* @brief The location of the token. */
Location location; Location location;
} Token; } Token;
typedef struct TokenStream TokenStream; typedef struct TokenStream TokenStream;
@@ -71,4 +71,4 @@ void tokenstream_close(TokenStream* ts);
*/ */
Token tokenstream_next(TokenStream* ts); Token tokenstream_next(TokenStream* ts);
#endif #endif
+24 -5
View File
@@ -2,26 +2,45 @@
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <stdarg.h>
/* Portable va_copy fallback for pre-C99 or platforms without va_copy. */
#ifndef va_copy
# if defined(__va_copy)
# define va_copy(dest, src) __va_copy(dest, src)
# else
# define va_copy(dest, src) ((dest) = (src))
# endif
#endif
char* format_string_va(const char* fmt, va_list args) { char* format_string_va(const char* fmt, va_list args) {
if (!fmt) return NULL; /* Declarations first to satisfy -std=c89 */
va_list args_copy; va_list args_copy;
int needed;
char* buf;
if (!fmt) return NULL;
va_copy(args_copy, args); va_copy(args_copy, args);
int needed = vsnprintf(NULL, 0, fmt, args_copy); needed = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy); va_end(args_copy);
if (needed < 0) return NULL; if (needed < 0) return NULL;
char* buf = (char*)malloc((size_t)needed + 1); buf = (char*)malloc((size_t)needed + 1);
if (!buf) return NULL; if (!buf) return NULL;
vsnprintf(buf, (size_t)needed + 1, fmt, args); vsnprintf(buf, (size_t)needed + 1, fmt, args);
return buf; return buf;
} }
char* format_string(const char* fmt, ...) { char* format_string(const char* fmt, ...) {
if (!fmt) return NULL; /* Declarations first to satisfy -std=c89 */
va_list args; va_list args;
char* s;
if (!fmt) return NULL;
va_start(args, fmt); va_start(args, fmt);
char* s = format_string_va(fmt, args); s = format_string_va(fmt, args);
va_end(args); va_end(args);
return s; return s;
} }