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 = 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 == ' ' || 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 = 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 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 = 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; 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 = HashFnv64(Fnv64Basis, 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 = 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; 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; }