Files
metadesk/code/metagen/metagen.c
T
2025-02-01 14:28:12 -05:00

1137 lines
34 KiB
C

// 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_RightShift: 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;
}