static MD_Map cg_tbl_top_level_node_grid_map = {0}; static MD_Map cg_tbl_top_level_table_header_map = {0}; static MD_Map cg_tbl_layer_map_gen = {0}; static MD_Map cg_tbl_layer_map_gen_enum = {0}; static MD_Map cg_tbl_layer_map_gen_data = {0}; static MD_String8 cg_tbl_tag__table = MD_S8LitComp("table"); static MD_String8 cg_tbl_tag__table_gen = MD_S8LitComp("table_gen"); static MD_String8 cg_tbl_tag__table_gen_enum = MD_S8LitComp("table_gen_enum"); static MD_String8 cg_tbl_tag__table_gen_data = MD_S8LitComp("table_gen_data"); static CG_NodeArray CG_NodeArrayMake(MD_u64 count) { CG_NodeArray result = {0}; result.count = count; result.v = MD_PushArrayZero(cg_arena, MD_Node *, result.count); for(MD_u64 idx = 0; idx < result.count; idx += 1) { result.v[idx] = MD_NilNode(); } return result; } static CG_NodeGrid CG_GridFromNode(MD_Node *node) { CG_NodeGrid grid = {0}; //- rjf: determine dimensions MD_u64 row_count = 0; MD_u64 column_count = 0; { for(MD_EachNode(row, node->first_child)) { row_count += 1; MD_u64 cell_count_this_row = MD_ChildCountFromNode(row); column_count = MD_Max(cell_count_this_row, column_count); } } //- rjf: allocate cells / row parents { grid.cells = CG_NodeArrayMake(row_count * column_count); grid.row_parents = CG_NodeArrayMake(row_count); } //- rjf: fill cells { MD_u64 row_idx = 0; for(MD_EachNode(row, node->first_child)) { MD_u64 col_idx = 0; grid.row_parents.v[row_idx] = row; for(MD_EachNode(cell, row->first_child)) { grid.cells.v[row_idx * column_count + col_idx] = cell; col_idx += 1; } row_idx += 1; } } return grid; } static CG_TableHeader CG_TableHeaderFromTag(MD_Node *tag) { CG_TableHeader result = {0}; result.column_count = MD_ChildCountFromNode(tag); result.column_descs = MD_PushArrayZero(cg_arena, CG_ColumnDesc, result.column_count); MD_u64 idx = 0; for(MD_EachNode(column_node, tag->first_child)) { result.column_descs[idx].kind = CG_ColumnKind_Default; result.column_descs[idx].name = column_node->string; MD_Node *check_for_tag = MD_TagFromString(column_node, MD_S8Lit("check_for_tag"), 0); if(!MD_NodeIsNil(check_for_tag)) { result.column_descs[idx].kind = CG_ColumnKind_CheckForTag; result.column_descs[idx].tag_string = check_for_tag->first_child->string; } MD_Node *default_value = MD_TagFromString(column_node, MD_S8Lit("default"), 0); if(!MD_NodeIsNil(default_value)) { result.column_descs[idx].default_value = default_value->first_child->string; } idx += 1; } return result; } static MD_u64 CG_RowChildIndexFromColumnName(CG_TableHeader *header, MD_String8 column_name) { MD_u64 result = 0; for(MD_u64 idx = 0; idx < header->column_count; idx += 1) { if(MD_S8Match(header->column_descs[idx].name, column_name, 0)) { break; } if(header->column_descs[idx].kind == CG_ColumnKind_Default) { result += 1; } } return result; } static MD_i64 CG_TableExprEvaluate_Numeric(CG_ExpandInfo *info, MD_Expr *expr) { MD_i64 result = 0; CG_TableOp op = expr->op ? expr->op->op_id : CG_TableOp_Null; switch(op) { case CG_TableOp_Equal: case CG_TableOp_IsNotEqual: { MD_ArenaTemp scratch = MD_GetScratch(0, 0); MD_String8List left_strs = {0}; MD_String8List right_strs = {0}; CG_TableExprEvaluate_String(info, expr->left, &left_strs); CG_TableExprEvaluate_String(info, expr->right, &right_strs); MD_String8 left_str = MD_S8ListJoin(scratch.arena, left_strs, 0); MD_String8 right_str = MD_S8ListJoin(scratch.arena, right_strs, 0); result = MD_S8Match(left_str, right_str, 0); if(op == CG_TableOp_IsNotEqual) { result = !result; } MD_ReleaseScratch(scratch); }break; case CG_TableOp_BooleanAnd: case CG_TableOp_BooleanOr: { MD_i64 left = CG_TableExprEvaluate_Numeric(info, expr->left); MD_i64 right = CG_TableExprEvaluate_Numeric(info, expr->right); switch(op) { case CG_TableOp_BooleanAnd: result = left && right; break; case CG_TableOp_BooleanOr: result = left || right; break; } }break; } return result; } static void CG_TableExprEvaluate_String(CG_ExpandInfo *info, MD_Expr *expr, MD_String8List *out) { CG_TableOp op = expr->op ? expr->op->op_id : CG_TableOp_Null; switch(op) { default: case CG_TableOp_Null: { MD_S8ListPush(cg_arena, out, expr->md_node->string); }break; case CG_TableOp_Dot: { MD_Expr *label_expr = expr->left; MD_Expr *column_query_expr = expr->right; MD_Node *label_node = label_expr->md_node; MD_Node *column_query_node = column_query_expr->md_node; MD_String8 label = label_node->string; MD_String8 column_query = column_query_node->string; MD_b32 column_query_is_by_expand_idx = MD_S8Match(column_query_node->string, MD_S8Lit("_it"), 0); MD_b32 column_query_is_by_name = !column_query_is_by_expand_idx && column_query_node->flags & MD_NodeFlag_Identifier; MD_b32 column_query_is_by_index = column_query_node->flags & MD_NodeFlag_Numeric; // rjf: find which expansion this label refers to, grab its iterator CG_ExpandIter *iter = 0; for(CG_ExpandIter *it = info->first_expand_iter; it != 0; it = it->next) { if(MD_S8Match(it->label, label, 0)) { iter = it; break; } } // rjf: error on invalid label if(iter == 0) { MD_PrintMessageFmt(stderr, MD_CodeLocFromNode(label_node), MD_MessageKind_Error, "Expansion label \"%S\" was not found as referring to a valid @expand tag.", label); } // rjf: generate strings from iterator's table if(iter != 0) { CG_NodeGrid *grid = iter->grid; CG_TableHeader *header = iter->header; MD_Node *row = grid->row_parents.v[iter->idx]; // rjf: grab the cell string given the row & column_query MD_String8 cell_string = {0}; { // NOTE(rjf): expansion index counter if(column_query_is_by_expand_idx) { MD_i64 index = iter->idx; cell_string = MD_S8Fmt(cg_arena, "%" PRIu64 "", index); } // NOTE(rjf): by-name index (look into table header) else if(column_query_is_by_name && header != 0) { MD_u64 column_idx = 0; CG_ColumnDesc *column = 0; for(MD_u64 col_idx = 0; col_idx < header->column_count; col_idx += 1) { if(MD_S8Match(header->column_descs[col_idx].name, column_query, 0)) { column = &header->column_descs[col_idx]; column_idx = col_idx; break; } } MD_u64 row_child_idx = CG_RowChildIndexFromColumnName(header, column_query); // rjf: error on invalid column if(column == 0) { MD_PrintMessageFmt(stderr, MD_CodeLocFromNode(column_query_node), MD_MessageKind_Error, "Column query \"%S\" did not map to a valid column for expansion label \"%S\".", column_query, label); } if(column != 0) { switch(column->kind) { default: case CG_ColumnKind_Default: { MD_Node *cell_node = MD_ChildFromIndex(row, row_child_idx); cell_string = cell_node->string; if(MD_S8Match(cell_node->raw_string, MD_S8Lit("."), 0)) { cell_string = column->default_value; } }break; case CG_ColumnKind_CheckForTag: { MD_b32 has_tag = MD_NodeHasTag(row, column->tag_string, 0); cell_string = has_tag ? MD_S8Lit("1") : MD_S8Lit("0"); }break; } } } // NOTE(rjf): by-index (grab nth child of row) else if(column_query_is_by_index) { MD_i64 index = MD_CStyleIntFromString(column_query); cell_string = MD_ChildFromIndex(row, index)->string; } } MD_S8ListPush(cg_arena, out, cell_string); } }break; case CG_TableOp_Bump: { MD_u64 dst = MD_CStyleIntFromString(expr->unary_operand->md_node->string); MD_u64 src = out->total_size; MD_u64 spaces_to_print = dst - src; if(dst > src) { for(MD_u64 space_idx = 0; space_idx < spaces_to_print; space_idx += 1) { MD_S8ListPush(cg_arena, out, MD_S8Lit(" ")); } } }break; case CG_TableOp_CheckIfTrue: { MD_i64 check_val = CG_TableExprEvaluate_Numeric(info, expr->left); if(check_val) { CG_TableExprEvaluate_String(info, expr->right, out); } }break; case CG_TableOp_Concat: { CG_TableExprEvaluate_String(info, expr->left, out); CG_TableExprEvaluate_String(info, expr->right, out); }break; } } static void CG_LoopExpansionDimension(CG_ExpandIter *it, CG_ExpandInfo *info, MD_String8List *out) { if(it->next) { for(MD_u64 idx = 0; idx < it->count; idx += 1) { it->idx = idx; CG_LoopExpansionDimension(it->next, info, out); } } else { for(MD_u64 idx = 0; idx < it->count; idx += 1) { it->idx = idx; MD_String8List expansion_strs = {0}; MD_u64 start_idx = 0; for(MD_u64 char_idx = 0; char_idx <= info->strexpr.size; char_idx += 1) { MD_b32 is_expr_marker = info->strexpr.str[char_idx] == '$'; // rjf: push regular string contents if(char_idx == info->strexpr.size || is_expr_marker) { MD_String8 normal_string_chunk = MD_S8Substring(info->strexpr, start_idx, char_idx); MD_String8 escaped = CG_EscapedFromString(cg_arena, normal_string_chunk); MD_S8ListPush(cg_arena, &expansion_strs, escaped); } // rjf: handle expansion if(is_expr_marker) { MD_String8 expr_string = MD_S8Skip(info->strexpr, char_idx+1); { MD_i64 paren_nest = 0; for(MD_u64 expr_str_char_idx = 0; expr_str_char_idx < expr_string.size; expr_str_char_idx += 1) { if(expr_string.str[expr_str_char_idx] == '(') { paren_nest += 1; } else if(expr_string.str[expr_str_char_idx] == ')') { paren_nest -= 1; if(paren_nest == 0) { expr_string.size = expr_str_char_idx+1; break; } } } } MD_ParseResult parse = MD_ParseOneNode(cg_arena, expr_string, 0); MD_Node *node = parse.node; MD_ExprParseResult expr_parse = MD_ExprParse(cg_arena, &info->expr_op_table, node->first_child, MD_NilNode()); MD_Expr *expr = expr_parse.expr; CG_TableExprEvaluate_String(info, expr, &expansion_strs); MD_String8 parsed_string = MD_S8Substring(info->strexpr, char_idx+1, char_idx+1+parse.string_advance); parsed_string = MD_S8ChopWhitespace(parsed_string); start_idx = char_idx+1+parsed_string.size; } } // rjf: push expansion string to output list MD_String8 expansion_str = MD_S8ListJoin(cg_arena, expansion_strs, 0); MD_S8ListPush(cg_arena, out, expansion_str); } } } static MD_String8List CG_GenStringListFromNode(MD_ExprOprTable expr_op_table, MD_Node *gen) { MD_String8List result = {0}; MD_ArenaTemp scratch = MD_GetScratch(0, 0); for(MD_EachNode(strexpr, gen->first_child)) { //- rjf: build expansion iterator list CG_ExpandIter *first_iter = 0; CG_ExpandIter *last_iter = 0; { for(MD_EachNode(tag, strexpr->first_tag)) { if(MD_S8Match(tag->string, MD_S8Lit("expand"), 0)) { MD_Node *table_name_node = MD_ChildFromIndex(tag, 0); MD_Node *label_node = MD_ChildFromIndex(tag, 1); MD_String8 table_name = table_name_node->string; MD_String8 label = label_node->string; // rjf: grab the table associated with table_name CG_NodeGrid *grid = 0; { MD_MapSlot *slot = MD_MapLookup(&cg_tbl_top_level_node_grid_map, MD_MapKeyStr(table_name)); if(slot != 0) { grid = slot->val; } } // rjf: grab the table header associated with table_name CG_TableHeader *header = 0; { MD_MapSlot *slot = MD_MapLookup(&cg_tbl_top_level_table_header_map, MD_MapKeyStr(table_name)); if(slot != 0) { header = slot->val; } } // rjf: make iterator node if we got a grid if(grid != 0) { CG_ExpandIter *iter = MD_PushArrayZero(scratch.arena, CG_ExpandIter, 1); MD_QueuePush(first_iter, last_iter, iter); iter->grid = grid; iter->header = header; iter->label = label; iter->count = grid->row_parents.count; } // rjf: print out an error if grid is 0 if(grid == 0) { MD_PrintMessageFmt(stderr, MD_CodeLocFromNode(tag), MD_MessageKind_Error, "Table \"%S\" was not found.", table_name); } } } } //- rjf: generate string list for this strexpr & push to result if(first_iter != 0) { CG_ExpandInfo info = {0}; { info.strexpr = strexpr->string; info.first_expand_iter = first_iter; info.expr_op_table = expr_op_table; } CG_LoopExpansionDimension(first_iter, &info, &result); } //- rjf: generate non-expansion strings else { MD_String8 escaped = CG_EscapedFromString(cg_arena, strexpr->string); MD_S8ListPush(cg_arena, &result, escaped); } } MD_ReleaseScratch(scratch); return result; } static void CG_TBL_Generate(MD_Node *file_list) { //- rjf: initialize all maps cg_tbl_top_level_node_grid_map = MD_MapMake(cg_arena); cg_tbl_top_level_table_header_map = MD_MapMake(cg_arena); cg_tbl_layer_map_gen = MD_MapMake(cg_arena); cg_tbl_layer_map_gen_enum = MD_MapMake(cg_arena); cg_tbl_layer_map_gen_data = MD_MapMake(cg_arena); //- rjf: build table expression operator table MD_ExprOprTable table_expr_op_table = {0}; { MD_ExprOprList ops_list = {0}; MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 10, MD_S8Lit("."), CG_TableOp_Dot, 0); MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Prefix, 9, MD_S8Lit("=>"), CG_TableOp_Bump, 0); MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 6, MD_S8Lit("??"), CG_TableOp_CheckIfTrue, 0); MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 7, MD_S8Lit(".."), CG_TableOp_Concat, 0); MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 8, MD_S8Lit("=="), CG_TableOp_Equal, 0); MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 8, MD_S8Lit("!="), CG_TableOp_IsNotEqual, 0); MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 5, MD_S8Lit("&&"), CG_TableOp_BooleanAnd, 0); MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 4, MD_S8Lit("||"), CG_TableOp_BooleanOr, 0); table_expr_op_table = MD_ExprBakeOprTableFromList(cg_arena, &ops_list); } //- rjf: gather phase for(MD_EachNode(file_ref, file_list->first_child)) { MD_Node *file = MD_ResolveNodeFromReference(file_ref); MD_String8 layer_name = file->string; MD_MapKey layer_key = MD_MapKeyStr(layer_name); for(MD_EachNode(node, file->first_child)) { MD_Node *table_tag = MD_TagFromString(node, cg_tbl_tag__table, 0); if(!MD_NodeIsNil(table_tag)) { CG_NodeGrid *grid = MD_PushArrayZero(cg_arena, CG_NodeGrid, 1); *grid = CG_GridFromNode(node); MD_MapOverwrite(cg_arena, &cg_tbl_top_level_node_grid_map, MD_MapKeyStr(node->string), grid); CG_TableHeader *header = MD_PushArrayZero(cg_arena, CG_TableHeader, 1); *header = CG_TableHeaderFromTag(table_tag); MD_MapOverwrite(cg_arena, &cg_tbl_top_level_table_header_map, MD_MapKeyStr(node->string), header); } if(MD_NodeHasTag(node, cg_tbl_tag__table_gen, 0)) { MD_MapInsert(cg_arena, &cg_tbl_layer_map_gen, layer_key, node); } if(MD_NodeHasTag(node, cg_tbl_tag__table_gen_enum, 0)) { MD_MapInsert(cg_arena, &cg_tbl_layer_map_gen_enum, layer_key, node); } if(MD_NodeHasTag(node, cg_tbl_tag__table_gen_data, 0)) { MD_MapInsert(cg_arena, &cg_tbl_layer_map_gen_data, layer_key, node); } } } //- rjf: generation phase for(MD_EachNode(file_ref, file_list->first_child)) { MD_Node *file = MD_ResolveNodeFromReference(file_ref); MD_String8 layer_name = file->string; MD_MapKey layer_key = MD_MapKeyStr(layer_name); //- rjf: generate all table enums for(MD_MapSlot *slot = MD_MapLookup(&cg_tbl_layer_map_gen_enum, layer_key); slot != 0; slot = MD_MapScan(slot->next, layer_key)) { MD_Node *gen = (MD_Node *)slot->val; CG_FilePair f = CG_FilePairFromNode(gen); FILE *file = CG_FileFromNodePair(gen, &f); fprintf(file, "enum %.*s\n{\n", MD_S8VArg(gen->string)); MD_String8List gen_strings = CG_GenStringListFromNode(table_expr_op_table, gen); MD_StringJoin join = { MD_S8Lit(""), MD_S8Lit("\n"), MD_S8Lit("") }; MD_String8 gen_string = MD_S8ListJoin(cg_arena, gen_strings, &join); fprintf(file, "%.*s", MD_S8VArg(gen_string)); fprintf(file, "\n};\n\n"); } //- rjf: generate all data tables for(MD_MapSlot *slot = MD_MapLookup(&cg_tbl_layer_map_gen_data, layer_key); slot != 0; slot = MD_MapScan(slot->next, layer_key)) { MD_Node *gen = (MD_Node *)slot->val; MD_Node *tag = MD_TagFromString(gen, cg_tbl_tag__table_gen_data, 0); MD_Node *data_table_type_node = tag->first_child; MD_String8 data_table_type = data_table_type_node->string; MD_String8List gen_strings = CG_GenStringListFromNode(table_expr_op_table, gen); MD_String8 h_decl_specifier = MD_S8Lit("extern"); CG_FilePair f = CG_FilePairFromNode(gen); fprintf(f.h, "%.*s %.*s %.*s[%" PRIu64 "];\n\n", MD_S8VArg(h_decl_specifier), MD_S8VArg(data_table_type), MD_S8VArg(gen->string), gen_strings.node_count); fprintf(f.c, "%.*s %.*s[%" PRIu64 "] =\n{\n", MD_S8VArg(data_table_type), MD_S8VArg(gen->string), gen_strings.node_count); MD_StringJoin join = { MD_S8Lit(""), MD_S8Lit("\n"), MD_S8Lit("") }; MD_String8 gen_string = MD_S8ListJoin(cg_arena, gen_strings, &join); fprintf(f.c, "%.*s", MD_S8VArg(gen_string)); fprintf(f.c, "\n};\n"); fprintf(f.c, "\n"); } //- rjf: generate all general generations for(MD_MapSlot *slot = MD_MapLookup(&cg_tbl_layer_map_gen, layer_key); slot != 0; slot = MD_MapScan(slot->next, layer_key)) { MD_Node *gen = (MD_Node *)slot->val; CG_FilePair f = CG_FilePairFromNode(gen); FILE *file = CG_FileFromNodePair(gen, &f); MD_String8List gen_strings = CG_GenStringListFromNode(table_expr_op_table, gen); MD_StringJoin join = { MD_S8Lit(""), MD_S8Lit("\n"), MD_S8Lit("") }; MD_String8 gen_string = MD_S8ListJoin(cg_arena, gen_strings, &join); fprintf(file, "%.*s", MD_S8VArg(gen_string)); fprintf(file, "\n\n"); } } }