549 lines
19 KiB
C
549 lines
19 KiB
C
Readonly M_Token M_NilToken = {
|
|
.next = &M_NilToken,
|
|
.file = &M_NilTokenFile,
|
|
};
|
|
|
|
Readonly M_TokenFile M_NilTokenFile = {
|
|
.next = &M_NilTokenFile,
|
|
.first_token = &M_NilToken,
|
|
.last_token = &M_NilToken,
|
|
};
|
|
|
|
Readonly M_Entry M_NilEntry = {
|
|
.next = &M_NilEntry,
|
|
.name_token = &M_NilToken,
|
|
.arg_tokens[0] = &M_NilToken,
|
|
.arg_tokens[1] = &M_NilToken,
|
|
};
|
|
StaticAssert(countof(M_NilEntry.arg_tokens) == 2); /* NilEntry must define point args to nil tokens */
|
|
|
|
Readonly M_Layer M_NilLayer = {
|
|
.next = &M_NilLayer,
|
|
.file = &M_NilTokenFile,
|
|
.first = &M_NilEntry,
|
|
.last = &M_NilEntry,
|
|
};
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Error helpers
|
|
|
|
M_Error *M_PushError(Arena *arena, M_ErrorList *l, M_Token *token, String msg)
|
|
{
|
|
M_Error *e = PushStruct(arena, M_Error);
|
|
e->msg = msg;
|
|
e->token = token ? token : &M_NilToken;
|
|
QueuePush(l->first, l->last, e);
|
|
++l->count;
|
|
return e;
|
|
}
|
|
|
|
void M_AppendErrors(Arena *arena, M_ErrorList *dst, M_ErrorList src)
|
|
{
|
|
for (M_Error *e = src.first; e; e = e->next)
|
|
{
|
|
M_PushError(arena, dst, e->token, e->msg);
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Lexer operations
|
|
|
|
M_TokenFile *M_PushTokenFile(Arena *arena, M_TokenFileList *l, String name, String data)
|
|
{
|
|
M_TokenFile *tf = PushStruct(arena, M_TokenFile);
|
|
*tf = M_NilTokenFile;
|
|
tf->valid = 1;
|
|
tf->name = name;
|
|
tf->data = data;
|
|
M_PushToken(arena, tf, Lit(""), M_TokenKind_Eol);
|
|
QueuePushNZ(&M_NilTokenFile, l->first, l->last, tf, next);
|
|
++l->count;
|
|
return tf;
|
|
}
|
|
|
|
M_Token *M_PushToken(Arena *arena, M_TokenFile *file, String s, M_TokenKind kind)
|
|
{
|
|
M_Token *t = PushStruct(arena, M_Token);
|
|
*t = M_NilToken;
|
|
t->valid = 1;
|
|
t->s = s;
|
|
t->kind = kind;
|
|
if (file)
|
|
{
|
|
t->file = file;
|
|
}
|
|
QueuePushNZ(&M_NilToken, file->first_token, file->last_token, t, next);
|
|
++file->tokens_count;
|
|
return t;
|
|
}
|
|
|
|
M_TokenFileList M_TokensFromSrcDirs(Arena *arena, StringList src_dirs)
|
|
{
|
|
TempArena scratch = BeginScratch(arena);
|
|
M_TokenFileList result = ZI;
|
|
result.first = &M_NilTokenFile;
|
|
result.last = &M_NilTokenFile;
|
|
|
|
/* Build token file list from src dirs */
|
|
Dict *dedup_dict = InitDict(scratch.arena, 1021);
|
|
for (StringListNode *dir_name_node = src_dirs.first; dir_name_node; dir_name_node = dir_name_node->next)
|
|
{
|
|
String dir_name = dir_name_node->s;
|
|
StringList files = ZI;
|
|
F_FilesFromDir(arena, &files, dir_name, F_IterFlag_Recurse);
|
|
for (StringListNode *file_name_node = files.first; file_name_node; file_name_node = file_name_node->next)
|
|
{
|
|
String file_name = file_name_node->s;
|
|
if (StringEndsWith(file_name, Lit(".lay")))
|
|
{
|
|
String tmp_full = F_GetFull(scratch.arena, file_name);
|
|
u64 hash = HashFnv64(Fnv64Basis, tmp_full);
|
|
if (DictValueFromHash(dedup_dict, hash) == 0)
|
|
{
|
|
SetDictValue(scratch.arena, dedup_dict, hash, 1);
|
|
String full = PushString(arena, tmp_full);
|
|
String data = F_DataFromFile(arena, full);
|
|
M_PushTokenFile(arena, &result, full, data);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (M_TokenFile *tf = result.first; tf->valid; tf = tf->next)
|
|
{
|
|
Enum(TokenizerMode)
|
|
{
|
|
TokenizerMode_None,
|
|
TokenizerMode_SingleLineComment,
|
|
TokenizerMode_MultiLineComment,
|
|
};
|
|
TokenizerMode mode = TokenizerMode_None;
|
|
|
|
u8 *t = tf->data.text;
|
|
u64 l = tf->data.len;
|
|
u64 p = 0;
|
|
String cur_ident = STRING(0, t);
|
|
while (p < l)
|
|
{
|
|
switch (mode)
|
|
{
|
|
case TokenizerMode_None:
|
|
{
|
|
u8 c = t[p];
|
|
b32 is_eol = c == '\r' || c == '\n' || c == 0 || (p + 1) >= l;
|
|
b32 is_whitespace = is_eol || c == ' ';
|
|
if (is_whitespace)
|
|
{ /* Whitespace */
|
|
if (cur_ident.len > 0)
|
|
{
|
|
M_PushToken(arena, tf, cur_ident, M_TokenKind_Ident);
|
|
cur_ident.len = 0;
|
|
}
|
|
if (is_eol)
|
|
{
|
|
M_PushToken(arena, tf, STRING(1, &t[p]), M_TokenKind_Eol);
|
|
}
|
|
p += 1;
|
|
}
|
|
else if (c == '/' && (p + 1) < l && t[p + 1] == '/')
|
|
{ /* Start single line comment */
|
|
mode = TokenizerMode_SingleLineComment;
|
|
p += 2;
|
|
}
|
|
else if (c == '/' && (p + 1) < l && t[p + 1] == '*')
|
|
{ /* Start multi line comment */
|
|
mode = TokenizerMode_MultiLineComment;
|
|
p += 2;
|
|
}
|
|
else
|
|
{
|
|
if (cur_ident.len == 0)
|
|
{
|
|
cur_ident.text = &t[p];
|
|
}
|
|
++cur_ident.len;
|
|
p += 1;
|
|
}
|
|
} break;
|
|
|
|
case TokenizerMode_SingleLineComment:
|
|
{
|
|
if (t[p] == '\n')
|
|
{ /* End single line comment */
|
|
mode = TokenizerMode_None;
|
|
p += 1;
|
|
}
|
|
else
|
|
{
|
|
p += 1;
|
|
}
|
|
} break;
|
|
|
|
case TokenizerMode_MultiLineComment:
|
|
{
|
|
if (t[p] == '*' && (p + 1) < l && t[p + 1] == '/')
|
|
{ /* End multi line comment */
|
|
mode = TokenizerMode_None;
|
|
p += 2;
|
|
}
|
|
else
|
|
{
|
|
p += 1;
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Parser operations
|
|
|
|
M_Layer *M_PushLayer(Arena *arena, M_LayerList *list, M_TokenFile *file)
|
|
{
|
|
M_Layer *layer = PushStruct(arena, M_Layer);
|
|
*layer = M_NilLayer;
|
|
layer->valid = 1;
|
|
layer->file = file;
|
|
QueuePushNZ(&M_NilLayer, list->first, list->last, layer, next);
|
|
++list->count;
|
|
return layer;
|
|
}
|
|
|
|
M_Entry *M_PushEntry(Arena *arena, M_Layer *l, M_EntryKind kind, M_Token *entry_token, u64 args_count, M_Token **args)
|
|
{
|
|
M_Entry *e = PushStruct(arena, M_Entry);
|
|
*e = M_NilEntry;
|
|
e->valid = 1;
|
|
e->kind = kind;
|
|
if (entry_token) e->name_token = entry_token;
|
|
for (u64 i = 0; i < MinU64(args_count, countof(e->arg_tokens)); ++i)
|
|
{
|
|
e->arg_tokens[i] = args[i];
|
|
}
|
|
e->args_count = args_count;
|
|
QueuePushNZ(&M_NilEntry, l->first, l->last, e, next);
|
|
++l->count;
|
|
return e;
|
|
}
|
|
|
|
M_LayerList M_LayersFromTokenFiles(Arena *arena, M_TokenFileList lexed)
|
|
{
|
|
TempArena scratch = BeginScratch(arena);
|
|
M_LayerList result = ZI;
|
|
result.first = &M_NilLayer;
|
|
result.last = &M_NilLayer;
|
|
|
|
/* Copy errors */
|
|
b32 lexer_errors_present = 0;
|
|
for (M_TokenFile *lf = lexed.first; lf->valid; lf = lf->next)
|
|
{
|
|
if (lf->errors.count > 0)
|
|
{
|
|
M_Layer *layer = M_PushLayer(arena, &result, lf);
|
|
M_AppendErrors(arena, &layer->errors, lf->errors);
|
|
lexer_errors_present = 1;
|
|
}
|
|
}
|
|
|
|
if (!lexer_errors_present)
|
|
{
|
|
Dict *rules_dict = InitDict(scratch.arena, 1024);
|
|
for (u64 i = 0; i < countof(M_entry_kind_rules); ++i)
|
|
{
|
|
char *rule_cstr = M_entry_kind_rules[i];
|
|
u64 rule_hash = HashFnv64(Fnv64Basis, StringFromCstrNoLimit(rule_cstr));
|
|
SetDictValue(scratch.arena, rules_dict, rule_hash, i + 1);
|
|
}
|
|
|
|
for (M_TokenFile *lf = lexed.first; lf->valid; lf = lf->next)
|
|
{
|
|
M_Layer *layer = M_PushLayer(arena, &result, lf);
|
|
|
|
M_Token *entry_token = &M_NilToken;
|
|
M_Token *arg_tokens[countof(M_NilEntry.arg_tokens) + 1];
|
|
u64 args_count = 0;
|
|
for (u64 i = 0; i < countof(arg_tokens); ++i)
|
|
{
|
|
arg_tokens[i] = &M_NilToken;
|
|
}
|
|
|
|
M_Token *token = lf->first_token;
|
|
while (token->valid)
|
|
{
|
|
if (token->kind == M_TokenKind_Eol)
|
|
{
|
|
if (entry_token->valid)
|
|
{
|
|
M_Entry *entry = M_PushEntry(arena, layer, M_EntryKind_Unknown, entry_token, args_count, arg_tokens);
|
|
{
|
|
u64 entry_hash = HashFnv64(Fnv64Basis, entry_token->s);
|
|
u64 kind_plus_1 = DictValueFromHash(rules_dict, entry_hash);
|
|
if (kind_plus_1 > 0)
|
|
{
|
|
entry->kind = kind_plus_1 - 1;
|
|
}
|
|
else
|
|
{
|
|
String err = StringF(arena, "Unexpected token \"%F\"", FmtString(entry_token->s));
|
|
M_PushError(arena, &layer->errors, entry->name_token, err);
|
|
}
|
|
}
|
|
}
|
|
token = token->next;
|
|
entry_token = &M_NilToken;
|
|
args_count = 0;
|
|
for (u64 i = 0; i < countof(arg_tokens); ++i)
|
|
{
|
|
arg_tokens[i] = &M_NilToken;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Parse entry */
|
|
entry_token = token;
|
|
token = token->next;
|
|
/* Parse args */
|
|
while (token->kind != M_TokenKind_Eol)
|
|
{
|
|
if (args_count < countof(arg_tokens))
|
|
{
|
|
arg_tokens[args_count++] = token;
|
|
}
|
|
token = token->next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EndScratch(scratch);
|
|
return result;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////
|
|
//~ Flatten operations
|
|
|
|
M_Layer M_GetFlattenedEntries(Arena *arena, M_LayerList unflattened, StringList starting_layer_names)
|
|
{
|
|
TempArena scratch = BeginScratch(arena);
|
|
M_Layer result = M_NilLayer;
|
|
|
|
/* Copy errors */
|
|
b32 unflattened_errors_present = 0;
|
|
for (M_Layer *layer = unflattened.first; layer->valid; layer = layer->next)
|
|
{
|
|
if (layer->errors.count > 0)
|
|
{
|
|
M_AppendErrors(arena, &result.errors, layer->errors);
|
|
unflattened_errors_present = 1;
|
|
}
|
|
}
|
|
|
|
if (!unflattened_errors_present)
|
|
{
|
|
Enum(IterStateMode)
|
|
{
|
|
IterStateMode_None,
|
|
IterStateMode_Entered,
|
|
IterStateMode_Exited
|
|
};
|
|
Struct(IterState)
|
|
{
|
|
M_Layer *layer;
|
|
b32 is_entered;
|
|
b32 is_exited;
|
|
} NilIterState = {
|
|
.layer = &M_NilLayer
|
|
};
|
|
|
|
/* Construct state lookups */
|
|
Dict *layer_ptr_to_state = InitDict(scratch.arena, 1021);
|
|
Dict *layer_name_to_state = InitDict(scratch.arena, 1021);
|
|
for (M_Layer *layer = unflattened.first; layer->valid; layer = layer->next)
|
|
{
|
|
M_Entry *name_entry = &M_NilEntry;
|
|
u64 num_name_entries = 0;
|
|
for (M_Entry *entry = layer->first; entry->valid; entry = entry->next)
|
|
{
|
|
if (entry->kind == M_EntryKind_Layer)
|
|
{
|
|
if (num_name_entries == 0)
|
|
{
|
|
name_entry = entry;
|
|
}
|
|
else
|
|
{
|
|
String err = StringF(arena, "Layer already has name \"%F\"", FmtString(name_entry->arg_tokens[0]->s));
|
|
M_PushError(arena, &result.errors, entry->name_token, err);
|
|
}
|
|
++num_name_entries;
|
|
}
|
|
}
|
|
if (num_name_entries == 1 && name_entry->arg_tokens[0]->s.len > 0)
|
|
{
|
|
IterState *state = PushStruct(scratch.arena, IterState);
|
|
state->layer = layer;
|
|
|
|
String name = name_entry->arg_tokens[0]->s;
|
|
|
|
u64 name_hash = HashFnv64(Fnv64Basis, name);
|
|
DictEntry *ptr_dict_entry = EnsureDictEntry(scratch.arena, layer_ptr_to_state, (u64)layer);
|
|
DictEntry *name_dict_entry = EnsureDictEntry(scratch.arena, layer_name_to_state, name_hash);
|
|
|
|
ptr_dict_entry->value = (u64)state;
|
|
if (name_dict_entry->value == 0)
|
|
{
|
|
name_dict_entry->value = (u64)state;
|
|
}
|
|
else
|
|
{
|
|
String err = StringF(arena, "Layer with name \"%F\" already exists", FmtString(name));
|
|
M_PushError(arena, &result.errors, name_entry->arg_tokens[0], err);
|
|
}
|
|
}
|
|
else if (num_name_entries == 0)
|
|
{
|
|
M_PushError(arena, &result.errors, layer->file->first_token, Lit("Layer is missing a name"));
|
|
}
|
|
}
|
|
|
|
Struct(StackNode) { StackNode *next; IterState *state; b32 exit; };
|
|
StackNode *stack = 0;
|
|
|
|
/* Init stack with starting layers */
|
|
for (StringListNode *sln = starting_layer_names.first; sln; sln = sln->next)
|
|
{
|
|
String name = sln->s;
|
|
u64 name_hash = HashFnv64(Fnv64Basis, name);
|
|
IterState *state = (IterState *)DictValueOrNilFromHash(layer_name_to_state, name_hash, (u64)&NilIterState);
|
|
M_Layer *layer = state->layer;
|
|
if (layer->valid)
|
|
{
|
|
StackNode *n = PushStruct(scratch.arena, StackNode);
|
|
n->state = state;
|
|
StackPush(stack, n);
|
|
}
|
|
else
|
|
{
|
|
String err = StringF(arena, "Starting layer \"%F\" not found", FmtString(name));
|
|
M_PushError(arena, &result.errors, 0, err);
|
|
}
|
|
}
|
|
|
|
/* Flatten via post-order DFS */
|
|
while (stack)
|
|
{
|
|
StackNode *stack_node = stack;
|
|
StackPop(stack);
|
|
IterState *state = stack_node->state;
|
|
M_Layer *layer = state->layer;
|
|
|
|
if (stack_node->exit)
|
|
{
|
|
/* Push items to sorted root */
|
|
state->is_entered = 0;
|
|
state->is_exited = 1;
|
|
for (M_Entry *entry = layer->first; entry->valid; entry = entry->next)
|
|
{
|
|
M_PushEntry(arena, &result, entry->kind, entry->name_token, entry->args_count, entry->arg_tokens);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (state->is_entered)
|
|
{
|
|
/* Cyclic dependency */
|
|
/* FIXME: Handle cyclic dependency error */
|
|
Assert(0);
|
|
}
|
|
else if (!state->is_exited)
|
|
{
|
|
state->is_entered = 1;
|
|
|
|
/* Push downstream impl enters to stack */
|
|
for (M_Entry *entry = layer->first; entry->valid; entry = entry->next)
|
|
{
|
|
b32 include = (PlatformIsWindows && entry->kind == M_EntryKind_DefaultWindowsImpl);
|
|
if (include)
|
|
{
|
|
M_Token *impl_token = entry->arg_tokens[0];
|
|
if (impl_token->valid)
|
|
{
|
|
String impl_name = impl_token->s;
|
|
u64 hash = HashFnv64(Fnv64Basis, impl_name);
|
|
IterState *impl_layer_state = (IterState *)DictValueOrNilFromHash(layer_name_to_state, hash, (u64)&NilIterState);
|
|
M_Layer *impl_layer = impl_layer_state->layer;
|
|
if (impl_layer->valid)
|
|
{
|
|
if (!impl_layer_state->is_exited)
|
|
{
|
|
StackNode *n = PushStruct(scratch.arena, StackNode);
|
|
n->state = impl_layer_state;
|
|
StackPush(stack, n);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
String err = StringF(arena, "Layer \"%F\" not found", FmtString(impl_name));
|
|
M_PushError(arena, &result.errors, impl_token, err);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
M_PushError(arena, &result.errors, entry->name_token, Lit("Expected layer argument"));
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Push node exit to stack */
|
|
{
|
|
stack_node->exit = 1;
|
|
StackPush(stack, stack_node);
|
|
}
|
|
|
|
/* Push upstream dep enters to stack */
|
|
for (M_Entry *entry = layer->first; entry->valid; entry = entry->next)
|
|
{
|
|
if (entry->kind == M_EntryKind_Dep)
|
|
{
|
|
M_Token *dep_token = entry->arg_tokens[0];
|
|
if (dep_token->valid)
|
|
{
|
|
String dep_name = dep_token->s;
|
|
u64 hash = HashFnv64(Fnv64Basis, dep_name);
|
|
IterState *dep_layer_state = (IterState *)DictValueOrNilFromHash(layer_name_to_state, hash, (u64)&NilIterState);
|
|
M_Layer *dep_layer = dep_layer_state->layer;
|
|
if (dep_layer->valid)
|
|
{
|
|
if (!dep_layer_state->is_exited)
|
|
{
|
|
StackNode *n = PushStruct(scratch.arena, StackNode);
|
|
n->state = dep_layer_state;
|
|
StackPush(stack, n);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
String err = StringF(arena, "Layer \"%F\" not found", FmtString(dep_name));
|
|
M_PushError(arena, &result.errors, dep_token, err);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
M_PushError(arena, &result.errors, entry->name_token, Lit("Expected layer argument"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EndScratch(scratch);
|
|
return result;
|
|
}
|