Convert codebase to C89 compatibility and update test scripts
This commit is contained in:
@@ -4,14 +4,14 @@
|
|||||||
#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,13 +20,13 @@ 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;
|
||||||
|
|
||||||
|
|||||||
@@ -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_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
|
||||||
|
|||||||
+5
-5
@@ -9,19 +9,19 @@
|
|||||||
#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;
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -2,4 +2,5 @@
|
|||||||
|
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
puts("Hello, world");
|
puts("Hello, world");
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
+14
-6
@@ -5,7 +5,13 @@
|
|||||||
#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;
|
||||||
@@ -17,7 +23,7 @@ Module* parser_parse(TokenStream* ts) {
|
|||||||
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);
|
||||||
@@ -51,7 +57,7 @@ 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);
|
||||||
@@ -59,9 +65,9 @@ Module* parser_parse(TokenStream* ts) {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +101,9 @@ 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 */
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < module->import_count; i++) {
|
||||||
free(module->imports[i].module_name);
|
free(module->imports[i].module_name);
|
||||||
}
|
}
|
||||||
free(module->imports);
|
free(module->imports);
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
@@ -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");
|
||||||
|
|||||||
+18
-8
@@ -9,12 +9,16 @@ 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");
|
||||||
|
|
||||||
|
t = tokenstream_next(ts);
|
||||||
if (t.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
|
if (t.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
|
||||||
|
|
||||||
Token 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);
|
tokenstream_close(ts);
|
||||||
@@ -93,12 +97,18 @@ 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;");
|
||||||
|
|
||||||
|
t1 = tokenstream_next(ts);
|
||||||
if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
|
if (t1.token != TOKEN_MODULE) fail("expected TOKEN_MODULE");
|
||||||
|
|
||||||
char* buf1 = malloc((size_t)t1.text.length + 1);
|
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';
|
||||||
@@ -106,10 +116,10 @@ static void test_tokenstream_info(void) {
|
|||||||
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';
|
||||||
|
|||||||
+28
-13
@@ -12,7 +12,7 @@ struct TokenStream {
|
|||||||
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;
|
||||||
@@ -39,7 +39,8 @@ static const KeywordMap keywords[] = {
|
|||||||
*/
|
*/
|
||||||
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,14 +118,18 @@ 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) {
|
||||||
|
/* Declarations first for C89 */
|
||||||
|
TokenStream* ts;
|
||||||
|
const char* name_src;
|
||||||
|
|
||||||
if (code == NULL) return NULL;
|
if (code == NULL) return NULL;
|
||||||
|
|
||||||
TokenStream* ts = (TokenStream*)malloc(sizeof(struct TokenStream));
|
ts = (TokenStream*)malloc(sizeof(struct TokenStream));
|
||||||
if (ts == NULL) {
|
if (ts == NULL) {
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* name_src = filename ? filename : "unknown";
|
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);
|
||||||
@@ -147,14 +152,20 @@ void tokenstream_close(TokenStream* ts) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token tokenstream_next(TokenStream* ts) {
|
Token tokenstream_next(TokenStream* ts) {
|
||||||
|
/* Declarations first for C89 */
|
||||||
|
char c;
|
||||||
|
int start_line;
|
||||||
|
int start_column;
|
||||||
|
const char* line_start;
|
||||||
|
const char* start_text;
|
||||||
|
Token t;
|
||||||
|
|
||||||
if (ts == NULL) {
|
if (ts == NULL) {
|
||||||
Token t = {0};
|
Token t = {0};
|
||||||
t.token = TOKEN_EOF;
|
t.token = TOKEN_EOF;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
char c;
|
|
||||||
|
|
||||||
/* 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)) {
|
||||||
@@ -194,10 +205,10 @@ 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);
|
||||||
|
|
||||||
@@ -213,17 +224,21 @@ 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 */
|
||||||
|
size_t length;
|
||||||
|
TokenType type;
|
||||||
|
|
||||||
|
length = 1;
|
||||||
while (is_identifier_part(peek_char(ts))) {
|
while (is_identifier_part(peek_char(ts))) {
|
||||||
read_char(ts);
|
read_char(ts);
|
||||||
length++;
|
length++;
|
||||||
}
|
}
|
||||||
TokenType type = lookup_keyword(start_text, length);
|
type = lookup_keyword(start_text, length);
|
||||||
return create_token(ts, type, start_text, length, start_line, start_column, line_start);
|
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;
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-9
@@ -10,40 +10,40 @@
|
|||||||
* 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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user