power_play/src/meta/meta_lay.c

573 lines
17 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 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;
SllQueuePush(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);
}
}
////////////////////////////////////////////////////////////
//~ Lex
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);
SllQueuePushNZ(&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;
}
SllQueuePushNZ(&M_NilToken, file->first_token, file->last_token, t, next);
++file->tokens_count;
return t;
}
M_TokenFileList M_TokensFromSrcDirs(Arena *arena, StringList src_dirs)
{
M_TokenFileList result = Zi;
TempArena scratch = BeginScratch(arena);
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 = HashString(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 == ' ' || c == '\t';
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;
}
}
}
EndScratch(scratch);
return result;
}
////////////////////////////////////////////////////////////
//~ Parse
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;
SllQueuePushNZ(&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;
SllQueuePushNZ(&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 = HashString(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 = HashString(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
M_Layer M_FlattenEntries(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 = HashString(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 = HashString(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;
SllStackPush(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;
SllStackPop(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 entries to stack
for (M_Entry *entry = layer->first; entry->valid; entry = entry->next)
{
if (entry->kind == M_EntryKind_DefaultDownstream)
{
M_Token *platform_token = entry->arg_tokens[0];
M_Token *downstream_layer_token = entry->arg_tokens[1];
if (platform_token->valid && downstream_layer_token->valid)
{
// Determine platform match
b32 should_include = 0;
{
String platform_name = platform_token->s;
if (MatchString(platform_name, Lit("Any")))
{
should_include = 1;
}
else if (MatchString(platform_name, Lit("Win32")))
{
should_include = IsPlatformWindows;
}
else
{
String err = StringF(arena, "Unknown platform \"%F\"", FmtString(platform_name));
M_PushError(arena, &result.errors, platform_token, err);
}
}
// Include layer downstream
if (should_include)
{
String downstream_layer_name = downstream_layer_token->s;
u64 hash = HashString(downstream_layer_name);
IterState *downstream_layer_state = (IterState *)DictValueOrNilFromHash(layer_name_to_state, hash, (u64)&NilIterState);
M_Layer *downstream_layer = downstream_layer_state->layer;
if (downstream_layer->valid)
{
if (!downstream_layer_state->is_exited)
{
StackNode *n = PushStruct(scratch.arena, StackNode);
n->state = downstream_layer_state;
SllStackPush(stack, n);
}
}
else
{
String err = StringF(arena, "Layer \"%F\" not found", FmtString(downstream_layer_name));
M_PushError(arena, &result.errors, downstream_layer_token, err);
}
}
}
else
{
M_PushError(arena, &result.errors, entry->name_token, Lit("Expected platform and layer arguments"));
}
}
}
// Push node exit to stack
{
stack_node->exit = 1;
SllStackPush(stack, stack_node);
}
// Push upstream dep entries 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 = HashString(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;
SllStackPush(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;
}