Convert codebase to C89 compatibility and update test scripts
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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';
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <stdio.h>
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
puts("Hello, world");
|
||||
}
|
||||
puts("Hello, world");
|
||||
return 0;
|
||||
}
|
||||
|
||||
+39
-31
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user