Init new repository
This commit is contained in:
125
src/build_file/ast_verify.cpp
Normal file
125
src/build_file/ast_verify.cpp
Normal file
@@ -0,0 +1,125 @@
|
||||
thread_local LC_Map DebugASTMap;
|
||||
|
||||
void DebugVerify_WalkAST(LC_ASTWalker *ctx, LC_AST *n) {
|
||||
bool created = LC_InsertWithoutReplace(&DebugASTMap, n, (void *)(intptr_t)100);
|
||||
if (!created) LC_ReportASTError(n, "we got to the same ast node twice using ast walker");
|
||||
// AST_Decl doesn't need to hold types, it's just a standin for LC_Decl
|
||||
if (LC_IsDecl(n)) {
|
||||
if (n->type) {
|
||||
LC_ReportASTError(n, "decl holds type");
|
||||
}
|
||||
}
|
||||
|
||||
if (LC_IsStmt(n)) {
|
||||
// These might pop up in a for loop as expressions
|
||||
if (n->kind == LC_ASTKind_StmtVar || n->kind == LC_ASTKind_StmtConst || n->kind == LC_ASTKind_StmtAssign || n->kind == LC_ASTKind_StmtExpr) {
|
||||
if (!n->type) {
|
||||
LC_ReportASTError(n, "typed stmt doesn't hold type");
|
||||
}
|
||||
} else {
|
||||
if (n->type) {
|
||||
LC_ReportASTError(n, "stmt holds type");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LC_IsType(n) && ctx->inside_discarded == 0) {
|
||||
if (!n->type) {
|
||||
LC_ReportASTError(n, "typespec doesn't hold type");
|
||||
}
|
||||
}
|
||||
|
||||
if (LC_IsExpr(n)) {
|
||||
bool parent_is_const = false;
|
||||
bool parent_is_builtin = false;
|
||||
for (int i = ctx->stack.len - 1; i >= 0; i -= 1) {
|
||||
LC_AST *parent = ctx->stack.data[i];
|
||||
if (parent->kind == LC_ASTKind_DeclConst || parent->kind == LC_ASTKind_StmtConst || parent->const_val.type) {
|
||||
parent_is_const = true;
|
||||
}
|
||||
if (parent->kind == LC_ASTKind_ExprBuiltin) {
|
||||
parent_is_builtin = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool should_have_type = parent_is_builtin == false && ctx->inside_note == 0 && ctx->inside_discarded == 0;
|
||||
if (should_have_type && !n->type) {
|
||||
LC_ReportASTError(n, "expression doesn't have type");
|
||||
}
|
||||
|
||||
if (should_have_type && !parent_is_const) {
|
||||
bool type_is_ok = !LC_IsUntyped(n->type) || n->type == L->tuntypedbool;
|
||||
if (!type_is_ok) {
|
||||
LC_ReportASTError(n, "expression is still untyped");
|
||||
}
|
||||
}
|
||||
|
||||
if (should_have_type && n->kind == LC_ASTKind_ExprIdent) {
|
||||
if (n->eident.resolved_decl == NULL) {
|
||||
LC_ReportASTError(n, "identifier doesn't hold resolved declaration");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VerifyCopy_Walk(LC_ASTWalker *ctx, LC_AST *n) {
|
||||
int *counter = (int *)ctx->user_data;
|
||||
counter[0] += 1;
|
||||
}
|
||||
|
||||
void VerifyASTCopy(LC_ASTRefList packages) {
|
||||
for (LC_ASTRef *it = packages.first; it; it = it->next) {
|
||||
LC_ASTFor(file, it->ast->apackage.ffile) {
|
||||
LC_TempArena c = LC_BeginTemp(L->arena);
|
||||
LC_AST *copy = LC_CopyAST(L->arena, file);
|
||||
|
||||
int copy_counter = 0;
|
||||
int original_counter = 0;
|
||||
|
||||
{
|
||||
LC_ASTWalker walker = LC_GetDefaultWalker(L->arena, VerifyCopy_Walk);
|
||||
walker.visit_notes = walker.visit_discarded = true;
|
||||
walker.user_data = (void *)©_counter;
|
||||
LC_WalkAST(&walker, copy);
|
||||
walker.user_data = (void *)&original_counter;
|
||||
LC_WalkAST(&walker, copy);
|
||||
}
|
||||
|
||||
IO_Assert(copy_counter == original_counter);
|
||||
IO_Assert(copy_counter > 1);
|
||||
IO_Assert(original_counter > 1);
|
||||
|
||||
LC_EndTemp(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DebugVerifyAST(LC_ASTRefList packages) {
|
||||
LC_TempArena checkpoint = LC_BeginTemp(L->arena);
|
||||
|
||||
DebugASTMap = {L->arena};
|
||||
LC_MapReserve(&DebugASTMap, 4096);
|
||||
|
||||
LC_ASTWalker walker = LC_GetDefaultWalker(L->arena, DebugVerify_WalkAST);
|
||||
walker.visit_notes = walker.visit_discarded = true;
|
||||
for (LC_ASTRef *it = packages.first; it; it = it->next) {
|
||||
LC_WalkAST(&walker, it->ast);
|
||||
}
|
||||
LC_WalkAST(&walker, L->tstring->decl->ast);
|
||||
LC_WalkAST(&walker, L->tany->decl->ast);
|
||||
|
||||
for (int i = 0; i < L->ast_count; i += 1) {
|
||||
LC_AST *it = (LC_AST *)L->ast_arena->memory.data + i;
|
||||
if (it == L->builtin_package) continue;
|
||||
if (it->kind == LC_ASTKind_Ignore) continue;
|
||||
intptr_t value = (intptr_t)LC_MapGetP(&DebugASTMap, it);
|
||||
|
||||
if (value != 100) {
|
||||
LC_ReportASTError(it, "marking verification failed!");
|
||||
}
|
||||
}
|
||||
LC_MapClear(&DebugASTMap);
|
||||
|
||||
VerifyASTCopy(packages);
|
||||
LC_EndTemp(checkpoint);
|
||||
}
|
||||
166
src/build_file/package_compiler.cpp
Normal file
166
src/build_file/package_compiler.cpp
Normal file
@@ -0,0 +1,166 @@
|
||||
bool Expand(MA_Arena *arena, S8_List *out, S8_String filepath) {
|
||||
S8_String c = OS_ReadFile(arena, filepath);
|
||||
if (c.str == 0) return false;
|
||||
// S8_String name = S8_SkipToLastSlash(filepath);
|
||||
// S8_AddF(arena, out, "// %.*s\n", S8_Expand(name));
|
||||
|
||||
S8_String path = S8_ChopLastSlash(filepath);
|
||||
S8_String include = S8_Lit("#include \"");
|
||||
for (;;) {
|
||||
int64_t idx = -1;
|
||||
if (S8_Seek(c, include, 0, &idx)) {
|
||||
S8_String str_to_add = S8_GetPrefix(c, idx);
|
||||
S8_AddNode(arena, out, str_to_add);
|
||||
S8_String save = c;
|
||||
c = S8_Skip(c, idx + include.len);
|
||||
|
||||
S8_String filename = c;
|
||||
filename.len = 0;
|
||||
while (filename.str[filename.len] != '"' && filename.len < c.len) {
|
||||
filename.len += 1;
|
||||
}
|
||||
|
||||
c = S8_Skip(c, filename.len + 1);
|
||||
S8_String inc_path = S8_Format(arena, "%.*s/%.*s", S8_Expand(path), S8_Expand(filename));
|
||||
|
||||
bool dont_expand_and_remove_include = false;
|
||||
if (S8_EndsWith(inc_path, "lib_compiler.h")) dont_expand_and_remove_include = true;
|
||||
|
||||
if (!dont_expand_and_remove_include) {
|
||||
if (!Expand(arena, out, inc_path)) {
|
||||
S8_String s = S8_GetPrefix(save, save.len - c.len);
|
||||
S8_AddNode(arena, out, s);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
S8_AddNode(arena, out, c);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
S8_String ExpandIncludes(MA_Arena *arena, S8_String filepath) {
|
||||
S8_List out = S8_MakeEmptyList();
|
||||
S8_String result = S8_MakeEmpty();
|
||||
Expand(arena, &out, filepath);
|
||||
result = S8_Merge(arena, out);
|
||||
return result;
|
||||
}
|
||||
|
||||
void PackageCompiler() {
|
||||
MA_Scratch scratch;
|
||||
S8_String header = ExpandIncludes(scratch, S8_Lit("../src/compiler/lib_compiler.h"));
|
||||
S8_String impl = ExpandIncludes(scratch, S8_Lit("../src/compiler/lib_compiler.c"));
|
||||
|
||||
S8_List list = {0};
|
||||
S8_AddF(scratch, &list, R"FOO(/*
|
||||
This is a compiler frontend in a single-header-file library form.
|
||||
This is a **beta** so things may change between versions!
|
||||
|
||||
# How to use
|
||||
|
||||
In *one* of your C or C++ files to create the implementation:
|
||||
|
||||
```
|
||||
#define LIB_COMPILER_IMPLEMENTATION
|
||||
#include "lib_compiler.h"
|
||||
```
|
||||
|
||||
In the rest of your files you can just include it like a regular
|
||||
header.
|
||||
|
||||
# Examples
|
||||
|
||||
See online repository for code examples
|
||||
|
||||
# Overrides
|
||||
|
||||
You can override libc calls, the arena implementation using
|
||||
preprocessor at compile time, here is an example of how you
|
||||
would go about it:
|
||||
|
||||
```
|
||||
#define LC_vsnprintf stbsp_vsnprintf
|
||||
#define LC_MemoryZero(p, size) __builtin_memset(p, 0, size);
|
||||
#define LC_MemoryCopy(dst, src, size) __builtin_memcpy(dst, src, size)
|
||||
|
||||
#define LIB_COMPILER_IMPLEMENTATION
|
||||
#include "lib_compiler.h"
|
||||
```
|
||||
|
||||
Look for '@override' to find things that can be overridden using macro preprocessor
|
||||
Look for '@api' to find the main functions that you are supposed to use
|
||||
Look for '@configurable' to find runtime callbacks you can register and other settings
|
||||
|
||||
# License (MIT)
|
||||
|
||||
See end of file
|
||||
|
||||
*/
|
||||
)FOO");
|
||||
S8_AddNode(scratch, &list, header);
|
||||
S8_AddF(scratch, &list, "\n#ifdef LIB_COMPILER_IMPLEMENTATION\n");
|
||||
S8_AddNode(scratch, &list, impl);
|
||||
S8_AddF(scratch, &list, "\n#endif // LIB_COMPILER_IMPLEMENTATION\n");
|
||||
S8_AddF(scratch, &list, R"FOO(
|
||||
/*
|
||||
Copyright (c) 2024 Krzosa Karol
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
)FOO");
|
||||
S8_String result = S8_Merge(scratch, list);
|
||||
|
||||
if (PrintAllFunctions) {
|
||||
S8_List lines = S8_Split(Perm, result, S8_Lit("\n"), 0);
|
||||
S8_For(_line, lines) {
|
||||
S8_String line = _line->string;
|
||||
|
||||
if (!S8_Seek(line, "{", S8_FindFlag_MatchFindLast)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int64_t idx = 0;
|
||||
if (S8_Seek(line, ")", 0, &idx)) {
|
||||
line.len = idx + 1;
|
||||
}
|
||||
|
||||
S8_String proc_name = line;
|
||||
if (S8_Seek(line, "(", 0, &idx)) {
|
||||
proc_name.len = idx;
|
||||
for (int64_t i = proc_name.len - 1; i > 0; i -= 1) {
|
||||
bool is_ident = CHAR_IsIdent(proc_name.str[i]) || CHAR_IsDigit(proc_name.str[i]);
|
||||
if (!is_ident) {
|
||||
proc_name = S8_Skip(proc_name, i + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (S8_StartsWith(line, "LC_FUNCTION", false)) {
|
||||
if (PrintAllFunctions) IO_Printf("%.*s;\n", S8_Expand(line));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
OS_WriteFile(S8_Lit("../lib_compiler.h"), result);
|
||||
}
|
||||
37
src/build_file/test_readme.cpp
Normal file
37
src/build_file/test_readme.cpp
Normal file
@@ -0,0 +1,37 @@
|
||||
void ReadmeReadFile(LC_AST *package, LoadedFile *file) {
|
||||
S8_String result = {};
|
||||
if (package->apackage.name == LC_ILit("readme_test")) {
|
||||
S8_String code = *(S8_String *)L->user_data;
|
||||
file->content = S8_Copy(L->arena, code);
|
||||
} else {
|
||||
file->content = OS_ReadFile(L->arena, file->path);
|
||||
}
|
||||
}
|
||||
|
||||
void TestReadme() {
|
||||
MA_Scratch scratch;
|
||||
|
||||
S8_String readme_path = OS_GetAbsolutePath(scratch, "../README.md");
|
||||
S8_String readme = OS_ReadFile(scratch, readme_path);
|
||||
S8_List split = S8_Split(scratch, readme, "```");
|
||||
S8_For(it, split) {
|
||||
if (S8_StartsWith(it->string, " odin", S8_IgnoreCase)) {
|
||||
S8_String code = S8_Skip(it->string, 5);
|
||||
|
||||
LC_Lang *lang = LC_LangAlloc();
|
||||
lang->use_colored_terminal_output = UseColoredIO;
|
||||
lang->user_data = (void *)&code;
|
||||
lang->on_file_load = ReadmeReadFile;
|
||||
LC_LangBegin(lang);
|
||||
LC_DeclareNote(LC_ILit("do_something"));
|
||||
LC_DeclareNote(LC_ILit("serialize"));
|
||||
defer { LC_LangEnd(lang); };
|
||||
|
||||
LC_RegisterPackageDir("../pkgs");
|
||||
LC_Intern name = LC_ILit("readme_test");
|
||||
LC_AddSingleFilePackage(name, readme_path);
|
||||
LC_ResolvePackageByName(name);
|
||||
// LC_FindUnusedLocalsAndRemoveUnusedGlobalDecls();
|
||||
}
|
||||
}
|
||||
}
|
||||
463
src/build_file/testsuite.cpp
Normal file
463
src/build_file/testsuite.cpp
Normal file
@@ -0,0 +1,463 @@
|
||||
|
||||
enum TestResult {
|
||||
OK,
|
||||
Failed_Lex,
|
||||
Failed_Parse,
|
||||
Failed_Resolve,
|
||||
Failed_CCompile,
|
||||
Failed_Run,
|
||||
Failed_Condition,
|
||||
Failed_Package,
|
||||
};
|
||||
|
||||
const char *TestResultString[] = {
|
||||
"OK",
|
||||
"Failed_Lex",
|
||||
"Failed_Parse",
|
||||
"Failed_Resolve",
|
||||
"Failed_CCompile",
|
||||
"Failed_Run",
|
||||
"Failed_Condition",
|
||||
"Failed_Package",
|
||||
};
|
||||
|
||||
struct Test {
|
||||
char *path;
|
||||
bool msg_found[256];
|
||||
char *msg_expected[256];
|
||||
int msg_count;
|
||||
TestResult expected_result;
|
||||
bool dont_run;
|
||||
};
|
||||
|
||||
struct Task {
|
||||
bool do_run;
|
||||
S8_String exe;
|
||||
S8_String cmd;
|
||||
Process process;
|
||||
};
|
||||
|
||||
enum TestKind {
|
||||
TestKind_File,
|
||||
TestKind_Dir,
|
||||
};
|
||||
|
||||
struct TestDesc {
|
||||
TestKind kind;
|
||||
S8_String absolute_path;
|
||||
S8_String filename;
|
||||
S8_String package_name;
|
||||
bool dont_run;
|
||||
};
|
||||
|
||||
TestResult Compile(S8_String name, S8_String file_to_compile) {
|
||||
TestResult result = OK;
|
||||
int errcode = 0;
|
||||
Array<Task> tasks = {MA_GetAllocator(Perm)};
|
||||
|
||||
{
|
||||
Task tcc = {};
|
||||
tcc.do_run = UseTCC;
|
||||
tcc.exe = Fmt("%.*s/tcc_%.*s.exe", S8_Expand(name), S8_Expand(name));
|
||||
tcc.cmd = Fmt("tcc -std=c99 %.*s " IF_MAC("-lm") IF_LINUX("-lm") " -o %.*s", S8_Expand(file_to_compile), S8_Expand(tcc.exe));
|
||||
tasks.add(tcc);
|
||||
|
||||
Task clang = {};
|
||||
clang.do_run = UseClang;
|
||||
clang.exe = Fmt("%.*s/clang_%.*s.exe", S8_Expand(name), S8_Expand(name));
|
||||
clang.cmd = Fmt("clang -Wno-string-plus-int -g -std=c99 " IF_MAC("-lm") IF_LINUX("-lm") " %.*s -o %.*s", S8_Expand(file_to_compile), S8_Expand(clang.exe));
|
||||
tasks.add(clang);
|
||||
|
||||
Task cl = {};
|
||||
cl.do_run = UseCL;
|
||||
cl.exe = Fmt("%.*s/cl_%.*s.exe", S8_Expand(name), S8_Expand(name));
|
||||
cl.cmd = Fmt("cl %.*s -Zi -std:c11 -nologo /Fd:%.*s.pdb -FC -Fe:%.*s", S8_Expand(file_to_compile), S8_Expand(file_to_compile), S8_Expand(cl.exe));
|
||||
tasks.add(cl);
|
||||
|
||||
Task gcc = {};
|
||||
gcc.do_run = UseGCC;
|
||||
gcc.exe = Fmt("%.*s/gcc_%.*s.exe", S8_Expand(name), S8_Expand(name));
|
||||
gcc.cmd = Fmt("gcc -Wno-string-plus-int -g -std=c99 " IF_MAC("-lm") IF_LINUX("-lm") " %.*s -o %.*s", S8_Expand(file_to_compile), S8_Expand(gcc.exe));
|
||||
tasks.add(gcc);
|
||||
}
|
||||
|
||||
For(tasks) {
|
||||
if (!it.do_run) continue;
|
||||
errcode = Run(it.cmd);
|
||||
if (errcode != 0) {
|
||||
result = Failed_CCompile;
|
||||
goto end_of_test;
|
||||
}
|
||||
it.exe = OS_GetAbsolutePath(Perm, it.exe);
|
||||
}
|
||||
|
||||
For(tasks) {
|
||||
if (!it.do_run) continue;
|
||||
it.process = RunEx(it.exe);
|
||||
}
|
||||
|
||||
For(tasks) {
|
||||
if (!it.do_run) continue;
|
||||
errcode += Wait(&it.process);
|
||||
}
|
||||
|
||||
if (errcode != 0) {
|
||||
result = Failed_Run;
|
||||
goto end_of_test;
|
||||
}
|
||||
end_of_test:
|
||||
return result;
|
||||
}
|
||||
|
||||
thread_local Test *T;
|
||||
thread_local Array<S8_String> Messages;
|
||||
|
||||
void HandleOutputMessageGlobalTest(LC_Token *pos, char *str, int len) {
|
||||
S8_String s = S8_Make(str, len);
|
||||
s = S8_Trim(s);
|
||||
|
||||
if (S8_StartsWith(s, S8_Lit("Executing: "), 0)) return;
|
||||
if (S8_StartsWith(s, S8_Lit("> "), 0)) return;
|
||||
if (T && T->msg_expected[0]) {
|
||||
for (int i = 0; i < 256; i += 1) {
|
||||
if (T->msg_expected[i] == 0) break;
|
||||
|
||||
char *str = T->msg_expected[i];
|
||||
bool *found = T->msg_found + i;
|
||||
if (!found[0]) {
|
||||
S8_String expected = S8_MakeFromChar(str);
|
||||
if (S8_Seek(s, expected, S8_FindFlag_IgnoreCase, NULL)) {
|
||||
found[0] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (pos) s = S8_Format(Perm, "%s(%d,%d): error: %.*s\n", (char *)pos->lex->file, pos->line, pos->column, len, str);
|
||||
Messages.add(S8_Copy(Perm, s));
|
||||
}
|
||||
|
||||
TestResult PrintGlobalTestResult(char *path, TestResult result) {
|
||||
const char *red = "\033[31m";
|
||||
const char *reset = "\033[0m";
|
||||
const char *green = "\033[32m";
|
||||
if (!UseColoredIO) {
|
||||
red = "";
|
||||
reset = "";
|
||||
green = "";
|
||||
}
|
||||
|
||||
TestResult actual_result = result;
|
||||
if (result == T->expected_result) {
|
||||
result = OK;
|
||||
} else {
|
||||
result = Failed_Condition;
|
||||
}
|
||||
|
||||
if (T->msg_expected[0]) {
|
||||
bool all_found = true;
|
||||
for (int i = 0; i < T->msg_count; i += 1) {
|
||||
if (T->msg_expected[i] == 0) {
|
||||
IO_Printf("%sCount of expected messages doesn't match expected messages%s\n", red, reset);
|
||||
all_found = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!T->msg_found[i]) {
|
||||
all_found = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!all_found) {
|
||||
result = Failed_Condition;
|
||||
|
||||
for (int i = 0; i < 256; i += 1) {
|
||||
char *str = T->msg_expected[i];
|
||||
if (str == 0) break;
|
||||
|
||||
const char *color = green;
|
||||
if (!T->msg_found[i]) color = red;
|
||||
IO_Printf("%sMessage not found - %s%s\n", color, T->msg_expected[i], reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (T->msg_count != Messages.len) {
|
||||
IO_Printf("%sCount of expected messages: %d doesn't match actual message count: %d%s\n", red, T->msg_count, Messages.len, reset);
|
||||
result = Failed_Condition;
|
||||
}
|
||||
|
||||
const char *color = green;
|
||||
if (result != OK) {
|
||||
color = red;
|
||||
IO_Printf("%-50s - %s Expected result: %s, got instead: %s%s\n", path, color, TestResultString[T->expected_result], TestResultString[actual_result], reset);
|
||||
} else {
|
||||
IO_Printf("%-50s - %s%s%s\n", path, color, TestResultString[result], reset);
|
||||
}
|
||||
|
||||
if (result != OK) {
|
||||
For(Messages) IO_Printf("%.*s", S8_Expand(it));
|
||||
}
|
||||
Messages.reset();
|
||||
return result;
|
||||
}
|
||||
|
||||
void ParseErrorNotationAndFillGlobalTest(S8_String s) {
|
||||
S8_String count_lit = S8_Lit("// #expected_error_count: ");
|
||||
S8_String errlit = S8_Lit("// #error: ");
|
||||
S8_String resultlit = S8_Lit("// #failed: ");
|
||||
S8_String dont_run_lit = S8_Lit("// #dont_run");
|
||||
S8_List errors = S8_Split(Perm, s, S8_Lit("\n"), 0);
|
||||
S8_For(it, errors) {
|
||||
if (S8_StartsWith(it->string, errlit, 0)) {
|
||||
S8_String error = S8_Skip(it->string, errlit.len);
|
||||
error = S8_Trim(error);
|
||||
error = S8_Copy(Perm, error);
|
||||
T->msg_expected[T->msg_count++] = error.str;
|
||||
}
|
||||
if (S8_StartsWith(it->string, resultlit, 0)) {
|
||||
if (S8_Seek(it->string, S8_Lit("parse"), 0, 0)) {
|
||||
T->expected_result = Failed_Parse;
|
||||
}
|
||||
if (S8_Seek(it->string, S8_Lit("resolve"), 0, 0)) {
|
||||
T->expected_result = Failed_Resolve;
|
||||
}
|
||||
if (S8_Seek(it->string, S8_Lit("lex"), 0, 0)) {
|
||||
T->expected_result = Failed_Lex;
|
||||
}
|
||||
if (S8_Seek(it->string, S8_Lit("package"), 0, 0)) {
|
||||
T->expected_result = Failed_Package;
|
||||
}
|
||||
}
|
||||
if (S8_StartsWith(it->string, count_lit, 0)) {
|
||||
S8_String count = S8_Skip(it->string, count_lit.len);
|
||||
count = S8_Copy(Perm, count);
|
||||
T->msg_count = atoi(count.str);
|
||||
}
|
||||
if (S8_StartsWith(it->string, dont_run_lit, 0)) {
|
||||
T->dont_run = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
S8_String PushDir(S8_String dir_name) {
|
||||
S8_String prev = OS_GetWorkingDir(Perm);
|
||||
OS_MakeDir(dir_name);
|
||||
OS_SetWorkingDir(dir_name);
|
||||
return prev;
|
||||
}
|
||||
|
||||
void RunTestFile(TestDesc it) {
|
||||
LC_Lang *lang = LC_LangAlloc();
|
||||
lang->on_message = HandleOutputMessageGlobalTest;
|
||||
lang->use_colored_terminal_output = UseColoredIO;
|
||||
lang->breakpoint_on_error = BreakpointOnError;
|
||||
LC_LangBegin(lang);
|
||||
|
||||
Test test = {};
|
||||
test.dont_run = it.dont_run;
|
||||
T = &test;
|
||||
TestResult result = OK;
|
||||
|
||||
S8_String s = OS_ReadFile(L->arena, it.absolute_path);
|
||||
if (s.len == 0) {
|
||||
IO_FatalErrorf("Failed to open file %.*s", S8_Expand(it.absolute_path));
|
||||
}
|
||||
|
||||
ParseErrorNotationAndFillGlobalTest(s);
|
||||
|
||||
S8_String code = {};
|
||||
S8_String out_path = {};
|
||||
|
||||
LC_RegisterPackageDir("../../pkgs");
|
||||
|
||||
LC_Intern name = LC_ILit("testing");
|
||||
LC_AddSingleFilePackage(name, it.absolute_path);
|
||||
|
||||
L->first_package = name;
|
||||
LC_ParsePackagesUsingRegistry(name);
|
||||
if (L->errors) {
|
||||
result = Failed_Parse;
|
||||
goto end_of_test;
|
||||
}
|
||||
|
||||
LC_OrderAndResolveTopLevelDecls(name);
|
||||
LC_ResolveAllProcBodies();
|
||||
LC_FindUnusedLocalsAndRemoveUnusedGlobalDecls();
|
||||
if (L->errors) {
|
||||
result = Failed_Resolve;
|
||||
goto end_of_test;
|
||||
}
|
||||
|
||||
DebugVerifyAST(L->ordered_packages);
|
||||
|
||||
OS_MakeDir(it.filename);
|
||||
code = LC_GenerateUnityBuild(L->ordered_packages);
|
||||
out_path = S8_Format(L->arena, "%.*s/%.*s.c", S8_Expand(it.filename), S8_Expand(it.filename));
|
||||
OS_WriteFile(out_path, code);
|
||||
|
||||
{
|
||||
LC_BeginStringGen(L->arena);
|
||||
LC_GenLCNode(L->ordered_packages.first->ast);
|
||||
S8_String c = LC_EndStringGen(L->arena);
|
||||
S8_String p = S8_Format(L->arena, "%.*s/%.*s.generated.lc", S8_Expand(it.filename), S8_Expand(it.filename));
|
||||
p = OS_GetAbsolutePath(L->arena, p);
|
||||
OS_WriteFile(p, c);
|
||||
LC_ParseFile(NULL, p.str, c.str, 0);
|
||||
}
|
||||
|
||||
if (!T->dont_run) result = Compile(it.filename, out_path);
|
||||
|
||||
end_of_test:
|
||||
result = PrintGlobalTestResult(it.filename.str, result);
|
||||
LC_LangEnd(lang);
|
||||
}
|
||||
|
||||
void RunTestDir(TestDesc it, S8_String package_name) {
|
||||
LC_Lang *lang = LC_LangAlloc();
|
||||
lang->on_message = HandleOutputMessageGlobalTest;
|
||||
lang->use_colored_terminal_output = UseColoredIO;
|
||||
lang->breakpoint_on_error = BreakpointOnError;
|
||||
LC_LangBegin(lang);
|
||||
|
||||
Test test = {};
|
||||
T = &test;
|
||||
TestResult result = OK;
|
||||
|
||||
S8_String path_to_test_file = S8_Format(L->arena, "%.*s/test.txt", S8_Expand(it.absolute_path));
|
||||
S8_String error_notation = OS_ReadFile(L->arena, path_to_test_file);
|
||||
if (error_notation.str) ParseErrorNotationAndFillGlobalTest(error_notation);
|
||||
|
||||
LC_Intern first_package = LC_ILit(package_name.str);
|
||||
LC_RegisterPackageDir(it.absolute_path.str);
|
||||
LC_RegisterPackageDir("../../pkgs");
|
||||
LC_ASTRefList packages = LC_ResolvePackageByName(first_package);
|
||||
LC_FindUnusedLocalsAndRemoveUnusedGlobalDecls();
|
||||
if (L->errors != 0) result = Failed_Package;
|
||||
|
||||
if (result == OK && T->expected_result == OK) {
|
||||
DebugVerifyAST(packages);
|
||||
|
||||
OS_MakeDir(it.filename);
|
||||
S8_String code = LC_GenerateUnityBuild(packages);
|
||||
S8_String out_path = S8_Format(L->arena, "%.*s/%.*s.c", S8_Expand(it.filename), S8_Expand(it.filename));
|
||||
OS_WriteFile(out_path, code);
|
||||
if (!T->dont_run) result = Compile(it.filename, out_path);
|
||||
}
|
||||
|
||||
result = PrintGlobalTestResult(it.filename.str, result);
|
||||
LC_LangEnd(lang);
|
||||
}
|
||||
|
||||
void TestThread(Array<TestDesc> tests) {
|
||||
MA_InitScratch();
|
||||
For(tests) {
|
||||
if (it.kind == TestKind_Dir) {
|
||||
RunTestDir(it, it.package_name);
|
||||
} else if (it.kind == TestKind_File) {
|
||||
RunTestFile(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldSkip(S8_String filename, bool is_directory = false) {
|
||||
if (TestsToRun.first) {
|
||||
bool found = false;
|
||||
S8_For(match, TestsToRun) {
|
||||
if (match->string == filename) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_directory) {
|
||||
if (S8_StartsWith(filename, "example_", true) || S8_StartsWith(filename, "compilation", true))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ShouldRun(S8_String filename) {
|
||||
bool result = !ShouldSkip(filename, false);
|
||||
return result;
|
||||
}
|
||||
|
||||
void RunTests() {
|
||||
MA_Scratch scratch;
|
||||
int test_count = 0;
|
||||
S8_String working_dir = PushDir("test_result");
|
||||
|
||||
Array<TestDesc> tests = {MA_GetAllocator(scratch)};
|
||||
|
||||
for (OS_FileIter it = OS_IterateFiles(scratch, S8_Lit("../../tests")); OS_IsValid(it); OS_Advance(&it)) {
|
||||
if (ShouldSkip(it.filename, it.is_directory)) continue;
|
||||
TestDesc desc = {it.is_directory ? TestKind_Dir : TestKind_File, it.absolute_path, it.filename, "main"};
|
||||
tests.add(desc);
|
||||
}
|
||||
|
||||
test_count = tests.len;
|
||||
const int thread_count = ThreadCount;
|
||||
Array<Array<TestDesc>> work = {MA_GetAllocator(scratch)};
|
||||
Array<std::thread *> threads = {MA_GetAllocator(scratch)};
|
||||
|
||||
for (int i = 0; i < thread_count; i += 1) {
|
||||
work.add({MA_GetAllocator(scratch)});
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
For(tests) {
|
||||
work[i].add(it);
|
||||
i = (i + 1) % thread_count;
|
||||
}
|
||||
|
||||
For(work) threads.add(new std::thread(TestThread, it));
|
||||
For(threads) it->join();
|
||||
|
||||
//
|
||||
// Non automated tests
|
||||
//
|
||||
if (ShouldRun("example_ui_and_hot_reloading")) {
|
||||
test_count += 1;
|
||||
TestResult result = OK;
|
||||
Test t = {};
|
||||
t.path = OS_GetAbsolutePath(scratch, "../../tests/example_ui_and_hot_reloading/").str;
|
||||
t.expected_result = OK;
|
||||
T = &t;
|
||||
|
||||
LC_Lang *lang = LC_LangAlloc();
|
||||
lang->on_message = HandleOutputMessageGlobalTest;
|
||||
lang->use_colored_terminal_output = UseColoredIO;
|
||||
lang->breakpoint_on_error = BreakpointOnError;
|
||||
LC_LangBegin(lang);
|
||||
LC_RegisterPackageDir(T->path);
|
||||
LC_ASTRefList dll_packages = LC_ResolvePackageByName(LC_ILit("dll"));
|
||||
LC_ASTRefList exe_packages = LC_ResolvePackageByName(LC_ILit("exe"));
|
||||
result = L->errors >= 1 ? Failed_Package : OK;
|
||||
|
||||
if (result == OK) {
|
||||
DebugVerifyAST(dll_packages);
|
||||
// DebugVerifyAST(exe_packages);
|
||||
|
||||
S8_String prev = PushDir("example_ui_and_hot_reloading");
|
||||
S8_String code = LC_GenerateUnityBuild(exe_packages);
|
||||
OS_WriteFile("example_ui_and_hot_reloading", code);
|
||||
OS_SetWorkingDir(prev);
|
||||
}
|
||||
|
||||
LC_LangEnd(lang);
|
||||
PrintGlobalTestResult("example_ui_and_hot_reloading", result);
|
||||
#if 0
|
||||
OS_SystemF("clang unity_exe.c -o platform.exe -g -O0 -I\"../..\"");
|
||||
OS_SystemF("clang unity_dll.c -o game.dll -O0 -shared -g -I\"../..\" -Wl,-export:APP_Update");
|
||||
#endif
|
||||
}
|
||||
|
||||
OS_SetWorkingDir(working_dir);
|
||||
IO_Printf("Total test count = %d\n", test_count);
|
||||
}
|
||||
Reference in New Issue
Block a user