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
#define AST_H
#include <stdbool.h>
#include "bool.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. */
char* module_name;
/// @brief Whether the import is public or not.
bool is_public;
/* @brief Whether the import is public or not. */
bool is_public;
} ImportDeclaration;
/**
@@ -20,14 +20,14 @@ typedef struct {
* Every file matches an entire Module.
*/
typedef struct {
/// @brief The name of the module.
char* name;
/* @brief The name of the module. */
char* name;
/// @brief The list of imports in the module.
ImportDeclaration* imports;
/* @brief The list of imports in the module. */
ImportDeclaration* imports;
/// @brief The number of imports in the module.
size_t import_count;
/* @brief The number of imports in the module. */
size_t import_count;
} Module;
#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_TEST_DEPS := $(V0_TEST_OBJ:.o=.d)
CFLAGS += -Werror -Wall -pedantic -std=c89
v0/bin/c2: $(V0_SRC_OBJ)
$(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)
$(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
valgrind --quiet --leak-check=full --error-exitcode=1 v0/bin/test
$(TEST_CMD)
generate_golden:: v0/bin/test
GENERATE_GOLDEN=1 v0/bin/test
+9 -9
View File
@@ -9,20 +9,20 @@
#include <stddef.h>
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;
/// @brief The entire line of text where the token was found.
String line_text;
/* @brief The entire line of text where the token was found. */
String line_text;
/// @brief The line number where the token was found.
int line;
/* @brief The line number where the token was found. */
int line;
/// @brief The starting column number where the token was found.
int column_start;
/* @brief The starting column number where the token was found. */
int column_start;
/// @brief The ending column number where the token was found.
int column_end;
/* @brief The ending column number where the token was found. */
int column_end;
} Location;
#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, ...) {
/* Declarations first for C89 */
char* line_prefix = NULL;
char* formatted_msg = NULL;
char* header = 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);
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;
// Format the message
va_list args;
/* Format the message */
va_start(args, msg);
formatted_msg = format_string_va(msg, args);
va_end(args);
if (!formatted_msg) goto cleanup;
// Header logic
/* Header logic */
if (loc->filename && loc->filename[0] != '\0') {
header = format_string("--- %s ---\n", loc->filename);
} else {
@@ -47,26 +53,26 @@ void log_on_line(Location* loc, int to_column, const char* msg, ...) {
}
if (!header) goto cleanup;
size_t total_size = strlen(header) + 20 +
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) + 3 + strlen(formatted_msg) + 2 + // indent + msg\n
10;
total_size = strlen(header) + 20 +
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) + 3 + strlen(formatted_msg) + 2 + /* indent + msg\n */
10;
buffer = (char*)malloc(total_size);
if (!buffer) goto cleanup;
char* p = buffer;
p = buffer;
p += sprintf(p, "%s", header);
p += sprintf(p, "%s%.*s\n", line_prefix, (int)loc->line_text.length, loc->line_text.data);
// Caret line
for (int i = 0; i < (int)(strlen(line_prefix) + loc->column_start - 1); i++) *p++ = ' ';
for (int i = 0; i < caret_len; i++) *p++ = '^';
/* Caret line */
for (i1 = 0; i1 < (int)(strlen(line_prefix) + loc->column_start - 1); i1++) *p++ = ' ';
for (i2 = 0; i2 < caret_len; i2++) *p++ = '^';
*p++ = '\n';
// Message line
for (size_t i = 0; i < strlen(line_prefix); i++) *p++ = ' ';
/* Message line */
for (i3 = 0; i3 < strlen(line_prefix); i3++) *p++ = ' ';
p += sprintf(p, "%s\n", formatted_msg);
*p = '\0';
+3 -2
View File
@@ -1,5 +1,6 @@
#include <stdio.h>
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>
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) {
log_on_line(&t.location, t.location.column_end, "expected 'module' keyword");
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;
}
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 = (Module*)malloc(sizeof(Module));
if (module == NULL) {
fprintf(stderr, "Out of memory\n");
exit(1);
}
module->name = NULL;
module->imports = NULL;
module->import_count = 0;
module = (Module*)malloc(sizeof(Module));
if (module == NULL) {
fprintf(stderr, "Out of memory\n");
exit(1);
}
module->name = NULL;
module->imports = NULL;
module->import_count = 0;
module->name = (char*)malloc(t.text.length + 1);
if (module->name == NULL) {
@@ -51,19 +57,19 @@ Module* parser_parse(TokenStream* ts) {
break;
}
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;
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);
bool is_public = false;
if (t.token == TOKEN_IDENTIFIER && strncmp(t.text.data, "public", t.text.length) == 0) {
is_public = true;
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");
@@ -94,12 +100,14 @@ Module* parser_parse(TokenStream* ts) {
void parser_free(Module* module) {
if (module == NULL) return;
if (module->imports != NULL) {
for (size_t i = 0; i < module->import_count; i++) {
free(module->imports[i].module_name);
}
free(module->imports);
}
if (module->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);
}
free(module->imports);
}
free(module->name);
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) {
FILE* f = fopen(filepath, "r");
FILE* f;
long size;
char* content;
f = fopen(filepath, "r");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long size = ftell(f);
size = ftell(f);
fseek(f, 0, SEEK_SET);
char* content = malloc(size + 1);
content = malloc(size + 1);
if (!content) {
fclose(f);
return NULL;
@@ -51,12 +55,13 @@ char* read_file_content(const char* filepath) {
void assert_log_file(const char* msg) {
char* filepath = format_string("v0/tests/%s.log", s_currentTestName);
const char* generate;
char* content;
if (!filepath) {
fail("out of memory");
return;
}
const char* generate = getenv("GENERATE_GOLDEN");
generate = getenv("GENERATE_GOLDEN");
if (generate && strcmp(generate, "1") == 0) {
FILE* f = fopen(filepath, "w");
if (!f) {
@@ -68,7 +73,7 @@ void assert_log_file(const char* msg) {
return;
}
char* content = read_file_content(filepath);
content = read_file_content(filepath);
if (!content) {
fail("could not open golden file for reading");
free(filepath);
@@ -105,7 +110,10 @@ void assert_false(bool condition, const char* msg) {
}
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) {
fail("out of memory");
return NULL;
@@ -118,7 +126,7 @@ TokenStream* tokenstream_get_test(void) {
free(filepath);
return NULL;
}
TokenStream* ts = tokenstream_open(filepath, s_testSource);
ts = tokenstream_open(filepath, s_testSource);
free(filepath);
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);
s_logOutput = NULL;
}
@@ -180,16 +188,23 @@ static TestCase s_tests[] = {
int main(int argc, char** argv) {
/* Declarations first for C89 */
const char** failedTests;
int failedCount;
int i;
int j;
(void)argc;
(void)argv;
s_totalTests = sizeof(s_tests) / sizeof(s_tests[0]);
s_greenTests = 0;
const char* failedTests[s_totalTests + 1];
int failedCount = 0;
/* Allocate failed tests array dynamically to avoid VLAs */
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;
log_set_output(log_append);
printf("%s...", s_tests[i].name);
@@ -215,11 +230,12 @@ int main(int argc, char** argv) {
if (failedCount > 0) {
printf("\nFailed tests:\n");
for (int i = 0; i < failedCount; i++) {
printf(" - %s\n", failedTests[i]);
for (j = 0; j < failedCount; j++) {
printf(" - %s\n", failedTests[j]);
}
}
printf("\n%d/%d tests passed.\n", s_greenTests, s_totalTests);
free(failedTests);
return failedCount > 0 ? 1 : 0;
}
+2 -2
View File
@@ -6,8 +6,6 @@
#include "token.h"
#include <stdbool.h>
typedef void (*Test)(void);
/**
@@ -56,6 +54,8 @@ void assert_int(int expected, int actual, const char* msg);
/**
* Asserts that a condition is true.
*/
#include "bool.h"
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'");
log_set_output(NULL); // Reset to default
log_set_output(NULL);
free(s_lastLoggedError);
s_lastLoggedError = NULL;
}
static void test_log_on_line(void) {
Location loc = {
.filename = "v0/tests/log_on_line.c2",
.line_text = { "int main() []", 13 },
.line = 1,
.column_start = 12,
.column_end = 13
};
Location loc;
loc.filename = "v0/tests/log_on_line.c2";
loc.line_text.data = "int main() []";
loc.line_text.length = 13;
loc.line = 1;
loc.column_start = 12;
loc.column_end = 13;
log_on_line(&loc, 13, "unexpected token");
assert_log_file("expected formatted error message");
}
static void test_log_on_line_variadic(void) {
Location loc = {
.filename = "v0/tests/log_on_line_variadic.c2",
.line_text = { "int main() []", 13 },
.line = 1,
.column_start = 12,
.column_end = 13
};
Location loc;
loc.filename = "v0/tests/log_on_line_variadic.c2";
loc.line_text.data = "int main() []";
loc.line_text.length = 13;
loc.line = 1;
loc.column_start = 12;
loc.column_end = 13;
log_on_line(&loc, 13, "unexpected token '%c'", 'x');
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) {
TokenStream* ts = tokenstream_open("test.c", "module");
TokenStream* ts;
Token t;
Token eof;
Token t = tokenstream_next(ts);
if (t.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
ts = tokenstream_open("test.c", "module");
Token eof = tokenstream_next(ts);
if (eof.token != TOKEN_EOF) fail("expected EOF");
t = tokenstream_next(ts);
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) {
@@ -93,29 +97,35 @@ static void test_tokenstream_unknown_token(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);
if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
char* buf1 = malloc((size_t)t1.text.length + 1);
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'");
if (t1.location.line != 1) fail("expected line 1");
if (t1.location.column_start != 1) fail("expected column 1");
if (t1.location.line != 1) fail("expected line 1");
if (t1.location.column_start != 1) fail("expected column 1");
Token t2 = tokenstream_next(ts);
if (t2.token != TOKEN_IDENTIFIER) fail("expected TOKEN_IDENTIFIER");
char* buf2 = malloc((size_t)t2.text.length + 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'");
if (t2.location.line != 1) fail("expected line 1");
if (t2.location.column_start != 8) fail("expected column 8");
if (t2.location.line != 1) fail("expected line 1");
if (t2.location.column_start != 8) fail("expected column 8");
free(buf1);
free(buf2);
+65 -50
View File
@@ -5,17 +5,17 @@
#include <string.h>
struct TokenStream {
char* filename;
const char* code;
size_t pos;
int line;
int column;
const char* line_start;
char* filename;
const char* code;
size_t pos;
int line;
int column;
const char* line_start;
// End of last non-EOF token
int last_line;
int last_column_end;
const char* last_line_start;
/* End of last non-EOF token */
int last_line;
int last_column_end;
const char* last_line_start;
};
/**
@@ -38,8 +38,9 @@ static const KeywordMap keywords[] = {
* Returns TOKEN_IDENTIFIER if not found.
*/
static TokenType lookup_keyword(const char* str, size_t length) {
int count = sizeof(keywords) / sizeof(keywords[0]);
for (int i = 0; i < count; i++) {
int count = sizeof(keywords) / sizeof(keywords[0]);
int i;
for (i = 0; i < count; i++) {
if (strlen(keywords[i].keyword) == length &&
strncmp(keywords[i].keyword, str, length) == 0) {
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) {
if (code == NULL) return NULL;
/* Declarations first for C89 */
TokenStream* ts;
const char* name_src;
TokenStream* ts = (TokenStream*)malloc(sizeof(struct TokenStream));
if (ts == NULL) {
return NULL;
}
if (code == 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);
if (ts->filename) {
memcpy(ts->filename, name_src, strlen(name_src) + 1);
}
ts->code = code;
ts->pos = 0;
ts->line = 1;
ts->column = 1;
ts->line_start = code;
ts->last_line = 1;
ts->last_column_end = 0;
ts->last_line_start = code;
return ts;
ts->code = code;
ts->pos = 0;
ts->line = 1;
ts->column = 1;
ts->line_start = code;
ts->last_line = 1;
ts->last_column_end = 0;
ts->last_line_start = code;
return ts;
}
void tokenstream_close(TokenStream* ts) {
@@ -147,16 +152,22 @@ void tokenstream_close(TokenStream* ts) {
}
Token tokenstream_next(TokenStream* ts) {
if (ts == NULL) {
Token t = {0};
t.token = TOKEN_EOF;
return t;
}
/* Declarations first for C89 */
char c;
int start_line;
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 */
while ((c = peek_char(ts)) != '\0') {
/* Skip whitespace and comments */
while ((c = peek_char(ts)) != '\0') {
if (isspace(c)) {
read_char(ts);
continue;
@@ -194,12 +205,12 @@ Token tokenstream_next(TokenStream* ts) {
return t;
}
int start_line = ts->line;
int start_column = ts->column;
const char* line_start = ts->line_start;
const char* start_text = &ts->code[ts->pos];
start_line = ts->line;
start_column = ts->column;
line_start = ts->line_start;
start_text = &ts->code[ts->pos];
c = read_char(ts);
c = read_char(ts);
/* Single-character tokens */
switch (c) {
@@ -212,18 +223,22 @@ Token tokenstream_next(TokenStream* ts) {
}
/* Keywords and identifiers */
if (is_identifier_start(c)) {
size_t length = 1;
while (is_identifier_part(peek_char(ts))) {
read_char(ts);
length++;
}
TokenType type = lookup_keyword(start_text, length);
return create_token(ts, type, start_text, length, start_line, start_column, line_start);
}
if (is_identifier_start(c)) {
/* Declarations first for C89 */
size_t length;
TokenType type;
length = 1;
while (is_identifier_part(peek_char(ts))) {
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 */
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);
return t;
}
+18 -18
View File
@@ -10,41 +10,41 @@
* A list of all possible tokens.
*/
typedef enum {
// Keywords
TOKEN_MODULE,
/* Keywords */
TOKEN_MODULE,
TOKEN_IMPORT,
TOKEN_SEMICOLON,
// Symbols
TOKEN_PARENT_OPEN,
/* Symbols */
TOKEN_PARENT_OPEN,
TOKEN_PARENT_CLOSE,
TOKEN_BRACKET_OPEN,
TOKEN_BRACKET_CLOSE,
TOKEN_COMMA,
// Primitives
TOKEN_VOID,
/* Primitives */
TOKEN_VOID,
// Variable
TOKEN_IDENTIFIER,
/* Variable */
TOKEN_IDENTIFIER,
// Others
TOKEN_EOF,
TOKEN_UNKNOWN,
/* Others */
TOKEN_EOF,
TOKEN_UNKNOWN
} TokenType;
/**
* Holds additional information about a token.
*/
typedef struct {
/// @brief The actual token.
TokenType token;
/* @brief The actual token. */
TokenType token;
/// @brief The textual representation of a token.
String text;
/* @brief The textual representation of a token. */
String text;
/// @brief The location of the token.
Location location;
/* @brief The location of the token. */
Location location;
} Token;
typedef struct TokenStream TokenStream;
@@ -71,4 +71,4 @@ void tokenstream_close(TokenStream* ts);
*/
Token tokenstream_next(TokenStream* ts);
#endif
#endif
+24 -5
View File
@@ -2,26 +2,45 @@
#include <stdio.h>
#include <stdlib.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) {
if (!fmt) return NULL;
/* Declarations first to satisfy -std=c89 */
va_list args_copy;
int needed;
char* buf;
if (!fmt) return NULL;
va_copy(args_copy, args);
int needed = vsnprintf(NULL, 0, fmt, args_copy);
needed = vsnprintf(NULL, 0, fmt, args_copy);
va_end(args_copy);
if (needed < 0) return NULL;
char* buf = (char*)malloc((size_t)needed + 1);
buf = (char*)malloc((size_t)needed + 1);
if (!buf) return NULL;
vsnprintf(buf, (size_t)needed + 1, fmt, args);
return buf;
}
char* format_string(const char* fmt, ...) {
if (!fmt) return NULL;
/* Declarations first to satisfy -std=c89 */
va_list args;
char* s;
if (!fmt) return NULL;
va_start(args, fmt);
char* s = format_string_va(fmt, args);
s = format_string_va(fmt, args);
va_end(args);
return s;
}