From 2ba468ffafbde4b68a8f4be4a5887cb87f370762 Mon Sep 17 00:00:00 2001 From: Ed_ Date: Sat, 1 Feb 2025 12:49:46 -0500 Subject: [PATCH] Bring over RADDBG's mdesk and metagen modules --- mdesk/mdesk.c | 1200 ++++++++++ mdesk/mdesk.h | 325 +++ metagen/metagen.c | 1136 ++++++++++ metagen/metagen.h | 329 +++ metagen/metagen_base/metagen_base_arena.c | 195 ++ metagen/metagen_base/metagen_base_arena.h | 80 + .../metagen_base/metagen_base_command_line.c | 232 ++ .../metagen_base/metagen_base_command_line.h | 54 + .../metagen_base_context_cracking.h | 247 +++ metagen/metagen_base/metagen_base_core.c | 562 +++++ metagen/metagen_base/metagen_base_core.h | 800 +++++++ .../metagen_base/metagen_base_entry_point.c | 92 + .../metagen_base/metagen_base_entry_point.h | 10 + metagen/metagen_base/metagen_base_inc.c | 19 + metagen/metagen_base/metagen_base_inc.h | 23 + metagen/metagen_base/metagen_base_log.c | 103 + metagen/metagen_base/metagen_base_log.h | 65 + metagen/metagen_base/metagen_base_markup.c | 21 + metagen/metagen_base/metagen_base_markup.h | 12 + metagen/metagen_base/metagen_base_math.c | 616 +++++ metagen/metagen_base/metagen_base_math.h | 649 ++++++ metagen/metagen_base/metagen_base_profile.c | 2 + metagen/metagen_base/metagen_base_profile.h | 74 + metagen/metagen_base/metagen_base_strings.c | 1975 +++++++++++++++++ metagen/metagen_base/metagen_base_strings.h | 381 ++++ .../metagen_base_thread_context.c | 87 + .../metagen_base_thread_context.h | 41 + metagen/metagen_main.c | 656 ++++++ .../core/linux/metagen_os_core_linux.c | 1260 +++++++++++ .../core/linux/metagen_os_core_linux.h | 134 ++ metagen/metagen_os/core/metagen_os_core.c | 173 ++ metagen/metagen_os/core/metagen_os_core.h | 336 +++ .../core/win32/metagen_os_core_win32.c | 1646 ++++++++++++++ .../core/win32/metagen_os_core_win32.h | 122 + metagen/metagen_os/metagen_os_inc.c | 12 + metagen/metagen_os/metagen_os_inc.h | 25 + 36 files changed, 13694 insertions(+) create mode 100644 mdesk/mdesk.c create mode 100644 mdesk/mdesk.h create mode 100644 metagen/metagen.c create mode 100644 metagen/metagen.h create mode 100644 metagen/metagen_base/metagen_base_arena.c create mode 100644 metagen/metagen_base/metagen_base_arena.h create mode 100644 metagen/metagen_base/metagen_base_command_line.c create mode 100644 metagen/metagen_base/metagen_base_command_line.h create mode 100644 metagen/metagen_base/metagen_base_context_cracking.h create mode 100644 metagen/metagen_base/metagen_base_core.c create mode 100644 metagen/metagen_base/metagen_base_core.h create mode 100644 metagen/metagen_base/metagen_base_entry_point.c create mode 100644 metagen/metagen_base/metagen_base_entry_point.h create mode 100644 metagen/metagen_base/metagen_base_inc.c create mode 100644 metagen/metagen_base/metagen_base_inc.h create mode 100644 metagen/metagen_base/metagen_base_log.c create mode 100644 metagen/metagen_base/metagen_base_log.h create mode 100644 metagen/metagen_base/metagen_base_markup.c create mode 100644 metagen/metagen_base/metagen_base_markup.h create mode 100644 metagen/metagen_base/metagen_base_math.c create mode 100644 metagen/metagen_base/metagen_base_math.h create mode 100644 metagen/metagen_base/metagen_base_profile.c create mode 100644 metagen/metagen_base/metagen_base_profile.h create mode 100644 metagen/metagen_base/metagen_base_strings.c create mode 100644 metagen/metagen_base/metagen_base_strings.h create mode 100644 metagen/metagen_base/metagen_base_thread_context.c create mode 100644 metagen/metagen_base/metagen_base_thread_context.h create mode 100644 metagen/metagen_main.c create mode 100644 metagen/metagen_os/core/linux/metagen_os_core_linux.c create mode 100644 metagen/metagen_os/core/linux/metagen_os_core_linux.h create mode 100644 metagen/metagen_os/core/metagen_os_core.c create mode 100644 metagen/metagen_os/core/metagen_os_core.h create mode 100644 metagen/metagen_os/core/win32/metagen_os_core_win32.c create mode 100644 metagen/metagen_os/core/win32/metagen_os_core_win32.h create mode 100644 metagen/metagen_os/metagen_os_inc.c create mode 100644 metagen/metagen_os/metagen_os_inc.h diff --git a/mdesk/mdesk.c b/mdesk/mdesk.c new file mode 100644 index 0000000..ca6d85e --- /dev/null +++ b/mdesk/mdesk.c @@ -0,0 +1,1200 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Message Type Functions + +internal void +md_msg_list_push(Arena *arena, MD_MsgList *msgs, MD_Node *node, MD_MsgKind kind, String8 string) +{ + MD_Msg *msg = push_array(arena, MD_Msg, 1); + msg->node = node; + msg->kind = kind; + msg->string = string; + SLLQueuePush(msgs->first, msgs->last, msg); + msgs->count += 1; + msgs->worst_message_kind = Max(kind, msgs->worst_message_kind); +} + +internal void +md_msg_list_pushf(Arena *arena, MD_MsgList *msgs, MD_Node *node, MD_MsgKind kind, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(arena, fmt, args); + md_msg_list_push(arena, msgs, node, kind, string); + va_end(args); +} + +internal void +md_msg_list_concat_in_place(MD_MsgList *dst, MD_MsgList *to_push) +{ + if(to_push->first != 0) + { + if(dst->last) + { + dst->last->next = to_push->first; + dst->last = to_push->last; + dst->count += to_push->count; + dst->worst_message_kind = Max(dst->worst_message_kind, to_push->worst_message_kind); + } + else + { + MemoryCopyStruct(dst, to_push); + } + } + MemoryZeroStruct(to_push); +} + +//////////////////////////////// +//~ rjf: Token Type Functions + +internal MD_Token +md_token_make(Rng1U64 range, MD_TokenFlags flags) +{ + MD_Token token = {range, flags}; + return token; +} + +internal B32 +md_token_match(MD_Token a, MD_Token b) +{ + return (a.range.min == b.range.min && + a.range.max == b.range.max && + a.flags == b.flags); +} + +internal String8List +md_string_list_from_token_flags(Arena *arena, MD_TokenFlags flags) +{ + String8List strs = {0}; + if(flags & MD_TokenFlag_Identifier ){str8_list_push(arena, &strs, str8_lit("Identifier"));} + if(flags & MD_TokenFlag_Numeric ){str8_list_push(arena, &strs, str8_lit("Numeric"));} + if(flags & MD_TokenFlag_StringLiteral ){str8_list_push(arena, &strs, str8_lit("StringLiteral"));} + if(flags & MD_TokenFlag_Symbol ){str8_list_push(arena, &strs, str8_lit("Symbol"));} + if(flags & MD_TokenFlag_Reserved ){str8_list_push(arena, &strs, str8_lit("Reserved"));} + if(flags & MD_TokenFlag_Comment ){str8_list_push(arena, &strs, str8_lit("Comment"));} + if(flags & MD_TokenFlag_Whitespace ){str8_list_push(arena, &strs, str8_lit("Whitespace"));} + if(flags & MD_TokenFlag_Newline ){str8_list_push(arena, &strs, str8_lit("Newline"));} + if(flags & MD_TokenFlag_BrokenComment ){str8_list_push(arena, &strs, str8_lit("BrokenComment"));} + if(flags & MD_TokenFlag_BrokenStringLiteral ){str8_list_push(arena, &strs, str8_lit("BrokenStringLiteral"));} + if(flags & MD_TokenFlag_BadCharacter ){str8_list_push(arena, &strs, str8_lit("BadCharacter"));} + return strs; +} + +internal void +md_token_chunk_list_push(Arena *arena, MD_TokenChunkList *list, U64 cap, MD_Token token) +{ + MD_TokenChunkNode *node = list->last; + if(node == 0 || node->count >= node->cap) + { + node = push_array(arena, MD_TokenChunkNode, 1); + node->cap = cap; + node->v = push_array_no_zero(arena, MD_Token, cap); + SLLQueuePush(list->first, list->last, node); + list->chunk_count += 1; + } + MemoryCopyStruct(&node->v[node->count], &token); + node->count += 1; + list->total_token_count += 1; +} + +internal MD_TokenArray +md_token_array_from_chunk_list(Arena *arena, MD_TokenChunkList *chunks) +{ + MD_TokenArray result = {0}; + result.count = chunks->total_token_count; + result.v = push_array_no_zero(arena, MD_Token, result.count); + U64 write_idx = 0; + for(MD_TokenChunkNode *n = chunks->first; n != 0; n = n->next) + { + MemoryCopy(result.v+write_idx, n->v, sizeof(MD_Token)*n->count); + write_idx += n->count; + } + return result; +} + +internal String8 +md_content_string_from_token_flags_str8(MD_TokenFlags flags, String8 string) +{ + U64 num_chop = 0; + U64 num_skip = 0; + { + num_skip += 3*!!(flags & MD_TokenFlag_StringTriplet); + num_chop += 3*!!(flags & MD_TokenFlag_StringTriplet); + num_skip += 1*(!(flags & MD_TokenFlag_StringTriplet) && flags & MD_TokenFlag_StringLiteral); + num_chop += 1*(!(flags & MD_TokenFlag_StringTriplet) && flags & MD_TokenFlag_StringLiteral); + } + String8 result = string; + result = str8_chop(result, num_chop); + result = str8_skip(result, num_skip); + return result; +} + +//////////////////////////////// +//~ rjf: Node Type Functions + +//- rjf: flag conversions + +internal MD_NodeFlags +md_node_flags_from_token_flags(MD_TokenFlags flags) +{ + MD_NodeFlags result = 0; + result |= MD_NodeFlag_Identifier*!!(flags&MD_TokenFlag_Identifier); + result |= MD_NodeFlag_Numeric*!!(flags&MD_TokenFlag_Numeric); + result |= MD_NodeFlag_StringLiteral*!!(flags&MD_TokenFlag_StringLiteral); + result |= MD_NodeFlag_Symbol*!!(flags&MD_TokenFlag_Symbol); + result |= MD_NodeFlag_StringSingleQuote *!!(flags&MD_TokenFlag_StringSingleQuote); + result |= MD_NodeFlag_StringDoubleQuote *!!(flags&MD_TokenFlag_StringDoubleQuote); + result |= MD_NodeFlag_StringTick*!!(flags&MD_TokenFlag_StringTick); + result |= MD_NodeFlag_StringTriplet*!!(flags&MD_TokenFlag_StringTriplet); + return result; +} + +//- rjf: nil + +internal B32 +md_node_is_nil(MD_Node *node) +{ + return (node == 0 || node == &md_nil_node || node->kind == MD_NodeKind_Nil); +} + +//- rjf: iteration + +internal MD_NodeRec +md_node_rec_depth_first(MD_Node *node, MD_Node *subtree_root, U64 child_off, U64 sib_off) +{ + MD_NodeRec rec = {0}; + rec.next = &md_nil_node; + if(!md_node_is_nil(*MemberFromOffset(MD_Node **, node, child_off))) + { + rec.next = *MemberFromOffset(MD_Node **, node, child_off); + rec.push_count = 1; + } + else for(MD_Node *p = node; !md_node_is_nil(p) && p != subtree_root; p = p->parent, rec.pop_count += 1) + { + if(!md_node_is_nil(*MemberFromOffset(MD_Node **, p, sib_off))) + { + rec.next = *MemberFromOffset(MD_Node **, p, sib_off); + break; + } + } + return rec; +} + +//- rjf: tree building + +internal MD_Node * +md_push_node(Arena *arena, MD_NodeKind kind, MD_NodeFlags flags, String8 string, String8 raw_string, U64 src_offset) +{ + MD_Node *node = push_array(arena, MD_Node, 1); + node->first = node->last = node->parent = node->next = node->prev = node->first_tag = node->last_tag = &md_nil_node; + node->kind = kind; + node->flags = flags; + node->string = string; + node->raw_string = raw_string; + node->src_offset = src_offset; + return node; +} + +internal void +md_node_insert_child(MD_Node *parent, MD_Node *prev_child, MD_Node *node) +{ + node->parent = parent; + DLLInsert_NPZ(&md_nil_node, parent->first, parent->last, prev_child, node, next, prev); +} + +internal void +md_node_insert_tag(MD_Node *parent, MD_Node *prev_child, MD_Node *node) +{ + node->kind = MD_NodeKind_Tag; + node->parent = parent; + DLLInsert_NPZ(&md_nil_node, parent->first_tag, parent->last_tag, prev_child, node, next, prev); +} + +internal void +md_node_push_child(MD_Node *parent, MD_Node *node) +{ + node->parent = parent; + DLLPushBack_NPZ(&md_nil_node, parent->first, parent->last, node, next, prev); +} + +internal void +md_node_push_tag(MD_Node *parent, MD_Node *node) +{ + node->kind = MD_NodeKind_Tag; + node->parent = parent; + DLLPushBack_NPZ(&md_nil_node, parent->first_tag, parent->last_tag, node, next, prev); +} + +internal void +md_unhook(MD_Node *node) +{ + MD_Node *parent = node->parent; + if(!md_node_is_nil(parent)) + { + if(node->kind == MD_NodeKind_Tag) + { + DLLRemove_NPZ(&md_nil_node, parent->first_tag, parent->last_tag, node, next, prev); + } + else + { + DLLRemove_NPZ(&md_nil_node, parent->first, parent->last, node, next, prev); + } + node->parent = &md_nil_node; + } +} + +//- rjf: tree introspection + +internal MD_Node * +md_node_from_chain_string(MD_Node *first, MD_Node *opl, String8 string, StringMatchFlags flags) +{ + MD_Node *result = &md_nil_node; + for(MD_Node *n = first; !md_node_is_nil(n) && n != opl; n = n->next) + { + if(str8_match(n->string, string, flags)) + { + result = n; + break; + } + } + return result; +} + +internal MD_Node * +md_node_from_chain_index(MD_Node *first, MD_Node *opl, U64 index) +{ + MD_Node *result = &md_nil_node; + S64 idx = 0; + for(MD_Node *n = first; !md_node_is_nil(n) && n != opl; n = n->next, idx += 1) + { + if(index == idx) + { + result = n; + break; + } + } + return result; +} + +internal MD_Node * +md_node_from_chain_flags(MD_Node *first, MD_Node *opl, MD_NodeFlags flags) +{ + MD_Node *result = &md_nil_node; + for(MD_Node *n = first; !md_node_is_nil(n) && n != opl; n = n->next) + { + if(n->flags & flags) + { + result = n; + break; + } + } + return result; +} + +internal U64 +md_index_from_node(MD_Node *node) +{ + U64 index = 0; + for(MD_Node *n = node->prev; !md_node_is_nil(n); n = n->prev) + { + index += 1; + } + return index; +} + +internal MD_Node * +md_root_from_node(MD_Node *node) +{ + MD_Node *result = node; + for(MD_Node *p = node->parent; (p->kind == MD_NodeKind_Main || p->kind == MD_NodeKind_Tag) && !md_node_is_nil(p); p = p->parent) + { + result = p; + } + return result; +} + +internal MD_Node * +md_child_from_string(MD_Node *node, String8 child_string, StringMatchFlags flags) +{ + return md_node_from_chain_string(node->first, &md_nil_node, child_string, flags); +} + +internal MD_Node * +md_tag_from_string(MD_Node *node, String8 tag_string, StringMatchFlags flags) +{ + return md_node_from_chain_string(node->first_tag, &md_nil_node, tag_string, flags); +} + +internal MD_Node * +md_child_from_index(MD_Node *node, U64 index) +{ + return md_node_from_chain_index(node->first, &md_nil_node, index); +} + +internal MD_Node * +md_tag_from_index(MD_Node *node, U64 index) +{ + return md_node_from_chain_index(node->first_tag, &md_nil_node, index); +} + +internal MD_Node * +md_tag_arg_from_index(MD_Node *node, String8 tag_string, StringMatchFlags flags, U64 index) +{ + MD_Node *tag = md_tag_from_string(node, tag_string, flags); + return md_child_from_index(tag, index); +} + +internal MD_Node * +md_tag_arg_from_string(MD_Node *node, String8 tag_string, StringMatchFlags tag_str_flags, String8 arg_string, StringMatchFlags arg_str_flags) +{ + MD_Node *tag = md_tag_from_string(node, tag_string, tag_str_flags); + MD_Node *arg = md_child_from_string(tag, arg_string, arg_str_flags); + return arg; +} + +internal B32 +md_node_has_child(MD_Node *node, String8 string, StringMatchFlags flags) +{ + return !md_node_is_nil(md_child_from_string(node, string, flags)); +} + +internal B32 +md_node_has_tag(MD_Node *node, String8 string, StringMatchFlags flags) +{ + return !md_node_is_nil(md_tag_from_string(node, string, flags)); +} + +internal U64 +md_child_count_from_node(MD_Node *node) +{ + U64 result = 0; + for(MD_Node *child = node->first; !md_node_is_nil(child); child = child->next) + { + result += 1; + } + return result; +} + +internal U64 +md_tag_count_from_node(MD_Node *node) +{ + U64 result = 0; + for(MD_Node *child = node->first_tag; !md_node_is_nil(child); child = child->next) + { + result += 1; + } + return result; +} + +internal String8 +md_string_from_children(Arena *arena, MD_Node *root) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List strs = {0}; + for MD_EachNode(child, root->first) + { + if(child->flags == child->prev->flags) + { + str8_list_push(scratch.arena, &strs, str8_lit(" ")); + } + str8_list_push(scratch.arena, &strs, child->string); + } + String8 result = str8_list_join(arena, &strs, 0); + scratch_end(scratch); + return result; +} + +//- rjf: tree comparison + +internal B32 +md_node_match(MD_Node *a, MD_Node *b, StringMatchFlags flags) +{ + B32 result = 0; + if(a->kind == b->kind && str8_match(a->string, b->string, flags)) + { + result = 1; + if(result) + { + result = result && a->flags == b->flags; + } + if(result && a->kind != MD_NodeKind_Tag) + { + for(MD_Node *a_tag = a->first_tag, *b_tag = b->first_tag; + !md_node_is_nil(a_tag) || !md_node_is_nil(b_tag); + a_tag = a_tag->next, b_tag = b_tag->next) + { + if(md_node_match(a_tag, b_tag, flags)) + { + for(MD_Node *a_tag_arg = a_tag->first, *b_tag_arg = b_tag->first; + !md_node_is_nil(a_tag_arg) || !md_node_is_nil(b_tag_arg); + a_tag_arg = a_tag_arg->next, b_tag_arg = b_tag_arg->next) + { + if(!md_tree_match(a_tag_arg, b_tag_arg, flags)) + { + result = 0; + goto end; + } + } + } + else + { + result = 0; + goto end; + } + } + } + } + end:; + return result; +} + +internal B32 +md_tree_match(MD_Node *a, MD_Node *b, StringMatchFlags flags) +{ + B32 result = md_node_match(a, b, flags); + if(result) + { + for(MD_Node *a_child = a->first, *b_child = b->first; + !md_node_is_nil(a_child) || !md_node_is_nil(b_child); + a_child = a_child->next, b_child = b_child->next) + { + if(!md_tree_match(a_child, b_child, flags)) + { + result = 0; + goto end; + } + } + } + end:; + return result; +} + +//- rjf: tree duplication + +internal MD_Node * +md_tree_copy(Arena *arena, MD_Node *src_root) +{ + MD_Node *dst_root = &md_nil_node; + MD_Node *dst_parent = dst_root; + { + MD_NodeRec rec = {0}; + for(MD_Node *src = src_root; !md_node_is_nil(src); src = rec.next) + { + MD_Node *dst = push_array(arena, MD_Node, 1); + dst->first = dst->last = dst->parent = dst->next = dst->prev = &md_nil_node; + dst->first_tag = dst->last_tag = &md_nil_node; + dst->kind = src->kind; + dst->flags = src->flags; + dst->string = push_str8_copy(arena, src->string); + dst->raw_string = push_str8_copy(arena, src->raw_string); + dst->src_offset = src->src_offset; + dst->parent = dst_parent; + if(dst_parent != &md_nil_node) + { + DLLPushBack_NPZ(&md_nil_node, dst_parent->first, dst_parent->last, dst, next, prev); + } + else + { + dst_root = dst_parent = dst; + } + rec = md_node_rec_depth_first_pre(src, src_root); + if(rec.push_count != 0) + { + dst_parent = dst; + } + else for(U64 idx = 0; idx < rec.pop_count; idx += 1) + { + dst_parent = dst_parent->parent; + } + } + } + return dst_root; +} + +//////////////////////////////// +//~ rjf: Text -> Tokens Functions + +internal MD_TokenizeResult +md_tokenize_from_text(Arena *arena, String8 text) +{ + Temp scratch = scratch_begin(&arena, 1); + MD_TokenChunkList tokens = {0}; + MD_MsgList msgs = {0}; + U8 *byte_first = text.str; + U8 *byte_opl = byte_first + text.size; + U8 *byte = byte_first; + + //- rjf: scan string & produce tokens + for(;byte < byte_opl;) + { + MD_TokenFlags token_flags = 0; + U8 *token_start = 0; + U8 *token_opl = 0; + + //- rjf: whitespace + if(token_flags == 0 && (*byte == ' ' || *byte == '\t' || *byte == '\v' || *byte == '\r')) + { + token_flags = MD_TokenFlag_Whitespace; + token_start = byte; + token_opl = byte; + byte += 1; + for(;byte <= byte_opl; byte += 1) + { + token_opl += 1; + if(byte == byte_opl || (*byte != ' ' && *byte != '\t' && *byte != '\v' && *byte != '\r')) + { + break; + } + } + } + + //- rjf: newlines + if(token_flags == 0 && *byte == '\n') + { + token_flags = MD_TokenFlag_Newline; + token_start = byte; + token_opl = byte+1; + byte += 1; + } + + //- rjf: single-line comments + if(token_flags == 0 && (byte+1 < byte_opl && *byte == '/' && byte[1] == '/')) + { + token_flags = MD_TokenFlag_Comment; + token_start = byte; + token_opl = byte+2; + byte += 2; + B32 escaped = 0; + for(;byte <= byte_opl; byte += 1) + { + token_opl += 1; + if(byte == byte_opl) + { + break; + } + if(escaped) + { + escaped = 0; + } + else + { + if(*byte == '\n') + { + break; + } + else if(*byte == '\\') + { + escaped = 1; + } + } + } + } + + //- rjf: multi-line comments + if(token_flags == 0 && (byte+1 < byte_opl && *byte == '/' && byte[1] == '*')) + { + token_flags = MD_TokenFlag_Comment; + token_start = byte; + token_opl = byte+2; + byte += 2; + for(;byte <= byte_opl; byte += 1) + { + token_opl += 1; + if(byte == byte_opl) + { + token_flags |= MD_TokenFlag_BrokenComment; + break; + } + if(byte+1 < byte_opl && byte[0] == '*' && byte[1] == '/') + { + token_opl += 2; + break; + } + } + } + + //- rjf: identifiers + if(token_flags == 0 && (('A' <= *byte && *byte <= 'Z') || + ('a' <= *byte && *byte <= 'z') || + *byte == '_' || + utf8_class[*byte>>3] >= 2 )) + { + token_flags = MD_TokenFlag_Identifier; + token_start = byte; + token_opl = byte; + byte += 1; + for(;byte <= byte_opl; byte += 1) + { + token_opl += 1; + if(byte == byte_opl || + (!('A' <= *byte && *byte <= 'Z') && + !('a' <= *byte && *byte <= 'z') && + !('0' <= *byte && *byte <= '9') && + *byte != '_' && + utf8_class[*byte>>3] < 2)) + { + break; + } + } + } + + //- rjf: numerics + if(token_flags == 0 && (('0' <= *byte && *byte <= '9') || + (*byte == '.' && byte+1 < byte_opl && '0' <= byte[1] && byte[1] <= '9') || + (*byte == '-' && byte+1 < byte_opl && '0' <= byte[1] && byte[1] <= '9') || + *byte == '_')) + { + token_flags = MD_TokenFlag_Numeric; + token_start = byte; + token_opl = byte; + byte += 1; + for(;byte <= byte_opl; byte += 1) + { + token_opl += 1; + if(byte == byte_opl || + (!('A' <= *byte && *byte <= 'Z') && + !('a' <= *byte && *byte <= 'z') && + !('0' <= *byte && *byte <= '9') && + *byte != '_' && + *byte != '.')) + { + break; + } + } + } + + //- rjf: triplet string literals + if(token_flags == 0 && byte+2 < byte_opl && + ((byte[0] == '"' && byte[1] == '"' && byte[2] == '"') || + (byte[0] == '\''&& byte[1] == '\''&& byte[2] == '\'') || + (byte[0] == '`' && byte[1] == '`' && byte[2] == '`'))) + { + U8 literal_style = byte[0]; + token_flags = MD_TokenFlag_StringLiteral|MD_TokenFlag_StringTriplet; + token_flags |= (literal_style == '\'')*MD_TokenFlag_StringSingleQuote; + token_flags |= (literal_style == '"')*MD_TokenFlag_StringDoubleQuote; + token_flags |= (literal_style == '`')*MD_TokenFlag_StringTick; + token_start = byte; + token_opl = byte+3; + byte += 3; + for(;byte <= byte_opl; byte += 1) + { + if(byte == byte_opl) + { + token_flags |= MD_TokenFlag_BrokenStringLiteral; + token_opl = byte; + break; + } + if(byte+2 < byte_opl && (byte[0] == literal_style && byte[1] == literal_style && byte[2] == literal_style)) + { + byte += 3; + token_opl = byte; + break; + } + } + } + + //- rjf: singlet string literals + if(token_flags == 0 && (byte[0] == '"' || byte[0] == '\'' || byte[0] == '`')) + { + U8 literal_style = byte[0]; + token_flags = MD_TokenFlag_StringLiteral; + token_flags |= (literal_style == '\'')*MD_TokenFlag_StringSingleQuote; + token_flags |= (literal_style == '"')*MD_TokenFlag_StringDoubleQuote; + token_flags |= (literal_style == '`')*MD_TokenFlag_StringTick; + token_start = byte; + token_opl = byte+1; + byte += 1; + B32 escaped = 0; + for(;byte <= byte_opl; byte += 1) + { + if(byte == byte_opl || *byte == '\n') + { + token_opl = byte; + token_flags |= MD_TokenFlag_BrokenStringLiteral; + break; + } + if(!escaped && byte[0] == '\\') + { + escaped = 1; + } + else if(!escaped && byte[0] == literal_style) + { + token_opl = byte+1; + byte += 1; + break; + } + else if(escaped) + { + escaped = 0; + } + } + } + + //- rjf: non-reserved symbols + if(token_flags == 0 && (*byte == '~' || *byte == '!' || *byte == '$' || *byte == '%' || *byte == '^' || + *byte == '&' || *byte == '*' || *byte == '-' || *byte == '=' || *byte == '+' || + *byte == '<' || *byte == '.' || *byte == '>' || *byte == '/' || *byte == '?' || + *byte == '|')) + { + token_flags = MD_TokenFlag_Symbol; + token_start = byte; + token_opl = byte; + byte += 1; + for(;byte <= byte_opl; byte += 1) + { + token_opl += 1; + if(byte == byte_opl || + (*byte != '~' && *byte != '!' && *byte != '$' && *byte != '%' && *byte != '^' && + *byte != '&' && *byte != '*' && *byte != '-' && *byte != '=' && *byte != '+' && + *byte != '<' && *byte != '.' && *byte != '>' && *byte != '/' && *byte != '?' && + *byte != '|')) + { + break; + } + } + } + + //- rjf: reserved symbols + if(token_flags == 0 && (*byte == '{' || *byte == '}' || *byte == '(' || *byte == ')' || + *byte == '[' || *byte == ']' || *byte == '#' || *byte == ',' || + *byte == '\\'|| *byte == ':' || *byte == ';' || *byte == '@')) + { + token_flags = MD_TokenFlag_Reserved; + token_start = byte; + token_opl = byte+1; + byte += 1; + } + + //- rjf: bad characters in all other cases + if(token_flags == 0) + { + token_flags = MD_TokenFlag_BadCharacter; + token_start = byte; + token_opl = byte+1; + byte += 1; + } + + //- rjf; push token if formed + if(token_flags != 0 && token_start != 0 && token_opl > token_start) + { + MD_Token token = {{(U64)(token_start - byte_first), (U64)(token_opl - byte_first)}, token_flags}; + md_token_chunk_list_push(scratch.arena, &tokens, 4096, token); + } + + //- rjf: push errors on unterminated comments + if(token_flags & MD_TokenFlag_BrokenComment) + { + MD_Node *error = md_push_node(arena, MD_NodeKind_ErrorMarker, 0, str8_lit(""), str8_lit(""), token_start - byte_first); + String8 error_string = str8_lit("Unterminated comment."); + md_msg_list_push(arena, &msgs, error, MD_MsgKind_Error, error_string); + } + + //- rjf: push errors on unterminated strings + if(token_flags & MD_TokenFlag_BrokenStringLiteral) + { + MD_Node *error = md_push_node(arena, MD_NodeKind_ErrorMarker, 0, str8_lit(""), str8_lit(""), token_start - byte_first); + String8 error_string = str8_lit("Unterminated string literal."); + md_msg_list_push(arena, &msgs, error, MD_MsgKind_Error, error_string); + } + } + + //- rjf: bake, fill & return + MD_TokenizeResult result = {0}; + { + result.tokens = md_token_array_from_chunk_list(arena, &tokens); + result.msgs = msgs; + } + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: Tokens -> Tree Functions + +internal MD_ParseResult +md_parse_from_text_tokens(Arena *arena, String8 filename, String8 text, MD_TokenArray tokens) +{ + Temp scratch = scratch_begin(&arena, 1); + + //- rjf: set up outputs + MD_MsgList msgs = {0}; + MD_Node *root = md_push_node(arena, MD_NodeKind_File, 0, filename, text, 0); + + //- rjf: set up parse rule stack + typedef enum MD_ParseWorkKind + { + MD_ParseWorkKind_Main, + MD_ParseWorkKind_MainImplicit, + MD_ParseWorkKind_NodeOptionalFollowUp, + MD_ParseWorkKind_NodeChildrenStyleScan, + } + MD_ParseWorkKind; + typedef struct MD_ParseWorkNode MD_ParseWorkNode; + struct MD_ParseWorkNode + { + MD_ParseWorkNode *next; + MD_ParseWorkKind kind; + MD_Node *parent; + MD_Node *first_gathered_tag; + MD_Node *last_gathered_tag; + MD_NodeFlags gathered_node_flags; + S32 counted_newlines; + }; + MD_ParseWorkNode first_work = + { + 0, + MD_ParseWorkKind_Main, + root, + }; + MD_ParseWorkNode broken_work = { 0, MD_ParseWorkKind_Main, root,}; + MD_ParseWorkNode *work_top = &first_work; + MD_ParseWorkNode *work_free = 0; +#define MD_ParseWorkPush(work_kind, work_parent) do\ +{\ +MD_ParseWorkNode *work_node = work_free;\ +if(work_node == 0) {work_node = push_array(scratch.arena, MD_ParseWorkNode, 1);}\ +else { SLLStackPop(work_free); }\ +work_node->kind = (work_kind);\ +work_node->parent = (work_parent);\ +SLLStackPush(work_top, work_node);\ +}while(0) +#define MD_ParseWorkPop() do\ +{\ +SLLStackPop(work_top);\ +if(work_top == 0) {work_top = &broken_work;}\ +}while(0) + + //- rjf: parse + MD_Token *tokens_first = tokens.v; + MD_Token *tokens_opl = tokens_first + tokens.count; + MD_Token *token = tokens_first; + for(;token < tokens_opl;) + { + //- rjf: unpack token + String8 token_string = str8_substr(text, token[0].range); + + //- rjf: whitespace -> always no-op & inc + if(token->flags & MD_TokenFlag_Whitespace) + { + token += 1; + goto end_consume; + } + + //- rjf: comments -> always no-op & inc + if(token->flags & MD_TokenGroup_Comment) + { + token += 1; + goto end_consume; + } + + //- rjf: [node follow up] : following label -> work top parent has children. we need + // to scan for explicit delimiters, else parse an implicitly delimited set of children + if(work_top->kind == MD_ParseWorkKind_NodeOptionalFollowUp && str8_match(token_string, str8_lit(":"), 0)) + { + MD_Node *parent = work_top->parent; + MD_ParseWorkPop(); + MD_ParseWorkPush(MD_ParseWorkKind_NodeChildrenStyleScan, parent); + token += 1; + goto end_consume; + } + + //- rjf: [node follow up] anything but : following label -> node has no children. just + // pop & move on + if(work_top->kind == MD_ParseWorkKind_NodeOptionalFollowUp) + { + MD_ParseWorkPop(); + goto end_consume; + } + + //- rjf: [main] separators -> mark & inc + if(work_top->kind == MD_ParseWorkKind_Main && token->flags & MD_TokenFlag_Reserved && + (str8_match(token_string, str8_lit(","), 0) || + str8_match(token_string, str8_lit(";"), 0))) + { + MD_Node *parent = work_top->parent; + if(!md_node_is_nil(parent->last)) + { + parent->last->flags |= MD_NodeFlag_IsBeforeComma*!!str8_match(token_string, str8_lit(","), 0); + parent->last->flags |= MD_NodeFlag_IsBeforeSemicolon*!!str8_match(token_string, str8_lit(";"), 0); + work_top->gathered_node_flags |= MD_NodeFlag_IsAfterComma*!!str8_match(token_string, str8_lit(","), 0); + work_top->gathered_node_flags |= MD_NodeFlag_IsAfterSemicolon*!!str8_match(token_string, str8_lit(";"), 0); + } + token += 1; + goto end_consume; + } + + //- rjf: [main_implicit] separators -> pop + if(work_top->kind == MD_ParseWorkKind_MainImplicit && token->flags & MD_TokenFlag_Reserved && + (str8_match(token_string, str8_lit(","), 0) || + str8_match(token_string, str8_lit(";"), 0))) + { + MD_ParseWorkPop(); + goto end_consume; + } + + //- rjf: [main, main_implicit] unexpected reserved tokens + if((work_top->kind == MD_ParseWorkKind_Main || work_top->kind == MD_ParseWorkKind_MainImplicit) && + token->flags & MD_TokenFlag_Reserved && + (str8_match(token_string, str8_lit("#"), 0) || + str8_match(token_string, str8_lit("\\"), 0) || + str8_match(token_string, str8_lit(":"), 0))) + { + MD_Node *error = md_push_node(arena, MD_NodeKind_ErrorMarker, 0, token_string, token_string, token->range.min); + String8 error_string = push_str8f(arena, "Unexpected reserved symbol \"%S\".", token_string); + md_msg_list_push(arena, &msgs, error, MD_MsgKind_Error, error_string); + token += 1; + goto end_consume; + } + + //- rjf: [main, main_implicit] tag signifier -> create new tag + if((work_top->kind == MD_ParseWorkKind_Main || work_top->kind == MD_ParseWorkKind_MainImplicit) && + token[0].flags & MD_TokenFlag_Reserved && str8_match(token_string, str8_lit("@"), 0)) + { + if(token+1 >= tokens_opl || + !(token[1].flags & MD_TokenGroup_Label)) + { + MD_Node *error = md_push_node(arena, MD_NodeKind_ErrorMarker, 0, token_string, token_string, token->range.min); + String8 error_string = str8_lit("Tag label expected after @ symbol."); + md_msg_list_push(arena, &msgs, error, MD_MsgKind_Error, error_string); + token += 1; + goto end_consume; + } + else + { + String8 tag_name_raw = str8_substr(text, token[1].range); + String8 tag_name = md_content_string_from_token_flags_str8(token[1].flags, tag_name_raw); + MD_Node *node = md_push_node(arena, MD_NodeKind_Tag, md_node_flags_from_token_flags(token[1].flags), tag_name, tag_name_raw, token[0].range.min); + DLLPushBack_NPZ(&md_nil_node, work_top->first_gathered_tag, work_top->last_gathered_tag, node, next, prev); + if(token+2 < tokens_opl && token[2].flags & MD_TokenFlag_Reserved && str8_match(str8_substr(text, token[2].range), str8_lit("("), 0)) + { + token += 3; + MD_ParseWorkPush(MD_ParseWorkKind_Main, node); + } + else + { + token += 2; + } + goto end_consume; + } + } + + //- rjf: [main, main_implicit] label -> create new main + if((work_top->kind == MD_ParseWorkKind_Main || work_top->kind == MD_ParseWorkKind_MainImplicit) && + token->flags & MD_TokenGroup_Label) + { + String8 node_string_raw = token_string; + String8 node_string = md_content_string_from_token_flags_str8(token->flags, node_string_raw); + MD_NodeFlags flags = md_node_flags_from_token_flags(token->flags)|work_top->gathered_node_flags; + work_top->gathered_node_flags = 0; + MD_Node *node = md_push_node(arena, MD_NodeKind_Main, flags, node_string, node_string_raw, token[0].range.min); + node->first_tag = work_top->first_gathered_tag; + node->last_tag = work_top->last_gathered_tag; + for(MD_Node *tag = work_top->first_gathered_tag; !md_node_is_nil(tag); tag = tag->next) + { + tag->parent = node; + } + work_top->first_gathered_tag = work_top->last_gathered_tag = &md_nil_node; + md_node_push_child(work_top->parent, node); + MD_ParseWorkPush(MD_ParseWorkKind_NodeOptionalFollowUp, node); + token += 1; + goto end_consume; + } + + //- rjf: [main] {s, [s, and (s -> create new main + if(work_top->kind == MD_ParseWorkKind_Main && token->flags & MD_TokenFlag_Reserved && + (str8_match(token_string, str8_lit("{"), 0) || + str8_match(token_string, str8_lit("["), 0) || + str8_match(token_string, str8_lit("("), 0))) + { + MD_NodeFlags flags = md_node_flags_from_token_flags(token->flags)|work_top->gathered_node_flags; + flags |= MD_NodeFlag_HasBraceLeft*!!str8_match(token_string, str8_lit("{"), 0); + flags |= MD_NodeFlag_HasBracketLeft*!!str8_match(token_string, str8_lit("["), 0); + flags |= MD_NodeFlag_HasParenLeft*!!str8_match(token_string, str8_lit("("), 0); + work_top->gathered_node_flags = 0; + MD_Node *node = md_push_node(arena, MD_NodeKind_Main, flags, str8_lit(""), str8_lit(""), token[0].range.min); + node->first_tag = work_top->first_gathered_tag; + node->last_tag = work_top->last_gathered_tag; + for(MD_Node *tag = work_top->first_gathered_tag; !md_node_is_nil(tag); tag = tag->next) + { + tag->parent = node; + } + work_top->first_gathered_tag = work_top->last_gathered_tag = &md_nil_node; + md_node_push_child(work_top->parent, node); + MD_ParseWorkPush(MD_ParseWorkKind_Main, node); + token += 1; + goto end_consume; + } + + //- rjf: [node children style scan] {s, [s, and (s -> explicitly delimited children + if(work_top->kind == MD_ParseWorkKind_NodeChildrenStyleScan && token->flags & MD_TokenFlag_Reserved && + (str8_match(token_string, str8_lit("{"), 0) || + str8_match(token_string, str8_lit("["), 0) || + str8_match(token_string, str8_lit("("), 0))) + { + MD_Node *parent = work_top->parent; + parent->flags |= MD_NodeFlag_HasBraceLeft*!!str8_match(token_string, str8_lit("{"), 0); + parent->flags |= MD_NodeFlag_HasBracketLeft*!!str8_match(token_string, str8_lit("["), 0); + parent->flags |= MD_NodeFlag_HasParenLeft*!!str8_match(token_string, str8_lit("("), 0); + MD_ParseWorkPop(); + MD_ParseWorkPush(MD_ParseWorkKind_Main, parent); + token += 1; + goto end_consume; + } + + //- rjf: [node children style scan] count newlines + if(work_top->kind == MD_ParseWorkKind_NodeChildrenStyleScan && token->flags & MD_TokenFlag_Newline) + { + work_top->counted_newlines += 1; + token += 1; + goto end_consume; + } + + //- rjf: [main_implicit] newline -> pop + if(work_top->kind == MD_ParseWorkKind_MainImplicit && token->flags & MD_TokenFlag_Newline) + { + MD_ParseWorkPop(); + token += 1; + goto end_consume; + } + + //- rjf: [all but main_implicit] newline -> no-op & inc + if(work_top->kind != MD_ParseWorkKind_MainImplicit && token->flags & MD_TokenFlag_Newline) + { + token += 1; + goto end_consume; + } + + //- rjf: [node children style scan] anything causing implicit set -> <2 newlines, all good, + // >=2 newlines, houston we have a problem + if(work_top->kind == MD_ParseWorkKind_NodeChildrenStyleScan) + { + if(work_top->counted_newlines >= 2) + { + MD_Node *node = work_top->parent; + MD_Node *error = md_push_node(arena, MD_NodeKind_ErrorMarker, 0, token_string, token_string, token->range.min); + String8 error_string = push_str8f(arena, "More than two newlines following \"%S\", which has implicitly-delimited children, resulting in an empty list of children.", node->string); + md_msg_list_push(arena, &msgs, error, MD_MsgKind_Warning, error_string); + MD_ParseWorkPop(); + } + else + { + MD_Node *parent = work_top->parent; + MD_ParseWorkPop(); + MD_ParseWorkPush(MD_ParseWorkKind_MainImplicit, parent); + } + goto end_consume; + } + + //- rjf: [main] }s, ]s, and )s -> pop + if(work_top->kind == MD_ParseWorkKind_Main && token->flags & MD_TokenFlag_Reserved && + (str8_match(token_string, str8_lit("}"), 0) || + str8_match(token_string, str8_lit("]"), 0) || + str8_match(token_string, str8_lit(")"), 0))) + { + MD_Node *parent = work_top->parent; + parent->flags |= MD_NodeFlag_HasBraceRight*!!str8_match(token_string, str8_lit("}"), 0); + parent->flags |= MD_NodeFlag_HasBracketRight*!!str8_match(token_string, str8_lit("]"), 0); + parent->flags |= MD_NodeFlag_HasParenRight*!!str8_match(token_string, str8_lit(")"), 0); + MD_ParseWorkPop(); + token += 1; + goto end_consume; + } + + //- rjf: [main implicit] }s, ]s, and )s -> pop without advancing + if(work_top->kind == MD_ParseWorkKind_MainImplicit && token->flags & MD_TokenFlag_Reserved && + (str8_match(token_string, str8_lit("}"), 0) || + str8_match(token_string, str8_lit("]"), 0) || + str8_match(token_string, str8_lit(")"), 0))) + { + MD_ParseWorkPop(); + goto end_consume; + } + + //- rjf: no consumption -> unexpected token! we don't know what to do with this. + { + MD_Node *error = md_push_node(arena, MD_NodeKind_ErrorMarker, 0, token_string, token_string, token->range.min); + String8 error_string = push_str8f(arena, "Unexpected \"%S\" token.", token_string); + md_msg_list_push(arena, &msgs, error, MD_MsgKind_Error, error_string); + token += 1; + } + + end_consume:; + } + + //- rjf: fill & return + MD_ParseResult result = {0}; + result.root = root; + result.msgs = msgs; + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: Bundled Text -> Tree Functions + +internal MD_ParseResult +md_parse_from_text(Arena *arena, String8 filename, String8 text) +{ + Temp scratch = scratch_begin(&arena, 1); + MD_TokenizeResult tokenize = md_tokenize_from_text(scratch.arena, text); + MD_ParseResult parse = md_parse_from_text_tokens(arena, filename, text, tokenize.tokens); + scratch_end(scratch); + return parse; +} + +//////////////////////////////// +//~ rjf: Tree -> Text Functions + +internal String8List +md_debug_string_list_from_tree(Arena *arena, MD_Node *root) +{ + String8List strings = {0}; + { + char *indentation = " "; + S32 depth = 0; + for(MD_Node *node = root, *next = &md_nil_node; !md_node_is_nil(node); node = next) + { + // rjf: get next recursion + MD_NodeRec rec = md_node_rec_depth_first_pre(node, root); + next = rec.next; + + // rjf: extract node info + String8 kind_string = str8_lit("Unknown"); + switch(node->kind) + { + default:{}break; + case MD_NodeKind_File: {kind_string = str8_lit("File"); }break; + case MD_NodeKind_ErrorMarker:{kind_string = str8_lit("ErrorMarker");}break; + case MD_NodeKind_Main: {kind_string = str8_lit("Main"); }break; + case MD_NodeKind_Tag: {kind_string = str8_lit("Tag"); }break; + case MD_NodeKind_List: {kind_string = str8_lit("List"); }break; + case MD_NodeKind_Reference: {kind_string = str8_lit("Reference"); }break; + } + + // rjf: push node line + str8_list_pushf(arena, &strings, "%.*s\"%S\" : %S", depth, indentation, node->string, kind_string); + + // rjf: children -> open brace + if(rec.push_count != 0) + { + str8_list_pushf(arena, &strings, "%.*s{", depth, indentation); + } + + // rjf: descend + depth += rec.push_count; + + // rjf: popping -> close braces + for(S32 pop_idx = 0; pop_idx < rec.pop_count; pop_idx += 1) + { + str8_list_pushf(arena, &strings, "%.*s}", depth-1-pop_idx, indentation); + } + + // rjf: ascend + depth -= rec.pop_count; + } + } + return strings; +} diff --git a/mdesk/mdesk.h b/mdesk/mdesk.h new file mode 100644 index 0000000..cda7cb1 --- /dev/null +++ b/mdesk/mdesk.h @@ -0,0 +1,325 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef MDESK_H +#define MDESK_H + +//////////////////////////////// +//~ rjf: Messages + +typedef enum MD_MsgKind +{ + MD_MsgKind_Null, + MD_MsgKind_Note, + MD_MsgKind_Warning, + MD_MsgKind_Error, + MD_MsgKind_FatalError, +} +MD_MsgKind; + +typedef struct MD_Msg MD_Msg; +struct MD_Msg +{ + MD_Msg *next; + struct MD_Node *node; + MD_MsgKind kind; + String8 string; +}; + +typedef struct MD_MsgList MD_MsgList; +struct MD_MsgList +{ + MD_Msg *first; + MD_Msg *last; + U64 count; + MD_MsgKind worst_message_kind; +}; + +//////////////////////////////// +//~ rjf: Token Types + +typedef U32 MD_TokenFlags; +enum +{ + // rjf: base kind info + MD_TokenFlag_Identifier = (1<<0), + MD_TokenFlag_Numeric = (1<<1), + MD_TokenFlag_StringLiteral = (1<<2), + MD_TokenFlag_Symbol = (1<<3), + MD_TokenFlag_Reserved = (1<<4), + MD_TokenFlag_Comment = (1<<5), + MD_TokenFlag_Whitespace = (1<<6), + MD_TokenFlag_Newline = (1<<7), + + // rjf: decoration info + MD_TokenFlag_StringSingleQuote = (1<<8), + MD_TokenFlag_StringDoubleQuote = (1<<9), + MD_TokenFlag_StringTick = (1<<10), + MD_TokenFlag_StringTriplet = (1<<11), + + // rjf: error info + MD_TokenFlag_BrokenComment = (1<<12), + MD_TokenFlag_BrokenStringLiteral = (1<<13), + MD_TokenFlag_BadCharacter = (1<<14), +}; + +typedef U32 MD_TokenGroups; +enum +{ + MD_TokenGroup_Comment = MD_TokenFlag_Comment, + MD_TokenGroup_Whitespace = (MD_TokenFlag_Whitespace| + MD_TokenFlag_Newline), + MD_TokenGroup_Irregular = (MD_TokenGroup_Comment| + MD_TokenGroup_Whitespace), + MD_TokenGroup_Regular = ~MD_TokenGroup_Irregular, + MD_TokenGroup_Label = (MD_TokenFlag_Identifier| + MD_TokenFlag_Numeric| + MD_TokenFlag_StringLiteral| + MD_TokenFlag_Symbol), + MD_TokenGroup_Error = (MD_TokenFlag_BrokenComment| + MD_TokenFlag_BrokenStringLiteral| + MD_TokenFlag_BadCharacter), +}; + +typedef struct MD_Token MD_Token; +struct MD_Token +{ + Rng1U64 range; + MD_TokenFlags flags; +}; + +typedef struct MD_TokenChunkNode MD_TokenChunkNode; +struct MD_TokenChunkNode +{ + MD_TokenChunkNode *next; + MD_Token *v; + U64 count; + U64 cap; +}; + +typedef struct MD_TokenChunkList MD_TokenChunkList; +struct MD_TokenChunkList +{ + MD_TokenChunkNode *first; + MD_TokenChunkNode *last; + U64 chunk_count; + U64 total_token_count; +}; + +typedef struct MD_TokenArray MD_TokenArray; +struct MD_TokenArray +{ + MD_Token *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Node Types + +typedef enum MD_NodeKind +{ + MD_NodeKind_Nil, + MD_NodeKind_File, + MD_NodeKind_ErrorMarker, + MD_NodeKind_Main, + MD_NodeKind_Tag, + MD_NodeKind_List, + MD_NodeKind_Reference, + MD_NodeKind_COUNT +} +MD_NodeKind; + +typedef U32 MD_NodeFlags; +enum +{ + MD_NodeFlag_MaskSetDelimiters = (0x3F<<0), + MD_NodeFlag_HasParenLeft = (1<<0), + MD_NodeFlag_HasParenRight = (1<<1), + MD_NodeFlag_HasBracketLeft = (1<<2), + MD_NodeFlag_HasBracketRight = (1<<3), + MD_NodeFlag_HasBraceLeft = (1<<4), + MD_NodeFlag_HasBraceRight = (1<<5), + + MD_NodeFlag_MaskSeparators = (0xF<<6), + MD_NodeFlag_IsBeforeSemicolon = (1<<6), + MD_NodeFlag_IsAfterSemicolon = (1<<7), + MD_NodeFlag_IsBeforeComma = (1<<8), + MD_NodeFlag_IsAfterComma = (1<<9), + + MD_NodeFlag_MaskStringDelimiters = (0xF<<10), + MD_NodeFlag_StringSingleQuote = (1<<10), + MD_NodeFlag_StringDoubleQuote = (1<<11), + MD_NodeFlag_StringTick = (1<<12), + MD_NodeFlag_StringTriplet = (1<<13), + + MD_NodeFlag_MaskLabelKind = (0xF<<14), + MD_NodeFlag_Numeric = (1<<14), + MD_NodeFlag_Identifier = (1<<15), + MD_NodeFlag_StringLiteral = (1<<16), + MD_NodeFlag_Symbol = (1<<17), +}; +#define MD_NodeFlag_AfterFromBefore(f) ((f) << 1) + +typedef struct MD_Node MD_Node; +struct MD_Node +{ + // rjf: tree links + MD_Node *next; + MD_Node *prev; + MD_Node *parent; + MD_Node *first; + MD_Node *last; + + // rjf: tag links + MD_Node *first_tag; + MD_Node *last_tag; + + // rjf: node info + MD_NodeKind kind; + MD_NodeFlags flags; + String8 string; + String8 raw_string; + + // rjf: source code info + U64 src_offset; + + // rjf: user-controlled generation number + // + // (unused by mdesk layer, but can be used by usage code to use MD_Node trees + // in a "retained mode" way, where stable generational handles can be formed + // to nodes) + U64 user_gen; + + // rjf: extra padding to 128 bytes + U64 _unused_[2]; +}; + +typedef struct MD_NodeRec MD_NodeRec; +struct MD_NodeRec +{ + MD_Node *next; + S32 push_count; + S32 pop_count; +}; + +//////////////////////////////// +//~ rjf: Text -> Tokens Types + +typedef struct MD_TokenizeResult MD_TokenizeResult; +struct MD_TokenizeResult +{ + MD_TokenArray tokens; + MD_MsgList msgs; +}; + +//////////////////////////////// +//~ rjf: Tokens -> Tree Types + +typedef struct MD_ParseResult MD_ParseResult; +struct MD_ParseResult +{ + MD_Node *root; + MD_MsgList msgs; +}; + +//////////////////////////////// +//~ rjf: Globals + +global read_only MD_Node md_nil_node = +{ + &md_nil_node, + &md_nil_node, + &md_nil_node, + &md_nil_node, + &md_nil_node, + &md_nil_node, + &md_nil_node, +}; + +//////////////////////////////// +//~ rjf: Message Type Functions + +internal void md_msg_list_push(Arena *arena, MD_MsgList *msgs, MD_Node *node, MD_MsgKind kind, String8 string); +internal void md_msg_list_pushf(Arena *arena, MD_MsgList *msgs, MD_Node *node, MD_MsgKind kind, char *fmt, ...); +internal void md_msg_list_concat_in_place(MD_MsgList *dst, MD_MsgList *to_push); + +//////////////////////////////// +//~ rjf: Token Type Functions + +internal MD_Token md_token_make(Rng1U64 range, MD_TokenFlags flags); +internal B32 md_token_match(MD_Token a, MD_Token b); +internal String8List md_string_list_from_token_flags(Arena *arena, MD_TokenFlags flags); +internal void md_token_chunk_list_push(Arena *arena, MD_TokenChunkList *list, U64 cap, MD_Token token); +internal MD_TokenArray md_token_array_from_chunk_list(Arena *arena, MD_TokenChunkList *chunks); +internal String8 md_content_string_from_token_flags_str8(MD_TokenFlags flags, String8 string); + +//////////////////////////////// +//~ rjf: Node Type Functions + +//- rjf: flag conversions +internal MD_NodeFlags md_node_flags_from_token_flags(MD_TokenFlags flags); + +//- rjf: nil +internal B32 md_node_is_nil(MD_Node *node); + +//- rjf: iteration +#define MD_EachNode(it, first) (MD_Node *it = first; !md_node_is_nil(it); it = it->next) +internal MD_NodeRec md_node_rec_depth_first(MD_Node *node, MD_Node *subtree_root, U64 child_off, U64 sib_off); +#define md_node_rec_depth_first_pre(node, subtree_root) md_node_rec_depth_first((node), (subtree_root), OffsetOf(MD_Node, first), OffsetOf(MD_Node, next)) +#define md_node_rec_depth_first_pre_rev(node, subtree_root) md_node_rec_depth_first((node), (subtree_root), OffsetOf(MD_Node, last), OffsetOf(MD_Node, prev)) + +//- rjf: tree building +internal MD_Node *md_push_node(Arena *arena, MD_NodeKind kind, MD_NodeFlags flags, String8 string, String8 raw_string, U64 src_offset); +internal void md_node_insert_child(MD_Node *parent, MD_Node *prev_child, MD_Node *node); +internal void md_node_insert_tag(MD_Node *parent, MD_Node *prev_child, MD_Node *node); +internal void md_node_push_child(MD_Node *parent, MD_Node *node); +internal void md_node_push_tag(MD_Node *parent, MD_Node *node); +internal void md_unhook(MD_Node *node); + +//- rjf: tree introspection +internal MD_Node * md_node_from_chain_string(MD_Node *first, MD_Node *opl, String8 string, StringMatchFlags flags); +internal MD_Node * md_node_from_chain_index(MD_Node *first, MD_Node *opl, U64 index); +internal MD_Node * md_node_from_chain_flags(MD_Node *first, MD_Node *opl, MD_NodeFlags flags); +internal U64 md_index_from_node(MD_Node *node); +internal MD_Node * md_root_from_node(MD_Node *node); +internal MD_Node * md_child_from_string(MD_Node *node, String8 child_string, StringMatchFlags flags); +internal MD_Node * md_tag_from_string(MD_Node *node, String8 tag_string, StringMatchFlags flags); +internal MD_Node * md_child_from_index(MD_Node *node, U64 index); +internal MD_Node * md_tag_from_index(MD_Node *node, U64 index); +internal MD_Node * md_tag_arg_from_index(MD_Node *node, String8 tag_string, StringMatchFlags flags, U64 index); +internal MD_Node * md_tag_arg_from_string(MD_Node *node, String8 tag_string, StringMatchFlags tag_str_flags, String8 arg_string, StringMatchFlags arg_str_flags); +internal B32 md_node_has_child(MD_Node *node, String8 string, StringMatchFlags flags); +internal B32 md_node_has_tag(MD_Node *node, String8 string, StringMatchFlags flags); +internal U64 md_child_count_from_node(MD_Node *node); +internal U64 md_tag_count_from_node(MD_Node *node); +internal String8 md_string_from_children(Arena *arena, MD_Node *root); + +//- rjf: tree comparison +internal B32 md_tree_match(MD_Node *a, MD_Node *b, StringMatchFlags flags); +internal B32 md_node_match(MD_Node *a, MD_Node *b, StringMatchFlags flags); + +//- rjf: tree duplication +internal MD_Node *md_tree_copy(Arena *arena, MD_Node *src_root); + +//////////////////////////////// +//~ rjf: Text -> Tokens Functions + +internal MD_TokenizeResult md_tokenize_from_text(Arena *arena, String8 text); + +//////////////////////////////// +//~ rjf: Tokens -> Tree Functions + +internal MD_ParseResult md_parse_from_text_tokens(Arena *arena, String8 filename, String8 text, MD_TokenArray tokens); + +//////////////////////////////// +//~ rjf: Bundled Text -> Tree Functions + +internal MD_ParseResult md_parse_from_text(Arena *arena, String8 filename, String8 text); +#define md_tree_from_string(arena, string) (md_parse_from_text((arena), str8_zero(), (string)).root) + +//////////////////////////////// +//~ rjf: Tree -> Text Functions + +internal String8List md_debug_string_list_from_tree(Arena *arena, MD_Node *root); + +#endif // MDESK_H diff --git a/metagen/metagen.c b/metagen/metagen.c new file mode 100644 index 0000000..8529327 --- /dev/null +++ b/metagen/metagen.c @@ -0,0 +1,1136 @@ +// 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; +} diff --git a/metagen/metagen.h b/metagen/metagen.h new file mode 100644 index 0000000..339397c --- /dev/null +++ b/metagen/metagen.h @@ -0,0 +1,329 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef METAGEN_H +#define METAGEN_H + +//////////////////////////////// +//~ rjf: Message Type + +typedef struct MG_Msg MG_Msg; +struct MG_Msg +{ + String8 location; + String8 kind; + String8 msg; +}; + +typedef struct MG_MsgNode MG_MsgNode; +struct MG_MsgNode +{ + MG_MsgNode *next; + MG_Msg v; +}; + +typedef struct MG_MsgList MG_MsgList; +struct MG_MsgList +{ + MG_MsgNode *first; + MG_MsgNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Parse Artifact Types + +typedef struct MG_FileParse MG_FileParse; +struct MG_FileParse +{ + MD_Node *root; +}; + +typedef struct MG_FileParseNode MG_FileParseNode; +struct MG_FileParseNode +{ + MG_FileParseNode *next; + MG_FileParse v; +}; + +typedef struct MG_FileParseList MG_FileParseList; +struct MG_FileParseList +{ + MG_FileParseNode *first; + MG_FileParseNode *last; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Map Type + +typedef struct MG_MapNode MG_MapNode; +struct MG_MapNode +{ + MG_MapNode *next; + String8 key; + void *val; +}; + +typedef struct MG_MapSlot MG_MapSlot; +struct MG_MapSlot +{ + MG_MapNode *first; + MG_MapNode *last; +}; + +typedef struct MG_Map MG_Map; +struct MG_Map +{ + MG_MapSlot *slots; + U64 slots_count; +}; + +//////////////////////////////// +//~ rjf: String Expression Types + +typedef enum MG_StrExprOpKind +{ + MG_StrExprOpKind_Null, + MG_StrExprOpKind_Prefix, + MG_StrExprOpKind_Postfix, + MG_StrExprOpKind_Binary, + MG_StrExprOpKind_COUNT +} +MG_StrExprOpKind; + +typedef enum MG_StrExprOp +{ + MG_StrExprOp_Null, + +#define MG_StrExprOp_FirstString MG_StrExprOp_Dot + MG_StrExprOp_Dot, + MG_StrExprOp_ExpandIfTrue, + MG_StrExprOp_Concat, + MG_StrExprOp_BumpToColumn, +#define MG_StrExprOp_LastString MG_StrExprOp_BumpToColumn + +#define MG_StrExprOp_FirstNumeric MG_StrExprOp_Add + MG_StrExprOp_Add, + MG_StrExprOp_Subtract, + MG_StrExprOp_Multiply, + MG_StrExprOp_Divide, + MG_StrExprOp_Modulo, + MG_StrExprOp_LeftShift, + MG_StrExprOp_RightShift, + MG_StrExprOp_BitwiseAnd, + MG_StrExprOp_BitwiseOr, + MG_StrExprOp_BitwiseXor, + MG_StrExprOp_BitwiseNegate, + MG_StrExprOp_BooleanAnd, + MG_StrExprOp_BooleanOr, + MG_StrExprOp_BooleanNot, + MG_StrExprOp_Equals, + MG_StrExprOp_DoesNotEqual, +#define MG_StrExprOp_LastNumeric MG_StrExprOp_DoesNotEqual + + MG_StrExprOp_COUNT, +} +MG_StrExprOp; + +typedef struct MG_StrExpr MG_StrExpr; +struct MG_StrExpr +{ + MG_StrExpr *parent; + MG_StrExpr *left; + MG_StrExpr *right; + MG_StrExprOp op; + MD_Node *node; +}; + +typedef struct MG_StrExprParseResult MG_StrExprParseResult; +struct MG_StrExprParseResult +{ + MG_StrExpr *root; + MD_MsgList msgs; + MD_Node *next_node; +}; + +//////////////////////////////// +//~ rjf: Table Generation Types + +typedef struct MG_NodeArray MG_NodeArray; +struct MG_NodeArray +{ + MD_Node **v; + U64 count; +}; + +typedef struct MG_NodeGrid MG_NodeGrid; +struct MG_NodeGrid +{ + U64 x_stride; + U64 y_stride; + MG_NodeArray cells; + MG_NodeArray row_parents; +}; + +typedef enum MG_ColumnKind +{ + MG_ColumnKind_DirectCell, + MG_ColumnKind_CheckForTag, + MG_ColumnKind_TagChild, + MG_ColumnKind_COUNT +} +MG_ColumnKind; + +typedef struct MG_ColumnDesc MG_ColumnDesc; +struct MG_ColumnDesc +{ + String8 name; + MG_ColumnKind kind; + String8 tag_name; +}; + +typedef struct MG_ColumnDescArray MG_ColumnDescArray; +struct MG_ColumnDescArray +{ + U64 count; + MG_ColumnDesc *v; +}; + +typedef struct MG_TableExpandTask MG_TableExpandTask; +struct MG_TableExpandTask +{ + MG_TableExpandTask *next; + String8 expansion_label; + MG_NodeGrid *grid; + MG_ColumnDescArray column_descs; + U64 count; + U64 idx; +}; + +typedef struct MG_TableExpandInfo MG_TableExpandInfo; +struct MG_TableExpandInfo +{ + MG_TableExpandTask *first_expand_task; + String8 missing_value_fallback; +}; + +//////////////////////////////// +//~ rjf: Main Output Path Types + +typedef struct MG_Layer MG_Layer; +struct MG_Layer +{ + String8 key; + B32 is_library; + String8 gen_folder_name; + String8 h_name_override; + String8 c_name_override; + String8List enums; + String8List structs; + String8List h_functions; + String8List h_tables; + String8List h_catchall; + String8List h_header; + String8List h_footer; + String8List c_functions; + String8List c_tables; + String8List c_catchall; + String8List c_header; + String8List c_footer; +}; + +typedef struct MG_LayerNode MG_LayerNode; +struct MG_LayerNode +{ + MG_LayerNode *next; + MG_Layer v; +}; + +typedef struct MG_LayerSlot MG_LayerSlot; +struct MG_LayerSlot +{ + MG_LayerNode *first; + MG_LayerNode *last; +}; + +typedef struct MG_State MG_State; +struct MG_State +{ + U64 slots_count; + MG_LayerSlot *slots; +}; + +//////////////////////////////// +//~ rjf: Globals + +global Arena *mg_arena = 0; +global MG_State *mg_state = 0; +read_only global MG_StrExpr mg_str_expr_nil = {&mg_str_expr_nil, &mg_str_expr_nil, &mg_str_expr_nil}; + +//////////////////////////////// +//~ rjf: Basic Helpers + +internal U64 mg_hash_from_string(String8 string); +internal TxtPt mg_txt_pt_from_string_off(String8 string, U64 off); + +//////////////////////////////// +//~ rjf: Message Lists + +internal void mg_msg_list_push(Arena *arena, MG_MsgList *msgs, MG_Msg *msg); + +//////////////////////////////// +//~ rjf: String Escaping + +internal String8 mg_escaped_from_str8(Arena *arena, String8 string); + +//////////////////////////////// +//~ 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); + +//////////////////////////////// +//~ rjf: C-String-Izing + +internal String8 mg_c_string_literal_from_multiline_string(String8 string); +internal String8 mg_c_array_literal_contents_from_data(String8 data); + +//////////////////////////////// +//~ rjf: Map Functions + +internal MG_Map mg_push_map(Arena *arena, U64 slot_count); +internal void *mg_map_ptr_from_string(MG_Map *map, String8 string); +internal void mg_map_insert_ptr(Arena *arena, MG_Map *map, String8 string, void *val); + +//////////////////////////////// +//~ rjf: String Expression Parsing + +internal MG_StrExpr *mg_push_str_expr(Arena *arena, MG_StrExprOp op, MD_Node *node); +internal MG_StrExprParseResult mg_str_expr_parse_from_first_opl__min_prec(Arena *arena, MD_Node *first, MD_Node *opl, S8 min_prec); +internal MG_StrExprParseResult mg_str_expr_parse_from_first_opl(Arena *arena, MD_Node *first, MD_Node *opl); +internal MG_StrExprParseResult mg_str_expr_parse_from_root(Arena *arena, MD_Node *root); + +//////////////////////////////// +//~ rjf: Table Generation Functions + +internal MG_NodeArray mg_node_array_make(Arena *arena, U64 count); +internal MG_NodeArray mg_child_array_from_node(Arena *arena, MD_Node *node); +internal MG_NodeGrid mg_node_grid_make_from_node(Arena *arena, MD_Node *root); +internal MG_NodeArray mg_row_from_index(MG_NodeGrid grid, U64 index); +internal MG_NodeArray mg_column_from_index(Arena *arena, MG_NodeGrid grid, U64 index); +internal MD_Node *mg_node_from_grid_xy(MG_NodeGrid grid, U64 x, U64 y); + +internal MG_ColumnDescArray mg_column_desc_array_make(Arena *arena, U64 count, MG_ColumnDesc *descs); +internal MG_ColumnDescArray mg_column_desc_array_from_tag(Arena *arena, MD_Node *tag); +internal U64 mg_column_index_from_name(MG_ColumnDescArray descs, String8 name); +internal String8 mg_string_from_row_desc_idx(MD_Node *row_parent, MG_ColumnDescArray descs, U64 idx); + +internal S64 mg_eval_table_expand_expr__numeric(MG_StrExpr *expr, MG_TableExpandInfo *info); +internal void mg_eval_table_expand_expr__string(Arena *arena, MG_StrExpr *expr, MG_TableExpandInfo *info, String8List *out); +internal void mg_loop_table_column_expansion(Arena *arena, String8 strexpr, MG_TableExpandInfo *info, MG_TableExpandTask *task, String8List *out); +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); + +//////////////////////////////// +//~ rjf: Layer Lookup Functions + +internal String8 mg_layer_key_from_path(String8 path); +internal MG_Layer *mg_layer_from_key(String8 key); + +#endif //METAGEN_H diff --git a/metagen/metagen_base/metagen_base_arena.c b/metagen/metagen_base/metagen_base_arena.c new file mode 100644 index 0000000..c7f3da7 --- /dev/null +++ b/metagen/metagen_base/metagen_base_arena.c @@ -0,0 +1,195 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Arena Functions + +//- rjf: arena creation/destruction + +internal Arena * +arena_alloc_(ArenaParams *params) +{ + // rjf: round up reserve/commit sizes + U64 reserve_size = params->reserve_size; + U64 commit_size = params->commit_size; + if(params->flags & ArenaFlag_LargePages) + { + reserve_size = AlignPow2(reserve_size, os_get_system_info()->large_page_size); + commit_size = AlignPow2(commit_size, os_get_system_info()->large_page_size); + } + else + { + reserve_size = AlignPow2(reserve_size, os_get_system_info()->page_size); + commit_size = AlignPow2(commit_size, os_get_system_info()->page_size); + } + + // rjf: reserve/commit initial block + void *base = params->optional_backing_buffer; + if(base == 0) + { + if(params->flags & ArenaFlag_LargePages) + { + base = os_reserve_large(reserve_size); + os_commit_large(base, commit_size); + } + else + { + base = os_reserve(reserve_size); + os_commit(base, commit_size); + } + } + + // rjf: panic on arena creation failure +#if OS_FEATURE_GRAPHICAL + if(Unlikely(base == 0)) + { + os_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); + os_abort(1); + } +#endif + + // rjf: extract arena header & fill + Arena *arena = (Arena *)base; + arena->current = arena; + arena->flags = params->flags; + arena->cmt_size = (U32)params->commit_size; + arena->res_size = params->reserve_size; + arena->base_pos = 0; + arena->pos = ARENA_HEADER_SIZE; + arena->cmt = commit_size; + arena->res = reserve_size; + AsanPoisonMemoryRegion(base, commit_size); + AsanUnpoisonMemoryRegion(base, ARENA_HEADER_SIZE); + return arena; +} + +internal void +arena_release(Arena *arena) +{ + for(Arena *n = arena->current, *prev = 0; n != 0; n = prev) + { + prev = n->prev; + os_release(n, n->res); + } +} + +//- rjf: arena push/pop core functions + +internal void * +arena_push(Arena *arena, U64 size, U64 align) +{ + Arena *current = arena->current; + U64 pos_pre = AlignPow2(current->pos, align); + U64 pos_pst = pos_pre + size; + + // rjf: chain, if needed + if(current->res < pos_pst && !(arena->flags & ArenaFlag_NoChain)) + { + U64 res_size = current->res_size; + U64 cmt_size = current->cmt_size; + if(size + ARENA_HEADER_SIZE > res_size) + { + res_size = size + ARENA_HEADER_SIZE; + cmt_size = size + ARENA_HEADER_SIZE; + } + Arena *new_block = arena_alloc(.reserve_size = res_size, + .commit_size = cmt_size, + .flags = current->flags); + new_block->base_pos = current->base_pos + current->res; + SLLStackPush_N(arena->current, new_block, prev); + current = new_block; + pos_pre = AlignPow2(current->pos, align); + pos_pst = pos_pre + size; + } + + // rjf: commit new pages, if needed + if(current->cmt < pos_pst && !(current->flags & ArenaFlag_LargePages)) + { + U64 cmt_pst_aligned = AlignPow2(pos_pst, current->cmt_size); + U64 cmt_pst_clamped = ClampTop(cmt_pst_aligned, current->res); + U64 cmt_size = cmt_pst_clamped - current->cmt; + os_commit((U8 *)current + current->cmt, cmt_size); + current->cmt = cmt_pst_clamped; + } + + // rjf: push onto current block + void *result = 0; + if(current->cmt >= pos_pst) + { + result = (U8 *)current+pos_pre; + current->pos = pos_pst; + AsanUnpoisonMemoryRegion(result, size); + } + + // rjf: panic on failure +#if OS_FEATURE_GRAPHICAL + if(Unlikely(result == 0)) + { + os_graphical_message(1, str8_lit("Fatal Allocation Failure"), str8_lit("Unexpected memory allocation failure.")); + os_abort(1); + } +#endif + + return result; +} + +internal U64 +arena_pos(Arena *arena) +{ + Arena *current = arena->current; + U64 pos = current->base_pos + current->pos; + return pos; +} + +internal void +arena_pop_to(Arena *arena, U64 pos) +{ + U64 big_pos = ClampBot(ARENA_HEADER_SIZE, pos); + Arena *current = arena->current; + for(Arena *prev = 0; current->base_pos >= big_pos; current = prev) + { + prev = current->prev; + os_release(current, current->res); + } + arena->current = current; + U64 new_pos = big_pos - current->base_pos; + AssertAlways(new_pos <= current->pos); + AsanPoisonMemoryRegion((U8*)current + new_pos, (current->pos - new_pos)); + current->pos = new_pos; +} + +//- rjf: arena push/pop helpers + +internal void +arena_clear(Arena *arena) +{ + arena_pop_to(arena, 0); +} + +internal void +arena_pop(Arena *arena, U64 amt) +{ + U64 pos_old = arena_pos(arena); + U64 pos_new = pos_old; + if(amt < pos_old) + { + pos_new = pos_old - amt; + } + arena_pop_to(arena, pos_new); +} + +//- rjf: temporary arena scopes + +internal Temp +temp_begin(Arena *arena) +{ + U64 pos = arena_pos(arena); + Temp temp = {arena, pos}; + return temp; +} + +internal void +temp_end(Temp temp) +{ + arena_pop_to(temp.arena, temp.pos); +} diff --git a/metagen/metagen_base/metagen_base_arena.h b/metagen/metagen_base/metagen_base_arena.h new file mode 100644 index 0000000..7b212b8 --- /dev/null +++ b/metagen/metagen_base/metagen_base_arena.h @@ -0,0 +1,80 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_ARENA_H +#define BASE_ARENA_H + +//////////////////////////////// +//~ rjf: Constants + +#define ARENA_HEADER_SIZE 64 + +//////////////////////////////// +//~ rjf: Types + +typedef U32 ArenaFlags; +enum +{ + ArenaFlag_NoChain = (1<<0), + ArenaFlag_LargePages = (1<<1), +}; + +typedef struct ArenaParams ArenaParams; +struct ArenaParams +{ + ArenaFlags flags; + U64 reserve_size; + U64 commit_size; + void *optional_backing_buffer; +}; + +typedef struct Arena Arena; +struct Arena +{ + Arena *prev; // previous arena in chain + Arena *current; // current arena in chain + ArenaFlags flags; + U32 cmt_size; + U64 res_size; + U64 base_pos; + U64 pos; + U64 cmt; + U64 res; +}; +StaticAssert(sizeof(Arena) <= ARENA_HEADER_SIZE, arena_header_size_check); + +typedef struct Temp Temp; +struct Temp +{ + Arena *arena; + U64 pos; +}; + +//////////////////////////////// +//~ rjf: Arena Functions + +//- rjf: arena creation/destruction +internal Arena *arena_alloc_(ArenaParams *params); +#define arena_alloc(...) arena_alloc_(&(ArenaParams){.reserve_size = MB(64), .commit_size = KB(64), __VA_ARGS__}) +internal void arena_release(Arena *arena); + +//- rjf: arena push/pop/pos core functions +internal void *arena_push(Arena *arena, U64 size, U64 align); +internal U64 arena_pos(Arena *arena); +internal void arena_pop_to(Arena *arena, U64 pos); + +//- rjf: arena push/pop helpers +internal void arena_clear(Arena *arena); +internal void arena_pop(Arena *arena, U64 amt); + +//- rjf: temporary arena scopes +internal Temp temp_begin(Arena *arena); +internal void temp_end(Temp temp); + +//- rjf: push helper macros +#define push_array_no_zero_aligned(a, T, c, align) (T *)arena_push((a), sizeof(T)*(c), (align)) +#define push_array_aligned(a, T, c, align) (T *)MemoryZero(push_array_no_zero_aligned(a, T, c, align), sizeof(T)*(c)) +#define push_array_no_zero(a, T, c) push_array_no_zero_aligned(a, T, c, Max(8, AlignOf(T))) +#define push_array(a, T, c) push_array_aligned(a, T, c, Max(8, AlignOf(T))) + +#endif // BASE_ARENA_H diff --git a/metagen/metagen_base/metagen_base_command_line.c b/metagen/metagen_base/metagen_base_command_line.c new file mode 100644 index 0000000..2c76158 --- /dev/null +++ b/metagen/metagen_base/metagen_base_command_line.c @@ -0,0 +1,232 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ NOTE(rjf): Command Line Option Parsing + +internal U64 +cmd_line_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 CmdLineOpt ** +cmd_line_slot_from_string(CmdLine *cmd_line, String8 string) +{ + CmdLineOpt **slot = 0; + if(cmd_line->option_table_size != 0) + { + U64 hash = cmd_line_hash_from_string(string); + U64 bucket = hash % cmd_line->option_table_size; + slot = &cmd_line->option_table[bucket]; + } + return slot; +} + +internal CmdLineOpt * +cmd_line_opt_from_slot(CmdLineOpt **slot, String8 string) +{ + CmdLineOpt *result = 0; + for(CmdLineOpt *var = *slot; var; var = var->hash_next) + { + if(str8_match(string, var->string, 0)) + { + result = var; + break; + } + } + return result; +} + +internal void +cmd_line_push_opt(CmdLineOptList *list, CmdLineOpt *var) +{ + SLLQueuePush(list->first, list->last, var); + list->count += 1; +} + +internal CmdLineOpt * +cmd_line_insert_opt(Arena *arena, CmdLine *cmd_line, String8 string, String8List values) +{ + CmdLineOpt *var = 0; + CmdLineOpt **slot = cmd_line_slot_from_string(cmd_line, string); + CmdLineOpt *existing_var = cmd_line_opt_from_slot(slot, string); + if(existing_var != 0) + { + var = existing_var; + } + else + { + var = push_array(arena, CmdLineOpt, 1); + var->hash_next = *slot; + var->hash = cmd_line_hash_from_string(string); + var->string = push_str8_copy(arena, string); + var->value_strings = values; + StringJoin join = {0}; + join.pre = str8_lit(""); + join.sep = str8_lit(","); + join.post = str8_lit(""); + var->value_string = str8_list_join(arena, &var->value_strings, &join); + *slot = var; + cmd_line_push_opt(&cmd_line->options, var); + } + return var; +} + +internal CmdLine +cmd_line_from_string_list(Arena *arena, String8List command_line) +{ + CmdLine parsed = {0}; + parsed.exe_name = command_line.first->string; + + // NOTE(rjf): Set up config option table. + { + parsed.option_table_size = 4096; + parsed.option_table = push_array(arena, CmdLineOpt *, parsed.option_table_size); + } + + // NOTE(rjf): Parse command line. + B32 after_passthrough_option = 0; + B32 first_passthrough = 1; + for(String8Node *node = command_line.first->next, *next = 0; node != 0; node = next) + { + next = node->next; + String8 option_name = node->string; + + // NOTE(rjf): Look at -- or - at the start of an argument to determine if it's + // a flag option. All arguments after a single "--" (with no trailing string + // on the command line will be considered as input files. + B32 is_option = 1; + if(after_passthrough_option == 0) + { + if(str8_match(node->string, str8_lit("--"), 0)) + { + after_passthrough_option = 1; + is_option = 0; + } + else if(str8_match(str8_prefix(node->string, 2), str8_lit("--"), 0)) + { + option_name = str8_skip(option_name, 2); + } + else if(str8_match(str8_prefix(node->string, 1), str8_lit("-"), 0)) + { + option_name = str8_skip(option_name, 1); + } + else + { + is_option = 0; + } + } + else + { + is_option = 0; + } + + // NOTE(rjf): This string is an option. + if(is_option) + { + B32 has_arguments = 0; + U64 arg_signifier_position1 = str8_find_needle(option_name, 0, str8_lit(":"), 0); + U64 arg_signifier_position2 = str8_find_needle(option_name, 0, str8_lit("="), 0); + U64 arg_signifier_position = Min(arg_signifier_position1, arg_signifier_position2); + String8 arg_portion_this_string = str8_skip(option_name, arg_signifier_position+1); + if(arg_signifier_position < option_name.size) + { + has_arguments = 1; + } + option_name = str8_prefix(option_name, arg_signifier_position); + + String8List arguments = {0}; + + // NOTE(rjf): Parse arguments. + if(has_arguments) + { + for(String8Node *n = node; n; n = n->next) + { + next = n->next; + + String8 string = n->string; + if(n == node) + { + string = arg_portion_this_string; + } + + U8 splits[] = { ',' }; + String8List args_in_this_string = str8_split(arena, string, splits, ArrayCount(splits), 0); + for(String8Node *sub_arg = args_in_this_string.first; sub_arg; sub_arg = sub_arg->next) + { + str8_list_push(arena, &arguments, sub_arg->string); + } + if(!str8_match(str8_postfix(n->string, 1), str8_lit(","), 0) && + (n != node || arg_portion_this_string.size != 0)) + { + break; + } + } + } + + // NOTE(rjf): Register config variable. + cmd_line_insert_opt(arena, &parsed, option_name, arguments); + } + + // NOTE(rjf): Default path, treat as a passthrough config option to be + // handled by tool-specific code. + else if(!str8_match(node->string, str8_lit("--"), 0) || !first_passthrough) + { + str8_list_push(arena, &parsed.inputs, node->string); + after_passthrough_option = 1; + first_passthrough = 0; + } + } + + return parsed; +} + +internal CmdLineOpt * +cmd_line_opt_from_string(CmdLine *cmd_line, String8 name) +{ + return cmd_line_opt_from_slot(cmd_line_slot_from_string(cmd_line, name), name); +} + +internal String8List +cmd_line_strings(CmdLine *cmd_line, String8 name) +{ + String8List result = {0}; + CmdLineOpt *var = cmd_line_opt_from_string(cmd_line, name); + if(var != 0) + { + result = var->value_strings; + } + return result; +} + +internal String8 +cmd_line_string(CmdLine *cmd_line, String8 name) +{ + String8 result = {0}; + CmdLineOpt *var = cmd_line_opt_from_string(cmd_line, name); + if(var != 0) + { + result = var->value_string; + } + return result; +} + +internal B32 +cmd_line_has_flag(CmdLine *cmd_line, String8 name) +{ + CmdLineOpt *var = cmd_line_opt_from_string(cmd_line, name); + return(var != 0); +} + +internal B32 +cmd_line_has_argument(CmdLine *cmd_line, String8 name) +{ + CmdLineOpt *var = cmd_line_opt_from_string(cmd_line, name); + return(var != 0 && var->value_strings.node_count > 0); +} diff --git a/metagen/metagen_base/metagen_base_command_line.h b/metagen/metagen_base/metagen_base_command_line.h new file mode 100644 index 0000000..b18fc87 --- /dev/null +++ b/metagen/metagen_base/metagen_base_command_line.h @@ -0,0 +1,54 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_COMMAND_LINE_H +#define BASE_COMMAND_LINE_H + +//////////////////////////////// +//~ rjf: Parsed Command Line Types + +typedef struct CmdLineOpt CmdLineOpt; +struct CmdLineOpt +{ + CmdLineOpt *next; + CmdLineOpt *hash_next; + U64 hash; + String8 string; + String8List value_strings; + String8 value_string; +}; + +typedef struct CmdLineOptList CmdLineOptList; +struct CmdLineOptList +{ + U64 count; + CmdLineOpt *first; + CmdLineOpt *last; +}; + +typedef struct CmdLine CmdLine; +struct CmdLine +{ + String8 exe_name; + CmdLineOptList options; + String8List inputs; + U64 option_table_size; + CmdLineOpt **option_table; +}; + +//////////////////////////////// +//~ NOTE(rjf): Command Line Option Parsing + +internal U64 cmd_line_hash_from_string(String8 string); +internal CmdLineOpt** cmd_line_slot_from_string(CmdLine *cmd_line, String8 string); +internal CmdLineOpt* cmd_line_opt_from_slot(CmdLineOpt **slot, String8 string); +internal void cmd_line_push_opt(CmdLineOptList *list, CmdLineOpt *var); +internal CmdLineOpt* cmd_line_insert_opt(Arena *arena, CmdLine *cmd_line, String8 string, String8List values); +internal CmdLine cmd_line_from_string_list(Arena *arena, String8List arguments); +internal CmdLineOpt* cmd_line_opt_from_string(CmdLine *cmd_line, String8 name); +internal String8List cmd_line_strings(CmdLine *cmd_line, String8 name); +internal String8 cmd_line_string(CmdLine *cmd_line, String8 name); +internal B32 cmd_line_has_flag(CmdLine *cmd_line, String8 name); +internal B32 cmd_line_has_argument(CmdLine *cmd_line, String8 name); + +#endif // BASE_COMMAND_LINE_H diff --git a/metagen/metagen_base/metagen_base_context_cracking.h b/metagen/metagen_base/metagen_base_context_cracking.h new file mode 100644 index 0000000..9df1dc5 --- /dev/null +++ b/metagen/metagen_base/metagen_base_context_cracking.h @@ -0,0 +1,247 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_CONTEXT_CRACKING_H +#define BASE_CONTEXT_CRACKING_H + +//////////////////////////////// +//~ rjf: Clang OS/Arch Cracking + +#if defined(__clang__) + +# define COMPILER_CLANG 1 + +# if defined(_WIN32) +# define OS_WINDOWS 1 +# elif defined(__gnu_linux__) || defined(__linux__) +# define OS_LINUX 1 +# elif defined(__APPLE__) && defined(__MACH__) +# define OS_MAC 1 +# else +# error This compiler/OS combo is not supported. +# endif + +# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) +# define ARCH_X64 1 +# elif defined(i386) || defined(__i386) || defined(__i386__) +# define ARCH_X86 1 +# elif defined(__aarch64__) +# define ARCH_ARM64 1 +# elif defined(__arm__) +# define ARCH_ARM32 1 +# else +# error Architecture not supported. +# endif + +//////////////////////////////// +//~ rjf: MSVC OS/Arch Cracking + +#elif defined(_MSC_VER) + +# define COMPILER_MSVC 1 + +# if _MSC_VER >= 1920 +# define COMPILER_MSVC_YEAR 2019 +# elif _MSC_VER >= 1910 +# define COMPILER_MSVC_YEAR 2017 +# elif _MSC_VER >= 1900 +# define COMPILER_MSVC_YEAR 2015 +# elif _MSC_VER >= 1800 +# define COMPILER_MSVC_YEAR 2013 +# elif _MSC_VER >= 1700 +# define COMPILER_MSVC_YEAR 2012 +# elif _MSC_VER >= 1600 +# define COMPILER_MSVC_YEAR 2010 +# elif _MSC_VER >= 1500 +# define COMPILER_MSVC_YEAR 2008 +# elif _MSC_VER >= 1400 +# define COMPILER_MSVC_YEAR 2005 +# else +# define COMPILER_MSVC_YEAR 0 +# endif + +# if defined(_WIN32) +# define OS_WINDOWS 1 +# else +# error This compiler/OS combo is not supported. +# endif + +# if defined(_M_AMD64) +# define ARCH_X64 1 +# elif defined(_M_IX86) +# define ARCH_X86 1 +# elif defined(_M_ARM64) +# define ARCH_ARM64 1 +# elif defined(_M_ARM) +# define ARCH_ARM32 1 +# else +# error Architecture not supported. +# endif + +//////////////////////////////// +//~ rjf: GCC OS/Arch Cracking + +#elif defined(__GNUC__) || defined(__GNUG__) + +# define COMPILER_GCC 1 + +# if defined(__gnu_linux__) || defined(__linux__) +# define OS_LINUX 1 +# else +# error This compiler/OS combo is not supported. +# endif + +# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) +# define ARCH_X64 1 +# elif defined(i386) || defined(__i386) || defined(__i386__) +# define ARCH_X86 1 +# elif defined(__aarch64__) +# define ARCH_ARM64 1 +# elif defined(__arm__) +# define ARCH_ARM32 1 +# else +# error Architecture not supported. +# endif + +#else +# error Compiler not supported. +#endif + +//////////////////////////////// +//~ rjf: Arch Cracking + +#if defined(ARCH_X64) +# define ARCH_64BIT 1 +#elif defined(ARCH_X86) +# define ARCH_32BIT 1 +#endif + +#if ARCH_ARM32 || ARCH_ARM64 || ARCH_X64 || ARCH_X86 +# define ARCH_LITTLE_ENDIAN 1 +#else +# error Endianness of this architecture not understood by context cracker. +#endif + +//////////////////////////////// +//~ rjf: Language Cracking + +#if defined(__cplusplus) +# define LANG_CPP 1 +#else +# define LANG_C 1 +#endif + +//////////////////////////////// +//~ rjf: Build Option Cracking + +#if !defined(BUILD_DEBUG) +# define BUILD_DEBUG 1 +#endif + +#if !defined(BUILD_SUPPLEMENTARY_UNIT) +# define BUILD_SUPPLEMENTARY_UNIT 0 +#endif + +#if !defined(BUILD_ENTRY_DEFINING_UNIT) +# define BUILD_ENTRY_DEFINING_UNIT 1 +#endif + +#if !defined(BUILD_CONSOLE_INTERFACE) +# define BUILD_CONSOLE_INTERFACE 0 +#endif + +#if !defined(BUILD_VERSION_MAJOR) +# define BUILD_VERSION_MAJOR 0 +#endif + +#if !defined(BUILD_VERSION_MINOR) +# define BUILD_VERSION_MINOR 0 +#endif + +#if !defined(BUILD_VERSION_PATCH) +# define BUILD_VERSION_PATCH 0 +#endif + +#define BUILD_VERSION_STRING_LITERAL Stringify(BUILD_VERSION_MAJOR) "." Stringify(BUILD_VERSION_MINOR) "." Stringify(BUILD_VERSION_PATCH) +#if BUILD_DEBUG +# define BUILD_MODE_STRING_LITERAL_APPEND " [Debug]" +#else +# define BUILD_MODE_STRING_LITERAL_APPEND "" +#endif +#if defined(BUILD_GIT_HASH) +# define BUILD_GIT_HASH_STRING_LITERAL_APPEND " [" BUILD_GIT_HASH "]" +#else +# define BUILD_GIT_HASH_STRING_LITERAL_APPEND "" +#endif + +#if !defined(BUILD_TITLE) +# define BUILD_TITLE "Untitled" +#endif + +#if !defined(BUILD_RELEASE_PHASE_STRING_LITERAL) +# define BUILD_RELEASE_PHASE_STRING_LITERAL "ALPHA" +#endif + +#if !defined(BUILD_ISSUES_LINK_STRING_LITERAL) +# define BUILD_ISSUES_LINK_STRING_LITERAL "https://github.com/EpicGames/raddebugger/issues" +#endif + +#define BUILD_TITLE_STRING_LITERAL BUILD_TITLE " (" BUILD_VERSION_STRING_LITERAL " " BUILD_RELEASE_PHASE_STRING_LITERAL ") - " __DATE__ "" BUILD_GIT_HASH_STRING_LITERAL_APPEND BUILD_MODE_STRING_LITERAL_APPEND + +//////////////////////////////// +//~ rjf: Zero All Undefined Options + +#if !defined(ARCH_32BIT) +# define ARCH_32BIT 0 +#endif +#if !defined(ARCH_64BIT) +# define ARCH_64BIT 0 +#endif +#if !defined(ARCH_X64) +# define ARCH_X64 0 +#endif +#if !defined(ARCH_X86) +# define ARCH_X86 0 +#endif +#if !defined(ARCH_ARM64) +# define ARCH_ARM64 0 +#endif +#if !defined(ARCH_ARM32) +# define ARCH_ARM32 0 +#endif +#if !defined(COMPILER_MSVC) +# define COMPILER_MSVC 0 +#endif +#if !defined(COMPILER_GCC) +# define COMPILER_GCC 0 +#endif +#if !defined(COMPILER_CLANG) +# define COMPILER_CLANG 0 +#endif +#if !defined(OS_WINDOWS) +# define OS_WINDOWS 0 +#endif +#if !defined(OS_LINUX) +# define OS_LINUX 0 +#endif +#if !defined(OS_MAC) +# define OS_MAC 0 +#endif +#if !defined(LANG_CPP) +# define LANG_CPP 0 +#endif +#if !defined(LANG_C) +# define LANG_C 0 +#endif + +//////////////////////////////// +//~ rjf: Unsupported Errors + +#if ARCH_X86 +# error You tried to build in x86 (32 bit) mode, but currently, only building in x64 (64 bit) mode is supported. +#endif +#if !ARCH_X64 +# error You tried to build with an unsupported architecture. Currently, only building in x64 mode is supported. +#endif + +#endif // BASE_CONTEXT_CRACKING_H diff --git a/metagen/metagen_base/metagen_base_core.c b/metagen/metagen_base/metagen_base_core.c new file mode 100644 index 0000000..2d40fe3 --- /dev/null +++ b/metagen/metagen_base/metagen_base_core.c @@ -0,0 +1,562 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Safe Casts + +internal U16 +safe_cast_u16(U32 x) +{ + AssertAlways(x <= max_U16); + U16 result = (U16)x; + return result; +} + +internal U32 +safe_cast_u32(U64 x) +{ + AssertAlways(x <= max_U32); + U32 result = (U32)x; + return result; +} + +internal S32 +safe_cast_s32(S64 x) +{ + AssertAlways(x <= max_S32); + S32 result = (S32)x; + return result; +} + +//////////////////////////////// +//~ rjf: Large Base Type Functions + +internal U128 +u128_zero(void) +{ + U128 v = {0}; + return v; +} + +internal U128 +u128_make(U64 v0, U64 v1) +{ + U128 v = {v0, v1}; + return v; +} + +internal B32 +u128_match(U128 a, U128 b) +{ + return MemoryMatchStruct(&a, &b); +} + +//////////////////////////////// +//~ rjf: Bit Patterns + +internal U32 +u32_from_u64_saturate(U64 x){ + U32 x32 = (x > max_U32)?max_U32:(U32)x; + return(x32); +} + +internal U64 +u64_up_to_pow2(U64 x){ + if (x == 0){ + x = 1; + } + else{ + x -= 1; + x |= (x >> 1); + x |= (x >> 2); + x |= (x >> 4); + x |= (x >> 8); + x |= (x >> 16); + x |= (x >> 32); + x += 1; + } + return(x); +} + +internal S32 +extend_sign32(U32 x, U32 size){ + U32 high_bit = size * 8; + U32 shift = 32 - high_bit; + S32 result = ((S32)x << shift) >> shift; + return result; +} + +internal S64 +extend_sign64(U64 x, U64 size){ + U64 high_bit = size * 8; + U64 shift = 64 - high_bit; + S64 result = ((S64)x << shift) >> shift; + return result; +} + +internal F32 +inf32(void){ + union { U32 u; F32 f; } x; + x.u = exponent32; + return(x.f); +} + +internal F32 +neg_inf32(void){ + union { U32 u; F32 f; } x; + x.u = sign32 | exponent32; + return(x.f); +} + +internal U16 +bswap_u16(U16 x) +{ + U16 result = (((x & 0xFF00) >> 8) | + ((x & 0x00FF) << 8)); + return result; +} + +internal U32 +bswap_u32(U32 x) +{ + U32 result = (((x & 0xFF000000) >> 24) | + ((x & 0x00FF0000) >> 8) | + ((x & 0x0000FF00) << 8) | + ((x & 0x000000FF) << 24)); + return result; +} + +internal U64 +bswap_u64(U64 x) +{ + // TODO(nick): naive bswap, replace with something that is faster like an intrinsic + U64 result = (((x & 0xFF00000000000000ULL) >> 56) | + ((x & 0x00FF000000000000ULL) >> 40) | + ((x & 0x0000FF0000000000ULL) >> 24) | + ((x & 0x000000FF00000000ULL) >> 8) | + ((x & 0x00000000FF000000ULL) << 8) | + ((x & 0x0000000000FF0000ULL) << 24) | + ((x & 0x000000000000FF00ULL) << 40) | + ((x & 0x00000000000000FFULL) << 56)); + return result; +} + +#if COMPILER_MSVC || (COMPILER_CLANG && OS_WINDOWS) + +internal U64 +count_bits_set16(U16 val) +{ + return __popcnt16(val); +} + +internal U64 +count_bits_set32(U32 val) +{ + return __popcnt(val); +} + +internal U64 +count_bits_set64(U64 val) +{ + return __popcnt64(val); +} + +internal U64 +ctz32(U32 mask) +{ + unsigned long idx; + _BitScanForward(&idx, mask); + return idx; +} + +internal U64 +ctz64(U64 mask) +{ + unsigned long idx; + _BitScanForward64(&idx, mask); + return idx; +} + +internal U64 +clz32(U32 mask) +{ + unsigned long idx; + _BitScanReverse(&idx, mask); + return 31 - idx; +} + +internal U64 +clz64(U64 mask) +{ + unsigned long idx; + _BitScanReverse64(&idx, mask); + return 63 - idx; +} + +#elif COMPILER_CLANG || COMPILER_GCC + +internal U64 +count_bits_set16(U16 val) +{ + NotImplemented; + return 0; +} + +internal U64 +count_bits_set32(U32 val) +{ + NotImplemented; + return 0; +} + +internal U64 +count_bits_set64(U64 val) +{ + NotImplemented; + return 0; +} + +internal U64 +ctz32(U32 val) +{ + NotImplemented; + return 0; +} + +internal U64 +clz32(U32 val) +{ + NotImplemented; + return 0; +} + +internal U64 +clz64(U64 val) +{ + NotImplemented; + return 0; +} + +#else +# error "Bit intrinsic functions not defined for this compiler." +#endif + +//////////////////////////////// +//~ rjf: Enum -> Sign + +internal S32 +sign_from_side_S32(Side side){ + return((side == Side_Min)?-1:1); +} + +internal F32 +sign_from_side_F32(Side side){ + return((side == Side_Min)?-1.f:1.f); +} + +//////////////////////////////// +//~ rjf: Memory Functions + +internal B32 +memory_is_zero(void *ptr, U64 size){ + B32 result = 1; + + // break down size + U64 extra = (size&0x7); + U64 count8 = (size >> 3); + + // check with 8-byte stride + U64 *p64 = (U64*)ptr; + if(result) + { + for (U64 i = 0; i < count8; i += 1, p64 += 1){ + if (*p64 != 0){ + result = 0; + goto done; + } + } + } + + // check extra + if(result) + { + U8 *p8 = (U8*)p64; + for (U64 i = 0; i < extra; i += 1, p8 += 1){ + if (*p8 != 0){ + result = 0; + goto done; + } + } + } + + done:; + return(result); +} + +//////////////////////////////// +//~ rjf: Text 2D Coordinate/Range Functions + +internal TxtPt +txt_pt(S64 line, S64 column) +{ + TxtPt p = {0}; + p.line = line; + p.column = column; + return p; +} + +internal B32 +txt_pt_match(TxtPt a, TxtPt b) +{ + return a.line == b.line && a.column == b.column; +} + +internal B32 +txt_pt_less_than(TxtPt a, TxtPt b) +{ + B32 result = 0; + if(a.line < b.line) + { + result = 1; + } + else if(a.line == b.line) + { + result = a.column < b.column; + } + return result; +} + +internal TxtPt +txt_pt_min(TxtPt a, TxtPt b) +{ + TxtPt result = b; + if(txt_pt_less_than(a, b)) + { + result = a; + } + return result; +} + +internal TxtPt +txt_pt_max(TxtPt a, TxtPt b) +{ + TxtPt result = a; + if(txt_pt_less_than(a, b)) + { + result = b; + } + return result; +} + +internal TxtRng +txt_rng(TxtPt min, TxtPt max) +{ + TxtRng range = {0}; + if(txt_pt_less_than(min, max)) + { + range.min = min; + range.max = max; + } + else + { + range.min = max; + range.max = min; + } + return range; +} + +internal TxtRng +txt_rng_intersect(TxtRng a, TxtRng b) +{ + TxtRng result = {0}; + result.min = txt_pt_max(a.min, b.min); + result.max = txt_pt_min(a.max, b.max); + if(txt_pt_less_than(result.max, result.min)) + { + MemoryZeroStruct(&result); + } + return result; +} + +internal TxtRng +txt_rng_union(TxtRng a, TxtRng b) +{ + TxtRng result = {0}; + result.min = txt_pt_min(a.min, b.min); + result.max = txt_pt_max(a.max, b.max); + return result; +} + +internal B32 +txt_rng_contains(TxtRng r, TxtPt pt) +{ + B32 result = ((txt_pt_less_than(r.min, pt) || txt_pt_match(r.min, pt)) && + txt_pt_less_than(pt, r.max)); + return result; +} + +//////////////////////////////// +//~ rjf: Toolchain/Environment Enum Functions + +internal U64 +bit_size_from_arch(Architecture arch) +{ + // TODO(rjf): metacode + U64 arch_bitsize = 0; + switch(arch) + { + case Architecture_x64: arch_bitsize = 64; break; + case Architecture_x86: arch_bitsize = 32; break; + case Architecture_arm64: arch_bitsize = 64; break; + case Architecture_arm32: arch_bitsize = 32; break; + default: break; + } + return arch_bitsize; +} + +internal U64 +max_instruction_size_from_arch(Architecture arch) +{ + // TODO(rjf): make this real + return 64; +} + +internal OperatingSystem +operating_system_from_context(void){ + OperatingSystem os = OperatingSystem_Null; +#if OS_WINDOWS + os = OperatingSystem_Windows; +#elif OS_LINUX + os = OperatingSystem_Linux; +#elif OS_MAC + os = OperatingSystem_Mac; +#endif + return os; +} + +internal Architecture +architecture_from_context(void){ + Architecture arch = Architecture_Null; +#if ARCH_X64 + arch = Architecture_x64; +#elif ARCH_X86 + arch = Architecture_x86; +#elif ARCH_ARM64 + arch = Architecture_arm64; +#elif ARCH_ARM32 + arch = Architecture_arm32; +#endif + return arch; +} + +internal Compiler +compiler_from_context(void){ + Compiler compiler = Compiler_Null; +#if COMPILER_MSVC + compiler = Compiler_msvc; +#elif COMPILER_GCC + compiler = Compiler_gcc; +#elif COMPILER_CLANG + compiler = Compiler_clang; +#endif + return compiler; +} + +//////////////////////////////// +//~ rjf: Time Functions + +internal DenseTime +dense_time_from_date_time(DateTime date_time){ + DenseTime result = 0; + result += date_time.year; + result *= 12; + result += date_time.mon; + result *= 31; + result += date_time.day; + result *= 24; + result += date_time.hour; + result *= 60; + result += date_time.min; + result *= 61; + result += date_time.sec; + result *= 1000; + result += date_time.msec; + return(result); +} + +internal DateTime +date_time_from_dense_time(DenseTime time){ + DateTime result = {0}; + result.msec = time%1000; + time /= 1000; + result.sec = time%61; + time /= 61; + result.min = time%60; + time /= 60; + result.hour = time%24; + time /= 24; + result.day = time%31; + time /= 31; + result.mon = time%12; + time /= 12; + Assert(time <= max_U32); + result.year = (U32)time; + return(result); +} + +internal DateTime +date_time_from_micro_seconds(U64 time){ + DateTime result = {0}; + result.micro_sec = time%1000; + time /= 1000; + result.msec = time%1000; + time /= 1000; + result.sec = time%60; + time /= 60; + result.min = time%60; + time /= 60; + result.hour = time%24; + time /= 24; + result.day = time%31; + time /= 31; + result.mon = time%12; + time /= 12; + Assert(time <= max_U32); + result.year = (U32)time; + return(result); +} + +//////////////////////////////// +//~ rjf: Non-Fancy Ring Buffer Reads/Writes + +internal U64 +ring_write(U8 *ring_base, U64 ring_size, U64 ring_pos, void *src_data, U64 src_data_size) +{ + Assert(src_data_size <= ring_size); + { + U64 ring_off = ring_pos%ring_size; + U64 bytes_before_split = ring_size-ring_off; + U64 pre_split_bytes = Min(bytes_before_split, src_data_size); + U64 pst_split_bytes = src_data_size-pre_split_bytes; + void *pre_split_data = src_data; + void *pst_split_data = ((U8 *)src_data + pre_split_bytes); + MemoryCopy(ring_base+ring_off, pre_split_data, pre_split_bytes); + MemoryCopy(ring_base+0, pst_split_data, pst_split_bytes); + } + return src_data_size; +} + +internal U64 +ring_read(U8 *ring_base, U64 ring_size, U64 ring_pos, void *dst_data, U64 read_size) +{ + Assert(read_size <= ring_size); + { + U64 ring_off = ring_pos%ring_size; + U64 bytes_before_split = ring_size-ring_off; + U64 pre_split_bytes = Min(bytes_before_split, read_size); + U64 pst_split_bytes = read_size-pre_split_bytes; + MemoryCopy(dst_data, ring_base+ring_off, pre_split_bytes); + MemoryCopy((U8 *)dst_data + pre_split_bytes, ring_base+0, pst_split_bytes); + } + return read_size; +} diff --git a/metagen/metagen_base/metagen_base_core.h b/metagen/metagen_base/metagen_base_core.h new file mode 100644 index 0000000..7b872b6 --- /dev/null +++ b/metagen/metagen_base/metagen_base_core.h @@ -0,0 +1,800 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_CORE_H +#define BASE_CORE_H + +//////////////////////////////// +//~ rjf: Foreign Includes + +#include +#include +#include +#include +#include + +//////////////////////////////// +//~ rjf: Codebase Keywords + +#define internal static +#define global static +#define local_persist static + +#if COMPILER_MSVC || (COMPILER_CLANG && OS_WINDOWS) +# pragma section(".rdata$", read) +# define read_only __declspec(allocate(".rdata$")) +#elif (COMPILER_CLANG && OS_LINUX) +# define read_only __attribute__((section(".rodata"))) +#else +// NOTE(rjf): I don't know of a useful way to do this in GCC land. +// __attribute__((section(".rodata"))) looked promising, but it introduces a +// strange warning about malformed section attributes, and it doesn't look +// like writing to that section reliably produces access violations, strangely +// enough. (It does on Clang) +# define read_only +#endif + +#if COMPILER_MSVC +# define thread_static __declspec(thread) +#elif COMPILER_CLANG || COMPILER_GCC +# define thread_static __thread +#endif + +//////////////////////////////// +//~ rjf: Linkage Keyword Macros + +#if OS_WINDOWS +# define shared_function C_LINKAGE __declspec(dllexport) +#else +# define shared_function C_LINKAGE +#endif + +#if LANG_CPP +# define C_LINKAGE_BEGIN extern "C"{ +# define C_LINKAGE_END } +# define C_LINKAGE extern "C" +#else +# define C_LINKAGE_BEGIN +# define C_LINKAGE_END +# define C_LINKAGE +#endif + +//////////////////////////////// +//~ rjf: Units + +#define KB(n) (((U64)(n)) << 10) +#define MB(n) (((U64)(n)) << 20) +#define GB(n) (((U64)(n)) << 30) +#define TB(n) (((U64)(n)) << 40) +#define Thousand(n) ((n)*1000) +#define Million(n) ((n)*1000000) +#define Billion(n) ((n)*1000000000) + +//////////////////////////////// +//~ rjf: Branch Predictor Hints + +#if defined(__clang__) +# define Expect(expr, val) __builtin_expect((expr), (val)) +#else +# define Expect(expr, val) (expr) +#endif + +#define Likely(expr) Expect(expr,1) +#define Unlikely(expr) Expect(expr,0) + +//////////////////////////////// +//~ rjf: Clamps, Mins, Maxes + +#define Min(A,B) (((A)<(B))?(A):(B)) +#define Max(A,B) (((A)>(B))?(A):(B)) +#define ClampTop(A,X) Min(A,X) +#define ClampBot(X,B) Max(X,B) +#define Clamp(A,X,B) (((X)<(A))?(A):((X)>(B))?(B):(X)) + +//////////////////////////////// +//~ rjf: Type -> Alignment + +#if COMPILER_MSVC +# define AlignOf(T) __alignof(T) +#elif COMPILER_CLANG +# define AlignOf(T) __alignof(T) +#elif COMPILER_GCC +# define AlignOf(T) __alignof__(T) +#else +# error AlignOf not defined for this compiler. +#endif + +//////////////////////////////// +//~ rjf: Member Offsets + +#define Member(T,m) (((T*)0)->m) +#define OffsetOf(T,m) IntFromPtr(&Member(T,m)) +#define MemberFromOffset(T,ptr,off) (T)((((U8 *)ptr)+(off))) +#define CastFromMember(T,m,ptr) (T*)(((U8*)ptr) - OffsetOf(T,m)) + +//////////////////////////////// +//~ rjf: For-Loop Construct Macros + +#define DeferLoop(begin, end) for(int _i_ = ((begin), 0); !_i_; _i_ += 1, (end)) +#define DeferLoopChecked(begin, end) for(int _i_ = 2 * !(begin); (_i_ == 2 ? ((end), 0) : !_i_); _i_ += 1, (end)) + +#define EachEnumVal(type, it) type it = (type)0; it < type##_COUNT; it = (type)(it+1) +#define EachNonZeroEnumVal(type, it) type it = (type)1; it < type##_COUNT; it = (type)(it+1) + +//////////////////////////////// +//~ rjf: Memory Operation Macros + +#define MemoryCopy(dst, src, size) memmove((dst), (src), (size)) +#define MemorySet(dst, byte, size) memset((dst), (byte), (size)) +#define MemoryCompare(a, b, size) memcmp((a), (b), (size)) +#define MemoryStrlen(ptr) strlen(ptr) + +#define MemoryCopyStruct(d,s) MemoryCopy((d),(s),sizeof(*(d))) +#define MemoryCopyArray(d,s) MemoryCopy((d),(s),sizeof(d)) +#define MemoryCopyTyped(d,s,c) MemoryCopy((d),(s),sizeof(*(d))*(c)) + +#define MemoryZero(s,z) memset((s),0,(z)) +#define MemoryZeroStruct(s) MemoryZero((s),sizeof(*(s))) +#define MemoryZeroArray(a) MemoryZero((a),sizeof(a)) +#define MemoryZeroTyped(m,c) MemoryZero((m),sizeof(*(m))*(c)) + +#define MemoryMatch(a,b,z) (MemoryCompare((a),(b),(z)) == 0) +#define MemoryMatchStruct(a,b) MemoryMatch((a),(b),sizeof(*(a))) +#define MemoryMatchArray(a,b) MemoryMatch((a),(b),sizeof(a)) + +#define MemoryRead(T,p,e) ( ((p)+sizeof(T)<=(e))?(*(T*)(p)):(0) ) +#define MemoryConsume(T,p,e) ( ((p)+sizeof(T)<=(e))?((p)+=sizeof(T),*(T*)((p)-sizeof(T))):((p)=(e),0) ) + +//////////////////////////////// +//~ rjf: Asserts + +#if COMPILER_MSVC +# define Trap() __debugbreak() +#elif COMPILER_CLANG || COMPILER_GCC +# define Trap() __builtin_trap() +#else +# error Unknown trap intrinsic for this compiler. +#endif + +#define AssertAlways(x) do{if(!(x)) {Trap();}}while(0) +#if BUILD_DEBUG +# define Assert(x) AssertAlways(x) +#else +# define Assert(x) (void)(x) +#endif +#define InvalidPath Assert(!"Invalid Path!") +#define NotImplemented Assert(!"Not Implemented!") +#define NoOp ((void)0) +#define StaticAssert(C, ID) global U8 Glue(ID, __LINE__)[(C)?1:-1] + +//////////////////////////////// +//~ rjf: Atomic Operations + +#if OS_WINDOWS +# include +# include +# include +# include +# if ARCH_X64 +# define ins_atomic_u64_eval(x) InterlockedAdd64((volatile __int64 *)(x), 0) +# define ins_atomic_u64_inc_eval(x) InterlockedIncrement64((volatile __int64 *)(x)) +# define ins_atomic_u64_dec_eval(x) InterlockedDecrement64((volatile __int64 *)(x)) +# define ins_atomic_u64_eval_assign(x,c) InterlockedExchange64((volatile __int64 *)(x),(c)) +# define ins_atomic_u64_add_eval(x,c) InterlockedAdd64((volatile __int64 *)(x), c) +# define ins_atomic_u64_eval_cond_assign(x,k,c) InterlockedCompareExchange64((volatile __int64 *)(x),(k),(c)) +# define ins_atomic_u32_eval(x,c) InterlockedAdd((volatile LONG *)(x), 0) +# define ins_atomic_u32_eval_assign(x,c) InterlockedExchange((volatile LONG *)(x),(c)) +# define ins_atomic_u32_eval_cond_assign(x,k,c) InterlockedCompareExchange((volatile LONG *)(x),(k),(c)) +# define ins_atomic_ptr_eval_assign(x,c) (void*)ins_atomic_u64_eval_assign((volatile __int64 *)(x), (__int64)(c)) +# else +# error Atomic intrinsics not defined for this operating system / architecture combination. +# endif +#elif OS_LINUX +# if ARCH_X64 +# define ins_atomic_u64_inc_eval(x) __sync_fetch_and_add((volatile U64 *)(x), 1) +# else +# error Atomic intrinsics not defined for this operating system / architecture combination. +# endif +#else +# error Atomic intrinsics not defined for this operating system. +#endif + +//////////////////////////////// +//~ rjf: Linked List Building Macros + +//- rjf: linked list macro helpers +#define CheckNil(nil,p) ((p) == 0 || (p) == nil) +#define SetNil(nil,p) ((p) = nil) + +//- rjf: doubly-linked-lists +#define DLLInsert_NPZ(nil,f,l,p,n,next,prev) (CheckNil(nil,f) ? \ +((f) = (l) = (n), SetNil(nil,(n)->next), SetNil(nil,(n)->prev)) :\ +CheckNil(nil,p) ? \ +((n)->next = (f), (f)->prev = (n), (f) = (n), SetNil(nil,(n)->prev)) :\ +((p)==(l)) ? \ +((l)->next = (n), (n)->prev = (l), (l) = (n), SetNil(nil, (n)->next)) :\ +(((!CheckNil(nil,p) && CheckNil(nil,(p)->next)) ? (0) : ((p)->next->prev = (n))), ((n)->next = (p)->next), ((p)->next = (n)), ((n)->prev = (p)))) +#define DLLPushBack_NPZ(nil,f,l,n,next,prev) DLLInsert_NPZ(nil,f,l,l,n,next,prev) +#define DLLPushFront_NPZ(nil,f,l,n,next,prev) DLLInsert_NPZ(nil,l,f,f,n,prev,next) +#define DLLRemove_NPZ(nil,f,l,n,next,prev) (((n) == (f) ? (f) = (n)->next : (0)),\ +((n) == (l) ? (l) = (l)->prev : (0)),\ +(CheckNil(nil,(n)->prev) ? (0) :\ +((n)->prev->next = (n)->next)),\ +(CheckNil(nil,(n)->next) ? (0) :\ +((n)->next->prev = (n)->prev))) + +//- rjf: singly-linked, doubly-headed lists (queues) +#define SLLQueuePush_NZ(nil,f,l,n,next) (CheckNil(nil,f)?\ +((f)=(l)=(n),SetNil(nil,(n)->next)):\ +((l)->next=(n),(l)=(n),SetNil(nil,(n)->next))) +#define SLLQueuePushFront_NZ(nil,f,l,n,next) (CheckNil(nil,f)?\ +((f)=(l)=(n),SetNil(nil,(n)->next)):\ +((n)->next=(f),(f)=(n))) +#define SLLQueuePop_NZ(nil,f,l,next) ((f)==(l)?\ +(SetNil(nil,f),SetNil(nil,l)):\ +((f)=(f)->next)) + +//- rjf: singly-linked, singly-headed lists (stacks) +#define SLLStackPush_N(f,n,next) ((n)->next=(f), (f)=(n)) +#define SLLStackPop_N(f,next) ((f)=(f)->next) + +//- rjf: doubly-linked-list helpers +#define DLLInsert_NP(f,l,p,n,next,prev) DLLInsert_NPZ(0,f,l,p,n,next,prev) +#define DLLPushBack_NP(f,l,n,next,prev) DLLPushBack_NPZ(0,f,l,n,next,prev) +#define DLLPushFront_NP(f,l,n,next,prev) DLLPushFront_NPZ(0,f,l,n,next,prev) +#define DLLRemove_NP(f,l,n,next,prev) DLLRemove_NPZ(0,f,l,n,next,prev) +#define DLLInsert(f,l,p,n) DLLInsert_NPZ(0,f,l,p,n,next,prev) +#define DLLPushBack(f,l,n) DLLPushBack_NPZ(0,f,l,n,next,prev) +#define DLLPushFront(f,l,n) DLLPushFront_NPZ(0,f,l,n,next,prev) +#define DLLRemove(f,l,n) DLLRemove_NPZ(0,f,l,n,next,prev) + +//- rjf: singly-linked, doubly-headed list helpers +#define SLLQueuePush_N(f,l,n,next) SLLQueuePush_NZ(0,f,l,n,next) +#define SLLQueuePushFront_N(f,l,n,next) SLLQueuePushFront_NZ(0,f,l,n,next) +#define SLLQueuePop_N(f,l,next) SLLQueuePop_NZ(0,f,l,next) +#define SLLQueuePush(f,l,n) SLLQueuePush_NZ(0,f,l,n,next) +#define SLLQueuePushFront(f,l,n) SLLQueuePushFront_NZ(0,f,l,n,next) +#define SLLQueuePop(f,l) SLLQueuePop_NZ(0,f,l,next) + +//- rjf: singly-linked, singly-headed list helpers +#define SLLStackPush(f,n) SLLStackPush_N(f,n,next) +#define SLLStackPop(f) SLLStackPop_N(f,next) + +//////////////////////////////// +//~ rjf: Address Sanitizer Markup + +#if COMPILER_MSVC +# if defined(__SANITIZE_ADDRESS__) +# define ASAN_ENABLED 1 +# define NO_ASAN __declspec(no_sanitize_address) +# else +# define NO_ASAN +# endif +#elif COMPILER_CLANG +# if defined(__has_feature) +# if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +# define ASAN_ENABLED 1 +# endif +# endif +# define NO_ASAN __attribute__((no_sanitize("address"))) +#else +# define NO_ASAN +#endif + +#if ASAN_ENABLED +#pragma comment(lib, "clang_rt.asan-x86_64.lib") +C_LINKAGE void __asan_poison_memory_region(void const volatile *addr, size_t size); +C_LINKAGE void __asan_unpoison_memory_region(void const volatile *addr, size_t size); +# define AsanPoisonMemoryRegion(addr, size) __asan_poison_memory_region((addr), (size)) +# define AsanUnpoisonMemoryRegion(addr, size) __asan_unpoison_memory_region((addr), (size)) +#else +# define AsanPoisonMemoryRegion(addr, size) ((void)(addr), (void)(size)) +# define AsanUnpoisonMemoryRegion(addr, size) ((void)(addr), (void)(size)) +#endif + +//////////////////////////////// +//~ rjf: Misc. Helper Macros + +#define Stringify_(S) #S +#define Stringify(S) Stringify_(S) + +#define Glue_(A,B) A##B +#define Glue(A,B) Glue_(A,B) + +#define ArrayCount(a) (sizeof(a) / sizeof((a)[0])) + +#define CeilIntegerDiv(a,b) (((a) + (b) - 1)/(b)) + +#define Swap(T,a,b) do{T t__ = a; a = b; b = t__;}while(0) + +#if ARCH_64BIT +# define IntFromPtr(ptr) ((U64)(ptr)) +#elif ARCH_32BIT +# define IntFromPtr(ptr) ((U32)(ptr)) +#else +# error Missing pointer-to-integer cast for this architecture. +#endif +#define PtrFromInt(i) (void*)((U8*)0 + (i)) + +#define Compose64Bit(a,b) ((((U64)a) << 32) | ((U64)b)); +#define AlignPow2(x,b) (((x) + (b) - 1)&(~((b) - 1))) +#define AlignDownPow2(x,b) ((x)&(~((b) - 1))) +#define AlignPadPow2(x,b) ((0-(x)) & ((b) - 1)) +#define IsPow2(x) ((x)!=0 && ((x)&((x)-1))==0) +#define IsPow2OrZero(x) ((((x) - 1)&(x)) == 0) + +#define ExtractBit(word, idx) (((word) >> (idx)) & 1) + +#if LANG_CPP +# define zero_struct {} +#else +# define zero_struct {0} +#endif + +#if COMPILER_MSVC && COMPILER_MSVC_YEAR < 2015 +# define this_function_name "unknown" +#else +# define this_function_name __func__ +#endif + +//////////////////////////////// +//~ rjf: Base Types + +typedef uint8_t U8; +typedef uint16_t U16; +typedef uint32_t U32; +typedef uint64_t U64; +typedef int8_t S8; +typedef int16_t S16; +typedef int32_t S32; +typedef int64_t S64; +typedef S8 B8; +typedef S16 B16; +typedef S32 B32; +typedef S64 B64; +typedef float F32; +typedef double F64; +typedef void VoidProc(void); +typedef struct U128 U128; +struct U128 +{ + U64 u64[2]; +}; + +//////////////////////////////// +//~ rjf: Basic Types & Spaces + +typedef enum Dimension +{ + Dimension_X, + Dimension_Y, + Dimension_Z, + Dimension_W, +} +Dimension; + +typedef enum Side +{ + Side_Invalid = -1, + Side_Min, + Side_Max, + Side_COUNT, +} +Side; +#define side_flip(s) ((Side)(!(s))) + +typedef enum Axis2 +{ + Axis2_Invalid = -1, + Axis2_X, + Axis2_Y, + Axis2_COUNT, +} +Axis2; +#define axis2_flip(a) ((Axis2)(!(a))) + +typedef enum Corner +{ + Corner_Invalid = -1, + Corner_00, + Corner_01, + Corner_10, + Corner_11, + Corner_COUNT +} +Corner; + +typedef enum Dir2 +{ + Dir2_Invalid = -1, + Dir2_Left, + Dir2_Up, + Dir2_Right, + Dir2_Down, + Dir2_COUNT +} +Dir2; +#define axis2_from_dir2(d) (((d) & 1) ? Axis2_Y : Axis2_X) +#define side_from_dir2(d) (((d) < Dir2_Right) ? Side_Min : Side_Max) + +//////////////////////////////// +//~ rjf: Toolchain/Environment Enums + +typedef enum OperatingSystem +{ + OperatingSystem_Null, + OperatingSystem_Windows, + OperatingSystem_Linux, + OperatingSystem_Mac, + OperatingSystem_COUNT, +} +OperatingSystem; + +typedef enum Architecture +{ + Architecture_Null, + Architecture_x64, + Architecture_x86, + Architecture_arm64, + Architecture_arm32, + Architecture_COUNT, +} +Architecture; + +typedef enum Compiler +{ + Compiler_Null, + Compiler_msvc, + Compiler_gcc, + Compiler_clang, + Compiler_COUNT, +} +Compiler; + +//////////////////////////////// +//~ rjf: Text 2D Coordinates & Ranges + +typedef struct TxtPt TxtPt; +struct TxtPt +{ + S64 line; + S64 column; +}; + +typedef struct TxtRng TxtRng; +struct TxtRng +{ + TxtPt min; + TxtPt max; +}; + +//////////////////////////////// +//~ NOTE(allen): Constants + +global U32 sign32 = 0x80000000; +global U32 exponent32 = 0x7F800000; +global U32 mantissa32 = 0x007FFFFF; + +global F32 big_golden32 = 1.61803398875f; +global F32 small_golden32 = 0.61803398875f; + +global F32 pi32 = 3.1415926535897f; + +global F64 machine_epsilon64 = 4.94065645841247e-324; + +global U64 max_U64 = 0xffffffffffffffffull; +global U32 max_U32 = 0xffffffff; +global U16 max_U16 = 0xffff; +global U8 max_U8 = 0xff; + +global S64 max_S64 = (S64)0x7fffffffffffffffull; +global S32 max_S32 = (S32)0x7fffffff; +global S16 max_S16 = (S16)0x7fff; +global S8 max_S8 = (S8)0x7f; + +global S64 min_S64 = (S64)0xffffffffffffffffull; +global S32 min_S32 = (S32)0xffffffff; +global S16 min_S16 = (S16)0xffff; +global S8 min_S8 = (S8)0xff; + +global const U32 bitmask1 = 0x00000001; +global const U32 bitmask2 = 0x00000003; +global const U32 bitmask3 = 0x00000007; +global const U32 bitmask4 = 0x0000000f; +global const U32 bitmask5 = 0x0000001f; +global const U32 bitmask6 = 0x0000003f; +global const U32 bitmask7 = 0x0000007f; +global const U32 bitmask8 = 0x000000ff; +global const U32 bitmask9 = 0x000001ff; +global const U32 bitmask10 = 0x000003ff; +global const U32 bitmask11 = 0x000007ff; +global const U32 bitmask12 = 0x00000fff; +global const U32 bitmask13 = 0x00001fff; +global const U32 bitmask14 = 0x00003fff; +global const U32 bitmask15 = 0x00007fff; +global const U32 bitmask16 = 0x0000ffff; +global const U32 bitmask17 = 0x0001ffff; +global const U32 bitmask18 = 0x0003ffff; +global const U32 bitmask19 = 0x0007ffff; +global const U32 bitmask20 = 0x000fffff; +global const U32 bitmask21 = 0x001fffff; +global const U32 bitmask22 = 0x003fffff; +global const U32 bitmask23 = 0x007fffff; +global const U32 bitmask24 = 0x00ffffff; +global const U32 bitmask25 = 0x01ffffff; +global const U32 bitmask26 = 0x03ffffff; +global const U32 bitmask27 = 0x07ffffff; +global const U32 bitmask28 = 0x0fffffff; +global const U32 bitmask29 = 0x1fffffff; +global const U32 bitmask30 = 0x3fffffff; +global const U32 bitmask31 = 0x7fffffff; +global const U32 bitmask32 = 0xffffffff; + +global const U64 bitmask33 = 0x00000001ffffffffull; +global const U64 bitmask34 = 0x00000003ffffffffull; +global const U64 bitmask35 = 0x00000007ffffffffull; +global const U64 bitmask36 = 0x0000000fffffffffull; +global const U64 bitmask37 = 0x0000001fffffffffull; +global const U64 bitmask38 = 0x0000003fffffffffull; +global const U64 bitmask39 = 0x0000007fffffffffull; +global const U64 bitmask40 = 0x000000ffffffffffull; +global const U64 bitmask41 = 0x000001ffffffffffull; +global const U64 bitmask42 = 0x000003ffffffffffull; +global const U64 bitmask43 = 0x000007ffffffffffull; +global const U64 bitmask44 = 0x00000fffffffffffull; +global const U64 bitmask45 = 0x00001fffffffffffull; +global const U64 bitmask46 = 0x00003fffffffffffull; +global const U64 bitmask47 = 0x00007fffffffffffull; +global const U64 bitmask48 = 0x0000ffffffffffffull; +global const U64 bitmask49 = 0x0001ffffffffffffull; +global const U64 bitmask50 = 0x0003ffffffffffffull; +global const U64 bitmask51 = 0x0007ffffffffffffull; +global const U64 bitmask52 = 0x000fffffffffffffull; +global const U64 bitmask53 = 0x001fffffffffffffull; +global const U64 bitmask54 = 0x003fffffffffffffull; +global const U64 bitmask55 = 0x007fffffffffffffull; +global const U64 bitmask56 = 0x00ffffffffffffffull; +global const U64 bitmask57 = 0x01ffffffffffffffull; +global const U64 bitmask58 = 0x03ffffffffffffffull; +global const U64 bitmask59 = 0x07ffffffffffffffull; +global const U64 bitmask60 = 0x0fffffffffffffffull; +global const U64 bitmask61 = 0x1fffffffffffffffull; +global const U64 bitmask62 = 0x3fffffffffffffffull; +global const U64 bitmask63 = 0x7fffffffffffffffull; +global const U64 bitmask64 = 0xffffffffffffffffull; + +global const U32 bit1 = (1<<0); +global const U32 bit2 = (1<<1); +global const U32 bit3 = (1<<2); +global const U32 bit4 = (1<<3); +global const U32 bit5 = (1<<4); +global const U32 bit6 = (1<<5); +global const U32 bit7 = (1<<6); +global const U32 bit8 = (1<<7); +global const U32 bit9 = (1<<8); +global const U32 bit10 = (1<<9); +global const U32 bit11 = (1<<10); +global const U32 bit12 = (1<<11); +global const U32 bit13 = (1<<12); +global const U32 bit14 = (1<<13); +global const U32 bit15 = (1<<14); +global const U32 bit16 = (1<<15); +global const U32 bit17 = (1<<16); +global const U32 bit18 = (1<<17); +global const U32 bit19 = (1<<18); +global const U32 bit20 = (1<<19); +global const U32 bit21 = (1<<20); +global const U32 bit22 = (1<<21); +global const U32 bit23 = (1<<22); +global const U32 bit24 = (1<<23); +global const U32 bit25 = (1<<24); +global const U32 bit26 = (1<<25); +global const U32 bit27 = (1<<26); +global const U32 bit28 = (1<<27); +global const U32 bit29 = (1<<28); +global const U32 bit30 = (1<<29); +global const U32 bit31 = (1<<30); +global const U32 bit32 = (1<<31); + +global const U64 bit33 = (1ull<<32); +global const U64 bit34 = (1ull<<33); +global const U64 bit35 = (1ull<<34); +global const U64 bit36 = (1ull<<35); +global const U64 bit37 = (1ull<<36); +global const U64 bit38 = (1ull<<37); +global const U64 bit39 = (1ull<<38); +global const U64 bit40 = (1ull<<39); +global const U64 bit41 = (1ull<<40); +global const U64 bit42 = (1ull<<41); +global const U64 bit43 = (1ull<<42); +global const U64 bit44 = (1ull<<43); +global const U64 bit45 = (1ull<<44); +global const U64 bit46 = (1ull<<45); +global const U64 bit47 = (1ull<<46); +global const U64 bit48 = (1ull<<47); +global const U64 bit49 = (1ull<<48); +global const U64 bit50 = (1ull<<49); +global const U64 bit51 = (1ull<<50); +global const U64 bit52 = (1ull<<51); +global const U64 bit53 = (1ull<<52); +global const U64 bit54 = (1ull<<53); +global const U64 bit55 = (1ull<<54); +global const U64 bit56 = (1ull<<55); +global const U64 bit57 = (1ull<<56); +global const U64 bit58 = (1ull<<57); +global const U64 bit59 = (1ull<<58); +global const U64 bit60 = (1ull<<59); +global const U64 bit61 = (1ull<<60); +global const U64 bit62 = (1ull<<61); +global const U64 bit63 = (1ull<<62); +global const U64 bit64 = (1ull<<63); + +//////////////////////////////// +//~ allen: Time + +typedef enum WeekDay +{ + WeekDay_Sun, + WeekDay_Mon, + WeekDay_Tue, + WeekDay_Wed, + WeekDay_Thu, + WeekDay_Fri, + WeekDay_Sat, + WeekDay_COUNT, +} +WeekDay; + +typedef enum Month +{ + Month_Jan, + Month_Feb, + Month_Mar, + Month_Apr, + Month_May, + Month_Jun, + Month_Jul, + Month_Aug, + Month_Sep, + Month_Oct, + Month_Nov, + Month_Dec, + Month_COUNT, +} +Month; + +typedef struct DateTime DateTime; +struct DateTime +{ + U16 micro_sec; // [0,999] + U16 msec; // [0,999] + U16 sec; // [0,60] + U16 min; // [0,59] + U16 hour; // [0,24] + U16 day; // [0,30] + union + { + WeekDay week_day; + U32 wday; + }; + union + { + Month month; + U32 mon; + }; + U32 year; // 1 = 1 CE, 0 = 1 BC +}; + +typedef U64 DenseTime; + +//////////////////////////////// +//~ allen: Files + +typedef U32 FilePropertyFlags; +enum +{ + FilePropertyFlag_IsFolder = (1 << 0), +}; + +typedef struct FileProperties FileProperties; +struct FileProperties +{ + U64 size; + DenseTime modified; + DenseTime created; + FilePropertyFlags flags; +}; + +//////////////////////////////// +//~ rjf: Safe Casts + +internal U16 safe_cast_u16(U32 x); +internal U32 safe_cast_u32(U64 x); +internal S32 safe_cast_s32(S64 x); + +//////////////////////////////// +//~ rjf: Large Base Type Functions + +internal U128 u128_zero(void); +internal U128 u128_make(U64 v0, U64 v1); +internal B32 u128_match(U128 a, U128 b); + +//////////////////////////////// +//~ rjf: Bit Patterns + +internal U32 u32_from_u64_saturate(U64 x); +internal U64 u64_up_to_pow2(U64 x); +internal S32 extend_sign32(U32 x, U32 size); +internal S64 extend_sign64(U64 x, U64 size); + +internal F32 inf32(void); +internal F32 neg_inf32(void); + +internal U16 bswap_u16(U16 x); +internal U32 bswap_u32(U32 x); +internal U64 bswap_u64(U64 x); + +internal U64 count_bits_set16(U16 val); +internal U64 count_bits_set32(U32 val); +internal U64 count_bits_set64(U64 val); + +internal U64 ctz32(U32 val); +internal U64 ctz64(U64 val); +internal U64 clz32(U32 val); +internal U64 clz64(U64 val); + +//////////////////////////////// +//~ rjf: Enum -> Sign + +internal S32 sign_from_side_S32(Side side); +internal F32 sign_from_side_F32(Side side); + +//////////////////////////////// +//~ rjf: Memory Functions + +internal B32 memory_is_zero(void *ptr, U64 size); + +//////////////////////////////// +//~ rjf: Text 2D Coordinate/Range Functions + +internal TxtPt txt_pt(S64 line, S64 column); +internal B32 txt_pt_match(TxtPt a, TxtPt b); +internal B32 txt_pt_less_than(TxtPt a, TxtPt b); +internal TxtPt txt_pt_min(TxtPt a, TxtPt b); +internal TxtPt txt_pt_max(TxtPt a, TxtPt b); +internal TxtRng txt_rng(TxtPt min, TxtPt max); +internal TxtRng txt_rng_intersect(TxtRng a, TxtRng b); +internal TxtRng txt_rng_union(TxtRng a, TxtRng b); +internal B32 txt_rng_contains(TxtRng r, TxtPt pt); + +//////////////////////////////// +//~ rjf: Toolchain/Environment Enum Functions + +internal U64 bit_size_from_arch(Architecture arch); +internal U64 max_instruction_size_from_arch(Architecture arch); + +internal OperatingSystem operating_system_from_context(void); +internal Architecture architecture_from_context(void); +internal Compiler compiler_from_context(void); + +//////////////////////////////// +//~ rjf: Time Functions + +internal DenseTime dense_time_from_date_time(DateTime date_time); +internal DateTime date_time_from_dense_time(DenseTime time); +internal DateTime date_time_from_micro_seconds(U64 time); + +//////////////////////////////// +//~ rjf: Non-Fancy Ring Buffer Reads/Writes + +internal U64 ring_write(U8 *ring_base, U64 ring_size, U64 ring_pos, void *src_data, U64 src_data_size); +internal U64 ring_read(U8 *ring_base, U64 ring_size, U64 ring_pos, void *dst_data, U64 read_size); +#define ring_write_struct(ring_base, ring_size, ring_pos, ptr) ring_write((ring_base), (ring_size), (ring_pos), (ptr), sizeof(*(ptr))) +#define ring_read_struct(ring_base, ring_size, ring_pos, ptr) ring_read((ring_base), (ring_size), (ring_pos), (ptr), sizeof(*(ptr))) + +//////////////////////////////// +//~ rjf: Sorts + +#define quick_sort(ptr, count, element_size, cmp_function) qsort((ptr), (count), (element_size), (int (*)(const void *, const void *))(cmp_function)) + +#endif // BASE_CORE_H diff --git a/metagen/metagen_base/metagen_base_entry_point.c b/metagen/metagen_base/metagen_base_entry_point.c new file mode 100644 index 0000000..b024554 --- /dev/null +++ b/metagen/metagen_base/metagen_base_entry_point.c @@ -0,0 +1,92 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal void +main_thread_base_entry_point(void (*entry_point)(CmdLine *cmdline), char **arguments, U64 arguments_count) +{ +#if PROFILE_TELEMETRY + local_persist U8 tm_data[MB(64)]; + tmLoadLibrary(TM_RELEASE); + tmSetMaxThreadCount(256); + tmInitialize(sizeof(tm_data), (char *)tm_data); +#endif + ThreadNameF("[main thread]"); + Temp scratch = scratch_begin(0, 0); + String8List command_line_argument_strings = os_string_list_from_argcv(scratch.arena, (int)arguments_count, arguments); + CmdLine cmdline = cmd_line_from_string_list(scratch.arena, command_line_argument_strings); + B32 capture = cmd_line_has_flag(&cmdline, str8_lit("capture")); + if(capture) + { + ProfBeginCapture(arguments[0]); + } +#if defined(TASK_SYSTEM_H) && !defined(TS_INIT_MANUAL) + ts_init(); +#endif +#if defined(HASH_STORE_H) && !defined(HS_INIT_MANUAL) + hs_init(); +#endif +#if defined(FILE_STREAM_H) && !defined(FS_INIT_MANUAL) + fs_init(); +#endif +#if defined(TEXT_CACHE_H) && !defined(TXT_INIT_MANUAL) + txt_init(); +#endif +#if defined(MUTABLE_TEXT_H) && !defined(MTX_INIT_MANUAL) + mtx_init(); +#endif +#if defined(DASM_CACHE_H) && !defined(DASM_INIT_MANUAL) + dasm_init(); +#endif +#if defined(DI_H) && !defined(DI_INIT_MANUAL) + di_init(); +#endif +#if defined(FUZZY_SEARCH_H) && !defined(FZY_INIT_MANUAL) + fzy_init(); +#endif +#if defined(DEMON_CORE_H) && !defined(DMN_INIT_MANUAL) + dmn_init(); +#endif +#if defined(CTRL_CORE_H) && !defined(CTRL_INIT_MANUAL) + ctrl_init(); +#endif +#if defined(OS_GRAPHICAL_H) && !defined(OS_GFX_INIT_MANUAL) + os_gfx_init(); +#endif +#if defined(FONT_PROVIDER_H) && !defined(FP_INIT_MANUAL) + fp_init(); +#endif +#if defined(RENDER_CORE_H) && !defined(R_INIT_MANUAL) + r_init(&cmdline); +#endif +#if defined(TEXTURE_CACHE_H) && !defined(TEX_INIT_MANUAL) + tex_init(); +#endif +#if defined(GEO_CACHE_H) && !defined(GEO_INIT_MANUAL) + geo_init(); +#endif +#if defined(FONT_CACHE_H) && !defined(F_INIT_MANUAL) + f_init(); +#endif +#if defined(DF_CORE_H) && !defined(DF_INIT_MANUAL) + DF_StateDeltaHistory *hist = df_state_delta_history_alloc(); + df_core_init(&cmdline, hist); +#endif +#if defined(DF_GFX_H) && !defined(DF_GFX_INIT_MANUAL) + df_gfx_init(update_and_render, df_state_delta_history()); +#endif + entry_point(&cmdline); + if(capture) + { + ProfEndCapture(); + } + scratch_end(scratch); +} + +internal void +supplement_thread_base_entry_point(void (*entry_point)(void *params), void *params) +{ + TCTX tctx; + tctx_init_and_equip(&tctx); + entry_point(params); + tctx_release(); +} diff --git a/metagen/metagen_base/metagen_base_entry_point.h b/metagen/metagen_base/metagen_base_entry_point.h new file mode 100644 index 0000000..fd6c0d9 --- /dev/null +++ b/metagen/metagen_base/metagen_base_entry_point.h @@ -0,0 +1,10 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_ENTRY_POINT_H +#define BASE_ENTRY_POINT_H + +internal void main_thread_base_entry_point(void (*entry_point)(CmdLine *cmdline), char **arguments, U64 arguments_count); +internal void supplement_thread_base_entry_point(void (*entry_point)(void *params), void *params); + +#endif // BASE_ENTRY_POINT_H diff --git a/metagen/metagen_base/metagen_base_inc.c b/metagen/metagen_base/metagen_base_inc.c new file mode 100644 index 0000000..2d034a9 --- /dev/null +++ b/metagen/metagen_base/metagen_base_inc.c @@ -0,0 +1,19 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Base Includes + +#undef RADDBG_LAYER_COLOR +#define RADDBG_LAYER_COLOR 0.20f, 0.60f, 0.80f + +#include "metagen_base_core.c" +#include "metagen_base_profile.c" +#include "metagen_base_arena.c" +#include "metagen_base_math.c" +#include "metagen_base_strings.c" +#include "metagen_base_thread_context.c" +#include "metagen_base_command_line.c" +#include "metagen_base_markup.c" +#include "metagen_base_log.c" +#include "metagen_base_entry_point.c" diff --git a/metagen/metagen_base/metagen_base_inc.h b/metagen/metagen_base/metagen_base_inc.h new file mode 100644 index 0000000..8d17b40 --- /dev/null +++ b/metagen/metagen_base/metagen_base_inc.h @@ -0,0 +1,23 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_INC_H +#define BASE_INC_H + +//////////////////////////////// +//~ rjf: Base Includes + +#include "metagen_base_context_cracking.h" + +#include "metagen_base_core.h" +#include "metagen_base_profile.h" +#include "metagen_base_arena.h" +#include "metagen_base_math.h" +#include "metagen_base_strings.h" +#include "metagen_base_thread_context.h" +#include "metagen_base_command_line.h" +#include "metagen_base_markup.h" +#include "metagen_base_log.h" +#include "metagen_base_entry_point.h" + +#endif // BASE_INC_H diff --git a/metagen/metagen_base/metagen_base_log.c b/metagen/metagen_base/metagen_base_log.c new file mode 100644 index 0000000..a5a917a --- /dev/null +++ b/metagen/metagen_base/metagen_base_log.c @@ -0,0 +1,103 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Globals/Thread-Locals + +C_LINKAGE thread_static Log *log_active; +#if !BUILD_SUPPLEMENTARY_UNIT +C_LINKAGE thread_static Log *log_active = 0; +#endif + +//////////////////////////////// +//~ rjf: Log Creation/Selection + +internal Log * +log_alloc(void) +{ + Arena *arena = arena_alloc(); + Log *log = push_array(arena, Log, 1); + log->arena = arena; + return log; +} + +internal void +log_release(Log *log) +{ + arena_release(log->arena); +} + +internal void +log_select(Log *log) +{ + log_active = log; +} + +//////////////////////////////// +//~ rjf: Log Building/Clearing + +internal void +log_msg(LogMsgKind kind, String8 string) +{ + if(log_active != 0 && log_active->top_scope != 0) + { + String8 string_copy = push_str8_copy(log_active->arena, string); + str8_list_push(log_active->arena, &log_active->top_scope->strings[kind], string_copy); + } +} + +internal void +log_msgf(LogMsgKind kind, char *fmt, ...) +{ + if(log_active != 0) + { + Temp scratch = scratch_begin(0, 0); + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(scratch.arena, fmt, args); + log_msg(kind, string); + va_end(args); + scratch_end(scratch); + } +} + +//////////////////////////////// +//~ rjf: Log Scopes + +internal void +log_scope_begin(void) +{ + if(log_active != 0) + { + U64 pos = arena_pos(log_active->arena); + LogScope *scope = push_array(log_active->arena, LogScope, 1); + scope->pos = pos; + SLLStackPush(log_active->top_scope, scope); + } +} + +internal LogScopeResult +log_scope_end(Arena *arena) +{ + LogScopeResult result = {0}; + if(log_active != 0) + { + LogScope *scope = log_active->top_scope; + if(scope != 0) + { + SLLStackPop(log_active->top_scope); + if(arena != 0) + { + for(EachEnumVal(LogMsgKind, kind)) + { + Temp scratch = scratch_begin(&arena, 1); + String8 result_unindented = str8_list_join(scratch.arena, &scope->strings[kind], 0); + result.strings[kind] = indented_from_string(arena, result_unindented); + scratch_end(scratch); + } + } + arena_pop_to(log_active->arena, scope->pos); + } + } + return result; +} diff --git a/metagen/metagen_base/metagen_base_log.h b/metagen/metagen_base/metagen_base_log.h new file mode 100644 index 0000000..52a297d --- /dev/null +++ b/metagen/metagen_base/metagen_base_log.h @@ -0,0 +1,65 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_LOG_H +#define BASE_LOG_H + +//////////////////////////////// +//~ rjf: Log Types + +typedef enum LogMsgKind +{ + LogMsgKind_Info, + LogMsgKind_UserError, + LogMsgKind_COUNT +} +LogMsgKind; + +typedef struct LogScope LogScope; +struct LogScope +{ + LogScope *next; + U64 pos; + String8List strings[LogMsgKind_COUNT]; +}; + +typedef struct LogScopeResult LogScopeResult; +struct LogScopeResult +{ + String8 strings[LogMsgKind_COUNT]; +}; + +typedef struct Log Log; +struct Log +{ + Arena *arena; + LogScope *top_scope; +}; + +//////////////////////////////// +//~ rjf: Log Creation/Selection + +internal Log *log_alloc(void); +internal void log_release(Log *log); +internal void log_select(Log *log); + +//////////////////////////////// +//~ rjf: Log Building + +internal void log_msg(LogMsgKind kind, String8 string); +internal void log_msgf(LogMsgKind kind, char *fmt, ...); +#define log_info(s) log_msg(LogMsgKind_Info, (s)) +#define log_infof(fmt, ...) log_msgf(LogMsgKind_Info, (fmt), __VA_ARGS__) +#define log_user_error(s) log_msg(LogMsgKind_UserError, (s)) +#define log_user_errorf(fmt, ...) log_msgf(LogMsgKind_UserError, (fmt), __VA_ARGS__) + +#define LogInfoNamedBlock(s) DeferLoop(log_infof("%S:\n{\n", (s)), log_infof("}\n")) +#define LogInfoNamedBlockF(fmt, ...) DeferLoop((log_infof(fmt, __VA_ARGS__), log_infof(":\n{\n")), log_infof("}\n")) + +//////////////////////////////// +//~ rjf: Log Scopes + +internal void log_scope_begin(void); +internal LogScopeResult log_scope_end(Arena *arena); + +#endif // BASE_LOG_H diff --git a/metagen/metagen_base/metagen_base_markup.c b/metagen/metagen_base/metagen_base_markup.c new file mode 100644 index 0000000..033fb49 --- /dev/null +++ b/metagen/metagen_base/metagen_base_markup.c @@ -0,0 +1,21 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +internal void +set_thread_name(String8 string) +{ + ProfThreadName("%.*s", str8_varg(string)); + os_set_thread_name(string); +} + +internal void +set_thread_namef(char *fmt, ...) +{ + Temp scratch = scratch_begin(0, 0); + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(scratch.arena, fmt, args); + set_thread_name(string); + va_end(args); + scratch_end(scratch); +} diff --git a/metagen/metagen_base/metagen_base_markup.h b/metagen/metagen_base/metagen_base_markup.h new file mode 100644 index 0000000..0f255ef --- /dev/null +++ b/metagen/metagen_base/metagen_base_markup.h @@ -0,0 +1,12 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_MARKUP_H +#define BASE_MARKUP_H + +internal void set_thread_name(String8 string); +internal void set_thread_namef(char *fmt, ...); +#define ThreadNameF(...) (set_thread_namef(__VA_ARGS__)) +#define ThreadName(str) (set_thread_name(str)) + +#endif // BASE_MARKUP_H diff --git a/metagen/metagen_base/metagen_base_math.c b/metagen/metagen_base/metagen_base_math.c new file mode 100644 index 0000000..5585443 --- /dev/null +++ b/metagen/metagen_base/metagen_base_math.c @@ -0,0 +1,616 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Scalar Ops + +internal F32 +mix_1f32(F32 a, F32 b, F32 t) +{ + F32 c = (a + (b-a) * Clamp(0.f, t, 1.f)); + return c; +} + +internal F64 +mix_1f64(F64 a, F64 b, F64 t) +{ + F64 c = (a + (b-a) * Clamp(0.0, t, 1.0)); + return c; +} + +//////////////////////////////// +//~ rjf: Vector Ops + +internal Vec2F32 vec_2f32(F32 x, F32 y) {Vec2F32 v = {x, y}; return v;} +internal Vec2F32 add_2f32(Vec2F32 a, Vec2F32 b) {Vec2F32 c = {a.x+b.x, a.y+b.y}; return c;} +internal Vec2F32 sub_2f32(Vec2F32 a, Vec2F32 b) {Vec2F32 c = {a.x-b.x, a.y-b.y}; return c;} +internal Vec2F32 mul_2f32(Vec2F32 a, Vec2F32 b) {Vec2F32 c = {a.x*b.x, a.y*b.y}; return c;} +internal Vec2F32 div_2f32(Vec2F32 a, Vec2F32 b) {Vec2F32 c = {a.x/b.x, a.y/b.y}; return c;} +internal Vec2F32 scale_2f32(Vec2F32 v, F32 s) {Vec2F32 c = {v.x*s, v.y*s}; return c;} +internal F32 dot_2f32(Vec2F32 a, Vec2F32 b) {F32 c = a.x*b.x + a.y*b.y; return c;} +internal F32 length_squared_2f32(Vec2F32 v) {F32 c = v.x*v.x + v.y*v.y; return c;} +internal F32 length_2f32(Vec2F32 v) {F32 c = sqrt_f32(v.x*v.x + v.y*v.y); return c;} +internal Vec2F32 normalize_2f32(Vec2F32 v) {v = scale_2f32(v, 1.f/length_2f32(v)); return v;} +internal Vec2F32 mix_2f32(Vec2F32 a, Vec2F32 b, F32 t) {Vec2F32 c = {mix_1f32(a.x, b.x, t), mix_1f32(a.y, b.y, t)}; return c;} + +internal Vec2S64 vec_2s64(S64 x, S64 y) {Vec2S64 v = {x, y}; return v;} +internal Vec2S64 add_2s64(Vec2S64 a, Vec2S64 b) {Vec2S64 c = {a.x+b.x, a.y+b.y}; return c;} +internal Vec2S64 sub_2s64(Vec2S64 a, Vec2S64 b) {Vec2S64 c = {a.x-b.x, a.y-b.y}; return c;} +internal Vec2S64 mul_2s64(Vec2S64 a, Vec2S64 b) {Vec2S64 c = {a.x*b.x, a.y*b.y}; return c;} +internal Vec2S64 div_2s64(Vec2S64 a, Vec2S64 b) {Vec2S64 c = {a.x/b.x, a.y/b.y}; return c;} +internal Vec2S64 scale_2s64(Vec2S64 v, S64 s) {Vec2S64 c = {v.x*s, v.y*s}; return c;} +internal S64 dot_2s64(Vec2S64 a, Vec2S64 b) {S64 c = a.x*b.x + a.y*b.y; return c;} +internal S64 length_squared_2s64(Vec2S64 v) {S64 c = v.x*v.x + v.y*v.y; return c;} +internal S64 length_2s64(Vec2S64 v) {S64 c = (S64)sqrt_f64((F64)(v.x*v.x + v.y*v.y)); return c;} +internal Vec2S64 normalize_2s64(Vec2S64 v) {v = scale_2s64(v, (S64)(1.f/length_2s64(v))); return v;} +internal Vec2S64 mix_2s64(Vec2S64 a, Vec2S64 b, F32 t) {Vec2S64 c = {(S64)mix_1f32((F32)a.x, (F32)b.x, t), (S64)mix_1f32((F32)a.y, (F32)b.y, t)}; return c;} + +internal Vec2S32 vec_2s32(S32 x, S32 y) {Vec2S32 v = {x, y}; return v;} +internal Vec2S32 add_2s32(Vec2S32 a, Vec2S32 b) {Vec2S32 c = {a.x+b.x, a.y+b.y}; return c;} +internal Vec2S32 sub_2s32(Vec2S32 a, Vec2S32 b) {Vec2S32 c = {a.x-b.x, a.y-b.y}; return c;} +internal Vec2S32 mul_2s32(Vec2S32 a, Vec2S32 b) {Vec2S32 c = {a.x*b.x, a.y*b.y}; return c;} +internal Vec2S32 div_2s32(Vec2S32 a, Vec2S32 b) {Vec2S32 c = {a.x/b.x, a.y/b.y}; return c;} +internal Vec2S32 scale_2s32(Vec2S32 v, S32 s) {Vec2S32 c = {v.x*s, v.y*s}; return c;} +internal S32 dot_2s32(Vec2S32 a, Vec2S32 b) {S32 c = a.x*b.x + a.y*b.y; return c;} +internal S32 length_squared_2s32(Vec2S32 v) {S32 c = v.x*v.x + v.y*v.y; return c;} +internal S32 length_2s32(Vec2S32 v) {S32 c = (S32)sqrt_f32((F32)v.x*(F32)v.x + (F32)v.y*(F32)v.y); return c;} +internal Vec2S32 normalize_2s32(Vec2S32 v) {v = scale_2s32(v, (S32)(1.f/length_2s32(v))); return v;} +internal Vec2S32 mix_2s32(Vec2S32 a, Vec2S32 b, F32 t) {Vec2S32 c = {(S32)mix_1f32((F32)a.x, (F32)b.x, t), (S32)mix_1f32((F32)a.y, (F32)b.y, t)}; return c;} + +internal Vec2S16 vec_2s16(S16 x, S16 y) {Vec2S16 v = {x, y}; return v;} +internal Vec2S16 add_2s16(Vec2S16 a, Vec2S16 b) {Vec2S16 c = {(S16)(a.x+b.x), (S16)(a.y+b.y)}; return c;} +internal Vec2S16 sub_2s16(Vec2S16 a, Vec2S16 b) {Vec2S16 c = {(S16)(a.x-b.x), (S16)(a.y-b.y)}; return c;} +internal Vec2S16 mul_2s16(Vec2S16 a, Vec2S16 b) {Vec2S16 c = {(S16)(a.x*b.x), (S16)(a.y*b.y)}; return c;} +internal Vec2S16 div_2s16(Vec2S16 a, Vec2S16 b) {Vec2S16 c = {(S16)(a.x/b.x), (S16)(a.y/b.y)}; return c;} +internal Vec2S16 scale_2s16(Vec2S16 v, S16 s) {Vec2S16 c = {(S16)(v.x*s), (S16)(v.y*s)}; return c;} +internal S16 dot_2s16(Vec2S16 a, Vec2S16 b) {S16 c = a.x*b.x + a.y*b.y; return c;} +internal S16 length_squared_2s16(Vec2S16 v) {S16 c = v.x*v.x + v.y*v.y; return c;} +internal S16 length_2s16(Vec2S16 v) {S16 c = (S16)sqrt_f32((F32)(v.x*v.x + v.y*v.y)); return c;} +internal Vec2S16 normalize_2s16(Vec2S16 v) {v = scale_2s16(v, (S16)(1.f/length_2s16(v))); return v;} +internal Vec2S16 mix_2s16(Vec2S16 a, Vec2S16 b, F32 t) {Vec2S16 c = {(S16)mix_1f32((F32)a.x, (F32)b.x, t), (S16)mix_1f32((F32)a.y, (F32)b.y, t)}; return c;} + +internal Vec3F32 vec_3f32(F32 x, F32 y, F32 z) {Vec3F32 v = {x, y, z}; return v;} +internal Vec3F32 add_3f32(Vec3F32 a, Vec3F32 b) {Vec3F32 c = {a.x+b.x, a.y+b.y, a.z+b.z}; return c;} +internal Vec3F32 sub_3f32(Vec3F32 a, Vec3F32 b) {Vec3F32 c = {a.x-b.x, a.y-b.y, a.z-b.z}; return c;} +internal Vec3F32 mul_3f32(Vec3F32 a, Vec3F32 b) {Vec3F32 c = {a.x*b.x, a.y*b.y, a.z*b.z}; return c;} +internal Vec3F32 div_3f32(Vec3F32 a, Vec3F32 b) {Vec3F32 c = {a.x/b.x, a.y/b.y, a.z/b.z}; return c;} +internal Vec3F32 scale_3f32(Vec3F32 v, F32 s) {Vec3F32 c = {v.x*s, v.y*s, v.z*s}; return c;} +internal F32 dot_3f32(Vec3F32 a, Vec3F32 b) {F32 c = a.x*b.x + a.y*b.y + a.z*b.z; return c;} +internal F32 length_squared_3f32(Vec3F32 v) {F32 c = v.x*v.x + v.y*v.y + v.z*v.z; return c;} +internal F32 length_3f32(Vec3F32 v) {F32 c = sqrt_f32(v.x*v.x + v.y*v.y + v.z*v.z); return c;} +internal Vec3F32 normalize_3f32(Vec3F32 v) {v = scale_3f32(v, 1.f/length_3f32(v)); return v;} +internal Vec3F32 mix_3f32(Vec3F32 a, Vec3F32 b, F32 t) {Vec3F32 c = {mix_1f32(a.x, b.x, t), mix_1f32(a.y, b.y, t), mix_1f32(a.z, b.z, t)}; return c;} +internal Vec3F32 cross_3f32(Vec3F32 a, Vec3F32 b) {Vec3F32 c = {a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x}; return c;} + +internal Vec3S32 vec_3s32(S32 x, S32 y, S32 z) {Vec3S32 v = {x, y, z}; return v;} +internal Vec3S32 add_3s32(Vec3S32 a, Vec3S32 b) {Vec3S32 c = {a.x+b.x, a.y+b.y, a.z+b.z}; return c;} +internal Vec3S32 sub_3s32(Vec3S32 a, Vec3S32 b) {Vec3S32 c = {a.x-b.x, a.y-b.y, a.z-b.z}; return c;} +internal Vec3S32 mul_3s32(Vec3S32 a, Vec3S32 b) {Vec3S32 c = {a.x*b.x, a.y*b.y, a.z*b.z}; return c;} +internal Vec3S32 div_3s32(Vec3S32 a, Vec3S32 b) {Vec3S32 c = {a.x/b.x, a.y/b.y, a.z/b.z}; return c;} +internal Vec3S32 scale_3s32(Vec3S32 v, S32 s) {Vec3S32 c = {v.x*s, v.y*s, v.z*s}; return c;} +internal S32 dot_3s32(Vec3S32 a, Vec3S32 b) {S32 c = a.x*b.x + a.y*b.y + a.z*b.z; return c;} +internal S32 length_squared_3s32(Vec3S32 v) {S32 c = v.x*v.x + v.y*v.y + v.z*v.z; return c;} +internal S32 length_3s32(Vec3S32 v) {S32 c = (S32)sqrt_f32((F32)(v.x*v.x + v.y*v.y + v.z*v.z)); return c;} +internal Vec3S32 normalize_3s32(Vec3S32 v) {v = scale_3s32(v, (S32)(1.f/length_3s32(v))); return v;} +internal Vec3S32 mix_3s32(Vec3S32 a, Vec3S32 b, F32 t) {Vec3S32 c = {(S32)mix_1f32((F32)a.x, (F32)b.x, t), (S32)mix_1f32((F32)a.y, (F32)b.y, t), (S32)mix_1f32((F32)a.z, (F32)b.z, t)}; return c;} +internal Vec3S32 cross_3s32(Vec3S32 a, Vec3S32 b) {Vec3S32 c = {a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x}; return c;} + +internal Vec4F32 vec_4f32(F32 x, F32 y, F32 z, F32 w) {Vec4F32 v = {x, y, z, w}; return v;} +internal Vec4F32 add_4f32(Vec4F32 a, Vec4F32 b) {Vec4F32 c = {a.x+b.x, a.y+b.y, a.z+b.z, a.w+b.w}; return c;} +internal Vec4F32 sub_4f32(Vec4F32 a, Vec4F32 b) {Vec4F32 c = {a.x-b.x, a.y-b.y, a.z-b.z, a.w-b.w}; return c;} +internal Vec4F32 mul_4f32(Vec4F32 a, Vec4F32 b) {Vec4F32 c = {a.x*b.x, a.y*b.y, a.z*b.z, a.w*b.w}; return c;} +internal Vec4F32 div_4f32(Vec4F32 a, Vec4F32 b) {Vec4F32 c = {a.x/b.x, a.y/b.y, a.z/b.z, a.w/b.w}; return c;} +internal Vec4F32 scale_4f32(Vec4F32 v, F32 s) {Vec4F32 c = {v.x*s, v.y*s, v.z*s, v.w*s}; return c;} +internal F32 dot_4f32(Vec4F32 a, Vec4F32 b) {F32 c = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; return c;} +internal F32 length_squared_4f32(Vec4F32 v) {F32 c = v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w; return c;} +internal F32 length_4f32(Vec4F32 v) {F32 c = sqrt_f32(v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w); return c;} +internal Vec4F32 normalize_4f32(Vec4F32 v) {v = scale_4f32(v, 1.f/length_4f32(v)); return v;} +internal Vec4F32 mix_4f32(Vec4F32 a, Vec4F32 b, F32 t) {Vec4F32 c = {mix_1f32(a.x, b.x, t), mix_1f32(a.y, b.y, t), mix_1f32(a.z, b.z, t), mix_1f32(a.w, b.w, t)}; return c;} + +internal Vec4S32 vec_4s32(S32 x, S32 y, S32 z, S32 w) {Vec4S32 v = {x, y, z, w}; return v;} +internal Vec4S32 add_4s32(Vec4S32 a, Vec4S32 b) {Vec4S32 c = {a.x+b.x, a.y+b.y, a.z+b.z, a.w+b.w}; return c;} +internal Vec4S32 sub_4s32(Vec4S32 a, Vec4S32 b) {Vec4S32 c = {a.x-b.x, a.y-b.y, a.z-b.z, a.w-b.w}; return c;} +internal Vec4S32 mul_4s32(Vec4S32 a, Vec4S32 b) {Vec4S32 c = {a.x*b.x, a.y*b.y, a.z*b.z, a.w*b.w}; return c;} +internal Vec4S32 div_4s32(Vec4S32 a, Vec4S32 b) {Vec4S32 c = {a.x/b.x, a.y/b.y, a.z/b.z, a.w/b.w}; return c;} +internal Vec4S32 scale_4s32(Vec4S32 v, S32 s) {Vec4S32 c = {v.x*s, v.y*s, v.z*s, v.w*s}; return c;} +internal S32 dot_4s32(Vec4S32 a, Vec4S32 b) {S32 c = a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w; return c;} +internal S32 length_squared_4s32(Vec4S32 v) {S32 c = v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w; return c;} +internal S32 length_4s32(Vec4S32 v) {S32 c = (S32)sqrt_f32((F32)(v.x*v.x + v.y*v.y + v.z*v.z + v.w*v.w)); return c;} +internal Vec4S32 normalize_4s32(Vec4S32 v) {v = scale_4s32(v, (S32)(1.f/length_4s32(v))); return v;} +internal Vec4S32 mix_4s32(Vec4S32 a, Vec4S32 b, F32 t) {Vec4S32 c = {(S32)mix_1f32((F32)a.x, (F32)b.x, t), (S32)mix_1f32((F32)a.y, (F32)b.y, t), (S32)mix_1f32((F32)a.z, (F32)b.z, t), (S32)mix_1f32((F32)a.w, (F32)b.w, t)}; return c;} + +//////////////////////////////// +//~ rjf: Matrix Ops + +internal Mat3x3F32 +mat_3x3f32(F32 diagonal) +{ + Mat3x3F32 result = {0}; + result.v[0][0] = diagonal; + result.v[1][1] = diagonal; + result.v[2][2] = diagonal; + return result; +} + +internal Mat3x3F32 +make_translate_3x3f32(Vec2F32 delta) +{ + Mat3x3F32 mat = mat_3x3f32(1.f); + mat.v[2][0] = delta.x; + mat.v[2][1] = delta.y; + return mat; +} + +internal Mat3x3F32 +make_scale_3x3f32(Vec2F32 scale) +{ + Mat3x3F32 mat = mat_3x3f32(1.f); + mat.v[0][0] = scale.x; + mat.v[1][1] = scale.y; + return mat; +} + +internal Mat3x3F32 +mul_3x3f32(Mat3x3F32 a, Mat3x3F32 b) +{ + Mat3x3F32 c = {0}; + for(int j = 0; j < 3; j += 1) + { + for(int i = 0; i < 3; i += 1) + { + c.v[i][j] = (a.v[0][j]*b.v[i][0] + + a.v[1][j]*b.v[i][1] + + a.v[2][j]*b.v[i][2]); + } + } + return c; +} + +internal Mat4x4F32 +mat_4x4f32(F32 diagonal) +{ + Mat4x4F32 result = {0}; + result.v[0][0] = diagonal; + result.v[1][1] = diagonal; + result.v[2][2] = diagonal; + result.v[3][3] = diagonal; + return result; +} + +internal Mat4x4F32 +make_translate_4x4f32(Vec3F32 delta) +{ + Mat4x4F32 result = mat_4x4f32(1.f); + result.v[3][0] = delta.x; + result.v[3][1] = delta.y; + result.v[3][2] = delta.z; + return result; +} + +internal Mat4x4F32 +make_scale_4x4f32(Vec3F32 scale) +{ + Mat4x4F32 result = mat_4x4f32(1.f); + result.v[0][0] = scale.x; + result.v[1][1] = scale.y; + result.v[2][2] = scale.z; + return result; +} + +internal Mat4x4F32 +make_perspective_4x4f32(F32 fov, F32 aspect_ratio, F32 near_z, F32 far_z) +{ + Mat4x4F32 result = mat_4x4f32(1.f); + F32 tan_theta_over_2 = tan_f32(fov / 2); + result.v[0][0] = 1.f / tan_theta_over_2; + result.v[1][1] = aspect_ratio / tan_theta_over_2; + result.v[2][3] = 1.f; + result.v[2][2] = -(near_z + far_z) / (near_z - far_z); + result.v[3][2] = (2.f * near_z * far_z) / (near_z - far_z); + result.v[3][3] = 0.f; + return result; +} + +internal Mat4x4F32 +make_orthographic_4x4f32(F32 left, F32 right, F32 bottom, F32 top, F32 near_z, F32 far_z) +{ + Mat4x4F32 result = mat_4x4f32(1.f); + + result.v[0][0] = 2.f / (right - left); + result.v[1][1] = 2.f / (top - bottom); + result.v[2][2] = 2.f / (far_z - near_z); + result.v[3][3] = 1.f; + + result.v[3][0] = (left + right) / (left - right); + result.v[3][1] = (bottom + top) / (bottom - top); + result.v[3][2] = (near_z + far_z) / (near_z - far_z); + + return result; +} + +internal Mat4x4F32 +make_look_at_4x4f32(Vec3F32 eye, Vec3F32 center, Vec3F32 up) +{ + Mat4x4F32 result; + Vec3F32 f = normalize_3f32(sub_3f32(eye, center)); + Vec3F32 s = normalize_3f32(cross_3f32(f, up)); + Vec3F32 u = cross_3f32(s, f); + result.v[0][0] = s.x; + result.v[0][1] = u.x; + result.v[0][2] = -f.x; + result.v[0][3] = 0.0f; + result.v[1][0] = s.y; + result.v[1][1] = u.y; + result.v[1][2] = -f.y; + result.v[1][3] = 0.0f; + result.v[2][0] = s.z; + result.v[2][1] = u.z; + result.v[2][2] = -f.z; + result.v[2][3] = 0.0f; + result.v[3][0] = -dot_3f32(s, eye); + result.v[3][1] = -dot_3f32(u, eye); + result.v[3][2] = dot_3f32(f, eye); + result.v[3][3] = 1.0f; + return result; +} + +internal Mat4x4F32 +make_rotate_4x4f32(Vec3F32 axis, F32 turns) +{ + Mat4x4F32 result = mat_4x4f32(1.f); + axis = normalize_3f32(axis); + F32 sin_theta = sin_f32(turns); + F32 cos_theta = cos_f32(turns); + F32 cos_value = 1.f - cos_theta; + result.v[0][0] = (axis.x * axis.x * cos_value) + cos_theta; + result.v[0][1] = (axis.x * axis.y * cos_value) + (axis.z * sin_theta); + result.v[0][2] = (axis.x * axis.z * cos_value) - (axis.y * sin_theta); + result.v[1][0] = (axis.y * axis.x * cos_value) - (axis.z * sin_theta); + result.v[1][1] = (axis.y * axis.y * cos_value) + cos_theta; + result.v[1][2] = (axis.y * axis.z * cos_value) + (axis.x * sin_theta); + result.v[2][0] = (axis.z * axis.x * cos_value) + (axis.y * sin_theta); + result.v[2][1] = (axis.z * axis.y * cos_value) - (axis.x * sin_theta); + result.v[2][2] = (axis.z * axis.z * cos_value) + cos_theta; + return result; +} + +internal Mat4x4F32 +mul_4x4f32(Mat4x4F32 a, Mat4x4F32 b) +{ + Mat4x4F32 c = {0}; + for(int j = 0; j < 4; j += 1) + { + for(int i = 0; i < 4; i += 1) + { + c.v[i][j] = (a.v[0][j]*b.v[i][0] + + a.v[1][j]*b.v[i][1] + + a.v[2][j]*b.v[i][2] + + a.v[3][j]*b.v[i][3]); + } + } + return c; +} + +internal Mat4x4F32 +scale_4x4f32(Mat4x4F32 m, F32 scale) +{ + for(int j = 0; j < 4; j += 1) + { + for(int i = 0; i < 4; i += 1) + { + m.v[i][j] *= scale; + } + } + return m; +} + +internal Mat4x4F32 +inverse_4x4f32(Mat4x4F32 m) +{ + F32 coef00 = m.v[2][2] * m.v[3][3] - m.v[3][2] * m.v[2][3]; + F32 coef02 = m.v[1][2] * m.v[3][3] - m.v[3][2] * m.v[1][3]; + F32 coef03 = m.v[1][2] * m.v[2][3] - m.v[2][2] * m.v[1][3]; + F32 coef04 = m.v[2][1] * m.v[3][3] - m.v[3][1] * m.v[2][3]; + F32 coef06 = m.v[1][1] * m.v[3][3] - m.v[3][1] * m.v[1][3]; + F32 coef07 = m.v[1][1] * m.v[2][3] - m.v[2][1] * m.v[1][3]; + F32 coef08 = m.v[2][1] * m.v[3][2] - m.v[3][1] * m.v[2][2]; + F32 coef10 = m.v[1][1] * m.v[3][2] - m.v[3][1] * m.v[1][2]; + F32 coef11 = m.v[1][1] * m.v[2][2] - m.v[2][1] * m.v[1][2]; + F32 coef12 = m.v[2][0] * m.v[3][3] - m.v[3][0] * m.v[2][3]; + F32 coef14 = m.v[1][0] * m.v[3][3] - m.v[3][0] * m.v[1][3]; + F32 coef15 = m.v[1][0] * m.v[2][3] - m.v[2][0] * m.v[1][3]; + F32 coef16 = m.v[2][0] * m.v[3][2] - m.v[3][0] * m.v[2][2]; + F32 coef18 = m.v[1][0] * m.v[3][2] - m.v[3][0] * m.v[1][2]; + F32 coef19 = m.v[1][0] * m.v[2][2] - m.v[2][0] * m.v[1][2]; + F32 coef20 = m.v[2][0] * m.v[3][1] - m.v[3][0] * m.v[2][1]; + F32 coef22 = m.v[1][0] * m.v[3][1] - m.v[3][0] * m.v[1][1]; + F32 coef23 = m.v[1][0] * m.v[2][1] - m.v[2][0] * m.v[1][1]; + + Vec4F32 fac0 = { coef00, coef00, coef02, coef03 }; + Vec4F32 fac1 = { coef04, coef04, coef06, coef07 }; + Vec4F32 fac2 = { coef08, coef08, coef10, coef11 }; + Vec4F32 fac3 = { coef12, coef12, coef14, coef15 }; + Vec4F32 fac4 = { coef16, coef16, coef18, coef19 }; + Vec4F32 fac5 = { coef20, coef20, coef22, coef23 }; + + Vec4F32 vec0 = { m.v[1][0], m.v[0][0], m.v[0][0], m.v[0][0] }; + Vec4F32 vec1 = { m.v[1][1], m.v[0][1], m.v[0][1], m.v[0][1] }; + Vec4F32 vec2 = { m.v[1][2], m.v[0][2], m.v[0][2], m.v[0][2] }; + Vec4F32 vec3 = { m.v[1][3], m.v[0][3], m.v[0][3], m.v[0][3] }; + + Vec4F32 inv0 = add_4f32(sub_4f32(mul_4f32(vec1, fac0), mul_4f32(vec2, fac1)), mul_4f32(vec3, fac2)); + Vec4F32 inv1 = add_4f32(sub_4f32(mul_4f32(vec0, fac0), mul_4f32(vec2, fac3)), mul_4f32(vec3, fac4)); + Vec4F32 inv2 = add_4f32(sub_4f32(mul_4f32(vec0, fac1), mul_4f32(vec1, fac3)), mul_4f32(vec3, fac5)); + Vec4F32 inv3 = add_4f32(sub_4f32(mul_4f32(vec0, fac2), mul_4f32(vec1, fac4)), mul_4f32(vec2, fac5)); + + Vec4F32 sign_a = { +1, -1, +1, -1 }; + Vec4F32 sign_b = { -1, +1, -1, +1 }; + + Mat4x4F32 inverse; + for(U32 i = 0; i < 4; i += 1) + { + inverse.v[0][i] = inv0.v[i] * sign_a.v[i]; + inverse.v[1][i] = inv1.v[i] * sign_b.v[i]; + inverse.v[2][i] = inv2.v[i] * sign_a.v[i]; + inverse.v[3][i] = inv3.v[i] * sign_b.v[i]; + } + + Vec4F32 row0 = { inverse.v[0][0], inverse.v[1][0], inverse.v[2][0], inverse.v[3][0] }; + Vec4F32 m0 = { m.v[0][0], m.v[0][1], m.v[0][2], m.v[0][3] }; + Vec4F32 dot0 = mul_4f32(m0, row0); + F32 dot1 = (dot0.x + dot0.y) + (dot0.z + dot0.w); + + F32 one_over_det = 1 / dot1; + + return scale_4x4f32(inverse, one_over_det); +} + +internal Mat4x4F32 +derotate_4x4f32(Mat4x4F32 mat) +{ + Vec3F32 scale = + { + length_3f32(v3f32(mat.v[0][0], mat.v[0][1], mat.v[0][2])), + length_3f32(v3f32(mat.v[1][0], mat.v[1][1], mat.v[1][2])), + length_3f32(v3f32(mat.v[2][0], mat.v[2][1], mat.v[2][2])), + }; + mat.v[0][0] = scale.x; + mat.v[1][0] = 0.f; + mat.v[2][0] = 0.f; + mat.v[0][1] = 0.f; + mat.v[1][1] = scale.y; + mat.v[2][1] = 0.f; + mat.v[0][2] = 0.f; + mat.v[1][2] = 0.f; + mat.v[2][2] = scale.z; + return mat; +} + +//////////////////////////////// +//~ rjf: Range Ops + +internal Rng1U32 rng_1u32(U32 min, U32 max) {Rng1U32 r = {min, max}; if(r.min > r.max) { Swap(U32, r.min, r.max); } return r;} +internal Rng1U32 shift_1u32(Rng1U32 r, U32 x) {r.min += x; r.max += x; return r;} +internal Rng1U32 pad_1u32(Rng1U32 r, U32 x) {r.min -= x; r.max += x; return r;} +internal U32 center_1u32(Rng1U32 r) {U32 c = (r.min+r.max)/2; return c;} +internal B32 contains_1u32(Rng1U32 r, U32 x) {B32 c = (r.min <= x && x < r.max); return c;} +internal U32 dim_1u32(Rng1U32 r) {U32 c = r.max-r.min; return c;} +internal Rng1U32 union_1u32(Rng1U32 a, Rng1U32 b) {Rng1U32 c = {Min(a.min, b.min), Max(a.max, b.max)}; return c;} +internal Rng1U32 intersect_1u32(Rng1U32 a, Rng1U32 b) {Rng1U32 c = {Max(a.min, b.min), Min(a.max, b.max)}; return c;} +internal U32 clamp_1u32(Rng1U32 r, U32 v) {v = Clamp(r.min, v, r.max); return v;} + +internal Rng1S32 rng_1s32(S32 min, S32 max) {Rng1S32 r = {min, max}; if(r.min > r.max) { Swap(S32, r.min, r.max); } return r;} +internal Rng1S32 shift_1s32(Rng1S32 r, S32 x) {r.min += x; r.max += x; return r;} +internal Rng1S32 pad_1s32(Rng1S32 r, S32 x) {r.min -= x; r.max += x; return r;} +internal S32 center_1s32(Rng1S32 r) {S32 c = (r.min+r.max)/2; return c;} +internal B32 contains_1s32(Rng1S32 r, S32 x) {B32 c = (r.min <= x && x < r.max); return c;} +internal S32 dim_1s32(Rng1S32 r) {S32 c = r.max-r.min; return c;} +internal Rng1S32 union_1s32(Rng1S32 a, Rng1S32 b) {Rng1S32 c = {Min(a.min, b.min), Max(a.max, b.max)}; return c;} +internal Rng1S32 intersect_1s32(Rng1S32 a, Rng1S32 b) {Rng1S32 c = {Max(a.min, b.min), Min(a.max, b.max)}; return c;} +internal S32 clamp_1s32(Rng1S32 r, S32 v) {v = Clamp(r.min, v, r.max); return v;} + +internal Rng1U64 rng_1u64(U64 min, U64 max) {Rng1U64 r = {min, max}; if(r.min > r.max) { Swap(U64, r.min, r.max); } return r;} +internal Rng1U64 shift_1u64(Rng1U64 r, U64 x) {r.min += x; r.max += x; return r;} +internal Rng1U64 pad_1u64(Rng1U64 r, U64 x) {r.min -= x; r.max += x; return r;} +internal U64 center_1u64(Rng1U64 r) {U64 c = (r.min+r.max)/2; return c;} +internal B32 contains_1u64(Rng1U64 r, U64 x) {B32 c = (r.min <= x && x < r.max); return c;} +internal U64 dim_1u64(Rng1U64 r) {U64 c = r.max-r.min; return c;} +internal Rng1U64 union_1u64(Rng1U64 a, Rng1U64 b) {Rng1U64 c = {Min(a.min, b.min), Max(a.max, b.max)}; return c;} +internal Rng1U64 intersect_1u64(Rng1U64 a, Rng1U64 b) {Rng1U64 c = {Max(a.min, b.min), Min(a.max, b.max)}; return c;} +internal U64 clamp_1u64(Rng1U64 r, U64 v) {v = Clamp(r.min, v, r.max); return v;} + +internal Rng1S64 rng_1s64(S64 min, S64 max) {Rng1S64 r = {min, max}; if(r.min > r.max) { Swap(S64, r.min, r.max); } return r;} +internal Rng1S64 shift_1s64(Rng1S64 r, S64 x) {r.min += x; r.max += x; return r;} +internal Rng1S64 pad_1s64(Rng1S64 r, S64 x) {r.min -= x; r.max += x; return r;} +internal S64 center_1s64(Rng1S64 r) {S64 c = (r.min+r.max)/2; return c;} +internal B32 contains_1s64(Rng1S64 r, S64 x) {B32 c = (r.min <= x && x < r.max); return c;} +internal S64 dim_1s64(Rng1S64 r) {S64 c = r.max-r.min; return c;} +internal Rng1S64 union_1s64(Rng1S64 a, Rng1S64 b) {Rng1S64 c = {Min(a.min, b.min), Max(a.max, b.max)}; return c;} +internal Rng1S64 intersect_1s64(Rng1S64 a, Rng1S64 b) {Rng1S64 c = {Max(a.min, b.min), Min(a.max, b.max)}; return c;} +internal S64 clamp_1s64(Rng1S64 r, S64 v) {v = Clamp(r.min, v, r.max); return v;} + +internal Rng1F32 rng_1f32(F32 min, F32 max) {Rng1F32 r = {min, max}; if(r.min > r.max) { Swap(F32, r.min, r.max); } return r;} +internal Rng1F32 shift_1f32(Rng1F32 r, F32 x) {r.min += x; r.max += x; return r;} +internal Rng1F32 pad_1f32(Rng1F32 r, F32 x) {r.min -= x; r.max += x; return r;} +internal F32 center_1f32(Rng1F32 r) {F32 c = (r.min+r.max)/2; return c;} +internal B32 contains_1f32(Rng1F32 r, F32 x) {B32 c = (r.min <= x && x < r.max); return c;} +internal F32 dim_1f32(Rng1F32 r) {F32 c = r.max-r.min; return c;} +internal Rng1F32 union_1f32(Rng1F32 a, Rng1F32 b) {Rng1F32 c = {Min(a.min, b.min), Max(a.max, b.max)}; return c;} +internal Rng1F32 intersect_1f32(Rng1F32 a, Rng1F32 b) {Rng1F32 c = {Max(a.min, b.min), Min(a.max, b.max)}; return c;} +internal F32 clamp_1f32(Rng1F32 r, F32 v) {v = Clamp(r.min, v, r.max); return v;} + +internal Rng2S16 rng_2s16(Vec2S16 min, Vec2S16 max) {Rng2S16 r = {min, max}; return r;} +internal Rng2S16 shift_2s16(Rng2S16 r, Vec2S16 x) {r.min = add_2s16(r.min, x); r.max = add_2s16(r.max, x); return r;} +internal Rng2S16 pad_2s16(Rng2S16 r, S16 x) {Vec2S16 xv = {x, x}; r.min = sub_2s16(r.min, xv); r.max = add_2s16(r.max, xv); return r;} +internal Vec2S16 center_2s16(Rng2S16 r) {Vec2S16 c = {(S16)((r.min.x+r.max.x)/2), (S16)((r.min.y+r.max.y)/2)}; return c;} +internal B32 contains_2s16(Rng2S16 r, Vec2S16 x) {B32 c = (r.min.x <= x.x && x.x < r.max.x && r.min.y <= x.y && x.y < r.max.y); return c;} +internal Vec2S16 dim_2s16(Rng2S16 r) {Vec2S16 dim = {(S16)(r.max.x-r.min.x), (S16)(r.max.y-r.min.y)}; return dim;} +internal Rng2S16 union_2s16(Rng2S16 a, Rng2S16 b) {Rng2S16 c; c.p0.x = Min(a.min.x, b.min.x); c.p0.y = Min(a.min.y, b.min.y); c.p1.x = Max(a.max.x, b.max.x); c.p1.y = Max(a.max.y, b.max.y); return c;} +internal Rng2S16 intersect_2s16(Rng2S16 a, Rng2S16 b) {Rng2S16 c; c.p0.x = Max(a.min.x, b.min.x); c.p0.y = Max(a.min.y, b.min.y); c.p1.x = Min(a.max.x, b.max.x); c.p1.y = Min(a.max.y, b.max.y); return c;} +internal Vec2S16 clamp_2s16(Rng2S16 r, Vec2S16 v) {v.x = Clamp(r.min.x, v.x, r.max.x); v.y = Clamp(r.min.y, v.y, r.max.y); return v;} + +internal Rng2S32 rng_2s32(Vec2S32 min, Vec2S32 max) {Rng2S32 r = {min, max}; return r;} +internal Rng2S32 shift_2s32(Rng2S32 r, Vec2S32 x) {r.min = add_2s32(r.min, x); r.max = add_2s32(r.max, x); return r;} +internal Rng2S32 pad_2s32(Rng2S32 r, S32 x) {Vec2S32 xv = {x, x}; r.min = sub_2s32(r.min, xv); r.max = add_2s32(r.max, xv); return r;} +internal Vec2S32 center_2s32(Rng2S32 r) {Vec2S32 c = {(r.min.x+r.max.x)/2, (r.min.y+r.max.y)/2}; return c;} +internal B32 contains_2s32(Rng2S32 r, Vec2S32 x) {B32 c = (r.min.x <= x.x && x.x < r.max.x && r.min.y <= x.y && x.y < r.max.y); return c;} +internal Vec2S32 dim_2s32(Rng2S32 r) {Vec2S32 dim = {r.max.x-r.min.x, r.max.y-r.min.y}; return dim;} +internal Rng2S32 union_2s32(Rng2S32 a, Rng2S32 b) {Rng2S32 c; c.p0.x = Min(a.min.x, b.min.x); c.p0.y = Min(a.min.y, b.min.y); c.p1.x = Max(a.max.x, b.max.x); c.p1.y = Max(a.max.y, b.max.y); return c;} +internal Rng2S32 intersect_2s32(Rng2S32 a, Rng2S32 b) {Rng2S32 c; c.p0.x = Max(a.min.x, b.min.x); c.p0.y = Max(a.min.y, b.min.y); c.p1.x = Min(a.max.x, b.max.x); c.p1.y = Min(a.max.y, b.max.y); return c;} +internal Vec2S32 clamp_2s32(Rng2S32 r, Vec2S32 v) {v.x = Clamp(r.min.x, v.x, r.max.x); v.y = Clamp(r.min.y, v.y, r.max.y); return v;} + +internal Rng2S64 rng_2s64(Vec2S64 min, Vec2S64 max) {Rng2S64 r = {min, max}; return r;} +internal Rng2S64 shift_2s64(Rng2S64 r, Vec2S64 x) {r.min = add_2s64(r.min, x); r.max = add_2s64(r.max, x); return r;} +internal Rng2S64 pad_2s64(Rng2S64 r, S64 x) {Vec2S64 xv = {x, x}; r.min = sub_2s64(r.min, xv); r.max = add_2s64(r.max, xv); return r;} +internal Vec2S64 center_2s64(Rng2S64 r) {Vec2S64 c = {(r.min.x+r.max.x)/2, (r.min.y+r.max.y)/2}; return c;} +internal B32 contains_2s64(Rng2S64 r, Vec2S64 x) {B32 c = (r.min.x <= x.x && x.x < r.max.x && r.min.y <= x.y && x.y < r.max.y); return c;} +internal Vec2S64 dim_2s64(Rng2S64 r) {Vec2S64 dim = {r.max.x-r.min.x, r.max.y-r.min.y}; return dim;} +internal Rng2S64 union_2s64(Rng2S64 a, Rng2S64 b) {Rng2S64 c; c.p0.x = Min(a.min.x, b.min.x); c.p0.y = Min(a.min.y, b.min.y); c.p1.x = Max(a.max.x, b.max.x); c.p1.y = Max(a.max.y, b.max.y); return c;} +internal Rng2S64 intersect_2s64(Rng2S64 a, Rng2S64 b) {Rng2S64 c; c.p0.x = Max(a.min.x, b.min.x); c.p0.y = Max(a.min.y, b.min.y); c.p1.x = Min(a.max.x, b.max.x); c.p1.y = Min(a.max.y, b.max.y); return c;} +internal Vec2S64 clamp_2s64(Rng2S64 r, Vec2S64 v) {v.x = Clamp(r.min.x, v.x, r.max.x); v.y = Clamp(r.min.y, v.y, r.max.y); return v;} + +internal Rng2F32 rng_2f32(Vec2F32 min, Vec2F32 max) {Rng2F32 r = {min, max}; return r;} +internal Rng2F32 shift_2f32(Rng2F32 r, Vec2F32 x) {r.min = add_2f32(r.min, x); r.max = add_2f32(r.max, x); return r;} +internal Rng2F32 pad_2f32(Rng2F32 r, F32 x) {Vec2F32 xv = {x, x}; r.min = sub_2f32(r.min, xv); r.max = add_2f32(r.max, xv); return r;} +internal Vec2F32 center_2f32(Rng2F32 r) {Vec2F32 c = {(r.min.x+r.max.x)/2, (r.min.y+r.max.y)/2}; return c;} +internal B32 contains_2f32(Rng2F32 r, Vec2F32 x) {B32 c = (r.min.x <= x.x && x.x < r.max.x && r.min.y <= x.y && x.y < r.max.y); return c;} +internal Vec2F32 dim_2f32(Rng2F32 r) {Vec2F32 dim = {r.max.x-r.min.x, r.max.y-r.min.y}; return dim;} +internal Rng2F32 union_2f32(Rng2F32 a, Rng2F32 b) {Rng2F32 c; c.p0.x = Min(a.min.x, b.min.x); c.p0.y = Min(a.min.y, b.min.y); c.p1.x = Max(a.max.x, b.max.x); c.p1.y = Max(a.max.y, b.max.y); return c;} +internal Rng2F32 intersect_2f32(Rng2F32 a, Rng2F32 b) {Rng2F32 c; c.p0.x = Max(a.min.x, b.min.x); c.p0.y = Max(a.min.y, b.min.y); c.p1.x = Min(a.max.x, b.max.x); c.p1.y = Min(a.max.y, b.max.y); return c;} +internal Vec2F32 clamp_2f32(Rng2F32 r, Vec2F32 v) {v.x = Clamp(r.min.x, v.x, r.max.x); v.y = Clamp(r.min.y, v.y, r.max.y); return v;} + +//////////////////////////////// +//~ rjf: Miscellaneous Ops + +internal Vec3F32 +hsv_from_rgb(Vec3F32 rgb) +{ + F32 c_max = Max(rgb.x, Max(rgb.y, rgb.z)); + F32 c_min = Min(rgb.x, Min(rgb.y, rgb.z)); + F32 delta = c_max - c_min; + F32 h = ((delta == 0.f) ? 0.f : + (c_max == rgb.x) ? mod_f32((rgb.y - rgb.z)/delta + 6.f, 6.f) : + (c_max == rgb.y) ? (rgb.z - rgb.x)/delta + 2.f : + (c_max == rgb.z) ? (rgb.x - rgb.y)/delta + 4.f : + 0.f); + F32 s = (c_max == 0.f) ? 0.f : (delta/c_max); + F32 v = c_max; + Vec3F32 hsv = {h/6.f, s, v}; + return hsv; +} + +internal Vec3F32 +rgb_from_hsv(Vec3F32 hsv) +{ + F32 h = mod_f32(hsv.x * 360.f, 360.f); + F32 s = hsv.y; + F32 v = hsv.z; + + F32 c = v*s; + F32 x = c*(1.f - abs_f32(mod_f32(h/60.f, 2.f) - 1.f)); + F32 m = v - c; + + F32 r = 0; + F32 g = 0; + F32 b = 0; + + if ((h >= 0.f && h < 60.f) || (h >= 360.f && h < 420.f)){ + r = c; + g = x; + b = 0; + } + else if (h >= 60.f && h < 120.f){ + r = x; + g = c; + b = 0; + } + else if (h >= 120.f && h < 180.f){ + r = 0; + g = c; + b = x; + } + else if (h >= 180.f && h < 240.f){ + r = 0; + g = x; + b = c; + } + else if (h >= 240.f && h < 300.f){ + r = x; + g = 0; + b = c; + } + else if ((h >= 300.f && h <= 360.f) || (h >= -60.f && h <= 0.f)){ + r = c; + g = 0; + b = x; + } + + Vec3F32 rgb = {r + m, g + m, b + m}; + return(rgb); +} + +internal Vec4F32 +hsva_from_rgba(Vec4F32 rgba) +{ + Vec3F32 rgb = v3f32(rgba.x, rgba.y, rgba.z); + Vec3F32 hsv = hsv_from_rgb(rgb); + Vec4F32 hsva = v4f32(hsv.x, hsv.y, hsv.z, rgba.w); + return hsva; +} + +internal Vec4F32 +rgba_from_hsva(Vec4F32 hsva) +{ + Vec3F32 hsv = v3f32(hsva.x, hsva.y, hsva.z); + Vec3F32 rgb = rgb_from_hsv(hsv); + Vec4F32 rgba = v4f32(rgb.x, rgb.y, rgb.z, hsva.w); + return rgba; +} + +internal Vec4F32 +rgba_from_u32(U32 hex) +{ + Vec4F32 result = v4f32(((hex&0xff000000)>>24)/255.f, + ((hex&0x00ff0000)>>16)/255.f, + ((hex&0x0000ff00)>> 8)/255.f, + ((hex&0x000000ff)>> 0)/255.f); + return result; +} + +internal U32 +u32_from_rgba(Vec4F32 rgba) +{ + U32 result = 0; + result |= ((U32)((U8)(rgba.x*255.f))) << 24; + result |= ((U32)((U8)(rgba.y*255.f))) << 16; + result |= ((U32)((U8)(rgba.z*255.f))) << 8; + result |= ((U32)((U8)(rgba.w*255.f))) << 0; + return result; +} + +//////////////////////////////// +//~ rjf: List Type Functions + +internal void +rng1s64_list_push(Arena *arena, Rng1S64List *list, Rng1S64 rng) +{ + Rng1S64Node *n = push_array(arena, Rng1S64Node, 1); + MemoryCopyStruct(&n->v, &rng); + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal Rng1S64Array +rng1s64_array_from_list(Arena *arena, Rng1S64List *list) +{ + Rng1S64Array arr = {0}; + arr.count = list->count; + arr.v = push_array_no_zero(arena, Rng1S64, arr.count); + U64 idx = 0; + for(Rng1S64Node *n = list->first; n != 0; n = n->next) + { + arr.v[idx] = n->v; + idx += 1; + } + return arr; +} diff --git a/metagen/metagen_base/metagen_base_math.h b/metagen/metagen_base/metagen_base_math.h new file mode 100644 index 0000000..3406e6a --- /dev/null +++ b/metagen/metagen_base/metagen_base_math.h @@ -0,0 +1,649 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_MATH_H +#define BASE_MATH_H + +//////////////////////////////// +//~ rjf: Vector Types + +//- rjf: 2-vectors + +typedef union Vec2F32 Vec2F32; +union Vec2F32 +{ + struct + { + F32 x; + F32 y; + }; + F32 v[2]; +}; + +typedef union Vec2S64 Vec2S64; +union Vec2S64 +{ + struct + { + S64 x; + S64 y; + }; + S64 v[2]; +}; + +typedef union Vec2S32 Vec2S32; +union Vec2S32 +{ + struct + { + S32 x; + S32 y; + }; + S32 v[2]; +}; + +typedef union Vec2S16 Vec2S16; +union Vec2S16 +{ + struct + { + S16 x; + S16 y; + }; + S16 v[2]; +}; + +//- rjf: 3-vectors + +typedef union Vec3F32 Vec3F32; +union Vec3F32 +{ + struct + { + F32 x; + F32 y; + F32 z; + }; + struct + { + Vec2F32 xy; + F32 _z0; + }; + struct + { + F32 _x0; + Vec2F32 yz; + }; + F32 v[3]; +}; + +typedef union Vec3S32 Vec3S32; +union Vec3S32 +{ + struct + { + S32 x; + S32 y; + S32 z; + }; + struct + { + Vec2S32 xy; + S32 _z0; + }; + struct + { + S32 _x0; + Vec2S32 yz; + }; + S32 v[3]; +}; + +//- rjf: 4-vectors + +typedef union Vec4F32 Vec4F32; +union Vec4F32 +{ + struct + { + F32 x; + F32 y; + F32 z; + F32 w; + }; + struct + { + Vec2F32 xy; + Vec2F32 zw; + }; + struct + { + Vec3F32 xyz; + F32 _z0; + }; + struct + { + F32 _x0; + Vec3F32 yzw; + }; + F32 v[4]; +}; + +typedef union Vec4S32 Vec4S32; +union Vec4S32 +{ + struct + { + S32 x; + S32 y; + S32 z; + S32 w; + }; + struct + { + Vec2S32 xy; + Vec2S32 zw; + }; + struct + { + Vec3S32 xyz; + S32 _z0; + }; + struct + { + S32 _x0; + Vec3S32 yzw; + }; + S32 v[4]; +}; + +//////////////////////////////// +//~ rjf: Matrix Types + +typedef struct Mat3x3F32 Mat3x3F32; +struct Mat3x3F32 +{ + F32 v[3][3]; +}; + +typedef struct Mat4x4F32 Mat4x4F32; +struct Mat4x4F32 +{ + F32 v[4][4]; +}; + +//////////////////////////////// +//~ rjf: Range Types + +//- rjf: 1-range + +typedef union Rng1U32 Rng1U32; +union Rng1U32 +{ + struct + { + U32 min; + U32 max; + }; + U32 v[2]; +}; + +typedef union Rng1S32 Rng1S32; +union Rng1S32 +{ + struct + { + S32 min; + S32 max; + }; + S32 v[2]; +}; + +typedef union Rng1U64 Rng1U64; +union Rng1U64 +{ + struct + { + U64 min; + U64 max; + }; + U64 v[2]; +}; + +typedef union Rng1S64 Rng1S64; +union Rng1S64 +{ + struct + { + S64 min; + S64 max; + }; + S64 v[2]; +}; + +typedef union Rng1F32 Rng1F32; +union Rng1F32 +{ + struct + { + F32 min; + F32 max; + }; + F32 v[2]; +}; + +//- rjf: 2-range (rectangles) + +typedef union Rng2S16 Rng2S16; +union Rng2S16 +{ + struct + { + Vec2S16 min; + Vec2S16 max; + }; + struct + { + Vec2S16 p0; + Vec2S16 p1; + }; + struct + { + S16 x0; + S16 y0; + S16 x1; + S16 y1; + }; + Vec2S16 v[2]; +}; + +typedef union Rng2S32 Rng2S32; +union Rng2S32 +{ + struct + { + Vec2S32 min; + Vec2S32 max; + }; + struct + { + Vec2S32 p0; + Vec2S32 p1; + }; + struct + { + S32 x0; + S32 y0; + S32 x1; + S32 y1; + }; + Vec2S32 v[2]; +}; + +typedef union Rng2F32 Rng2F32; +union Rng2F32 +{ + struct + { + Vec2F32 min; + Vec2F32 max; + }; + struct + { + Vec2F32 p0; + Vec2F32 p1; + }; + struct + { + F32 x0; + F32 y0; + F32 x1; + F32 y1; + }; + Vec2F32 v[2]; +}; + +typedef union Rng2S64 Rng2S64; +union Rng2S64 +{ + struct + { + Vec2S64 min; + Vec2S64 max; + }; + struct + { + Vec2S64 p0; + Vec2S64 p1; + }; + struct + { + S64 x0; + S64 y0; + S64 x1; + S64 y1; + }; + Vec2S64 v[2]; +}; + +//////////////////////////////// +//~ rjf: List Types + +typedef struct Rng1S64Node Rng1S64Node; +struct Rng1S64Node +{ + Rng1S64Node *next; + Rng1S64 v; +}; + +typedef struct Rng1S64List Rng1S64List; +struct Rng1S64List +{ + Rng1S64Node *first; + Rng1S64Node *last; + U64 count; +}; + +typedef struct Rng1S64Array Rng1S64Array; +struct Rng1S64Array +{ + Rng1S64 *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Scalar Ops + +#define abs_s64(v) (S64)llabs(v) + +#define sqrt_f32(v) sqrtf(v) +#define mod_f32(a, b) fmodf((a), (b)) +#define pow_f32(b, e) powf((b), (e)) +#define ceil_f32(v) ceilf(v) +#define floor_f32(v) floorf(v) +#define round_f32(v) roundf(v) +#define abs_f32(v) fabsf(v) +#define radians_from_turns_f32(v) ((v)*2*3.1415926535897f) +#define turns_from_radians_f32(v) ((v)/2*3.1415926535897f) +#define degrees_from_turns_f32(v) ((v)*360.f) +#define turns_from_degrees_f32(v) ((v)/360.f) +#define degrees_from_radians_f32(v) (degrees_from_turns_f32(turns_from_radians_f32(v))) +#define radians_from_degrees_f32(v) (radians_from_turns_f32(turns_from_degrees_f32(v))) +#define sin_f32(v) sinf(radians_from_turns_f32(v)) +#define cos_f32(v) cosf(radians_from_turns_f32(v)) +#define tan_f32(v) tanf(radians_from_turns_f32(v)) + +#define sqrt_f64(v) sqrt(v) +#define mod_f64(a, b) fmod((a), (b)) +#define pow_f64(b, e) pow((b), (e)) +#define ceil_f64(v) ceil(v) +#define floor_f64(v) floor(v) +#define round_f64(v) round(v) +#define abs_f64(v) fabs(v) +#define radians_from_turns_f64(v) ((v)*2*3.1415926535897) +#define turns_from_radians_f64(v) ((v)/2*3.1415926535897) +#define degrees_from_turns_f64(v) ((v)*360.0) +#define turns_from_degrees_f64(v) ((v)/360.0) +#define degrees_from_radians_f64(v) (degrees_from_turns_f64(turns_from_radians_f64(v))) +#define radians_from_degrees_f64(v) (radians_from_turns_f64(turns_from_degrees_f64(v))) +#define sin_f64(v) sin(radians_from_turns_f64(v)) +#define cos_f64(v) cos(radians_from_turns_f64(v)) +#define tan_f64(v) tan(radians_from_turns_f64(v)) + +internal F32 mix_1f32(F32 a, F32 b, F32 t); +internal F64 mix_1f64(F64 a, F64 b, F64 t); + +//////////////////////////////// +//~ rjf: Vector Ops + +#define v2f32(x, y) vec_2f32((x), (y)) +internal Vec2F32 vec_2f32(F32 x, F32 y); +internal Vec2F32 add_2f32(Vec2F32 a, Vec2F32 b); +internal Vec2F32 sub_2f32(Vec2F32 a, Vec2F32 b); +internal Vec2F32 mul_2f32(Vec2F32 a, Vec2F32 b); +internal Vec2F32 div_2f32(Vec2F32 a, Vec2F32 b); +internal Vec2F32 scale_2f32(Vec2F32 v, F32 s); +internal F32 dot_2f32(Vec2F32 a, Vec2F32 b); +internal F32 length_squared_2f32(Vec2F32 v); +internal F32 length_2f32(Vec2F32 v); +internal Vec2F32 normalize_2f32(Vec2F32 v); +internal Vec2F32 mix_2f32(Vec2F32 a, Vec2F32 b, F32 t); + +#define v2s64(x, y) vec_2s64((x), (y)) +internal Vec2S64 vec_2s64(S64 x, S64 y); +internal Vec2S64 add_2s64(Vec2S64 a, Vec2S64 b); +internal Vec2S64 sub_2s64(Vec2S64 a, Vec2S64 b); +internal Vec2S64 mul_2s64(Vec2S64 a, Vec2S64 b); +internal Vec2S64 div_2s64(Vec2S64 a, Vec2S64 b); +internal Vec2S64 scale_2s64(Vec2S64 v, S64 s); +internal S64 dot_2s64(Vec2S64 a, Vec2S64 b); +internal S64 length_squared_2s64(Vec2S64 v); +internal S64 length_2s64(Vec2S64 v); +internal Vec2S64 normalize_2s64(Vec2S64 v); +internal Vec2S64 mix_2s64(Vec2S64 a, Vec2S64 b, F32 t); + +#define v2s32(x, y) vec_2s32((x), (y)) +internal Vec2S32 vec_2s32(S32 x, S32 y); +internal Vec2S32 add_2s32(Vec2S32 a, Vec2S32 b); +internal Vec2S32 sub_2s32(Vec2S32 a, Vec2S32 b); +internal Vec2S32 mul_2s32(Vec2S32 a, Vec2S32 b); +internal Vec2S32 div_2s32(Vec2S32 a, Vec2S32 b); +internal Vec2S32 scale_2s32(Vec2S32 v, S32 s); +internal S32 dot_2s32(Vec2S32 a, Vec2S32 b); +internal S32 length_squared_2s32(Vec2S32 v); +internal S32 length_2s32(Vec2S32 v); +internal Vec2S32 normalize_2s32(Vec2S32 v); +internal Vec2S32 mix_2s32(Vec2S32 a, Vec2S32 b, F32 t); + +#define v2s16(x, y) vec_2s16((x), (y)) +internal Vec2S16 vec_2s16(S16 x, S16 y); +internal Vec2S16 add_2s16(Vec2S16 a, Vec2S16 b); +internal Vec2S16 sub_2s16(Vec2S16 a, Vec2S16 b); +internal Vec2S16 mul_2s16(Vec2S16 a, Vec2S16 b); +internal Vec2S16 div_2s16(Vec2S16 a, Vec2S16 b); +internal Vec2S16 scale_2s16(Vec2S16 v, S16 s); +internal S16 dot_2s16(Vec2S16 a, Vec2S16 b); +internal S16 length_squared_2s16(Vec2S16 v); +internal S16 length_2s16(Vec2S16 v); +internal Vec2S16 normalize_2s16(Vec2S16 v); +internal Vec2S16 mix_2s16(Vec2S16 a, Vec2S16 b, F32 t); + +#define v3f32(x, y, z) vec_3f32((x), (y), (z)) +internal Vec3F32 vec_3f32(F32 x, F32 y, F32 z); +internal Vec3F32 add_3f32(Vec3F32 a, Vec3F32 b); +internal Vec3F32 sub_3f32(Vec3F32 a, Vec3F32 b); +internal Vec3F32 mul_3f32(Vec3F32 a, Vec3F32 b); +internal Vec3F32 div_3f32(Vec3F32 a, Vec3F32 b); +internal Vec3F32 scale_3f32(Vec3F32 v, F32 s); +internal F32 dot_3f32(Vec3F32 a, Vec3F32 b); +internal F32 length_squared_3f32(Vec3F32 v); +internal F32 length_3f32(Vec3F32 v); +internal Vec3F32 normalize_3f32(Vec3F32 v); +internal Vec3F32 mix_3f32(Vec3F32 a, Vec3F32 b, F32 t); +internal Vec3F32 cross_3f32(Vec3F32 a, Vec3F32 b); + +#define v3s32(x, y, z) vec_3s32((x), (y), (z)) +internal Vec3S32 vec_3s32(S32 x, S32 y, S32 z); +internal Vec3S32 add_3s32(Vec3S32 a, Vec3S32 b); +internal Vec3S32 sub_3s32(Vec3S32 a, Vec3S32 b); +internal Vec3S32 mul_3s32(Vec3S32 a, Vec3S32 b); +internal Vec3S32 div_3s32(Vec3S32 a, Vec3S32 b); +internal Vec3S32 scale_3s32(Vec3S32 v, S32 s); +internal S32 dot_3s32(Vec3S32 a, Vec3S32 b); +internal S32 length_squared_3s32(Vec3S32 v); +internal S32 length_3s32(Vec3S32 v); +internal Vec3S32 normalize_3s32(Vec3S32 v); +internal Vec3S32 mix_3s32(Vec3S32 a, Vec3S32 b, F32 t); +internal Vec3S32 cross_3s32(Vec3S32 a, Vec3S32 b); + +#define v4f32(x, y, z, w) vec_4f32((x), (y), (z), (w)) +internal Vec4F32 vec_4f32(F32 x, F32 y, F32 z, F32 w); +internal Vec4F32 add_4f32(Vec4F32 a, Vec4F32 b); +internal Vec4F32 sub_4f32(Vec4F32 a, Vec4F32 b); +internal Vec4F32 mul_4f32(Vec4F32 a, Vec4F32 b); +internal Vec4F32 div_4f32(Vec4F32 a, Vec4F32 b); +internal Vec4F32 scale_4f32(Vec4F32 v, F32 s); +internal F32 dot_4f32(Vec4F32 a, Vec4F32 b); +internal F32 length_squared_4f32(Vec4F32 v); +internal F32 length_4f32(Vec4F32 v); +internal Vec4F32 normalize_4f32(Vec4F32 v); +internal Vec4F32 mix_4f32(Vec4F32 a, Vec4F32 b, F32 t); + +#define v4s32(x, y, z, w) vec_4s32((x), (y), (z), (w)) +internal Vec4S32 vec_4s32(S32 x, S32 y, S32 z, S32 w); +internal Vec4S32 add_4s32(Vec4S32 a, Vec4S32 b); +internal Vec4S32 sub_4s32(Vec4S32 a, Vec4S32 b); +internal Vec4S32 mul_4s32(Vec4S32 a, Vec4S32 b); +internal Vec4S32 div_4s32(Vec4S32 a, Vec4S32 b); +internal Vec4S32 scale_4s32(Vec4S32 v, S32 s); +internal S32 dot_4s32(Vec4S32 a, Vec4S32 b); +internal S32 length_squared_4s32(Vec4S32 v); +internal S32 length_4s32(Vec4S32 v); +internal Vec4S32 normalize_4s32(Vec4S32 v); +internal Vec4S32 mix_4s32(Vec4S32 a, Vec4S32 b, F32 t); + +//////////////////////////////// +//~ rjf: Matrix Ops + +internal Mat3x3F32 mat_3x3f32(F32 diagonal); +internal Mat3x3F32 make_translate_3x3f32(Vec2F32 delta); +internal Mat3x3F32 make_scale_3x3f32(Vec2F32 scale); +internal Mat3x3F32 mul_3x3f32(Mat3x3F32 a, Mat3x3F32 b); + +internal Mat4x4F32 mat_4x4f32(F32 diagonal); +internal Mat4x4F32 make_translate_4x4f32(Vec3F32 delta); +internal Mat4x4F32 make_scale_4x4f32(Vec3F32 scale); +internal Mat4x4F32 make_perspective_4x4f32(F32 fov, F32 aspect_ratio, F32 near_z, F32 far_z); +internal Mat4x4F32 make_orthographic_4x4f32(F32 left, F32 right, F32 bottom, F32 top, F32 near_z, F32 far_z); +internal Mat4x4F32 make_look_at_4x4f32(Vec3F32 eye, Vec3F32 center, Vec3F32 up); +internal Mat4x4F32 make_rotate_4x4f32(Vec3F32 axis, F32 turns); +internal Mat4x4F32 mul_4x4f32(Mat4x4F32 a, Mat4x4F32 b); +internal Mat4x4F32 scale_4x4f32(Mat4x4F32 m, F32 scale); +internal Mat4x4F32 inverse_4x4f32(Mat4x4F32 m); +internal Mat4x4F32 derotate_4x4f32(Mat4x4F32 mat); + +//////////////////////////////// +//~ rjf: Range Ops + +#define r1u32(min, max) rng_1u32((min), (max)) +internal Rng1U32 rng_1u32(U32 min, U32 max); +internal Rng1U32 shift_1u32(Rng1U32 r, U32 x); +internal Rng1U32 pad_1u32(Rng1U32 r, U32 x); +internal U32 center_1u32(Rng1U32 r); +internal B32 contains_1u32(Rng1U32 r, U32 x); +internal U32 dim_1u32(Rng1U32 r); +internal Rng1U32 union_1u32(Rng1U32 a, Rng1U32 b); +internal Rng1U32 intersect_1u32(Rng1U32 a, Rng1U32 b); +internal U32 clamp_1u32(Rng1U32 r, U32 v); + +#define r1s32(min, max) rng_1s32((min), (max)) +internal Rng1S32 rng_1s32(S32 min, S32 max); +internal Rng1S32 shift_1s32(Rng1S32 r, S32 x); +internal Rng1S32 pad_1s32(Rng1S32 r, S32 x); +internal S32 center_1s32(Rng1S32 r); +internal B32 contains_1s32(Rng1S32 r, S32 x); +internal S32 dim_1s32(Rng1S32 r); +internal Rng1S32 union_1s32(Rng1S32 a, Rng1S32 b); +internal Rng1S32 intersect_1s32(Rng1S32 a, Rng1S32 b); +internal S32 clamp_1s32(Rng1S32 r, S32 v); + +#define r1u64(min, max) rng_1u64((min), (max)) +internal Rng1U64 rng_1u64(U64 min, U64 max); +internal Rng1U64 shift_1u64(Rng1U64 r, U64 x); +internal Rng1U64 pad_1u64(Rng1U64 r, U64 x); +internal U64 center_1u64(Rng1U64 r); +internal B32 contains_1u64(Rng1U64 r, U64 x); +internal U64 dim_1u64(Rng1U64 r); +internal Rng1U64 union_1u64(Rng1U64 a, Rng1U64 b); +internal Rng1U64 intersect_1u64(Rng1U64 a, Rng1U64 b); +internal U64 clamp_1u64(Rng1U64 r, U64 v); + +#define r1s64(min, max) rng_1s64((min), (max)) +internal Rng1S64 rng_1s64(S64 min, S64 max); +internal Rng1S64 shift_1s64(Rng1S64 r, S64 x); +internal Rng1S64 pad_1s64(Rng1S64 r, S64 x); +internal S64 center_1s64(Rng1S64 r); +internal B32 contains_1s64(Rng1S64 r, S64 x); +internal S64 dim_1s64(Rng1S64 r); +internal Rng1S64 union_1s64(Rng1S64 a, Rng1S64 b); +internal Rng1S64 intersect_1s64(Rng1S64 a, Rng1S64 b); +internal S64 clamp_1s64(Rng1S64 r, S64 v); + +#define r1f32(min, max) rng_1f32((min), (max)) +internal Rng1F32 rng_1f32(F32 min, F32 max); +internal Rng1F32 shift_1f32(Rng1F32 r, F32 x); +internal Rng1F32 pad_1f32(Rng1F32 r, F32 x); +internal F32 center_1f32(Rng1F32 r); +internal B32 contains_1f32(Rng1F32 r, F32 x); +internal F32 dim_1f32(Rng1F32 r); +internal Rng1F32 union_1f32(Rng1F32 a, Rng1F32 b); +internal Rng1F32 intersect_1f32(Rng1F32 a, Rng1F32 b); +internal F32 clamp_1f32(Rng1F32 r, F32 v); + +#define r2s16(min, max) rng_2s16((min), (max)) +#define r2s16p(x, y, z, w) r2s16(v2s16((x), (y)), v2s16((z), (w))) +internal Rng2S16 rng_2s16(Vec2S16 min, Vec2S16 max); +internal Rng2S16 shift_2s16(Rng2S16 r, Vec2S16 x); +internal Rng2S16 pad_2s16(Rng2S16 r, S16 x); +internal Vec2S16 center_2s16(Rng2S16 r); +internal B32 contains_2s16(Rng2S16 r, Vec2S16 x); +internal Vec2S16 dim_2s16(Rng2S16 r); +internal Rng2S16 union_2s16(Rng2S16 a, Rng2S16 b); +internal Rng2S16 intersect_2s16(Rng2S16 a, Rng2S16 b); +internal Vec2S16 clamp_2s16(Rng2S16 r, Vec2S16 v); + +#define r2s32(min, max) rng_2s32((min), (max)) +#define r2s32p(x, y, z, w) r2s32(v2s32((x), (y)), v2s32((z), (w))) +internal Rng2S32 rng_2s32(Vec2S32 min, Vec2S32 max); +internal Rng2S32 shift_2s32(Rng2S32 r, Vec2S32 x); +internal Rng2S32 pad_2s32(Rng2S32 r, S32 x); +internal Vec2S32 center_2s32(Rng2S32 r); +internal B32 contains_2s32(Rng2S32 r, Vec2S32 x); +internal Vec2S32 dim_2s32(Rng2S32 r); +internal Rng2S32 union_2s32(Rng2S32 a, Rng2S32 b); +internal Rng2S32 intersect_2s32(Rng2S32 a, Rng2S32 b); +internal Vec2S32 clamp_2s32(Rng2S32 r, Vec2S32 v); + +#define r2s64(min, max) rng_2s64((min), (max)) +#define r2s64p(x, y, z, w) r2s64(v2s64((x), (y)), v2s64((z), (w))) +internal Rng2S64 rng_2s64(Vec2S64 min, Vec2S64 max); +internal Rng2S64 shift_2s64(Rng2S64 r, Vec2S64 x); +internal Rng2S64 pad_2s64(Rng2S64 r, S64 x); +internal Vec2S64 center_2s64(Rng2S64 r); +internal B32 contains_2s64(Rng2S64 r, Vec2S64 x); +internal Vec2S64 dim_2s64(Rng2S64 r); +internal Rng2S64 union_2s64(Rng2S64 a, Rng2S64 b); +internal Rng2S64 intersect_2s64(Rng2S64 a, Rng2S64 b); +internal Vec2S64 clamp_2s64(Rng2S64 r, Vec2S64 v); + +#define r2f32(min, max) rng_2f32((min), (max)) +#define r2f32p(x, y, z, w) r2f32(v2f32((x), (y)), v2f32((z), (w))) +internal Rng2F32 rng_2f32(Vec2F32 min, Vec2F32 max); +internal Rng2F32 shift_2f32(Rng2F32 r, Vec2F32 x); +internal Rng2F32 pad_2f32(Rng2F32 r, F32 x); +internal Vec2F32 center_2f32(Rng2F32 r); +internal B32 contains_2f32(Rng2F32 r, Vec2F32 x); +internal Vec2F32 dim_2f32(Rng2F32 r); +internal Rng2F32 union_2f32(Rng2F32 a, Rng2F32 b); +internal Rng2F32 intersect_2f32(Rng2F32 a, Rng2F32 b); +internal Vec2F32 clamp_2f32(Rng2F32 r, Vec2F32 v); + +//////////////////////////////// +//~ rjf: Miscellaneous Ops + +internal Vec3F32 hsv_from_rgb(Vec3F32 rgb); +internal Vec3F32 rgb_from_hsv(Vec3F32 hsv); +internal Vec4F32 hsva_from_rgba(Vec4F32 rgba); +internal Vec4F32 rgba_from_hsva(Vec4F32 hsva); +internal Vec4F32 rgba_from_u32(U32 hex); +internal U32 u32_from_rgba(Vec4F32 rgba); + +#define rgba_from_u32_lit_comp(h) { (((h)&0xff000000)>>24)/255.f, (((h)&0x00ff0000)>>16)/255.f, (((h)&0x0000ff00)>> 8)/255.f, (((h)&0x000000ff)>> 0)/255.f } + +//////////////////////////////// +//~ rjf: List Type Functions + +internal void rng1s64_list_push(Arena *arena, Rng1S64List *list, Rng1S64 rng); +internal Rng1S64Array rng1s64_array_from_list(Arena *arena, Rng1S64List *list); + +#endif // BASE_MATH_H diff --git a/metagen/metagen_base/metagen_base_profile.c b/metagen/metagen_base/metagen_base_profile.c new file mode 100644 index 0000000..7ea8904 --- /dev/null +++ b/metagen/metagen_base/metagen_base_profile.c @@ -0,0 +1,2 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) diff --git a/metagen/metagen_base/metagen_base_profile.h b/metagen/metagen_base/metagen_base_profile.h new file mode 100644 index 0000000..0809f20 --- /dev/null +++ b/metagen/metagen_base/metagen_base_profile.h @@ -0,0 +1,74 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_PROFILE_H +#define BASE_PROFILE_H + +//////////////////////////////// +//~ rjf: Zero Settings + +#if !defined(PROFILE_TELEMETRY) +# define PROFILE_TELEMETRY 0 +#endif + +#if !defined(MARKUP_LAYER_COLOR) +# define MARKUP_LAYER_COLOR 1.00f, 0.00f, 1.00f +#endif + +//////////////////////////////// +//~ rjf: Third Party Includes + +#if PROFILE_TELEMETRY +# include "rad_tm.h" +# if OS_WINDOWS +# pragma comment(lib, "rad_tm_win64.lib") +# endif +#endif + +//////////////////////////////// +//~ rjf: Telemetry Profile Defines + +#if PROFILE_TELEMETRY +# define ProfBegin(...) tmEnter(0, 0, __VA_ARGS__) +# define ProfBeginDynamic(...) (TM_API_PTR ? TM_API_PTR->_tmEnterZoneV_Core(0, 0, __FILE__, &g_telemetry_filename_id, __LINE__, __VA_ARGS__) : (void)0) +# define ProfEnd(...) (TM_API_PTR ? TM_API_PTR->_tmLeaveZone(0) : (void)0) +# define ProfTick(...) tmTick(0) +# define ProfIsCapturing(...) tmRunning() +# define ProfBeginCapture(...) tmOpen(0, __VA_ARGS__, __DATE__, "localhost", TMCT_TCP, TELEMETRY_DEFAULT_PORT, TMOF_INIT_NETWORKING|TMOF_CAPTURE_CONTEXT_SWITCHES, 100) +# define ProfEndCapture(...) tmClose(0) +# define ProfThreadName(...) (TM_API_PTR ? TM_API_PTR->_tmThreadName(0, 0, __VA_ARGS__) : (void)0) +# define ProfMsg(...) (TM_API_PTR ? TM_API_PTR->_tmMessageV_Core(0, TMMF_ICON_NOTE, __FILE__, &g_telemetry_filename_id, __LINE__, __VA_ARGS__) : (void)0) +# define ProfBeginLockWait(...) tmStartWaitForLock(0, 0, __VA_ARGS__) +# define ProfEndLockWait(...) tmEndWaitForLock(0) +# define ProfLockTake(...) tmAcquiredLock(0, 0, __VA_ARGS__) +# define ProfLockDrop(...) tmReleasedLock(0, __VA_ARGS__) +# define ProfColor(color) tmZoneColorSticky(color) +#endif + +//////////////////////////////// +//~ rjf: Zeroify Undefined Defines + +#if !defined(ProfBegin) +# define ProfBegin(...) (0) +# define ProfBeginDynamic(...) (0) +# define ProfEnd(...) (0) +# define ProfTick(...) (0) +# define ProfIsCapturing(...) (0) +# define ProfBeginCapture(...) (0) +# define ProfEndCapture(...) (0) +# define ProfThreadName(...) (0) +# define ProfMsg(...) (0) +# define ProfBeginLockWait(...) (0) +# define ProfEndLockWait(...) (0) +# define ProfLockTake(...) (0) +# define ProfLockDrop(...) (0) +# define ProfColor(...) (0) +#endif + +//////////////////////////////// +//~ rjf: Helper Wrappers + +#define ProfBeginFunction(...) ProfBegin(this_function_name) +#define ProfScope(...) DeferLoop(ProfBeginDynamic(__VA_ARGS__), ProfEnd()) + +#endif // BASE_PROFILE_H diff --git a/metagen/metagen_base/metagen_base_strings.c b/metagen/metagen_base/metagen_base_strings.c new file mode 100644 index 0000000..d88a708 --- /dev/null +++ b/metagen/metagen_base/metagen_base_strings.c @@ -0,0 +1,1975 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Third Party Includes + +#if !BUILD_SUPPLEMENTARY_UNIT +# define STB_SPRINTF_IMPLEMENTATION +# define STB_SPRINTF_STATIC +# include "third_party/stb/stb_sprintf.h" +#endif + +//////////////////////////////// +//~ NOTE(allen): String <-> Integer Tables + +read_only global U8 integer_symbols[16] = { + '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F', +}; + +// NOTE(allen): Includes reverses for uppercase and lowercase hex. +read_only global U8 integer_symbol_reverse[128] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, +}; + +read_only global U8 base64[64] = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + '_', '$', +}; + +read_only global U8 base64_reverse[128] = { + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0x3F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0xFF,0xFF,0xFF,0xFF,0xFF,0x00, + 0xFF,0x24,0x25,0x26,0x27,0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,0x32, + 0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B,0x3C,0x3D,0xFF,0xFF,0xFF,0xFF,0x3E, + 0xFF,0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18, + 0x19,0x1A,0x1B,0x1C,0x1D,0x1E,0x1F,0x20,0x21,0x22,0x23,0xFF,0xFF,0xFF,0xFF,0xFF, +}; + +//////////////////////////////// +//~ rjf: Character Classification & Conversion Functions + +internal B32 +char_is_space(U8 c){ + return(c == ' ' || c == '\n' || c == '\t' || c == '\r' || c == '\f' || c == '\v'); +} + +internal B32 +char_is_upper(U8 c){ + return('A' <= c && c <= 'Z'); +} + +internal B32 +char_is_lower(U8 c){ + return('a' <= c && c <= 'z'); +} + +internal B32 +char_is_alpha(U8 c){ + return(char_is_upper(c) || char_is_lower(c)); +} + +internal B32 +char_is_slash(U8 c){ + return(c == '/' || c == '\\'); +} + +internal B32 +char_is_digit(U8 c, U32 base){ + B32 result = 0; + if (0 < base && base <= 16){ + U8 val = integer_symbol_reverse[c]; + if (val < base){ + result = 1; + } + } + return(result); +} + +internal U8 +char_to_lower(U8 c){ + if (char_is_upper(c)){ + c += ('a' - 'A'); + } + return(c); +} + +internal U8 +char_to_upper(U8 c){ + if (char_is_lower(c)){ + c += ('A' - 'a'); + } + return(c); +} + +internal U8 +char_to_correct_slash(U8 c){ + if(char_is_slash(c)){ + c = '/'; + } + return(c); +} + +//////////////////////////////// +//~ rjf: C-String Measurement + +internal U64 +cstring8_length(U8 *c){ + U8 *p = c; + for (;*p != 0; p += 1); + return(p - c); +} + +internal U64 +cstring16_length(U16 *c){ + U16 *p = c; + for (;*p != 0; p += 1); + return(p - c); +} + +internal U64 +cstring32_length(U32 *c){ + U32 *p = c; + for (;*p != 0; p += 1); + return(p - c); +} + +//////////////////////////////// +//~ rjf: String Constructors + +internal String8 +str8(U8 *str, U64 size){ + String8 result = {str, size}; + return(result); +} + +internal String8 +str8_range(U8 *first, U8 *one_past_last){ + String8 result = {first, (U64)(one_past_last - first)}; + return(result); +} + +internal String8 +str8_zero(void){ + String8 result = {0}; + return(result); +} + +internal String16 +str16(U16 *str, U64 size){ + String16 result = {str, size}; + return(result); +} + +internal String16 +str16_range(U16 *first, U16 *one_past_last){ + String16 result = {first, (U64)(one_past_last - first)}; + return(result); +} + +internal String16 +str16_zero(void){ + String16 result = {0}; + return(result); +} + +internal String32 +str32(U32 *str, U64 size){ + String32 result = {str, size}; + return(result); +} + +internal String32 +str32_range(U32 *first, U32 *one_past_last){ + String32 result = {first, (U64)(one_past_last - first)}; + return(result); +} + +internal String32 +str32_zero(void){ + String32 result = {0}; + return(result); +} + +internal String8 +str8_cstring(char *c){ + String8 result = {(U8*)c, cstring8_length((U8*)c)}; + return(result); +} + +internal String16 +str16_cstring(U16 *c){ + String16 result = {(U16*)c, cstring16_length((U16*)c)}; + return(result); +} + +internal String32 +str32_cstring(U32 *c){ + String32 result = {(U32*)c, cstring32_length((U32*)c)}; + return(result); +} + +internal String8 +str8_cstring_capped(void *cstr, void *cap) +{ + char *ptr = (char*)cstr; + char *opl = (char*)cap; + for (;ptr < opl && *ptr != 0; ptr += 1); + U64 size = (U64)(ptr - (char *)cstr); + String8 result = {(U8*)cstr, size}; + return(result); +} + +//////////////////////////////// +//~ rjf: String Stylization + +internal String8 +upper_from_str8(Arena *arena, String8 string) +{ + string = push_str8_copy(arena, string); + for(U64 idx = 0; idx < string.size; idx += 1) + { + string.str[idx] = char_to_upper(string.str[idx]); + } + return string; +} + +internal String8 +lower_from_str8(Arena *arena, String8 string) +{ + string = push_str8_copy(arena, string); + for(U64 idx = 0; idx < string.size; idx += 1) + { + string.str[idx] = char_to_lower(string.str[idx]); + } + return string; +} + +internal String8 +backslashed_from_str8(Arena *arena, String8 string) +{ + string = push_str8_copy(arena, string); + for(U64 idx = 0; idx < string.size; idx += 1) + { + string.str[idx] = char_is_slash(string.str[idx]) ? '\\' : string.str[idx]; + } + return string; +} + +//////////////////////////////// +//~ rjf: String Matching + +internal B32 +str8_match(String8 a, String8 b, StringMatchFlags flags){ + B32 result = 0; + if (a.size == b.size || (flags & StringMatchFlag_RightSideSloppy)){ + B32 case_insensitive = (flags & StringMatchFlag_CaseInsensitive); + B32 slash_insensitive = (flags & StringMatchFlag_SlashInsensitive); + U64 size = Min(a.size, b.size); + result = 1; + for (U64 i = 0; i < size; i += 1){ + U8 at = a.str[i]; + U8 bt = b.str[i]; + if (case_insensitive){ + at = char_to_upper(at); + bt = char_to_upper(bt); + } + if (slash_insensitive){ + at = char_to_correct_slash(at); + bt = char_to_correct_slash(bt); + } + if (at != bt){ + result = 0; + break; + } + } + } + return(result); +} + +internal U64 +str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags){ + U8 *p = string.str + start_pos; + U64 stop_offset = Max(string.size + 1, needle.size) - needle.size; + U8 *stop_p = string.str + stop_offset; + if (needle.size > 0){ + U8 *string_opl = string.str + string.size; + String8 needle_tail = str8_skip(needle, 1); + StringMatchFlags adjusted_flags = flags | StringMatchFlag_RightSideSloppy; + U8 needle_first_char_adjusted = needle.str[0]; + if(adjusted_flags & StringMatchFlag_CaseInsensitive){ + needle_first_char_adjusted = char_to_upper(needle_first_char_adjusted); + } + for (;p < stop_p; p += 1){ + U8 haystack_char_adjusted = *p; + if(adjusted_flags & StringMatchFlag_CaseInsensitive){ + haystack_char_adjusted = char_to_upper(haystack_char_adjusted); + } + if (haystack_char_adjusted == needle_first_char_adjusted){ + if (str8_match(str8_range(p + 1, string_opl), needle_tail, adjusted_flags)){ + break; + } + } + } + } + U64 result = string.size; + if (p < stop_p){ + result = (U64)(p - string.str); + } + return(result); +} + +internal B32 +str8_ends_with(String8 string, String8 end, StringMatchFlags flags){ + String8 postfix = str8_postfix(string, end.size); + B32 is_match = str8_match(end, postfix, flags); + return is_match; +} + +//////////////////////////////// +//~ rjf: String Slicing + +internal String8 +str8_substr(String8 str, Rng1U64 range){ + range.min = ClampTop(range.min, str.size); + range.max = ClampTop(range.max, str.size); + str.str += range.min; + str.size = dim_1u64(range); + return(str); +} + +internal String8 +str8_prefix(String8 str, U64 size){ + str.size = ClampTop(size, str.size); + return(str); +} + +internal String8 +str8_skip(String8 str, U64 amt){ + amt = ClampTop(amt, str.size); + str.str += amt; + str.size -= amt; + return(str); +} + +internal String8 +str8_postfix(String8 str, U64 size){ + size = ClampTop(size, str.size); + str.str = (str.str + str.size) - size; + str.size = size; + return(str); +} + +internal String8 +str8_chop(String8 str, U64 amt){ + amt = ClampTop(amt, str.size); + str.size -= amt; + return(str); +} + +internal String8 +str8_skip_chop_whitespace(String8 string){ + U8 *first = string.str; + U8 *opl = first + string.size; + for (;first < opl; first += 1){ + if (!char_is_space(*first)){ + break; + } + } + for (;opl > first;){ + opl -= 1; + if (!char_is_space(*opl)){ + opl += 1; + break; + } + } + String8 result = str8_range(first, opl); + return(result); +} + +//////////////////////////////// +//~ rjf: String Formatting & Copying + +internal String8 +push_str8_cat(Arena *arena, String8 s1, String8 s2){ + String8 str; + str.size = s1.size + s2.size; + str.str = push_array_no_zero(arena, U8, str.size + 1); + MemoryCopy(str.str, s1.str, s1.size); + MemoryCopy(str.str + s1.size, s2.str, s2.size); + str.str[str.size] = 0; + return(str); +} + +internal String8 +push_str8_copy(Arena *arena, String8 s){ + String8 str; + str.size = s.size; + str.str = push_array_no_zero(arena, U8, str.size + 1); + MemoryCopy(str.str, s.str, s.size); + str.str[str.size] = 0; + return(str); +} + +internal String8 +push_str8fv(Arena *arena, char *fmt, va_list args){ + va_list args2; + va_copy(args2, args); + U32 needed_bytes = raddbg_vsnprintf(0, 0, fmt, args) + 1; + String8 result = {0}; + result.str = push_array_no_zero(arena, U8, needed_bytes); + result.size = raddbg_vsnprintf((char*)result.str, needed_bytes, fmt, args2); + result.str[result.size] = 0; + va_end(args2); + return(result); +} + +internal String8 +push_str8f(Arena *arena, char *fmt, ...){ + va_list args; + va_start(args, fmt); + String8 result = push_str8fv(arena, fmt, args); + va_end(args); + return(result); +} + +//////////////////////////////// +//~ rjf: String <=> Integer Conversions + +//- rjf: string -> integer + +internal S64 +sign_from_str8(String8 string, String8 *string_tail){ + // count negative signs + U64 neg_count = 0; + U64 i = 0; + for (; i < string.size; i += 1){ + if (string.str[i] == '-'){ + neg_count += 1; + } + else if (string.str[i] != '+'){ + break; + } + } + + // output part of string after signs + *string_tail = str8_skip(string, i); + + // output integer sign + S64 sign = (neg_count & 1)?-1:+1; + return(sign); +} + +internal B32 +str8_is_integer(String8 string, U32 radix){ + B32 result = 0; + if (string.size > 0){ + if (1 < radix && radix <= 16){ + result = 1; + for (U64 i = 0; i < string.size; i += 1){ + U8 c = string.str[i]; + if (!(c < 0x80) || integer_symbol_reverse[c] >= radix){ + result = 0; + break; + } + } + } + } + return(result); +} + +internal U64 +u64_from_str8(String8 string, U32 radix){ + U64 x = 0; + if (1 < radix && radix <= 16){ + for (U64 i = 0; i < string.size; i += 1){ + x *= radix; + x += integer_symbol_reverse[string.str[i]&0x7F]; + } + } + return(x); +} + +internal S64 +s64_from_str8(String8 string, U32 radix){ + S64 sign = sign_from_str8(string, &string); + S64 x = (S64)u64_from_str8(string, radix) * sign; + return(x); +} + +internal B32 +try_u64_from_str8_c_rules(String8 string, U64 *x){ + B32 is_integer = 0; + if (str8_is_integer(string, 10)){ + is_integer = 1; + *x = u64_from_str8(string, 10); + } + else{ + String8 hex_string = str8_skip(string, 2); + if (str8_match(str8_prefix(string, 2), str8_lit("0x"), 0) && + str8_is_integer(hex_string, 0x10)){ + is_integer = 1; + *x = u64_from_str8(hex_string, 0x10); + } + else if (str8_match(str8_prefix(string, 2), str8_lit("0b"), 0) && + str8_is_integer(hex_string, 2)){ + is_integer = 1; + *x = u64_from_str8(hex_string, 2); + } + else{ + String8 oct_string = str8_skip(string, 1); + if (str8_match(str8_prefix(string, 1), str8_lit("0"), 0) && + str8_is_integer(hex_string, 010)){ + is_integer = 1; + *x = u64_from_str8(oct_string, 010); + } + } + } + return(is_integer); +} + +internal B32 +try_s64_from_str8_c_rules(String8 string, S64 *x){ + String8 string_tail = {0}; + S64 sign = sign_from_str8(string, &string_tail); + U64 x_u64 = 0; + B32 is_integer = try_u64_from_str8_c_rules(string_tail, &x_u64); + *x = x_u64*sign; + return(is_integer); +} + +//- rjf: integer -> string + +internal String8 +str8_from_memory_size(Arena *arena, U64 z){ + String8 result = {0}; + if (z < KB(1)){ + result = push_str8f(arena, "%llu b", z); + } + else if (z < MB(1)){ + result = push_str8f(arena, "%llu.%02llu Kb", z/KB(1), ((100*z)/KB(1))%100); + } + else if (z < GB(1)){ + result = push_str8f(arena, "%llu.%02llu Mb", z/MB(1), ((100*z)/MB(1))%100); + } + else{ + result = push_str8f(arena, "%llu.%02llu Gb", z/GB(1), ((100*z)/GB(1))%100); + } + return(result); +} + +internal String8 +str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator) +{ + String8 result = {0}; + { + // rjf: prefix + String8 prefix = {0}; + switch(radix) + { + case 16:{prefix = str8_lit("0x");}break; + case 8: {prefix = str8_lit("0o");}break; + case 2: {prefix = str8_lit("0b");}break; + } + + // rjf: determine # of chars between separators + U8 digit_group_size = 3; + switch(radix) + { + default:break; + case 2: + case 8: + case 16: + {digit_group_size = 4;}break; + } + + // rjf: prep + U64 needed_leading_0s = 0; + { + U64 needed_digits = 1; + { + U64 u64_reduce = u64; + for(;;) + { + u64_reduce /= radix; + if(u64_reduce == 0) + { + break; + } + needed_digits += 1; + } + } + needed_leading_0s = (min_digits > needed_digits) ? min_digits - needed_digits : 0; + U64 needed_separators = 0; + if(digit_group_separator != 0) + { + needed_separators = (needed_digits+needed_leading_0s)/digit_group_size; + if(needed_separators > 0 && (needed_digits+needed_leading_0s)%digit_group_size == 0) + { + needed_separators -= 1; + } + } + result.size = prefix.size + needed_leading_0s + needed_separators + needed_digits; + result.str = push_array_no_zero(arena, U8, result.size + 1); + result.str[result.size] = 0; + } + + // rjf: fill contents + { + U64 u64_reduce = u64; + U64 digits_until_separator = digit_group_size; + for(U64 idx = 0; idx < result.size; idx += 1) + { + if(digits_until_separator == 0 && digit_group_separator != 0) + { + result.str[result.size - idx - 1] = digit_group_separator; + digits_until_separator = digit_group_size+1; + } + else + { + result.str[result.size - idx - 1] = char_to_lower(integer_symbols[u64_reduce%radix]); + u64_reduce /= radix; + } + digits_until_separator -= 1; + if(u64_reduce == 0) + { + break; + } + } + for(U64 leading_0_idx = 0; leading_0_idx < needed_leading_0s; leading_0_idx += 1) + { + result.str[prefix.size + leading_0_idx] = '0'; + } + } + + // rjf: fill prefix + if(prefix.size != 0) + { + MemoryCopy(result.str, prefix.str, prefix.size); + } + } + return result; +} + +internal String8 +str8_from_s64(Arena *arena, S64 s64, U32 radix, U8 min_digits, U8 digit_group_separator) +{ + String8 result = {0}; + // TODO(rjf): preeeeetty sloppy... + if(s64 < 0) + { + Temp scratch = scratch_begin(&arena, 1); + String8 numeric_part = str8_from_u64(scratch.arena, (U64)(-s64), radix, min_digits, digit_group_separator); + result = push_str8f(arena, "-%S", numeric_part); + scratch_end(scratch); + } + else + { + result = str8_from_u64(arena, (U64)s64, radix, min_digits, digit_group_separator); + } + return result; +} + +//////////////////////////////// +//~ rjf: String <=> Float Conversions + +internal F64 +f64_from_str8(String8 string) +{ + // TODO(rjf): crappy implementation for now that just uses atof. + F64 result = 0; + if(string.size > 0) + { + // rjf: find starting pos of numeric string, as well as sign + F64 sign = +1.0; + //U64 first_numeric = 0; + if(string.str[0] == '-') + { + //first_numeric = 1; + sign = -1.0; + } + else if(string.str[0] == '+') + { + //first_numeric = 1; + sign = 1.0; + } + + // rjf: gather numerics + U64 num_valid_chars = 0; + char buffer[64]; + for(U64 idx = 0; idx < string.size && num_valid_chars < sizeof(buffer)-1; idx += 1) + { + if(char_is_digit(string.str[idx], 10) || string.str[idx] == '.') + { + buffer[num_valid_chars] = string.str[idx]; + num_valid_chars += 1; + } + } + + // rjf: null-terminate (the reason for all of this!!!!!!) + buffer[num_valid_chars] = 0; + + // rjf: do final conversion + result = sign * atof(buffer); + } + return result; +} + +//////////////////////////////// +//~ rjf: String List Construction Functions + +internal String8Node* +str8_list_push_node(String8List *list, String8Node *node){ + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size += node->string.size; + return(node); +} + +internal String8Node* +str8_list_push_node_set_string(String8List *list, String8Node *node, String8 string){ + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size += string.size; + node->string = string; + return(node); +} + +internal String8Node* +str8_list_push_node_front(String8List *list, String8Node *node){ + SLLQueuePushFront(list->first, list->last, node); + list->node_count += 1; + list->total_size += node->string.size; + return(node); +} + +internal String8Node* +str8_list_push_node_front_set_string(String8List *list, String8Node *node, String8 string){ + SLLQueuePushFront(list->first, list->last, node); + list->node_count += 1; + list->total_size += string.size; + node->string = string; + return(node); +} + +internal String8Node* +str8_list_push(Arena *arena, String8List *list, String8 string){ + String8Node *node = push_array_no_zero(arena, String8Node, 1); + str8_list_push_node_set_string(list, node, string); + return(node); +} + +internal String8Node* +str8_list_push_front(Arena *arena, String8List *list, String8 string){ + String8Node *node = push_array_no_zero(arena, String8Node, 1); + str8_list_push_node_front_set_string(list, node, string); + return(node); +} + +internal void +str8_list_concat_in_place(String8List *list, String8List *to_push){ + if(to_push->node_count != 0){ + if (list->last){ + list->node_count += to_push->node_count; + list->total_size += to_push->total_size; + list->last->next = to_push->first; + list->last = to_push->last; + } + else{ + *list = *to_push; + } + MemoryZeroStruct(to_push); + } +} + +internal String8Node* +str8_list_push_aligner(Arena *arena, String8List *list, U64 min, U64 align){ + String8Node *node = push_array_no_zero(arena, String8Node, 1); + U64 new_size = list->total_size + min; + U64 increase_size = 0; + if (align > 1){ + // NOTE(allen): assert is power of 2 + Assert(((align - 1) & align) == 0); + U64 mask = align - 1; + new_size += mask; + new_size &= (~mask); + increase_size = new_size - list->total_size; + } + local_persist const U8 zeroes_buffer[64] = {0}; + Assert(increase_size <= ArrayCount(zeroes_buffer)); + SLLQueuePush(list->first, list->last, node); + list->node_count += 1; + list->total_size = new_size; + node->string.str = (U8*)zeroes_buffer; + node->string.size = increase_size; + return(node); +} + +internal String8Node* +str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...){ + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(arena, fmt, args); + String8Node *result = str8_list_push(arena, list, string); + va_end(args); + return(result); +} + +internal String8Node* +str8_list_push_frontf(Arena *arena, String8List *list, char *fmt, ...){ + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(arena, fmt, args); + String8Node *result = str8_list_push_front(arena, list, string); + va_end(args); + return(result); +} + +internal String8List +str8_list_copy(Arena *arena, String8List *list){ + String8List result = {0}; + for (String8Node *node = list->first; + node != 0; + node = node->next){ + String8Node *new_node = push_array_no_zero(arena, String8Node, 1); + String8 new_string = push_str8_copy(arena, node->string); + str8_list_push_node_set_string(&result, new_node, new_string); + } + return(result); +} + +internal String8List +str8_split(Arena *arena, String8 string, U8 *split_chars, U64 split_char_count, StringSplitFlags flags){ + String8List list = {0}; + + B32 keep_empties = (flags & StringSplitFlag_KeepEmpties); + + U8 *ptr = string.str; + U8 *opl = string.str + string.size; + for (;ptr < opl;){ + U8 *first = ptr; + for (;ptr < opl; ptr += 1){ + U8 c = *ptr; + B32 is_split = 0; + for (U64 i = 0; i < split_char_count; i += 1){ + if (split_chars[i] == c){ + is_split = 1; + break; + } + } + if (is_split){ + break; + } + } + + String8 string = str8_range(first, ptr); + if (keep_empties || string.size > 0){ + str8_list_push(arena, &list, string); + } + ptr += 1; + } + + return(list); +} + +internal String8List +str8_split_by_string_chars(Arena *arena, String8 string, String8 split_chars, StringSplitFlags flags){ + String8List list = str8_split(arena, string, split_chars.str, split_chars.size, flags); + return list; +} + +internal String8List +str8_list_split_by_string_chars(Arena *arena, String8List list, String8 split_chars, StringSplitFlags flags){ + String8List result = {0}; + for (String8Node *node = list.first; node != 0; node = node->next){ + String8List split = str8_split_by_string_chars(arena, node->string, split_chars, flags); + str8_list_concat_in_place(&result, &split); + } + return result; +} + +internal String8 +str8_list_join(Arena *arena, String8List *list, StringJoin *optional_params){ + StringJoin join = {0}; + if (optional_params != 0){ + MemoryCopyStruct(&join, optional_params); + } + + U64 sep_count = 0; + if (list->node_count > 0){ + sep_count = list->node_count - 1; + } + + String8 result; + result.size = join.pre.size + join.post.size + sep_count*join.sep.size + list->total_size; + U8 *ptr = result.str = push_array_no_zero(arena, U8, result.size + 1); + + MemoryCopy(ptr, join.pre.str, join.pre.size); + ptr += join.pre.size; + for (String8Node *node = list->first; + node != 0; + node = node->next){ + MemoryCopy(ptr, node->string.str, node->string.size); + ptr += node->string.size; + if (node->next != 0){ + MemoryCopy(ptr, join.sep.str, join.sep.size); + ptr += join.sep.size; + } + } + MemoryCopy(ptr, join.post.str, join.post.size); + ptr += join.post.size; + + *ptr = 0; + + return(result); +} + +internal void +str8_list_from_flags(Arena *arena, String8List *list, + U32 flags, String8 *flag_string_table, U32 flag_string_count){ + for (U32 i = 0; i < flag_string_count; i += 1){ + U32 flag = (1 << i); + if (flags & flag){ + str8_list_push(arena, list, flag_string_table[i]); + } + } +} + +//////////////////////////////// +//~ rjf; String Arrays + +internal String8Array +str8_array_from_list(Arena *arena, String8List *list) +{ + String8Array array; + array.count = list->node_count; + array.v = push_array_no_zero(arena, String8, array.count); + U64 idx = 0; + for(String8Node *n = list->first; n != 0; n = n->next, idx += 1) + { + array.v[idx] = n->string; + } + return array; +} + +internal String8Array +str8_array_reserve(Arena *arena, U64 count) +{ + String8Array arr; + arr.count = 0; + arr.v = push_array(arena, String8, count); + return arr; +} + +//////////////////////////////// +//~ rjf: String Path Helpers + +internal String8 +str8_chop_last_slash(String8 string){ + if (string.size > 0){ + U8 *ptr = string.str + string.size - 1; + for (;ptr >= string.str; ptr -= 1){ + if (*ptr == '/' || *ptr == '\\'){ + break; + } + } + if (ptr >= string.str){ + string.size = (U64)(ptr - string.str); + } + else{ + string.size = 0; + } + } + return(string); +} + +internal String8 +str8_skip_last_slash(String8 string){ + if (string.size > 0){ + U8 *ptr = string.str + string.size - 1; + for (;ptr >= string.str; ptr -= 1){ + if (*ptr == '/' || *ptr == '\\'){ + break; + } + } + if (ptr >= string.str){ + ptr += 1; + string.size = (U64)(string.str + string.size - ptr); + string.str = ptr; + } + } + return(string); +} + +internal String8 +str8_chop_last_dot(String8 string) +{ + String8 result = string; + U64 p = string.size; + for (;p > 0;){ + p -= 1; + if (string.str[p] == '.'){ + result = str8_prefix(string, p); + break; + } + } + return(result); +} + +internal String8 +str8_skip_last_dot(String8 string){ + String8 result = string; + U64 p = string.size; + for (;p > 0;){ + p -= 1; + if (string.str[p] == '.'){ + result = str8_skip(string, p + 1); + break; + } + } + return(result); +} + +internal PathStyle +path_style_from_str8(String8 string){ + PathStyle result = PathStyle_Relative; + if (string.size >= 1 && string.str[0] == '/'){ + result = PathStyle_UnixAbsolute; + } + else if (string.size >= 2 && + char_is_alpha(string.str[0]) && + string.str[1] == ':'){ + if (string.size == 2 || + char_is_slash(string.str[2])){ + result = PathStyle_WindowsAbsolute; + } + } + return(result); +} + +internal String8List +str8_split_path(Arena *arena, String8 string){ + String8List result = str8_split(arena, string, (U8*)"/\\", 2, 0); + return(result); +} + +internal void +str8_path_list_resolve_dots_in_place(String8List *path, PathStyle style){ + Temp scratch = scratch_begin(0, 0); + + String8MetaNode *stack = 0; + String8MetaNode *free_meta_node = 0; + String8Node *first = path->first; + + MemoryZeroStruct(path); + for (String8Node *node = first, *next = 0; + node != 0; + node = next){ + // save next now + next = node->next; + + // cases: + if (node == first && style == PathStyle_WindowsAbsolute){ + goto save_without_stack; + } + if (node->string.size == 1 && node->string.str[0] == '.'){ + goto do_nothing; + } + if (node->string.size == 2 && node->string.str[0] == '.' && node->string.str[1] == '.'){ + if (stack != 0){ + goto eliminate_stack_top; + } + else{ + goto save_without_stack; + } + } + goto save_with_stack; + + + // handlers: + save_with_stack: + { + str8_list_push_node(path, node); + + String8MetaNode *stack_node = free_meta_node; + if (stack_node != 0){ + SLLStackPop(free_meta_node); + } + else{ + stack_node = push_array_no_zero(scratch.arena, String8MetaNode, 1); + } + SLLStackPush(stack, stack_node); + stack_node->node = node; + + continue; + } + + save_without_stack: + { + str8_list_push_node(path, node); + + continue; + } + + eliminate_stack_top: + { + path->node_count -= 1; + path->total_size -= stack->node->string.size; + + SLLStackPop(stack); + + if (stack == 0){ + path->last = path->first; + } + else{ + path->last = stack->node; + } + continue; + } + + do_nothing: continue; + } + scratch_end(scratch); +} + +internal String8 +str8_path_list_join_by_style(Arena *arena, String8List *path, PathStyle style){ + StringJoin params = {0}; + switch (style){ + case PathStyle_Relative: + case PathStyle_WindowsAbsolute: + { + params.sep = str8_lit("/"); + }break; + + case PathStyle_UnixAbsolute: + { + params.pre = str8_lit("/"); + params.sep = str8_lit("/"); + }break; + } + + String8 result = str8_list_join(arena, path, ¶ms); + return(result); +} + +internal String8TxtPtPair +str8_txt_pt_pair_from_string(String8 string) +{ + String8TxtPtPair pair = {0}; + { + String8 file_part = {0}; + String8 line_part = {0}; + String8 col_part = {0}; + + // rjf: grab file part + for(U64 idx = 0; idx <= string.size; idx += 1) + { + U8 byte = (idx < string.size) ? (string.str[idx]) : 0; + U8 next_byte = ((idx+1 < string.size) ? (string.str[idx+1]) : 0); + if(byte == ':' && next_byte != '/' && next_byte != '\\') + { + file_part = str8_prefix(string, idx); + line_part = str8_skip(string, idx+1); + break; + } + else if(byte == 0) + { + file_part = string; + break; + } + } + + // rjf: grab line/column + { + U64 colon_pos = str8_find_needle(line_part, 0, str8_lit(":"), 0); + if(colon_pos < line_part.size) + { + col_part = str8_skip(line_part, colon_pos+1); + line_part = str8_prefix(line_part, colon_pos); + } + } + + // rjf: convert line/column strings to numerics + U64 line = 0; + U64 column = 0; + try_u64_from_str8_c_rules(line_part, &line); + try_u64_from_str8_c_rules(col_part, &column); + + // rjf: fill + pair.string = file_part; + pair.pt = txt_pt((S64)line, (S64)column); + if(pair.pt.line == 0) { pair.pt.line = 1; } + if(pair.pt.column == 0) { pair.pt.column = 1; } + } + return pair; +} + +//////////////////////////////// +//~ rjf: UTF-8 & UTF-16 Decoding/Encoding + +read_only global U8 utf8_class[32] = { + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,2,2,2,2,3,3,4,5, +}; + +internal UnicodeDecode +utf8_decode(U8 *str, U64 max){ + UnicodeDecode result = {1, max_U32}; + U8 byte = str[0]; + U8 byte_class = utf8_class[byte >> 3]; + switch (byte_class) + { + case 1: + { + result.codepoint = byte; + }break; + case 2: + { + if (2 < max) + { + U8 cont_byte = str[1]; + if (utf8_class[cont_byte >> 3] == 0) + { + result.codepoint = (byte & bitmask5) << 6; + result.codepoint |= (cont_byte & bitmask6); + result.inc = 2; + } + } + }break; + case 3: + { + if (2 < max) + { + U8 cont_byte[2] = {str[1], str[2]}; + if (utf8_class[cont_byte[0] >> 3] == 0 && + utf8_class[cont_byte[1] >> 3] == 0) + { + result.codepoint = (byte & bitmask4) << 12; + result.codepoint |= ((cont_byte[0] & bitmask6) << 6); + result.codepoint |= (cont_byte[1] & bitmask6); + result.inc = 3; + } + } + }break; + case 4: + { + if (3 < max) + { + U8 cont_byte[3] = {str[1], str[2], str[3]}; + if (utf8_class[cont_byte[0] >> 3] == 0 && + utf8_class[cont_byte[1] >> 3] == 0 && + utf8_class[cont_byte[2] >> 3] == 0) + { + result.codepoint = (byte & bitmask3) << 18; + result.codepoint |= ((cont_byte[0] & bitmask6) << 12); + result.codepoint |= ((cont_byte[1] & bitmask6) << 6); + result.codepoint |= (cont_byte[2] & bitmask6); + result.inc = 4; + } + } + } + } + return(result); +} + +internal UnicodeDecode +utf16_decode(U16 *str, U64 max){ + UnicodeDecode result = {1, max_U32}; + result.codepoint = str[0]; + result.inc = 1; + if (max > 1 && 0xD800 <= str[0] && str[0] < 0xDC00 && 0xDC00 <= str[1] && str[1] < 0xE000){ + result.codepoint = ((str[0] - 0xD800) << 10) | ((str[1] - 0xDC00) + 0x10000); + result.inc = 2; + } + return(result); +} + +internal U32 +utf8_encode(U8 *str, U32 codepoint){ + U32 inc = 0; + if (codepoint <= 0x7F){ + str[0] = (U8)codepoint; + inc = 1; + } + else if (codepoint <= 0x7FF){ + str[0] = (bitmask2 << 6) | ((codepoint >> 6) & bitmask5); + str[1] = bit8 | (codepoint & bitmask6); + inc = 2; + } + else if (codepoint <= 0xFFFF){ + str[0] = (bitmask3 << 5) | ((codepoint >> 12) & bitmask4); + str[1] = bit8 | ((codepoint >> 6) & bitmask6); + str[2] = bit8 | ( codepoint & bitmask6); + inc = 3; + } + else if (codepoint <= 0x10FFFF){ + str[0] = (bitmask4 << 4) | ((codepoint >> 18) & bitmask3); + str[1] = bit8 | ((codepoint >> 12) & bitmask6); + str[2] = bit8 | ((codepoint >> 6) & bitmask6); + str[3] = bit8 | ( codepoint & bitmask6); + inc = 4; + } + else{ + str[0] = '?'; + inc = 1; + } + return(inc); +} + +internal U32 +utf16_encode(U16 *str, U32 codepoint){ + U32 inc = 1; + if (codepoint == max_U32){ + str[0] = (U16)'?'; + } + else if (codepoint < 0x10000){ + str[0] = (U16)codepoint; + } + else{ + U32 v = codepoint - 0x10000; + str[0] = safe_cast_u16(0xD800 + (v >> 10)); + str[1] = safe_cast_u16(0xDC00 + (v & bitmask10)); + inc = 2; + } + return(inc); +} + +internal U32 +utf8_from_utf32_single(U8 *buffer, U32 character){ + return(utf8_encode(buffer, character)); +} + +//////////////////////////////// +//~ rjf: Unicode String Conversions + +internal String8 +str8_from_16(Arena *arena, String16 in){ + U64 cap = in.size*3; + U8 *str = push_array_no_zero(arena, U8, cap + 1); + U16 *ptr = in.str; + U16 *opl = ptr + in.size; + U64 size = 0; + UnicodeDecode consume; + for (;ptr < opl; ptr += consume.inc){ + consume = utf16_decode(ptr, opl - ptr); + size += utf8_encode(str + size, consume.codepoint); + } + str[size] = 0; + arena_pop(arena, (cap - size)); + return(str8(str, size)); +} + +internal String16 +str16_from_8(Arena *arena, String8 in){ + U64 cap = in.size*2; + U16 *str = push_array_no_zero(arena, U16, cap + 1); + U8 *ptr = in.str; + U8 *opl = ptr + in.size; + U64 size = 0; + UnicodeDecode consume; + for (;ptr < opl; ptr += consume.inc){ + consume = utf8_decode(ptr, opl - ptr); + size += utf16_encode(str + size, consume.codepoint); + } + str[size] = 0; + arena_pop(arena, (cap - size)*2); + return(str16(str, size)); +} + +internal String8 +str8_from_32(Arena *arena, String32 in){ + U64 cap = in.size*4; + U8 *str = push_array_no_zero(arena, U8, cap + 1); + U32 *ptr = in.str; + U32 *opl = ptr + in.size; + U64 size = 0; + for (;ptr < opl; ptr += 1){ + size += utf8_encode(str + size, *ptr); + } + str[size] = 0; + arena_pop(arena, (cap - size)); + return(str8(str, size)); +} + +internal String32 +str32_from_8(Arena *arena, String8 in){ + U64 cap = in.size; + U32 *str = push_array_no_zero(arena, U32, cap + 1); + U8 *ptr = in.str; + U8 *opl = ptr + in.size; + U64 size = 0; + UnicodeDecode consume; + for (;ptr < opl; ptr += consume.inc){ + consume = utf8_decode(ptr, opl - ptr); + str[size] = consume.codepoint; + size += 1; + } + str[size] = 0; + arena_pop(arena, (cap - size)*4); + return(str32(str, size)); +} + +//////////////////////////////// +//~ rjf: Basic Types & Space Enum -> String Conversions + +internal String8 +string_from_dimension(Dimension dimension){ + local_persist String8 strings[] = { + str8_lit_comp("X"), + str8_lit_comp("Y"), + str8_lit_comp("Z"), + str8_lit_comp("W"), + }; + String8 result = str8_lit("error"); + if ((U32)dimension < 4){ + result = strings[dimension]; + } + return(result); +} + +internal String8 +string_from_side(Side side){ + local_persist String8 strings[] = { + str8_lit_comp("Min"), + str8_lit_comp("Max"), + }; + String8 result = str8_lit("error"); + if ((U32)side < 2){ + result = strings[side]; + } + return(result); +} + +internal String8 +string_from_operating_system(OperatingSystem os){ + local_persist String8 strings[] = { + str8_lit_comp("Null"), + str8_lit_comp("Windows"), + str8_lit_comp("Linux"), + str8_lit_comp("Mac"), + }; + String8 result = str8_lit("error"); + if (os < OperatingSystem_COUNT){ + result = strings[os]; + } + return(result); +} + +internal String8 +string_from_architecture(Architecture arch){ + local_persist String8 strings[] = { + str8_lit_comp("Null"), + str8_lit_comp("x64"), + str8_lit_comp("x86"), + str8_lit_comp("arm64"), + str8_lit_comp("arm32"), + }; + String8 result = str8_lit("error"); + if (arch < Architecture_COUNT){ + result = strings[arch]; + } + return(result); +} + +//////////////////////////////// +//~ rjf: Time Types -> String + +internal String8 +string_from_week_day(WeekDay week_day){ + local_persist String8 strings[] = { + str8_lit_comp("Sun"), + str8_lit_comp("Mon"), + str8_lit_comp("Tue"), + str8_lit_comp("Wed"), + str8_lit_comp("Thu"), + str8_lit_comp("Fri"), + str8_lit_comp("Sat"), + }; + String8 result = str8_lit("Err"); + if ((U32)week_day < WeekDay_COUNT){ + result = strings[week_day]; + } + return(result); +} + +internal String8 +string_from_month(Month month){ + local_persist String8 strings[] = { + str8_lit_comp("Jan"), + str8_lit_comp("Feb"), + str8_lit_comp("Mar"), + str8_lit_comp("Apr"), + str8_lit_comp("May"), + str8_lit_comp("Jun"), + str8_lit_comp("Jul"), + str8_lit_comp("Aug"), + str8_lit_comp("Sep"), + str8_lit_comp("Oct"), + str8_lit_comp("Nov"), + str8_lit_comp("Dec"), + }; + String8 result = str8_lit("Err"); + if ((U32)month < Month_COUNT){ + result = strings[month]; + } + return(result); +} + +internal String8 +push_date_time_string(Arena *arena, DateTime *date_time){ + char *mon_str = (char*)string_from_month(date_time->month).str; + U32 adjusted_hour = date_time->hour%12; + if (adjusted_hour == 0){ + adjusted_hour = 12; + } + char *ampm = "am"; + if (date_time->hour >= 12){ + ampm = "pm"; + } + String8 result = push_str8f(arena, "%d %s %d, %02d:%02d:%02d %s", + date_time->day, mon_str, date_time->year, + adjusted_hour, date_time->min, date_time->sec, ampm); + return(result); +} + +internal String8 +push_file_name_date_time_string(Arena *arena, DateTime *date_time){ + char *mon_str = (char*)string_from_month(date_time->month).str; + String8 result = push_str8f(arena, "%d-%s-%0d--%02d-%02d-%02d", + date_time->year, mon_str, date_time->day, + date_time->hour, date_time->min, date_time->sec); + return(result); +} + +internal String8 +string_from_elapsed_time(Arena *arena, DateTime dt){ + Temp scratch = scratch_begin(&arena, 1); + String8List list = {0}; + if (dt.year){ + str8_list_pushf(scratch.arena, &list, "%dy", dt.year); + str8_list_pushf(scratch.arena, &list, "%um", dt.mon); + str8_list_pushf(scratch.arena, &list, "%ud", dt.day); + } else if (dt.mon){ + str8_list_pushf(scratch.arena, &list, "%um", dt.mon); + str8_list_pushf(scratch.arena, &list, "%ud", dt.day); + } else if (dt.day){ + str8_list_pushf(scratch.arena, &list, "%ud", dt.day); + } + str8_list_pushf(scratch.arena, &list, "%u:%u:%u:%u ms", dt.hour, dt.min, dt.sec, dt.msec); + StringJoin join = { str8_lit_comp(""), str8_lit_comp(" "), str8_lit_comp("") }; + String8 result = str8_list_join(arena, &list, &join); + scratch_end(scratch); + return(result); +} + +//////////////////////////////// +//~ rjf: Basic Text Indentation + +internal String8 +indented_from_string(Arena *arena, String8 string) +{ + Temp scratch = scratch_begin(&arena, 1); + read_only local_persist U8 indentation_bytes[] = " "; + String8List indented_strings = {0}; + S64 depth = 0; + S64 next_depth = 0; + U64 line_begin_off = 0; + for(U64 off = 0; off <= string.size; off += 1) + { + U8 byte = off 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: String <-> Color + +internal String8 +hex_string_from_rgba_4f32(Arena *arena, Vec4F32 rgba) +{ + String8 hex_string = push_str8f(arena, "%02x%02x%02x%02x", (U8)(rgba.x*255.f), (U8)(rgba.y*255.f), (U8)(rgba.z*255.f), (U8)(rgba.w*255.f)); + return hex_string; +} + +internal Vec4F32 +rgba_from_hex_string_4f32(String8 hex_string) +{ + U8 byte_text[8] = {0}; + U64 byte_text_idx = 0; + for(U64 idx = 0; idx < hex_string.size && byte_text_idx < ArrayCount(byte_text); idx += 1) + { + if(char_is_digit(hex_string.str[idx], 16)) + { + byte_text[byte_text_idx] = char_to_lower(hex_string.str[idx]); + byte_text_idx += 1; + } + } + U8 byte_vals[4] = {0}; + for(U64 idx = 0; idx < 4; idx += 1) + { + byte_vals[idx] = (U8)u64_from_str8(str8(&byte_text[idx*2], 2), 16); + } + Vec4F32 rgba = v4f32(byte_vals[0]/255.f, byte_vals[1]/255.f, byte_vals[2]/255.f, byte_vals[3]/255.f); + return rgba; +} + +//////////////////////////////// +//~ rjf: String Fuzzy Matching + +internal FuzzyMatchRangeList +fuzzy_match_find(Arena *arena, String8 needle, String8 haystack) +{ + FuzzyMatchRangeList result = {0}; + Temp scratch = scratch_begin(&arena, 1); + String8List needles = str8_split(scratch.arena, needle, (U8*)" ", 1, 0); + result.needle_part_count = needles.node_count; + for(String8Node *needle_n = needles.first; needle_n != 0; needle_n = needle_n->next) + { + U64 find_pos = 0; + for(;find_pos < haystack.size;) + { + find_pos = str8_find_needle(haystack, find_pos, needle_n->string, StringMatchFlag_CaseInsensitive); + B32 is_in_gathered_ranges = 0; + for(FuzzyMatchRangeNode *n = result.first; n != 0; n = n->next) + { + if(n->range.min <= find_pos && find_pos < n->range.max) + { + is_in_gathered_ranges = 1; + find_pos = n->range.max; + break; + } + } + if(!is_in_gathered_ranges) + { + break; + } + } + if(find_pos < haystack.size) + { + Rng1U64 range = r1u64(find_pos, find_pos+needle_n->string.size); + FuzzyMatchRangeNode *n = push_array(arena, FuzzyMatchRangeNode, 1); + n->range = range; + SLLQueuePush(result.first, result.last, n); + result.count += 1; + result.total_dim += dim_1u64(range); + } + } + scratch_end(scratch); + return result; +} + +internal FuzzyMatchRangeList +fuzzy_match_range_list_copy(Arena *arena, FuzzyMatchRangeList *src) +{ + FuzzyMatchRangeList dst = {0}; + for(FuzzyMatchRangeNode *src_n = src->first; src_n != 0; src_n = src_n->next) + { + FuzzyMatchRangeNode *dst_n = push_array(arena, FuzzyMatchRangeNode, 1); + SLLQueuePush(dst.first, dst.last, dst_n); + dst_n->range = src_n->range; + } + dst.count = src->count; + dst.needle_part_count = src->needle_part_count; + dst.total_dim = src->total_dim; + return dst; +} + +//////////////////////////////// +//~ NOTE(allen): Serialization Helpers + +internal void +str8_serial_begin(Arena *arena, String8List *srl){ + String8Node *node = push_array(arena, String8Node, 1); + node->string.str = push_array_no_zero(arena, U8, 0); + srl->first = srl->last = node; + srl->node_count = 1; + srl->total_size = 0; +} + +internal String8 +str8_serial_end(Arena *arena, String8List *srl){ + U64 size = srl->total_size; + U8 *out = push_array_no_zero(arena, U8, size); + str8_serial_write_to_dst(srl, out); + String8 result = str8(out, size); + return result; +} + +internal void +str8_serial_write_to_dst(String8List *srl, void *out){ + U8 *ptr = (U8*)out; + for (String8Node *node = srl->first; + node != 0; + node = node->next){ + U64 size = node->string.size; + MemoryCopy(ptr, node->string.str, size); + ptr += size; + } +} + +internal U64 +str8_serial_push_align(Arena *arena, String8List *srl, U64 align){ + Assert(IsPow2(align)); + + U64 pos = srl->total_size; + U64 new_pos = AlignPow2(pos, align); + U64 size = (new_pos - pos); + + if(size != 0) + { + U8 *buf = push_array(arena, U8, size); + + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += size; + srl->total_size += size; + } + else{ + str8_list_push(arena, srl, str8(buf, size)); + } + } + return size; +} + +internal void * +str8_serial_push_size(Arena *arena, String8List *srl, U64 size) +{ + void *result = 0; + if(size != 0) + { + U8 *buf = push_array_no_zero(arena, U8, size); + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += size; + srl->total_size += size; + } + else{ + str8_list_push(arena, srl, str8(buf, size)); + } + result = buf; + } + return result; +} + +internal void * +str8_serial_push_data(Arena *arena, String8List *srl, void *data, U64 size){ + void *result = str8_serial_push_size(arena, srl, size); + if(result != 0) + { + MemoryCopy(result, data, size); + } + return result; +} + +internal void +str8_serial_push_data_list(Arena *arena, String8List *srl, String8Node *first){ + for (String8Node *node = first; + node != 0; + node = node->next){ + str8_serial_push_data(arena, srl, node->string.str, node->string.size); + } +} + +internal void +str8_serial_push_u64(Arena *arena, String8List *srl, U64 x){ + U8 *buf = push_array_no_zero(arena, U8, 8); + MemoryCopy(buf, &x, 8); + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += 8; + srl->total_size += 8; + } + else{ + str8_list_push(arena, srl, str8(buf, 8)); + } +} + +internal void +str8_serial_push_u32(Arena *arena, String8List *srl, U32 x){ + U8 *buf = push_array_no_zero(arena, U8, 4); + MemoryCopy(buf, &x, 4); + String8 *str = &srl->last->string; + if (str->str + str->size == buf){ + srl->last->string.size += 4; + srl->total_size += 4; + } + else{ + str8_list_push(arena, srl, str8(buf, 4)); + } +} + +internal void +str8_serial_push_u16(Arena *arena, String8List *srl, U16 x){ + str8_serial_push_data(arena, srl, &x, sizeof(x)); +} + +internal void +str8_serial_push_u8(Arena *arena, String8List *srl, U8 x){ + str8_serial_push_data(arena, srl, &x, sizeof(x)); +} + +internal void +str8_serial_push_cstr(Arena *arena, String8List *srl, String8 str){ + str8_serial_push_data(arena, srl, str.str, str.size); + str8_serial_push_u8(arena, srl, 0); +} + +internal void +str8_serial_push_string(Arena *arena, String8List *srl, String8 str){ + str8_serial_push_data(arena, srl, str.str, str.size); +} + +//////////////////////////////// +//~ rjf: Deserialization Helpers + +internal U64 +str8_deserial_read(String8 string, U64 off, void *read_dst, U64 read_size, U64 granularity) +{ + U64 bytes_left = string.size-Min(off, string.size); + U64 actually_readable_size = Min(bytes_left, read_size); + U64 legally_readable_size = actually_readable_size - actually_readable_size%granularity; + if(legally_readable_size > 0) + { + MemoryCopy(read_dst, string.str+off, legally_readable_size); + } + return legally_readable_size; +} + +internal U64 +str8_deserial_find_first_match(String8 string, U64 off, U16 scan_val) +{ + U64 cursor = off; + for (;;) { + U16 val = 0; + str8_deserial_read_struct(string, cursor, &val); + if (val == scan_val) { + break; + } + cursor += sizeof(val); + } + return cursor; +} + +internal void * +str8_deserial_get_raw_ptr(String8 string, U64 off, U64 size) +{ + void *raw_ptr = 0; + if (off + size <= string.size) { + raw_ptr = string.str + off; + } + return raw_ptr; +} + +internal U64 +str8_deserial_read_cstr(String8 string, U64 off, String8 *cstr_out) +{ + U64 cstr_size = 0; + if (off < string.size) { + U8 *ptr = string.str + off; + U8 *cap = string.str + string.size; + *cstr_out = str8_cstring_capped(ptr, cap); + cstr_size = (cstr_out->size + 1); + } + return cstr_size; +} + +internal U64 +str8_deserial_read_windows_utf16_string16(String8 string, U64 off, String16 *str_out) +{ + U64 null_off = str8_deserial_find_first_match(string, off, 0); + U64 size = null_off - off; + U16 *str = (U16 *)str8_deserial_get_raw_ptr(string, off, size); + U64 count = size / sizeof(*str); + *str_out = str16(str, count); + + U64 read_size_with_null = size + sizeof(*str); + return read_size_with_null; +} + +internal U64 +str8_deserial_read_block(String8 string, U64 off, U64 size, String8 *block_out) +{ + Rng1U64 range = rng_1u64(off, off + size); + *block_out = str8_substr(string, range); + return block_out->size; +} diff --git a/metagen/metagen_base/metagen_base_strings.h b/metagen/metagen_base/metagen_base_strings.h new file mode 100644 index 0000000..c42b350 --- /dev/null +++ b/metagen/metagen_base/metagen_base_strings.h @@ -0,0 +1,381 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_STRINGS_H +#define BASE_STRINGS_H + +//////////////////////////////// +//~ rjf: Third Party Includes + +#define STB_SPRINTF_DECORATE(name) raddbg_##name +#include "third_party/stb/stb_sprintf.h" + +//////////////////////////////// +//~ rjf: String Types + +typedef struct String8 String8; +struct String8 +{ + U8 *str; + U64 size; +}; + +typedef struct String16 String16; +struct String16 +{ + U16 *str; + U64 size; +}; + +typedef struct String32 String32; +struct String32 +{ + U32 *str; + U64 size; +}; + +//////////////////////////////// +//~ rjf: String List & Array Types + +typedef struct String8Node String8Node; +struct String8Node +{ + String8Node *next; + String8 string; +}; + +typedef struct String8MetaNode String8MetaNode; +struct String8MetaNode +{ + String8MetaNode *next; + String8Node *node; +}; + +typedef struct String8List String8List; +struct String8List +{ + String8Node *first; + String8Node *last; + U64 node_count; + U64 total_size; +}; + +typedef struct String8Array String8Array; +struct String8Array +{ + String8 *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: String Matching, Splitting, & Joining Types + +typedef U32 StringMatchFlags; +enum +{ + StringMatchFlag_CaseInsensitive = (1 << 0), + StringMatchFlag_RightSideSloppy = (1 << 1), + StringMatchFlag_SlashInsensitive = (1 << 2), +}; + +typedef U32 StringSplitFlags; +enum +{ + StringSplitFlag_KeepEmpties = (1 << 0), +}; + +typedef enum PathStyle +{ + PathStyle_Relative, + PathStyle_WindowsAbsolute, + PathStyle_UnixAbsolute, + +#if OS_WINDOWS + PathStyle_SystemAbsolute = PathStyle_WindowsAbsolute +#elif OS_LINUX + PathStyle_SystemAbsolute = PathStyle_UnixAbsolute +#else +# error "absolute path style is undefined for this OS" +#endif +} +PathStyle; + +typedef struct StringJoin StringJoin; +struct StringJoin +{ + String8 pre; + String8 sep; + String8 post; +}; + +//////////////////////////////// +//~ rjf: String Pair Types + +typedef struct String8TxtPtPair String8TxtPtPair; +struct String8TxtPtPair +{ + String8 string; + TxtPt pt; +}; + +//////////////////////////////// +//~ rjf: UTF Decoding Types + +typedef struct UnicodeDecode UnicodeDecode; +struct UnicodeDecode +{ + U32 inc; + U32 codepoint; +}; + +//////////////////////////////// +//~ rjf: String Fuzzy Matching Types + +typedef struct FuzzyMatchRangeNode FuzzyMatchRangeNode; +struct FuzzyMatchRangeNode +{ + FuzzyMatchRangeNode *next; + Rng1U64 range; +}; + +typedef struct FuzzyMatchRangeList FuzzyMatchRangeList; +struct FuzzyMatchRangeList +{ + FuzzyMatchRangeNode *first; + FuzzyMatchRangeNode *last; + U64 count; + U64 needle_part_count; + U64 total_dim; +}; + +//////////////////////////////// +//~ rjf: Character Classification & Conversion Functions + +internal B32 char_is_space(U8 c); +internal B32 char_is_upper(U8 c); +internal B32 char_is_lower(U8 c); +internal B32 char_is_alpha(U8 c); +internal B32 char_is_slash(U8 c); +internal B32 char_is_digit(U8 c, U32 base); +internal U8 char_to_lower(U8 c); +internal U8 char_to_upper(U8 c); +internal U8 char_to_correct_slash(U8 c); + +//////////////////////////////// +//~ rjf: C-String Measurement + +internal U64 cstring8_length(U8 *c); +internal U64 cstring16_length(U16 *c); +internal U64 cstring32_length(U32 *c); + +//////////////////////////////// +//~ rjf: String Constructors + +#define str8_lit(S) str8((U8*)(S), sizeof(S) - 1) +#define str8_lit_comp(S) {(U8*)(S), sizeof(S) - 1,} +#define str8_varg(S) (int)((S).size), ((S).str) + +#define str8_array(S,C) str8((U8*)(S), sizeof(*(S))*(C)) +#define str8_array_fixed(S) str8((U8*)(S), sizeof(S)) +#define str8_struct(S) str8((U8*)(S), sizeof(*(S))) + +internal String8 str8(U8 *str, U64 size); +internal String8 str8_range(U8 *first, U8 *one_past_last); +internal String8 str8_zero(void); +internal String16 str16(U16 *str, U64 size); +internal String16 str16_range(U16 *first, U16 *one_past_last); +internal String16 str16_zero(void); +internal String32 str32(U32 *str, U64 size); +internal String32 str32_range(U32 *first, U32 *one_past_last); +internal String32 str32_zero(void); +internal String8 str8_cstring(char *c); +internal String16 str16_cstring(U16 *c); +internal String32 str32_cstring(U32 *c); +internal String8 str8_cstring_capped(void *cstr, void *cap); + +//////////////////////////////// +//~ rjf: String Stylization + +internal String8 upper_from_str8(Arena *arena, String8 string); +internal String8 lower_from_str8(Arena *arena, String8 string); +internal String8 backslashed_from_str8(Arena *arena, String8 string); + +//////////////////////////////// +//~ rjf: String Matching + +internal B32 str8_match(String8 a, String8 b, StringMatchFlags flags); +internal U64 str8_find_needle(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags); +internal B32 str8_ends_with(String8 string, String8 end, StringMatchFlags flags); + +//////////////////////////////// +//~ rjf: String Slicing + +internal String8 str8_substr(String8 str, Rng1U64 range); +internal String8 str8_prefix(String8 str, U64 size); +internal String8 str8_skip(String8 str, U64 amt); +internal String8 str8_postfix(String8 str, U64 size); +internal String8 str8_chop(String8 str, U64 amt); +internal String8 str8_skip_chop_whitespace(String8 string); + +//////////////////////////////// +//~ rjf: String Formatting & Copying + +internal String8 push_str8_cat(Arena *arena, String8 s1, String8 s2); +internal String8 push_str8_copy(Arena *arena, String8 s); +internal String8 push_str8fv(Arena *arena, char *fmt, va_list args); +internal String8 push_str8f(Arena *arena, char *fmt, ...); + +//////////////////////////////// +//~ rjf: String <=> Integer Conversions + +//- rjf: string -> integer +internal S64 sign_from_str8(String8 string, String8 *string_tail); +internal B32 str8_is_integer(String8 string, U32 radix); +internal U64 u64_from_str8(String8 string, U32 radix); +internal S64 s64_from_str8(String8 string, U32 radix); +internal B32 try_u64_from_str8_c_rules(String8 string, U64 *x); +internal B32 try_s64_from_str8_c_rules(String8 string, S64 *x); + +//- rjf: integer -> string +internal String8 str8_from_memory_size(Arena *arena, U64 z); +internal String8 str8_from_u64(Arena *arena, U64 u64, U32 radix, U8 min_digits, U8 digit_group_separator); +internal String8 str8_from_s64(Arena *arena, S64 s64, U32 radix, U8 min_digits, U8 digit_group_separator); + +//////////////////////////////// +//~ rjf: String <=> Float Conversions + +internal F64 f64_from_str8(String8 string); + +//////////////////////////////// +//~ rjf: String List Construction Functions + +internal String8Node* str8_list_push_node(String8List *list, String8Node *node); +internal String8Node* str8_list_push_node_set_string(String8List *list, String8Node *node, String8 string); +internal String8Node* str8_list_push_node_front(String8List *list, String8Node *node); +internal String8Node* str8_list_push_node_front_set_string(String8List *list, String8Node *node, String8 string); +internal String8Node* str8_list_push(Arena *arena, String8List *list, String8 string); +internal String8Node* str8_list_push_front(Arena *arena, String8List *list, String8 string); +internal void str8_list_concat_in_place(String8List *list, String8List *to_push); +internal String8Node* str8_list_push_aligner(Arena *arena, String8List *list, U64 min, U64 align); +internal String8Node* str8_list_pushf(Arena *arena, String8List *list, char *fmt, ...); +internal String8Node* str8_list_push_frontf(Arena *arena, String8List *list, char *fmt, ...); +internal String8List str8_list_copy(Arena *arena, String8List *list); +#define str8_list_first(list) ((list)->first ? (list)->first->string : str8_zero()) + +//////////////////////////////// +//~ rjf: String Splitting & Joining + +internal String8List str8_split(Arena *arena, String8 string, U8 *split_chars, U64 split_char_count, StringSplitFlags flags); +internal String8List str8_split_by_string_chars(Arena *arena, String8 string, String8 split_chars, StringSplitFlags flags); +internal String8List str8_list_split_by_string_chars(Arena *arena, String8List list, String8 split_chars, StringSplitFlags flags); +internal String8 str8_list_join(Arena *arena, String8List *list, StringJoin *optional_params); +internal void str8_list_from_flags(Arena *arena, String8List *list, U32 flags, String8 *flag_string_table, U32 flag_string_count); + +//////////////////////////////// +//~ rjf; String Arrays + +internal String8Array str8_array_from_list(Arena *arena, String8List *list); +internal String8Array str8_array_reserve(Arena *arena, U64 count); + +//////////////////////////////// +//~ rjf: String Path Helpers + +internal String8 str8_chop_last_slash(String8 string); +internal String8 str8_skip_last_slash(String8 string); +internal String8 str8_chop_last_dot(String8 string); +internal String8 str8_skip_last_dot(String8 string); + +internal PathStyle path_style_from_str8(String8 string); +internal String8List str8_split_path(Arena *arena, String8 string); +internal void str8_path_list_resolve_dots_in_place(String8List *path, PathStyle style); +internal String8 str8_path_list_join_by_style(Arena *arena, String8List *path, PathStyle style); + +internal String8TxtPtPair str8_txt_pt_pair_from_string(String8 string); + +//////////////////////////////// +//~ rjf: UTF-8 & UTF-16 Decoding/Encoding + +internal UnicodeDecode utf8_decode(U8 *str, U64 max); +internal UnicodeDecode utf16_decode(U16 *str, U64 max); +internal U32 utf8_encode(U8 *str, U32 codepoint); +internal U32 utf16_encode(U16 *str, U32 codepoint); +internal U32 utf8_from_utf32_single(U8 *buffer, U32 character); + +//////////////////////////////// +//~ rjf: Unicode String Conversions + +internal String8 str8_from_16(Arena *arena, String16 in); +internal String16 str16_from_8(Arena *arena, String8 in); +internal String8 str8_from_32(Arena *arena, String32 in); +internal String32 str32_from_8(Arena *arena, String8 in); + +//////////////////////////////// +//~ rjf: Basic Types & Space Enum -> String Conversions + +internal String8 string_from_dimension(Dimension dimension); +internal String8 string_from_side(Side side); +internal String8 string_from_operating_system(OperatingSystem os); +internal String8 string_from_architecture(Architecture arch); + +//////////////////////////////// +//~ rjf: Time Types -> String + +internal String8 string_from_week_day(WeekDay week_day); +internal String8 string_from_month(Month month); +internal String8 push_date_time_string(Arena *arena, DateTime *date_time); +internal String8 push_file_name_date_time_string(Arena *arena, DateTime *date_time); +internal String8 string_from_elapsed_time(Arena *arena, DateTime dt); + +//////////////////////////////// +//~ rjf: Basic Text Indentation + +internal String8 indented_from_string(Arena *arena, String8 string); + +//////////////////////////////// +//~ rjf: Text Wrapping + +internal String8List wrapped_lines_from_string(Arena *arena, String8 string, U64 first_line_max_width, U64 max_width, U64 wrap_indent); + +//////////////////////////////// +//~ rjf: String <-> Color + +internal String8 hex_string_from_rgba_4f32(Arena *arena, Vec4F32 rgba); +internal Vec4F32 rgba_from_hex_string_4f32(String8 hex_string); + +//////////////////////////////// +//~ rjf: String Fuzzy Matching + +internal FuzzyMatchRangeList fuzzy_match_find(Arena *arena, String8 needle, String8 haystack); +internal FuzzyMatchRangeList fuzzy_match_range_list_copy(Arena *arena, FuzzyMatchRangeList *src); + +//////////////////////////////// +//~ NOTE(allen): Serialization Helpers + +internal void str8_serial_begin(Arena *arena, String8List *srl); +internal String8 str8_serial_end(Arena *arena, String8List *srl); +internal void str8_serial_write_to_dst(String8List *srl, void *out); +internal U64 str8_serial_push_align(Arena *arena, String8List *srl, U64 align); +internal void * str8_serial_push_size(Arena *arena, String8List *srl, U64 size); +internal void * str8_serial_push_data(Arena *arena, String8List *srl, void *data, U64 size); +internal void str8_serial_push_data_list(Arena *arena, String8List *srl, String8Node *first); +internal void str8_serial_push_u64(Arena *arena, String8List *srl, U64 x); +internal void str8_serial_push_u32(Arena *arena, String8List *srl, U32 x); +internal void str8_serial_push_u16(Arena *arena, String8List *srl, U16 x); +internal void str8_serial_push_u8(Arena *arena, String8List *srl, U8 x); +internal void str8_serial_push_cstr(Arena *arena, String8List *srl, String8 str); +internal void str8_serial_push_string(Arena *arena, String8List *srl, String8 str); +#define str8_serial_push_array(arena, srl, ptr, count) str8_serial_push_data(arena, srl, ptr, sizeof(*(ptr)) * (count)) +#define str8_serial_push_struct(arena, srl, ptr) str8_serial_push_array(arena, srl, ptr, 1) + +//////////////////////////////// +//~ rjf: Deserialization Helpers + +internal U64 str8_deserial_read(String8 string, U64 off, void *read_dst, U64 read_size, U64 granularity); +internal U64 str8_deserial_find_first_match(String8 string, U64 off, U16 scan_val); +internal void * str8_deserial_get_raw_ptr(String8 string, U64 off, U64 size);internal U64 str8_deserial_read_cstr(String8 string, U64 off, String8 *cstr_out); +internal U64 str8_deserial_read_windows_utf16_string16(String8 string, U64 off, String16 *str_out); +internal U64 str8_deserial_read_block(String8 string, U64 off, U64 size, String8 *block_out); +#define str8_deserial_read_array(string, off, ptr, count) str8_deserial_read((string), (off), (ptr), sizeof(*(ptr))*(count), sizeof(*(ptr))) +#define str8_deserial_read_struct(string, off, ptr) str8_deserial_read((string), (off), (ptr), sizeof(*(ptr)), sizeof(*(ptr))) + +#endif // BASE_STRINGS_H diff --git a/metagen/metagen_base/metagen_base_thread_context.c b/metagen/metagen_base/metagen_base_thread_context.c new file mode 100644 index 0000000..45ab7af --- /dev/null +++ b/metagen/metagen_base/metagen_base_thread_context.c @@ -0,0 +1,87 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +// NOTE(allen): Thread Context Functions + +C_LINKAGE thread_static TCTX* tctx_thread_local; +#if !BUILD_SUPPLEMENTARY_UNIT +C_LINKAGE thread_static TCTX* tctx_thread_local = 0; +#endif + +internal void +tctx_init_and_equip(TCTX *tctx){ + MemoryZeroStruct(tctx); + Arena **arena_ptr = tctx->arenas; + for (U64 i = 0; i < ArrayCount(tctx->arenas); i += 1, arena_ptr += 1){ + *arena_ptr = arena_alloc(); + } + tctx_thread_local = tctx; +} + +internal void +tctx_release(void) +{ + for(U64 i = 0; i < ArrayCount(tctx_thread_local->arenas); i += 1) + { + arena_release(tctx_thread_local->arenas[i]); + } +} + +internal TCTX* +tctx_get_equipped(void){ + return(tctx_thread_local); +} + +internal Arena* +tctx_get_scratch(Arena **conflicts, U64 count){ + TCTX *tctx = tctx_get_equipped(); + + Arena *result = 0; + Arena **arena_ptr = tctx->arenas; + for (U64 i = 0; i < ArrayCount(tctx->arenas); i += 1, arena_ptr += 1){ + Arena **conflict_ptr = conflicts; + B32 has_conflict = 0; + for (U64 j = 0; j < count; j += 1, conflict_ptr += 1){ + if (*arena_ptr == *conflict_ptr){ + has_conflict = 1; + break; + } + } + if (!has_conflict){ + result = *arena_ptr; + break; + } + } + + return(result); +} + +internal void +tctx_set_thread_name(String8 string){ + TCTX *tctx = tctx_get_equipped(); + U64 size = ClampTop(string.size, sizeof(tctx->thread_name)); + MemoryCopy(tctx->thread_name, string.str, size); + tctx->thread_name_size = size; +} + +internal String8 +tctx_get_thread_name(void){ + TCTX *tctx = tctx_get_equipped(); + String8 result = str8(tctx->thread_name, tctx->thread_name_size); + return(result); +} + +internal void +tctx_write_srcloc(char *file_name, U64 line_number){ + TCTX *tctx = tctx_get_equipped(); + tctx->file_name = file_name; + tctx->line_number = line_number; +} + +internal void +tctx_read_srcloc(char **file_name, U64 *line_number){ + TCTX *tctx = tctx_get_equipped(); + *file_name = tctx->file_name; + *line_number = tctx->line_number; +} diff --git a/metagen/metagen_base/metagen_base_thread_context.h b/metagen/metagen_base/metagen_base_thread_context.h new file mode 100644 index 0000000..4597e28 --- /dev/null +++ b/metagen/metagen_base/metagen_base_thread_context.h @@ -0,0 +1,41 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_THREAD_CONTEXT_H +#define BASE_THREAD_CONTEXT_H + +//////////////////////////////// +// NOTE(allen): Thread Context + +typedef struct TCTX TCTX; +struct TCTX +{ + Arena *arenas[2]; + + U8 thread_name[32]; + U64 thread_name_size; + + char *file_name; + U64 line_number; +}; + +//////////////////////////////// +// NOTE(allen): Thread Context Functions + +internal void tctx_init_and_equip(TCTX *tctx); +internal void tctx_release(void); +internal TCTX* tctx_get_equipped(void); + +internal Arena* tctx_get_scratch(Arena **conflicts, U64 count); + +internal void tctx_set_thread_name(String8 name); +internal String8 tctx_get_thread_name(void); + +internal void tctx_write_srcloc(char *file_name, U64 line_number); +internal void tctx_read_srcloc(char **file_name, U64 *line_number); +#define tctx_write_this_srcloc() tctx_write_srcloc(__FILE__, __LINE__) + +#define scratch_begin(conflicts, count) temp_begin(tctx_get_scratch((conflicts), (count))) +#define scratch_end(scratch) temp_end(scratch) + +#endif // BASE_THREAD_CONTEXT_H diff --git a/metagen/metagen_main.c b/metagen/metagen_main.c new file mode 100644 index 0000000..1ea423b --- /dev/null +++ b/metagen/metagen_main.c @@ -0,0 +1,656 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Build Options + +#define BUILD_CONSOLE_INTERFACE 1 + +//////////////////////////////// +//~ rjf: Includes + +//- rjf: headers +#include "metagen/metagen_base/metagen_base_inc.h" +#include "metagen/metagen_os/metagen_os_inc.h" +#include "mdesk/mdesk.h" +#include "metagen.h" + +//- rjf: impls +#include "metagen/metagen_base/metagen_base_inc.c" +#include "metagen/metagen_os/metagen_os_inc.c" +#include "mdesk/mdesk.c" +#include "metagen.c" + +//////////////////////////////// +//~ rjf: Entry Point + +internal void +entry_point(CmdLine *cmdline) +{ + ////////////////////////////// + //- rjf: set up state + // + MG_MsgList msgs = {0}; + mg_arena = arena_alloc(.reserve_size = GB(64), .commit_size = MB(64)); + mg_state = push_array(mg_arena, MG_State, 1); + mg_state->slots_count = 256; + mg_state->slots = push_array(mg_arena, MG_LayerSlot, mg_state->slots_count); + + ////////////////////////////// + //- rjf: extract paths + // + String8 build_dir_path = os_get_process_info()->binary_path; + String8 project_dir_path = str8_chop_last_slash(build_dir_path); + String8 code_dir_path = push_str8f(mg_arena, "%S/src", project_dir_path); + + ////////////////////////////// + //- rjf: search code directories for all files to consider + // + String8List file_paths = {0}; + DeferLoop(printf("searching %.*s...", str8_varg(code_dir_path)), printf(" %i files found\n", (int)file_paths.node_count)) + { + typedef struct Task Task; + struct Task + { + Task *next; + String8 path; + }; + Task start_task = {0, code_dir_path}; + Task *first_task = &start_task; + Task *last_task = &start_task; + for(Task *task = first_task; task != 0; task = task->next) + { + OS_FileIter *it = os_file_iter_begin(mg_arena, task->path, 0); + for(OS_FileInfo info = {0}; os_file_iter_next(mg_arena, it, &info);) + { + String8 file_path = push_str8f(mg_arena, "%S/%S", task->path, info.name); + if(info.props.flags & FilePropertyFlag_IsFolder) + { + Task *next_task = push_array(mg_arena, Task, 1); + SLLQueuePush(first_task, last_task, next_task); + next_task->path = file_path; + } + else + { + str8_list_push(mg_arena, &file_paths, file_path); + } + } + os_file_iter_end(it); + } + } + + ////////////////////////////// + //- rjf: parse all metadesk files + // + MG_FileParseList parses = {0}; + DeferLoop(printf("parsing metadesk..."), printf(" %i metadesk files parsed\n", (int)parses.count)) + { + for(String8Node *n = file_paths.first; n != 0; n = n->next) + { + String8 file_path = n->string; + String8 file_ext = str8_skip_last_dot(file_path); + if(str8_match(file_ext, str8_lit("mdesk"), 0)) + { + String8 data = os_data_from_file_path(mg_arena, file_path); + MD_TokenizeResult tokenize = md_tokenize_from_text(mg_arena, data); + MD_ParseResult parse = md_parse_from_text_tokens(mg_arena, file_path, data, tokenize.tokens); + for(MD_Msg *m = parse.msgs.first; m != 0; m = m->next) + { + TxtPt pt = mg_txt_pt_from_string_off(data, m->node->src_offset); + String8 msg_kind_string = {0}; + switch(m->kind) + { + default:{}break; + case MD_MsgKind_Note: {msg_kind_string = str8_lit("note");}break; + case MD_MsgKind_Warning: {msg_kind_string = str8_lit("warning");}break; + case MD_MsgKind_Error: {msg_kind_string = str8_lit("error");}break; + case MD_MsgKind_FatalError: {msg_kind_string = str8_lit("fatal error");}break; + } + String8 location = push_str8f(mg_arena, "%S:%I64d:%I64d", file_path, pt.line, pt.column); + MG_Msg dst_m = {location, msg_kind_string, m->string}; + mg_msg_list_push(mg_arena, &msgs, &dst_m); + } + MG_FileParseNode *parse_n = push_array(mg_arena, MG_FileParseNode, 1); + SLLQueuePush(parses.first, parses.last, parse_n); + parse_n->v.root = parse.root; + parses.count += 1; + } + } + } + + ////////////////////////////// + //- rjf: gather tables + // + MG_Map table_grid_map = mg_push_map(mg_arena, 1024); + MG_Map table_col_map = mg_push_map(mg_arena, 1024); + U64 table_count = 0; + DeferLoop(printf("gathering tables..."), printf(" %i tables found\n", (int)table_count)) + { + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + for MD_EachNode(node, file->first) + { + MD_Node *table_tag = md_tag_from_string(node, str8_lit("table"), 0); + if(!md_node_is_nil(table_tag)) + { + MG_NodeGrid *table = push_array(mg_arena, MG_NodeGrid, 1); + MG_ColumnDescArray *col_descs = push_array(mg_arena, MG_ColumnDescArray, 1); + *table = mg_node_grid_make_from_node(mg_arena, node); + *col_descs = mg_column_desc_array_from_tag(mg_arena, table_tag); + mg_map_insert_ptr(mg_arena, &table_grid_map, node->string, table); + mg_map_insert_ptr(mg_arena, &table_col_map, node->string, col_descs); + table_count += 1; + } + } + } + } + + ////////////////////////////// + //- rjf: gather layer options + // + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + String8 layer_key = mg_layer_key_from_path(file->string); + MG_Layer *layer = mg_layer_from_key(layer_key); + for MD_EachNode(node, file->first) + { + if(md_node_has_tag(node, str8_lit("option"), 0)) + { + if(str8_match(node->string, str8_lit("library"), 0)) + { + layer->is_library = 1; + } + } + if(md_node_has_tag(node, str8_lit("gen_folder"), 0)) + { + layer->gen_folder_name = node->string; + } + if(md_node_has_tag(node, str8_lit("h_name"), 0)) + { + layer->h_name_override = node->string; + } + if(md_node_has_tag(node, str8_lit("c_name"), 0)) + { + layer->c_name_override = node->string; + } + if(md_node_has_tag(node, str8_lit("h_header"), 0)) + { + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + str8_list_push(mg_arena, &layer->h_header, n->string); + str8_list_push(mg_arena, &layer->h_header, str8_lit("\n")); + } + } + if(md_node_has_tag(node, str8_lit("h_footer"), 0)) + { + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + str8_list_push(mg_arena, &layer->h_footer, n->string); + str8_list_push(mg_arena, &layer->h_footer, str8_lit("\n")); + } + } + if(md_node_has_tag(node, str8_lit("c_header"), 0)) + { + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + str8_list_push(mg_arena, &layer->c_header, n->string); + str8_list_push(mg_arena, &layer->c_header, str8_lit("\n")); + } + } + if(md_node_has_tag(node, str8_lit("c_footer"), 0)) + { + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + str8_list_push(mg_arena, &layer->c_footer, n->string); + str8_list_push(mg_arena, &layer->c_footer, str8_lit("\n")); + } + } + } + } + + ////////////////////////////// + //- rjf: generate enums + // + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + for MD_EachNode(node, file->first) + { + MD_Node *tag = md_tag_from_string(node, str8_lit("enum"), 0); + if(!md_node_is_nil(tag)) + { + String8 enum_name = node->string; + String8 enum_member_prefix = enum_name; + if(str8_match(str8_postfix(enum_name, 5), str8_lit("Flags"), 0)) + { + enum_member_prefix = str8_chop(enum_name, 1); + } + String8 enum_base_type_name = tag->first->string; + String8 layer_key = mg_layer_key_from_path(file->string); + MG_Layer *layer = mg_layer_from_key(layer_key); + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + if(enum_base_type_name.size == 0) + { + str8_list_pushf(mg_arena, &layer->enums, "typedef enum %S\n{\n", enum_name); + } + else + { + str8_list_pushf(mg_arena, &layer->enums, "typedef %S %S;\n", enum_base_type_name, enum_name); + str8_list_pushf(mg_arena, &layer->enums, "typedef enum %SEnum\n{\n", enum_name); + } + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + String8 escaped = mg_escaped_from_str8(mg_arena, n->string); + str8_list_pushf(mg_arena, &layer->enums, "%S_%S,\n", enum_member_prefix, escaped); + } + if(enum_base_type_name.size == 0) + { + str8_list_pushf(mg_arena, &layer->enums, "} %S;\n\n", enum_name); + } + else + { + str8_list_pushf(mg_arena, &layer->enums, "} %SEnum;\n\n", enum_name); + } + } + } + } + + ////////////////////////////// + //- rjf: generate xlists + // + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + for MD_EachNode(node, file->first) + { + MD_Node *tag = md_tag_from_string(node, str8_lit("xlist"), 0); + if(!md_node_is_nil(tag)) + { + String8 layer_key = mg_layer_key_from_path(file->string); + MG_Layer *layer = mg_layer_from_key(layer_key); + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + str8_list_pushf(mg_arena, &layer->enums, "#define %S \\\n", node->string); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + String8 escaped = mg_escaped_from_str8(mg_arena, n->string); + str8_list_pushf(mg_arena, &layer->enums, "X(%S)\\\n", escaped); + } + str8_list_push(mg_arena, &layer->enums, str8_lit("\n")); + } + } + } + + ////////////////////////////// + //- rjf: generate structs + // + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + for MD_EachNode(node, file->first) + { + if(md_node_has_tag(node, str8_lit("struct"), 0)) + { + String8 layer_key = mg_layer_key_from_path(file->string); + MG_Layer *layer = mg_layer_from_key(layer_key); + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + str8_list_pushf(mg_arena, &layer->structs, "typedef struct %S %S;\n", node->string, node->string); + str8_list_pushf(mg_arena, &layer->structs, "struct %S\n{\n", node->string); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + String8 escaped = mg_escaped_from_str8(mg_arena, n->string); + str8_list_pushf(mg_arena, &layer->structs, "%S;\n", escaped); + } + str8_list_pushf(mg_arena, &layer->structs, "};\n\n"); + } + } + } + + ////////////////////////////// + //- rjf: generate data tables + // + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + for MD_EachNode(node, file->first) + { + MD_Node *tag = md_tag_from_string(node, str8_lit("data"), 0); + if(!md_node_is_nil(tag)) + { + String8 element_type = tag->first->string; + String8 layer_key = mg_layer_key_from_path(file->string); + MG_Layer *layer = mg_layer_from_key(layer_key); + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + if(!md_node_has_tag(node, str8_lit("c_file"), 0)) + { + str8_list_pushf(mg_arena, &layer->h_tables, "extern %S %S[%I64u];\n", element_type, node->string, gen_strings.node_count); + } + str8_list_pushf(mg_arena, &layer->c_tables, "%S %S[%I64u] =\n{\n", element_type, node->string, gen_strings.node_count); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + String8 escaped = mg_escaped_from_str8(mg_arena, n->string); + str8_list_pushf(mg_arena, &layer->c_tables, "%S,\n", escaped); + } + str8_list_push(mg_arena, &layer->c_tables, str8_lit("};\n\n")); + } + } + } + + ////////////////////////////// + //- rjf: generate enum -> string mapping functions + // + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + for MD_EachNode(node, file->first) + { + MD_Node *tag = md_tag_from_string(node, str8_lit("enum2string_switch"), 0); + if(!md_node_is_nil(tag)) + { + String8 enum_type = tag->first->string; + String8 layer_key = mg_layer_key_from_path(file->string); + MG_Layer *layer = mg_layer_from_key(layer_key); + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + str8_list_pushf(mg_arena, &layer->h_functions, "internal String8 %S(%S v);\n", node->string, enum_type); + str8_list_pushf(mg_arena, &layer->c_functions, "internal String8\n%S(%S v)\n{\n", node->string, enum_type); + str8_list_pushf(mg_arena, &layer->c_functions, "String8 result = str8_lit(\"\");\n", enum_type); + str8_list_pushf(mg_arena, &layer->c_functions, "switch(v)\n"); + str8_list_pushf(mg_arena, &layer->c_functions, "{\n"); + str8_list_pushf(mg_arena, &layer->c_functions, "default:{}break;\n"); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + String8 escaped = mg_escaped_from_str8(mg_arena, n->string); + str8_list_pushf(mg_arena, &layer->c_functions, "%S;\n", escaped); + } + str8_list_pushf(mg_arena, &layer->c_functions, "}\n"); + str8_list_pushf(mg_arena, &layer->c_functions, "return result;\n"); + str8_list_pushf(mg_arena, &layer->c_functions, "}\n\n"); + } + } + } + + ////////////////////////////// + //- rjf: generate catch-all generations + // + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + for MD_EachNode(node, file->first) + { + MD_Node *tag = md_tag_from_string(node, str8_lit("gen"), 0); + if(!md_node_is_nil(tag)) + { + String8 layer_key = mg_layer_key_from_path(file->string); + MG_Layer *layer = mg_layer_from_key(layer_key); + B32 prefer_c_file = md_node_has_tag(node, str8_lit("c_file"), 0); + String8List *out = prefer_c_file ? &layer->c_catchall : &layer->h_catchall; + if(tag->first->string.size == 0){} + else if(str8_match(tag->first->string, str8_lit("enums"), 0)) { out = &layer->enums; } + else if(str8_match(tag->first->string, str8_lit("structs"), 0)) { out = &layer->structs; } + else if(str8_match(tag->first->string, str8_lit("functions"), 0)) { out = prefer_c_file ? &layer->c_functions : &layer->h_functions; } + else if(str8_match(tag->first->string, str8_lit("tables"), 0)) { out = prefer_c_file ? &layer->c_tables : &layer->h_tables; } + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), node); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + String8 trimmed = str8_skip_chop_whitespace(n->string); + String8 escaped = mg_escaped_from_str8(mg_arena, trimmed); + str8_list_push(mg_arena, out, escaped); + str8_list_push(mg_arena, out, str8_lit("\n")); + } + } + } + } + + ////////////////////////////// + //- rjf: gather & generate all embeds + // + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + for MD_EachNode(node, file->first) + { + if(md_node_has_tag(node, str8_lit("embed_string"), 0)) + { + String8 layer_key = mg_layer_key_from_path(file->string); + MG_Layer *layer = mg_layer_from_key(layer_key); + String8 embed_string = mg_c_string_literal_from_multiline_string(node->first->string); + str8_list_pushf(mg_arena, &layer->h_tables, "read_only global String8 %S =\nstr8_lit_comp(\n", node->string); + str8_list_push (mg_arena, &layer->h_tables, embed_string); + str8_list_pushf(mg_arena, &layer->h_tables, ");\n\n"); + } + if(md_node_has_tag(node, str8_lit("embed_file"), 0)) + { + String8 layer_key = mg_layer_key_from_path(file->string); + MG_Layer *layer = mg_layer_from_key(layer_key); + String8 data = os_data_from_file_path(mg_arena, node->first->string); + String8 embed_string = mg_c_array_literal_contents_from_data(data); + str8_list_pushf(mg_arena, &layer->h_tables, "read_only global U8 %S__data[] =\n{\n", node->string); + str8_list_push (mg_arena, &layer->h_tables, embed_string); + str8_list_pushf(mg_arena, &layer->h_tables, "};\n\n"); + str8_list_pushf(mg_arena, &layer->h_tables, "read_only global String8 %S = {%S__data, sizeof(%S__data)};\n", + node->string, + node->string, + node->string); + } + } + } + + ////////////////////////////// + //- rjf: generate all markdown in build folder + // + for(MG_FileParseNode *n = parses.first; n != 0; n = n->next) + { + MD_Node *file = n->v.root; + for MD_EachNode(node, file->first) + { + //- rjf: generate markdown page + if(md_node_has_tag(node, str8_lit("markdown"), 0)) + { + String8List md_strs = {0}; + for(MD_Node *piece = node->first; !md_node_is_nil(piece); piece = piece->next) + { + if(md_node_has_tag(piece, str8_lit("title"), 0)) + { + str8_list_pushf(mg_arena, &md_strs, "# %S\n\n", piece->string); + } + if(md_node_has_tag(piece, str8_lit("subtitle"), 0)) + { + str8_list_pushf(mg_arena, &md_strs, "## %S\n\n", piece->string); + } + if(md_node_has_tag(piece, str8_lit("p"), 0)) + { + String8 paragraph_text = piece->string; + String8List paragraph_lines = mg_wrapped_lines_from_string(mg_arena, paragraph_text, 80, 80, 0); + for(String8Node *n = paragraph_lines.first; n != 0; n = n->next) + { + str8_list_push(mg_arena, &md_strs, n->string); + str8_list_push(mg_arena, &md_strs, str8_lit("\n")); + } + str8_list_push(mg_arena, &md_strs, str8_lit("\n")); + } + if(md_node_has_tag(piece, str8_lit("unordered_list"), 0)) + { + String8List gen_strings = mg_string_list_from_table_gen(mg_arena, table_grid_map, table_col_map, str8_lit(""), piece); + for(String8Node *n = gen_strings.first; n != 0; n = n->next) + { + str8_list_pushf(mg_arena, &md_strs, " - "); + String8 item_text = n->string; + String8List item_lines = mg_wrapped_lines_from_string(mg_arena, item_text, 80-3, 80, 3); + for(String8Node *line_n = item_lines.first; line_n != 0; line_n = line_n->next) + { + str8_list_push(mg_arena, &md_strs, line_n->string); + str8_list_pushf(mg_arena, &md_strs, "\n"); + } + } + str8_list_pushf(mg_arena, &md_strs, "\n"); + } + } + String8 output_path = push_str8f(mg_arena, "%S/%S.md", build_dir_path, node->string); + FILE *file = fopen((char *)output_path.str, "w"); + for(String8Node *n = md_strs.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, file); + } + fclose(file); + } + } + } + + ////////////////////////////// + //- rjf: write all layer output files + // + DeferLoop(printf("generating layer code..."), printf("\n")) + { + for(U64 slot_idx = 0; slot_idx < mg_state->slots_count; slot_idx += 1) + { + MG_LayerSlot *slot = &mg_state->slots[slot_idx]; + for(MG_LayerNode *n = slot->first; n != 0; n = n->next) + { + MG_Layer *layer = &n->v; + String8 layer_generated_folder = {0}; + if(layer->gen_folder_name.size != 0) + { + String8 gen_folder = layer->gen_folder_name; + layer_generated_folder = push_str8f(mg_arena, "%S/%S", code_dir_path, gen_folder); + } + else + { + String8 gen_folder = str8_lit("generated"); + layer_generated_folder = push_str8f(mg_arena, "%S/%S/%S", code_dir_path, layer->key, gen_folder); + } + if(os_make_directory(layer_generated_folder)) + { + String8List layer_key_parts = str8_split_path(mg_arena, layer->key); + StringJoin join = {0}; + join.sep = str8_lit("_"); + String8 layer_key_filename = str8_list_join(mg_arena, &layer_key_parts, &join); + String8 layer_key_filename_upper = upper_from_str8(mg_arena, layer_key_filename); + String8 h_path = push_str8f(mg_arena, "%S/%S.meta.h", layer_generated_folder, layer_key_filename); + String8 c_path = push_str8f(mg_arena, "%S/%S.meta.c", layer_generated_folder, layer_key_filename); + if(layer->h_name_override.size != 0) + { + h_path = push_str8f(mg_arena, "%S/%S", layer_generated_folder, str8_skip_last_slash(layer->h_name_override)); + } + if(layer->c_name_override.size != 0) + { + c_path = push_str8f(mg_arena, "%S/%S", layer_generated_folder, str8_skip_last_slash(layer->c_name_override)); + } + { + FILE *h = fopen((char *)h_path.str, "w"); + fprintf(h, "// Copyright (c) 2024 Epic Games Tools\n"); + fprintf(h, "// Licensed under the MIT license (https://opensource.org/license/mit/)\n\n"); + if(layer->h_header.first == 0) + { + fprintf(h, "//- GENERATED CODE\n\n"); + fprintf(h, "#ifndef %.*s_META_H\n", str8_varg(layer_key_filename_upper)); + fprintf(h, "#define %.*s_META_H\n\n", str8_varg(layer_key_filename_upper)); + } + else for(String8Node *n = layer->h_header.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, h); + } + for(String8Node *n = layer->enums.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, h); + } + for(String8Node *n = layer->structs.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, h); + } + for(String8Node *n = layer->h_catchall.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, h); + } + for(String8Node *n = layer->h_functions.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, h); + } + if(layer->h_tables.first != 0) + { + if(!layer->is_library) + { + fprintf(h, "C_LINKAGE_BEGIN\n"); + } + for(String8Node *n = layer->h_tables.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, h); + } + fprintf(h, "\n"); + if(!layer->is_library) + { + fprintf(h, "C_LINKAGE_END\n\n"); + } + } + if(layer->h_footer.first == 0) + { + fprintf(h, "#endif // %.*s_META_H\n", str8_varg(layer_key_filename_upper)); + } + else for(String8Node *n = layer->h_footer.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, h); + } + fclose(h); + } + { + FILE *c = fopen((char *)c_path.str, "w"); + fprintf(c, "// Copyright (c) 2024 Epic Games Tools\n"); + fprintf(c, "// Licensed under the MIT license (https://opensource.org/license/mit/)\n\n"); + if(layer->c_header.first == 0) + { + fprintf(c, "//- GENERATED CODE\n\n"); + } + else for(String8Node *n = layer->c_header.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, c); + } + for(String8Node *n = layer->c_catchall.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, c); + } + if(layer->c_tables.first != 0) + { + if(!layer->is_library) + { + fprintf(c, "C_LINKAGE_BEGIN\n"); + } + for(String8Node *n = layer->c_tables.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, c); + } + if(!layer->is_library) + { + fprintf(c, "C_LINKAGE_END\n\n"); + } + } + for(String8Node *n = layer->c_functions.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, c); + } + if(layer->c_footer.first != 0) + { + for(String8Node *n = layer->c_footer.first; n != 0; n = n->next) + { + fwrite(n->string.str, n->string.size, 1, c); + } + } + fclose(c); + } + } + } + } + } + + ////////////////////////////// + //- rjf: write out all messages to stderr + // + for(MG_MsgNode *n = msgs.first; n != 0; n = n->next) + { + MG_Msg *msg = &n->v; + fprintf(stderr, "%.*s: %.*s: %.*s\n", str8_varg(msg->location), str8_varg(msg->kind), str8_varg(msg->msg)); + } +} diff --git a/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/metagen/metagen_os/core/linux/metagen_os_core_linux.c new file mode 100644 index 0000000..9a30f6e --- /dev/null +++ b/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -0,0 +1,1260 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Helpers + +internal DateTime +os_lnx_date_time_from_tm(tm in, U32 msec) +{ + DateTime dt = {0}; + dt.sec = in.tm_sec; + dt.min = in.tm_min; + dt.hour = in.tm_hour; + dt.day = in.tm_mday-1; + dt.mon = in.tm_mon; + dt.year = in.tm_year+1900; + dt.msec = msec; + return dt; +} + +internal tm +os_lnx_tm_from_date_time(DateTime dt) +{ + tm result = {0}; + result.tm_sec = dt.sec; + result.tm_min = dt.min; + result.tm_hour= dt.hour; + result.tm_mday= dt.day+1; + result.tm_mon = dt.mon; + result.tm_year= dt.year-1900; + return result; +} + +internal timespec +os_lnx_timespec_from_date_time(DateTime dt) +{ + tm tm_val = os_lnx_tm_from_date_time(dt); + time_t seconds = timegm(&tm_val); + timespec result = {0}; + result.tv_sec = seconds; + return result; +} + +internal DenseTime +os_lnx_dense_time_from_timespec(timespec in) +{ + DenseTime result = 0; + { + struct tm tm_time = {0}; + gmtime_r(&in.tv_sec, &tm_time); + DateTime date_time = os_lnx_date_time_from_tm(tm_time, in.tv_nsec/Million(1)); + result = dense_time_from_date_time(date_time); + } + return result; +} + +internal FileProperties +os_lnx_file_properties_from_stat(struct stat *s) +{ + FileProperties props = {0}; + props.size = s->st_size; + props.created = os_lnx_dense_time_from_timespec(s->st_ctim); + props.modified = os_lnx_dense_time_from_timespec(s->st_mtim); + if(s->st_mode & S_IFDIR) + { + props.flags |= FilePropertyFlag_IsFolder; + } + return props; +} + +internal void +os_lnx_safe_call_sig_handler(int x) +{ + OS_LNX_SafeCallChain *chain = os_lnx_safe_call_chain; + if(chain != 0 && chain->fail_handler != 0) + { + chain->fail_handler(chain->ptr); + } + abort(); +} + +//////////////////////////////// +//~ rjf: Entities + +internal OS_LNX_Entity * +os_lnx_entity_alloc(OS_LNX_EntityKind kind) +{ + OS_LNX_Entity *entity = 0; + DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), + pthread_mutex_unlock(&os_lnx_state.entity_mutex)) + { + entity = os_lnx_state.entity_free; + if(entity) + { + SLLStackPop(os_lnx_state.entity_free); + } + else + { + entity = push_array_no_zero(os_lnx_state.entity_arena, OS_LNX_Entity, 1); + } + } + MemoryZeroStruct(entity); + entity->kind = kind; + return entity; +} + +internal void +os_lnx_entity_release(OS_LNX_Entity *entity) +{ + DeferLoop(pthread_mutex_lock(&os_lnx_state.entity_mutex), + pthread_mutex_unlock(&os_lnx_state.entity_mutex)) + { + SLLStackPush(os_lnx_state.entity_free, entity); + } +} + +//////////////////////////////// +//~ rjf: Thread Entry Point + +internal void * +os_lnx_thread_entry_point(void *ptr) +{ + OS_LNX_Entity *entity = (OS_LNX_Entity *)ptr; + OS_ThreadFunctionType *func = entity->thread.func; + void *thread_ptr = entity->thread.ptr; + TCTX tctx_; + tctx_init_and_equip(&tctx_); + func(thread_ptr); + tctx_release(); + return 0; +} + +//////////////////////////////// +//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) + +internal OS_SystemInfo * +os_get_system_info(void) +{ + return &os_lnx_state.system_info; +} + +internal OS_ProcessInfo * +os_get_process_info(void) +{ + return &os_lnx_state.process_info; +} + +internal String8 +os_get_current_path(Arena *arena) +{ + char *cwdir = getcwd(0, 0); + String8 string = push_str8_copy(arena, str8_cstring(cwdir)); + return string; +} + +//////////////////////////////// +//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) + +//- rjf: basic + +internal void * +os_reserve(U64 size) +{ + void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + return result; +} + +internal B32 +os_commit(void *ptr, U64 size) +{ + mprotect(ptr, size, PROT_READ|PROT_WRITE); + return 1; +} + +internal void +os_decommit(void *ptr, U64 size) +{ + madvise(ptr, size, MADV_DONTNEED); + mprotect(ptr, size, PROT_NONE); +} + +internal void +os_release(void *ptr, U64 size) +{ + munmap(ptr, size); +} + +//- rjf: large pages + +internal void * +os_reserve_large(U64 size) +{ + void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB, -1, 0); + return result; +} + +internal B32 +os_commit_large(void *ptr, U64 size) +{ + mprotect(ptr, size, PROT_READ|PROT_WRITE); + return 1; +} + +//////////////////////////////// +//~ rjf: @os_hooks Thread Info (Implemented Per-OS) + +internal U32 +os_tid(void) +{ + U32 result = 0; +#if defined(SYS_gettid) + result = syscall(SYS_gettid); +#else + result = gettid(); +#endif + return result; +} + +internal void +os_set_thread_name(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String8 name_copy = push_str8_copy(scratch.arena, name); + pthread_t current_thread = pthread_self(); + pthread_setname_np(current_thread, (char *)name_copy.str); + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: @os_hooks Aborting (Implemented Per-OS) + +internal void +os_abort(S32 exit_code) +{ + exit(exit_code); +} + +//////////////////////////////// +//~ rjf: @os_hooks File System (Implemented Per-OS) + +//- rjf: files + +internal OS_Handle +os_file_open(OS_AccessFlags flags, String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + int lnx_flags = 0; + if(flags & (OS_AccessFlag_Read|OS_AccessFlag_Write)) + { + lnx_flags = O_RDWR; + } + else if(flags & OS_AccessFlag_Write) + { + lnx_flags = O_WRONLY; + } + else if(flags & OS_AccessFlag_Read) + { + lnx_flags = O_RDONLY; + } + if(flags & OS_AccessFlag_Append) + { + lnx_flags |= O_APPEND; + } + int fd = open((char *)path_copy.str, lnx_flags); + OS_Handle handle = {0}; + if(fd != -1) + { + handle.u64[0] = fd; + } + scratch_end(scratch); + return handle; +} + +internal void +os_file_close(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { return; } + int fd = (int)file.u64[0]; + close(fd); +} + +internal U64 +os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + int fd = (int)file.u64[0]; + if(rng.min != 0) + { + lseek(fd, rng.min, SEEK_SET); + } + U64 total_num_bytes_to_read = dim_1u64(rng); + U64 total_num_bytes_read = 0; + U64 total_num_bytes_left_to_read = total_num_bytes_to_read; + for(;total_num_bytes_left_to_read > 0;) + { + int read_result = read(fd, (U8 *)out_data + total_num_bytes_read, total_num_bytes_left_to_read); + if(read_result >= 0) + { + total_num_bytes_read += read_result; + total_num_bytes_left_to_read -= read_result; + } + else if(errno != EINTR) + { + break; + } + } + return total_num_bytes_read; +} + +internal U64 +os_file_write(OS_Handle file, Rng1U64 rng, void *data) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + int fd = (int)file.u64[0]; + if(rng.min != 0) + { + lseek(fd, rng.min, SEEK_SET); + } + U64 total_num_bytes_to_write = dim_1u64(rng); + U64 total_num_bytes_written = 0; + U64 total_num_bytes_left_to_write = total_num_bytes_to_write; + for(;total_num_bytes_left_to_write > 0;) + { + int write_result = write(fd, (U8 *)data + total_num_bytes_written, total_num_bytes_left_to_write); + if(write_result >= 0) + { + total_num_bytes_written += write_result; + total_num_bytes_left_to_write -= write_result; + } + else if(errno != EINTR) + { + break; + } + } + return total_num_bytes_written; +} + +internal B32 +os_file_set_times(OS_Handle file, DateTime date_time) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + int fd = (int)file.u64[0]; + timespec time = os_lnx_timespec_from_date_time(date_time); + timespec times[2] = {time, time}; + int futimens_result = futimens(fd, times); + B32 good = (futimens_result != -1); + return good; +} + +internal FileProperties +os_properties_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { return (FileProperties){0}; } + int fd = (int)file.u64[0]; + struct stat fd_stat = {0}; + int fstat_result = fstat(fd, &fd_stat); + FileProperties props = {0}; + if(fstat_result != -1) + { + props = os_lnx_file_properties_from_stat(&fd_stat); + } + return props; +} + +internal OS_FileID +os_id_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { return (OS_FileID){0}; } + int fd = (int)file.u64[0]; + struct stat fd_stat = {0}; + int fstat_result = fstat(fd, &fd_stat); + OS_FileID id = {0}; + if(fstat_result != -1) + { + id.v[0] = fd_stat.st_dev; + id.v[1] = fd_stat.st_ino; + } + return id; +} + +internal B32 +os_delete_file_at_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 result = 0; + String8 path_copy = push_str8_copy(scratch.arena, path); + if(remove((char*)path_copy.str) != -1) + { + result = 1; + } + scratch_end(scratch); + return result; +} + +internal B32 +os_copy_file_path(String8 dst, String8 src) +{ + B32 result = 0; + OS_Handle src_h = os_file_open(OS_AccessFlag_Read, src); + OS_Handle dst_h = os_file_open(OS_AccessFlag_Write, dst); + if(!os_handle_match(src_h, os_handle_zero()) && + !os_handle_match(dst_h, os_handle_zero())) + { + FileProperties src_props = os_properties_from_file(src_h); + U64 size = src_props.size; + U64 total_bytes_copied = 0; + U64 bytes_left_to_copy = size; + for(;bytes_left_to_copy > 0;) + { + Temp scratch = scratch_begin(0, 0); + U64 buffer_size = Min(bytes_left_to_copy, MB(8)); + U8 *buffer = push_array_no_zero(scratch.arena, U8, buffer_size); + U64 bytes_read = os_file_read(src_h, r1u64(total_bytes_copied, total_bytes_copied+buffer_size), buffer); + U64 bytes_written = os_file_write(dst_h, r1u64(total_bytes_copied, total_bytes_copied+bytes_read), buffer); + U64 bytes_copied = Min(bytes_read, bytes_written); + bytes_left_to_copy -= bytes_copied; + total_bytes_copied += bytes_copied; + scratch_end(scratch); + if(bytes_copied == 0) + { + break; + } + } + } + os_file_close(src_h); + os_file_close(dst_h); + return result; +} + +internal String8 +os_full_path_from_path(Arena *arena, String8 path) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 path_copy = push_str8_copy(scratch.arena, path); + char buffer[PATH_MAX] = {0}; + realpath((char *)path_copy.str, buffer); + String8 result = push_str8_copy(arena, str8_cstring(buffer)); + scratch_end(scratch); + return result; +} + +internal B32 +os_file_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + int access_result = access((char *)path_copy.str, F_OK); + B32 result = 0; + if(access_result == 0) + { + result = 1; + } + scratch_end(scratch); + return result; +} + +internal FileProperties +os_properties_from_file_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String8 path_copy = push_str8_copy(scratch.arena, path); + struct stat f_stat = {0}; + int stat_result = stat((char *)path_copy.str, &f_stat); + FileProperties props = {0}; + if(stat_result != -1) + { + props = os_lnx_file_properties_from_stat(&f_stat); + } + scratch_end(scratch); + return props; +} + +//- rjf: file maps + +internal OS_Handle +os_file_map_open(OS_AccessFlags flags, OS_Handle file) +{ + OS_Handle map = file; + return map; +} + +internal void +os_file_map_close(OS_Handle map) +{ + // NOTE(rjf): nothing to do; `map` handles are the same as `file` handles in + // the linux implementation (on Windows they require separate handles) +} + +internal void * +os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) +{ + if(os_handle_match(map, os_handle_zero())) { return 0; } + int fd = (int)map.u64[0]; + int prot_flags = 0; + if(flags & OS_AccessFlag_Write) { prot_flags |= PROT_WRITE; } + if(flags & OS_AccessFlag_Read) { prot_flags |= PROT_READ; } + int map_flags = MAP_PRIVATE; + void *base = mmap(0, dim_1u64(range), prot_flags, map_flags, fd, range.min); + return base; +} + +internal void +os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range) +{ + munmap(ptr, dim_1u64(range)); +} + +//- rjf: directory iteration + +internal OS_FileIter * +os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) +{ + OS_FileIter *base_iter = push_array(arena, OS_FileIter, 1); + base_iter->flags = flags; + OS_LNX_FileIter *iter = (OS_LNX_FileIter *)base_iter->memory; + { + String8 path_copy = push_str8_copy(arena, path); + iter->dir = opendir((char *)path_copy.str); + iter->path = path_copy; + } + return base_iter; +} + +internal B32 +os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) +{ + B32 good = 0; + OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; + for(;;) + { + // rjf: get next entry + lnx_iter->dp = readdir(lnx_iter->dir); + good = (lnx_iter->dp != 0); + + // rjf: unpack entry info + struct stat st = {0}; + int stat_result = 0; + if(good) + { + Temp scratch = scratch_begin(&arena, 1); + String8 full_path = push_str8f(scratch.arena, "%S/%s", lnx_iter->path, lnx_iter->dp->d_name); + stat_result = stat((char *)full_path.str, &st); + scratch_end(scratch); + } + + // rjf: determine if filtered + B32 filtered = 0; + if(good) + { + filtered = ((st.st_mode == S_IFDIR && iter->flags & OS_FileIterFlag_SkipFolders) || + (st.st_mode == S_IFREG && iter->flags & OS_FileIterFlag_SkipFiles) || + (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == 0) || + (lnx_iter->dp->d_name[0] == '.' && lnx_iter->dp->d_name[1] == '.' && lnx_iter->dp->d_name[2] == 0)); + } + + // rjf: output & exit, if good & unfiltered + if(good && !filtered) + { + info_out->name = push_str8_copy(arena, str8_cstring(lnx_iter->dp->d_name)); + if(stat_result != -1) + { + info_out->props = os_lnx_file_properties_from_stat(&st); + } + break; + } + + // rjf: exit if not good + if(!good) + { + break; + } + } + return good; +} + +internal void +os_file_iter_end(OS_FileIter *iter) +{ + OS_LNX_FileIter *lnx_iter = (OS_LNX_FileIter *)iter->memory; + closedir(lnx_iter->dir); +} + +//- rjf: directory creation + +internal B32 +os_make_directory(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 result = 0; + String8 path_copy = push_str8_copy(scratch.arena, path); + if(mkdir((char*)path_copy.str, 0777) != -1) + { + result = 1; + } + scratch_end(scratch); + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) + +internal OS_Handle +os_shared_memory_alloc(U64 size, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String8 name_copy = push_str8_copy(scratch.arena, name); + int id = shm_open((char *)name_copy.str, O_RDWR, 0); + ftruncate(id, size); + OS_Handle result = {(U64)id}; + scratch_end(scratch); + return result; +} + +internal OS_Handle +os_shared_memory_open(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String8 name_copy = push_str8_copy(scratch.arena, name); + int id = shm_open((char *)name_copy.str, O_RDWR, 0); + OS_Handle result = {(U64)id}; + scratch_end(scratch); + return result; +} + +internal void +os_shared_memory_close(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())){return;} + int id = (int)handle.u64[0]; + close(id); +} + +internal void * +os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) +{ + if(os_handle_match(handle, os_handle_zero())){return 0;} + int id = (int)handle.u64[0]; + void *base = mmap(0, dim_1u64(range), PROT_READ|PROT_WRITE, MAP_SHARED, id, range.min); + return base; +} + +internal void +os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range) +{ + if(os_handle_match(handle, os_handle_zero())){return;} + munmap(ptr, dim_1u64(range)); +} + +//////////////////////////////// +//~ rjf: @os_hooks Time (Implemented Per-OS) + +internal U64 +os_now_microseconds(void) +{ + struct timespec t; + clock_gettime(CLOCK_MONOTONIC, &t); + U64 result = t.tv_sec*Million(1) + (t.tv_nsec/Thousand(1)); + return result; +} + +internal U32 +os_now_unix(void) +{ + time_t t = time(0); + return (U32)t; +} + +internal DateTime +os_now_universal_time(void) +{ + time_t t = 0; + time(&t); + struct tm universal_tm = {0}; + gmtime_r(&t, &universal_tm); + DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); + return result; +} + +internal DateTime +os_universal_time_from_local(DateTime *date_time) +{ + // rjf: local DateTime -> universal time_t + tm local_tm = os_lnx_tm_from_date_time(*date_time); + local_tm.tm_isdst = -1; + time_t universal_t = mktime(&local_tm); + + // rjf: universal time_t -> DateTime + tm universal_tm = {0}; + gmtime_r(&universal_t, &universal_tm); + DateTime result = os_lnx_date_time_from_tm(universal_tm, 0); + return result; +} + +internal DateTime +os_local_time_from_universal(DateTime *date_time) +{ + // rjf: universal DateTime -> local time_t + tm universal_tm = os_lnx_tm_from_date_time(*date_time); + universal_tm.tm_isdst = -1; + time_t universal_t = timegm(&universal_tm); + tm local_tm = {0}; + localtime_r(&universal_t, &local_tm); + + // rjf: local tm -> DateTime + DateTime result = os_lnx_date_time_from_tm(local_tm, 0); + return result; +} + +internal void +os_sleep_milliseconds(U32 msec) +{ + usleep(msec*Thousand(1)); +} + +//////////////////////////////// +//~ rjf: @os_hooks Child Processes (Implemented Per-OS) + +internal OS_Handle +os_process_launch(OS_ProcessLaunchParams *params) +{ + NotImplemented; +} + +internal B32 +os_process_join(OS_Handle handle, U64 endt_us) +{ + NotImplemented; +} + +internal void +os_process_detach(OS_Handle handle) +{ + NotImplemented; +} + +//////////////////////////////// +//~ rjf: @os_hooks Threads (Implemented Per-OS) + +internal OS_Handle +os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Thread); + entity->thread.func = func; + entity->thread.ptr = ptr; + { + pthread_attr_t attr; + pthread_attr_init(&attr); + int pthread_result = pthread_create(&entity->thread.handle, &attr, os_lnx_thread_entry_point, entity); + pthread_attr_destroy(&attr); + if(pthread_result == -1) + { + os_lnx_entity_release(entity); + entity = 0; + } + } + OS_Handle handle = {(U64)entity}; + return handle; +} + +internal B32 +os_thread_join(OS_Handle handle, U64 endt_us) +{ + if(os_handle_match(handle, os_handle_zero())) { return 0; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; + int join_result = pthread_join(entity->thread.handle, 0); + B32 result = (join_result == 0); + os_lnx_entity_release(entity); + return result; +} + +internal void +os_thread_detach(OS_Handle handle) +{ + if(os_handle_match(handle, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)handle.u64[0]; + os_lnx_entity_release(entity); +} + +//////////////////////////////// +//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) + +//- rjf: mutexes + +internal OS_Handle +os_mutex_alloc(void) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_Mutex); + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + int init_result = pthread_mutex_init(&entity->mutex_handle, &attr); + pthread_mutexattr_destroy(&attr); + if(init_result == -1) + { + os_lnx_entity_release(entity); + entity = 0; + } + OS_Handle handle = {(U64)entity}; + return handle; +} + +internal void +os_mutex_release(OS_Handle mutex) +{ + if(os_handle_match(mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + pthread_mutex_destroy(&entity->mutex_handle); + os_lnx_entity_release(entity); +} + +internal void +os_mutex_take(OS_Handle mutex) +{ + if(os_handle_match(mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + pthread_mutex_lock(&entity->mutex_handle); +} + +internal void +os_mutex_drop(OS_Handle mutex) +{ + if(os_handle_match(mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)mutex.u64[0]; + pthread_mutex_unlock(&entity->mutex_handle); +} + +//- rjf: reader/writer mutexes + +internal OS_Handle +os_rw_mutex_alloc(void) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_RWMutex); + int init_result = pthread_rwlock_init(&entity->rwmutex_handle, 0); + if(init_result == -1) + { + os_lnx_entity_release(entity); + entity = 0; + } + OS_Handle handle = {(U64)entity}; + return handle; +} + +internal void +os_rw_mutex_release(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + pthread_rwlock_destroy(&entity->rwmutex_handle); + os_lnx_entity_release(entity); +} + +internal void +os_rw_mutex_take_r(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + pthread_rwlock_rdlock(&entity->rwmutex_handle); +} + +internal void +os_rw_mutex_drop_r(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + pthread_rwlock_unlock(&entity->rwmutex_handle); +} + +internal void +os_rw_mutex_take_w(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + pthread_rwlock_wrlock(&entity->rwmutex_handle); +} + +internal void +os_rw_mutex_drop_w(OS_Handle rw_mutex) +{ + if(os_handle_match(rw_mutex, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)rw_mutex.u64[0]; + pthread_rwlock_unlock(&entity->rwmutex_handle); +} + +//- rjf: condition variables + +internal OS_Handle +os_condition_variable_alloc(void) +{ + OS_LNX_Entity *entity = os_lnx_entity_alloc(OS_LNX_EntityKind_ConditionVariable); + int init_result = pthread_cond_init(&entity->cv.cond_handle, 0); + if(init_result == -1) + { + os_lnx_entity_release(entity); + entity = 0; + } + int init2_result = 0; + if(entity) + { + init2_result = pthread_mutex_init(&entity->cv.rwlock_mutex_handle, 0); + } + if(init2_result == -1) + { + pthread_cond_destroy(&entity->cv.cond_handle); + os_lnx_entity_release(entity); + entity = 0; + } + OS_Handle handle = {(U64)entity}; + return handle; +} + +internal void +os_condition_variable_release(OS_Handle cv) +{ + if(os_handle_match(cv, os_handle_zero())) { return; } + OS_LNX_Entity *entity = (OS_LNX_Entity *)cv.u64[0]; + pthread_cond_destroy(&entity->cv.cond_handle); + pthread_mutex_destroy(&entity->cv.rwlock_mutex_handle); + os_lnx_entity_release(entity); +} + +internal B32 +os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us) +{ + if(os_handle_match(cv, os_handle_zero())) { return 0; } + if(os_handle_match(mutex, os_handle_zero())) { return 0; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + OS_LNX_Entity *mutex_entity = (OS_LNX_Entity *)mutex.u64[0]; + struct timespec endt_timespec; + endt_timespec.tv_sec = endt_us/Million(1); + endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); + int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &mutex_entity->mutex_handle, &endt_timespec); + B32 result = (wait_result != ETIMEDOUT); + return result; +} + +internal B32 +os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + // TODO(rjf): because pthread does not supply cv/rw natively, I had to hack + // this together, but this would probably just be a lot better if we just + // implemented the primitives ourselves with e.g. futexes + // + if(os_handle_match(cv, os_handle_zero())) { return 0; } + if(os_handle_match(mutex_rw, os_handle_zero())) { return 0; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + OS_LNX_Entity *rw_mutex_entity = (OS_LNX_Entity *)mutex_rw.u64[0]; + struct timespec endt_timespec; + endt_timespec.tv_sec = endt_us/Million(1); + endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); + B32 result = 0; + for(;;) + { + pthread_mutex_lock(&cv_entity->cv.rwlock_mutex_handle); + int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &cv_entity->cv.rwlock_mutex_handle, &endt_timespec); + if(wait_result != ETIMEDOUT) + { + pthread_rwlock_rdlock(&rw_mutex_entity->rwmutex_handle); + pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); + result = 1; + break; + } + pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); + if(wait_result == ETIMEDOUT) + { + break; + } + } + return result; +} + +internal B32 +os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + // TODO(rjf): because pthread does not supply cv/rw natively, I had to hack + // this together, but this would probably just be a lot better if we just + // implemented the primitives ourselves with e.g. futexes + // + if(os_handle_match(cv, os_handle_zero())) { return 0; } + if(os_handle_match(mutex_rw, os_handle_zero())) { return 0; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + OS_LNX_Entity *rw_mutex_entity = (OS_LNX_Entity *)mutex_rw.u64[0]; + struct timespec endt_timespec; + endt_timespec.tv_sec = endt_us/Million(1); + endt_timespec.tv_nsec = Thousand(1) * (endt_us - (endt_us/Million(1))*Million(1)); + B32 result = 0; + for(;;) + { + pthread_mutex_lock(&cv_entity->cv.rwlock_mutex_handle); + int wait_result = pthread_cond_timedwait(&cv_entity->cv.cond_handle, &cv_entity->cv.rwlock_mutex_handle, &endt_timespec); + if(wait_result != ETIMEDOUT) + { + pthread_rwlock_wrlock(&rw_mutex_entity->rwmutex_handle); + pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); + result = 1; + break; + } + pthread_mutex_unlock(&cv_entity->cv.rwlock_mutex_handle); + if(wait_result == ETIMEDOUT) + { + break; + } + } + return result; +} + +internal void +os_condition_variable_signal(OS_Handle cv) +{ + if(os_handle_match(cv, os_handle_zero())) { return; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + pthread_cond_signal(&cv_entity->cv.cond_handle); +} + +internal void +os_condition_variable_broadcast(OS_Handle cv) +{ + if(os_handle_match(cv, os_handle_zero())) { return; } + OS_LNX_Entity *cv_entity = (OS_LNX_Entity *)cv.u64[0]; + pthread_cond_broadcast(&cv_entity->cv.cond_handle); +} + +//- rjf: cross-process semaphores + +internal OS_Handle +os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) +{ + NotImplemented; +} + +internal void +os_semaphore_release(OS_Handle semaphore) +{ + NotImplemented; +} + +internal OS_Handle +os_semaphore_open(String8 name) +{ + NotImplemented; +} + +internal void +os_semaphore_close(OS_Handle semaphore) +{ + NotImplemented; +} + +internal B32 +os_semaphore_take(OS_Handle semaphore, U64 endt_us) +{ + NotImplemented; +} + +internal void +os_semaphore_drop(OS_Handle semaphore) +{ + NotImplemented; +} + +//////////////////////////////// +//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) + +internal OS_Handle +os_library_open(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + char *path_cstr = (char *)push_str8_copy(scratch.arena, path).str; + void *so = dlopen(path_cstr, RTLD_LAZY); + OS_Handle lib = { (U64)so }; + scratch_end(scratch); + return lib; +} + +internal VoidProc* +os_library_load_proc(OS_Handle lib, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + void *so = (void *)lib.u64; + char *name_cstr = (char *)push_str8_copy(scratch.arena, name).str; + VoidProc *proc = (VoidProc *)dlsym(so, name_cstr); + scratch_end(scratch); + return proc; +} + +internal void +os_library_close(OS_Handle lib) +{ + void *so = (void *)lib.u64; + dlclose(so); +} + +//////////////////////////////// +//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) + +internal void +os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr) +{ + // rjf: push handler to chain + OS_LNX_SafeCallChain chain = {0}; + SLLStackPush(os_lnx_safe_call_chain, &chain); + chain.fail_handler = fail_handler; + chain.ptr = ptr; + + // rjf: set up sig handler info + struct sigaction new_act = {0}; + new_act.sa_handler = os_lnx_safe_call_sig_handler; + int signals_to_handle[] = + { + SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGTRAP, + }; + struct sigaction og_act[ArrayCount(signals_to_handle)] = {0}; + + // rjf: attach handler info for all signals + for(U32 i = 0; i < ArrayCount(signals_to_handle); i += 1) + { + sigaction(signals_to_handle[i], &new_act, &og_act[i]); + } + + // rjf: call function + func(ptr); + + // rjf: reset handler info for all signals + for(U32 i = 0; i < ArrayCount(signals_to_handle); i += 1) + { + sigaction(signals_to_handle[i], &og_act[i], 0); + } +} + +//////////////////////////////// +//~ rjf: @os_hooks GUIDs (Implemented Per-OS) + +internal OS_Guid +os_make_guid(void) +{ + U8 random_bytes[16] = {0}; + StaticAssert(sizeof(random_bytes) == sizeof(OS_Guid), os_lnx_guid_size_check); + getrandom(random_bytes, sizeof(random_bytes), 0); + OS_Guid guid = {0}; + MemoryCopy(&guid, random_bytes, sizeof(random_bytes)); + guid.data3 &= 0x0fff; + guid.data3 |= (4 << 12); + guid.data4[0] &= 0x3f; + guid.data4[0] |= 0x80; + return guid; +} + +//////////////////////////////// +//~ rjf: @os_hooks Entry Points (Implemented Per-OS) + +int +main(int argc, char **argv) +{ + //- rjf: set up OS layer + { + //- rjf: get statically-allocated system/process info + { + OS_SystemInfo *info = &os_lnx_state.system_info; + info->logical_processor_count = (U32)get_nprocs(); + info->page_size = (U64)getpagesize(); + info->large_page_size = MB(2); + info->allocation_granularity = info->page_size; + } + { + OS_ProcessInfo *info = &os_lnx_state.process_info; + info->pid = (U32)getpid(); + } + + //- rjf: set up thread context + local_persist TCTX tctx; + tctx_init_and_equip(&tctx); + + //- rjf: set up dynamically allocated state + os_lnx_state.arena = arena_alloc(); + os_lnx_state.entity_arena = arena_alloc(); + pthread_mutex_init(&os_lnx_state.entity_mutex, 0); + + //- rjf: grab dynamically allocated system info + { + Temp scratch = scratch_begin(0, 0); + OS_SystemInfo *info = &os_lnx_state.system_info; + + // rjf: get machine name + B32 got_final_result = 0; + U8 *buffer = 0; + int size = 0; + for(S64 cap = 4096, r = 0; r < 4; cap *= 2, r += 1) + { + scratch_end(scratch); + buffer = push_array_no_zero(scratch.arena, U8, cap); + size = gethostname((char*)buffer, cap); + if(size < cap) + { + got_final_result = 1; + break; + } + } + + // rjf: save name to info + if(got_final_result && size > 0) + { + info->machine_name.size = size; + info->machine_name.str = push_array_no_zero(os_lnx_state.arena, U8, info->machine_name.size + 1); + MemoryCopy(info->machine_name.str, buffer, info->machine_name.size); + info->machine_name.str[info->machine_name.size] = 0; + } + + scratch_end(scratch); + } + + //- rjf: grab dynamically allocated process info + { + Temp scratch = scratch_begin(0, 0); + OS_ProcessInfo *info = &os_lnx_state.process_info; + + // rjf: grab binary path + { + // rjf: get self string + B32 got_final_result = 0; + U8 *buffer = 0; + int size = 0; + for(S64 cap = PATH_MAX, r = 0; r < 4; cap *= 2, r += 1) + { + scratch_end(scratch); + buffer = push_array_no_zero(scratch.arena, U8, cap); + size = readlink("/proc/self/exe", (char*)buffer, cap); + if(size < cap) + { + got_final_result = 1; + break; + } + } + + // rjf: save + if(got_final_result && size > 0) + { + String8 full_name = str8(buffer, size); + String8 name_chopped = str8_chop_last_slash(full_name); + info->binary_path = push_str8_copy(os_lnx_state.arena, name_chopped); + } + } + + // rjf: grab initial directory + { + info->initial_path = os_get_current_path(os_lnx_state.arena); + } + + // rjf: grab home directory + { + char *home = getenv("HOME"); + info->user_program_data_path = str8_cstring(home); + } + + scratch_end(scratch); + } + } + + //- rjf: call into "real" entry point + main_thread_base_entry_point(entry_point, argv, (U64)argc); +} diff --git a/metagen/metagen_os/core/linux/metagen_os_core_linux.h b/metagen/metagen_os/core/linux/metagen_os_core_linux.h new file mode 100644 index 0000000..a5dea2e --- /dev/null +++ b/metagen/metagen_os/core/linux/metagen_os_core_linux.h @@ -0,0 +1,134 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_CORE_LINUX_H +#define OS_CORE_LINUX_H + +//////////////////////////////// +//~ rjf: Includes + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int pthread_setname_np(pthread_t thread, const char *name); +int pthread_getname_np(pthread_t thread, char *name, size_t size); + +typedef struct tm tm; +typedef struct timespec timespec; + +//////////////////////////////// +//~ rjf: File Iterator + +typedef struct OS_LNX_FileIter OS_LNX_FileIter; +struct OS_LNX_FileIter +{ + DIR *dir; + struct dirent *dp; + String8 path; +}; +StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(OS_LNX_FileIter), os_lnx_file_iter_size_check); + +//////////////////////////////// +//~ rjf: Safe Call Handler Chain + +typedef struct OS_LNX_SafeCallChain OS_LNX_SafeCallChain; +struct OS_LNX_SafeCallChain +{ + OS_LNX_SafeCallChain *next; + OS_ThreadFunctionType *fail_handler; + void *ptr; +}; + +//////////////////////////////// +//~ rjf: Entities + +typedef enum OS_LNX_EntityKind +{ + OS_LNX_EntityKind_Thread, + OS_LNX_EntityKind_Mutex, + OS_LNX_EntityKind_RWMutex, + OS_LNX_EntityKind_ConditionVariable, +} +OS_LNX_EntityKind; + +typedef struct OS_LNX_Entity OS_LNX_Entity; +struct OS_LNX_Entity +{ + OS_LNX_Entity *next; + OS_LNX_EntityKind kind; + union + { + struct + { + pthread_t handle; + OS_ThreadFunctionType *func; + void *ptr; + } thread; + pthread_mutex_t mutex_handle; + pthread_rwlock_t rwmutex_handle; + struct + { + pthread_cond_t cond_handle; + pthread_mutex_t rwlock_mutex_handle; + } cv; + }; +}; + +//////////////////////////////// +//~ rjf: State + +typedef struct OS_LNX_State OS_LNX_State; +struct OS_LNX_State +{ + Arena *arena; + OS_SystemInfo system_info; + OS_ProcessInfo process_info; + pthread_mutex_t entity_mutex; + Arena *entity_arena; + OS_LNX_Entity *entity_free; +}; + +//////////////////////////////// +//~ rjf: Globals + +global OS_LNX_State os_lnx_state = {0}; +thread_static OS_LNX_SafeCallChain *os_lnx_safe_call_chain = 0; + +//////////////////////////////// +//~ rjf: Helpers + +internal DateTime os_lnx_date_time_from_tm(tm in, U32 msec); +internal tm os_lnx_tm_from_date_time(DateTime dt); +internal timespec os_lnx_timespec_from_date_time(DateTime dt); +internal DenseTime os_lnx_dense_time_from_timespec(timespec in); +internal FileProperties os_lnx_file_properties_from_stat(struct stat *s); +internal void os_lnx_safe_call_sig_handler(int x); + +//////////////////////////////// +//~ rjf: Entities + +internal OS_LNX_Entity *os_lnx_entity_alloc(OS_LNX_EntityKind kind); +internal void os_lnx_entity_release(OS_LNX_Entity *entity); + +//////////////////////////////// +//~ rjf: Thread Entry Point + +internal void *os_lnx_thread_entry_point(void *ptr); + +#endif // OS_CORE_LINUX_H diff --git a/metagen/metagen_os/core/metagen_os_core.c b/metagen/metagen_os/core/metagen_os_core.c new file mode 100644 index 0000000..c1bade5 --- /dev/null +++ b/metagen/metagen_os/core/metagen_os_core.c @@ -0,0 +1,173 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Handle Type Functions (Helpers, Implemented Once) + +internal OS_Handle +os_handle_zero(void) +{ + OS_Handle handle = {0}; + return handle; +} + +internal B32 +os_handle_match(OS_Handle a, OS_Handle b) +{ + return a.u64[0] == b.u64[0]; +} + +internal void +os_handle_list_push(Arena *arena, OS_HandleList *handles, OS_Handle handle) +{ + OS_HandleNode *n = push_array(arena, OS_HandleNode, 1); + n->v = handle; + SLLQueuePush(handles->first, handles->last, n); + handles->count += 1; +} + +internal OS_HandleArray +os_handle_array_from_list(Arena *arena, OS_HandleList *list) +{ + OS_HandleArray result = {0}; + result.count = list->count; + result.v = push_array_no_zero(arena, OS_Handle, result.count); + U64 idx = 0; + for(OS_HandleNode *n = list->first; n != 0; n = n->next, idx += 1) + { + result.v[idx] = n->v; + } + return result; +} + +//////////////////////////////// +//~ rjf: Command Line Argc/Argv Helper (Helper, Implemented Once) + +internal String8List +os_string_list_from_argcv(Arena *arena, int argc, char **argv) +{ + String8List result = {0}; + for(int i = 0; i < argc; i += 1) + { + String8 str = str8_cstring(argv[i]); + str8_list_push(arena, &result, str); + } + return result; +} + +//////////////////////////////// +//~ rjf: Filesystem Helpers (Helpers, Implemented Once) + +internal String8 +os_data_from_file_path(Arena *arena, String8 path) +{ + OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, path); + FileProperties props = os_properties_from_file(file); + String8 data = os_string_from_file_range(arena, file, r1u64(0, props.size)); + os_file_close(file); + return data; +} + +internal B32 +os_write_data_to_file_path(String8 path, String8 data) +{ + B32 good = 0; + OS_Handle file = os_file_open(OS_AccessFlag_Write, path); + if(!os_handle_match(file, os_handle_zero())) + { + good = 1; + os_file_write(file, r1u64(0, data.size), data.str); + os_file_close(file); + } + return good; +} + +internal B32 +os_write_data_list_to_file_path(String8 path, String8List list) +{ + B32 good = 0; + OS_Handle file = os_file_open(OS_AccessFlag_Write, path); + if(!os_handle_match(file, os_handle_zero())) + { + good = 1; + U64 off = 0; + for(String8Node *n = list.first; n != 0; n = n->next) + { + os_file_write(file, r1u64(off, off+n->string.size), n->string.str); + off += n->string.size; + } + os_file_close(file); + } + return good; +} + +internal B32 +os_append_data_to_file_path(String8 path, String8 data) +{ + B32 good = 0; + if(data.size != 0) + { + OS_Handle file = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append, path); + if(!os_handle_match(file, os_handle_zero())) + { + good = 1; + U64 pos = os_properties_from_file(file).size; + os_file_write(file, r1u64(pos, pos+data.size), data.str); + os_file_close(file); + } + } + return good; +} + +internal OS_FileID +os_id_from_file_path(String8 path) +{ + OS_Handle file = os_file_open(OS_AccessFlag_Read|OS_AccessFlag_ShareRead, path); + OS_FileID id = os_id_from_file(file); + os_file_close(file); + return id; +} + +internal S64 +os_file_id_compare(OS_FileID a, OS_FileID b) +{ + S64 cmp = MemoryCompare((void*)&a.v[0], (void*)&b.v[0], sizeof(a.v)); + return cmp; +} + +internal String8 +os_string_from_file_range(Arena *arena, OS_Handle file, Rng1U64 range) +{ + U64 pre_pos = arena_pos(arena); + String8 result; + result.size = dim_1u64(range); + result.str = push_array_no_zero(arena, U8, result.size); + U64 actual_read_size = os_file_read(file, range, result.str); + if(actual_read_size < result.size) + { + arena_pop_to(arena, pre_pos + actual_read_size); + result.size = actual_read_size; + } + return result; +} + +//////////////////////////////// +//~ rjf: GUID Helpers (Helpers, Implemented Once) + +internal String8 +os_string_from_guid(Arena *arena, OS_Guid guid) +{ + String8 result = push_str8f(arena, "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + guid.data1, + guid.data2, + guid.data3, + guid.data4[0], + guid.data4[1], + guid.data4[2], + guid.data4[3], + guid.data4[4], + guid.data4[5], + guid.data4[6], + guid.data4[7]); + return result; +} diff --git a/metagen/metagen_os/core/metagen_os_core.h b/metagen/metagen_os/core/metagen_os_core.h new file mode 100644 index 0000000..361643c --- /dev/null +++ b/metagen/metagen_os/core/metagen_os_core.h @@ -0,0 +1,336 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_CORE_H +#define OS_CORE_H + +//////////////////////////////// +//~ rjf: System Info + +typedef struct OS_SystemInfo OS_SystemInfo; +struct OS_SystemInfo +{ + U32 logical_processor_count; + U64 page_size; + U64 large_page_size; + U64 allocation_granularity; + String8 machine_name; +}; + +//////////////////////////////// +//~ rjf: Process Info + +typedef struct OS_ProcessInfo OS_ProcessInfo; +struct OS_ProcessInfo +{ + U32 pid; + String8 binary_path; + String8 initial_path; + String8 user_program_data_path; + String8List module_load_paths; + String8List environment; +}; + +//////////////////////////////// +//~ rjf: Access Flags + +typedef U32 OS_AccessFlags; +enum +{ + OS_AccessFlag_Read = (1<<0), + OS_AccessFlag_Write = (1<<1), + OS_AccessFlag_Execute = (1<<2), + OS_AccessFlag_Append = (1<<3), + OS_AccessFlag_ShareRead = (1<<4), + OS_AccessFlag_ShareWrite = (1<<5), +}; + +//////////////////////////////// +//~ rjf: Files + +typedef U32 OS_FileIterFlags; +enum +{ + OS_FileIterFlag_SkipFolders = (1 << 0), + OS_FileIterFlag_SkipFiles = (1 << 1), + OS_FileIterFlag_SkipHiddenFiles = (1 << 2), + OS_FileIterFlag_Done = (1 << 31), +}; + +typedef struct OS_FileIter OS_FileIter; +struct OS_FileIter +{ + OS_FileIterFlags flags; + U8 memory[800]; +}; + +typedef struct OS_FileInfo OS_FileInfo; +struct OS_FileInfo +{ + String8 name; + FileProperties props; +}; + +// nick: on-disk file identifier +typedef struct OS_FileID OS_FileID; +struct OS_FileID +{ + U64 v[3]; +}; + +//////////////////////////////// +//~ rjf: Process Launch Parameters + +typedef struct OS_ProcessLaunchParams OS_ProcessLaunchParams; +struct OS_ProcessLaunchParams +{ + String8List cmd_line; + String8 path; + String8List env; + B32 inherit_env; + B32 consoleless; +}; + +//////////////////////////////// +//~ rjf: Handle Type + +typedef struct OS_Handle OS_Handle; +struct OS_Handle +{ + U64 u64[1]; +}; + +typedef struct OS_HandleNode OS_HandleNode; +struct OS_HandleNode +{ + OS_HandleNode *next; + OS_Handle v; +}; + +typedef struct OS_HandleList OS_HandleList; +struct OS_HandleList +{ + OS_HandleNode *first; + OS_HandleNode *last; + U64 count; +}; + +typedef struct OS_HandleArray OS_HandleArray; +struct OS_HandleArray +{ + OS_Handle *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Globally Unique IDs + +typedef struct OS_Guid OS_Guid; +struct OS_Guid +{ + U32 data1; + U16 data2; + U16 data3; + U8 data4[8]; +}; +StaticAssert(sizeof(OS_Guid) == 16, os_guid_check); + +//////////////////////////////// +//~ rjf: Thread Types + +typedef void OS_ThreadFunctionType(void *ptr); + +//////////////////////////////// +//~ rjf: Handle Type Functions (Helpers, Implemented Once) + +internal OS_Handle os_handle_zero(void); +internal B32 os_handle_match(OS_Handle a, OS_Handle b); +internal void os_handle_list_push(Arena *arena, OS_HandleList *handles, OS_Handle handle); +internal OS_HandleArray os_handle_array_from_list(Arena *arena, OS_HandleList *list); + +//////////////////////////////// +//~ rjf: Command Line Argc/Argv Helper (Helper, Implemented Once) + +internal String8List os_string_list_from_argcv(Arena *arena, int argc, char **argv); + +//////////////////////////////// +//~ rjf: Filesystem Helpers (Helpers, Implemented Once) + +internal String8 os_data_from_file_path(Arena *arena, String8 path); +internal B32 os_write_data_to_file_path(String8 path, String8 data); +internal B32 os_write_data_list_to_file_path(String8 path, String8List list); +internal B32 os_append_data_to_file_path(String8 path, String8 data); +internal OS_FileID os_id_from_file_path(String8 path); +internal S64 os_file_id_compare(OS_FileID a, OS_FileID b); +internal String8 os_string_from_file_range(Arena *arena, OS_Handle file, Rng1U64 range); + +//////////////////////////////// +//~ rjf: GUID Helpers (Helpers, Implemented Once) + +internal String8 os_string_from_guid(Arena *arena, OS_Guid guid); + +//////////////////////////////// +//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) + +internal OS_SystemInfo *os_get_system_info(void); +internal OS_ProcessInfo *os_get_process_info(void); +internal String8 os_get_current_path(Arena *arena); + +//////////////////////////////// +//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) + +//- rjf: basic +internal void *os_reserve(U64 size); +internal B32 os_commit(void *ptr, U64 size); +internal void os_decommit(void *ptr, U64 size); +internal void os_release(void *ptr, U64 size); + +//- rjf: large pages +internal void *os_reserve_large(U64 size); +internal B32 os_commit_large(void *ptr, U64 size); + +//////////////////////////////// +//~ rjf: @os_hooks Thread Info (Implemented Per-OS) + +internal U32 os_tid(void); +internal void os_set_thread_name(String8 string); + +//////////////////////////////// +//~ rjf: @os_hooks Aborting (Implemented Per-OS) + +internal void os_abort(S32 exit_code); + +//////////////////////////////// +//~ rjf: @os_hooks File System (Implemented Per-OS) + +//- rjf: files +internal OS_Handle os_file_open(OS_AccessFlags flags, String8 path); +internal void os_file_close(OS_Handle file); +internal U64 os_file_read(OS_Handle file, Rng1U64 rng, void *out_data); +internal U64 os_file_write(OS_Handle file, Rng1U64 rng, void *data); +internal B32 os_file_set_times(OS_Handle file, DateTime time); +internal FileProperties os_properties_from_file(OS_Handle file); +internal OS_FileID os_id_from_file(OS_Handle file); +internal B32 os_delete_file_at_path(String8 path); +internal B32 os_copy_file_path(String8 dst, String8 src); +internal String8 os_full_path_from_path(Arena *arena, String8 path); +internal B32 os_file_path_exists(String8 path); +internal FileProperties os_properties_from_file_path(String8 path); + +//- rjf: file maps +internal OS_Handle os_file_map_open(OS_AccessFlags flags, OS_Handle file); +internal void os_file_map_close(OS_Handle map); +internal void * os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range); +internal void os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range); + +//- rjf: directory iteration +internal OS_FileIter *os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags); +internal B32 os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out); +internal void os_file_iter_end(OS_FileIter *iter); + +//- rjf: directory creation +internal B32 os_make_directory(String8 path); + +//////////////////////////////// +//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) + +internal OS_Handle os_shared_memory_alloc(U64 size, String8 name); +internal OS_Handle os_shared_memory_open(String8 name); +internal void os_shared_memory_close(OS_Handle handle); +internal void * os_shared_memory_view_open(OS_Handle handle, Rng1U64 range); +internal void os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range); + +//////////////////////////////// +//~ rjf: @os_hooks Time (Implemented Per-OS) + +internal U64 os_now_microseconds(void); +internal U32 os_now_unix(void); +internal DateTime os_now_universal_time(void); +internal DateTime os_universal_time_from_local(DateTime *local_time); +internal DateTime os_local_time_from_universal(DateTime *universal_time); +internal void os_sleep_milliseconds(U32 msec); + +//////////////////////////////// +//~ rjf: @os_hooks Child Processes (Implemented Per-OS) + +internal OS_Handle os_process_launch(OS_ProcessLaunchParams *params); +internal B32 os_process_join(OS_Handle handle, U64 endt_us); +internal void os_process_detach(OS_Handle handle); + +//////////////////////////////// +//~ rjf: @os_hooks Threads (Implemented Per-OS) + +internal OS_Handle os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params); +internal B32 os_thread_join(OS_Handle handle, U64 endt_us); +internal void os_thread_detach(OS_Handle handle); + +//////////////////////////////// +//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) + +//- rjf: recursive mutexes +internal OS_Handle os_mutex_alloc(void); +internal void os_mutex_release(OS_Handle mutex); +internal void os_mutex_take(OS_Handle mutex); +internal void os_mutex_drop(OS_Handle mutex); + +//- rjf: reader/writer mutexes +internal OS_Handle os_rw_mutex_alloc(void); +internal void os_rw_mutex_release(OS_Handle rw_mutex); +internal void os_rw_mutex_take_r(OS_Handle mutex); +internal void os_rw_mutex_drop_r(OS_Handle mutex); +internal void os_rw_mutex_take_w(OS_Handle mutex); +internal void os_rw_mutex_drop_w(OS_Handle mutex); + +//- rjf: condition variables +internal OS_Handle os_condition_variable_alloc(void); +internal void os_condition_variable_release(OS_Handle cv); +// returns false on timeout, true on signal, (max_wait_ms = max_U64) -> no timeout +internal B32 os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us); +internal B32 os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us); +internal B32 os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us); +internal void os_condition_variable_signal(OS_Handle cv); +internal void os_condition_variable_broadcast(OS_Handle cv); + +//- rjf: cross-process semaphores +internal OS_Handle os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name); +internal void os_semaphore_release(OS_Handle semaphore); +internal OS_Handle os_semaphore_open(String8 name); +internal void os_semaphore_close(OS_Handle semaphore); +internal B32 os_semaphore_take(OS_Handle semaphore, U64 endt_us); +internal void os_semaphore_drop(OS_Handle semaphore); + +//- rjf: scope macros +#define OS_MutexScope(mutex) DeferLoop(os_mutex_take(mutex), os_mutex_drop(mutex)) +#define OS_MutexScopeR(mutex) DeferLoop(os_rw_mutex_take_r(mutex), os_rw_mutex_drop_r(mutex)) +#define OS_MutexScopeW(mutex) DeferLoop(os_rw_mutex_take_w(mutex), os_rw_mutex_drop_w(mutex)) +#define OS_MutexScopeRWPromote(mutex) DeferLoop((os_rw_mutex_drop_r(mutex), os_rw_mutex_take_w(mutex)), (os_rw_mutex_drop_w(mutex), os_rw_mutex_take_r(mutex))) + +//////////////////////////////// +//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) + +internal OS_Handle os_library_open(String8 path); +internal void os_library_close(OS_Handle lib); +internal VoidProc *os_library_load_proc(OS_Handle lib, String8 name); + +//////////////////////////////// +//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) + +internal void os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr); + +//////////////////////////////// +//~ rjf: @os_hooks GUIDs (Implemented Per-OS) + +internal OS_Guid os_make_guid(void); + +//////////////////////////////// +//~ rjf: @os_hooks Entry Points (Implemented Per-OS) + +// NOTE(rjf): The implementation of `os_core` will define low-level entry +// points if BUILD_ENTRY_DEFINING_UNIT is defined to 1. These will call +// into the standard codebase program entry points, named "entry_point". + +#if BUILD_ENTRY_DEFINING_UNIT +internal void entry_point(CmdLine *cmdline); +#endif + +#endif // OS_CORE_H diff --git a/metagen/metagen_os/core/win32/metagen_os_core_win32.c b/metagen/metagen_os/core/win32/metagen_os_core_win32.c new file mode 100644 index 0000000..8a06be3 --- /dev/null +++ b/metagen/metagen_os/core/win32/metagen_os_core_win32.c @@ -0,0 +1,1646 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Modern Windows SDK Functions +// +// (We must dynamically link to them, since they can be missing in older SDKs) + +typedef HRESULT W32_SetThreadDescription_Type(HANDLE hThread, PCWSTR lpThreadDescription); +global W32_SetThreadDescription_Type *w32_SetThreadDescription_func = 0; + +//////////////////////////////// +//~ rjf: File Info Conversion Helpers + +internal FilePropertyFlags +os_w32_file_property_flags_from_dwFileAttributes(DWORD dwFileAttributes) +{ + FilePropertyFlags flags = 0; + if(dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + flags |= FilePropertyFlag_IsFolder; + } + return flags; +} + +internal void +os_w32_file_properties_from_attribute_data(FileProperties *properties, WIN32_FILE_ATTRIBUTE_DATA *attributes) +{ + properties->size = Compose64Bit(attributes->nFileSizeHigh, attributes->nFileSizeLow); + os_w32_dense_time_from_file_time(&properties->created, &attributes->ftCreationTime); + os_w32_dense_time_from_file_time(&properties->modified, &attributes->ftLastWriteTime); + properties->flags = os_w32_file_property_flags_from_dwFileAttributes(attributes->dwFileAttributes); +} + +//////////////////////////////// +//~ rjf: Time Conversion Helpers + +internal void +os_w32_date_time_from_system_time(DateTime *out, SYSTEMTIME *in) +{ + out->year = in->wYear; + out->mon = in->wMonth - 1; + out->wday = in->wDayOfWeek; + out->day = in->wDay; + out->hour = in->wHour; + out->min = in->wMinute; + out->sec = in->wSecond; + out->msec = in->wMilliseconds; +} + +internal void +os_w32_system_time_from_date_time(SYSTEMTIME *out, DateTime *in) +{ + out->wYear = (WORD)(in->year); + out->wMonth = in->mon + 1; + out->wDay = in->day; + out->wHour = in->hour; + out->wMinute = in->min; + out->wSecond = in->sec; + out->wMilliseconds = in->msec; +} + +internal void +os_w32_dense_time_from_file_time(DenseTime *out, FILETIME *in) +{ + SYSTEMTIME systime = {0}; + FileTimeToSystemTime(in, &systime); + DateTime date_time = {0}; + os_w32_date_time_from_system_time(&date_time, &systime); + *out = dense_time_from_date_time(date_time); +} + +internal U32 +os_w32_sleep_ms_from_endt_us(U64 endt_us) +{ + U32 sleep_ms = 0; + if(endt_us == max_U64) + { + sleep_ms = INFINITE; + } + else + { + U64 begint = os_now_microseconds(); + if(begint < endt_us) + { + U64 sleep_us = endt_us - begint; + sleep_ms = (U32)((sleep_us + 999)/1000); + } + } + return sleep_ms; +} + +//////////////////////////////// +//~ rjf: Entity Functions + +internal OS_W32_Entity * +os_w32_entity_alloc(OS_W32_EntityKind kind) +{ + OS_W32_Entity *result = 0; + EnterCriticalSection(&os_w32_state.entity_mutex); + { + result = os_w32_state.entity_free; + if(result) + { + SLLStackPop(os_w32_state.entity_free); + } + else + { + result = push_array_no_zero(os_w32_state.entity_arena, OS_W32_Entity, 1); + } + MemoryZeroStruct(result); + } + LeaveCriticalSection(&os_w32_state.entity_mutex); + result->kind = kind; + return result; +} + +internal void +os_w32_entity_release(OS_W32_Entity *entity) +{ + entity->kind = OS_W32_EntityKind_Null; + EnterCriticalSection(&os_w32_state.entity_mutex); + SLLStackPush(os_w32_state.entity_free, entity); + LeaveCriticalSection(&os_w32_state.entity_mutex); +} + +//////////////////////////////// +//~ rjf: Thread Entry Point + +internal DWORD +os_w32_thread_entry_point(void *ptr) +{ + OS_W32_Entity *entity = (OS_W32_Entity *)ptr; + OS_ThreadFunctionType *func = entity->thread.func; + void *thread_ptr = entity->thread.ptr; + TCTX tctx_; + tctx_init_and_equip(&tctx_); + func(thread_ptr); + tctx_release(); + return 0; +} + +//////////////////////////////// +//~ rjf: @os_hooks System/Process Info (Implemented Per-OS) + +internal OS_SystemInfo * +os_get_system_info(void) +{ + return &os_w32_state.system_info; +} + +internal OS_ProcessInfo * +os_get_process_info(void) +{ + return &os_w32_state.process_info; +} + +internal String8 +os_get_current_path(Arena *arena) +{ + Temp scratch = scratch_begin(&arena, 1); + DWORD length = GetCurrentDirectoryW(0, 0); + U16 *memory = push_array_no_zero(scratch.arena, U16, length + 1); + length = GetCurrentDirectoryW(length + 1, (WCHAR*)memory); + String8 name = str8_from_16(arena, str16(memory, length)); + scratch_end(scratch); + return name; +} + +//////////////////////////////// +//~ rjf: @os_hooks Memory Allocation (Implemented Per-OS) + +//- rjf: basic + +internal void * +os_reserve(U64 size) +{ + void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); + return result; +} + +internal B32 +os_commit(void *ptr, U64 size) +{ + B32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); + return result; +} + +internal void +os_decommit(void *ptr, U64 size) +{ + VirtualFree(ptr, size, MEM_DECOMMIT); +} + +internal void +os_release(void *ptr, U64 size) +{ + // NOTE(rjf): size not used - not necessary on Windows, but necessary for other OSes. + VirtualFree(ptr, 0, MEM_RELEASE); +} + +//- rjf: large pages + +internal void * +os_reserve_large(U64 size) +{ + // we commit on reserve because windows + void *result = VirtualAlloc(0, size, MEM_RESERVE|MEM_COMMIT|MEM_LARGE_PAGES, PAGE_READWRITE); + return result; +} + +internal B32 +os_commit_large(void *ptr, U64 size) +{ + return 1; +} + +//////////////////////////////// +//~ rjf: @os_hooks Thread Info (Implemented Per-OS) + +internal U32 +os_tid(void) +{ + DWORD id = GetCurrentThreadId(); + return (U32)id; +} + +internal void +os_set_thread_name(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + + // rjf: windows 10 style + if(w32_SetThreadDescription_func) + { + String16 name16 = str16_from_8(scratch.arena, name); + HRESULT hr = w32_SetThreadDescription_func(GetCurrentThread(), (WCHAR*)name16.str); + } + + // rjf: raise-exception style + { + String8 name_copy = push_str8_copy(scratch.arena, name); +#pragma pack(push,8) + typedef struct THREADNAME_INFO THREADNAME_INFO; + struct THREADNAME_INFO + { + U32 dwType; // Must be 0x1000. + char *szName; // Pointer to name (in user addr space). + U32 dwThreadID; // Thread ID (-1=caller thread). + U32 dwFlags; // Reserved for future use, must be zero. + }; +#pragma pack(pop) + THREADNAME_INFO info; + info.dwType = 0x1000; + info.szName = (char *)name_copy.str; + info.dwThreadID = os_tid(); + info.dwFlags = 0; +#pragma warning(push) +#pragma warning(disable: 6320 6322) + __try + { + RaiseException(0x406D1388, 0, sizeof(info) / sizeof(void *), (const ULONG_PTR *)&info); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + } +#pragma warning(pop) + } + + scratch_end(scratch); +} + +//////////////////////////////// +//~ rjf: @os_hooks Aborting (Implemented Per-OS) + +internal void +os_abort(S32 exit_code) +{ + ExitProcess(exit_code); +} + +//////////////////////////////// +//~ rjf: @os_hooks File System (Implemented Per-OS) + +//- rjf: files + +internal OS_Handle +os_file_open(OS_AccessFlags flags, String8 path) +{ + OS_Handle result = {0}; + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + DWORD access_flags = 0; + DWORD share_mode = 0; + DWORD creation_disposition = OPEN_EXISTING; + if(flags & OS_AccessFlag_Read) {access_flags |= GENERIC_READ;} + if(flags & OS_AccessFlag_Write) {access_flags |= GENERIC_WRITE;} + if(flags & OS_AccessFlag_Execute) {access_flags |= GENERIC_EXECUTE;} + if(flags & OS_AccessFlag_ShareRead) {share_mode |= FILE_SHARE_READ;} + if(flags & OS_AccessFlag_ShareWrite) {share_mode |= FILE_SHARE_WRITE|FILE_SHARE_DELETE;} + if(flags & OS_AccessFlag_Write) {creation_disposition = CREATE_ALWAYS;} + if(flags & OS_AccessFlag_Append) {creation_disposition = OPEN_ALWAYS;} + HANDLE file = CreateFileW((WCHAR *)path16.str, access_flags, share_mode, 0, creation_disposition, FILE_ATTRIBUTE_NORMAL, 0); + if(file != INVALID_HANDLE_VALUE) + { + result.u64[0] = (U64)file; + } + scratch_end(scratch); + return result; +} + +internal void +os_file_close(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { return; } + HANDLE handle = (HANDLE)file.u64[0]; + BOOL result = CloseHandle(handle); + (void)result; +} + +internal U64 +os_file_read(OS_Handle file, Rng1U64 rng, void *out_data) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + HANDLE handle = (HANDLE)file.u64[0]; + + // rjf: clamp range by file size + U64 size = 0; + GetFileSizeEx(handle, (LARGE_INTEGER *)&size); + Rng1U64 rng_clamped = r1u64(ClampTop(rng.min, size), ClampTop(rng.max, size)); + U64 total_read_size = 0; + + // rjf: read loop + { + U64 to_read = dim_1u64(rng_clamped); + for(U64 off = rng.min; total_read_size < to_read;) + { + U64 amt64 = to_read - total_read_size; + U32 amt32 = u32_from_u64_saturate(amt64); + DWORD read_size = 0; + OVERLAPPED overlapped = {0}; + overlapped.Offset = (off&0x00000000ffffffffull); + overlapped.OffsetHigh = (off&0xffffffff00000000ull) >> 32; + ReadFile(handle, (U8 *)out_data + total_read_size, amt32, &read_size, &overlapped); + off += read_size; + total_read_size += read_size; + if(read_size != amt32) + { + break; + } + } + } + + return total_read_size; +} + +internal U64 +os_file_write(OS_Handle file, Rng1U64 rng, void *data) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + HANDLE win_handle = (HANDLE)file.u64[0]; + U64 src_off = 0; + U64 dst_off = rng.min; + U64 bytes_to_write_total = rng.max-rng.min; + U64 total_bytes_written = 0; + for(;src_off < bytes_to_write_total;) + { + void *bytes_src = (void *)((U8 *)data + src_off); + U64 bytes_to_write_64 = (bytes_to_write_total-src_off); + U32 bytes_to_write_32 = u32_from_u64_saturate(bytes_to_write_64); + U32 bytes_written = 0; + OVERLAPPED overlapped = {0}; + overlapped.Offset = (dst_off&0x00000000ffffffffull); + overlapped.OffsetHigh = (dst_off&0xffffffff00000000ull) >> 32; + BOOL success = WriteFile(win_handle, bytes_src, bytes_to_write_32, (DWORD *)&bytes_written, &overlapped); + if(success == 0) + { + break; + } + src_off += bytes_written; + dst_off += bytes_written; + total_bytes_written += bytes_written; + } + return total_bytes_written; +} + +internal B32 +os_file_set_time(OS_Handle file, DateTime time) +{ + if(os_handle_match(file, os_handle_zero())) { return 0; } + B32 result = 0; + HANDLE handle = (HANDLE)file.u64[0]; + SYSTEMTIME system_time = {0}; + os_w32_system_time_from_date_time(&system_time, &time); + FILETIME file_time = {0}; + result = (SystemTimeToFileTime(&system_time, &file_time) && + SetFileTime(handle, &file_time, &file_time, &file_time)); + return result; +} + +internal FileProperties +os_properties_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { FileProperties r = {0}; return r; } + FileProperties props = {0}; + HANDLE handle = (HANDLE)file.u64[0]; + BY_HANDLE_FILE_INFORMATION info; + BOOL info_good = GetFileInformationByHandle(handle, &info); + if(info_good) + { + U32 size_lo = info.nFileSizeLow; + U32 size_hi = info.nFileSizeHigh; + props.size = (U64)size_lo | (((U64)size_hi)<<32); + os_w32_dense_time_from_file_time(&props.modified, &info.ftLastWriteTime); + os_w32_dense_time_from_file_time(&props.created, &info.ftCreationTime); + props.flags = os_w32_file_property_flags_from_dwFileAttributes(info.dwFileAttributes); + } + return props; +} + +internal OS_FileID +os_id_from_file(OS_Handle file) +{ + if(os_handle_match(file, os_handle_zero())) { OS_FileID r = {0}; return r; } + OS_FileID result = {0}; + HANDLE handle = (HANDLE)file.u64[0]; + BY_HANDLE_FILE_INFORMATION info; + BOOL is_ok = GetFileInformationByHandle(handle, &info); + if(is_ok) + { + result.v[0] = info.dwVolumeSerialNumber; + result.v[1] = info.nFileIndexLow; + result.v[2] = info.nFileIndexHigh; + } + return result; +} + +internal B32 +os_delete_file_at_path(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + B32 result = DeleteFileW((WCHAR*)path16.str); + scratch_end(scratch); + return result; +} + +internal B32 +os_copy_file_path(String8 dst, String8 src) +{ + Temp scratch = scratch_begin(0, 0); + String16 dst16 = str16_from_8(scratch.arena, dst); + String16 src16 = str16_from_8(scratch.arena, src); + B32 result = CopyFileW((WCHAR*)src16.str, (WCHAR*)dst16.str, 0); + scratch_end(scratch); + return result; +} + +internal String8 +os_full_path_from_path(Arena *arena, String8 path) +{ + Temp scratch = scratch_begin(&arena, 1); + DWORD buffer_size = MAX_PATH + 1; + U16 *buffer = push_array_no_zero(scratch.arena, U16, buffer_size); + String16 path16 = str16_from_8(scratch.arena, path); + DWORD path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, (WCHAR*)buffer, NULL); + String8 full_path = str8_from_16(arena, str16(buffer, path16_size)); + scratch_end(scratch); + return full_path; +} + +internal B32 +os_file_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0,0); + String16 path16 = str16_from_8(scratch.arena, path); + DWORD attributes = GetFileAttributesW((WCHAR *)path16.str); + B32 exists = (attributes != INVALID_FILE_ATTRIBUTES) && !!(~attributes & FILE_ATTRIBUTE_DIRECTORY); + scratch_end(scratch); + return exists; +} + +internal FileProperties +os_properties_from_file_path(String8 path) +{ + WIN32_FIND_DATAW find_data = {0}; + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + HANDLE handle = FindFirstFileW((WCHAR *)path16.str, &find_data); + FileProperties props = {0}; + if(handle != INVALID_HANDLE_VALUE) + { + props.size = Compose64Bit(find_data.nFileSizeHigh, find_data.nFileSizeLow); + os_w32_dense_time_from_file_time(&props.created, &find_data.ftCreationTime); + os_w32_dense_time_from_file_time(&props.modified, &find_data.ftLastWriteTime); + props.flags = os_w32_file_property_flags_from_dwFileAttributes(find_data.dwFileAttributes); + } + FindClose(handle); + scratch_end(scratch); + return props; +} + +//- rjf: file maps + +internal OS_Handle +os_file_map_open(OS_AccessFlags flags, OS_Handle file) +{ + OS_Handle map = {0}; + { + HANDLE file_handle = (HANDLE)file.u64[0]; + DWORD protect_flags = 0; + { + switch(flags) + { + default:{}break; + case OS_AccessFlag_Read: + {protect_flags = PAGE_READONLY;}break; + case OS_AccessFlag_Write: + case OS_AccessFlag_Read|OS_AccessFlag_Write: + {protect_flags = PAGE_READWRITE;}break; + case OS_AccessFlag_Execute: + case OS_AccessFlag_Read|OS_AccessFlag_Execute: + {protect_flags = PAGE_EXECUTE_READ;}break; + case OS_AccessFlag_Execute|OS_AccessFlag_Write|OS_AccessFlag_Read: + case OS_AccessFlag_Execute|OS_AccessFlag_Write: + {protect_flags = PAGE_EXECUTE_READWRITE;}break; + } + } + HANDLE map_handle = CreateFileMappingA(file_handle, 0, protect_flags, 0, 0, 0); + map.u64[0] = (U64)map_handle; + } + return map; +} + +internal void +os_file_map_close(OS_Handle map) +{ + HANDLE handle = (HANDLE)map.u64[0]; + BOOL result = CloseHandle(handle); + (void)result; +} + +internal void * +os_file_map_view_open(OS_Handle map, OS_AccessFlags flags, Rng1U64 range) +{ + HANDLE handle = (HANDLE)map.u64[0]; + U32 off_lo = (U32)((range.min&0x00000000ffffffffull)>>0); + U32 off_hi = (U32)((range.min&0xffffffff00000000ull)>>32); + U64 size = dim_1u64(range); + DWORD access_flags = 0; + { + switch(flags) + { + default:{}break; + case OS_AccessFlag_Read: + { + access_flags = FILE_MAP_READ; + }break; + case OS_AccessFlag_Write: + { + access_flags = FILE_MAP_WRITE; + }break; + case OS_AccessFlag_Read|OS_AccessFlag_Write: + { + access_flags = FILE_MAP_ALL_ACCESS; + }break; + case OS_AccessFlag_Execute: + case OS_AccessFlag_Read|OS_AccessFlag_Execute: + case OS_AccessFlag_Write|OS_AccessFlag_Execute: + case OS_AccessFlag_Read|OS_AccessFlag_Write|OS_AccessFlag_Execute: + { + access_flags = FILE_MAP_ALL_ACCESS|FILE_MAP_EXECUTE; + }break; + } + } + void *result = MapViewOfFile(handle, access_flags, off_hi, off_lo, size); + return result; +} + +internal void +os_file_map_view_close(OS_Handle map, void *ptr, Rng1U64 range) +{ + BOOL result = UnmapViewOfFile(ptr); + (void)result; +} + +//- rjf: directory iteration + +internal OS_FileIter * +os_file_iter_begin(Arena *arena, String8 path, OS_FileIterFlags flags) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 path_with_wildcard = push_str8_cat(scratch.arena, path, str8_lit("\\*")); + String16 path16 = str16_from_8(scratch.arena, path_with_wildcard); + OS_FileIter *iter = push_array(arena, OS_FileIter, 1); + iter->flags = flags; + OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; + if(path.size == 0) + { + w32_iter->is_volume_iter = 1; + WCHAR buffer[512] = {0}; + DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer); + String8List drive_strings = {0}; + for(U64 off = 0; off < (U64)length;) + { + String16 next_drive_string_16 = str16_cstring((U16 *)buffer+off); + off += next_drive_string_16.size+1; + String8 next_drive_string = str8_from_16(arena, next_drive_string_16); + next_drive_string = str8_chop_last_slash(next_drive_string); + str8_list_push(scratch.arena, &drive_strings, next_drive_string); + } + w32_iter->drive_strings = str8_array_from_list(arena, &drive_strings); + w32_iter->drive_strings_iter_idx = 0; + } + else + { + w32_iter->handle = FindFirstFileW((WCHAR*)path16.str, &w32_iter->find_data); + } + scratch_end(scratch); + return iter; +} + +internal B32 +os_file_iter_next(Arena *arena, OS_FileIter *iter, OS_FileInfo *info_out) +{ + B32 result = 0; + OS_FileIterFlags flags = iter->flags; + OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; + switch(w32_iter->is_volume_iter) + { + //- rjf: file iteration + default: + case 0: + { + if (!(flags & OS_FileIterFlag_Done) && w32_iter->handle != INVALID_HANDLE_VALUE) + { + do + { + // check is usable + B32 usable_file = 1; + + WCHAR *file_name = w32_iter->find_data.cFileName; + DWORD attributes = w32_iter->find_data.dwFileAttributes; + if (file_name[0] == '.'){ + if (flags & OS_FileIterFlag_SkipHiddenFiles){ + usable_file = 0; + } + else if (file_name[1] == 0){ + usable_file = 0; + } + else if (file_name[1] == '.' && file_name[2] == 0){ + usable_file = 0; + } + } + if (attributes & FILE_ATTRIBUTE_DIRECTORY){ + if (flags & OS_FileIterFlag_SkipFolders){ + usable_file = 0; + } + } + else{ + if (flags & OS_FileIterFlag_SkipFiles){ + usable_file = 0; + } + } + + // emit if usable + if (usable_file){ + info_out->name = str8_from_16(arena, str16_cstring((U16*)file_name)); + info_out->props.size = (U64)w32_iter->find_data.nFileSizeLow | (((U64)w32_iter->find_data.nFileSizeHigh)<<32); + os_w32_dense_time_from_file_time(&info_out->props.created, &w32_iter->find_data.ftCreationTime); + os_w32_dense_time_from_file_time(&info_out->props.modified, &w32_iter->find_data.ftLastWriteTime); + info_out->props.flags = os_w32_file_property_flags_from_dwFileAttributes(attributes); + result = 1; + if (!FindNextFileW(w32_iter->handle, &w32_iter->find_data)){ + iter->flags |= OS_FileIterFlag_Done; + } + break; + } + }while(FindNextFileW(w32_iter->handle, &w32_iter->find_data)); + } + }break; + + //- rjf: volume iteration + case 1: + { + result = w32_iter->drive_strings_iter_idx < w32_iter->drive_strings.count; + if(result != 0) + { + MemoryZeroStruct(info_out); + info_out->name = w32_iter->drive_strings.v[w32_iter->drive_strings_iter_idx]; + info_out->props.flags |= FilePropertyFlag_IsFolder; + w32_iter->drive_strings_iter_idx += 1; + } + }break; + } + if(!result) + { + iter->flags |= OS_FileIterFlag_Done; + } + return result; +} + +internal void +os_file_iter_end(OS_FileIter *iter) +{ + OS_W32_FileIter *w32_iter = (OS_W32_FileIter*)iter->memory; + FindClose(w32_iter->handle); +} + +//- rjf: directory creation + +internal B32 +os_make_directory(String8 path) +{ + B32 result = 0; + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, path); + WIN32_FILE_ATTRIBUTE_DATA attributes = {0}; + GetFileAttributesExW((WCHAR*)name16.str, GetFileExInfoStandard, &attributes); + if(attributes.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + result = 1; + } + else if(CreateDirectoryW((WCHAR*)name16.str, 0)) + { + result = 1; + } + scratch_end(scratch); + return(result); +} + +//////////////////////////////// +//~ rjf: @os_hooks Shared Memory (Implemented Per-OS) + +internal OS_Handle +os_shared_memory_alloc(U64 size, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE file = CreateFileMappingW(INVALID_HANDLE_VALUE, + 0, + PAGE_READWRITE, + (U32)((size & 0xffffffff00000000) >> 32), + (U32)((size & 0x00000000ffffffff)), + (WCHAR *)name16.str); + OS_Handle result = {(U64)file}; + scratch_end(scratch); + return result; +} + +internal OS_Handle +os_shared_memory_open(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE file = OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, (WCHAR *)name16.str); + OS_Handle result = {(U64)file}; + scratch_end(scratch); + return result; +} + +internal void +os_shared_memory_close(OS_Handle handle) +{ + HANDLE file = (HANDLE)(handle.u64[0]); + CloseHandle(file); +} + +internal void * +os_shared_memory_view_open(OS_Handle handle, Rng1U64 range) +{ + HANDLE file = (HANDLE)(handle.u64[0]); + U64 offset = range.min; + U64 size = range.max-range.min; + void *ptr = MapViewOfFile(file, FILE_MAP_ALL_ACCESS, + (U32)((offset & 0xffffffff00000000) >> 32), + (U32)((offset & 0x00000000ffffffff)), + size); + return ptr; +} + +internal void +os_shared_memory_view_close(OS_Handle handle, void *ptr, Rng1U64 range) +{ + UnmapViewOfFile(ptr); +} + +//////////////////////////////// +//~ rjf: @os_hooks Time (Implemented Per-OS) + +internal U64 +os_now_microseconds(void) +{ + U64 result = 0; + LARGE_INTEGER large_int_counter; + if(QueryPerformanceCounter(&large_int_counter)) + { + result = (large_int_counter.QuadPart*Million(1))/os_w32_state.microsecond_resolution; + } + return result; +} + +internal U32 +os_now_unix(void) +{ + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + U64 win32_time = ((U64)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime; + U64 unix_time64 = ((win32_time - 0x19DB1DED53E8000ULL) / 10000000); + U32 unix_time32 = (U32)unix_time64; + return unix_time32; +} + +internal DateTime +os_now_universal_time(void) +{ + SYSTEMTIME systime = {0}; + GetSystemTime(&systime); + DateTime result = {0}; + os_w32_date_time_from_system_time(&result, &systime); + return result; +} + +internal DateTime +os_universal_time_from_local(DateTime *date_time) +{ + SYSTEMTIME systime = {0}; + os_w32_system_time_from_date_time(&systime, date_time); + FILETIME ftime = {0}; + SystemTimeToFileTime(&systime, &ftime); + FILETIME ftime_local = {0}; + LocalFileTimeToFileTime(&ftime, &ftime_local); + FileTimeToSystemTime(&ftime_local, &systime); + DateTime result = {0}; + os_w32_date_time_from_system_time(&result, &systime); + return result; +} + +internal DateTime +os_local_time_from_universal(DateTime *date_time) +{ + SYSTEMTIME systime = {0}; + os_w32_system_time_from_date_time(&systime, date_time); + FILETIME ftime = {0}; + SystemTimeToFileTime(&systime, &ftime); + FILETIME ftime_local = {0}; + FileTimeToLocalFileTime(&ftime, &ftime_local); + FileTimeToSystemTime(&ftime_local, &systime); + DateTime result = {0}; + os_w32_date_time_from_system_time(&result, &systime); + return result; +} + +internal void +os_sleep_milliseconds(U32 msec) +{ + Sleep(msec); +} + +//////////////////////////////// +//~ rjf: @os_hooks Child Processes (Implemented Per-OS) + +internal OS_Handle +os_process_launch(OS_ProcessLaunchParams *params) +{ + OS_Handle result = {0}; + Temp scratch = scratch_begin(0, 0); + + //- rjf: form full command string + String8 cmd = {0}; + { + StringJoin join_params = {0}; + join_params.pre = str8_lit("\""); + join_params.sep = str8_lit("\" \""); + join_params.post = str8_lit("\""); + cmd = str8_list_join(scratch.arena, ¶ms->cmd_line, &join_params); + } + + //- rjf: form environment + B32 use_null_env_arg = 0; + String8 env = {0}; + { + StringJoin join_params2 = {0}; + join_params2.sep = str8_lit("\0"); + join_params2.post = str8_lit("\0"); + String8List all_opts = params->env; + if(params->inherit_env != 0) + { + if(all_opts.node_count != 0) + { + MemoryZeroStruct(&all_opts); + for(String8Node *n = params->env.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + for(String8Node *n = os_w32_state.process_info.environment.first; n != 0; n = n->next) + { + str8_list_push(scratch.arena, &all_opts, n->string); + } + } + else + { + use_null_env_arg = 1; + } + } + if(use_null_env_arg == 0) + { + env = str8_list_join(scratch.arena, &all_opts, &join_params2); + } + } + + //- rjf: utf-8 -> utf-16 + String16 cmd16 = str16_from_8(scratch.arena, cmd); + String16 dir16 = str16_from_8(scratch.arena, params->path); + String16 env16 = {0}; + if(use_null_env_arg == 0) + { + env16 = str16_from_8(scratch.arena, env); + } + + //- rjf: determine creation flags + DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; + if(params->consoleless) + { + creation_flags |= CREATE_NO_WINDOW; + } + + //- rjf: launch + STARTUPINFOW startup_info = {sizeof(startup_info)}; + PROCESS_INFORMATION process_info = {0}; + if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, 0, creation_flags, use_null_env_arg ? 0 : (WCHAR*)env16.str, (WCHAR*)dir16.str, &startup_info, &process_info)) + { + result.u64[0] = (U64)process_info.hProcess; + CloseHandle(process_info.hThread); + } + + scratch_end(scratch); + return result; +} + +internal B32 +os_process_join(OS_Handle handle, U64 endt_us) +{ + HANDLE process = (HANDLE)(handle.u64[0]); + DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + DWORD result = WaitForSingleObject(process, sleep_ms); + return (result == WAIT_OBJECT_0); +} + +internal void +os_process_detach(OS_Handle handle) +{ + HANDLE process = (HANDLE)(handle.u64[0]); + CloseHandle(process); +} + +//////////////////////////////// +//~ rjf: @os_hooks Threads (Implemented Per-OS) + +internal OS_Handle +os_thread_launch(OS_ThreadFunctionType *func, void *ptr, void *params) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Thread); + entity->thread.func = func; + entity->thread.ptr = ptr; + entity->thread.handle = CreateThread(0, 0, os_w32_thread_entry_point, entity, 0, &entity->thread.tid); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal B32 +os_thread_join(OS_Handle handle, U64 endt_us) +{ + DWORD sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + OS_W32_Entity *entity = (OS_W32_Entity *)PtrFromInt(handle.u64[0]); + DWORD wait_result = WAIT_OBJECT_0; + if(entity != 0) + { + wait_result = WaitForSingleObject(entity->thread.handle, sleep_ms); + } + os_w32_entity_release(entity); + return (wait_result == WAIT_OBJECT_0); +} + +internal void +os_thread_detach(OS_Handle thread) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(thread.u64[0]); + os_w32_entity_release(entity); +} + +//////////////////////////////// +//~ rjf: @os_hooks Synchronization Primitives (Implemented Per-OS) + +//- rjf: mutexes + +internal OS_Handle +os_mutex_alloc(void) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_Mutex); + InitializeCriticalSection(&entity->mutex); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal void +os_mutex_release(OS_Handle mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + os_w32_entity_release(entity); +} + +internal void +os_mutex_take(OS_Handle mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + EnterCriticalSection(&entity->mutex); +} + +internal void +os_mutex_drop(OS_Handle mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + LeaveCriticalSection(&entity->mutex); +} + +//- rjf: reader/writer mutexes + +internal OS_Handle +os_rw_mutex_alloc(void) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_RWMutex); + InitializeSRWLock(&entity->rw_mutex); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal void +os_rw_mutex_release(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + os_w32_entity_release(entity); +} + +internal void +os_rw_mutex_take_r(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + AcquireSRWLockShared(&entity->rw_mutex); +} + +internal void +os_rw_mutex_drop_r(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + ReleaseSRWLockShared(&entity->rw_mutex); +} + +internal void +os_rw_mutex_take_w(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + AcquireSRWLockExclusive(&entity->rw_mutex); +} + +internal void +os_rw_mutex_drop_w(OS_Handle rw_mutex) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(rw_mutex.u64[0]); + ReleaseSRWLockExclusive(&entity->rw_mutex); +} + +//- rjf: condition variables + +internal OS_Handle +os_condition_variable_alloc(void) +{ + OS_W32_Entity *entity = os_w32_entity_alloc(OS_W32_EntityKind_ConditionVariable); + InitializeConditionVariable(&entity->cv); + OS_Handle result = {IntFromPtr(entity)}; + return result; +} + +internal void +os_condition_variable_release(OS_Handle cv) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + os_w32_entity_release(entity); +} + +internal B32 +os_condition_variable_wait(OS_Handle cv, OS_Handle mutex, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + BOOL result = 0; + if(sleep_ms > 0) + { + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex.u64[0]); + result = SleepConditionVariableCS(&entity->cv, &mutex_entity->mutex, sleep_ms); + } + return result; +} + +internal B32 +os_condition_variable_wait_rw_r(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + BOOL result = 0; + if(sleep_ms > 0) + { + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex_rw.u64[0]); + result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, + CONDITION_VARIABLE_LOCKMODE_SHARED); + } + return result; +} + +internal B32 +os_condition_variable_wait_rw_w(OS_Handle cv, OS_Handle mutex_rw, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + BOOL result = 0; + if(sleep_ms > 0) + { + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + OS_W32_Entity *mutex_entity = (OS_W32_Entity*)PtrFromInt(mutex_rw.u64[0]); + result = SleepConditionVariableSRW(&entity->cv, &mutex_entity->rw_mutex, sleep_ms, 0); + } + return result; +} + +internal void +os_condition_variable_signal(OS_Handle cv) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + WakeConditionVariable(&entity->cv); +} + +internal void +os_condition_variable_broadcast(OS_Handle cv) +{ + OS_W32_Entity *entity = (OS_W32_Entity*)PtrFromInt(cv.u64[0]); + WakeAllConditionVariable(&entity->cv); +} + +//- rjf: cross-process semaphores + +internal OS_Handle +os_semaphore_alloc(U32 initial_count, U32 max_count, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE handle = CreateSemaphoreW(0, initial_count, max_count, (WCHAR *)name16.str); + OS_Handle result = {(U64)handle}; + scratch_end(scratch); + return result; +} + +internal void +os_semaphore_release(OS_Handle semaphore) +{ + HANDLE handle = (HANDLE)semaphore.u64[0]; + CloseHandle(handle); +} + +internal OS_Handle +os_semaphore_open(String8 name) +{ + Temp scratch = scratch_begin(0, 0); + String16 name16 = str16_from_8(scratch.arena, name); + HANDLE handle = OpenSemaphoreW(SEMAPHORE_ALL_ACCESS , 0, (WCHAR *)name16.str); + OS_Handle result = {(U64)handle}; + scratch_end(scratch); + return result; +} + +internal void +os_semaphore_close(OS_Handle semaphore) +{ + HANDLE handle = (HANDLE)semaphore.u64[0]; + CloseHandle(handle); +} + +internal B32 +os_semaphore_take(OS_Handle semaphore, U64 endt_us) +{ + U32 sleep_ms = os_w32_sleep_ms_from_endt_us(endt_us); + HANDLE handle = (HANDLE)semaphore.u64[0]; + DWORD wait_result = WaitForSingleObject(handle, sleep_ms); + B32 result = (wait_result == WAIT_OBJECT_0); + return result; +} + +internal void +os_semaphore_drop(OS_Handle semaphore) +{ + HANDLE handle = (HANDLE)semaphore.u64[0]; + ReleaseSemaphore(handle, 1, 0); +} + +//////////////////////////////// +//~ rjf: @os_hooks Dynamically-Loaded Libraries (Implemented Per-OS) + +internal OS_Handle +os_library_open(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + String16 path16 = str16_from_8(scratch.arena, path); + HMODULE mod = LoadLibraryW((LPCWSTR)path16.str); + OS_Handle result = { (U64)mod }; + scratch_end(scratch); + return result; +} + +internal VoidProc* +os_library_load_proc(OS_Handle lib, String8 name) +{ + Temp scratch = scratch_begin(0, 0); + HMODULE mod = (HMODULE)lib.u64[0]; + name = push_str8_copy(scratch.arena, name); + VoidProc *result = (VoidProc*)GetProcAddress(mod, (LPCSTR)name.str); + scratch_end(scratch); + return result; +} + +internal void +os_library_close(OS_Handle lib) +{ + HMODULE mod = (HMODULE)lib.u64[0]; + FreeLibrary(mod); +} + +//////////////////////////////// +//~ rjf: @os_hooks Safe Calls (Implemented Per-OS) + +internal void +os_safe_call(OS_ThreadFunctionType *func, OS_ThreadFunctionType *fail_handler, void *ptr) +{ + __try + { + func(ptr); + } + __except (EXCEPTION_EXECUTE_HANDLER) + { + if(fail_handler != 0) + { + fail_handler(ptr); + } + ExitProcess(1); + } +} + +//////////////////////////////// +//~ rjf: @os_hooks GUIDs (Implemented Per-OS) + +internal OS_Guid +os_make_guid(void) +{ + OS_Guid result; MemoryZeroStruct(&result); + UUID uuid; + RPC_STATUS rpc_status = UuidCreate(&uuid); + if(rpc_status == RPC_S_OK) + { + result.data1 = uuid.Data1; + result.data2 = uuid.Data2; + result.data3 = uuid.Data3; + MemoryCopyArray(result.data4, uuid.Data4); + } + return result; +} + +//////////////////////////////// +//~ rjf: @os_hooks Entry Points (Implemented Per-OS) + +#include +#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside +#include + +internal B32 win32_g_is_quiet = 0; + +internal HRESULT WINAPI +win32_dialog_callback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, LONG_PTR data) +{ + if(msg == TDN_HYPERLINK_CLICKED) + { + ShellExecuteW(NULL, L"open", (LPWSTR)lparam, NULL, NULL, SW_SHOWNORMAL); + } + return S_OK; +} + +internal LONG WINAPI +win32_exception_filter(EXCEPTION_POINTERS* exception_ptrs) +{ + if(win32_g_is_quiet) + { + ExitProcess(1); + } + + static volatile LONG first = 0; + if(InterlockedCompareExchange(&first, 1, 0) != 0) + { + // prevent failures in other threads to popup same message box + // this handler just shows first thread that crashes + // we are terminating afterwards anyway + for (;;) Sleep(1000); + } + + WCHAR buffer[4096] = {0}; + int buflen = 0; + + DWORD exception_code = exception_ptrs->ExceptionRecord->ExceptionCode; + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"A fatal exception (code 0x%x) occurred. The process is terminating.\n", exception_code); + + // load dbghelp dynamically just in case if it is missing + HMODULE dbghelp = LoadLibraryA("dbghelp.dll"); + if(dbghelp) + { + DWORD (WINAPI *dbg_SymSetOptions)(DWORD SymOptions); + BOOL (WINAPI *dbg_SymInitializeW)(HANDLE hProcess, PCWSTR UserSearchPath, BOOL fInvadeProcess); + BOOL (WINAPI *dbg_StackWalk64)(DWORD MachineType, HANDLE hProcess, HANDLE hThread, + LPSTACKFRAME64 StackFrame, PVOID ContextRecord, PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine, + PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine, PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine, + PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress); + PVOID (WINAPI *dbg_SymFunctionTableAccess64)(HANDLE hProcess, DWORD64 AddrBase); + DWORD64 (WINAPI *dbg_SymGetModuleBase64)(HANDLE hProcess, DWORD64 qwAddr); + BOOL (WINAPI *dbg_SymFromAddrW)(HANDLE hProcess, DWORD64 Address, PDWORD64 Displacement, PSYMBOL_INFOW Symbol); + BOOL (WINAPI *dbg_SymGetLineFromAddrW64)(HANDLE hProcess, DWORD64 dwAddr, PDWORD pdwDisplacement, PIMAGEHLP_LINEW64 Line); + BOOL (WINAPI *dbg_SymGetModuleInfoW64)(HANDLE hProcess, DWORD64 qwAddr, PIMAGEHLP_MODULEW64 ModuleInfo); + + *(FARPROC*)&dbg_SymSetOptions = GetProcAddress(dbghelp, "SymSetOptions"); + *(FARPROC*)&dbg_SymInitializeW = GetProcAddress(dbghelp, "SymInitializeW"); + *(FARPROC*)&dbg_StackWalk64 = GetProcAddress(dbghelp, "StackWalk64"); + *(FARPROC*)&dbg_SymFunctionTableAccess64 = GetProcAddress(dbghelp, "SymFunctionTableAccess64"); + *(FARPROC*)&dbg_SymGetModuleBase64 = GetProcAddress(dbghelp, "SymGetModuleBase64"); + *(FARPROC*)&dbg_SymFromAddrW = GetProcAddress(dbghelp, "SymFromAddrW"); + *(FARPROC*)&dbg_SymGetLineFromAddrW64 = GetProcAddress(dbghelp, "SymGetLineFromAddrW64"); + *(FARPROC*)&dbg_SymGetModuleInfoW64 = GetProcAddress(dbghelp, "SymGetModuleInfoW64"); + + if(dbg_SymSetOptions && dbg_SymInitializeW && dbg_StackWalk64 && dbg_SymFunctionTableAccess64 && dbg_SymGetModuleBase64 && dbg_SymFromAddrW && dbg_SymGetLineFromAddrW64 && dbg_SymGetModuleInfoW64) + { + HANDLE process = GetCurrentProcess(); + HANDLE thread = GetCurrentThread(); + CONTEXT* context = exception_ptrs->ContextRecord; + + dbg_SymSetOptions(SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + if(dbg_SymInitializeW(process, L"", TRUE)) + { + // check that raddbg.pdb file is good + B32 raddbg_pdb_valid = 0; + { + IMAGEHLP_MODULEW64 module = {0}; + module.SizeOfStruct = sizeof(module); + if(dbg_SymGetModuleInfoW64(process, (DWORD64)&win32_exception_filter, &module)) + { + raddbg_pdb_valid = (module.SymType == SymPdb); + } + } + + if(!raddbg_pdb_valid) + { + buflen += wnsprintfW(buffer + buflen, sizeof(buffer) - buflen, + L"\nThe PDB debug information file for this executable is not valid or was not found. Please rebuild binary to get the call stack.\n"); + } + else + { + STACKFRAME64 frame = {0}; + DWORD image_type; +#if defined(_M_AMD64) + image_type = IMAGE_FILE_MACHINE_AMD64; + frame.AddrPC.Offset = context->Rip; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context->Rbp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context->Rsp; + frame.AddrStack.Mode = AddrModeFlat; +#elif defined(_M_ARM64) + image_type = IMAGE_FILE_MACHINE_ARM64; + frame.AddrPC.Offset = context->Pc; + frame.AddrPC.Mode = AddrModeFlat; + frame.AddrFrame.Offset = context->Fp; + frame.AddrFrame.Mode = AddrModeFlat; + frame.AddrStack.Offset = context->Sp; + frame.AddrStack.Mode = AddrModeFlat; +#else +# error Architecture not supported! +#endif + + for(U32 idx=0; ;idx++) + { + const U32 max_frames = 32; + if(idx == max_frames) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"..."); + break; + } + + if(!dbg_StackWalk64(image_type, process, thread, &frame, context, 0, dbg_SymFunctionTableAccess64, dbg_SymGetModuleBase64, 0)) + { + break; + } + + U64 address = frame.AddrPC.Offset; + if(address == 0) + { + break; + } + + if(idx==0) + { +#if BUILD_CONSOLE_INTERFACE + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nCreate a new issue with this report at %S.\n\n", BUILD_ISSUES_LINK_STRING_LITERAL); +#else + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, + L"\nPress Ctrl+C to copy this text to clipboard, then create a new issue at\n" + L"%S\n\n", BUILD_ISSUES_LINK_STRING_LITERAL, BUILD_ISSUES_LINK_STRING_LITERAL); +#endif + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"Call stack:\n"); + } + + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"%u. [0x%I64x]", idx + 1, address); + + struct { + SYMBOL_INFOW info; + WCHAR name[MAX_SYM_NAME]; + } symbol = {0}; + + symbol.info.SizeOfStruct = sizeof(symbol.info); + symbol.info.MaxNameLen = MAX_SYM_NAME; + + DWORD64 displacement = 0; + if(dbg_SymFromAddrW(process, address, &displacement, &symbol.info)) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s +%u", symbol.info.Name, (DWORD)displacement); + + IMAGEHLP_LINEW64 line = {0}; + line.SizeOfStruct = sizeof(line); + + DWORD line_displacement = 0; + if(dbg_SymGetLineFromAddrW64(process, address, &line_displacement, &line)) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L", %s line %u", PathFindFileNameW(line.FileName), line.LineNumber); + } + } + else + { + IMAGEHLP_MODULEW64 module = {0}; + module.SizeOfStruct = sizeof(module); + if(dbg_SymGetModuleInfoW64(process, address, &module)) + { + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L" %s", module.ModuleName); + } + } + + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\n"); + } + } + } + } + } + + buflen += wnsprintfW(buffer + buflen, ArrayCount(buffer) - buflen, L"\nVersion: %S%S", BUILD_VERSION_STRING_LITERAL, BUILD_GIT_HASH_STRING_LITERAL_APPEND); + +#if BUILD_CONSOLE_INTERFACE + fwprintf(stderr, L"\n--- Fatal Exception ---\n"); + fwprintf(stderr, L"%s\n\n", buffer); +#else + TASKDIALOGCONFIG dialog = {0}; + dialog.cbSize = sizeof(dialog); + dialog.dwFlags = TDF_SIZE_TO_CONTENT | TDF_ENABLE_HYPERLINKS | TDF_ALLOW_DIALOG_CANCELLATION; + dialog.pszMainIcon = TD_ERROR_ICON; + dialog.dwCommonButtons = TDCBF_CLOSE_BUTTON; + dialog.pszWindowTitle = L"Fatal Exception"; + dialog.pszContent = buffer; + dialog.pfCallback = &win32_dialog_callback; + TaskDialogIndirect(&dialog, 0, 0, 0); +#endif + + ExitProcess(1); +} + +#undef OS_WINDOWS // shlwapi uses its own OS_WINDOWS include inside +#define OS_WINDOWS 1 + +internal void +w32_entry_point_caller(int argc, WCHAR **wargv) +{ + SetUnhandledExceptionFilter(&win32_exception_filter); + + //- rjf: do OS layer initialization + { + // rjf: dynamically load windows functions which are not guaranteed + // in all SDKs + { + HMODULE module = LoadLibraryA("kernel32.dll"); + w32_SetThreadDescription_func = (W32_SetThreadDescription_Type *)GetProcAddress(module, "SetThreadDescription"); + FreeLibrary(module); + } + + // rjf: try to enable large pages if we can + { + HANDLE token; + if(OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) + { + LUID luid; + if(LookupPrivilegeValue(0, SE_LOCK_MEMORY_NAME, &luid)) + { + TOKEN_PRIVILEGES priv; + priv.PrivilegeCount = 1; + priv.Privileges[0].Luid = luid; + priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + AdjustTokenPrivileges(token, 0, &priv, sizeof(priv), 0, 0); + } + CloseHandle(token); + } + } + + // rjf: get system info + SYSTEM_INFO sysinfo = {0}; + GetSystemInfo(&sysinfo); + + // rjf: set up non-dynamically-alloc'd state + // + // (we need to set up some basics before this layer can supply + // memory allocation primitives) + { + os_w32_state.microsecond_resolution = 1; + LARGE_INTEGER large_int_resolution; + if(QueryPerformanceFrequency(&large_int_resolution)) + { + os_w32_state.microsecond_resolution = large_int_resolution.QuadPart; + } + } + { + OS_SystemInfo *info = &os_w32_state.system_info; + info->logical_processor_count = (U64)sysinfo.dwNumberOfProcessors; + info->page_size = sysinfo.dwPageSize; + info->large_page_size = GetLargePageMinimum(); + info->allocation_granularity = sysinfo.dwAllocationGranularity; + } + { + OS_ProcessInfo *info = &os_w32_state.process_info; + info->pid = GetCurrentProcessId(); + } + + // rjf: set up thread context + local_persist TCTX tctx; + tctx_init_and_equip(&tctx); + + // rjf: set up dynamically-alloc'd state + Arena *arena = arena_alloc(); + { + os_w32_state.arena = arena; + { + OS_SystemInfo *info = &os_w32_state.system_info; + U8 buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0}; + DWORD size = MAX_COMPUTERNAME_LENGTH + 1; + if(GetComputerNameA((char*)buffer, &size)) + { + info->machine_name = push_str8_copy(arena, str8(buffer, size)); + } + } + } + { + OS_ProcessInfo *info = &os_w32_state.process_info; + { + Temp scratch = scratch_begin(0, 0); + DWORD size = KB(32); + U16 *buffer = push_array_no_zero(scratch.arena, U16, size); + DWORD length = GetModuleFileNameW(0, (WCHAR*)buffer, size); + String8 name8 = str8_from_16(scratch.arena, str16(buffer, length)); + String8 name_chopped = str8_chop_last_slash(name8); + info->binary_path = push_str8_copy(arena, name_chopped); + scratch_end(scratch); + } + info->initial_path = os_get_current_path(arena); + { + Temp scratch = scratch_begin(0, 0); + U64 size = KB(32); + U16 *buffer = push_array_no_zero(scratch.arena, U16, size); + if(SUCCEEDED(SHGetFolderPathW(0, CSIDL_APPDATA, 0, 0, (WCHAR*)buffer))) + { + info->user_program_data_path = str8_from_16(arena, str16_cstring(buffer)); + } + scratch_end(scratch); + } + { + WCHAR *this_proc_env = GetEnvironmentStringsW(); + U64 start_idx = 0; + for(U64 idx = 0;; idx += 1) + { + if(this_proc_env[idx] == 0) + { + if(start_idx == idx) + { + break; + } + else + { + String16 string16 = str16((U16 *)this_proc_env + start_idx, idx - start_idx); + String8 string = str8_from_16(arena, string16); + str8_list_push(arena, &info->environment, string); + start_idx = idx+1; + } + } + } + } + } + + // rjf: set up entity storage + InitializeCriticalSection(&os_w32_state.entity_mutex); + os_w32_state.entity_arena = arena_alloc(); + } + + //- rjf: extract arguments + Arena *args_arena = arena_alloc(.reserve_size = MB(1), .commit_size = KB(32)); + char **argv = push_array(args_arena, char *, argc); + for(int i = 0; i < argc; i += 1) + { + String16 arg16 = str16_cstring((U16 *)wargv[i]); + String8 arg8 = str8_from_16(args_arena, arg16); + if(str8_match(arg8, str8_lit("--quiet"), StringMatchFlag_CaseInsensitive)) + { + win32_g_is_quiet = 1; + } + argv[i] = (char *)arg8.str; + } + + //- rjf: call into "real" entry point + main_thread_base_entry_point(entry_point, argv, (U64)argc); +} + +#if BUILD_CONSOLE_INTERFACE +int wmain(int argc, WCHAR **argv) +{ + w32_entry_point_caller(argc, argv); + return 0; +} +#else +int wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) +{ + w32_entry_point_caller(__argc, __wargv); + return 0; +} +#endif diff --git a/metagen/metagen_os/core/win32/metagen_os_core_win32.h b/metagen/metagen_os/core/win32/metagen_os_core_win32.h new file mode 100644 index 0000000..93be306 --- /dev/null +++ b/metagen/metagen_os/core/win32/metagen_os_core_win32.h @@ -0,0 +1,122 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_CORE_WIN32_H +#define OS_CORE_WIN32_H + +//////////////////////////////// +//~ rjf: Includes / Libraries + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#include +#pragma comment(lib, "user32") +#pragma comment(lib, "winmm") +#pragma comment(lib, "shell32") +#pragma comment(lib, "advapi32") +#pragma comment(lib, "rpcrt4") +#pragma comment(lib, "shlwapi") +#pragma comment(lib, "comctl32") +#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") // this is required for loading correct comctl32 dll file + +//////////////////////////////// +//~ rjf: File Iterator Types + +typedef struct OS_W32_FileIter OS_W32_FileIter; +struct OS_W32_FileIter +{ + HANDLE handle; + WIN32_FIND_DATAW find_data; + B32 is_volume_iter; + String8Array drive_strings; + U64 drive_strings_iter_idx; +}; +StaticAssert(sizeof(Member(OS_FileIter, memory)) >= sizeof(OS_W32_FileIter), file_iter_memory_size); + +//////////////////////////////// +//~ rjf: Entity Types + +typedef enum OS_W32_EntityKind +{ + OS_W32_EntityKind_Null, + OS_W32_EntityKind_Thread, + OS_W32_EntityKind_Mutex, + OS_W32_EntityKind_RWMutex, + OS_W32_EntityKind_ConditionVariable, +} +OS_W32_EntityKind; + +typedef struct OS_W32_Entity OS_W32_Entity; +struct OS_W32_Entity +{ + OS_W32_Entity *next; + OS_W32_EntityKind kind; + union + { + struct + { + OS_ThreadFunctionType *func; + void *ptr; + HANDLE handle; + DWORD tid; + } thread; + CRITICAL_SECTION mutex; + SRWLOCK rw_mutex; + CONDITION_VARIABLE cv; + }; +}; + +//////////////////////////////// +//~ rjf: State + +typedef struct OS_W32_State OS_W32_State; +struct OS_W32_State +{ + Arena *arena; + + // rjf: info + OS_SystemInfo system_info; + OS_ProcessInfo process_info; + U64 microsecond_resolution; + + // rjf: entity storage + CRITICAL_SECTION entity_mutex; + Arena *entity_arena; + OS_W32_Entity *entity_free; +}; + +//////////////////////////////// +//~ rjf: Globals + +global OS_W32_State os_w32_state = {0}; + +//////////////////////////////// +//~ rjf: File Info Conversion Helpers + +internal FilePropertyFlags os_w32_file_property_flags_from_dwFileAttributes(DWORD dwFileAttributes); +internal void os_w32_file_properties_from_attribute_data(FileProperties *properties, WIN32_FILE_ATTRIBUTE_DATA *attributes); + +//////////////////////////////// +//~ rjf: Time Conversion Helpers + +internal void os_w32_date_time_from_system_time(DateTime *out, SYSTEMTIME *in); +internal void os_w32_system_time_from_date_time(SYSTEMTIME *out, DateTime *in); +internal void os_w32_dense_time_from_file_time(DenseTime *out, FILETIME *in); +internal U32 os_w32_sleep_ms_from_endt_us(U64 endt_us); + +//////////////////////////////// +//~ rjf: Entity Functions + +internal OS_W32_Entity *os_w32_entity_alloc(OS_W32_EntityKind kind); +internal void os_w32_entity_release(OS_W32_Entity *entity); + +//////////////////////////////// +//~ rjf: Thread Entry Point + +internal DWORD os_w32_thread_entry_point(void *ptr); + +#endif // OS_CORE_WIN32_H diff --git a/metagen/metagen_os/metagen_os_inc.c b/metagen/metagen_os/metagen_os_inc.c new file mode 100644 index 0000000..33df464 --- /dev/null +++ b/metagen/metagen_os/metagen_os_inc.c @@ -0,0 +1,12 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "metagen/metagen_os/core/metagen_os_core.c" + +#if OS_WINDOWS +# include "metagen/metagen_os/core/win32/metagen_os_core_win32.c" +#elif OS_LINUX +# include "metagen/metagen_os/core/linux/metagen_os_core_linux.c" +#else +# error OS core layer not implemented for this operating system. +#endif diff --git a/metagen/metagen_os/metagen_os_inc.h b/metagen/metagen_os/metagen_os_inc.h new file mode 100644 index 0000000..4ed76be --- /dev/null +++ b/metagen/metagen_os/metagen_os_inc.h @@ -0,0 +1,25 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef OS_INC_H +#define OS_INC_H + +#if !defined(OS_FEATURE_GRAPHICAL) +# define OS_FEATURE_GRAPHICAL 0 +#endif + +#if !defined(OS_GFX_STUB) +# define OS_GFX_STUB 0 +#endif + +#include "metagen/metagen_os/core/metagen_os_core.h" + +#if OS_WINDOWS +# include "metagen/metagen_os/core/win32/metagen_os_core_win32.h" +#elif OS_LINUX +# include "metagen/metagen_os/core/linux/metagen_os_core_linux.h" +#else +# error OS core layer not implemented for this operating system. +#endif + +#endif // OS_INC_H