// Copyright (c) 2024 Epic Games Tools // Licensed under the MIT license (https://opensource.org/license/mit/) //////////////////////////////// //~ rjf: String Expression Operator Tables read_only global String8 mg_str_expr_op_symbol_string_table[MG_StrExprOp_COUNT] = { str8_lit_comp(""), str8_lit_comp("."), // MG_StrExprOp_Dot str8_lit_comp("->"), // MG_StrExprOp_ExpandIfTrue str8_lit_comp(".."), // MG_StrExprOp_Concat str8_lit_comp("=>"), // MG_StrExprOp_BumpToColumn str8_lit_comp("+"), // MG_StrExprOp_Add str8_lit_comp("-"), // MG_StrExprOp_Subtract str8_lit_comp("*"), // MG_StrExprOp_Multiply str8_lit_comp("/"), // MG_StrExprOp_Divide str8_lit_comp("%"), // MG_StrExprOp_Modulo str8_lit_comp("<<"), // MG_StrExprOp_LeftShift str8_lit_comp(">>"), // MG_StrExprOp_RightShift str8_lit_comp("&"), // MG_StrExprOp_BitwiseAnd str8_lit_comp("|"), // MG_StrExprOp_BitwiseOr str8_lit_comp("^"), // MG_StrExprOp_BitwiseXor str8_lit_comp("~"), // MG_StrExprOp_BitwiseNegate str8_lit_comp("&&"), // MG_StrExprOp_BooleanAnd str8_lit_comp("||"), // MG_StrExprOp_BooleanOr str8_lit_comp("!"), // MG_StrExprOp_BooleanNot str8_lit_comp("=="), // MG_StrExprOp_Equals str8_lit_comp("!="), // MG_StrExprOp_DoesNotEqual }; read_only global S8 mg_str_expr_op_precedence_table[MG_StrExprOp_COUNT] = { 0, 20, // MG_StrExprOp_Dot 1, // MG_StrExprOp_ExpandIfTrue 2, // MG_StrExprOp_Concat 12, // MG_StrExprOp_BumpToColumn 5, // MG_StrExprOp_Add 5, // MG_StrExprOp_Subtract 6, // MG_StrExprOp_Multiply 6, // MG_StrExprOp_Divide 6, // MG_StrExprOp_Modulo 7, // MG_StrExprOp_LeftShift 7, // MG_StrExprOp_RightShift 8, // MG_StrExprOp_BitwiseAnd 10, // MG_StrExprOp_BitwiseOr 9, // MG_StrExprOp_BitwiseXor 11, // MG_StrExprOp_BitwiseNegate 3, // MG_StrExprOp_BooleanAnd 3, // MG_StrExprOp_BooleanOr 11, // MG_StrExprOp_BooleanNot 4, // MG_StrExprOp_Equals 4, // MG_StrExprOp_DoesNotEqual }; read_only global MG_StrExprOpKind mg_str_expr_op_kind_table[MG_StrExprOp_COUNT] = { MG_StrExprOpKind_Null, MG_StrExprOpKind_Binary, // MG_StrExprOp_Dot MG_StrExprOpKind_Binary, // MG_StrExprOp_ExpandIfTrue MG_StrExprOpKind_Binary, // MG_StrExprOp_Concat MG_StrExprOpKind_Prefix, // MG_StrExprOp_BumpToColumn MG_StrExprOpKind_Binary, // MG_StrExprOp_Add MG_StrExprOpKind_Binary, // MG_StrExprOp_Subtract MG_StrExprOpKind_Binary, // MG_StrExprOp_Multiply MG_StrExprOpKind_Binary, // MG_StrExprOp_Divide MG_StrExprOpKind_Binary, // MG_StrExprOp_Modulo MG_StrExprOpKind_Binary, // MG_StrExprOp_LeftShift MG_StrExprOpKind_Binary, // MG_StrExprOp_RightShift MG_StrExprOpKind_Binary, // MG_StrExprOp_BitwiseAnd MG_StrExprOpKind_Binary, // MG_StrExprOp_BitwiseOr MG_StrExprOpKind_Binary, // MG_StrExprOp_BitwiseXor MG_StrExprOpKind_Prefix, // MG_StrExprOp_BitwiseNegate MG_StrExprOpKind_Binary, // MG_StrExprOp_BooleanAnd MG_StrExprOpKind_Binary, // MG_StrExprOp_BooleanOr MG_StrExprOpKind_Prefix, // MG_StrExprOp_BooleanNot MG_StrExprOpKind_Binary, // MG_StrExprOp_Equals MG_StrExprOpKind_Binary, // MG_StrExprOp_DoesNotEqual }; //////////////////////////////// //~ rjf: Basic Helpers internal U64 mg_hash_from_string(String8 string) { U64 result = 5381; for(U64 i = 0; i < string.size; i += 1) { result = ((result << 5) + result) + string.str[i]; } return result; } internal TxtPt mg_txt_pt_from_string_off(String8 string, U64 off) { TxtPt pt = txt_pt(1, 1); for(U64 idx = 0; idx < string.size && idx < off; idx += 1) { if(string.str[idx] == '\n') { pt.line += 1; pt.column = 1; } else { pt.column += 1; } } return pt; } //////////////////////////////// //~ rjf: Message Lists internal void mg_msg_list_push(Arena *arena, MG_MsgList *msgs, MG_Msg *msg) { MG_MsgNode *n = push_array(arena, MG_MsgNode, 1); MemoryCopyStruct(&n->v, msg); SLLQueuePush(msgs->first, msgs->last, n); msgs->count += 1; } //////////////////////////////// //~ rjf: String Escaping internal String8 mg_escaped_from_str8(Arena *arena, String8 string) { // NOTE(rjf): This doesn't handle hex/octal/unicode escape sequences right // now, just the simple stuff. Temp scratch = scratch_begin(&arena, 1); String8List strs = {0}; U64 start = 0; for(U64 idx = 0; idx <= string.size; idx += 1) { if(idx == string.size || string.str[idx] == '\\' || string.str[idx] == '\r') { String8 str = str8_substr(string, r1u64(start, idx)); if(str.size != 0) { str8_list_push(scratch.arena, &strs, str); } start = idx+1; } if(idx < string.size && string.str[idx] == '\\') { U8 next_char = string.str[idx+1]; U8 replace_byte = 0; switch(next_char) { default:{}break; case 'a': replace_byte = 0x07; break; case 'b': replace_byte = 0x08; break; case 'e': replace_byte = 0x1b; break; case 'f': replace_byte = 0x0c; break; case 'n': replace_byte = 0x0a; break; case 'r': replace_byte = 0x0d; break; case 't': replace_byte = 0x09; break; case 'v': replace_byte = 0x0b; break; case '\\':replace_byte = '\\'; break; case '\'':replace_byte = '\''; break; case '"': replace_byte = '"'; break; case '?': replace_byte = '?'; break; } String8 replace_string = push_str8_copy(scratch.arena, str8(&replace_byte, 1)); str8_list_push(scratch.arena, &strs, replace_string); if(replace_byte == '\\' || replace_byte == '"' || replace_byte == '\'') { idx += 1; start += 1; } } } String8 result = str8_list_join(arena, &strs, 0); scratch_end(scratch); return result; } //////////////////////////////// //~ rjf: String Wrapping internal String8List mg_wrapped_lines_from_string(Arena *arena, String8 string, U64 first_line_max_width, U64 max_width, U64 wrap_indent) { String8List list = {0}; Rng1U64 line_range = r1u64(0, 0); U64 wrapped_indent_level = 0; static char *spaces = " "; for (U64 idx = 0; idx <= string.size; idx += 1){ U8 chr = idx < string.size ? string.str[idx] : 0; if (chr == '\n'){ Rng1U64 candidate_line_range = line_range; candidate_line_range.max = idx; // NOTE(nick): when wrapping is interrupted with \n we emit a string without including \n // because later tool_fprint_list inserts separator after each node // except for last node, so don't strip last \n. if (idx + 1 == string.size){ candidate_line_range.max += 1; } String8 substr = str8_substr(string, candidate_line_range); str8_list_push(arena, &list, substr); line_range = r1u64(idx+1,idx+1); } else if (char_is_space(chr) || chr == 0){ Rng1U64 candidate_line_range = line_range; candidate_line_range.max = idx; String8 substr = str8_substr(string, candidate_line_range); U64 width_this_line = max_width-wrapped_indent_level; if (list.node_count == 0){ width_this_line = first_line_max_width; } if (substr.size > width_this_line){ String8 line = str8_substr(string, line_range); if (wrapped_indent_level > 0){ line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); } str8_list_push(arena, &list, line); line_range = r1u64(line_range.max+1, candidate_line_range.max); wrapped_indent_level = ClampTop(64, wrap_indent); } else{ line_range = candidate_line_range; } } } if (line_range.min < string.size && line_range.max > line_range.min){ String8 line = str8_substr(string, line_range); if (wrapped_indent_level > 0){ line = push_str8f(arena, "%.*s%S", wrapped_indent_level, spaces, line); } str8_list_push(arena, &list, line); } return list; } //////////////////////////////// //~ rjf: C-String-Izing internal String8 mg_c_string_literal_from_multiline_string(String8 string) { String8List strings = {0}; { str8_list_push(mg_arena, &strings, str8_lit("\"\"\n")); U64 active_line_start_off = 0; for(U64 off = 0; off <= string.size; off += 1) { B32 is_newline = (off < string.size && (string.str[off] == '\n' || string.str[off] == '\r')); B32 is_ender = (off >= string.size || is_newline); if(is_ender) { String8 line = str8_substr(string, r1u64(active_line_start_off, off)); str8_list_push(mg_arena, &strings, str8_lit("\"")); str8_list_push(mg_arena, &strings, line); if(is_newline) { str8_list_push(mg_arena, &strings, str8_lit("\\n\"\n")); } else { str8_list_push(mg_arena, &strings, str8_lit("\"\n")); } active_line_start_off = off+1; } if(is_newline && string.str[off] == '\r') { active_line_start_off += 1; off += 1; } } } String8 result = str8_list_join(mg_arena, &strings, 0); return result; } internal String8 mg_c_array_literal_contents_from_data(String8 data) { Temp scratch = scratch_begin(0, 0); String8List strings = {0}; { for(U64 off = 0; off < data.size;) { U64 chunk_size = Min(data.size-off, 64); U8 *chunk_bytes = data.str+off; String8 chunk_text_string = {0}; chunk_text_string.size = chunk_size*5; chunk_text_string.str = push_array(mg_arena, U8, chunk_text_string.size); for(U64 byte_idx = 0; byte_idx < chunk_size; byte_idx += 1) { String8 byte_str = push_str8f(scratch.arena, "0x%02x,", chunk_bytes[byte_idx]); MemoryCopy(chunk_text_string.str+byte_idx*5, byte_str.str, byte_str.size); } off += chunk_size; str8_list_push(mg_arena, &strings, chunk_text_string); str8_list_push(mg_arena, &strings, str8_lit("\n")); } } String8 result = str8_list_join(mg_arena, &strings, 0); scratch_end(scratch); return result; } //////////////////////////////// //~ rjf: Map Functions internal MG_Map mg_push_map(Arena *arena, U64 slot_count) { MG_Map map = {0}; map.slots_count = slot_count; map.slots = push_array(arena, MG_MapSlot, map.slots_count); return map; } internal void * mg_map_ptr_from_string(MG_Map *map, String8 string) { void *result = 0; { U64 hash = mg_hash_from_string(string); U64 slot_idx = hash%map->slots_count; MG_MapSlot *slot = &map->slots[slot_idx]; for(MG_MapNode *n = slot->first; n != 0; n = n->next) { if(str8_match(n->key, string, 0)) { result = n->val; break; } } } return result; } internal void mg_map_insert_ptr(Arena *arena, MG_Map *map, String8 string, void *val) { U64 hash = mg_hash_from_string(string); U64 slot_idx = hash%map->slots_count; MG_MapSlot *slot = &map->slots[slot_idx]; MG_MapNode *n = push_array(arena, MG_MapNode, 1); n->key = push_str8_copy(arena, string); n->val = val; SLLQueuePush(slot->first, slot->last, n); } //////////////////////////////// //~ rjf: String Expression Parsing internal MG_StrExpr * mg_push_str_expr(Arena *arena, MG_StrExprOp op, MD_Node *node) { MG_StrExpr *expr = push_array(arena, MG_StrExpr, 1); MemoryCopyStruct(expr, &mg_str_expr_nil); expr->op = op; expr->node = node; return expr; } internal MG_StrExprParseResult mg_str_expr_parse_from_first_opl__min_prec(Arena *arena, MD_Node *first, MD_Node *opl, S8 min_prec) { MG_StrExprParseResult parse = {&mg_str_expr_nil}; { MD_Node *it = first; //- rjf: consume prefix operators MG_StrExpr *leafmost_op = &mg_str_expr_nil; for(;it != opl && !md_node_is_nil(it);) { MG_StrExprOp found_op = MG_StrExprOp_Null; for(MG_StrExprOp op = (MG_StrExprOp)(MG_StrExprOp_Null+1); op < MG_StrExprOp_COUNT; op = (MG_StrExprOp)(op+1)) { if(mg_str_expr_op_kind_table[op] == MG_StrExprOpKind_Prefix && str8_match(it->string, mg_str_expr_op_symbol_string_table[op], 0) && mg_str_expr_op_precedence_table[op] >= min_prec) { found_op = op; break; } } if(found_op != MG_StrExprOp_Null) { MG_StrExpr *op_expr = mg_push_str_expr(arena, found_op, it); if(leafmost_op == &mg_str_expr_nil) { leafmost_op = op_expr; } op_expr->left = parse.root; parse.root = op_expr; it = it->next; } else { break; } } //- rjf: parse atom { MG_StrExpr *atom = &mg_str_expr_nil; if(it->flags & (MD_NodeFlag_Identifier|MD_NodeFlag_Numeric|MD_NodeFlag_StringLiteral) && md_node_is_nil(it->first)) { atom = mg_push_str_expr(arena, MG_StrExprOp_Null, it); it = it->next; } else if(!md_node_is_nil(it->first)) { MG_StrExprParseResult subparse = mg_str_expr_parse_from_first_opl__min_prec(arena, it->first, &md_nil_node, 0); atom = subparse.root; md_msg_list_concat_in_place(&parse.msgs, &subparse.msgs); it = it->next; } if(leafmost_op != &mg_str_expr_nil) { leafmost_op->left = atom; } else { parse.root = atom; } } //- rjf: parse binary operator extensions at this precedence level for(;it != opl && !md_node_is_nil(it);) { // rjf: find binary op kind of `it` MG_StrExprOp found_op = MG_StrExprOp_Null; for(MG_StrExprOp op = (MG_StrExprOp)(MG_StrExprOp_Null+1); op < MG_StrExprOp_COUNT; op = (MG_StrExprOp)(op+1)) { if(mg_str_expr_op_kind_table[op] == MG_StrExprOpKind_Binary && str8_match(it->string, mg_str_expr_op_symbol_string_table[op], 0) && mg_str_expr_op_precedence_table[op] >= min_prec) { found_op = op; break; } } // rjf: good found_op -> build binary expr if(found_op != MG_StrExprOp_Null) { MG_StrExpr *op_expr = mg_push_str_expr(arena, found_op, it); if(leafmost_op == &mg_str_expr_nil) { leafmost_op = op_expr; } op_expr->left = parse.root; parse.root = op_expr; it = it->next; } else { break; } // rjf: parse right hand side of binary operator MG_StrExprParseResult subparse = mg_str_expr_parse_from_first_opl__min_prec(arena, it, opl, mg_str_expr_op_precedence_table[found_op]+1); parse.root->right = subparse.root; md_msg_list_concat_in_place(&parse.msgs, &subparse.msgs); if(subparse.root == &mg_str_expr_nil) { md_msg_list_pushf(arena, &parse.msgs, it, MD_MsgKind_Error, "Missing right-hand-side of '%S'.", mg_str_expr_op_symbol_string_table[found_op]); } it = subparse.next_node; } // rjf: store next node for more caller-side parsing parse.next_node = it; } return parse; } internal MG_StrExprParseResult mg_str_expr_parse_from_first_opl(Arena *arena, MD_Node *first, MD_Node *opl) { MG_StrExprParseResult parse = mg_str_expr_parse_from_first_opl__min_prec(arena, first, opl, 0); return parse; } internal MG_StrExprParseResult mg_str_expr_parse_from_root(Arena *arena, MD_Node *root) { MG_StrExprParseResult parse = mg_str_expr_parse_from_first_opl__min_prec(arena, root->first, &md_nil_node, 0); return parse; } //////////////////////////////// //~ rjf: Table Generation Functions internal MG_NodeArray mg_node_array_make(Arena *arena, U64 count) { MG_NodeArray result = {0}; result.count = count; result.v = push_array(arena, MD_Node *, result.count); for(U64 idx = 0; idx < result.count; idx += 1) { result.v[idx] = &md_nil_node; } return result; } internal MG_NodeArray mg_child_array_from_node(Arena *arena, MD_Node *node) { MG_NodeArray children = mg_node_array_make(arena, md_child_count_from_node(node)); U64 idx = 0; for MD_EachNode(child, node->first) { children.v[idx] = child; idx += 1; } return children; } internal MG_NodeGrid mg_node_grid_make_from_node(Arena *arena, MD_Node *root) { MG_NodeGrid grid = {0}; // rjf: determine dimensions U64 row_count = md_child_count_from_node(root); U64 column_count = 0; for MD_EachNode(row, root->first) { U64 cell_count_this_row = md_child_count_from_node(row); column_count = Max(column_count, cell_count_this_row); } // rjf: fill grid grid.x_stride = 1; grid.y_stride = column_count; grid.cells = mg_node_array_make(arena, row_count*column_count); grid.row_parents = mg_node_array_make(arena, row_count); // rjf: fill nodes { U64 y = 0; for MD_EachNode(row, root->first) { U64 x = 0; grid.row_parents.v[y] = row; for MD_EachNode(cell, row->first) { grid.cells.v[x*grid.x_stride + y*grid.y_stride] = cell; x += 1; } y += 1; } } return grid; } internal MG_NodeArray mg_row_from_index(MG_NodeGrid grid, U64 index) { MG_NodeArray result = {0}; if(0 <= index && index < grid.cells.count / grid.x_stride) { result.count = grid.y_stride; result.v = &grid.cells.v[index*grid.y_stride]; } return result; } internal MG_NodeArray mg_column_from_index(Arena *arena, MG_NodeGrid grid, U64 index) { MG_NodeArray result = {0}; if(0 <= index && index < grid.y_stride) { U64 row_count = grid.cells.count / grid.y_stride; result = mg_node_array_make(arena, row_count); U64 idx = 0; for(U64 row_idx = 0; row_idx < row_count; row_idx += 1, idx += 1) { result.v[idx] = grid.cells.v[index*grid.x_stride + row_idx*grid.y_stride]; } } return result; } internal MD_Node * mg_node_from_grid_xy(MG_NodeGrid grid, U64 x, U64 y) { MD_Node *result = &md_nil_node; U64 idx = x*grid.x_stride + y*grid.y_stride; if(0 <= idx && idx < grid.cells.count) { result = grid.cells.v[idx]; } return result; } internal MG_ColumnDescArray mg_column_desc_array_make(Arena *arena, U64 count, MG_ColumnDesc *descs) { MG_ColumnDescArray result = {0}; result.count = count; result.v = push_array(arena, MG_ColumnDesc, result.count); MemoryCopy(result.v, descs, sizeof(*result.v)*result.count); return result; } internal MG_ColumnDescArray mg_column_desc_array_from_tag(Arena *arena, MD_Node *tag) { MG_ColumnDescArray result = {0}; result.count = md_child_count_from_node(tag); result.v = push_array(arena, MG_ColumnDesc, result.count); U64 idx = 0; for MD_EachNode(hdr, tag->first) { result.v[idx].name = push_str8_copy(arena, hdr->string); result.v[idx].kind = MG_ColumnKind_DirectCell; if(md_node_has_tag(hdr, str8_lit("tag_check"), 0)) { result.v[idx].kind = MG_ColumnKind_CheckForTag; } if(md_node_has_tag(hdr, str8_lit("tag_child"), 0)) { String8 tag_name = md_tag_from_string(hdr, str8_lit("tag_child"), 0)->first->string; result.v[idx].kind = MG_ColumnKind_TagChild; result.v[idx].tag_name = tag_name; } idx += 1; } return result; } internal U64 mg_column_index_from_name(MG_ColumnDescArray descs, String8 name) { U64 result = 0; for(U64 idx = 0; idx < descs.count; idx += 1) { if(str8_match(descs.v[idx].name, name, 0)) { result = idx; break; } } return result; } internal String8 mg_string_from_row_desc_idx(MD_Node *row_parent, MG_ColumnDescArray descs, U64 idx) { String8 result = {0}; // rjf: grab relevant column description MG_ColumnDesc *desc = 0; if(0 <= idx && idx < descs.count) { desc = descs.v + idx; } // rjf: grab node if(desc != 0) { switch(desc->kind) { default: break; case MG_ColumnKind_DirectCell: { // rjf: determine grid idx (shifted by synthetic columns) U64 cell_idx = idx; for(U64 col_idx = 0; col_idx < descs.count && col_idx < idx; col_idx += 1) { if(descs.v[col_idx].kind != MG_ColumnKind_DirectCell) { cell_idx -= 1; } } MD_Node *node = md_child_from_index(row_parent, cell_idx); result = node->string; }break; case MG_ColumnKind_CheckForTag: { String8 tag_name = desc->name; MD_Node *tag = md_tag_from_string(row_parent, tag_name, 0); result = md_node_is_nil(tag) ? str8_lit("0") : str8_lit("1"); }break; case MG_ColumnKind_TagChild: { String8 tag_name = desc->tag_name; MD_Node *tag = md_tag_from_string(row_parent, tag_name, 0); result = tag->first->string; }break; } } return result; } internal S64 mg_eval_table_expand_expr__numeric(MG_StrExpr *expr, MG_TableExpandInfo *info) { S64 result = 0; MG_StrExprOp op = expr->op; switch(op) { default: { if(MG_StrExprOp_FirstString <= op && op <= MG_StrExprOp_LastString) { Temp scratch = scratch_begin(0, 0); String8List result_strs = {0}; mg_eval_table_expand_expr__string(scratch.arena, expr, info, &result_strs); String8 result_str = str8_list_join(scratch.arena, &result_strs, 0); try_s64_from_str8_c_rules(result_str, &result); scratch_end(scratch); } }break; case MG_StrExprOp_Null: { try_s64_from_str8_c_rules(expr->node->string, &result); }break; //- rjf: numeric arithmetic binary ops case MG_StrExprOp_Add: case MG_StrExprOp_Subtract: case MG_StrExprOp_Multiply: case MG_StrExprOp_Divide: case MG_StrExprOp_Modulo: case MG_StrExprOp_LeftShift: case MG_StrExprOp_RightShift: case MG_StrExprOp_BitwiseAnd: case MG_StrExprOp_BitwiseOr: case MG_StrExprOp_BitwiseXor: case MG_StrExprOp_BooleanAnd: case MG_StrExprOp_BooleanOr: { S64 left_val = mg_eval_table_expand_expr__numeric(expr->left, info); S64 right_val = mg_eval_table_expand_expr__numeric(expr->right, info); switch(op) { default:break; case MG_StrExprOp_Add: result = left_val+right_val; break; case MG_StrExprOp_Subtract: result = left_val-right_val; break; case MG_StrExprOp_Multiply: result = left_val*right_val; break; case MG_StrExprOp_Divide: result = left_val/right_val; break; case MG_StrExprOp_Modulo: result = left_val%right_val; break; case MG_StrExprOp_LeftShift: result = left_val<>right_val; break; case MG_StrExprOp_BitwiseAnd: result = left_val&right_val; break; case MG_StrExprOp_BitwiseOr: result = left_val|right_val; break; case MG_StrExprOp_BitwiseXor: result = left_val^right_val; break; case MG_StrExprOp_BooleanAnd: result = left_val&&right_val; break; case MG_StrExprOp_BooleanOr: result = left_val||right_val; break; } }break; //- rjf: prefix unary ops case MG_StrExprOp_BitwiseNegate: case MG_StrExprOp_BooleanNot: { S64 right_val = mg_eval_table_expand_expr__numeric(expr->left, info); switch(op) { default:break; case MG_StrExprOp_BitwiseNegate: result = (S64)(~((U64)right_val)); break; case MG_StrExprOp_BooleanNot: result = !right_val; } }break; //- rjf: comparisons case MG_StrExprOp_Equals: case MG_StrExprOp_DoesNotEqual: { Temp scratch = scratch_begin(0, 0); String8List left_strs = {0}; String8List right_strs = {0}; mg_eval_table_expand_expr__string(scratch.arena, expr->left, info, &left_strs); mg_eval_table_expand_expr__string(scratch.arena, expr->right, info, &right_strs); String8 left_str = str8_list_join(scratch.arena, &left_strs, 0); String8 right_str = str8_list_join(scratch.arena, &right_strs, 0); B32 match = str8_match(left_str, right_str, 0); result = (op == MG_StrExprOp_Equals ? match : !match); scratch_end(scratch); }break; } return result; } internal void mg_eval_table_expand_expr__string(Arena *arena, MG_StrExpr *expr, MG_TableExpandInfo *info, String8List *out) { MG_StrExprOp op = expr->op; switch(op) { default: { if(MG_StrExprOp_FirstNumeric <= op && op <= MG_StrExprOp_LastNumeric) { S64 numeric_eval = mg_eval_table_expand_expr__numeric(expr, info); String8 numeric_eval_stringized = {0}; if(md_node_has_tag(md_root_from_node(expr->node), str8_lit("hex"), 0)) { numeric_eval_stringized = push_str8f(arena, "0x%I64x", numeric_eval); } else { numeric_eval_stringized = push_str8f(arena, "%I64d", numeric_eval); } str8_list_push(arena, out, numeric_eval_stringized); } }break; case MG_StrExprOp_Null: { str8_list_push(arena, out, expr->node->string); }break; case MG_StrExprOp_Dot: { // rjf: grab left/right MG_StrExpr *left_expr = expr->left; MD_Node *left_node = left_expr->node; MG_StrExpr *right_expr = expr->right; MD_Node *right_node = right_expr->node; // rjf: grab table name (LHS of .) and column lookup string (RHS of .) String8 expand_label = left_node->string; String8 column_lookup = right_node->string; // rjf: find which task corresponds to this table U64 row_idx = 0; MG_NodeGrid *grid = 0; MG_ColumnDescArray column_descs = {0}; { for(MG_TableExpandTask *task = info->first_expand_task; task != 0; task = task->next) { if(str8_match(expand_label, task->expansion_label, 0)) { row_idx = task->idx; grid = task->grid; column_descs = task->column_descs; break; } } } // rjf: grab row parent MD_Node *row_parent = &md_nil_node; if(grid && (0 <= row_idx && row_idx < grid->row_parents.count)) { row_parent = grid->row_parents.v[row_idx]; } // rjf: get string for this table lookup String8 lookup_string = {0}; { U64 column_idx = 0; if(str8_match(column_lookup, str8_lit("_it"), 0)) { lookup_string = push_str8f(arena, "%I64u", row_idx); } else { // NOTE(rjf): numeric column lookup (column index) if(right_node->flags & MD_NodeFlag_Numeric) { try_u64_from_str8_c_rules(column_lookup, &column_idx); } // NOTE(rjf): string column lookup (column name) if(right_node->flags & (MD_NodeFlag_Identifier|MD_NodeFlag_StringLiteral)) { column_idx = mg_column_index_from_name(column_descs, column_lookup); } lookup_string = mg_string_from_row_desc_idx(row_parent, column_descs, column_idx); if(str8_match(lookup_string, str8_lit("--"), 0)) { lookup_string = info->missing_value_fallback; } } } // rjf: push lookup string { str8_list_push(arena, out, lookup_string); } }break; case MG_StrExprOp_ExpandIfTrue: { S64 bool_value = mg_eval_table_expand_expr__numeric(expr->left, info); if(bool_value) { mg_eval_table_expand_expr__string(arena, expr->right, info, out); } }break; case MG_StrExprOp_Concat: { mg_eval_table_expand_expr__string(arena, expr->left, info, out); mg_eval_table_expand_expr__string(arena, expr->right, info, out); }break; case MG_StrExprOp_BumpToColumn: { S64 column = mg_eval_table_expand_expr__numeric(expr->left, info); S64 current_column = out->total_size; S64 spaces_to_push = column - current_column; if(spaces_to_push > 0) { String8 str = {0}; str.size = spaces_to_push; str.str = push_array(arena, U8, spaces_to_push); for(S64 idx = 0; idx < spaces_to_push; idx += 1) { str.str[idx] = ' '; } str8_list_push(arena, out, str); } }break; } } internal void mg_loop_table_column_expansion(Arena *arena, String8 strexpr, MG_TableExpandInfo *info, MG_TableExpandTask *task, String8List *out) { Temp scratch = scratch_begin(&arena, 1); for(U64 it_idx = 0; it_idx < task->count; it_idx += 1) { task->idx = it_idx; //- rjf: iterate all further dimensions, if there's left in the chain if(task->next) { mg_loop_table_column_expansion(arena, strexpr, info, task->next, out); } //- rjf: if this is the last task in the chain, perform expansion else { String8List expansion_strs = {0}; U64 start = 0; for(U64 char_idx = 0; char_idx <= strexpr.size;) { // rjf: push plain text parts of strexpr if(char_idx == strexpr.size || strexpr.str[char_idx] == '$') { String8 plain_text_substr = str8_substr(strexpr, r1u64(start, char_idx)); start = char_idx; if(plain_text_substr.size != 0) { str8_list_push(arena, &expansion_strs, plain_text_substr); } } // rjf: handle expansion expression if(strexpr.str[char_idx] == '$') { String8 string = str8_skip(strexpr, char_idx+1); Rng1U64 expr_range = {0}; S64 paren_nest = 0; for(U64 idx = 0; idx < string.size; idx += 1) { if(string.str[idx] == '(') { paren_nest += 1; if(paren_nest == 1) { expr_range.min = idx; } } if(string.str[idx] == ')') { paren_nest -= 1; if(paren_nest == 0) { expr_range.max = idx+1; break; } } } String8 expr_string = str8_substr(string, expr_range); MD_TokenizeResult expr_tokenize = md_tokenize_from_text(scratch.arena, expr_string); MD_ParseResult expr_base_parse = md_parse_from_text_tokens(scratch.arena, str8_lit(""), expr_string, expr_tokenize.tokens); MG_StrExprParseResult expr_parse = mg_str_expr_parse_from_root(scratch.arena, expr_base_parse.root->first); mg_eval_table_expand_expr__string(arena, expr_parse.root, info, &expansion_strs); char_idx = start = char_idx + 1 + expr_range.max; } else { char_idx += 1; } } String8 expansion_str = str8_list_join(arena, &expansion_strs, 0); if(expansion_str.size != 0) { str8_list_push(arena, out, expansion_str); } } } scratch_end(scratch); } internal String8List mg_string_list_from_table_gen(Arena *arena, MG_Map grid_name_map, MG_Map grid_column_desc_map, String8 fallback, MD_Node *gen) { String8List result = {0}; Temp scratch = scratch_begin(&arena, 1); if(md_node_is_nil(gen->first) && gen->string.size != 0) { str8_list_push(arena, &result, gen->string); str8_list_push(arena, &result, str8_lit("\n")); } else for MD_EachNode(strexpr_node, gen->first) { // rjf: build task list MG_TableExpandTask *first_task = 0; MG_TableExpandTask *last_task = 0; for MD_EachNode(tag, strexpr_node->first_tag) { if(str8_match(tag->string, str8_lit("expand"), 0)) { // rjf: grab args for this expansion MD_Node *table_name_node = md_child_from_index(tag, 0); MD_Node *expand_label_node = md_child_from_index(tag, 1); String8 table_name = table_name_node->string; String8 expand_label = expand_label_node->string; // rjf: lookup table / column descriptions MG_NodeGrid *grid = mg_map_ptr_from_string(&grid_name_map, table_name); MG_ColumnDescArray *column_descs = mg_map_ptr_from_string(&grid_column_desc_map, table_name); // rjf: figure out row count U64 grid_row_count = 0; if(grid != 0) { grid_row_count = grid->cells.count / grid->y_stride; } // rjf: push task for this expansion if(grid != 0) { MG_TableExpandTask *task = push_array(scratch.arena, MG_TableExpandTask, 1); task->expansion_label = expand_label; task->grid = grid; task->column_descs = *column_descs; task->count = grid_row_count; task->idx = 0; SLLQueuePush(first_task, last_task, task); } } } // rjf: do expansion generation, OR just push this string if we have no expansions { MG_TableExpandInfo info = {first_task, fallback}; if(first_task != 0) { mg_loop_table_column_expansion(arena, strexpr_node->string, &info, first_task, &result); } else { str8_list_push(arena, &result, strexpr_node->string); } } } scratch_end(scratch); return result; } //////////////////////////////// //~ rjf: Layer Lookup Functions internal String8 mg_layer_key_from_path(String8 path) { Temp scratch = scratch_begin(0, 0); U64 src_folder_pos = 0; for(U64 next_src_folder_pos = 0; next_src_folder_pos < path.size; next_src_folder_pos = str8_find_needle(path, next_src_folder_pos+1, str8_lit("src"), 0)) { src_folder_pos = next_src_folder_pos; } String8List path_parts = str8_split_path(scratch.arena, str8_chop_last_slash(str8_skip(path, src_folder_pos+4))); StringJoin join = {0}; join.sep = str8_lit("/"); String8 key = str8_list_join(mg_arena, &path_parts, &join); scratch_end(scratch); return key; } internal MG_Layer * mg_layer_from_key(String8 key) { U64 hash = mg_hash_from_string(key); U64 slot_idx = hash%mg_state->slots_count; MG_LayerSlot *slot = &mg_state->slots[slot_idx]; MG_Layer *layer = 0; for(MG_LayerNode *n = slot->first; n != 0; n = n->next) { if(str8_match(n->v.key, key, 0)) { layer = &n->v; break; } } if(layer == 0) { MG_LayerNode *n = push_array(mg_arena, MG_LayerNode, 1); SLLQueuePush(slot->first, slot->last, n); n->v.key = push_str8_copy(mg_arena, key); layer = &n->v; } return layer; }