From 84f6eb4583ae90a4769a48e6a8d3fed6ade779a8 Mon Sep 17 00:00:00 2001 From: Anton Ljungdahl Date: Tue, 27 May 2025 21:51:42 +0200 Subject: [PATCH] shader codegen and compilation --- .gitignore | 1 + anton_render.raddbgproj | 3 +- build.bat | 9 +- src/base/base_core.h | 7 +- src/base/base_math.h | 15 + src/gen_test/generated/gen_test.meta.c | 2 + src/gen_test/generated/gen_test.meta.h | 6 + src/gen_test/generated/test.meta.c | 8 - src/gen_test/generated/test.meta.h | 12 - src/main.c | 2 +- src/main.h | 4 + src/mdesk/mdesk.c | 1228 +++++ src/mdesk/mdesk.h | 346 ++ src/metagen/codegen.c | 148 - src/metagen/codegen.h | 26 - src/metagen/codegen_table.c | 564 --- src/metagen/codegen_table.h | 92 - src/metagen/metadesk_base/md.c | 4410 ----------------- src/metagen/metadesk_base/md.h | 1248 ----- src/metagen/metadesk_base/md_stb_sprintf.h | 1905 ------- src/metagen/metagen.c | 1144 +++++ src/metagen/metagen.h | 329 ++ src/metagen/metagen_base/metagen_base_arena.c | 248 + src/metagen/metagen_base/metagen_base_arena.h | 91 + .../metagen_base/metagen_base_command_line.c | 250 + .../metagen_base/metagen_base_command_line.h | 56 + .../metagen_base_context_cracking.h | 247 + src/metagen/metagen_base/metagen_base_core.c | 604 +++ src/metagen/metagen_base/metagen_base_core.h | 888 ++++ .../metagen_base/metagen_base_entry_point.c | 130 + .../metagen_base/metagen_base_entry_point.h | 12 + src/metagen/metagen_base/metagen_base_inc.c | 20 + src/metagen/metagen_base/metagen_base_inc.h | 24 + src/metagen/metagen_base/metagen_base_log.c | 103 + src/metagen/metagen_base/metagen_base_log.h | 65 + .../metagen_base/metagen_base_markup.c | 21 + .../metagen_base/metagen_base_markup.h | 12 + src/metagen/metagen_base/metagen_base_math.c | 659 +++ src/metagen/metagen_base/metagen_base_math.h | 675 +++ src/metagen/metagen_base/metagen_base_meta.c | 422 ++ src/metagen/metagen_base/metagen_base_meta.h | 298 ++ .../metagen_base/metagen_base_profile.c | 2 + .../metagen_base/metagen_base_profile.h | 96 + .../metagen_base/metagen_base_strings.c | 2446 +++++++++ .../metagen_base/metagen_base_strings.h | 412 ++ .../metagen_base_thread_context.c | 87 + .../metagen_base_thread_context.h | 41 + src/metagen/metagen_main.c | 652 +++ .../core/linux/metagen_os_core_linux.c | 1337 +++++ .../core/linux/metagen_os_core_linux.h | 137 + src/metagen/metagen_os/core/metagen_os_core.c | 240 + src/metagen/metagen_os/core/metagen_os_core.h | 335 ++ .../core/win32/metagen_os_core_win32.c | 1785 +++++++ .../core/win32/metagen_os_core_win32.h | 121 + src/metagen/metagen_os/metagen_os_inc.c | 10 + src/metagen/metagen_os/metagen_os_inc.h | 19 + .../generated/{os_core.meta.c => os.meta.c} | 2 + src/os/generated/os.meta.h | 124 + src/os/generated/os_core.meta.h | 121 - src/os/os_core.c | 2 +- src/os/os_core.h | 2 +- src/os/os_core.mdesk | 13 +- .../d3d12/generated/render_d3d12.meta.c | 16 + .../d3d12/generated/render_d3d12.meta.h | 71 + src/render/d3d12/render_d3d12.c | 470 +- src/render/d3d12/render_d3d12.h | 12 + src/render/d3d12/render_shaders_d3d12.mdesk | 108 + src/render/render_core.h | 23 + src/third_party/stb/stb_sprintf.h | 2055 ++++++++ 69 files changed, 18393 insertions(+), 8680 deletions(-) create mode 100644 src/gen_test/generated/gen_test.meta.c create mode 100644 src/gen_test/generated/gen_test.meta.h delete mode 100644 src/gen_test/generated/test.meta.c delete mode 100644 src/gen_test/generated/test.meta.h create mode 100644 src/main.h create mode 100644 src/mdesk/mdesk.c create mode 100644 src/mdesk/mdesk.h delete mode 100644 src/metagen/codegen.c delete mode 100644 src/metagen/codegen.h delete mode 100644 src/metagen/codegen_table.c delete mode 100644 src/metagen/codegen_table.h delete mode 100644 src/metagen/metadesk_base/md.c delete mode 100644 src/metagen/metadesk_base/md.h delete mode 100644 src/metagen/metadesk_base/md_stb_sprintf.h create mode 100644 src/metagen/metagen.c create mode 100644 src/metagen/metagen.h create mode 100644 src/metagen/metagen_base/metagen_base_arena.c create mode 100644 src/metagen/metagen_base/metagen_base_arena.h create mode 100644 src/metagen/metagen_base/metagen_base_command_line.c create mode 100644 src/metagen/metagen_base/metagen_base_command_line.h create mode 100644 src/metagen/metagen_base/metagen_base_context_cracking.h create mode 100644 src/metagen/metagen_base/metagen_base_core.c create mode 100644 src/metagen/metagen_base/metagen_base_core.h create mode 100644 src/metagen/metagen_base/metagen_base_entry_point.c create mode 100644 src/metagen/metagen_base/metagen_base_entry_point.h create mode 100644 src/metagen/metagen_base/metagen_base_inc.c create mode 100644 src/metagen/metagen_base/metagen_base_inc.h create mode 100644 src/metagen/metagen_base/metagen_base_log.c create mode 100644 src/metagen/metagen_base/metagen_base_log.h create mode 100644 src/metagen/metagen_base/metagen_base_markup.c create mode 100644 src/metagen/metagen_base/metagen_base_markup.h create mode 100644 src/metagen/metagen_base/metagen_base_math.c create mode 100644 src/metagen/metagen_base/metagen_base_math.h create mode 100644 src/metagen/metagen_base/metagen_base_meta.c create mode 100644 src/metagen/metagen_base/metagen_base_meta.h create mode 100644 src/metagen/metagen_base/metagen_base_profile.c create mode 100644 src/metagen/metagen_base/metagen_base_profile.h create mode 100644 src/metagen/metagen_base/metagen_base_strings.c create mode 100644 src/metagen/metagen_base/metagen_base_strings.h create mode 100644 src/metagen/metagen_base/metagen_base_thread_context.c create mode 100644 src/metagen/metagen_base/metagen_base_thread_context.h create mode 100644 src/metagen/metagen_main.c create mode 100644 src/metagen/metagen_os/core/linux/metagen_os_core_linux.c create mode 100644 src/metagen/metagen_os/core/linux/metagen_os_core_linux.h create mode 100644 src/metagen/metagen_os/core/metagen_os_core.c create mode 100644 src/metagen/metagen_os/core/metagen_os_core.h create mode 100644 src/metagen/metagen_os/core/win32/metagen_os_core_win32.c create mode 100644 src/metagen/metagen_os/core/win32/metagen_os_core_win32.h create mode 100644 src/metagen/metagen_os/metagen_os_inc.c create mode 100644 src/metagen/metagen_os/metagen_os_inc.h rename src/os/generated/{os_core.meta.c => os.meta.c} (97%) create mode 100644 src/os/generated/os.meta.h delete mode 100644 src/os/generated/os_core.meta.h create mode 100644 src/render/d3d12/generated/render_d3d12.meta.c create mode 100644 src/render/d3d12/generated/render_d3d12.meta.h create mode 100644 src/render/d3d12/render_shaders_d3d12.mdesk create mode 100644 src/third_party/stb/stb_sprintf.h diff --git a/.gitignore b/.gitignore index 34ab63b..6894a0f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +src/scratch.c build/* *sublime* *ctm* diff --git a/anton_render.raddbgproj b/anton_render.raddbgproj index da69b92..9838279 100644 --- a/anton_render.raddbgproj +++ b/anton_render.raddbgproj @@ -1,8 +1,9 @@ // raddbg 0.9.17 project file recent_file: path: "src/render/d3d12/render_d3d12.c" -recent_file: path: "src/os/win32/os_gfx_win32.c" +recent_file: path: "d:/os/obj/amd64fre/minkernel/crts/ucrt/src/appcrt/string/mt/objfre/amd64/minkernel/crts/ucrt/src/appcrt/string/amd64/strlen.asm" recent_file: path: "src/main.c" +recent_file: path: "src/os/win32/os_gfx_win32.c" target: { executable: "build/program.exe" diff --git a/build.bat b/build.bat index 05127ef..c7d394f 100644 --- a/build.bat +++ b/build.bat @@ -9,7 +9,7 @@ pushd .\build ctime -begin timeBuild.ctm -cl /Zi /nologo /wd5287 ../src/metagen/codegen.c +cl /Zi /nologo /wd5287 ..\\src\\metagen\\metagen_main.c set LastError=%ERRORLEVEL% ctime -end timeBuild.ctm %LastError% @@ -22,12 +22,7 @@ IF NOT %LastError%==0 GOTO :end ctime -begin timeBuild.ctm pushd src -pushd gen_test -..\..\build\codegen.exe ./ -popd -pushd os -..\..\build\codegen.exe ./ -popd +..\build\metagen_main.exe popd echo Codegen time: diff --git a/src/base/base_core.h b/src/base/base_core.h index 440766d..8b69dfc 100644 --- a/src/base/base_core.h +++ b/src/base/base_core.h @@ -11,13 +11,16 @@ #define COMPILER_MSVC 1 #endif +#if COMPILER_MSVC +# pragma section(".rdata$", read) +# define read_only __declspec(allocate(".rdata$")) +#endif + //////////////////////////////// //~ 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 diff --git a/src/base/base_math.h b/src/base/base_math.h index 928f143..3dff209 100644 --- a/src/base/base_math.h +++ b/src/base/base_math.h @@ -10,9 +10,24 @@ typedef union Vec3F32 { F32 x; F32 y; + F32 z; }; + F32 v[3]; } Vec3F32; +typedef union Vec4F32 +{ + struct + { + F32 x; + F32 y; + F32 z; + F32 w; + }; + F32 v[4]; +} +Vec4F32; + #endif /* BASE_MATH_H */ \ No newline at end of file diff --git a/src/gen_test/generated/gen_test.meta.c b/src/gen_test/generated/gen_test.meta.c new file mode 100644 index 0000000..464432c --- /dev/null +++ b/src/gen_test/generated/gen_test.meta.c @@ -0,0 +1,2 @@ +//- GENERATED CODE + diff --git a/src/gen_test/generated/gen_test.meta.h b/src/gen_test/generated/gen_test.meta.h new file mode 100644 index 0000000..33abf75 --- /dev/null +++ b/src/gen_test/generated/gen_test.meta.h @@ -0,0 +1,6 @@ +//- GENERATED CODE + +#ifndef GEN_TEST_META_H +#define GEN_TEST_META_H + +#endif // GEN_TEST_META_H diff --git a/src/gen_test/generated/test.meta.c b/src/gen_test/generated/test.meta.c deleted file mode 100644 index 8574982..0000000 --- a/src/gen_test/generated/test.meta.c +++ /dev/null @@ -1,8 +0,0 @@ -char * test_value_table[4] = -{ -"A", -"B", -"C", -"D", -}; - diff --git a/src/gen_test/generated/test.meta.h b/src/gen_test/generated/test.meta.h deleted file mode 100644 index 777aaa0..0000000 --- a/src/gen_test/generated/test.meta.h +++ /dev/null @@ -1,12 +0,0 @@ -typedef enum TestEnum -{ -TestEnum_A, -TestEnum_B, -TestEnum_C, -TestEnum_D, -TestEnum_COUNT -} -TestEnum; - -extern char * test_value_table[4]; - diff --git a/src/main.c b/src/main.c index b3e1fba..3d6e56c 100644 --- a/src/main.c +++ b/src/main.c @@ -59,7 +59,7 @@ entry_point() for(U64 i = 0; i < os_events_received_on_frame(); i += 1) { OS_Event *event = &os_events.list[i]; - if(event->kind != OS_EventKind_Null) + if(event->kind != OS_EventKind_Null && event->kind != OS_EventKind_MouseMove) { os_console_log_fmt("%s ", os_key_display_string[event->key]); if(event->kind == OS_EventKind_Release) diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..415b314 --- /dev/null +++ b/src/main.h @@ -0,0 +1,4 @@ +#ifndef MAIN_H +#define MAIN_H + +#endif // MAIN_H diff --git a/src/mdesk/mdesk.c b/src/mdesk/mdesk.c new file mode 100644 index 0000000..df8f2aa --- /dev/null +++ b/src/mdesk/mdesk.c @@ -0,0 +1,1228 @@ +// 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) || + str8_match(str8_substr(text, token[2].range), str8_lit("["), 0) || + 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 *all* current implicit work tasks + if(work_top->kind == MD_ParseWorkKind_MainImplicit && token->flags & MD_TokenFlag_Newline) + { + for(;work_top->kind == MD_ParseWorkKind_MainImplicit;) + { + 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; +} + +//////////////////////////////// +//~ rjf: Node Pointer List Functions + +internal void +md_node_ptr_list_push(Arena *arena, MD_NodePtrList *list, MD_Node *node) +{ + MD_NodePtrNode *n = push_array(arena, MD_NodePtrNode, 1); + n->v = node; + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal void +md_node_ptr_list_push_front(Arena *arena, MD_NodePtrList *list, MD_Node *node) +{ + MD_NodePtrNode *n = push_array(arena, MD_NodePtrNode, 1); + n->v = node; + SLLQueuePushFront(list->first, list->last, n); + list->count += 1; +} + diff --git a/src/mdesk/mdesk.h b/src/mdesk/mdesk.h new file mode 100644 index 0000000..dc899db --- /dev/null +++ b/src/mdesk/mdesk.h @@ -0,0 +1,346 @@ +// 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; +}; + +typedef struct MD_NodePtrNode MD_NodePtrNode; +struct MD_NodePtrNode +{ + MD_NodePtrNode *next; + MD_Node *v; +}; + +typedef struct MD_NodePtrList MD_NodePtrList; +struct MD_NodePtrList +{ + MD_NodePtrNode *first; + MD_NodePtrNode *last; + U64 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); + +//////////////////////////////// +//~ rjf: Node Pointer List Functions + +internal void md_node_ptr_list_push(Arena *arena, MD_NodePtrList *list, MD_Node *node); +internal void md_node_ptr_list_push_front(Arena *arena, MD_NodePtrList *list, MD_Node *node); + +#endif // MDESK_H diff --git a/src/metagen/codegen.c b/src/metagen/codegen.c deleted file mode 100644 index 6c7f7a0..0000000 --- a/src/metagen/codegen.c +++ /dev/null @@ -1,148 +0,0 @@ -#include - -#include "metadesk_base/md.h" -#include "metadesk_base/md.c" - -#include "codegen.h" - -#include "codegen_table.h" -#include "codegen_table.c" - -//////////////////////////////// -//~ rjf: Helpers - -static CG_FilePair -CG_FilePairFromNode(MD_Node *node) -{ - CG_FilePair result = {0}; - MD_CodeLoc loc = MD_CodeLocFromNode(node); - MD_String8 filename = loc.filename; - MD_b32 found = 0; - for(int i = 0; i < cg_file_pair_count; i += 1) - { - if(MD_S8Match(filename, cg_file_pairs[i].src_filename, 0)) - { - result = cg_file_pairs[i]; - found = 1; - break; - } - } - if(found == 0) - { - MD_String8 folder = MD_PathChopLastSlash(filename); - MD_String8 layer_name = MD_PathChopLastPeriod(MD_PathSkipLastSlash(loc.filename)); - MD_String8 gen_folder = MD_S8Fmt(cg_arena, "%.*s/generated", MD_S8VArg(folder)); - MD_String8 h_filename = MD_S8Fmt(cg_arena, "%.*s/%.*s.meta.h", MD_S8VArg(gen_folder), MD_S8VArg(layer_name)); - MD_String8 c_filename = MD_S8Fmt(cg_arena, "%.*s/%.*s.meta.c", MD_S8VArg(gen_folder), MD_S8VArg(layer_name)); - result.src_filename = filename; - result.h = fopen((char *)h_filename.str, "w"); - result.c = fopen((char *)c_filename.str, "w"); - cg_file_pairs[cg_file_pair_count] = result; - cg_file_pair_count += 1; - } - return result; -} - -static void -CG_CloseAllFiles(void) -{ - for(int i = 0; i < cg_file_pair_count; i += 1) - { - fclose(cg_file_pairs[i].h); - fclose(cg_file_pairs[i].c); - } -} - -static MD_String8 -CG_EscapedFromString(MD_Arena *arena, MD_String8 string) -{ - MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); - MD_String8List strs = {0}; - MD_b32 escaped = 0; - MD_u64 start = 0; - for(MD_u64 idx = 0; idx <= string.size; idx += 1) - { - if(idx < string.size && escaped) - { - escaped = 0; - start = idx+1; - MD_u8 replace_char = 0; - switch(string.str[idx]) - { - default: break; - case 'a': replace_char = 0x07; break; - case 'b': replace_char = 0x08; break; - case 'e': replace_char = 0x1b; break; - case 'f': replace_char = 0x0c; break; - case 'n': replace_char = 0x0a; break; - case 'r': replace_char = 0x0d; break; - case 't': replace_char = 0x09; break; - case 'v': replace_char = 0x0b; break; - case '\\': replace_char = 0x5c; break; - case '\'': replace_char = 0x27; break; - case '\"': replace_char = 0x22; break; - case '\?': replace_char = 0x3f; break; - } - if(replace_char) - { - MD_String8 string = MD_S8Copy(scratch.arena, MD_S8(&replace_char, 1)); - MD_S8ListPush(scratch.arena, &strs, string); - } - } - else if(idx == string.size || string.str[idx] == '\\') - { - escaped = (string.str[idx] == '\\'); - MD_String8 part = MD_S8Substring(string, start, idx); - MD_S8ListPush(scratch.arena, &strs, part); - start = idx; - } - } - MD_String8 result = MD_S8ListJoin(arena, strs, 0); - MD_ReleaseScratch(scratch); - return result; -} - -//////////////////////////////// -//~ rjf: Entry Point - -int main(int argument_count, char **arguments) -{ - cg_arena = MD_ArenaAlloc(); - - //- rjf: parse command line - MD_String8List options = MD_StringListFromArgCV(cg_arena, argument_count, arguments); - MD_CmdLine cmdln = MD_MakeCmdLineFromOptions(cg_arena, options); - - //- rjf: parse all files - MD_Node *file_list = MD_MakeList(cg_arena); - for(MD_String8Node *n = cmdln.inputs.first; n != 0; n = n->next) - { - MD_String8 code_dir = n->string; - printf("searching %.*s for metacode...\n", MD_S8VArg(code_dir)); - MD_FileIter it = {0}; - MD_FileIterBegin(&it, code_dir); - for(MD_FileInfo info = {0};;) - { - info = MD_FileIterNext(cg_arena, &it); - if(info.filename.size == 0) - { - break; - } - if(!(info.flags & MD_FileFlag_Directory) && - MD_S8Match(MD_PathSkipLastPeriod(info.filename), MD_S8Lit("mdesk"), MD_StringMatchFlag_CaseInsensitive)) - { - printf("parsing %.*s...\n", MD_S8VArg(info.filename)); - MD_String8 path = MD_S8Fmt(cg_arena, "%.*s/%.*s", MD_S8VArg(code_dir), MD_S8VArg(info.filename)); - MD_ParseResult parse = MD_ParseWholeFile(cg_arena, path); - MD_PushNewReference(cg_arena, file_list, parse.node); - } - } - MD_FileIterEnd(&it); - } - - //- rjf: send all parses to backends - CG_TBL_Generate(file_list); - CG_CloseAllFiles(); - - return 0; -} diff --git a/src/metagen/codegen.h b/src/metagen/codegen.h deleted file mode 100644 index 4b48865..0000000 --- a/src/metagen/codegen.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef METAPROGRAM_H -#define METAPROGRAM_H - -typedef struct CG_FilePair CG_FilePair; -struct CG_FilePair -{ - MD_String8 src_filename; - FILE *h; - FILE *c; -}; - -//////////////////////////////// -//~ rjf: Helpers - -static CG_FilePair CG_FilePairFromNode(MD_Node *node); -static void CG_CloseAllFiles(void); -static MD_String8 CG_EscapedFromString(MD_Arena *arena, MD_String8 string); - -//////////////////////////////// -//~ rjf: Globals - -static MD_Arena *cg_arena = 0; -static int cg_file_pair_count = 0; -static CG_FilePair cg_file_pairs[4096] = {0}; - -#endif // METAPROGRAM_H diff --git a/src/metagen/codegen_table.c b/src/metagen/codegen_table.c deleted file mode 100644 index fd3ef8e..0000000 --- a/src/metagen/codegen_table.c +++ /dev/null @@ -1,564 +0,0 @@ -static MD_Map cg_tbl_top_level_node_grid_map = {0}; -static MD_Map cg_tbl_top_level_table_header_map = {0}; - -static MD_Map cg_tbl_layer_map_gen = {0}; -static MD_Map cg_tbl_layer_map_gen_enum = {0}; -static MD_Map cg_tbl_layer_map_gen_data = {0}; - -static MD_String8 cg_tbl_tag__table = MD_S8LitComp("table"); -static MD_String8 cg_tbl_tag__table_gen = MD_S8LitComp("table_gen"); -static MD_String8 cg_tbl_tag__table_gen_enum = MD_S8LitComp("table_gen_enum"); -static MD_String8 cg_tbl_tag__table_gen_data = MD_S8LitComp("table_gen_data"); - -static CG_NodeArray -CG_NodeArrayMake(MD_u64 count) -{ - CG_NodeArray result = {0}; - result.count = count; - result.v = MD_PushArrayZero(cg_arena, MD_Node *, result.count); - for(MD_u64 idx = 0; idx < result.count; idx += 1) - { - result.v[idx] = MD_NilNode(); - } - return result; -} - -static CG_NodeGrid -CG_GridFromNode(MD_Node *node) -{ - CG_NodeGrid grid = {0}; - - //- rjf: determine dimensions - MD_u64 row_count = 0; - MD_u64 column_count = 0; - { - for(MD_EachNode(row, node->first_child)) - { - row_count += 1; - MD_u64 cell_count_this_row = MD_ChildCountFromNode(row); - column_count = MD_Max(cell_count_this_row, column_count); - } - } - - //- rjf: allocate cells / row parents - { - grid.cells = CG_NodeArrayMake(row_count * column_count); - grid.row_parents = CG_NodeArrayMake(row_count); - } - - //- rjf: fill cells - { - MD_u64 row_idx = 0; - for(MD_EachNode(row, node->first_child)) - { - MD_u64 col_idx = 0; - grid.row_parents.v[row_idx] = row; - for(MD_EachNode(cell, row->first_child)) - { - grid.cells.v[row_idx * column_count + col_idx] = cell; - col_idx += 1; - } - row_idx += 1; - } - } - - return grid; -} - -static CG_TableHeader -CG_TableHeaderFromTag(MD_Node *tag) -{ - CG_TableHeader result = {0}; - result.column_count = MD_ChildCountFromNode(tag); - result.column_descs = MD_PushArrayZero(cg_arena, CG_ColumnDesc, result.column_count); - MD_u64 idx = 0; - for(MD_EachNode(column_node, tag->first_child)) - { - result.column_descs[idx].kind = CG_ColumnKind_Default; - result.column_descs[idx].name = column_node->string; - MD_Node *check_for_tag = MD_TagFromString(column_node, MD_S8Lit("check_for_tag"), 0); - if(!MD_NodeIsNil(check_for_tag)) - { - result.column_descs[idx].kind = CG_ColumnKind_CheckForTag; - result.column_descs[idx].tag_string = check_for_tag->first_child->string; - } - idx += 1; - } - return result; -} - -static MD_u64 -CG_RowChildIndexFromColumnName(CG_TableHeader *header, MD_String8 column_name) -{ - MD_u64 result = 0; - for(MD_u64 idx = 0; idx < header->column_count; idx += 1) - { - if(MD_S8Match(header->column_descs[idx].name, column_name, 0)) - { - break; - } - if(header->column_descs[idx].kind == CG_ColumnKind_Default) - { - result += 1; - } - } - return result; -} - -static MD_i64 -CG_TableExprEvaluate_Numeric(CG_ExpandInfo *info, MD_Expr *expr) -{ - MD_i64 result = 0; - CG_TableOp op = expr->op ? expr->op->op_id : CG_TableOp_Null; - switch(op) - { - case CG_TableOp_Equal: - case CG_TableOp_IsNotEqual: - { - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - MD_String8List left_strs = {0}; - MD_String8List right_strs = {0}; - CG_TableExprEvaluate_String(info, expr->left, &left_strs); - CG_TableExprEvaluate_String(info, expr->right, &right_strs); - MD_String8 left_str = MD_S8ListJoin(scratch.arena, left_strs, 0); - MD_String8 right_str = MD_S8ListJoin(scratch.arena, right_strs, 0); - result = MD_S8Match(left_str, right_str, 0); - if(op == CG_TableOp_IsNotEqual) - { - result = !result; - } - MD_ReleaseScratch(scratch); - }break; - - case CG_TableOp_BooleanAnd: - case CG_TableOp_BooleanOr: - { - MD_i64 left = CG_TableExprEvaluate_Numeric(info, expr->left); - MD_i64 right = CG_TableExprEvaluate_Numeric(info, expr->right); - switch(op) - { - case CG_TableOp_BooleanAnd: result = left && right; break; - case CG_TableOp_BooleanOr: result = left || right; break; - } - }break; - } - return result; -} - -static void -CG_TableExprEvaluate_String(CG_ExpandInfo *info, MD_Expr *expr, MD_String8List *out) -{ - CG_TableOp op = expr->op ? expr->op->op_id : CG_TableOp_Null; - switch(op) - { - default: - case CG_TableOp_Null: - { - MD_S8ListPush(cg_arena, out, expr->md_node->string); - }break; - - case CG_TableOp_Dot: - { - MD_Expr *label_expr = expr->left; - MD_Expr *column_query_expr = expr->right; - MD_Node *label_node = label_expr->md_node; - MD_Node *column_query_node = column_query_expr->md_node; - MD_String8 label = label_node->string; - MD_String8 column_query = column_query_node->string; - MD_b32 column_query_is_by_name = column_query_node->flags & MD_NodeFlag_Identifier; - MD_b32 column_query_is_by_index = column_query_node->flags & MD_NodeFlag_Numeric; - - // rjf: find which expansion this label refers to, grab its iterator - CG_ExpandIter *iter = 0; - for(CG_ExpandIter *it = info->first_expand_iter; it != 0; it = it->next) - { - if(MD_S8Match(it->label, label, 0)) - { - iter = it; - break; - } - } - - // rjf: error on invalid label - if(iter == 0) - { - MD_PrintMessageFmt(stderr, MD_CodeLocFromNode(label_node), MD_MessageKind_Error, "Expansion label \"%S\" was not found as referring to a valid @expand tag.", label); - } - - // rjf: generate strings from iterator's table - if(iter != 0) - { - CG_NodeGrid *grid = iter->grid; - CG_TableHeader *header = iter->header; - MD_Node *row = grid->row_parents.v[iter->idx]; - - // rjf: grab the cell string given the row & column_query - MD_String8 cell_string = {0}; - { - // NOTE(rjf): by-name index (look into table header) - if(column_query_is_by_name && header != 0) - { - MD_u64 column_idx = 0; - CG_ColumnDesc *column = 0; - for(MD_u64 col_idx = 0; col_idx < header->column_count; col_idx += 1) - { - if(MD_S8Match(header->column_descs[col_idx].name, column_query, 0)) - { - column = &header->column_descs[col_idx]; - column_idx = col_idx; - break; - } - } - MD_u64 row_child_idx = CG_RowChildIndexFromColumnName(header, column_query); - - // rjf: error on invalid column - if(column == 0) - { - MD_PrintMessageFmt(stderr, MD_CodeLocFromNode(column_query_node), MD_MessageKind_Error, "Column query \"%S\" did not map to a valid column for expansion label \"%S\".", column_query, label); - } - - if(column != 0) - { - switch(column->kind) - { - default: - case CG_ColumnKind_Default: - { - MD_Node *cell_node = MD_ChildFromIndex(row, row_child_idx); - cell_string = cell_node->string; - if(MD_S8Match(cell_node->raw_string, MD_S8Lit("/"), 0)) - { - cell_string = MD_S8Lit(""); - } - }break; - - case CG_ColumnKind_CheckForTag: - { - MD_b32 has_tag = MD_NodeHasTag(row, column->tag_string, 0); - cell_string = has_tag ? MD_S8Lit("1") : MD_S8Lit("0"); - }break; - } - } - } - // NOTE(rjf): by-index (grab nth child of row) - else if(column_query_is_by_index) - { - MD_i64 index = MD_CStyleIntFromString(column_query); - cell_string = MD_ChildFromIndex(row, index)->string; - } - } - - MD_S8ListPush(cg_arena, out, cell_string); - } - - }break; - - case CG_TableOp_Bump: - { - MD_u64 dst = MD_CStyleIntFromString(expr->unary_operand->md_node->string); - MD_u64 src = out->total_size; - MD_u64 spaces_to_print = dst - src; - if(dst > src) - { - for(MD_u64 space_idx = 0; space_idx < spaces_to_print; space_idx += 1) - { - MD_S8ListPush(cg_arena, out, MD_S8Lit(" ")); - } - } - }break; - - case CG_TableOp_CheckIfTrue: - { - MD_i64 check_val = CG_TableExprEvaluate_Numeric(info, expr->left); - if(check_val) - { - CG_TableExprEvaluate_String(info, expr->right, out); - } - }break; - - case CG_TableOp_Concat: - { - CG_TableExprEvaluate_String(info, expr->left, out); - CG_TableExprEvaluate_String(info, expr->right, out); - }break; - } -} - -static void -CG_LoopExpansionDimension(CG_ExpandIter *it, CG_ExpandInfo *info, MD_String8List *out) -{ - if(it->next) - { - for(MD_u64 idx = 0; idx < it->count; idx += 1) - { - it->idx = idx; - CG_LoopExpansionDimension(it->next, info, out); - } - } - else - { - for(MD_u64 idx = 0; idx < it->count; idx += 1) - { - it->idx = idx; - MD_String8List expansion_strs = {0}; - MD_u64 start_idx = 0; - for(MD_u64 char_idx = 0; char_idx <= info->strexpr.size; char_idx += 1) - { - MD_b32 is_expr_marker = info->strexpr.str[char_idx] == '$'; - - // rjf: push regular string contents - if(char_idx == info->strexpr.size || is_expr_marker) - { - MD_String8 normal_string_chunk = MD_S8Substring(info->strexpr, start_idx, char_idx); - MD_String8 escaped = CG_EscapedFromString(cg_arena, normal_string_chunk); - MD_S8ListPush(cg_arena, &expansion_strs, escaped); - } - - // rjf: handle expansion - if(is_expr_marker) - { - MD_String8 expr_string = MD_S8Skip(info->strexpr, char_idx+1); - { - MD_i64 paren_nest = 0; - for(MD_u64 expr_str_char_idx = 0; expr_str_char_idx < expr_string.size; expr_str_char_idx += 1) - { - if(expr_string.str[expr_str_char_idx] == '(') - { - paren_nest += 1; - } - else if(expr_string.str[expr_str_char_idx] == ')') - { - paren_nest -= 1; - if(paren_nest == 0) - { - expr_string.size = expr_str_char_idx+1; - break; - } - } - } - } - MD_ParseResult parse = MD_ParseOneNode(cg_arena, expr_string, 0); - MD_Node *node = parse.node; - MD_ExprParseResult expr_parse = MD_ExprParse(cg_arena, &info->expr_op_table, node->first_child, MD_NilNode()); - MD_Expr *expr = expr_parse.expr; - CG_TableExprEvaluate_String(info, expr, &expansion_strs); - MD_String8 parsed_string = MD_S8Substring(info->strexpr, char_idx+1, char_idx+1+parse.string_advance); - parsed_string = MD_S8ChopWhitespace(parsed_string); - start_idx = char_idx+1+parsed_string.size; - } - } - - // rjf: push expansion string to output list - MD_String8 expansion_str = MD_S8ListJoin(cg_arena, expansion_strs, 0); - MD_S8ListPush(cg_arena, out, expansion_str); - } - } -} - -static MD_String8List -CG_GenStringListFromNode(MD_ExprOprTable expr_op_table, MD_Node *gen) -{ - MD_String8List result = {0}; - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - - for(MD_EachNode(strexpr, gen->first_child)) - { - //- rjf: build expansion iterator list - CG_ExpandIter *first_iter = 0; - CG_ExpandIter *last_iter = 0; - { - for(MD_EachNode(tag, strexpr->first_tag)) - { - if(MD_S8Match(tag->string, MD_S8Lit("expand"), 0)) - { - MD_Node *table_name_node = MD_ChildFromIndex(tag, 0); - MD_Node *label_node = MD_ChildFromIndex(tag, 1); - MD_String8 table_name = table_name_node->string; - MD_String8 label = label_node->string; - - // rjf: grab the table associated with table_name - CG_NodeGrid *grid = 0; - { - MD_MapSlot *slot = MD_MapLookup(&cg_tbl_top_level_node_grid_map, MD_MapKeyStr(table_name)); - if(slot != 0) - { - grid = slot->val; - } - } - - // rjf: grab the table header associated with table_name - CG_TableHeader *header = 0; - { - MD_MapSlot *slot = MD_MapLookup(&cg_tbl_top_level_table_header_map, MD_MapKeyStr(table_name)); - if(slot != 0) - { - header = slot->val; - } - } - - // rjf: make iterator node if we got a grid - if(grid != 0) - { - CG_ExpandIter *iter = MD_PushArrayZero(scratch.arena, CG_ExpandIter, 1); - MD_QueuePush(first_iter, last_iter, iter); - iter->grid = grid; - iter->header = header; - iter->label = label; - iter->count = grid->row_parents.count; - } - - // rjf: print out an error if grid is 0 - if(grid == 0) - { - MD_PrintMessageFmt(stderr, MD_CodeLocFromNode(tag), MD_MessageKind_Error, "Table \"%S\" was not found.", table_name); - } - - } - } - } - - //- rjf: generate string list for this strexpr & push to result - if(first_iter != 0) - { - CG_ExpandInfo info = {0}; - { - info.strexpr = strexpr->string; - info.first_expand_iter = first_iter; - info.expr_op_table = expr_op_table; - } - CG_LoopExpansionDimension(first_iter, &info, &result); - } - //- rjf: generate non-expansion strings - else - { - MD_String8 escaped = CG_EscapedFromString(cg_arena, strexpr->string); - MD_S8ListPush(cg_arena, &result, escaped); - } - - } - - MD_ReleaseScratch(scratch); - return result; -} - -static void -CG_TBL_Generate(MD_Node *file_list) -{ - //- rjf: initialize all maps - cg_tbl_top_level_node_grid_map = MD_MapMake(cg_arena); - cg_tbl_top_level_table_header_map = MD_MapMake(cg_arena); - cg_tbl_layer_map_gen = MD_MapMake(cg_arena); - cg_tbl_layer_map_gen_enum = MD_MapMake(cg_arena); - cg_tbl_layer_map_gen_data = MD_MapMake(cg_arena); - - //- rjf: build table expression operator table - MD_ExprOprTable table_expr_op_table = {0}; - { - MD_ExprOprList ops_list = {0}; - MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 10, MD_S8Lit("."), CG_TableOp_Dot, 0); - MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Prefix, 9, MD_S8Lit("=>"), CG_TableOp_Bump, 0); - MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 6, MD_S8Lit("??"), CG_TableOp_CheckIfTrue, 0); - MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 7, MD_S8Lit(".."), CG_TableOp_Concat, 0); - - MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 8, MD_S8Lit("=="), CG_TableOp_Equal, 0); - MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 8, MD_S8Lit("!="), CG_TableOp_IsNotEqual, 0); - MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 5, MD_S8Lit("&&"), CG_TableOp_BooleanAnd, 0); - MD_ExprOprPush(cg_arena, &ops_list, MD_ExprOprKind_Binary, 4, MD_S8Lit("||"), CG_TableOp_BooleanOr, 0); - table_expr_op_table = MD_ExprBakeOprTableFromList(cg_arena, &ops_list); - } - - //- rjf: gather phase - for(MD_EachNode(file_ref, file_list->first_child)) - { - MD_Node *file = MD_ResolveNodeFromReference(file_ref); - MD_String8 layer_name = file->string; - MD_MapKey layer_key = MD_MapKeyStr(layer_name); - for(MD_EachNode(node, file->first_child)) - { - MD_Node *table_tag = MD_TagFromString(node, cg_tbl_tag__table, 0); - if(!MD_NodeIsNil(table_tag)) - { - CG_NodeGrid *grid = MD_PushArrayZero(cg_arena, CG_NodeGrid, 1); - *grid = CG_GridFromNode(node); - MD_MapOverwrite(cg_arena, &cg_tbl_top_level_node_grid_map, MD_MapKeyStr(node->string), grid); - CG_TableHeader *header = MD_PushArrayZero(cg_arena, CG_TableHeader, 1); - *header = CG_TableHeaderFromTag(table_tag); - MD_MapOverwrite(cg_arena, &cg_tbl_top_level_table_header_map, MD_MapKeyStr(node->string), header); - } - if(MD_NodeHasTag(node, cg_tbl_tag__table_gen, 0)) - { - MD_MapInsert(cg_arena, &cg_tbl_layer_map_gen, layer_key, node); - } - if(MD_NodeHasTag(node, cg_tbl_tag__table_gen_enum, 0)) - { - MD_MapInsert(cg_arena, &cg_tbl_layer_map_gen_enum, layer_key, node); - } - if(MD_NodeHasTag(node, cg_tbl_tag__table_gen_data, 0)) - { - MD_MapInsert(cg_arena, &cg_tbl_layer_map_gen_data, layer_key, node); - } - } - } - - //- rjf: generation phase - for(MD_EachNode(file_ref, file_list->first_child)) - { - MD_Node *file = MD_ResolveNodeFromReference(file_ref); - MD_String8 layer_name = file->string; - MD_MapKey layer_key = MD_MapKeyStr(layer_name); - - //- rjf: generate all table enums - for(MD_MapSlot *slot = MD_MapLookup(&cg_tbl_layer_map_gen_enum, layer_key); - slot != 0; - slot = MD_MapScan(slot->next, layer_key)) - { - MD_Node *gen = (MD_Node *)slot->val; - CG_FilePair f = CG_FilePairFromNode(gen); - fprintf(f.h, "typedef enum %.*s\n{\n", MD_S8VArg(gen->string)); - MD_String8List gen_strings = CG_GenStringListFromNode(table_expr_op_table, gen); - MD_StringJoin join = { MD_S8Lit(""), MD_S8Lit("\n"), MD_S8Lit("") }; - MD_String8 gen_string = MD_S8ListJoin(cg_arena, gen_strings, &join); - fprintf(f.h, "%.*s", MD_S8VArg(gen_string)); - fprintf(f.h, "\n}\n%.*s;\n\n", MD_S8VArg(gen->string)); - } - - //- rjf: generate all data tables - for(MD_MapSlot *slot = MD_MapLookup(&cg_tbl_layer_map_gen_data, layer_key); - slot != 0; - slot = MD_MapScan(slot->next, layer_key)) - { - MD_Node *gen = (MD_Node *)slot->val; - MD_Node *tag = MD_TagFromString(gen, cg_tbl_tag__table_gen_data, 0); - MD_Node *data_table_type_node = tag->first_child; - MD_String8 data_table_type = data_table_type_node->string; - MD_String8List gen_strings = CG_GenStringListFromNode(table_expr_op_table, gen); - MD_String8 h_decl_specifier = MD_S8Lit("extern"); - - CG_FilePair f = CG_FilePairFromNode(gen); - fprintf(f.h, "%.*s %.*s %.*s[%" PRIu64 "];\n\n", MD_S8VArg(h_decl_specifier), MD_S8VArg(data_table_type), MD_S8VArg(gen->string), gen_strings.node_count); - fprintf(f.c, "%.*s %.*s[%" PRIu64 "] =\n{\n", MD_S8VArg(data_table_type), MD_S8VArg(gen->string), gen_strings.node_count); - MD_StringJoin join = { MD_S8Lit(""), MD_S8Lit("\n"), MD_S8Lit("") }; - MD_String8 gen_string = MD_S8ListJoin(cg_arena, gen_strings, &join); - fprintf(f.c, "%.*s", MD_S8VArg(gen_string)); - fprintf(f.c, "\n};\n"); - fprintf(f.c, "\n"); - } - - //- rjf: generate all general generations - for(MD_MapSlot *slot = MD_MapLookup(&cg_tbl_layer_map_gen, layer_key); - slot != 0; - slot = MD_MapScan(slot->next, layer_key)) - { - MD_Node *gen = (MD_Node *)slot->val; - CG_FilePair f = CG_FilePairFromNode(gen); - FILE *file = MD_NodeHasTag(gen, MD_S8Lit("c"), 0) ? f.c : f.h; - MD_String8List gen_strings = CG_GenStringListFromNode(table_expr_op_table, gen); - MD_StringJoin join = { MD_S8Lit(""), MD_S8Lit("\n"), MD_S8Lit("") }; - MD_String8 gen_string = MD_S8ListJoin(cg_arena, gen_strings, &join); - fprintf(file, "%.*s", MD_S8VArg(gen_string)); - fprintf(file, "\n\n"); - } - - } - -} diff --git a/src/metagen/codegen_table.h b/src/metagen/codegen_table.h deleted file mode 100644 index 08ceb7d..0000000 --- a/src/metagen/codegen_table.h +++ /dev/null @@ -1,92 +0,0 @@ -#ifndef METAPROGRAM_TABLE_H -#define METAPROGRAM_TABLE_H - -typedef enum CG_TableOp -{ - CG_TableOp_Null, - - CG_TableOp_BeginStringOps, - CG_TableOp_Dot, - CG_TableOp_Bump, - CG_TableOp_CheckIfTrue, - CG_TableOp_Concat, - CG_TableOp_EndStringOps, - - CG_TableOp_BeginNumericOps, - CG_TableOp_Equal, - CG_TableOp_IsNotEqual, - CG_TableOp_BooleanAnd, - CG_TableOp_BooleanOr, - CG_TableOp_EndNumericOps, - - CG_TableOp_COUNT -} -CG_TableOp; - -typedef struct CG_NodeArray CG_NodeArray; -struct CG_NodeArray -{ - MD_u64 count; - MD_Node **v; -}; - -typedef struct CG_NodeGrid CG_NodeGrid; -struct CG_NodeGrid -{ - CG_NodeArray cells; - CG_NodeArray row_parents; -}; - -typedef enum CG_ColumnKind -{ - CG_ColumnKind_Default, - CG_ColumnKind_CheckForTag, - CG_ColumnKind_COUNT -} -CG_ColumnKind; - -typedef struct CG_ColumnDesc CG_ColumnDesc; -struct CG_ColumnDesc -{ - CG_ColumnKind kind; - MD_String8 name; - MD_String8 tag_string; -}; - -typedef struct CG_TableHeader CG_TableHeader; -struct CG_TableHeader -{ - MD_u64 column_count; - CG_ColumnDesc *column_descs; -}; - -typedef struct CG_ExpandIter CG_ExpandIter; -struct CG_ExpandIter -{ - CG_ExpandIter *next; - CG_NodeGrid *grid; - CG_TableHeader *header; - MD_String8 label; - MD_u64 idx; - MD_u64 count; -}; - -typedef struct CG_ExpandInfo CG_ExpandInfo; -struct CG_ExpandInfo -{ - MD_String8 strexpr; - CG_ExpandIter *first_expand_iter; - MD_ExprOprTable expr_op_table; -}; - -static CG_NodeArray CG_NodeArrayMake(MD_u64 count); -static CG_NodeGrid CG_GridFromNode(MD_Node *node); -static CG_TableHeader CG_TableHeaderFromTag(MD_Node *tag); -static MD_u64 CG_RowChildIndexFromColumnName(CG_TableHeader *header, MD_String8 column_name); -static MD_i64 CG_TableExprEvaluate_Numeric(CG_ExpandInfo *info, MD_Expr *expr); -static void CG_TableExprEvaluate_String(CG_ExpandInfo *info, MD_Expr *expr, MD_String8List *out); -static void CG_LoopExpansionDimension(CG_ExpandIter *it, CG_ExpandInfo *info, MD_String8List *out); -static MD_String8List CG_GenStringListFromNode(MD_ExprOprTable expr_op_table, MD_Node *gen); -static void CG_TBL_Generate(MD_Node *file_list); - -#endif // METAPROGRAM_TABLE_H diff --git a/src/metagen/metadesk_base/md.c b/src/metagen/metadesk_base/md.c deleted file mode 100644 index 0654411..0000000 --- a/src/metagen/metadesk_base/md.c +++ /dev/null @@ -1,4410 +0,0 @@ -// LICENSE AT END OF FILE (MIT). - -/* -** Overrides & Options Macros -** -** Overridable -** "basic types" ** REQUIRED -** #define/typedef MD_i8, MD_i16, MD_i32, MD_i64 -** #define/typedef MD_u8, MD_u16, MD_u32, MD_u64 -** #define/typedef MD_f32, MD_f64 -** -** "memset" ** REQUIRED -** #define MD_IMPL_Memset (void*, int, uint64) -> void* -** #define MD_IMPL_Memmove (void*, void*, uint64) -> void* -** -** "file iteration" ** OPTIONAL (required for the metadesk FileIter helpers to work) -** #define MD_IMPL_FileIterBegin (MD_FileIter*, MD_String8) -> Boolean -** #define MD_IMPL_FileIterNext (MD_Arena*, MD_FileIter*) -> MD_FileInfo -** #define MD_IMPL_FileIterEnd (MD_FileIter*) -> void -** -** "file load" ** OPTIONAL (required for MD_ParseWholeFile to work) -** #define MD_IMPL_LoadEntireFile (MD_Arena*, MD_String8 filename) -> MD_String8 -** -** "low level memory" ** OPTIONAL (required when relying on the default arenas) -** #define MD_IMPL_Reserve (uint64) -> void* -** #define MD_IMPL_Commit (void*, uint64) -> MD_b32 -** #define MD_IMPL_Decommit (void*, uint64) -> void -** #define MD_IMPL_Release (void*, uint64) -> void -** -** -** "arena" ** REQUIRED -** #define MD_IMPL_Arena (must set before including md.h) -** #define MD_IMPL_ArenaMinPos uint64 -** #define MD_IMPL_ArenaAlloc () -> MD_IMPL_Arena* -** #define MD_IMPL_ArenaRelease (MD_IMPL_Arena*) -> void -** #define MD_IMPL_ArenaGetPos (MD_IMPL_Arena*) -> uint64 -** #define MD_IMPL_ArenaPush (MD_IMPL_Arena*, uint64) -> void* -** #define MD_IMPL_ArenaPopTo (MD_IMPL_Arena*, uint64) -> void -** #define MD_IMPL_ArenaSetAutoAlign (MD_IMPL_Arena*, uint64) -> void -** -** "scratch" ** REQUIRED -** #define MD_IMPL_GetScratch (MD_IMPL_Arena**, uint64) -> MD_IMPL_Arena* -** "scratch constants" ** OPTIONAL (required for default scratch) -** #define MD_IMPL_ScratchCount uint64 [default 2] -** -** "sprintf" ** REQUIRED -** #define MD_IMPL_Vsnprintf (char*, uint64, char const*, va_list) -> uint64 -** -** Static Parameters to the Default Arena Implementation -** #define MD_DEFAULT_ARENA_RES_SIZE uint64 [default 64 megabytes] -** #define MD_DEFAULT_ARENA_CMT_SIZE uint64 [default 64 kilabytes] -** -** Default Implementation Controls -** These controls default to '1' i.e. 'enabled' -** #define MD_DEFAULT_BASIC_TYPES -> construct "basic types" from stdint.h header -** #define MD_DEFAULT_MEMSET -> construct "memset" from CRT -** #define MD_DEFAULT_FILE_ITER -> construct "file iteration" from OS headers -** #define MD_DEFAULT_MEMORY -> construct "low level memory" from OS headers -** #define MD_DEFAULT_ARENA -> construct "arena" from "low level memory" -** #define MD_DEFAULT_SCRATCH -> construct "scratch" from "arena" -** #define MD_DEFAULT_SPRINTF -> construct "vsnprintf" from internal implementaion -** -*/ - -#if !defined(MD_C) -#define MD_C - -//~///////////////////////////////////////////////////////////////////////////// -/////////////////////////// CRT Implementation ///////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if MD_DEFAULT_MEMSET - -#include -#include - -#if !defined(MD_IMPL_Memset) -# define MD_IMPL_Memset MD_CRT_Memset -#endif -#if !defined(MD_IMPL_Memmove) -# define MD_IMPL_Memmove MD_CRT_Memmove -#endif - -#define MD_CRT_Memset memset -#define MD_CRT_Memmove memmove - -#endif - -#if MD_DEFAULT_FILE_LOAD - -#include - -#if !defined(MD_IMPL_LoadEntireFile) -# define MD_IMPL_LoadEntireFile MD_CRT_LoadEntireFile -#endif - -MD_FUNCTION MD_String8 -MD_CRT_LoadEntireFile(MD_Arena *arena, MD_String8 filename) -{ - MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); - MD_String8 file_contents = MD_ZERO_STRUCT; - MD_String8 filename_copy = MD_S8Copy(scratch.arena, filename); - FILE *file = fopen((char*)filename_copy.str, "rb"); - if(file != 0) - { - fseek(file, 0, SEEK_END); - MD_u64 file_size = ftell(file); - fseek(file, 0, SEEK_SET); - file_contents.str = MD_PushArray(arena, MD_u8, file_size+1); - if(file_contents.str) - { - file_contents.size = file_size; - fread(file_contents.str, 1, file_size, file); - file_contents.str[file_contents.size] = 0; - } - fclose(file); - } - MD_ReleaseScratch(scratch); - return file_contents; -} - -#endif - - -//~///////////////////////////////////////////////////////////////////////////// -/////////////////////////// Win32 Implementation /////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//- win32 header -#if (MD_DEFAULT_FILE_ITER || MD_2DEFAULT_MEMORY) && MD_OS_WINDOWS -# include -# pragma comment(lib, "User32.lib") -#endif - -//- win32 "file iteration" -#if MD_DEFAULT_FILE_ITER && MD_OS_WINDOWS - -#if !defined(MD_IMPL_FileIterBegin) -# define MD_IMPL_FileIterBegin MD_WIN32_FileIterBegin -#endif -#if !defined(MD_IMPL_FileIterNext) -# define MD_IMPL_FileIterNext MD_WIN32_FileIterNext -#endif -#if !defined(MD_IMPL_FileIterEnd) -# define MD_IMPL_FileIterEnd MD_WIN32_FileIterEnd -#endif - -typedef struct MD_WIN32_FileIter{ - HANDLE state; - MD_u64 first; - WIN32_FIND_DATAW find_data; -} MD_WIN32_FileIter; - -MD_StaticAssert(sizeof(MD_FileIter) >= sizeof(MD_WIN32_FileIter), file_iter_size_check); - -static MD_b32 -MD_WIN32_FileIterBegin(MD_FileIter *it, MD_String8 path) -{ - //- init search - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - - MD_u8 c = path.str[path.size - 1]; - MD_b32 need_star = (c == '/' || c == '\\'); - MD_String8 cpath = need_star ? MD_S8Fmt(scratch.arena, "%.*s*", MD_S8VArg(path)) : path; - MD_String16 cpath16 = MD_S16FromS8(scratch.arena, cpath); - - WIN32_FIND_DATAW find_data = MD_ZERO_STRUCT; - HANDLE state = FindFirstFileW((WCHAR*)cpath16.str, &find_data); - - MD_ReleaseScratch(scratch); - - //- fill results - MD_b32 result = !!state; - if (result) - { - MD_WIN32_FileIter *win32_it = (MD_WIN32_FileIter*)it; - win32_it->state = state; - win32_it->first = 1; - MD_MemoryCopy(&win32_it->find_data, &find_data, sizeof(find_data)); - } - return(result); -} - -static MD_FileInfo -MD_WIN32_FileIterNext(MD_Arena *arena, MD_FileIter *it) -{ - //- get low-level file info for this step - MD_b32 good = 0; - - MD_WIN32_FileIter *win32_it = (MD_WIN32_FileIter*)it; - WIN32_FIND_DATAW *find_data = &win32_it->find_data; - if (win32_it->first) - { - win32_it->first = 0; - good = 1; - } - else - { - good = FindNextFileW(win32_it->state, find_data); - } - - //- convert to MD_FileInfo - MD_FileInfo result = {0}; - if (good) - { - if (find_data->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - { - result.flags |= MD_FileFlag_Directory; - } - MD_u16 *filename_base = (MD_u16*)find_data->cFileName; - MD_u16 *ptr = filename_base; - for (;*ptr != 0; ptr += 1); - MD_String16 filename16 = {filename_base, (MD_u64)(ptr - filename_base)}; - result.filename = MD_S8FromS16(arena, filename16); - result.file_size = ((((MD_u64)find_data->nFileSizeHigh) << 32) | - ((MD_u64)find_data->nFileSizeLow)); - } - return(result); -} - -static void -MD_WIN32_FileIterEnd(MD_FileIter *it) -{ - MD_WIN32_FileIter *win32_it = (MD_WIN32_FileIter*)it; - CloseHandle(win32_it->state); -} - -#endif - -//- win32 "low level memory" -#if MD_DEFAULT_MEMORY && MD_OS_WINDOWS - -#if !defined(MD_IMPL_Reserve) -# define MD_IMPL_Reserve MD_WIN32_Reserve -#endif -#if !defined(MD_IMPL_Commit) -# define MD_IMPL_Commit MD_WIN32_Commit -#endif -#if !defined(MD_IMPL_Decommit) -# define MD_IMPL_Decommit MD_WIN32_Decommit -#endif -#if !defined(MD_IMPL_Release) -# define MD_IMPL_Release MD_WIN32_Release -#endif - -static void* -MD_WIN32_Reserve(MD_u64 size) -{ - void *result = VirtualAlloc(0, size, MEM_RESERVE, PAGE_READWRITE); - return(result); -} - -static MD_b32 -MD_WIN32_Commit(void *ptr, MD_u64 size) -{ - MD_b32 result = (VirtualAlloc(ptr, size, MEM_COMMIT, PAGE_READWRITE) != 0); - return(result); -} - -static void -MD_WIN32_Decommit(void *ptr, MD_u64 size) -{ - VirtualFree(ptr, size, MEM_DECOMMIT); -} - -static void -MD_WIN32_Release(void *ptr, MD_u64 size) -{ - VirtualFree(ptr, 0, MEM_RELEASE); -} - -#endif - -//~///////////////////////////////////////////////////////////////////////////// -////////////////////////// Linux Implementation //////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//- linux headers -#if (MD_DEFAULT_FILE_ITER || MD_DEFAULT_MEMORY) && MD_OS_LINUX -# include -# include -# include -# include -# include -# include -// NOTE(mal): To get these constants I need to #define _GNU_SOURCE, -// which invites non-POSIX behavior I'd rather avoid -# ifndef O_PATH -# define O_PATH 010000000 -# endif -# define AT_NO_AUTOMOUNT 0x800 -# define AT_SYMLINK_NOFOLLOW 0x100 -#endif - -//- linux "file iteration" -#if MD_DEFAULT_FILE_ITER && MD_OS_LINUX - -#if !defined(MD_IMPL_FileIterIncrement) -# define MD_IMPL_FileIterIncrement MD_LINUX_FileIterIncrement -#endif - -typedef struct MD_LINUX_FileIter MD_LINUX_FileIter; -struct MD_LINUX_FileIter -{ - int dir_fd; - DIR *dir; -}; -MD_StaticAssert(sizeof(MD_LINUX_FileIter) <= sizeof(MD_FileIter), file_iter_size_check); - -static MD_b32 -MD_LINUX_FileIterIncrement(MD_Arena *arena, MD_FileIter *opaque_it, MD_String8 path, - MD_FileInfo *out_info) -{ - MD_b32 result = 0; - - MD_LINUX_FileIter *it = (MD_LINUX_FileIter *)opaque_it; - if(it->dir == 0) - { - it->dir = opendir((char*)path.str); - it->dir_fd = open((char *)path.str, O_PATH|O_CLOEXEC); - } - - if(it->dir != 0 && it->dir_fd != -1) - { - struct dirent *dir_entry = readdir(it->dir); - if(dir_entry) - { - out_info->filename = MD_S8Fmt(arena, "%s", dir_entry->d_name); - out_info->flags = 0; - - struct stat st; - if(fstatat(it->dir_fd, dir_entry->d_name, &st, AT_NO_AUTOMOUNT|AT_SYMLINK_NOFOLLOW) == 0) - { - if((st.st_mode & S_IFMT) == S_IFDIR) - { - out_info->flags |= MD_FileFlag_Directory; - } - out_info->file_size = st.st_size; - } - result = 1; - } - } - - if(result == 0) - { - if(it->dir != 0) - { - closedir(it->dir); - it->dir = 0; - } - if(it->dir_fd != -1) - { - close(it->dir_fd); - it->dir_fd = -1; - } - } - - return result; -} - -#endif - -//- linux "low level memory" -#if MD_DEFAULT_MEMORY && MD_OS_LINUX - -#if !defined(MD_IMPL_Reserve) -# define MD_IMPL_Reserve MD_LINUX_Reserve -#endif -#if !defined(MD_IMPL_Commit) -# define MD_IMPL_Commit MD_LINUX_Commit -#endif -#if !defined(MD_IMPL_Decommit) -# define MD_IMPL_Decommit MD_LINUX_Decommit -#endif -#if !defined(MD_IMPL_Release) -# define MD_IMPL_Release MD_LINUX_Release -#endif - -static void* -MD_LINUX_Reserve(MD_u64 size) -{ - void *result = mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANONYMOUS, -1, (off_t)0); - return(result); -} - -static MD_b32 -MD_LINUX_Commit(void *ptr, MD_u64 size) -{ - MD_b32 result = (mprotect(ptr, size, PROT_READ|PROT_WRITE) == 0); - return(result); -} - -static void -MD_LINUX_Decommit(void *ptr, MD_u64 size) -{ - mprotect(ptr, size, PROT_NONE); - madvise(ptr, size, MADV_DONTNEED); -} - -static void -MD_LINUX_Release(void *ptr, MD_u64 size) -{ - munmap(ptr, size); -} - -#endif - -//~///////////////////////////////////////////////////////////////////////////// -///////////// MD Arena From Reserve/Commit/Decommit/Release //////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if MD_DEFAULT_ARENA - -#if !defined(MD_DEFAULT_ARENA_RES_SIZE) -# define MD_DEFAULT_ARENA_RES_SIZE (64 << 20) -#endif -#if !defined(MD_DEFAULT_ARENA_CMT_SIZE) -# define MD_DEFAULT_ARENA_CMT_SIZE (64 << 10) -#endif - -#define MD_DEFAULT_ARENA_VERY_BIG (MD_DEFAULT_ARENA_RES_SIZE - MD_IMPL_ArenaMinPos)/2 - -//- "low level memory" implementation check -#if !defined(MD_IMPL_Reserve) -# error Missing implementation for MD_IMPL_Reserve -#endif -#if !defined(MD_IMPL_Commit) -# error Missing implementation for MD_IMPL_Commit -#endif -#if !defined(MD_IMPL_Decommit) -# error Missing implementation for MD_IMPL_Decommit -#endif -#if !defined(MD_IMPL_Release) -# error Missing implementation for MD_IMPL_Release -#endif - -#define MD_IMPL_ArenaMinPos 64 -MD_StaticAssert(sizeof(MD_ArenaDefault) <= MD_IMPL_ArenaMinPos, arena_def_size_check); - -#define MD_IMPL_ArenaAlloc MD_ArenaDefaultAlloc -#define MD_IMPL_ArenaRelease MD_ArenaDefaultRelease -#define MD_IMPL_ArenaGetPos MD_ArenaDefaultGetPos -#define MD_IMPL_ArenaPush MD_ArenaDefaultPush -#define MD_IMPL_ArenaPopTo MD_ArenaDefaultPopTo -#define MD_IMPL_ArenaSetAutoAlign MD_ArenaDefaultSetAutoAlign - -static MD_ArenaDefault* -MD_ArenaDefaultAlloc__Size(MD_u64 cmt, MD_u64 res) -{ - MD_Assert(MD_IMPL_ArenaMinPos < cmt && cmt <= res); - MD_u64 cmt_clamped = MD_ClampTop(cmt, res); - MD_ArenaDefault *result = 0; - void *mem = MD_IMPL_Reserve(res); - if (MD_IMPL_Commit(mem, cmt_clamped)) - { - result = (MD_ArenaDefault*)mem; - result->prev = 0; - result->current = result; - result->base_pos = 0; - result->pos = MD_IMPL_ArenaMinPos; - result->cmt = cmt_clamped; - result->cap = res; - result->align = 8; - } - return(result); -} - -static MD_ArenaDefault* -MD_ArenaDefaultAlloc(void) -{ - MD_ArenaDefault *result = MD_ArenaDefaultAlloc__Size(MD_DEFAULT_ARENA_CMT_SIZE, - MD_DEFAULT_ARENA_RES_SIZE); - return(result); -} - -static void -MD_ArenaDefaultRelease(MD_ArenaDefault *arena) -{ - for (MD_ArenaDefault *node = arena->current, *prev = 0; - node != 0; - node = prev) - { - prev = node->prev; - MD_IMPL_Release(node, node->cap); - } -} - -static MD_u64 -MD_ArenaDefaultGetPos(MD_ArenaDefault *arena) -{ - MD_ArenaDefault *current = arena->current; - MD_u64 result = current->base_pos + current->pos; - return(result); -} - -static void* -MD_ArenaDefaultPush(MD_ArenaDefault *arena, MD_u64 size) -{ - // try to be fast! - MD_ArenaDefault *current = arena->current; - MD_u64 align = arena->align; - MD_u64 pos = current->pos; - MD_u64 pos_aligned = MD_AlignPow2(pos, align); - MD_u64 new_pos = pos_aligned + size; - void *result = (MD_u8*)current + pos_aligned; - current->pos = new_pos; - - // if it's not going to work do the slow path - if (new_pos > current->cmt) - { - result = 0; - current->pos = pos; - - // new chunk if necessary - if (new_pos > current->cap) - { - MD_ArenaDefault *new_arena = 0; - if (size > MD_DEFAULT_ARENA_VERY_BIG) - { - MD_u64 big_size_unrounded = size + MD_IMPL_ArenaMinPos; - MD_u64 big_size = MD_AlignPow2(big_size_unrounded, (4 << 10)); - new_arena = MD_ArenaDefaultAlloc__Size(big_size, big_size); - } - else - { - new_arena = MD_ArenaDefaultAlloc(); - } - - // link in new chunk & recompute new_pos - if (new_arena != 0) - { - new_arena->base_pos = current->base_pos + current->cap; - new_arena->prev = current; - current = new_arena; - pos_aligned = current->pos; - new_pos = pos_aligned + size; - } - } - - // move ahead if the current chunk has enough reserve - if (new_pos <= current->cap) - { - - // extend commit if necessary - if (new_pos > current->cmt) - { - MD_u64 new_cmt_unclamped = MD_AlignPow2(new_pos, MD_DEFAULT_ARENA_CMT_SIZE); - MD_u64 new_cmt = MD_ClampTop(new_cmt_unclamped, current->cap); - MD_u64 cmt_size = new_cmt - current->cmt; - if (MD_IMPL_Commit((MD_u8*)current + current->cmt, cmt_size)) - { - current->cmt = new_cmt; - } - } - - // move ahead if the current chunk has enough commit - if (new_pos <= current->cmt) - { - result = (MD_u8*)current + current->pos; - current->pos = new_pos; - } - } - } - - return(result); -} - -static void -MD_ArenaDefaultPopTo(MD_ArenaDefault *arena, MD_u64 pos) -{ - // pop chunks in the chain - MD_u64 pos_clamped = MD_ClampBot(MD_IMPL_ArenaMinPos, pos); - { - MD_ArenaDefault *node = arena->current; - for (MD_ArenaDefault *prev = 0; - node != 0 && node->base_pos >= pos; - node = prev) - { - prev = node->prev; - MD_IMPL_Release(node, node->cap); - } - arena->current = node; - } - - // reset the pos of the current - { - MD_ArenaDefault *current = arena->current; - MD_u64 local_pos_unclamped = pos - current->base_pos; - MD_u64 local_pos = MD_ClampBot(local_pos_unclamped, MD_IMPL_ArenaMinPos); - current->pos = local_pos; - } -} - -static void -MD_ArenaDefaultSetAutoAlign(MD_ArenaDefault *arena, MD_u64 align) -{ - arena->align = align; -} - -static void -MD_ArenaDefaultAbsorb(MD_ArenaDefault *arena, MD_ArenaDefault *sub_arena) -{ - MD_ArenaDefault *current = arena->current; - MD_u64 base_pos_shift = current->base_pos + current->cap; - for (MD_ArenaDefault *node = sub_arena->current; - node != 0; - node = node->prev) - { - node->base_pos += base_pos_shift; - } - sub_arena->prev = arena->current; - arena->current = sub_arena->current; -} - -#endif - -//- "arena" implementation checks -#if !defined(MD_IMPL_ArenaAlloc) -# error Missing implementation for MD_IMPL_ArenaAlloc -#endif -#if !defined(MD_IMPL_ArenaRelease) -# error Missing implementation for MD_IMPL_ArenaRelease -#endif -#if !defined(MD_IMPL_ArenaGetPos) -# error Missing implementation for MD_IMPL_ArenaGetPos -#endif -#if !defined(MD_IMPL_ArenaPush) -# error Missing implementation for MD_IMPL_ArenaPush -#endif -#if !defined(MD_IMPL_ArenaPopTo) -# error Missing implementation for MD_IMPL_ArenaPopTo -#endif -#if !defined(MD_IMPL_ArenaSetAutoAlign) -# error Missing implementation for MD_IMPL_ArenaSetAutoAlign -#endif -#if !defined(MD_IMPL_ArenaMinPos) -# error Missing implementation for MD_IMPL_ArenaMinPos -#endif - -//~///////////////////////////////////////////////////////////////////////////// -///////////////////////////// MD Scratch Pool ////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if MD_DEFAULT_SCRATCH - -#if !defined(MD_IMPL_ScratchCount) -# define MD_IMPL_ScratchCount 2llu -#endif - -#if !defined(MD_IMPL_GetScratch) -# define MD_IMPL_GetScratch MD_GetScratchDefault -#endif - -MD_THREAD_LOCAL MD_Arena *md_thread_scratch_pool[MD_IMPL_ScratchCount] = {0, 0}; - -static MD_Arena* -MD_GetScratchDefault(MD_Arena **conflicts, MD_u64 count) -{ - MD_Arena **scratch_pool = md_thread_scratch_pool; - if (scratch_pool[0] == 0) - { - MD_Arena **arena_ptr = scratch_pool; - for (MD_u64 i = 0; i < MD_IMPL_ScratchCount; i += 1, arena_ptr += 1) - { - *arena_ptr = MD_ArenaAlloc(); - } - } - MD_Arena *result = 0; - MD_Arena **arena_ptr = scratch_pool; - for (MD_u64 i = 0; i < MD_IMPL_ScratchCount; i += 1, arena_ptr += 1) - { - MD_Arena *arena = *arena_ptr; - MD_Arena **conflict_ptr = conflicts; - for (MD_u32 j = 0; j < count; j += 1, conflict_ptr += 1) - { - if (arena == *conflict_ptr) - { - arena = 0; - break; - } - } - if (arena != 0) - { - result = arena; - break; - } - } - return(result); -} - -#endif - -//~///////////////////////////////////////////////////////////////////////////// -//////////////////////// MD Library Implementation ///////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if MD_DEFAULT_SPRINTF -#define STB_SPRINTF_IMPLEMENTATION -#define STB_SPRINTF_DECORATE(name) md_stbsp_##name -#include "md_stb_sprintf.h" -#endif - - -//~ Nil Node Definition - -static MD_Node _md_nil_node = -{ - &_md_nil_node, // next - &_md_nil_node, // prev - &_md_nil_node, // parent - &_md_nil_node, // first_child - &_md_nil_node, // last_child - &_md_nil_node, // first_tag - &_md_nil_node, // last_tag - MD_NodeKind_Nil, // kind - 0, // flags - MD_ZERO_STRUCT, // string - MD_ZERO_STRUCT, // raw_string - 0, // at - &_md_nil_node, // ref_target - MD_ZERO_STRUCT, // prev_comment - MD_ZERO_STRUCT, // next_comment -}; - -//~ Arena Functions - -MD_FUNCTION MD_Arena* -MD_ArenaAlloc(void) -{ - return(MD_IMPL_ArenaAlloc()); -} - -MD_FUNCTION void -MD_ArenaRelease(MD_Arena *arena) -{ - MD_IMPL_ArenaRelease(arena); -} - -MD_FUNCTION void* -MD_ArenaPush(MD_Arena *arena, MD_u64 size) -{ - void *result = MD_IMPL_ArenaPush(arena, size); - return(result); -} - -MD_FUNCTION void -MD_ArenaPutBack(MD_Arena *arena, MD_u64 size) -{ - MD_u64 pos = MD_IMPL_ArenaGetPos(arena); - MD_u64 new_pos = pos - size; - MD_u64 new_pos_clamped = MD_ClampBot(MD_IMPL_ArenaMinPos, new_pos); - MD_IMPL_ArenaPopTo(arena, new_pos_clamped); -} - -MD_FUNCTION void -MD_ArenaSetAlign(MD_Arena *arena, MD_u64 boundary) -{ - MD_IMPL_ArenaSetAutoAlign(arena, boundary); -} - -MD_FUNCTION void -MD_ArenaPushAlign(MD_Arena *arena, MD_u64 boundary) -{ - MD_u64 pos = MD_IMPL_ArenaGetPos(arena); - MD_u64 align_m1 = boundary - 1; - MD_u64 new_pos_aligned = (pos + align_m1)&(~align_m1); - if (new_pos_aligned > pos) - { - MD_u64 amt = new_pos_aligned - pos; - MD_MemoryZero(MD_IMPL_ArenaPush(arena, amt), amt); - } -} - -MD_FUNCTION void -MD_ArenaClear(MD_Arena *arena) -{ - MD_IMPL_ArenaPopTo(arena, MD_IMPL_ArenaMinPos); -} - -MD_FUNCTION MD_ArenaTemp -MD_ArenaBeginTemp(MD_Arena *arena) -{ - MD_ArenaTemp result; - result.arena = arena; - result.pos = MD_IMPL_ArenaGetPos(arena); - return(result); -} - -MD_FUNCTION void -MD_ArenaEndTemp(MD_ArenaTemp temp) -{ - MD_IMPL_ArenaPopTo(temp.arena, temp.pos); -} - -//~ Arena Scratch Pool - -MD_FUNCTION MD_ArenaTemp -MD_GetScratch(MD_Arena **conflicts, MD_u64 count) -{ - MD_Arena *arena = MD_IMPL_GetScratch(conflicts, count); - MD_ArenaTemp result = MD_ZERO_STRUCT; - if (arena != 0) - { - result = MD_ArenaBeginTemp(arena); - } - return(result); -} - -//~ Characters - -MD_FUNCTION MD_b32 -MD_CharIsAlpha(MD_u8 c) -{ - return MD_CharIsAlphaUpper(c) || MD_CharIsAlphaLower(c); -} - -MD_FUNCTION MD_b32 -MD_CharIsAlphaUpper(MD_u8 c) -{ - return c >= 'A' && c <= 'Z'; -} - -MD_FUNCTION MD_b32 -MD_CharIsAlphaLower(MD_u8 c) -{ - return c >= 'a' && c <= 'z'; -} - -MD_FUNCTION MD_b32 -MD_CharIsDigit(MD_u8 c) -{ - return (c >= '0' && c <= '9'); -} - -MD_FUNCTION MD_b32 -MD_CharIsUnreservedSymbol(MD_u8 c) -{ - return (c == '~' || c == '!' || c == '$' || c == '%' || c == '^' || - c == '&' || c == '*' || c == '-' || c == '=' || c == '+' || - c == '<' || c == '.' || c == '>' || c == '/' || c == '?' || - c == '|'); -} - -MD_FUNCTION MD_b32 -MD_CharIsReservedSymbol(MD_u8 c) -{ - return (c == '{' || c == '}' || c == '(' || c == ')' || c == '\\' || - c == '[' || c == ']' || c == '#' || c == ',' || c == ';' || - c == ':' || c == '@'); -} - -MD_FUNCTION MD_b32 -MD_CharIsSpace(MD_u8 c) -{ - return c == ' ' || c == '\r' || c == '\t' || c == '\f' || c == '\v'; -} - -MD_FUNCTION MD_u8 -MD_CharToUpper(MD_u8 c) -{ - return (c >= 'a' && c <= 'z') ? ('A' + (c - 'a')) : c; -} - -MD_FUNCTION MD_u8 -MD_CharToLower(MD_u8 c) -{ - return (c >= 'A' && c <= 'Z') ? ('a' + (c - 'A')) : c; -} - -MD_FUNCTION MD_u8 -MD_CharToForwardSlash(MD_u8 c) -{ - return (c == '\\' ? '/' : c); -} - -//~ Strings - -MD_FUNCTION MD_u64 -MD_CalculateCStringLength(char *cstr) -{ - MD_u64 i = 0; - for(; cstr[i]; i += 1); - return i; -} - -MD_FUNCTION MD_String8 -MD_S8(MD_u8 *str, MD_u64 size) -{ - MD_String8 string; - string.str = str; - string.size = size; - return string; -} - -MD_FUNCTION MD_String8 -MD_S8Range(MD_u8 *first, MD_u8 *opl) -{ - MD_String8 string; - string.str = first; - string.size = (MD_u64)(opl - first); - return string; -} - -MD_FUNCTION MD_String8 -MD_S8Substring(MD_String8 str, MD_u64 min, MD_u64 max) -{ - if(max > str.size) - { - max = str.size; - } - if(min > str.size) - { - min = str.size; - } - if(min > max) - { - MD_u64 swap = min; - min = max; - max = swap; - } - str.size = max - min; - str.str += min; - return str; -} - -MD_FUNCTION MD_String8 -MD_S8Skip(MD_String8 str, MD_u64 min) -{ - return MD_S8Substring(str, min, str.size); -} - -MD_FUNCTION MD_String8 -MD_S8Chop(MD_String8 str, MD_u64 nmax) -{ - return MD_S8Substring(str, 0, str.size - nmax); -} - -MD_FUNCTION MD_String8 -MD_S8Prefix(MD_String8 str, MD_u64 size) -{ - return MD_S8Substring(str, 0, size); -} - -MD_FUNCTION MD_String8 -MD_S8Suffix(MD_String8 str, MD_u64 size) -{ - return MD_S8Substring(str, str.size - size, str.size); -} - -MD_FUNCTION MD_b32 -MD_S8Match(MD_String8 a, MD_String8 b, MD_MatchFlags flags) -{ - int result = 0; - if(a.size == b.size || flags & MD_StringMatchFlag_RightSideSloppy) - { - result = 1; - for(MD_u64 i = 0; i < a.size; i += 1) - { - MD_b32 match = (a.str[i] == b.str[i]); - if(flags & MD_StringMatchFlag_CaseInsensitive) - { - match |= (MD_CharToLower(a.str[i]) == MD_CharToLower(b.str[i])); - } - if(flags & MD_StringMatchFlag_SlashInsensitive) - { - match |= (MD_CharToForwardSlash(a.str[i]) == MD_CharToForwardSlash(b.str[i])); - } - if(match == 0) - { - result = 0; - break; - } - } - } - return result; -} - -MD_FUNCTION MD_u64 -MD_S8FindSubstring(MD_String8 str, MD_String8 substring, MD_u64 start_pos, MD_MatchFlags flags) -{ - MD_b32 found = 0; - MD_u64 found_idx = str.size; - for(MD_u64 i = start_pos; i < str.size; i += 1) - { - if(i + substring.size <= str.size) - { - MD_String8 substr_from_str = MD_S8Substring(str, i, i+substring.size); - if(MD_S8Match(substr_from_str, substring, flags)) - { - found_idx = i; - found = 1; - if(!(flags & MD_MatchFlag_FindLast)) - { - break; - } - } - } - } - return found_idx; -} - -MD_FUNCTION MD_String8 -MD_S8Copy(MD_Arena *arena, MD_String8 string) -{ - MD_String8 res; - res.size = string.size; - res.str = MD_PushArray(arena, MD_u8, string.size + 1); - MD_MemoryCopy(res.str, string.str, string.size); - res.str[string.size] = 0; - return(res); -} - -MD_FUNCTION MD_String8 -MD_S8FmtV(MD_Arena *arena, char *fmt, va_list args) -{ - MD_String8 result = MD_ZERO_STRUCT; - va_list args2; - va_copy(args2, args); - MD_u64 needed_bytes = MD_IMPL_Vsnprintf(0, 0, fmt, args)+1; - result.str = MD_PushArray(arena, MD_u8, needed_bytes); - result.size = needed_bytes - 1; - result.str[needed_bytes-1] = 0; - MD_IMPL_Vsnprintf((char*)result.str, needed_bytes, fmt, args2); - return result; -} - -MD_FUNCTION MD_String8 -MD_S8Fmt(MD_Arena *arena, char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - MD_String8 result = MD_S8FmtV(arena, fmt, args); - va_end(args); - return result; -} - -MD_FUNCTION void -MD_S8ListPush(MD_Arena *arena, MD_String8List *list, MD_String8 string) -{ - MD_String8Node *node = MD_PushArrayZero(arena, MD_String8Node, 1); - node->string = string; - - MD_QueuePush(list->first, list->last, node); - list->node_count += 1; - list->total_size += string.size; -} - -MD_FUNCTION void -MD_S8ListPushFmt(MD_Arena *arena, MD_String8List *list, char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - MD_String8 string = MD_S8FmtV(arena, fmt, args); - va_end(args); - MD_S8ListPush(arena, list, string); -} - -MD_FUNCTION void -MD_S8ListConcat(MD_String8List *list, MD_String8List *to_push) -{ - if(to_push->first) - { - list->node_count += to_push->node_count; - list->total_size += to_push->total_size; - - if(list->last == 0) - { - *list = *to_push; - } - else - { - list->last->next = to_push->first; - list->last = to_push->last; - } - } - MD_MemoryZeroStruct(to_push); -} - -MD_FUNCTION MD_String8List -MD_S8Split(MD_Arena *arena, MD_String8 string, int split_count, MD_String8 *splits) -{ - MD_String8List list = MD_ZERO_STRUCT; - - MD_u64 split_start = 0; - for(MD_u64 i = 0; i < string.size; i += 1) - { - MD_b32 was_split = 0; - for(int split_idx = 0; split_idx < split_count; split_idx += 1) - { - MD_b32 match = 0; - if(i + splits[split_idx].size <= string.size) - { - match = 1; - for(MD_u64 split_i = 0; split_i < splits[split_idx].size && i + split_i < string.size; split_i += 1) - { - if(splits[split_idx].str[split_i] != string.str[i + split_i]) - { - match = 0; - break; - } - } - } - if(match) - { - MD_String8 split_string = MD_S8(string.str + split_start, i - split_start); - MD_S8ListPush(arena, &list, split_string); - split_start = i + splits[split_idx].size; - i += splits[split_idx].size - 1; - was_split = 1; - break; - } - } - - if(was_split == 0 && i == string.size - 1) - { - MD_String8 split_string = MD_S8(string.str + split_start, i+1 - split_start); - MD_S8ListPush(arena, &list, split_string); - break; - } - } - - return list; -} - -MD_FUNCTION MD_String8 -MD_S8ListJoin(MD_Arena *arena, MD_String8List list, MD_StringJoin *join_ptr) -{ - // setup join parameters - MD_StringJoin join = MD_ZERO_STRUCT; - if (join_ptr != 0) - { - MD_MemoryCopy(&join, join_ptr, sizeof(join)); - } - - // calculate size & allocate - MD_u64 sep_count = 0; - if (list.node_count > 1) - { - sep_count = list.node_count - 1; - } - MD_String8 result = MD_ZERO_STRUCT; - result.size = (list.total_size + join.pre.size + - sep_count*join.mid.size + join.post.size); - result.str = MD_PushArrayZero(arena, MD_u8, result.size); - - // fill - MD_u8 *ptr = result.str; - MD_MemoryCopy(ptr, join.pre.str, join.pre.size); - ptr += join.pre.size; - for(MD_String8Node *node = list.first; node; node = node->next) - { - MD_MemoryCopy(ptr, node->string.str, node->string.size); - ptr += node->string.size; - if (node != list.last) - { - MD_MemoryCopy(ptr, join.mid.str, join.mid.size); - ptr += join.mid.size; - } - } - MD_MemoryCopy(ptr, join.pre.str, join.pre.size); - ptr += join.pre.size; - - return(result); -} - -MD_FUNCTION MD_String8 -MD_S8Stylize(MD_Arena *arena, MD_String8 string, MD_IdentifierStyle word_style, - MD_String8 separator) -{ - MD_String8 result = MD_ZERO_STRUCT; - - MD_String8List words = MD_ZERO_STRUCT; - - MD_b32 break_on_uppercase = 0; - { - break_on_uppercase = 1; - for(MD_u64 i = 0; i < string.size; i += 1) - { - if(!MD_CharIsAlpha(string.str[i]) && !MD_CharIsDigit(string.str[i])) - { - break_on_uppercase = 0; - break; - } - } - } - - MD_b32 making_word = 0; - MD_String8 word = MD_ZERO_STRUCT; - - for(MD_u64 i = 0; i < string.size;) - { - if(making_word) - { - if((break_on_uppercase && MD_CharIsAlphaUpper(string.str[i])) || - string.str[i] == '_' || MD_CharIsSpace(string.str[i]) || - i == string.size - 1) - { - if(i == string.size - 1) - { - word.size += 1; - } - making_word = 0; - MD_S8ListPush(arena, &words, word); - } - else - { - word.size += 1; - i += 1; - } - } - else - { - if(MD_CharIsAlpha(string.str[i])) - { - making_word = 1; - word.str = string.str + i; - word.size = 1; - } - i += 1; - } - } - - result.size = words.total_size; - if(words.node_count > 1) - { - result.size += separator.size*(words.node_count-1); - } - result.str = MD_PushArrayZero(arena, MD_u8, result.size); - - { - MD_u64 write_pos = 0; - for(MD_String8Node *node = words.first; node; node = node->next) - { - - // NOTE(rjf): Write word string to result. - { - MD_MemoryCopy(result.str + write_pos, node->string.str, node->string.size); - - // NOTE(rjf): Transform string based on word style. - switch(word_style) - { - case MD_IdentifierStyle_UpperCamelCase: - { - result.str[write_pos] = MD_CharToUpper(result.str[write_pos]); - for(MD_u64 i = write_pos+1; i < write_pos + node->string.size; i += 1) - { - result.str[i] = MD_CharToLower(result.str[i]); - } - }break; - - case MD_IdentifierStyle_LowerCamelCase: - { - MD_b32 is_first = (node == words.first); - result.str[write_pos] = (is_first ? - MD_CharToLower(result.str[write_pos]) : - MD_CharToUpper(result.str[write_pos])); - for(MD_u64 i = write_pos+1; i < write_pos + node->string.size; i += 1) - { - result.str[i] = MD_CharToLower(result.str[i]); - } - }break; - - case MD_IdentifierStyle_UpperCase: - { - for(MD_u64 i = write_pos; i < write_pos + node->string.size; i += 1) - { - result.str[i] = MD_CharToUpper(result.str[i]); - } - }break; - - case MD_IdentifierStyle_LowerCase: - { - for(MD_u64 i = write_pos; i < write_pos + node->string.size; i += 1) - { - result.str[i] = MD_CharToLower(result.str[i]); - } - }break; - - default: break; - } - - write_pos += node->string.size; - } - - if(node->next) - { - MD_MemoryCopy(result.str + write_pos, separator.str, separator.size); - write_pos += separator.size; - } - } - } - - return result; -} - -//~ Unicode Conversions - -MD_GLOBAL MD_u8 md_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, -}; - -MD_FUNCTION MD_DecodedCodepoint -MD_DecodeCodepointFromUtf8(MD_u8 *str, MD_u64 max) -{ -#define MD_bitmask1 0x01 -#define MD_bitmask2 0x03 -#define MD_bitmask3 0x07 -#define MD_bitmask4 0x0F -#define MD_bitmask5 0x1F -#define MD_bitmask6 0x3F -#define MD_bitmask7 0x7F -#define MD_bitmask8 0xFF -#define MD_bitmask9 0x01FF -#define MD_bitmask10 0x03FF - - MD_DecodedCodepoint result = {~((MD_u32)0), 1}; - MD_u8 byte = str[0]; - MD_u8 byte_class = md_utf8_class[byte >> 3]; - switch (byte_class) - { - case 1: - { - result.codepoint = byte; - }break; - - case 2: - { - if (2 <= max) - { - MD_u8 cont_byte = str[1]; - if (md_utf8_class[cont_byte >> 3] == 0) - { - result.codepoint = (byte & MD_bitmask5) << 6; - result.codepoint |= (cont_byte & MD_bitmask6); - result.advance = 2; - } - } - }break; - - case 3: - { - if (3 <= max) - { - MD_u8 cont_byte[2] = {str[1], str[2]}; - if (md_utf8_class[cont_byte[0] >> 3] == 0 && - md_utf8_class[cont_byte[1] >> 3] == 0) - { - result.codepoint = (byte & MD_bitmask4) << 12; - result.codepoint |= ((cont_byte[0] & MD_bitmask6) << 6); - result.codepoint |= (cont_byte[1] & MD_bitmask6); - result.advance = 3; - } - } - }break; - - case 4: - { - if (4 <= max) - { - MD_u8 cont_byte[3] = {str[1], str[2], str[3]}; - if (md_utf8_class[cont_byte[0] >> 3] == 0 && - md_utf8_class[cont_byte[1] >> 3] == 0 && - md_utf8_class[cont_byte[2] >> 3] == 0) - { - result.codepoint = (byte & MD_bitmask3) << 18; - result.codepoint |= ((cont_byte[0] & MD_bitmask6) << 12); - result.codepoint |= ((cont_byte[1] & MD_bitmask6) << 6); - result.codepoint |= (cont_byte[2] & MD_bitmask6); - result.advance = 4; - } - } - }break; - } - - return(result); -} - -MD_FUNCTION MD_DecodedCodepoint -MD_DecodeCodepointFromUtf16(MD_u16 *out, MD_u64 max) -{ - MD_DecodedCodepoint result = {~((MD_u32)0), 1}; - result.codepoint = out[0]; - result.advance = 1; - if (1 < max && 0xD800 <= out[0] && out[0] < 0xDC00 && 0xDC00 <= out[1] && out[1] < 0xE000) - { - result.codepoint = ((out[0] - 0xD800) << 10) | (out[1] - 0xDC00); - result.advance = 2; - } - return(result); -} - -MD_FUNCTION MD_u32 -MD_Utf8FromCodepoint(MD_u8 *out, MD_u32 codepoint) -{ -#define MD_bit8 0x80 - MD_u32 advance = 0; - if (codepoint <= 0x7F) - { - out[0] = (MD_u8)codepoint; - advance = 1; - } - else if (codepoint <= 0x7FF) - { - out[0] = (MD_bitmask2 << 6) | ((codepoint >> 6) & MD_bitmask5); - out[1] = MD_bit8 | (codepoint & MD_bitmask6); - advance = 2; - } - else if (codepoint <= 0xFFFF) - { - out[0] = (MD_bitmask3 << 5) | ((codepoint >> 12) & MD_bitmask4); - out[1] = MD_bit8 | ((codepoint >> 6) & MD_bitmask6); - out[2] = MD_bit8 | ( codepoint & MD_bitmask6); - advance = 3; - } - else if (codepoint <= 0x10FFFF) - { - out[0] = (MD_bitmask4 << 3) | ((codepoint >> 18) & MD_bitmask3); - out[1] = MD_bit8 | ((codepoint >> 12) & MD_bitmask6); - out[2] = MD_bit8 | ((codepoint >> 6) & MD_bitmask6); - out[3] = MD_bit8 | ( codepoint & MD_bitmask6); - advance = 4; - } - else - { - out[0] = '?'; - advance = 1; - } - return(advance); -} - -MD_FUNCTION MD_u32 -MD_Utf16FromCodepoint(MD_u16 *out, MD_u32 codepoint) -{ - MD_u32 advance = 1; - if (codepoint == ~((MD_u32)0)) - { - out[0] = (MD_u16)'?'; - } - else if (codepoint < 0x10000) - { - out[0] = (MD_u16)codepoint; - } - else - { - MD_u64 v = codepoint - 0x10000; - out[0] = 0xD800 + (v >> 10); - out[1] = 0xDC00 + (v & MD_bitmask10); - advance = 2; - } - return(advance); -} - -MD_FUNCTION MD_String8 -MD_S8FromS16(MD_Arena *arena, MD_String16 in) -{ - MD_u64 cap = in.size*3; - MD_u8 *str = MD_PushArrayZero(arena, MD_u8, cap + 1); - MD_u16 *ptr = in.str; - MD_u16 *opl = ptr + in.size; - MD_u64 size = 0; - MD_DecodedCodepoint consume; - for (;ptr < opl;) - { - consume = MD_DecodeCodepointFromUtf16(ptr, opl - ptr); - ptr += consume.advance; - size += MD_Utf8FromCodepoint(str + size, consume.codepoint); - } - str[size] = 0; - MD_ArenaPutBack(arena, cap - size); // := ((cap + 1) - (size + 1)) - return(MD_S8(str, size)); -} - -MD_FUNCTION MD_String16 -MD_S16FromS8(MD_Arena *arena, MD_String8 in) -{ - MD_u64 cap = in.size*2; - MD_u16 *str = MD_PushArrayZero(arena, MD_u16, cap + 1); - MD_u8 *ptr = in.str; - MD_u8 *opl = ptr + in.size; - MD_u64 size = 0; - MD_DecodedCodepoint consume; - for (;ptr < opl;) - { - consume = MD_DecodeCodepointFromUtf8(ptr, opl - ptr); - ptr += consume.advance; - size += MD_Utf16FromCodepoint(str + size, consume.codepoint); - } - str[size] = 0; - MD_ArenaPutBack(arena, 2*(cap - size)); // := 2*((cap + 1) - (size + 1)) - MD_String16 result = {str, size}; - return(result); -} - -MD_FUNCTION MD_String8 -MD_S8FromS32(MD_Arena *arena, MD_String32 in) -{ - MD_u64 cap = in.size*4; - MD_u8 *str = MD_PushArrayZero(arena, MD_u8, cap + 1); - MD_u32 *ptr = in.str; - MD_u32 *opl = ptr + in.size; - MD_u64 size = 0; - MD_DecodedCodepoint consume; - for (;ptr < opl; ptr += 1) - { - size += MD_Utf8FromCodepoint(str + size, *ptr); - } - str[size] = 0; - MD_ArenaPutBack(arena, cap - size); // := ((cap + 1) - (size + 1)) - return(MD_S8(str, size)); -} - -MD_FUNCTION MD_String32 -MD_S32FromS8(MD_Arena *arena, MD_String8 in) -{ - MD_u64 cap = in.size; - MD_u32 *str = MD_PushArrayZero(arena, MD_u32, cap + 1); - MD_u8 *ptr = in.str; - MD_u8 *opl = ptr + in.size; - MD_u64 size = 0; - MD_DecodedCodepoint consume; - for (;ptr < opl;) - { - consume = MD_DecodeCodepointFromUtf8(ptr, opl - ptr); - ptr += consume.advance; - str[size] = consume.codepoint; - size += 1; - } - str[size] = 0; - MD_ArenaPutBack(arena, 4*(cap - size)); // := 4*((cap + 1) - (size + 1)) - MD_String32 result = {str, size}; - return(result); -} - -//~ File Name Strings - -MD_FUNCTION MD_String8 -MD_PathChopLastPeriod(MD_String8 string) -{ - MD_u64 period_pos = MD_S8FindSubstring(string, MD_S8Lit("."), 0, MD_MatchFlag_FindLast); - if(period_pos < string.size) - { - string.size = period_pos; - } - return string; -} - -MD_FUNCTION MD_String8 -MD_PathSkipLastSlash(MD_String8 string) -{ - MD_u64 slash_pos = MD_S8FindSubstring(string, MD_S8Lit("/"), 0, - MD_StringMatchFlag_SlashInsensitive| - MD_MatchFlag_FindLast); - if(slash_pos < string.size) - { - string.str += slash_pos+1; - string.size -= slash_pos+1; - } - return string; -} - -MD_FUNCTION MD_String8 -MD_PathSkipLastPeriod(MD_String8 string) -{ - MD_u64 period_pos = MD_S8FindSubstring(string, MD_S8Lit("."), 0, MD_MatchFlag_FindLast); - if(period_pos < string.size) - { - string.str += period_pos+1; - string.size -= period_pos+1; - } - return string; -} - -MD_FUNCTION MD_String8 -MD_PathChopLastSlash(MD_String8 string) -{ - MD_u64 slash_pos = MD_S8FindSubstring(string, MD_S8Lit("/"), 0, - MD_StringMatchFlag_SlashInsensitive| - MD_MatchFlag_FindLast); - if(slash_pos < string.size) - { - string.size = slash_pos; - } - return string; -} - -MD_FUNCTION MD_String8 -MD_S8SkipWhitespace(MD_String8 string) -{ - for(MD_u64 i = 0; i < string.size; i += 1) - { - if(!MD_CharIsSpace(string.str[i])) - { - string = MD_S8Skip(string, i); - break; - } - } - return string; -} - -MD_FUNCTION MD_String8 -MD_S8ChopWhitespace(MD_String8 string) -{ - for(MD_u64 i = string.size-1; i < string.size; i -= 1) - { - if(!MD_CharIsSpace(string.str[i])) - { - string = MD_S8Prefix(string, i+1); - break; - } - } - return string; -} - -//~ Numeric Strings - -MD_GLOBAL MD_u8 md_char_to_value[] = { - 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, -}; - -MD_GLOBAL MD_u8 md_char_is_integer[] = { - 0,0,0,0,0,0,1,1, - 1,0,0,0,1,0,0,0, - 0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0, -}; - -MD_FUNCTION MD_b32 -MD_StringIsU64(MD_String8 string, MD_u32 radix) -{ - MD_b32 result = 0; - if (string.size > 0) - { - result = 1; - for (MD_u8 *ptr = string.str, *opl = string.str + string.size; - ptr < opl; - ptr += 1) - { - MD_u8 c = *ptr; - if (!md_char_is_integer[c >> 3]) - { - result = 0; - break; - } - if (md_char_to_value[(c - 0x30)&0x1F] >= radix) - { - result = 0; - break; - } - } - } - return(result); -} - -MD_FUNCTION MD_b32 -MD_StringIsCStyleInt(MD_String8 string) -{ - MD_u8 *ptr = string.str; - MD_u8 *opl = string.str + string.size; - - // consume sign - for (;ptr < opl && (*ptr == '+' || *ptr == '-'); ptr += 1); - - // radix from prefix - MD_u64 radix = 10; - if (ptr < opl) - { - MD_u8 c0 = *ptr; - if (c0 == '0') - { - ptr += 1; - radix = 8; - if (ptr < opl) - { - MD_u8 c1 = *ptr; - if (c1 == 'x') - { - ptr += 1; - radix = 0x10; - } - else if (c1 == 'b') - { - ptr += 1; - radix = 2; - } - } - } - } - - // check integer "digits" - MD_String8 digits_substr = MD_S8Range(ptr, opl); - MD_b32 result = MD_StringIsU64(digits_substr, radix); - - return(result); -} - -MD_FUNCTION MD_u64 -MD_U64FromString(MD_String8 string, MD_u32 radix) -{ - MD_Assert(2 <= radix && radix <= 16); - MD_u64 value = 0; - for (MD_u64 i = 0; i < string.size; i += 1) - { - value *= radix; - MD_u8 c = string.str[i]; - value += md_char_to_value[(c - 0x30)&0x1F]; - } - return(value); -} - -MD_FUNCTION MD_i64 -MD_CStyleIntFromString(MD_String8 string) -{ - MD_u64 p = 0; - - // consume sign - MD_i64 sign = +1; - if (p < string.size) - { - MD_u8 c = string.str[p]; - if (c == '-') - { - sign = -1; - p += 1; - } - else if (c == '+') - { - p += 1; - } - } - - // radix from prefix - MD_u64 radix = 10; - if (p < string.size) - { - MD_u8 c0 = string.str[p]; - if (c0 == '0') - { - p += 1; - radix = 8; - if (p < string.size) - { - MD_u8 c1 = string.str[p]; - if (c1 == 'x') - { - p += 1; - radix = 16; - } - else if (c1 == 'b') - { - p += 1; - radix = 2; - } - } - } - } - - // consume integer "digits" - MD_String8 digits_substr = MD_S8Skip(string, p); - MD_u64 n = MD_U64FromString(digits_substr, radix); - - // combine result - MD_i64 result = sign*n; - return(result); -} - -MD_FUNCTION MD_f64 -MD_F64FromString(MD_String8 string) -{ - char str[64]; - MD_u64 str_size = string.size; - if (str_size > sizeof(str) - 1) - { - str_size = sizeof(str) - 1; - } - MD_MemoryCopy(str, string.str, str_size); - str[str_size] = 0; - return(atof(str)); -} - - -MD_FUNCTION MD_String8 -MD_CStyleHexStringFromU64(MD_Arena *arena, MD_u64 x, MD_b32 caps) -{ - static char md_int_value_to_char[] = "0123456789abcdef"; - MD_u8 buffer[10]; - MD_u8 *opl = buffer + 10; - MD_u8 *ptr = opl; - if (x == 0) - { - ptr -= 1; - *ptr = '0'; - } - else - { - for (;;) - { - MD_u32 val = x%16; - x /= 16; - MD_u8 c = (MD_u8)md_int_value_to_char[val]; - if (caps) - { - c = MD_CharToUpper(c); - } - ptr -= 1; - *ptr = c; - if (x == 0) - { - break; - } - } - } - ptr -= 1; - *ptr = 'x'; - ptr -= 1; - *ptr = '0'; - - MD_String8 result = MD_ZERO_STRUCT; - result.size = (MD_u64)(ptr - buffer); - result.str = MD_PushArray(arena, MD_u8, result.size + 1); - MD_MemoryCopy(result.str, buffer, result.size); - result.str[result.size] =0; - return(result); -} - -//~ Enum/Flag Strings - -MD_FUNCTION MD_String8 -MD_StringFromNodeKind(MD_NodeKind kind) -{ - // NOTE(rjf): @maintenance Must be kept in sync with MD_NodeKind enum. - static char *cstrs[MD_NodeKind_COUNT] = - { - "Nil", - - "File", - "ErrorMarker", - - "Main", - "Tag", - - "List", - "Reference", - }; - return MD_S8CString(cstrs[kind]); -} - -MD_FUNCTION MD_String8List -MD_StringListFromNodeFlags(MD_Arena *arena, MD_NodeFlags flags) -{ - // NOTE(rjf): @maintenance Must be kept in sync with MD_NodeFlags enum. - static char *flag_cstrs[] = - { - "HasParenLeft", - "HasParenRight", - "HasBracketLeft", - "HasBracketRight", - "HasBraceLeft", - "HasBraceRight", - - "IsBeforeSemicolon", - "IsAfterSemicolon", - - "IsBeforeComma", - "IsAfterComma", - - "StringSingleQuote", - "StringDoubleQuote", - "StringTick", - "StringTriplet", - - "Numeric", - "Identifier", - "StringLiteral", - }; - - MD_String8List list = MD_ZERO_STRUCT; - MD_u64 bits = sizeof(flags) * 8; - for(MD_u64 i = 0; i < bits && i < MD_ArrayCount(flag_cstrs); i += 1) - { - if(flags & (1ull << i)) - { - MD_S8ListPush(arena, &list, MD_S8CString(flag_cstrs[i])); - } - } - return list; -} - -//~ Map Table Data Structure - -MD_FUNCTION MD_u64 -MD_HashStr(MD_String8 string) -{ - MD_u64 result = 5381; - for(MD_u64 i = 0; i < string.size; i += 1) - { - result = ((result << 5) + result) + string.str[i]; - } - return result; -} - -// NOTE(mal): Generic 64-bit hash function (https://nullprogram.com/blog/2018/07/31/) -// Reversible, so no collisions. Assumes all bits of the pointer matter. -MD_FUNCTION MD_u64 -MD_HashPtr(void *p) -{ - MD_u64 h = (MD_u64)p; - h = (h ^ (h >> 30)) * 0xbf58476d1ce4e5b9; - h = (h ^ (h >> 27)) * 0x94d049bb133111eb; - h = h ^ (h >> 31); - return h; -} - -MD_FUNCTION MD_Map -MD_MapMakeBucketCount(MD_Arena *arena, MD_u64 bucket_count) -{ - MD_Map result = {0}; - result.bucket_count = bucket_count; - result.buckets = MD_PushArrayZero(arena, MD_MapBucket, bucket_count); - return(result); -} - -MD_FUNCTION MD_Map -MD_MapMake(MD_Arena *arena) -{ - MD_Map result = MD_MapMakeBucketCount(arena, 4093); - return(result); -} - -MD_FUNCTION MD_MapKey -MD_MapKeyStr(MD_String8 string) -{ - MD_MapKey result = {0}; - if (string.size != 0) - { - result.hash = MD_HashStr(string); - result.size = string.size; - if (string.size > 0) - { - result.ptr = string.str; - } - } - return(result); -} - -MD_FUNCTION MD_MapKey -MD_MapKeyPtr(void *ptr) -{ - MD_MapKey result = {0}; - if (ptr != 0) - { - result.hash = MD_HashPtr(ptr); - result.size = 0; - result.ptr = ptr; - } - return(result); -} - -MD_FUNCTION MD_MapSlot* -MD_MapLookup(MD_Map *map, MD_MapKey key) -{ - MD_MapSlot *result = 0; - if (map->bucket_count > 0) - { - MD_u64 index = key.hash%map->bucket_count; - result = MD_MapScan(map->buckets[index].first, key); - } - return(result); -} - -MD_FUNCTION MD_MapSlot* -MD_MapScan(MD_MapSlot *first_slot, MD_MapKey key) -{ - MD_MapSlot *result = 0; - if (first_slot != 0) - { - MD_b32 ptr_kind = (key.size == 0); - MD_String8 key_string = MD_S8((MD_u8*)key.ptr, key.size); - for (MD_MapSlot *slot = first_slot; - slot != 0; - slot = slot->next) - { - if (slot->key.hash == key.hash) - { - if (ptr_kind) - { - if (slot->key.size == 0 && slot->key.ptr == key.ptr) - { - result = slot; - break; - } - } - else - { - MD_String8 slot_string = MD_S8((MD_u8*)slot->key.ptr, slot->key.size); - if (MD_S8Match(slot_string, key_string, 0)) - { - result = slot; - break; - } - } - } - } - } - return(result); -} - -MD_FUNCTION MD_MapSlot* -MD_MapInsert(MD_Arena *arena, MD_Map *map, MD_MapKey key, void *val) -{ - MD_MapSlot *result = 0; - if (map->bucket_count > 0) - { - MD_u64 index = key.hash%map->bucket_count; - MD_MapSlot *slot = MD_PushArrayZero(arena, MD_MapSlot, 1); - MD_MapBucket *bucket = &map->buckets[index]; - MD_QueuePush(bucket->first, bucket->last, slot); - slot->key = key; - slot->val = val; - result = slot; - } - return(result); -} - -MD_FUNCTION MD_MapSlot* -MD_MapOverwrite(MD_Arena *arena, MD_Map *map, MD_MapKey key, void *val) -{ - MD_MapSlot *result = MD_MapLookup(map, key); - if (result != 0) - { - result->val = val; - } - else - { - result = MD_MapInsert(arena, map, key, val); - } - return(result); -} - -//~ Parsing - -MD_FUNCTION MD_Token -MD_TokenFromString(MD_String8 string) -{ - MD_Token token = MD_ZERO_STRUCT; - - MD_u8 *one_past_last = string.str + string.size; - MD_u8 *first = string.str; - - if(first < one_past_last) - { - MD_u8 *at = first; - MD_u32 skip_n = 0; - MD_u32 chop_n = 0; - -#define MD_TokenizerScan(cond) for (; at < one_past_last && (cond); at += 1) - - switch (*at) - { - // NOTE(allen): Whitespace parsing - case '\n': - { - token.kind = MD_TokenKind_Newline; - at += 1; - }break; - - case ' ': case '\r': case '\t': case '\f': case '\v': - { - token.kind = MD_TokenKind_Whitespace; - at += 1; - MD_TokenizerScan(*at == ' ' || *at == '\r' || *at == '\t' || *at == '\f' || *at == '\v'); - }break; - - // NOTE(allen): Comment parsing - case '/': - { - if (at + 1 < one_past_last) - { - if (at[1] == '/') - { - // trim off the first '//' - skip_n = 2; - at += 2; - token.kind = MD_TokenKind_Comment; - MD_TokenizerScan(*at != '\n' && *at != '\r'); - } - else if (at[1] == '*') - { - // trim off the first '/*' - skip_n = 2; - at += 2; - token.kind = MD_TokenKind_BrokenComment; - int counter = 1; - for (;at < one_past_last && counter > 0; at += 1) - { - if (at + 1 < one_past_last) - { - if (at[0] == '*' && at[1] == '/') - { - at += 1; - counter -= 1; - } - else if (at[0] == '/' && at[1] == '*') - { - at += 1; - counter += 1; - } - } - } - if(counter == 0) - { - token.kind = MD_TokenKind_Comment; - chop_n = 2; - } - } - } - if (token.kind == 0) goto symbol_lex; - }break; - - // NOTE(allen): Strings - case '"': - case '\'': - case '`': - { - token.kind = MD_TokenKind_BrokenStringLiteral; - - // determine delimiter setup - MD_u8 d = *at; - MD_b32 is_triplet = (at + 2 < one_past_last && at[1] == d && at[2] == d); - - // lex triple-delimiter string - if (is_triplet) - { - skip_n = 3; - at += 3; - MD_u32 consecutive_d = 0; - for (;;) - { - // fail condition - if (at >= one_past_last) - { - break; - } - - if(at[0] == d) - { - consecutive_d += 1; - at += 1; - // close condition - if (consecutive_d == 3) - { - chop_n = 3; - token.kind = MD_TokenKind_StringLiteral; - break; - } - } - else - { - consecutive_d = 0; - - // escaping rule - if(at[0] == '\\') - { - at += 1; - if(at < one_past_last && (at[0] == d || at[0] == '\\')) - { - at += 1; - } - } - else{ - at += 1; - } - } - } - } - - // lex single-delimiter string - if (!is_triplet) - { - skip_n = 1; - at += 1; - for (;at < one_past_last;) - { - // close condition - if (*at == d) - { - at += 1; - chop_n = 1; - token.kind = MD_TokenKind_StringLiteral; - break; - } - - // fail condition - if (*at == '\n') - { - break; - } - - // escaping rule - if (at[0] == '\\') - { - at += 1; - if (at < one_past_last && (at[0] == d || at[0] == '\\')) - { - at += 1; - } - } - else - { - at += 1; - } - } - } - - //- rjf: set relevant node flags on token - token.node_flags |= MD_NodeFlag_StringLiteral; - switch(d) - { - case '\'': token.node_flags |= MD_NodeFlag_StringSingleQuote; break; - case '"': token.node_flags |= MD_NodeFlag_StringDoubleQuote; break; - case '`': token.node_flags |= MD_NodeFlag_StringTick; break; - default: break; - } - if(is_triplet) - { - token.node_flags |= MD_NodeFlag_StringTriplet; - } - - }break; - - // NOTE(allen): Identifiers, Numbers, Symbols - default: - { - if (MD_CharIsAlpha(*at) || *at == '_') - { - token.node_flags |= MD_NodeFlag_Identifier; - token.kind = MD_TokenKind_Identifier; - at += 1; - MD_TokenizerScan(MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '_'); - } - - else if (MD_CharIsDigit(*at)) - { - token.node_flags |= MD_NodeFlag_Numeric; - token.kind = MD_TokenKind_Numeric; - at += 1; - - for (; at < one_past_last;) - { - MD_b32 good = 0; - if (*at == 'e' || *at == 'E') - { - good = 1; - at += 1; - if (at < one_past_last && (*at == '+' || *at == '-')) - { - at += 1; - } - } - else if (MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '.' || *at == '_') - { - good = 1; - at += 1; - } - if (!good) - { - break; - } - } - } - - else if (MD_CharIsUnreservedSymbol(*at)) - { - symbol_lex: - - token.node_flags |= MD_NodeFlag_Symbol; - token.kind = MD_TokenKind_Symbol; - at += 1; - MD_TokenizerScan(MD_CharIsUnreservedSymbol(*at)); - } - - else if (MD_CharIsReservedSymbol(*at)) - { - token.kind = MD_TokenKind_Reserved; - at += 1; - } - - else - { - token.kind = MD_TokenKind_BadCharacter; - at += 1; - } - }break; - } - - token.raw_string = MD_S8Range(first, at); - token.string = MD_S8Substring(token.raw_string, skip_n, token.raw_string.size - chop_n); - -#undef MD_TokenizerScan - - } - - return token; -} - -MD_FUNCTION MD_u64 -MD_LexAdvanceFromSkips(MD_String8 string, MD_TokenKind skip_kinds) -{ - MD_u64 result = string.size; - MD_u64 p = 0; - for (;;) - { - MD_Token token = MD_TokenFromString(MD_S8Skip(string, p)); - if ((skip_kinds & token.kind) == 0) - { - result = p; - break; - } - p += token.raw_string.size; - } - return(result); -} - -MD_FUNCTION MD_ParseResult -MD_ParseResultZero(void) -{ - MD_ParseResult result = MD_ZERO_STRUCT; - result.node = MD_NilNode(); - return result; -} - -MD_FUNCTION MD_ParseResult -MD_ParseNodeSet(MD_Arena *arena, MD_String8 string, MD_u64 offset, MD_Node *parent, - MD_ParseSetRule rule) -{ - MD_ParseResult result = MD_ParseResultZero(); - MD_u64 off = offset; - - //- rjf: fill data from set opener - MD_Token initial_token = MD_TokenFromString(MD_S8Skip(string, offset)); - MD_u8 set_opener = 0; - MD_NodeFlags set_opener_flags = 0; - MD_b32 close_with_brace = 0; - MD_b32 close_with_paren = 0; - MD_b32 close_with_separator = 0; - MD_b32 parse_all = 0; - switch(rule) - { - default: break; - - case MD_ParseSetRule_EndOnDelimiter: - { - MD_u64 opener_check_off = off; - opener_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, opener_check_off), MD_TokenGroup_Irregular); - initial_token = MD_TokenFromString(MD_S8Skip(string, opener_check_off)); - if(initial_token.kind == MD_TokenKind_Reserved) - { - MD_u8 c = initial_token.raw_string.str[0]; - if(c == '{') - { - set_opener = '{'; - set_opener_flags |= MD_NodeFlag_HasBraceLeft; - opener_check_off += initial_token.raw_string.size; - off = opener_check_off; - close_with_brace = 1; - } - else if(c == '(') - { - set_opener = '('; - set_opener_flags |= MD_NodeFlag_HasParenLeft; - opener_check_off += initial_token.raw_string.size; - off = opener_check_off; - close_with_paren = 1; - } - else if(c == '[') - { - set_opener = '['; - set_opener_flags |= MD_NodeFlag_HasBracketLeft; - opener_check_off += initial_token.raw_string.size; - off = opener_check_off; - close_with_paren = 1; - } - else - { - close_with_separator = 1; - } - } - else - { - close_with_separator = 1; - } - }break; - - case MD_ParseSetRule_Global: - { - parse_all = 1; - }break; - } - - //- rjf: fill parent data from opener - parent->flags |= set_opener_flags; - - //- rjf: parse children - MD_b32 got_closer = 0; - MD_u64 parsed_child_count = 0; - if(set_opener != 0 || close_with_separator || parse_all) - { - MD_NodeFlags next_child_flags = 0; - for(;off < string.size;) - { - - //- rjf: check for separator closers - if(close_with_separator) - { - MD_u64 closer_check_off = off; - - //- rjf: check newlines - { - MD_Token potential_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); - if(potential_closer.kind == MD_TokenKind_Newline) - { - closer_check_off += potential_closer.raw_string.size; - off = closer_check_off; - - // NOTE(rjf): always terminate with a newline if we have >0 children - if(parsed_child_count > 0) - { - off = closer_check_off; - got_closer = 1; - break; - } - - // NOTE(rjf): terminate after double newline if we have 0 children - MD_Token next_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); - if(next_closer.kind == MD_TokenKind_Newline) - { - closer_check_off += next_closer.raw_string.size; - off = closer_check_off; - got_closer = 1; - break; - } - } - } - - //- rjf: check separators and possible braces from higher parents - { - closer_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token potential_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); - if(potential_closer.kind == MD_TokenKind_Reserved) - { - MD_u8 c = potential_closer.raw_string.str[0]; - if(c == ',' || c == ';') - { - off = closer_check_off; - closer_check_off += potential_closer.raw_string.size; - break; - } - else if(c == '}' || c == ']'|| c == ')') - { - goto end_parse; - } - } - } - - } - - //- rjf: check for non-separator closers - if(!close_with_separator && !parse_all) - { - MD_u64 closer_check_off = off; - closer_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token potential_closer = MD_TokenFromString(MD_S8Skip(string, closer_check_off)); - if(potential_closer.kind == MD_TokenKind_Reserved) - { - MD_u8 c = potential_closer.raw_string.str[0]; - if(close_with_brace && c == '}') - { - closer_check_off += potential_closer.raw_string.size; - off = closer_check_off; - parent->flags |= MD_NodeFlag_HasBraceRight; - got_closer = 1; - break; - } - else if(close_with_paren && c == ']') - { - closer_check_off += potential_closer.raw_string.size; - off = closer_check_off; - parent->flags |= MD_NodeFlag_HasBracketRight; - got_closer = 1; - break; - } - else if(close_with_paren && c == ')') - { - closer_check_off += potential_closer.raw_string.size; - off = closer_check_off; - parent->flags |= MD_NodeFlag_HasParenRight; - got_closer = 1; - break; - } - } - } - - //- rjf: parse next child - MD_ParseResult child_parse = MD_ParseOneNode(arena, string, off); - MD_MessageListConcat(&result.errors, &child_parse.errors); - off += child_parse.string_advance; - - //- rjf: hook child into parent - if(!MD_NodeIsNil(child_parse.node)) - { - // NOTE(rjf): @error No unnamed set children of implicitly-delimited sets - if(close_with_separator && - child_parse.node->string.size == 0 && - child_parse.node->flags & (MD_NodeFlag_HasParenLeft | - MD_NodeFlag_HasParenRight | - MD_NodeFlag_HasBracketLeft | - MD_NodeFlag_HasBracketRight | - MD_NodeFlag_HasBraceLeft | - MD_NodeFlag_HasBraceRight )) - { - MD_String8 error_str = MD_S8Lit("Unnamed set children of implicitly-delimited sets are not legal."); - MD_Message *error = MD_MakeNodeError(arena, child_parse.node, MD_MessageKind_Warning, - error_str); - MD_MessageListPush(&result.errors, error); - } - - MD_PushChild(parent, child_parse.node); - parsed_child_count += 1; - } - - //- rjf: check trailing separator - MD_NodeFlags trailing_separator_flags = 0; - if(!close_with_separator) - { - off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token trailing_separator = MD_TokenFromString(MD_S8Skip(string, off)); - if (trailing_separator.kind == MD_TokenKind_Reserved) - { - MD_u8 c = trailing_separator.string.str[0]; - if(c == ',') - { - trailing_separator_flags |= MD_NodeFlag_IsBeforeComma; - off += trailing_separator.raw_string.size; - } - else if(c == ';') - { - trailing_separator_flags |= MD_NodeFlag_IsBeforeSemicolon; - off += trailing_separator.raw_string.size; - } - } - } - - //- rjf: fill child flags - child_parse.node->flags |= next_child_flags | trailing_separator_flags; - - //- rjf: setup next_child_flags - next_child_flags = MD_NodeFlag_AfterFromBefore(trailing_separator_flags); - } - } - end_parse:; - - //- rjf: push missing closer error, if we have one - if(set_opener != 0 && got_closer == 0) - { - // NOTE(rjf): @error We didn't get a closer for the set - MD_String8 error_str = MD_S8Fmt(arena, "Unbalanced \"%c\"", set_opener); - MD_Message *error = MD_MakeTokenError(arena, string, initial_token, - MD_MessageKind_FatalError, error_str); - MD_MessageListPush(&result.errors, error); - } - - //- rjf: push empty implicit set error, - if(close_with_separator && parsed_child_count == 0) - { - // NOTE(rjf): @error No empty implicitly-delimited sets - MD_Message *error = MD_MakeTokenError(arena, string, initial_token, MD_MessageKind_Error, - MD_S8Lit("Empty implicitly-delimited node list")); - MD_MessageListPush(&result.errors, error); - } - - //- rjf: fill result info - result.node = parent; - result.string_advance = off - offset; - - return result; -} - -MD_FUNCTION MD_ParseResult -MD_ParseOneNode(MD_Arena *arena, MD_String8 string, MD_u64 offset) -{ - MD_ParseResult result = MD_ParseResultZero(); - MD_u64 off = offset; - - //- rjf: parse pre-comment - MD_String8 prev_comment = MD_ZERO_STRUCT; - { - MD_Token comment_token = MD_ZERO_STRUCT; - for(;off < string.size;) - { - MD_Token token = MD_TokenFromString(MD_S8Skip(string, off)); - if(token.kind == MD_TokenKind_Comment) - { - off += token.raw_string.size; - comment_token = token; - } - else if(token.kind == MD_TokenKind_Newline) - { - off += token.raw_string.size; - MD_Token next_token = MD_TokenFromString(MD_S8Skip(string, off)); - if(next_token.kind == MD_TokenKind_Comment) - { - // NOTE(mal): If more than one comment, use the last comment - comment_token = next_token; - } - else if(next_token.kind == MD_TokenKind_Newline) - { - MD_MemoryZeroStruct(&comment_token); - } - } - else if((token.kind & MD_TokenGroup_Whitespace) != 0) - { - off += token.raw_string.size; - } - else - { - break; - } - prev_comment = comment_token.string; - } - } - - //- rjf: parse tag list - MD_Node *first_tag = MD_NilNode(); - MD_Node *last_tag = MD_NilNode(); - { - for(;off < string.size;) - { - //- rjf: parse @ symbol, signifying start of tag - off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token next_token = MD_TokenFromString(MD_S8Skip(string, off)); - if(next_token.kind != MD_TokenKind_Reserved || - next_token.string.str[0] != '@') - { - break; - } - off += next_token.raw_string.size; - - //- rjf: parse string of tag node - MD_Token name = MD_TokenFromString(MD_S8Skip(string, off)); - MD_u64 name_off = off; - if((name.kind & MD_TokenGroup_Label) == 0) - { - // NOTE(rjf): @error Improper token for tag string - MD_String8 error_str = MD_S8Fmt(arena, "\"%.*s\" is not a proper tag label", - MD_S8VArg(name.raw_string)); - MD_Message *error = MD_MakeTokenError(arena, string, name, MD_MessageKind_Error, error_str); - MD_MessageListPush(&result.errors, error); - break; - } - off += name.raw_string.size; - - //- rjf: build tag - MD_Node *tag = MD_MakeNode(arena, MD_NodeKind_Tag, name.string, name.raw_string, name_off); - - //- rjf: parse tag arguments - MD_Token open_paren = MD_TokenFromString(MD_S8Skip(string, off)); - MD_ParseResult args_parse = MD_ParseResultZero(); - if(open_paren.kind == MD_TokenKind_Reserved && - open_paren.string.str[0] == '(') - { - args_parse = MD_ParseNodeSet(arena, string, off, tag, MD_ParseSetRule_EndOnDelimiter); - MD_MessageListConcat(&result.errors, &args_parse.errors); - } - off += args_parse.string_advance; - - //- rjf: push tag to result - MD_NodeDblPushBack(first_tag, last_tag, tag); - } - } - - //- rjf: parse node - MD_Node *parsed_node = MD_NilNode(); - MD_ParseResult children_parse = MD_ParseResultZero(); - retry:; - { - //- rjf: try to parse an unnamed set - off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token unnamed_set_opener = MD_TokenFromString(MD_S8Skip(string, off)); - if(unnamed_set_opener.kind == MD_TokenKind_Reserved) - { - MD_u8 c = unnamed_set_opener.string.str[0]; - if (c == '(' || c == '{' || c == '[') - { - parsed_node = MD_MakeNode(arena, MD_NodeKind_Main, MD_S8Lit(""), MD_S8Lit(""), - unnamed_set_opener.raw_string.str - string.str); - children_parse = MD_ParseNodeSet(arena, string, off, parsed_node, - MD_ParseSetRule_EndOnDelimiter); - off += children_parse.string_advance; - MD_MessageListConcat(&result.errors, &children_parse.errors); - } - else if (c == ')' || c == '}' || c == ']') - { - // NOTE(rjf): @error Unexpected set closing symbol - MD_String8 error_str = MD_S8Fmt(arena, "Unbalanced \"%c\"", c); - MD_Message *error = MD_MakeTokenError(arena, string, unnamed_set_opener, - MD_MessageKind_FatalError, error_str); - MD_MessageListPush(&result.errors, error); - off += unnamed_set_opener.raw_string.size; - } - else - { - // NOTE(rjf): @error Unexpected reserved symbol - MD_String8 error_str = MD_S8Fmt(arena, "Unexpected reserved symbol \"%c\"", c); - MD_Message *error = MD_MakeTokenError(arena, string, unnamed_set_opener, - MD_MessageKind_Error, error_str); - MD_MessageListPush(&result.errors, error); - off += unnamed_set_opener.raw_string.size; - } - goto end_parse; - - } - - //- rjf: try to parse regular node, with/without children - off += MD_LexAdvanceFromSkips(MD_S8Skip(string, off), MD_TokenGroup_Irregular); - MD_Token label_name = MD_TokenFromString(MD_S8Skip(string, off)); - if((label_name.kind & MD_TokenGroup_Label) != 0) - { - off += label_name.raw_string.size; - parsed_node = MD_MakeNode(arena, MD_NodeKind_Main, label_name.string, label_name.raw_string, - label_name.raw_string.str - string.str); - parsed_node->flags |= label_name.node_flags; - - //- rjf: try to parse children for this node - MD_u64 colon_check_off = off; - colon_check_off += MD_LexAdvanceFromSkips(MD_S8Skip(string, colon_check_off), MD_TokenGroup_Irregular); - MD_Token colon = MD_TokenFromString(MD_S8Skip(string, colon_check_off)); - if(colon.kind == MD_TokenKind_Reserved && - colon.string.str[0] == ':') - { - colon_check_off += colon.raw_string.size; - off = colon_check_off; - - children_parse = MD_ParseNodeSet(arena, string, off, parsed_node, - MD_ParseSetRule_EndOnDelimiter); - off += children_parse.string_advance; - MD_MessageListConcat(&result.errors, &children_parse.errors); - } - goto end_parse; - } - - //- rjf: collect bad token - MD_Token bad_token = MD_TokenFromString(MD_S8Skip(string, off)); - if(bad_token.kind & MD_TokenGroup_Error) - { - off += bad_token.raw_string.size; - - switch (bad_token.kind) - { - case MD_TokenKind_BadCharacter: - { - MD_String8List bytes = {0}; - for(int i_byte = 0; i_byte < bad_token.raw_string.size; ++i_byte) - { - MD_u8 b = bad_token.raw_string.str[i_byte]; - MD_S8ListPush(arena, &bytes, MD_CStyleHexStringFromU64(arena, b, 1)); - } - - MD_StringJoin join = MD_ZERO_STRUCT; - join.mid = MD_S8Lit(" "); - MD_String8 byte_string = MD_S8ListJoin(arena, bytes, &join); - - // NOTE(rjf): @error Bad character - MD_String8 error_str = MD_S8Fmt(arena, "Non-ASCII character \"%.*s\"", - MD_S8VArg(byte_string)); - MD_Message *error = MD_MakeTokenError(arena, string, bad_token, MD_MessageKind_Error, - error_str); - MD_MessageListPush(&result.errors, error); - }break; - - case MD_TokenKind_BrokenComment: - { - // NOTE(rjf): @error Broken Comments - MD_Message *error = MD_MakeTokenError(arena, string, bad_token, MD_MessageKind_Error, - MD_S8Lit("Unterminated comment")); - MD_MessageListPush(&result.errors, error); - }break; - - case MD_TokenKind_BrokenStringLiteral: - { - // NOTE(rjf): @error Broken String Literals - MD_Message *error = MD_MakeTokenError(arena, string, bad_token, MD_MessageKind_Error, - MD_S8Lit("Unterminated string literal")); - MD_MessageListPush(&result.errors, error); - }break; - } - goto retry; - } - } - - end_parse:; - - //- rjf: parse comments after nodes. - MD_String8 next_comment = MD_ZERO_STRUCT; - { - MD_Token comment_token = MD_ZERO_STRUCT; - for(;;) - { - MD_Token token = MD_TokenFromString(MD_S8Skip(string, off)); - if(token.kind == MD_TokenKind_Comment) - { - comment_token = token; - off += token.raw_string.size; - break; - } - - else if(token.kind == MD_TokenKind_Newline) - { - break; - } - else if((token.kind & MD_TokenGroup_Whitespace) != 0) - { - off += token.raw_string.size; - } - else - { - break; - } - } - next_comment = comment_token.string; - } - - //- rjf: fill result - parsed_node->prev_comment = prev_comment; - parsed_node->next_comment = next_comment; - result.node = parsed_node; - if(!MD_NodeIsNil(result.node)) - { - result.node->first_tag = first_tag; - result.node->last_tag = last_tag; - for(MD_Node *tag = first_tag; !MD_NodeIsNil(tag); tag = tag->next) - { - tag->parent = result.node; - } - } - result.string_advance = off - offset; - - return result; -} - -MD_FUNCTION MD_ParseResult -MD_ParseWholeString(MD_Arena *arena, MD_String8 filename, MD_String8 contents) -{ - MD_Node *root = MD_MakeNode(arena, MD_NodeKind_File, filename, contents, 0); - MD_ParseResult result = MD_ParseNodeSet(arena, contents, 0, root, MD_ParseSetRule_Global); - result.node = root; - for(MD_Message *error = result.errors.first; error != 0; error = error->next) - { - if(MD_NodeIsNil(error->node->parent)) - { - error->node->parent = root; - } - } - return result; -} - -MD_FUNCTION MD_ParseResult -MD_ParseWholeFile(MD_Arena *arena, MD_String8 filename) -{ - MD_String8 file_contents = MD_LoadEntireFile(arena, filename); - MD_ParseResult parse = MD_ParseWholeString(arena, filename, file_contents); - if(file_contents.str == 0) - { - // NOTE(rjf): @error File failing to load - MD_String8 error_str = MD_S8Fmt(arena, "Could not read file \"%.*s\"", MD_S8VArg(filename)); - MD_Message *error = MD_MakeNodeError(arena, parse.node, MD_MessageKind_FatalError, - error_str); - MD_MessageListPush(&parse.errors, error); - } - return parse; -} - -//~ Messages (Errors/Warnings) - -MD_FUNCTION MD_Node* -MD_MakeErrorMarkerNode(MD_Arena *arena, MD_String8 parse_contents, MD_u64 offset) -{ - MD_Node *result = MD_MakeNode(arena, MD_NodeKind_ErrorMarker, MD_S8Lit(""), parse_contents, - offset); - return(result); -} - -MD_FUNCTION MD_Message* -MD_MakeNodeError(MD_Arena *arena, MD_Node *node, MD_MessageKind kind, MD_String8 str) -{ - MD_Message *error = MD_PushArrayZero(arena, MD_Message, 1); - error->node = node; - error->kind = kind; - error->string = str; - return error; -} - -MD_FUNCTION MD_Message * -MD_MakeTokenError(MD_Arena *arena, MD_String8 parse_contents, MD_Token token, - MD_MessageKind kind, MD_String8 str) -{ - MD_u64 offset = token.raw_string.str - parse_contents.str; - MD_Node *err_node = MD_MakeErrorMarkerNode(arena, parse_contents, offset); - return MD_MakeNodeError(arena, err_node, kind, str); -} - -MD_FUNCTION void -MD_MessageListPush(MD_MessageList *list, MD_Message *message) -{ - MD_QueuePush(list->first, list->last, message); - if(message->kind > list->max_message_kind) - { - list->max_message_kind = message->kind; - } - list->node_count += 1; -} - -MD_FUNCTION void -MD_MessageListConcat(MD_MessageList *list, MD_MessageList *to_push) -{ - if(to_push->node_count != 0) - { - if(list->last != 0) - { - list->last->next = to_push->first; - list->last = to_push->last; - list->node_count += to_push->node_count; - if(to_push->max_message_kind > list->max_message_kind) - { - list->max_message_kind = to_push->max_message_kind; - } - } - else - { - *list = *to_push; - } - MD_MemoryZeroStruct(to_push); - } -} - -//~ Location Conversions - -MD_FUNCTION MD_CodeLoc -MD_CodeLocFromFileOffset(MD_String8 filename, MD_u8 *base, MD_u64 offset) -{ - MD_CodeLoc loc; - loc.filename = filename; - loc.line = 1; - loc.column = 1; - if(base != 0) - { - MD_u8 *at = base + offset; - for(MD_u64 i = 0; base+i < at && base[i]; i += 1) - { - if(base[i] == '\n') - { - loc.line += 1; - loc.column = 1; - } - else - { - loc.column += 1; - } - } - } - return loc; -} - -MD_FUNCTION MD_CodeLoc -MD_CodeLocFromNode(MD_Node *node) -{ - MD_Node *file_root = MD_NilNode(); - for(MD_Node *parent = node->parent; !MD_NodeIsNil(parent); parent = parent->parent) - { - if(parent->kind == MD_NodeKind_File) - { - file_root = parent; - break; - } - } - MD_Node *first_tag = file_root->first_tag; - MD_CodeLoc loc = {0}; - if(MD_NodeIsNil(first_tag)) - { - loc = MD_CodeLocFromFileOffset(file_root->string, file_root->raw_string.str, node->offset); - } - else - { - loc = MD_CodeLocFromFileOffset(file_root->string, first_tag->raw_string.str, node->offset); - } - return loc; -} - -//~ Tree/List Building - -MD_FUNCTION MD_b32 -MD_NodeIsNil(MD_Node *node) -{ - return(node == 0 || node == &_md_nil_node || node->kind == MD_NodeKind_Nil); -} - -MD_FUNCTION MD_Node * -MD_NilNode(void) { return &_md_nil_node; } - -MD_FUNCTION MD_Node * -MD_MakeNode(MD_Arena *arena, MD_NodeKind kind, MD_String8 string, MD_String8 raw_string, - MD_u64 offset) -{ - MD_Node *node = MD_PushArrayZero(arena, MD_Node, 1); - node->kind = kind; - node->string = string; - node->raw_string = raw_string; - node->next = node->prev = node->parent = - node->first_child = node->last_child = - node->first_tag = node->last_tag = node->ref_target = MD_NilNode(); - node->offset = offset; - return node; -} - -MD_FUNCTION void -MD_PushChild(MD_Node *parent, MD_Node *new_child) -{ - if (!MD_NodeIsNil(new_child)) - { - MD_NodeDblPushBack(parent->first_child, parent->last_child, new_child); - new_child->parent = parent; - } -} - -MD_FUNCTION void -MD_PushTag(MD_Node *node, MD_Node *tag) -{ - if (!MD_NodeIsNil(tag)) - { - MD_NodeDblPushBack(node->first_tag, node->last_tag, tag); - tag->parent = node; - } -} - -MD_FUNCTION MD_Node* -MD_MakeList(MD_Arena *arena) -{ - MD_String8 empty = {0}; - MD_Node *result = MD_MakeNode(arena, MD_NodeKind_List, empty, empty, 0); - return(result); -} - -MD_FUNCTION void -MD_ListConcatInPlace(MD_Node *list, MD_Node *to_push) -{ - if (!MD_NodeIsNil(to_push->first_child)) - { - if (!MD_NodeIsNil(list->first_child)) - { - list->last_child->next = to_push->first_child; - list->last_child = to_push->last_child; - } - else - { - list->first_child = to_push->first_child; - list->last_child = to_push->last_child; - } - to_push->first_child = to_push->last_child = MD_NilNode(); - } -} - -MD_FUNCTION MD_Node* -MD_PushNewReference(MD_Arena *arena, MD_Node *list, MD_Node *target) -{ - MD_Node *n = MD_MakeNode(arena, MD_NodeKind_Reference, target->string, target->raw_string, - target->offset); - n->ref_target = target; - MD_PushChild(list, n); - return(n); -} - -//~ Introspection Helpers - -MD_FUNCTION MD_Node * -MD_FirstNodeWithString(MD_Node *first, MD_String8 string, MD_MatchFlags flags) -{ - MD_Node *result = MD_NilNode(); - for(MD_Node *node = first; !MD_NodeIsNil(node); node = node->next) - { - if(MD_S8Match(string, node->string, flags)) - { - result = node; - break; - } - } - return result; -} - -MD_FUNCTION MD_Node * -MD_NodeAtIndex(MD_Node *first, int n) -{ - MD_Node *result = MD_NilNode(); - if(n >= 0) - { - int idx = 0; - for(MD_Node *node = first; !MD_NodeIsNil(node); node = node->next, idx += 1) - { - if(idx == n) - { - result = node; - break; - } - } - } - return result; -} - -MD_FUNCTION MD_Node * -MD_FirstNodeWithFlags(MD_Node *first, MD_NodeFlags flags) -{ - MD_Node *result = MD_NilNode(); - for(MD_Node *n = first; !MD_NodeIsNil(n); n = n->next) - { - if(n->flags & flags) - { - result = n; - break; - } - } - return result; -} - -MD_FUNCTION int -MD_IndexFromNode(MD_Node *node) -{ - int idx = 0; - for(MD_Node *last = node->prev; !MD_NodeIsNil(last); last = last->prev, idx += 1); - return idx; -} - -MD_FUNCTION MD_Node * -MD_RootFromNode(MD_Node *node) -{ - MD_Node *parent = node; - for(MD_Node *p = parent; !MD_NodeIsNil(p); p = p->parent) - { - parent = p; - } - return parent; -} - -MD_FUNCTION MD_Node * -MD_ChildFromString(MD_Node *node, MD_String8 child_string, MD_MatchFlags flags) -{ - return MD_FirstNodeWithString(node->first_child, child_string, flags); -} - -MD_FUNCTION MD_Node * -MD_TagFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags) -{ - return MD_FirstNodeWithString(node->first_tag, tag_string, flags); -} - -MD_FUNCTION MD_Node * -MD_ChildFromIndex(MD_Node *node, int n) -{ - return MD_NodeAtIndex(node->first_child, n); -} - -MD_FUNCTION MD_Node * -MD_TagFromIndex(MD_Node *node, int n) -{ - return MD_NodeAtIndex(node->first_tag, n); -} - -MD_FUNCTION MD_Node * -MD_TagArgFromIndex(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags, int n) -{ - MD_Node *tag = MD_TagFromString(node, tag_string, flags); - return MD_ChildFromIndex(tag, n); -} - -MD_FUNCTION MD_Node * -MD_TagArgFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags tag_str_flags, MD_String8 arg_string, MD_MatchFlags arg_str_flags) -{ - MD_Node *tag = MD_TagFromString(node, tag_string, tag_str_flags); - MD_Node *arg = MD_ChildFromString(tag, arg_string, arg_str_flags); - return arg; -} - -MD_FUNCTION MD_b32 -MD_NodeHasChild(MD_Node *node, MD_String8 string, MD_MatchFlags flags) -{ - return !MD_NodeIsNil(MD_ChildFromString(node, string, flags)); -} - -MD_FUNCTION MD_b32 -MD_NodeHasTag(MD_Node *node, MD_String8 string, MD_MatchFlags flags) -{ - return !MD_NodeIsNil(MD_TagFromString(node, string, flags)); -} - -MD_FUNCTION MD_i64 -MD_ChildCountFromNode(MD_Node *node) -{ - MD_i64 result = 0; - for(MD_EachNode(child, node->first_child)) - { - result += 1; - } - return result; -} - -MD_FUNCTION MD_i64 -MD_TagCountFromNode(MD_Node *node) -{ - MD_i64 result = 0; - for(MD_EachNode(tag, node->first_tag)) - { - result += 1; - } - return result; -} - -MD_FUNCTION MD_Node * -MD_ResolveNodeFromReference(MD_Node *node) -{ - MD_u64 safety = 100; - for(; safety > 0 && node->kind == MD_NodeKind_Reference; - safety -= 1, node = node->ref_target); - MD_Node *result = node; - return(result); -} - -MD_FUNCTION MD_Node* -MD_NodeNextWithLimit(MD_Node *node, MD_Node *opl) -{ - node = node->next; - if (node == opl) - { - node = MD_NilNode(); - } - return(node); -} - -MD_FUNCTION MD_String8 -MD_PrevCommentFromNode(MD_Node *node) -{ - return(node->prev_comment); -} - -MD_FUNCTION MD_String8 -MD_NextCommentFromNode(MD_Node *node) -{ - return(node->next_comment); -} - -//~ Error/Warning Helpers - -MD_FUNCTION MD_String8 -MD_StringFromMessageKind(MD_MessageKind kind) -{ - MD_String8 result = MD_ZERO_STRUCT; - switch (kind) - { - default: break; - case MD_MessageKind_Note: result = MD_S8Lit("note"); break; - case MD_MessageKind_Warning: result = MD_S8Lit("warning"); break; - case MD_MessageKind_Error: result = MD_S8Lit("error"); break; - case MD_MessageKind_FatalError: result = MD_S8Lit("fatal error"); break; - } - return(result); -} - -MD_FUNCTION MD_String8 -MD_FormatMessage(MD_Arena *arena, MD_CodeLoc loc, MD_MessageKind kind, MD_String8 string) -{ - MD_String8 kind_string = MD_StringFromMessageKind(kind); - MD_String8 result = MD_S8Fmt(arena, "" MD_FmtCodeLoc " %.*s: %.*s\n", - MD_CodeLocVArg(loc), MD_S8VArg(kind_string), MD_S8VArg(string)); - return(result); -} - -#if !MD_DISABLE_PRINT_HELPERS - -MD_FUNCTION void -MD_PrintMessage(FILE *file, MD_CodeLoc code_loc, MD_MessageKind kind, MD_String8 string) -{ - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - MD_String8 message = MD_FormatMessage(scratch.arena, code_loc, kind, string); - fwrite(message.str, message.size, 1, file); - MD_ReleaseScratch(scratch); -} - -MD_FUNCTION void -MD_PrintMessageFmt(FILE *file, MD_CodeLoc code_loc, MD_MessageKind kind, char *fmt, ...) -{ - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - va_list args; - va_start(args, fmt); - MD_String8 string = MD_S8FmtV(scratch.arena, fmt, args); - va_end(args); - MD_String8 message = MD_FormatMessage(scratch.arena, code_loc, kind, string); - fwrite(message.str, message.size, 1, file); - MD_ReleaseScratch(scratch); -} - -#endif - -//~ Tree Comparison/Verification - -MD_FUNCTION MD_b32 -MD_NodeMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags) -{ - MD_b32 result = 0; - if(a->kind == b->kind && MD_S8Match(a->string, b->string, flags)) - { - result = 1; - if(result && flags & MD_NodeMatchFlag_NodeFlags) - { - result = result && a->flags == b->flags; - } - if(result && a->kind != MD_NodeKind_Tag && (flags & MD_NodeMatchFlag_Tags)) - { - for(MD_Node *a_tag = a->first_tag, *b_tag = b->first_tag; - !MD_NodeIsNil(a_tag) || !MD_NodeIsNil(b_tag); - a_tag = a_tag->next, b_tag = b_tag->next) - { - if(MD_NodeMatch(a_tag, b_tag, flags)) - { - if(flags & MD_NodeMatchFlag_TagArguments) - { - for(MD_Node *a_tag_arg = a_tag->first_child, *b_tag_arg = b_tag->first_child; - !MD_NodeIsNil(a_tag_arg) || !MD_NodeIsNil(b_tag_arg); - a_tag_arg = a_tag_arg->next, b_tag_arg = b_tag_arg->next) - { - if(!MD_NodeDeepMatch(a_tag_arg, b_tag_arg, flags)) - { - result = 0; - goto end; - } - } - } - } - else - { - result = 0; - goto end; - } - } - } - } - end:; - return result; -} - -MD_FUNCTION MD_b32 -MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags) -{ - MD_b32 result = MD_NodeMatch(a, b, flags); - if(result) - { - for(MD_Node *a_child = a->first_child, *b_child = b->first_child; - !MD_NodeIsNil(a_child) || !MD_NodeIsNil(b_child); - a_child = a_child->next, b_child = b_child->next) - { - if(!MD_NodeDeepMatch(a_child, b_child, flags)) - { - result = 0; - goto end; - } - } - } - end:; - return result; -} - -//~ Expression Parsing - -MD_FUNCTION void -MD_ExprOprPush(MD_Arena *arena, MD_ExprOprList *list, - MD_ExprOprKind kind, MD_u64 precedence, MD_String8 string, - MD_u32 op_id, void *op_ptr) -{ - MD_ExprOpr *op = MD_PushArrayZero(arena, MD_ExprOpr, 1); - MD_QueuePush(list->first, list->last, op); - list->count += 1; - op->op_id = op_id; - op->kind = kind; - op->precedence = precedence; - op->string = string; - op->op_ptr = op_ptr; -} - -MD_GLOBAL MD_BakeOperatorErrorHandler md_bake_operator_error_handler = 0; - -MD_FUNCTION MD_BakeOperatorErrorHandler -MD_ExprSetBakeOperatorErrorHandler(MD_BakeOperatorErrorHandler handler){ - MD_BakeOperatorErrorHandler old_handler = md_bake_operator_error_handler; - md_bake_operator_error_handler = handler; - return old_handler; -} - -MD_FUNCTION MD_ExprOprTable -MD_ExprBakeOprTableFromList(MD_Arena *arena, MD_ExprOprList *list) -{ - MD_ExprOprTable result = MD_ZERO_STRUCT; - - // TODO(allen): @upgrade_potential(minor) - - for(MD_ExprOpr *op = list->first; - op != 0; - op = op->next) - { - MD_ExprOprKind op_kind = op->kind; - MD_String8 op_s = op->string; - - // error checking - MD_String8 error_str = MD_ZERO_STRUCT; - - MD_Token op_token = MD_TokenFromString(op_s); - MD_b32 is_setlike_op = - (op_s.size == 2 && - (MD_S8Match(op_s, MD_S8Lit("[]"), 0) || MD_S8Match(op_s, MD_S8Lit("()"), 0) || - MD_S8Match(op_s, MD_S8Lit("[)"), 0) || MD_S8Match(op_s, MD_S8Lit("(]"), 0) || - MD_S8Match(op_s, MD_S8Lit("{}"), 0))); - - if(op_kind != MD_ExprOprKind_Prefix && op_kind != MD_ExprOprKind_Postfix && - op_kind != MD_ExprOprKind_Binary && op_kind != MD_ExprOprKind_BinaryRightAssociative) - { - error_str = MD_S8Fmt(arena, "Ignored operator \"%.*s\" because its kind value (%d) does not match " - "any valid operator kind", MD_S8VArg(op_s), op_kind); - } - else if(is_setlike_op && op_kind != MD_ExprOprKind_Postfix) - { - error_str = - MD_S8Fmt(arena, "Ignored operator \"%.*s\". \"%.*s\" is only allowed as unary postfix", - MD_S8VArg(op_s), MD_S8VArg(op_s)); - } - else if(!is_setlike_op && - (op_token.kind != MD_TokenKind_Identifier && op_token.kind != MD_TokenKind_Symbol)) - { - error_str = MD_S8Fmt(arena, "Ignored operator \"%.*s\" because it is neither a symbol " - "nor an identifier token", MD_S8VArg(op_s)); - } - else if(!is_setlike_op && op_token.string.size < op_s.size) - { - error_str = MD_S8Fmt(arena, "Ignored operator \"%.*s\" because its prefix \"%.*s\" " - "constitutes a standalone operator", - MD_S8VArg(op_s), MD_S8VArg(op_token.string)); - } - else - { - for(MD_ExprOpr *op2 = list->first; - op2 != op; - op2 = op2->next) - { // NOTE(mal): O(n^2) - MD_ExprOprKind op2_kind = op2->kind; - MD_String8 op2_s = op2->string; - if(op->precedence == op2->precedence && - ((op_kind == MD_ExprOprKind_Binary && - op2_kind == MD_ExprOprKind_BinaryRightAssociative) || - (op_kind == MD_ExprOprKind_BinaryRightAssociative && - op2_kind == MD_ExprOprKind_Binary))) - { - error_str = - MD_S8Fmt(arena, "Ignored binary operator \"%.*s\" because another binary operator" - "has the same precedence and different associativity", MD_S8VArg(op_s)); - } - else if(MD_S8Match(op_s, op2_s, 0)) - { - if(op_kind == op2_kind) - { - error_str = MD_S8Fmt(arena, "Ignored repeat operator \"%.*s\"", MD_S8VArg(op_s)); - } - else if(op_kind != MD_ExprOprKind_Prefix && op2_kind != MD_ExprOprKind_Prefix) - { - error_str = - MD_S8Fmt(arena, "Ignored conflicting repeat operator \"%.*s\". There can't be" - "more than one posfix/binary operator associated to the same token", - MD_S8VArg(op_s)); - } - } - } - } - - // save error - if(error_str.size != 0 && md_bake_operator_error_handler) - { - md_bake_operator_error_handler(MD_MessageKind_Warning, error_str); - } - - // save list - else - { - MD_ExprOprList *list = result.table + op_kind; - MD_ExprOpr *op_node_copy = MD_PushArray(arena, MD_ExprOpr, 1); - *op_node_copy = *op; - MD_QueuePush(list->first, list->last, op_node_copy); - list->count += 1; - } - } - - return(result); -} - -MD_FUNCTION MD_ExprOpr* -MD_ExprOprFromKindString(MD_ExprOprTable *table, MD_ExprOprKind kind, MD_String8 s) -{ - // TODO(allen): @upgrade_potential - - // NOTE(mal): Look for operator on one or all (kind == MD_ExprOprKind_Null) tables - MD_ExprOpr *result = 0; - for(MD_ExprOprKind cur_kind = (MD_ExprOprKind)(MD_ExprOprKind_Null + 1); - cur_kind < MD_ExprOprKind_COUNT; - cur_kind = (MD_ExprOprKind)(cur_kind + 1)) - { - if(kind == MD_ExprOprKind_Null || kind == cur_kind) - { - MD_ExprOprList *op_list = table->table+cur_kind; - for(MD_ExprOpr *op = op_list->first; - op != 0; - op = op->next) - { - if(MD_S8Match(op->string, s, 0)) - { - result = op; - goto dbl_break; - } - } - } - } - dbl_break:; - return result; -} - -MD_FUNCTION MD_ExprParseResult -MD_ExprParse(MD_Arena *arena, MD_ExprOprTable *op_table, MD_Node *first, MD_Node *opl) -{ - // setup a context - MD_ExprParseCtx ctx = MD_ExprParse_MakeContext(op_table); - - // parse the top level - MD_Expr *expr = MD_ExprParse_TopLevel(arena, &ctx, first, opl); - - // fill result - MD_ExprParseResult result = {0}; - result.expr = expr; - result.errors = ctx.errors; - return(result); -} - -MD_FUNCTION MD_Expr* -MD_Expr_NewLeaf(MD_Arena *arena, MD_Node *node) -{ - MD_Expr *result = MD_PushArrayZero(arena, MD_Expr, 1); - result->md_node = node; - return(result); -} - -MD_FUNCTION MD_Expr* -MD_Expr_NewOpr(MD_Arena *arena, MD_ExprOpr *op, MD_Node *op_node, MD_Expr *l, MD_Expr *r) -{ - MD_Expr *result = MD_PushArrayZero(arena, MD_Expr, 1); - result->op = op; - result->md_node = op_node; - result->parent = 0; - result->left = l; - result->right = r; - if (l != 0) - { - MD_Assert(l->parent == 0); - l->parent = result; - } - if(r != 0) - { - MD_Assert(r->parent == 0); - r->parent = result; - } - return(result); -} - -MD_FUNCTION MD_ExprParseCtx -MD_ExprParse_MakeContext(MD_ExprOprTable *op_table) -{ - MD_ExprParseCtx result = MD_ZERO_STRUCT; - result.op_table = op_table; - - result.accel.postfix_set_ops[0] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("()")); - result.accel.postfix_set_flags[0] = MD_NodeFlag_HasParenLeft | MD_NodeFlag_HasParenRight; - - result.accel.postfix_set_ops[1] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("[]")); - result.accel.postfix_set_flags[1] = MD_NodeFlag_HasBracketLeft | MD_NodeFlag_HasBracketRight; - - result.accel.postfix_set_ops[2] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("{}")); - result.accel.postfix_set_flags[2] = MD_NodeFlag_HasBraceLeft | MD_NodeFlag_HasBraceRight; - - result.accel.postfix_set_ops[3] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("[)")); - result.accel.postfix_set_flags[3] = MD_NodeFlag_HasBracketLeft | MD_NodeFlag_HasParenRight; - - result.accel.postfix_set_ops[4] = MD_ExprOprFromKindString(op_table, MD_ExprOprKind_Postfix, MD_S8Lit("(]")); - result.accel.postfix_set_flags[4] = MD_NodeFlag_HasParenLeft | MD_NodeFlag_HasBracketRight; - - return(result); -} - -MD_FUNCTION MD_Expr* -MD_ExprParse_TopLevel(MD_Arena *arena, MD_ExprParseCtx *ctx, MD_Node *first, MD_Node *opl) -{ - // parse the node range - MD_Node *iter = first; - MD_Expr *expr = MD_ExprParse_MinPrecedence(arena, ctx, &iter, first, opl, 0); - - // check for failed-to-reach-end error - if(ctx->errors.max_message_kind == MD_MessageKind_Null) - { - MD_Node *stop_node = iter; - if(!MD_NodeIsNil(stop_node)) - { - MD_String8 error_str = MD_S8Lit("Expected binary or unary postfix operator."); - MD_Message *error = MD_MakeNodeError(arena,stop_node,MD_MessageKind_FatalError,error_str); - MD_MessageListPush(&ctx->errors, error); - } - } - - return(expr); -} - -MD_FUNCTION MD_b32 -MD_ExprParse_OprConsume(MD_ExprParseCtx *ctx, MD_Node **iter, MD_Node *opl, - MD_ExprOprKind kind, MD_u32 min_precedence, MD_ExprOpr **op_out) -{ - MD_b32 result = 0; - MD_Node *node = *iter; - if(!MD_NodeIsNil(node)) - { - MD_ExprOpr *op = MD_ExprOprFromKindString(ctx->op_table, kind, node->string); - if(op != 0 && op->precedence >= min_precedence) - { - result = 1; - *op_out = op; - *iter= MD_NodeNextWithLimit(*iter, opl); - } - } - return result; -} - -MD_FUNCTION MD_Expr* -MD_ExprParse_Atom(MD_Arena *arena, MD_ExprParseCtx *ctx, MD_Node **iter, - MD_Node *first, MD_Node *opl) -{ - // TODO(allen): nil - MD_Expr* result = 0; - - MD_Node *node = *iter; - MD_ExprOpr *op = 0; - - if(MD_NodeIsNil(node)) - { - MD_Node *last = first; - for (;last->next != opl; last = last->next); - - MD_Node *error_node = last->next; - if (MD_NodeIsNil(error_node)) - { - MD_Node *root = MD_RootFromNode(node); - MD_String8 parse_contents = root->raw_string; - MD_u64 offset = last->offset + last->raw_string.size; - error_node = MD_MakeErrorMarkerNode(arena, parse_contents, offset); - } - - MD_String8 error_str = MD_S8Lit("Unexpected end of expression."); - MD_Message *error = MD_MakeNodeError(arena, error_node, MD_MessageKind_FatalError, - error_str); - MD_MessageListPush(&ctx->errors, error); - } - else if((node->flags & MD_NodeFlag_HasParenLeft) && - (node->flags & MD_NodeFlag_HasParenRight)) - { // NOTE(mal): Parens - *iter = MD_NodeNextWithLimit(*iter, opl); - result = MD_ExprParse_TopLevel(arena, ctx, node->first_child, MD_NilNode()); - } - else if(((node->flags & MD_NodeFlag_HasBraceLeft) && (node->flags & MD_NodeFlag_HasBraceRight)) || - ((node->flags & MD_NodeFlag_HasBracketLeft) && (node->flags & MD_NodeFlag_HasBracketRight)) || - ((node->flags & MD_NodeFlag_HasBracketLeft) && (node->flags & MD_NodeFlag_HasParenRight)) || - ((node->flags & MD_NodeFlag_HasParenLeft) && (node->flags & MD_NodeFlag_HasBracketRight))) - { // NOTE(mal): Unparsed leaf sets ({...}, [...], [...), (...]) - *iter = MD_NodeNextWithLimit(*iter, opl); - result = MD_Expr_NewLeaf(arena, node); - } - else if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Prefix, 1, &op)) - { - MD_u32 min_precedence = op->precedence + 1; - MD_Expr *sub_expr = - MD_ExprParse_MinPrecedence(arena, ctx, iter, first, opl, min_precedence); - if(ctx->errors.max_message_kind == MD_MessageKind_Null) - { - result = MD_Expr_NewOpr(arena, op, node, sub_expr, 0); - } - } - else if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Null, 1, &op)) - { - MD_String8 error_str = MD_S8Fmt(arena, "Expected leaf. Got operator \"%.*s\".", MD_S8VArg(node->string)); - - MD_Message *error = MD_MakeNodeError(arena, node, MD_MessageKind_FatalError, error_str); - MD_MessageListPush(&ctx->errors, error); - } - else if(node->flags & - (MD_NodeFlag_HasParenLeft|MD_NodeFlag_HasParenRight|MD_NodeFlag_HasBracketLeft| - MD_NodeFlag_HasBracketRight|MD_NodeFlag_HasBraceLeft|MD_NodeFlag_HasBraceRight)) - { - MD_String8 error_str = MD_S8Fmt(arena, "Unexpected set.", MD_S8VArg(node->string)); - MD_Message *error = MD_MakeNodeError(arena, node, MD_MessageKind_FatalError, error_str); - MD_MessageListPush(&ctx->errors, error); - } - else{ // NOTE(mal): leaf - *iter = MD_NodeNextWithLimit(*iter, opl); - result = MD_Expr_NewLeaf(arena, node); - } - - return(result); -} - -MD_FUNCTION MD_Expr* -MD_ExprParse_MinPrecedence(MD_Arena *arena, MD_ExprParseCtx *ctx, - MD_Node **iter, MD_Node *first, MD_Node *opl, - MD_u32 min_precedence) -{ - // TODO(allen): nil - MD_Expr* result = 0; - - result = MD_ExprParse_Atom(arena, ctx, iter, first, opl); - if(ctx->errors.max_message_kind == MD_MessageKind_Null) - { - for (;!MD_NodeIsNil(*iter);) - { - MD_Node *node = *iter; - MD_ExprOpr *op = 0; - - if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Binary, - min_precedence, &op) || - MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_BinaryRightAssociative, - min_precedence, &op)) - { - MD_u32 next_min_precedence = op->precedence + (op->kind == MD_ExprOprKind_Binary); - MD_Expr *sub_expr = - MD_ExprParse_MinPrecedence(arena, ctx, iter, first, opl, next_min_precedence); - if(ctx->errors.max_message_kind == MD_MessageKind_Null) - { - result = MD_Expr_NewOpr(arena, op, node, result, sub_expr); - } - else{ - break; - } - } - - else - { - MD_b32 found_postfix_setlike_operator = 0; - for(MD_u32 i_op = 0; - i_op < MD_ArrayCount(ctx->accel.postfix_set_ops); - ++i_op) - { - MD_ExprOpr *op = ctx->accel.postfix_set_ops[i_op]; - if(op && op->precedence >= min_precedence && - node->flags == ctx->accel.postfix_set_flags[i_op]) - { - *iter = MD_NodeNextWithLimit(*iter, opl); - result = MD_Expr_NewOpr(arena, op, node, result, 0); - found_postfix_setlike_operator = 1; - break; - } - } - - if(!found_postfix_setlike_operator) - { - if(MD_ExprParse_OprConsume(ctx, iter, opl, MD_ExprOprKind_Postfix, - min_precedence, &op)) - { - result = MD_Expr_NewOpr(arena, op, node, result, 0); - } - else - { - break; // NOTE: Due to lack of progress - } - } - - } - - } - } - - return(result); -} - - - - -//~ String Generation - -MD_FUNCTION void -MD_DebugDumpFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, - int indent, MD_String8 indent_string, MD_GenerateFlags flags) -{ -#define MD_PrintIndent(_indent_level) do\ -{\ -for(int i = 0; i < (_indent_level); i += 1)\ -{\ -MD_S8ListPush(arena, out, indent_string);\ -}\ -}while(0) - - //- rjf: prev-comment - if(flags & MD_GenerateFlag_Comments && node->prev_comment.size != 0) - { - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("/*\n")); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, node->prev_comment); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("*/\n")); - } - - //- rjf: tags of node - if(flags & MD_GenerateFlag_Tags) - { - for(MD_EachNode(tag, node->first_tag)) - { - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("@")); - MD_S8ListPush(arena, out, tag->string); - if(flags & MD_GenerateFlag_TagArguments && !MD_NodeIsNil(tag->first_child)) - { - int tag_arg_indent = indent + 1 + tag->string.size + 1; - MD_S8ListPush(arena, out, MD_S8Lit("(")); - for(MD_EachNode(child, tag->first_child)) - { - int child_indent = tag_arg_indent; - if(MD_NodeIsNil(child->prev)) - { - child_indent = 0; - } - MD_DebugDumpFromNode(arena, out, child, child_indent, MD_S8Lit(" "), flags); - if(!MD_NodeIsNil(child->next)) - { - MD_S8ListPush(arena, out, MD_S8Lit(",\n")); - } - } - MD_S8ListPush(arena, out, MD_S8Lit(")\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - } - } - } - - //- rjf: node kind - if(flags & MD_GenerateFlag_NodeKind) - { - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("// kind: \"")); - MD_S8ListPush(arena, out, MD_StringFromNodeKind(node->kind)); - MD_S8ListPush(arena, out, MD_S8Lit("\"\n")); - } - - //- rjf: node flags - if(flags & MD_GenerateFlag_NodeFlags) - { - MD_PrintIndent(indent); - MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); - MD_String8List flag_strs = MD_StringListFromNodeFlags(scratch.arena, node->flags); - MD_StringJoin join = { MD_S8Lit(""), MD_S8Lit("|"), MD_S8Lit("") }; - MD_String8 flag_str = MD_S8ListJoin(arena, flag_strs, &join); - MD_S8ListPush(arena, out, MD_S8Lit("// flags: \"")); - MD_S8ListPush(arena, out, flag_str); - MD_S8ListPush(arena, out, MD_S8Lit("\"\n")); - MD_ReleaseScratch(scratch); - } - - //- rjf: location - if(flags & MD_GenerateFlag_Location) - { - MD_PrintIndent(indent); - MD_CodeLoc loc = MD_CodeLocFromNode(node); - MD_String8 string = MD_S8Fmt(arena, "// location: %.*s:%i:%i\n", MD_S8VArg(loc.filename), (int)loc.line, (int)loc.column); - MD_S8ListPush(arena, out, string); - } - - //- rjf: name of node - if(node->string.size != 0) - { - MD_PrintIndent(indent); - if(node->kind == MD_NodeKind_File) - { - MD_S8ListPush(arena, out, MD_S8Lit("`")); - MD_S8ListPush(arena, out, node->string); - MD_S8ListPush(arena, out, MD_S8Lit("`")); - } - else - { - MD_S8ListPush(arena, out, node->raw_string); - } - } - - //- rjf: children list - if(flags & MD_GenerateFlag_Children && !MD_NodeIsNil(node->first_child)) - { - if(node->string.size != 0) - { - MD_S8ListPush(arena, out, MD_S8Lit(":\n")); - } - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("{\n")); - for(MD_EachNode(child, node->first_child)) - { - MD_DebugDumpFromNode(arena, out, child, indent+1, indent_string, flags); - MD_S8ListPush(arena, out, MD_S8Lit(",\n")); - } - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("}")); - } - - //- rjf: next-comment - if(flags & MD_GenerateFlag_Comments && node->next_comment.size != 0) - { - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("\n/*\n")); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, node->next_comment); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("*/\n")); - } - -#undef MD_PrintIndent -} - -MD_FUNCTION void -MD_ReconstructionFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, - int indent, MD_String8 indent_string) -{ - MD_CodeLoc code_loc = MD_CodeLocFromNode(node); - -#define MD_PrintIndent(_indent_level) do\ -{\ -for(int i = 0; i < (_indent_level); i += 1)\ -{\ -MD_S8ListPush(arena, out, indent_string);\ -}\ -}while(0) - - //- rjf: prev-comment - if(node->prev_comment.size != 0) - { - MD_String8 comment = MD_S8SkipWhitespace(MD_S8ChopWhitespace(node->prev_comment)); - MD_b32 requires_multiline = MD_S8FindSubstring(comment, MD_S8Lit("\n"), 0, 0) < comment.size; - MD_PrintIndent(indent); - if(requires_multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("/*\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("// ")); - } - MD_S8ListPush(arena, out, comment); - if(requires_multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n*/\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - } - } - - //- rjf: tags of node - MD_u32 tag_first_line = MD_CodeLocFromNode(node->first_tag).line; - MD_u32 tag_last_line = tag_first_line; - { - for(MD_EachNode(tag, node->first_tag)) - { - MD_u32 tag_line = MD_CodeLocFromNode(tag).line; - if(tag_line != tag_last_line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - tag_last_line = tag_line; - } - else if(!MD_NodeIsNil(tag->prev)) - { - MD_S8ListPush(arena, out, MD_S8Lit(" ")); - } - - MD_PrintIndent(indent); - MD_S8ListPush(arena, out, MD_S8Lit("@")); - MD_S8ListPush(arena, out, tag->string); - if(!MD_NodeIsNil(tag->first_child)) - { - int tag_arg_indent = indent + 1 + tag->string.size + 1; - MD_S8ListPush(arena, out, MD_S8Lit("(")); - MD_u32 last_line = MD_CodeLocFromNode(tag).line; - for(MD_EachNode(child, tag->first_child)) - { - MD_CodeLoc child_loc = MD_CodeLocFromNode(child); - if(child_loc.line != last_line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - } - last_line = child_loc.line; - - int child_indent = tag_arg_indent; - if(MD_NodeIsNil(child->prev)) - { - child_indent = 0; - } - MD_ReconstructionFromNode(arena, out, child, child_indent, MD_S8Lit(" ")); - if(!MD_NodeIsNil(child->next)) - { - MD_S8ListPush(arena, out, MD_S8Lit(",\n")); - } - } - MD_S8ListPush(arena, out, MD_S8Lit(")")); - } - } - } - - //- rjf: name of node - if(node->string.size != 0) - { - if(tag_first_line != tag_last_line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - } - else if(!MD_NodeIsNil(node->first_tag) || !MD_NodeIsNil(node->prev)) - { - MD_S8ListPush(arena, out, MD_S8Lit(" ")); - } - if(node->kind == MD_NodeKind_File) - { - MD_S8ListPush(arena, out, MD_S8Lit("`")); - MD_S8ListPush(arena, out, node->string); - MD_S8ListPush(arena, out, MD_S8Lit("`")); - } - else - { - MD_S8ListPush(arena, out, node->raw_string); - } - } - - //- rjf: children list - if(!MD_NodeIsNil(node->first_child)) - { - if(node->string.size != 0) - { - MD_S8ListPush(arena, out, MD_S8Lit(":")); - } - - // rjf: figure out opener/closer symbols - MD_u8 opener_char = 0; - MD_u8 closer_char = 0; - if(node->flags & MD_NodeFlag_HasParenLeft) { opener_char = '('; } - else if(node->flags & MD_NodeFlag_HasBracketLeft) { opener_char = '['; } - else if(node->flags & MD_NodeFlag_HasBraceLeft) { opener_char = '{'; } - if(node->flags & MD_NodeFlag_HasParenRight) { closer_char = ')'; } - else if(node->flags & MD_NodeFlag_HasBracketRight){ closer_char = ']'; } - else if(node->flags & MD_NodeFlag_HasBraceRight) { closer_char = '}'; } - - MD_b32 multiline = 0; - for(MD_EachNode(child, node->first_child)) - { - MD_CodeLoc child_loc = MD_CodeLocFromNode(child); - if(child_loc.line != code_loc.line) - { - multiline = 1; - break; - } - } - - if(opener_char != 0) - { - if(multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit(" ")); - } - MD_S8ListPush(arena, out, MD_S8(&opener_char, 1)); - if(multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent+1); - } - } - MD_u32 last_line = MD_CodeLocFromNode(node->first_child).line; - for(MD_EachNode(child, node->first_child)) - { - int child_indent = 0; - MD_CodeLoc child_loc = MD_CodeLocFromNode(child); - if(child_loc.line != last_line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - child_indent = indent+1; - } - last_line = child_loc.line; - MD_ReconstructionFromNode(arena, out, child, child_indent, indent_string); - } - MD_PrintIndent(indent); - if(closer_char != 0) - { - if(last_line != code_loc.line) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - MD_PrintIndent(indent); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit(" ")); - } - MD_S8ListPush(arena, out, MD_S8(&closer_char, 1)); - } - } - - //- rjf: trailing separator symbols - if(node->flags & MD_NodeFlag_IsBeforeSemicolon) - { - MD_S8ListPush(arena, out, MD_S8Lit(";")); - } - else if(node->flags & MD_NodeFlag_IsBeforeComma) - { - MD_S8ListPush(arena, out, MD_S8Lit(",")); - } - - //- rjf: next-comment - // TODO(rjf): @node_comments - if(node->next_comment.size != 0) - { - MD_String8 comment = MD_S8SkipWhitespace(MD_S8ChopWhitespace(node->next_comment)); - MD_b32 requires_multiline = MD_S8FindSubstring(comment, MD_S8Lit("\n"), 0, 0) < comment.size; - MD_PrintIndent(indent); - if(requires_multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("/*\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("// ")); - } - MD_S8ListPush(arena, out, comment); - if(requires_multiline) - { - MD_S8ListPush(arena, out, MD_S8Lit("\n*/\n")); - } - else - { - MD_S8ListPush(arena, out, MD_S8Lit("\n")); - } - } - -#undef MD_PrintIndent -} - - -#if !MD_DISABLE_PRINT_HELPERS -MD_FUNCTION void -MD_PrintDebugDumpFromNode(FILE *file, MD_Node *node, MD_GenerateFlags flags) -{ - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - MD_String8List list = {0}; - MD_DebugDumpFromNode(scratch.arena, &list, node, - 0, MD_S8Lit(" "), flags); - MD_String8 string = MD_S8ListJoin(scratch.arena, list, 0); - fwrite(string.str, string.size, 1, file); - MD_ReleaseScratch(scratch); -} -#endif - - -//~ Command Line Argument Helper - -MD_FUNCTION MD_String8List -MD_StringListFromArgCV(MD_Arena *arena, int argument_count, char **arguments) -{ - MD_String8List options = MD_ZERO_STRUCT; - for(int i = 1; i < argument_count; i += 1) - { - MD_S8ListPush(arena, &options, MD_S8CString(arguments[i])); - } - return options; -} - -MD_FUNCTION MD_CmdLine -MD_MakeCmdLineFromOptions(MD_Arena *arena, MD_String8List options) -{ - MD_CmdLine cmdln = MD_ZERO_STRUCT; - MD_b32 parsing_only_inputs = 0; - - for(MD_String8Node *n = options.first, *next = 0; - n; n = next) - { - next = n->next; - - //- rjf: figure out whether or not this is an option by checking for `-` or `--` - // from the beginning of the string - MD_String8 option_name = MD_ZERO_STRUCT; - if(MD_S8Match(n->string, MD_S8Lit("--"), 0)) - { - parsing_only_inputs = 1; - } - else if(MD_S8Match(MD_S8Prefix(n->string, 2), MD_S8Lit("--"), 0)) - { - option_name = MD_S8Skip(n->string, 2); - } - else if(MD_S8Match(MD_S8Prefix(n->string, 1), MD_S8Lit("-"), 0)) - { - option_name = MD_S8Skip(n->string, 1); - } - - //- rjf: trim off anything after a `:` or `=`, use that as the first value string - MD_String8 first_value = MD_ZERO_STRUCT; - MD_b32 has_many_values = 0; - if(option_name.size != 0) - { - MD_u64 colon_signifier_pos = MD_S8FindSubstring(option_name, MD_S8Lit(":"), 0, 0); - MD_u64 equal_signifier_pos = MD_S8FindSubstring(option_name, MD_S8Lit("="), 0, 0); - MD_u64 signifier_pos = MD_Min(colon_signifier_pos, equal_signifier_pos); - if(signifier_pos < option_name.size) - { - first_value = MD_S8Skip(option_name, signifier_pos+1); - option_name = MD_S8Prefix(option_name, signifier_pos); - if(MD_S8Match(MD_S8Suffix(first_value, 1), MD_S8Lit(","), 0)) - { - has_many_values = 1; - } - } - } - - //- rjf: gather arguments - if(option_name.size != 0 && !parsing_only_inputs) - { - MD_String8List option_values = MD_ZERO_STRUCT; - - //- rjf: push first value - if(first_value.size != 0) - { - MD_S8ListPush(arena, &option_values, first_value); - } - - //- rjf: scan next string values, add them to option values until we hit a lack - // of a ',' between values - if(has_many_values) - { - for(MD_String8Node *v = next; v; v = v->next, next = v) - { - MD_String8 value_str = v->string; - MD_b32 next_has_arguments = MD_S8Match(MD_S8Suffix(value_str, 1), MD_S8Lit(","), 0); - MD_b32 in_quotes = 0; - MD_u64 start = 0; - for(MD_u64 i = 0; i <= value_str.size; i += 1) - { - if(i == value_str.size || (value_str.str[i] == ',' && in_quotes == 0)) - { - if(start != i) - { - MD_S8ListPush(arena, &option_values, MD_S8Substring(value_str, start, i)); - } - start = i+1; - } - else if(value_str.str[i] == '"') - { - in_quotes = !in_quotes; - } - } - if(next_has_arguments == 0) - { - break; - } - } - } - - //- rjf: insert the fully parsed option - { - MD_CmdLineOption *opt = MD_PushArrayZero(arena, MD_CmdLineOption, 1); - opt->name = option_name; - opt->values = option_values; - if(cmdln.last_option == 0) - { - cmdln.first_option = cmdln.last_option = opt; - } - else - { - cmdln.last_option->next = opt; - cmdln.last_option = cmdln.last_option->next; - } - } - } - - //- rjf: this argument is not an option, push it to regular inputs list. - else - { - MD_S8ListPush(arena, &cmdln.inputs, n->string); - } - } - - return cmdln; -} - -MD_FUNCTION MD_String8List -MD_CmdLineValuesFromString(MD_CmdLine cmdln, MD_String8 name) -{ - MD_String8List values = MD_ZERO_STRUCT; - for(MD_CmdLineOption *opt = cmdln.first_option; opt; opt = opt->next) - { - if(MD_S8Match(opt->name, name, 0)) - { - values = opt->values; - break; - } - } - return values; -} - -MD_FUNCTION MD_b32 -MD_CmdLineB32FromString(MD_CmdLine cmdln, MD_String8 name) -{ - MD_b32 result = 0; - for(MD_CmdLineOption *opt = cmdln.first_option; opt; opt = opt->next) - { - if(MD_S8Match(opt->name, name, 0)) - { - result = 1; - break; - } - } - return result; -} - -MD_FUNCTION MD_i64 -MD_CmdLineI64FromString(MD_CmdLine cmdln, MD_String8 name) -{ - MD_String8List values = MD_CmdLineValuesFromString(cmdln, name); - MD_ArenaTemp scratch = MD_GetScratch(0, 0); - MD_String8 value_str = MD_S8ListJoin(scratch.arena, values, 0); - MD_i64 result = MD_CStyleIntFromString(value_str); - MD_ReleaseScratch(scratch); - return(result); -} - -//~ File System - -MD_FUNCTION MD_String8 -MD_LoadEntireFile(MD_Arena *arena, MD_String8 filename) -{ - MD_String8 result = MD_ZERO_STRUCT; -#if defined(MD_IMPL_LoadEntireFile) - result = MD_IMPL_LoadEntireFile(arena, filename); -#endif - return(result); -} - -MD_FUNCTION MD_b32 -MD_FileIterBegin(MD_FileIter *it, MD_String8 path) -{ -#if !defined(MD_IMPL_FileIterBegin) - return(0); -#else - return(MD_IMPL_FileIterBegin(it, path)); -#endif -} - -MD_FUNCTION MD_FileInfo -MD_FileIterNext(MD_Arena *arena, MD_FileIter *it) -{ -#if !defined(MD_IMPL_FileIterNext) - MD_FileInfo result = {0}; - return(result); -#else - return(MD_IMPL_FileIterNext(arena, it)); -#endif -} - -MD_FUNCTION void -MD_FileIterEnd(MD_FileIter *it) -{ -#if defined(MD_IMPL_FileIterEnd) - MD_IMPL_FileIterEnd(it); -#endif -} - -#endif // MD_C - -/* -Copyright 2021 Dion Systems LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ diff --git a/src/metagen/metadesk_base/md.h b/src/metagen/metadesk_base/md.h deleted file mode 100644 index a7da6f5..0000000 --- a/src/metagen/metadesk_base/md.h +++ /dev/null @@ -1,1248 +0,0 @@ -// LICENSE AT END OF FILE (MIT). - -/* -** Weclome to Metadesk! -** -** Metadesk is a data description language designed to look like a programming -** language, and this is the accompanying parser library. While you are free to -** use it however you see fit, here are a couple of the uses we have intended -** to support: -** + quickly writing a C or C++ metaprogram from scratch -** + building "low budget" domain specific languages, such as marked-up -** webpage content, or asset metadata -** + creating robust and flexible config systems for applications -** -** If it's your first time with Metadesk, check out the "How to Build" section -** below, and consider looking at the examples included with the library. The -** examples_directory.txt will help you find your way from the intro examples -** through all the more advanced aspects of the library you might like to -** learn about. -** -** Direct issues, questions, suggestions, requests, etc to: -** https://github.com/Dion-Systems/metadesk -** -** -** How To Build: -** -** The library is set up as a direct source-include library, so if you have a -** single unit build you can just #include "md.h" and "md.c". If you have a -** multiple unit build you can #include "md.h" where necessary and add "md.c" -** as a separate compilation unit (extra care has to be taken if you intend to -** use overrides in a multiple unit build). -** -** See `bin/compile_flags.txt` for the flags to build with. -** -** The tests and examples can be built with the bash scripts in bin. There are -** a few things to know to use these scripts: -** 1. First you should run `bld_init.sh` which will initialize your copy of -** Metadesk's build system. -** 2. On Linux the shell scripts should work as written. On Windows you will -** need to use a bash interpreter specifically. Generally the `bash.exe` -** that comes with an install of git on Windows works well for this. -** Add it to your path or setup a batch script that calls it and then -** pass the bash scripts to the interpreter to build. -** 3. You should be able to run the scripts: -** `build_tests.sh` -** `build_examples.sh` -** `run_tests.sh` -** `run_examples.sh` -** `type_metadata_example.sh` -*/ - -#ifndef MD_H -#define MD_H - -#define MD_VERSION_MAJ 1 -#define MD_VERSION_MIN 0 - -//~ Set default values for controls -#if !defined(MD_DEFAULT_BASIC_TYPES) -# define MD_DEFAULT_BASIC_TYPES 1 -#endif -#if !defined(MD_DEFAULT_MEMSET) -# define MD_DEFAULT_MEMSET 1 -#endif -#if !defined(MD_DEFAULT_FILE_LOAD) -# define MD_DEFAULT_FILE_LOAD 1 -#endif -#if !defined(MD_DEFAULT_FILE_ITER) -# define MD_DEFAULT_FILE_ITER 1 -#endif -#if !defined(MD_DEFAULT_MEMORY) -# define MD_DEFAULT_MEMORY 1 -#endif -#if !defined(MD_DEFAULT_ARENA) -# define MD_DEFAULT_ARENA 1 -#endif -#if !defined(MD_DEFAULT_SCRATCH) -# define MD_DEFAULT_SCRATCH 1 -#endif -#if !defined(MD_DEFAULT_SPRINTF) -# define MD_DEFAULT_SPRINTF 1 -#endif - -#if !defined(MD_DISABLE_PRINT_HELPERS) -# define MD_DISABLE_PRINT_HELPERS 0 -#endif - - -//~///////////////////////////////////////////////////////////////////////////// -////////////////////////////// Context Cracking //////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -#if defined(__clang__) - -# define MD_COMPILER_CLANG 1 - -# if defined(__APPLE__) && defined(__MACH__) -# define MD_OS_MAC 1 -# elif defined(__gnu_linux__) -# define MD_OS_LINUX 1 -# elif defined(_WIN32) -# define MD_OS_WINDOWS 1 -# else -# error This compiler/platform combo is not supported yet -# endif - -# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) -# define MD_ARCH_X64 1 -# elif defined(i386) || defined(__i386) || defined(__i386__) -# define MD_ARCH_X86 1 -# elif defined(__aarch64__) -# define MD_ARCH_ARM64 1 -# elif defined(__arm__) -# define MD_ARCH_ARM32 1 -# else -# error architecture not supported yet -# endif - -#elif defined(_MSC_VER) - -# define MD_COMPILER_CL 1 - -# if defined(_WIN32) -# define MD_OS_WINDOWS 1 -# else -# error This compiler/platform combo is not supported yet -# endif - -# if defined(_M_AMD64) -# define MD_ARCH_X64 1 -# elif defined(_M_IX86) -# define MD_ARCH_X86 1 -# elif defined(_M_ARM64) -# define MD_ARCH_ARM64 1 -# elif defined(_M_ARM) -# define MD_ARCH_ARM32 1 -# else -# error architecture not supported yet -# endif - -# if _MSC_VER >= 1920 -# define MD_COMPILER_CL_YEAR 2019 -# elif _MSC_VER >= 1910 -# define MD_COMPILER_CL_YEAR 2017 -# elif _MSC_VER >= 1900 -# define MD_COMPILER_CL_YEAR 2015 -# elif _MSC_VER >= 1800 -# define MD_COMPILER_CL_YEAR 2013 -# elif _MSC_VER >= 1700 -# define MD_COMPILER_CL_YEAR 2012 -# elif _MSC_VER >= 1600 -# define MD_COMPILER_CL_YEAR 2010 -# elif _MSC_VER >= 1500 -# define MD_COMPILER_CL_YEAR 2008 -# elif _MSC_VER >= 1400 -# define MD_COMPILER_CL_YEAR 2005 -# else -# define MD_COMPILER_CL_YEAR 0 -# endif - -#elif defined(__GNUC__) || defined(__GNUG__) - -# define MD_COMPILER_GCC 1 - -# if defined(__gnu_linux__) -# define MD_OS_LINUX 1 -# else -# error This compiler/platform combo is not supported yet -# endif - -# if defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) -# define MD_ARCH_X64 1 -# elif defined(i386) || defined(__i386) || defined(__i386__) -# define MD_ARCH_X86 1 -# elif defined(__aarch64__) -# define MD_ARCH_ARM64 1 -# elif defined(__arm__) -# define MD_ARCH_ARM32 1 -# else -# error architecture not supported yet -# endif - -#else -# error This compiler is not supported yet -#endif - -#if defined(MD_ARCH_X64) -# define MD_ARCH_64BIT 1 -#elif defined(MD_ARCH_X86) -# define MD_ARCH_32BIT 1 -#endif - -#if defined(__cplusplus) -# define MD_LANG_CPP 1 - -// We can't get this 100% correct thanks to Microsoft's compiler. -// So this check lets us pre-define MD_CPP_VERSION if we have to. -# if !defined(MD_CPP_VERSION) -# if defined(MD_COMPILER_CL) -// CL is annoying and didn't update __cplusplus over time -// If it is available _MSVC_LANG serves the same role -# if defined(_MSVC_LANG) -# if _MSVC_LANG <= 199711L -# define MD_CPP_VERSION 98 -# elif _MSVC_LANG <= 201103L -# define MD_CPP_VERSION 11 -# elif _MSVC_LANG <= 201402L -# define MD_CPP_VERSION 14 -# elif _MSVC_LANG <= 201703L -# define MD_CPP_VERSION 17 -# elif _MSVC_LANG <= 202002L -# define MD_CPP_VERSION 20 -# else -# define MD_CPP_VERSION 23 -# endif -// If we don't have _MSVC_LANG we can guess from the compiler version -# else -# if MD_COMPILER_CL_YEAR <= 2010 -# define MD_CPP_VERSION 98 -# elif MD_COMPILER_CL_YEAR <= 2015 -# define MD_CPP_VERSION 11 -# else -# define MD_CPP_VERSION 17 -# endif -# endif -# else -// Other compilers use __cplusplus correctly -# if __cplusplus <= 199711L -# define MD_CPP_VERSION 98 -# elif __cplusplus <= 201103L -# define MD_CPP_VERSION 11 -# elif __cplusplus <= 201402L -# define MD_CPP_VERSION 14 -# elif __cplusplus <= 201703L -# define MD_CPP_VERSION 17 -# elif __cplusplus <= 202002L -# define MD_CPP_VERSION 20 -# else -# define MD_CPP_VERSION 23 -# endif -# endif -# endif - -#else -# define MD_LANG_C 1 -#endif - -// zeroify - -#if !defined(MD_ARCH_32BIT) -# define MD_ARCH_32BIT 0 -#endif -#if !defined(MD_ARCH_64BIT) -# define MD_ARCH_64BIT 0 -#endif -#if !defined(MD_ARCH_X64) -# define MD_ARCH_X64 0 -#endif -#if !defined(MD_ARCH_X86) -# define MD_ARCH_X86 0 -#endif -#if !defined(MD_ARCH_ARM64) -# define MD_ARCH_ARM64 0 -#endif -#if !defined(MD_ARCH_ARM32) -# define MD_ARCH_ARM32 0 -#endif -#if !defined(MD_COMPILER_CL) -# define MD_COMPILER_CL 0 -#endif -#if !defined(MD_COMPILER_GCC) -# define MD_COMPILER_GCC 0 -#endif -#if !defined(MD_COMPILER_CLANG) -# define MD_COMPILER_CLANG 0 -#endif -#if !defined(MD_OS_WINDOWS) -# define MD_OS_WINDOWS 0 -#endif -#if !defined(MD_OS_LINUX) -# define MD_OS_LINUX 0 -#endif -#if !defined(MD_OS_MAC) -# define MD_OS_MAC 0 -#endif -#if !defined(MD_LANG_C) -# define MD_LANG_C 0 -#endif -#if !defined(MD_LANG_CPP) -# define MD_LANG_CPP 0 -#endif -#if !defined(MD_CPP_VERSION) -# define MD_CPP_VERSION 0 -#endif - -#if MD_LANG_CPP -# define MD_ZERO_STRUCT {} -#else -# define MD_ZERO_STRUCT {0} -#endif - -#if MD_LANG_C -# define MD_C_LINKAGE_BEGIN -# define MD_C_LINKAGE_END -#else -# define MD_C_LINKAGE_BEGIN extern "C"{ -# define MD_C_LINKAGE_END } -#endif - -#if MD_COMPILER_CL -# define MD_THREAD_LOCAL __declspec(thread) -#elif MD_COMPILER_GCC || MD_COMPILER_CLANG -# define MD_THREAD_LOCAL __thread -#endif - -//~///////////////////////////////////////////////////////////////////////////// -///////////////////////////// Helpers, Macros, Etc ///////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//~ Linkage Wrappers - -#if !defined(MD_FUNCTION) -# define MD_FUNCTION -#endif - -#if !defined(MD_GLOBAL) -# define MD_GLOBAL static -#endif - -//~ Basic Utilities - -#define MD_Assert(c) if (!(c)) { *(volatile MD_u64 *)0 = 0; } -#define MD_StaticAssert(c,label) MD_u8 MD_static_assert_##label[(c)?(1):(-1)] -#define MD_ArrayCount(a) (sizeof(a) / sizeof((a)[0])) - -#define MD_Min(a,b) (((a)<(b))?(a):(b)) -#define MD_Max(a,b) (((a)>(b))?(a):(b)) -#define MD_ClampBot(a,b) MD_Max(a,b) -#define MD_ClampTop(a,b) MD_Min(a,b) - -#define MD_AlignPow2(x,b) (((x)+((b)-1))&(~((b)-1))) - -//~ Linked List Macros - -// terminator modes -#define MD_CheckNull(p) ((p)==0) -#define MD_SetNull(p) ((p)=0) -#define MD_CheckNil(p) (MD_NodeIsNil(p)) -#define MD_SetNil(p) ((p)=MD_NilNode()) - -// implementations -#define MD_QueuePush_NZ(f,l,n,next,zchk,zset) (zchk(f)?\ -(f)=(l)=(n):\ -((l)->next=(n),(l)=(n),zset((n)->next))) -#define MD_QueuePop_NZ(f,l,next,zset) ((f)==(l)?\ -(zset(f),zset(l)):\ -((f)=(f)->next)) -#define MD_StackPush_N(f,n,next) ((n)->next=(f),(f)=(n)) -#define MD_StackPop_NZ(f,next,zchk) (zchk(f)?0:(f)=(f)->next) - -#define MD_DblPushBack_NPZ(f,l,n,next,prev,zchk,zset) \ -(zchk(f)?\ -((f)=(l)=(n),zset((n)->next),zset((n)->prev)):\ -((n)->prev=(l),(l)->next=(n),(l)=(n),zset((n)->next))) -#define MD_DblRemove_NPZ(f,l,n,next,prev,zset) (((f)==(n)?\ -((f)=(f)->next,zset((f)->prev)):\ -(l)==(n)?\ -((l)=(l)->prev,zset((l)->next)):\ -((n)->next->prev=(n)->prev,\ -(n)->prev->next=(n)->next))) - -// compositions -#define MD_QueuePush(f,l,n) MD_QueuePush_NZ(f,l,n,next,MD_CheckNull,MD_SetNull) -#define MD_QueuePop(f,l) MD_QueuePop_NZ(f,l,next,MD_SetNull) -#define MD_StackPush(f,n) MD_StackPush_N(f,n,next) -#define MD_StackPop(f) MD_StackPop_NZ(f,next,MD_CheckNull) -#define MD_DblPushBack(f,l,n) MD_DblPushBack_NPZ(f,l,n,next,prev,MD_CheckNull,MD_SetNull) -#define MD_DblPushFront(f,l,n) MD_DblPushBack_NPZ(l,f,n,prev,next,MD_CheckNull,MD_SetNull) -#define MD_DblRemove(f,l,n) MD_DblRemove_NPZ(f,l,n,next,prev,MD_SetNull) - -#define MD_NodeDblPushBack(f,l,n) MD_DblPushBack_NPZ(f,l,n,next,prev,MD_CheckNil,MD_SetNil) -#define MD_NodeDblPushFront(f,l,n) MD_DblPushBack_NPZ(l,f,n,prev,next,MD_CheckNil,MD_SetNil) -#define MD_NodeDblRemove(f,l,n) MD_DblRemove_NPZ(f,l,n,next,prev,MD_SetNil) - - -//~ Memory Operations - -#define MD_MemorySet(p,v,z) (MD_IMPL_Memset(p,v,z)) -#define MD_MemoryZero(p,z) (MD_IMPL_Memset(p,0,z)) -#define MD_MemoryZeroStruct(p) (MD_IMPL_Memset(p,0,sizeof(*(p)))) -#define MD_MemoryCopy(d,s,z) (MD_IMPL_Memmove(d,s,z)) - -//~ sprintf -#if MD_DEFAULT_SPRINTF -#define STB_SPRINTF_DECORATE(name) md_stbsp_##name -#define MD_IMPL_Vsnprintf md_stbsp_vsnprintf -#include "md_stb_sprintf.h" -#endif - -//~///////////////////////////////////////////////////////////////////////////// -//////////////////////////////////// Types ///////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//~ Basic Types - -#include - -#if defined(MD_DEFAULT_BASIC_TYPES) - -#include -typedef int8_t MD_i8; -typedef int16_t MD_i16; -typedef int32_t MD_i32; -typedef int64_t MD_i64; -typedef uint8_t MD_u8; -typedef uint16_t MD_u16; -typedef uint32_t MD_u32; -typedef uint64_t MD_u64; -typedef float MD_f32; -typedef double MD_f64; - -#endif - -typedef MD_i8 MD_b8; -typedef MD_i16 MD_b16; -typedef MD_i32 MD_b32; -typedef MD_i64 MD_b64; - -//~ Default Arena - -#if MD_DEFAULT_ARENA - -typedef struct MD_ArenaDefault MD_ArenaDefault; -struct MD_ArenaDefault -{ - MD_ArenaDefault *prev; - MD_ArenaDefault *current; - MD_u64 base_pos; - MD_u64 pos; - MD_u64 cmt; - MD_u64 cap; - MD_u64 align; -}; -#define MD_IMPL_Arena MD_ArenaDefault - -#endif - -//~ Abstract Arena - -#if !defined(MD_IMPL_Arena) -# error Missing implementation for MD_IMPL_Arena -#endif - -typedef MD_IMPL_Arena MD_Arena; - -//~ Arena Helpers - -typedef struct MD_ArenaTemp MD_ArenaTemp; -struct MD_ArenaTemp -{ - MD_Arena *arena; - MD_u64 pos; -}; - -//~ Basic Unicode string types. - -typedef struct MD_String8 MD_String8; -struct MD_String8 -{ - MD_u8 *str; - MD_u64 size; -}; - -typedef struct MD_String16 MD_String16; -struct MD_String16 -{ - MD_u16 *str; - MD_u64 size; -}; - -typedef struct MD_String32 MD_String32; -struct MD_String32 -{ - MD_u32 *str; - MD_u64 size; -}; - -typedef struct MD_String8Node MD_String8Node; -struct MD_String8Node -{ - MD_String8Node *next; - MD_String8 string; -}; - -typedef struct MD_String8List MD_String8List; -struct MD_String8List -{ - MD_u64 node_count; - MD_u64 total_size; - MD_String8Node *first; - MD_String8Node *last; -}; - -typedef struct MD_StringJoin MD_StringJoin; -struct MD_StringJoin -{ - MD_String8 pre; - MD_String8 mid; - MD_String8 post; -}; - -// NOTE(rjf): @maintenance These three flag types must not overlap. -typedef MD_u32 MD_MatchFlags; -typedef MD_u32 MD_StringMatchFlags; -typedef MD_u32 MD_NodeMatchFlags; -enum -{ - MD_MatchFlag_FindLast = (1<<0), -}; -enum -{ - MD_StringMatchFlag_CaseInsensitive = (1<<4), - MD_StringMatchFlag_RightSideSloppy = (1<<5), - MD_StringMatchFlag_SlashInsensitive = (1<<6), -}; -enum -{ - MD_NodeMatchFlag_Tags = (1<<16), - MD_NodeMatchFlag_TagArguments = (1<<17), - MD_NodeMatchFlag_NodeFlags = (1<<18), -}; - -typedef struct MD_DecodedCodepoint MD_DecodedCodepoint; -struct MD_DecodedCodepoint -{ - MD_u32 codepoint; - MD_u32 advance; -}; - -typedef enum MD_IdentifierStyle -{ - MD_IdentifierStyle_UpperCamelCase, - MD_IdentifierStyle_LowerCamelCase, - MD_IdentifierStyle_UpperCase, - MD_IdentifierStyle_LowerCase, -} -MD_IdentifierStyle; - -//~ Node types that are used to build all ASTs. - -typedef enum MD_NodeKind -{ - // NOTE(rjf): @maintenance Must be kept in sync with MD_StringFromNodeKind. - - MD_NodeKind_Nil, - - // NOTE(rjf): Generated by parser - MD_NodeKind_File, - MD_NodeKind_ErrorMarker, - - // NOTE(rjf): Parsed from user Metadesk code - MD_NodeKind_Main, - MD_NodeKind_Tag, - - // NOTE(rjf): User-created data structures - MD_NodeKind_List, - MD_NodeKind_Reference, - - MD_NodeKind_COUNT, -} -MD_NodeKind; - -typedef MD_u64 MD_NodeFlags; -#define MD_NodeFlag_AfterFromBefore(f) ((f) << 1) -enum -{ - // NOTE(rjf): @maintenance Must be kept in sync with MD_StringListFromNodeFlags. - - // NOTE(rjf): @maintenance Because of MD_NodeFlag_AfterFromBefore, it is - // *required* that every single pair of "Before*" or "After*" flags be in - // the correct order which is that the Before* flag comes first, and the - // After* flag comes immediately after (After* being the more significant - // bit). - - 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_MaskSetDelimiters = (0x3F<<0), - - MD_NodeFlag_IsBeforeSemicolon = (1<<6), - MD_NodeFlag_IsAfterSemicolon = (1<<7), - MD_NodeFlag_IsBeforeComma = (1<<8), - MD_NodeFlag_IsAfterComma = (1<<9), - - MD_NodeFlag_MaskSeperators = (0xF<<6), - - MD_NodeFlag_StringSingleQuote = (1<<10), - MD_NodeFlag_StringDoubleQuote = (1<<11), - MD_NodeFlag_StringTick = (1<<12), - MD_NodeFlag_StringTriplet = (1<<13), - - MD_NodeFlag_MaskStringDelimiters = (0xF<<10), - - MD_NodeFlag_Numeric = (1<<14), - MD_NodeFlag_Identifier = (1<<15), - MD_NodeFlag_StringLiteral = (1<<16), - MD_NodeFlag_Symbol = (1<<17), - - MD_NodeFlag_MaskLabelKind = (0xF<<14), -}; - -typedef struct MD_Node MD_Node; -struct MD_Node -{ - // Tree relationship data. - MD_Node *next; - MD_Node *prev; - MD_Node *parent; - MD_Node *first_child; - MD_Node *last_child; - - // Tag list. - MD_Node *first_tag; - MD_Node *last_tag; - - // Node info. - MD_NodeKind kind; - MD_NodeFlags flags; - MD_String8 string; - MD_String8 raw_string; - - // Source code location information. - MD_u64 offset; - - // Reference. - MD_Node *ref_target; - - // Comments. - // @usage prev_comment/next_comment should be considered "hidden". Rely on - // the functions MD_PrevCommentFromNode/MD_NextCommentFromNode to access - // these. Directly access to these is likely to break in a future version. - MD_String8 prev_comment; - MD_String8 next_comment; -}; - -//~ Code Location Info. - -typedef struct MD_CodeLoc MD_CodeLoc; -struct MD_CodeLoc -{ - MD_String8 filename; - MD_u32 line; - MD_u32 column; -}; - -//~ String-To-Ptr and Ptr-To-Ptr tables - -typedef struct MD_MapKey MD_MapKey; -struct MD_MapKey -{ - MD_u64 hash; - MD_u64 size; - void *ptr; -}; - -typedef struct MD_MapSlot MD_MapSlot; -struct MD_MapSlot -{ - MD_MapSlot *next; - MD_MapKey key; - void *val; -}; - -typedef struct MD_MapBucket MD_MapBucket; -struct MD_MapBucket -{ - MD_MapSlot *first; - MD_MapSlot *last; -}; - -typedef struct MD_Map MD_Map; -struct MD_Map -{ - MD_MapBucket *buckets; - MD_u64 bucket_count; -}; - -//~ Tokens - -typedef MD_u32 MD_TokenKind; -enum -{ - MD_TokenKind_Identifier = (1<<0), - MD_TokenKind_Numeric = (1<<1), - MD_TokenKind_StringLiteral = (1<<2), - MD_TokenKind_Symbol = (1<<3), - MD_TokenKind_Reserved = (1<<4), - MD_TokenKind_Comment = (1<<5), - MD_TokenKind_Whitespace = (1<<6), - MD_TokenKind_Newline = (1<<7), - MD_TokenKind_BrokenComment = (1<<8), - MD_TokenKind_BrokenStringLiteral = (1<<9), - MD_TokenKind_BadCharacter = (1<<10), -}; - -typedef MD_u32 MD_TokenGroups; -enum -{ - MD_TokenGroup_Comment = MD_TokenKind_Comment, - MD_TokenGroup_Whitespace = (MD_TokenKind_Whitespace| - MD_TokenKind_Newline), - MD_TokenGroup_Irregular = (MD_TokenGroup_Comment| - MD_TokenGroup_Whitespace), - MD_TokenGroup_Regular = ~MD_TokenGroup_Irregular, - MD_TokenGroup_Label = (MD_TokenKind_Identifier| - MD_TokenKind_Numeric| - MD_TokenKind_StringLiteral| - MD_TokenKind_Symbol), - MD_TokenGroup_Error = (MD_TokenKind_BrokenComment| - MD_TokenKind_BrokenStringLiteral| - MD_TokenKind_BadCharacter), -}; - -typedef struct MD_Token MD_Token; -struct MD_Token -{ - MD_TokenKind kind; - MD_NodeFlags node_flags; - MD_String8 string; - MD_String8 raw_string; -}; - -//~ Parsing State - -typedef enum MD_MessageKind -{ - // NOTE(rjf): @maintenance This enum needs to be sorted in order of - // severity. - MD_MessageKind_Null, - MD_MessageKind_Note, - MD_MessageKind_Warning, - MD_MessageKind_Error, - MD_MessageKind_FatalError, -} -MD_MessageKind; - -typedef struct MD_Message MD_Message; -struct MD_Message -{ - MD_Message *next; - MD_Node *node; - MD_MessageKind kind; - MD_String8 string; - void *user_ptr; -}; - -typedef struct MD_MessageList MD_MessageList; -struct MD_MessageList -{ - MD_MessageKind max_message_kind; - // TODO(allen): rename - MD_u64 node_count; - MD_Message *first; - MD_Message *last; -}; - -typedef enum MD_ParseSetRule -{ - MD_ParseSetRule_EndOnDelimiter, - MD_ParseSetRule_Global, -} MD_ParseSetRule; - -typedef struct MD_ParseResult MD_ParseResult; -struct MD_ParseResult -{ - MD_Node *node; - MD_u64 string_advance; - MD_MessageList errors; -}; - -//~ Expression Parsing - -typedef enum MD_ExprOprKind -{ - MD_ExprOprKind_Null, - MD_ExprOprKind_Prefix, - MD_ExprOprKind_Postfix, - MD_ExprOprKind_Binary, - MD_ExprOprKind_BinaryRightAssociative, - MD_ExprOprKind_COUNT, -} MD_ExprOprKind; - -typedef struct MD_ExprOpr MD_ExprOpr; -struct MD_ExprOpr -{ - struct MD_ExprOpr *next; - MD_u32 op_id; - MD_ExprOprKind kind; - MD_u32 precedence; - MD_String8 string; - void *op_ptr; -}; - -typedef struct MD_ExprOprList MD_ExprOprList; -struct MD_ExprOprList -{ - MD_ExprOpr *first; - MD_ExprOpr *last; - MD_u64 count; -}; - -typedef struct MD_ExprOprTable MD_ExprOprTable; -struct MD_ExprOprTable -{ - // TODO(mal): @upgrade_potential Hash? - MD_ExprOprList table[MD_ExprOprKind_COUNT]; -}; - -typedef struct MD_Expr MD_Expr; -struct MD_Expr -{ - struct MD_Expr *parent; - union - { - struct MD_Expr *left; - struct MD_Expr *unary_operand; - }; - struct MD_Expr *right; - MD_ExprOpr *op; - MD_Node *md_node; -}; - -typedef struct MD_ExprParseResult MD_ExprParseResult; -struct MD_ExprParseResult -{ - MD_Expr *expr; - MD_MessageList errors; -}; - -// TODO(allen): nil MD_Expr - -typedef struct MD_ExprParseCtx MD_ExprParseCtx; -struct MD_ExprParseCtx -{ - MD_ExprOprTable *op_table; - -#define MD_POSTFIX_SETLIKE_OP_COUNT 5 // (), [], {}, [), (] - struct - { - MD_ExprOpr *postfix_set_ops[MD_POSTFIX_SETLIKE_OP_COUNT]; - MD_NodeFlags postfix_set_flags[MD_POSTFIX_SETLIKE_OP_COUNT]; - } accel; -#undef MD_POSTFIX_SETLIKE_OP_COUNT - - MD_MessageList errors; -}; - -typedef void (*MD_BakeOperatorErrorHandler)(MD_MessageKind kind, MD_String8 s); - -//~ String Generation Types - -typedef MD_u32 MD_GenerateFlags; -enum -{ - MD_GenerateFlag_Tags = (1<<0), - MD_GenerateFlag_TagArguments = (1<<1), - MD_GenerateFlag_Children = (1<<2), - MD_GenerateFlag_Comments = (1<<3), - MD_GenerateFlag_NodeKind = (1<<4), - MD_GenerateFlag_NodeFlags = (1<<5), - MD_GenerateFlag_Location = (1<<6), - - MD_GenerateFlags_Tree = (MD_GenerateFlag_Tags | - MD_GenerateFlag_TagArguments | - MD_GenerateFlag_Children), - MD_GenerateFlags_All = 0xffffffff, -}; - -//~ Command line parsing helper types. - -typedef struct MD_CmdLineOption MD_CmdLineOption; -struct MD_CmdLineOption -{ - MD_CmdLineOption *next; - MD_String8 name; - MD_String8List values; -}; - -typedef struct MD_CmdLine MD_CmdLine; -struct MD_CmdLine -{ - MD_String8List inputs; - MD_CmdLineOption *first_option; - MD_CmdLineOption *last_option; -}; - -//~ File system access types. - -typedef MD_u32 MD_FileFlags; -enum -{ - MD_FileFlag_Directory = (1<<0), -}; - -typedef struct MD_FileInfo MD_FileInfo; -struct MD_FileInfo -{ - MD_FileFlags flags; - MD_String8 filename; - MD_u64 file_size; -}; - -typedef struct MD_FileIter MD_FileIter; -struct MD_FileIter -{ - // This is opaque state to store OS-specific file-system iteration data. - MD_u8 opaque[640]; -}; - -//~///////////////////////////////////////////////////////////////////////////// -////////////////////////////////// Functions /////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////// - -//~ Arena - -MD_FUNCTION MD_Arena* MD_ArenaAlloc(void); -MD_FUNCTION void MD_ArenaRelease(MD_Arena *arena); - -MD_FUNCTION void* MD_ArenaPush(MD_Arena *arena, MD_u64 size); -MD_FUNCTION void MD_ArenaPutBack(MD_Arena *arena, MD_u64 size); -MD_FUNCTION void MD_ArenaSetAlign(MD_Arena *arena, MD_u64 boundary); -MD_FUNCTION void MD_ArenaPushAlign(MD_Arena *arena, MD_u64 boundary); -MD_FUNCTION void MD_ArenaClear(MD_Arena *arena); - -#define MD_PushArray(a,T,c) (T*)(MD_ArenaPush((a), sizeof(T)*(c))) -#define MD_PushArrayZero(a,T,c) (T*)(MD_MemoryZero(MD_PushArray(a,T,c), sizeof(T)*(c))) - -MD_FUNCTION MD_ArenaTemp MD_ArenaBeginTemp(MD_Arena *arena); -MD_FUNCTION void MD_ArenaEndTemp(MD_ArenaTemp temp); - -//~ Arena Scratch Pool - -MD_FUNCTION MD_ArenaTemp MD_GetScratch(MD_Arena **conflicts, MD_u64 count); - -#define MD_ReleaseScratch(scratch) MD_ArenaEndTemp(scratch) - -//~ Characters - -MD_FUNCTION MD_b32 MD_CharIsAlpha(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsAlphaUpper(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsAlphaLower(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsDigit(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsUnreservedSymbol(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsReservedSymbol(MD_u8 c); -MD_FUNCTION MD_b32 MD_CharIsSpace(MD_u8 c); -MD_FUNCTION MD_u8 MD_CharToUpper(MD_u8 c); -MD_FUNCTION MD_u8 MD_CharToLower(MD_u8 c); -MD_FUNCTION MD_u8 MD_CharToForwardSlash(MD_u8 c); - -//~ Strings - -MD_FUNCTION MD_u64 MD_CalculateCStringLength(char *cstr); - -MD_FUNCTION MD_String8 MD_S8(MD_u8 *str, MD_u64 size); -#define MD_S8CString(s) MD_S8((MD_u8 *)(s), MD_CalculateCStringLength(s)) - -#if MD_LANG_C -# define MD_S8Lit(s) (MD_String8){(MD_u8 *)(s), sizeof(s)-1} -#elif MD_LANG_CPP -# define MD_S8Lit(s) MD_S8((MD_u8*)(s), sizeof(s) - 1) -#endif -#define MD_S8LitComp(s) {(MD_u8 *)(s), sizeof(s)-1} - -#if MD_CPP_VERSION >= 11 -static inline MD_String8 -operator "" _md(const char *s, size_t size) -{ - MD_String8 str = MD_S8((MD_u8 *)s, (MD_u64)size); - return str; -} -#endif - -MD_FUNCTION MD_String8 MD_S8Range(MD_u8 *first, MD_u8 *opl); - -MD_FUNCTION MD_String8 MD_S8Substring(MD_String8 str, MD_u64 min, MD_u64 max); -MD_FUNCTION MD_String8 MD_S8Skip(MD_String8 str, MD_u64 min); -MD_FUNCTION MD_String8 MD_S8Chop(MD_String8 str, MD_u64 nmax); -MD_FUNCTION MD_String8 MD_S8Prefix(MD_String8 str, MD_u64 size); -MD_FUNCTION MD_String8 MD_S8Suffix(MD_String8 str, MD_u64 size); - -MD_FUNCTION MD_b32 MD_S8Match(MD_String8 a, MD_String8 b, MD_MatchFlags flags); -MD_FUNCTION MD_u64 MD_S8FindSubstring(MD_String8 str, MD_String8 substring, - MD_u64 start_pos, MD_MatchFlags flags); - -MD_FUNCTION MD_String8 MD_S8Copy(MD_Arena *arena, MD_String8 string); -MD_FUNCTION MD_String8 MD_S8FmtV(MD_Arena *arena, char *fmt, va_list args); - -MD_FUNCTION MD_String8 MD_S8Fmt(MD_Arena *arena, char *fmt, ...); - -#define MD_S8VArg(s) (int)(s).size, (s).str - -MD_FUNCTION void MD_S8ListPush(MD_Arena *arena, MD_String8List *list, - MD_String8 string); -MD_FUNCTION void MD_S8ListPushFmt(MD_Arena *arena, MD_String8List *list, - char *fmt, ...); - -MD_FUNCTION void MD_S8ListConcat(MD_String8List *list, MD_String8List *to_push); -MD_FUNCTION MD_String8List MD_S8Split(MD_Arena *arena, MD_String8 string, int split_count, - MD_String8 *splits); -MD_FUNCTION MD_String8 MD_S8ListJoin(MD_Arena *arena, MD_String8List list, - MD_StringJoin *join); - -MD_FUNCTION MD_String8 MD_S8Stylize(MD_Arena *arena, MD_String8 string, - MD_IdentifierStyle style, MD_String8 separator); - -//~ Unicode Conversions - -MD_FUNCTION MD_DecodedCodepoint MD_DecodeCodepointFromUtf8(MD_u8 *str, MD_u64 max); -MD_FUNCTION MD_DecodedCodepoint MD_DecodeCodepointFromUtf16(MD_u16 *str, MD_u64 max); -MD_FUNCTION MD_u32 MD_Utf8FromCodepoint(MD_u8 *out, MD_u32 codepoint); -MD_FUNCTION MD_u32 MD_Utf16FromCodepoint(MD_u16 *out, MD_u32 codepoint); -MD_FUNCTION MD_String8 MD_S8FromS16(MD_Arena *arena, MD_String16 str); -MD_FUNCTION MD_String16 MD_S16FromS8(MD_Arena *arena, MD_String8 str); -MD_FUNCTION MD_String8 MD_S8FromS32(MD_Arena *arena, MD_String32 str); -MD_FUNCTION MD_String32 MD_S32FromS8(MD_Arena *arena, MD_String8 str); - -//~ String Skipping/Chopping Helpers - -// This is intended for removing extensions. -MD_FUNCTION MD_String8 MD_PathChopLastPeriod(MD_String8 string); - -// This is intended for removing everything but the filename. -MD_FUNCTION MD_String8 MD_PathSkipLastSlash(MD_String8 string); - -// This is intended for getting an extension from a filename. -MD_FUNCTION MD_String8 MD_PathSkipLastPeriod(MD_String8 string); - -// This is intended for getting the folder string from a full path. -MD_FUNCTION MD_String8 MD_PathChopLastSlash(MD_String8 string); - -MD_FUNCTION MD_String8 MD_S8SkipWhitespace(MD_String8 string); -MD_FUNCTION MD_String8 MD_S8ChopWhitespace(MD_String8 string); - -//~ Numeric Strings - -MD_FUNCTION MD_b32 MD_StringIsU64(MD_String8 string, MD_u32 radix); -MD_FUNCTION MD_b32 MD_StringIsCStyleInt(MD_String8 string); - -MD_FUNCTION MD_u64 MD_U64FromString(MD_String8 string, MD_u32 radix); -MD_FUNCTION MD_i64 MD_CStyleIntFromString(MD_String8 string); -MD_FUNCTION MD_f64 MD_F64FromString(MD_String8 string); - -MD_FUNCTION MD_String8 MD_CStyleHexStringFromU64(MD_Arena *arena, MD_u64 x, MD_b32 caps); - -//~ Enum/Flag Strings - -MD_FUNCTION MD_String8 MD_StringFromNodeKind(MD_NodeKind kind); -MD_FUNCTION MD_String8List MD_StringListFromNodeFlags(MD_Arena *arena, MD_NodeFlags flags); - -//~ Map Table Data Structure - -MD_FUNCTION MD_u64 MD_HashStr(MD_String8 string); -MD_FUNCTION MD_u64 MD_HashPtr(void *p); - -MD_FUNCTION MD_Map MD_MapMakeBucketCount(MD_Arena *arena, MD_u64 bucket_count); -MD_FUNCTION MD_Map MD_MapMake(MD_Arena *arena); -MD_FUNCTION MD_MapKey MD_MapKeyStr(MD_String8 string); -MD_FUNCTION MD_MapKey MD_MapKeyPtr(void *ptr); -MD_FUNCTION MD_MapSlot* MD_MapLookup(MD_Map *map, MD_MapKey key); -MD_FUNCTION MD_MapSlot* MD_MapScan(MD_MapSlot *first_slot, MD_MapKey key); -MD_FUNCTION MD_MapSlot* MD_MapInsert(MD_Arena *arena, MD_Map *map, MD_MapKey key, void *val); -MD_FUNCTION MD_MapSlot* MD_MapOverwrite(MD_Arena *arena, MD_Map *map, MD_MapKey key, - void *val); - -//~ Parsing - -MD_FUNCTION MD_Token MD_TokenFromString(MD_String8 string); -MD_FUNCTION MD_u64 MD_LexAdvanceFromSkips(MD_String8 string, MD_TokenKind skip_kinds); -MD_FUNCTION MD_ParseResult MD_ParseResultZero(void); -MD_FUNCTION MD_ParseResult MD_ParseNodeSet(MD_Arena *arena, MD_String8 string, MD_u64 offset, MD_Node *parent, - MD_ParseSetRule rule); -MD_FUNCTION MD_ParseResult MD_ParseOneNode(MD_Arena *arena, MD_String8 string, MD_u64 offset); -MD_FUNCTION MD_ParseResult MD_ParseWholeString(MD_Arena *arena, MD_String8 filename, MD_String8 contents); - -MD_FUNCTION MD_ParseResult MD_ParseWholeFile(MD_Arena *arena, MD_String8 filename); - -//~ Messages (Errors/Warnings) - -MD_FUNCTION MD_Node* MD_MakeErrorMarkerNode(MD_Arena *arena, MD_String8 parse_contents, - MD_u64 offset); - -MD_FUNCTION MD_Message*MD_MakeNodeError(MD_Arena *arena, MD_Node *node, - MD_MessageKind kind, MD_String8 str); -MD_FUNCTION MD_Message*MD_MakeDetachedError(MD_Arena *arena, MD_MessageKind kind, - MD_String8 str, void *ptr); -MD_FUNCTION MD_Message*MD_MakeTokenError(MD_Arena *arena, MD_String8 parse_contents, - MD_Token token, MD_MessageKind kind, - MD_String8 str); - -MD_FUNCTION void MD_MessageListPush(MD_MessageList *list, MD_Message *message); -MD_FUNCTION void MD_MessageListConcat(MD_MessageList *list, MD_MessageList *to_push); - -//~ Location Conversion - -MD_FUNCTION MD_CodeLoc MD_CodeLocFromFileOffset(MD_String8 filename, MD_u8 *base, MD_u64 offset); -MD_FUNCTION MD_CodeLoc MD_CodeLocFromNode(MD_Node *node); - -//~ Tree/List Building - -MD_FUNCTION MD_b32 MD_NodeIsNil(MD_Node *node); -MD_FUNCTION MD_Node *MD_NilNode(void); -MD_FUNCTION MD_Node *MD_MakeNode(MD_Arena *arena, MD_NodeKind kind, MD_String8 string, - MD_String8 raw_string, MD_u64 offset); -MD_FUNCTION void MD_PushChild(MD_Node *parent, MD_Node *new_child); -MD_FUNCTION void MD_PushTag(MD_Node *node, MD_Node *tag); - -MD_FUNCTION MD_Node *MD_MakeList(MD_Arena *arena); -MD_FUNCTION void MD_ListConcatInPlace(MD_Node *list, MD_Node *to_push); -MD_FUNCTION MD_Node *MD_PushNewReference(MD_Arena *arena, MD_Node *list, MD_Node *target); - -//~ Introspection Helpers - -// These calls are for getting info from nodes, and introspecting -// on trees that are returned to you by the parser. - -MD_FUNCTION MD_Node * MD_FirstNodeWithString(MD_Node *first, MD_String8 string, MD_MatchFlags flags); -MD_FUNCTION MD_Node * MD_NodeAtIndex(MD_Node *first, int n); -MD_FUNCTION MD_Node * MD_FirstNodeWithFlags(MD_Node *first, MD_NodeFlags flags); -MD_FUNCTION int MD_IndexFromNode(MD_Node *node); -MD_FUNCTION MD_Node * MD_RootFromNode(MD_Node *node); -MD_FUNCTION MD_Node * MD_ChildFromString(MD_Node *node, MD_String8 child_string, MD_MatchFlags flags); -MD_FUNCTION MD_Node * MD_TagFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags); -MD_FUNCTION MD_Node * MD_ChildFromIndex(MD_Node *node, int n); -MD_FUNCTION MD_Node * MD_TagFromIndex(MD_Node *node, int n); -MD_FUNCTION MD_Node * MD_TagArgFromIndex(MD_Node *node, MD_String8 tag_string, MD_MatchFlags flags, int n); -MD_FUNCTION MD_Node * MD_TagArgFromString(MD_Node *node, MD_String8 tag_string, MD_MatchFlags tag_str_flags, MD_String8 arg_string, MD_MatchFlags arg_str_flags); -MD_FUNCTION MD_b32 MD_NodeHasChild(MD_Node *node, MD_String8 string, MD_MatchFlags flags); -MD_FUNCTION MD_b32 MD_NodeHasTag(MD_Node *node, MD_String8 string, MD_MatchFlags flags); -MD_FUNCTION MD_i64 MD_ChildCountFromNode(MD_Node *node); -MD_FUNCTION MD_i64 MD_TagCountFromNode(MD_Node *node); -MD_FUNCTION MD_Node * MD_ResolveNodeFromReference(MD_Node *node); -MD_FUNCTION MD_Node* MD_NodeNextWithLimit(MD_Node *node, MD_Node *opl); - -MD_FUNCTION MD_String8 MD_PrevCommentFromNode(MD_Node *node); -MD_FUNCTION MD_String8 MD_NextCommentFromNode(MD_Node *node); - -// NOTE(rjf): For-Loop Helpers -#define MD_EachNode(it, first) MD_Node *it = (first); !MD_NodeIsNil(it); it = it->next - -//~ Error/Warning Helpers - -MD_FUNCTION MD_String8 MD_StringFromMessageKind(MD_MessageKind kind); - -#define MD_FmtCodeLoc "%.*s:%i:%i:" -#define MD_CodeLocVArg(loc) MD_S8VArg((loc).filename), (loc).line, (loc).column - -MD_FUNCTION MD_String8 MD_FormatMessage(MD_Arena *arena, MD_CodeLoc loc, MD_MessageKind kind, - MD_String8 string); - -#if !MD_DISABLE_PRINT_HELPERS -#include -MD_FUNCTION void MD_PrintMessage(FILE *file, MD_CodeLoc loc, MD_MessageKind kind, - MD_String8 string); -MD_FUNCTION void MD_PrintMessageFmt(FILE *file, MD_CodeLoc code_loc, MD_MessageKind kind, - char *fmt, ...); - -#define MD_PrintGenNoteCComment(f) fprintf((f), "// generated by %s:%d\n", __FILE__, __LINE__) -#endif - -//~ Tree Comparison/Verification - -MD_FUNCTION MD_b32 MD_NodeMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags); -MD_FUNCTION MD_b32 MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags); - -//~ Expression Parsing - -MD_FUNCTION void MD_ExprOprPush(MD_Arena *arena, MD_ExprOprList *list, - MD_ExprOprKind kind, MD_u64 precedence, - MD_String8 op_string, - MD_u32 op_id, void *op_ptr); - -MD_FUNCTION MD_ExprOprTable MD_ExprBakeOprTableFromList(MD_Arena *arena, - MD_ExprOprList *list); -MD_FUNCTION MD_ExprOpr* MD_ExprOprFromKindString(MD_ExprOprTable *table, - MD_ExprOprKind kind, MD_String8 s); - -MD_FUNCTION MD_ExprParseResult MD_ExprParse(MD_Arena *arena, MD_ExprOprTable *op_table, - MD_Node *first, MD_Node *one_past_last); - -MD_FUNCTION MD_Expr* MD_Expr_NewLeaf(MD_Arena *arena, MD_Node *node); -MD_FUNCTION MD_Expr* MD_Expr_NewOpr(MD_Arena *arena, MD_ExprOpr *op, MD_Node *op_node, - MD_Expr *left, MD_Expr *right); - -MD_FUNCTION MD_ExprParseCtx MD_ExprParse_MakeContext(MD_ExprOprTable *table); - -MD_FUNCTION MD_Expr* MD_ExprParse_TopLevel(MD_Arena *arena, MD_ExprParseCtx *ctx, - MD_Node *first, MD_Node *opl); -MD_FUNCTION MD_b32 MD_ExprParse_OprConsume(MD_ExprParseCtx *ctx, - MD_Node **iter, MD_Node *opl, - MD_ExprOprKind kind, - MD_u32 min_precedence, - MD_ExprOpr **op_out); -MD_FUNCTION MD_Expr* MD_ExprParse_Atom(MD_Arena *arena, MD_ExprParseCtx *ctx, - MD_Node **iter, MD_Node *first, MD_Node *opl); -MD_FUNCTION MD_Expr* MD_ExprParse_MinPrecedence(MD_Arena *arena, MD_ExprParseCtx *ctx, - MD_Node **iter, MD_Node *first, MD_Node *opl, - MD_u32 min_precedence); - - -//~ String Generation - -MD_FUNCTION void MD_DebugDumpFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, - int indent, MD_String8 indent_string, - MD_GenerateFlags flags); -MD_FUNCTION void MD_ReconstructionFromNode(MD_Arena *arena, MD_String8List *out, MD_Node *node, - int indent, MD_String8 indent_string); - -//~ Command Line Argument Helper - -MD_FUNCTION MD_String8List MD_StringListFromArgCV(MD_Arena *arena, int argument_count, - char **arguments); -MD_FUNCTION MD_CmdLine MD_MakeCmdLineFromOptions(MD_Arena *arena, MD_String8List options); -MD_FUNCTION MD_String8List MD_CmdLineValuesFromString(MD_CmdLine cmdln, MD_String8 name); -MD_FUNCTION MD_b32 MD_CmdLineB32FromString(MD_CmdLine cmdln, MD_String8 name); -MD_FUNCTION MD_i64 MD_CmdLineI64FromString(MD_CmdLine cmdln, MD_String8 name); - -//~ File System - -MD_FUNCTION MD_String8 MD_LoadEntireFile(MD_Arena *arena, MD_String8 filename); -MD_FUNCTION MD_b32 MD_FileIterBegin(MD_FileIter *it, MD_String8 path); -MD_FUNCTION MD_FileInfo MD_FileIterNext(MD_Arena *arena, MD_FileIter *it); -MD_FUNCTION void MD_FileIterEnd(MD_FileIter *it); - -#endif // MD_H - -/* -Copyright 2021 Dion Systems LLC - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ diff --git a/src/metagen/metadesk_base/md_stb_sprintf.h b/src/metagen/metadesk_base/md_stb_sprintf.h deleted file mode 100644 index c5b5fcc..0000000 --- a/src/metagen/metadesk_base/md_stb_sprintf.h +++ /dev/null @@ -1,1905 +0,0 @@ -// NOTE(rjf): This library has been modified for Metadesk. - -// stb_sprintf - v1.09 - public domain snprintf() implementation -// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 -// http://github.com/nothings/stb -// -// allowed types: sc uidBboXx p AaGgEef n -// lengths : hh h ll j z t I64 I32 I -// -// Contributors: -// Fabian "ryg" Giesen (reformatting) -// -// Contributors (bugfixes): -// github:d26435 -// github:trex78 -// github:account-login -// Jari Komppa (SI suffixes) -// Rohit Nirmal -// Marcin Wojdyr -// Leonard Ritter -// Stefano Zanotti -// Adam Allison -// Arvid Gerstmann -// Markus Kolb -// -// LICENSE: -// -// See end of file for license information. - -#ifndef STB_SPRINTF_H_INCLUDE -#define STB_SPRINTF_H_INCLUDE - -/* -Single file sprintf replacement. - -Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. -Hereby placed in public domain. - -This is a full sprintf replacement that supports everything that -the C runtime sprintfs support, including float/double, 64-bit integers, -hex floats, field parameters (%*.*d stuff), length reads backs, etc. - -Why would you need this if sprintf already exists? Well, first off, -it's *much* faster (see below). It's also much smaller than the CRT -versions code-space-wise. We've also added some simple improvements -that are super handy (commas in thousands, callbacks at buffer full, -for example). Finally, the format strings for MSVC and GCC differ -for 64-bit integers (among other small things), so this lets you use -the same format strings in cross platform code. - -It uses the standard single file trick of being both the header file -and the source itself. If you just include it normally, you just get -the header file function definitions. To get the code, you include -it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. - -It only uses va_args macros from the C runtime to do it's work. It -does cast doubles to S64s and shifts and divides U64s, which does -drag in CRT code on most platforms. - -It compiles to roughly 8K with float support, and 4K without. -As a comparison, when using MSVC static libs, calling sprintf drags -in 16K. - -API: -==== -int stbsp_sprintf( char * buf, char const * fmt, ... ) -int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) - Convert an arg list into a buffer. stbsp_snprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) -int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) - Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns - a zero-terminated string (unlike regular snprintf). - -int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) - typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); - Convert into a buffer, calling back every STB_SPRINTF_MIN chars. - Your callback can then copy the chars out, print them or whatever. - This function is actually the workhorse for everything else. - The buffer you pass in must hold at least STB_SPRINTF_MIN characters. - // you return the next buffer to use or 0 to stop converting - -void stbsp_set_separators( char comma, char period ) - Set the comma and period characters to use. - -FLOATS/DOUBLES: -=============== -This code uses a internal float->ascii conversion method that uses -doubles with error correction (double-doubles, for ~105 bits of -precision). This conversion is round-trip perfect - that is, an atof -of the values output here will give you the bit-exact double back. - -One difference is that our insignificant digits will be different than -with MSVC or GCC (but they don't match each other either). We also -don't attempt to find the minimum length matching float (pre-MSVC15 -doesn't either). - -If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT -and you'll save 4K of code space. - -64-BIT INTS: -============ -This library also supports 64-bit integers and you can use MSVC style or -GCC style indicators (%I64d or %lld). It supports the C99 specifiers -for size_t and ptr_diff_t (%jd %zd) as well. - -EXTRAS: -======= -Like some GCCs, for integers and floats, you can use a ' (single quote) -specifier and commas will be inserted on the thousands: "%'d" on 12345 -would print 12,345. - -For integers and floats, you can use a "$" specifier and the number -will be converted to float and then divided to get kilo, mega, giga or -tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is -"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn -2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three -$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the -suffix, add "_" specifier: "%_$d" -> "2.53M". - -In addition to octal and hexadecimal conversions, you can print -integers in binary: "%b" for 256 would print 100. - -PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): -=================================================================== -"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) -"%24d" across all 32-bit ints (4.5x/4.2x faster) -"%x" across all 32-bit ints (4.5x/3.8x faster) -"%08x" across all 32-bit ints (4.3x/3.8x faster) -"%f" across e-10 to e+10 floats (7.3x/6.0x faster) -"%e" across e-10 to e+10 floats (8.1x/6.0x faster) -"%g" across e-10 to e+10 floats (10.0x/7.1x faster) -"%f" for values near e-300 (7.9x/6.5x faster) -"%f" for values near e+300 (10.0x/9.1x faster) -"%e" for values near e-300 (10.1x/7.0x faster) -"%e" for values near e+300 (9.2x/6.0x faster) -"%.320f" for values near e-300 (12.6x/11.2x faster) -"%a" for random values (8.6x/4.3x faster) -"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) -"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) -"%s%s%s" for 64 char strings (7.1x/7.3x faster) -"...512 char string..." ( 35.0x/32.5x faster!) -*/ - -#if defined(__clang__) -#if defined(__has_feature) && defined(__has_attribute) -#if __has_feature(address_sanitizer) -#if __has_attribute(__no_sanitize__) -#define STBSP__ASAN __attribute__((__no_sanitize__("address"))) -#elif __has_attribute(__no_sanitize_address__) -#define STBSP__ASAN __attribute__((__no_sanitize_address__)) -#elif __has_attribute(__no_address_safety_analysis__) -#define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) -#endif -#endif -#endif -#elif __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) -#if __SANITIZE_ADDRESS__ -#define STBSP__ASAN __attribute__((__no_sanitize_address__)) -#endif -#endif - -#ifndef STBSP__ASAN -#define STBSP__ASAN -#endif - -#ifdef STB_SPRINTF_STATIC -#define STBSP__PUBLICDEC static -#define STBSP__PUBLICDEF static STBSP__ASAN -#else -#ifdef __cplusplus -#define STBSP__PUBLICDEC extern "C" -#define STBSP__PUBLICDEF extern "C" STBSP__ASAN -#else -#define STBSP__PUBLICDEC extern -#define STBSP__PUBLICDEF STBSP__ASAN -#endif -#endif - -#include // for va_list() -#include // size_t, ptrdiff_t - -#ifndef STB_SPRINTF_MIN -#define STB_SPRINTF_MIN 512 // how many characters per callback -#endif -typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); - -#ifndef STB_SPRINTF_DECORATE -#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names -#endif - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...); -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...); - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); -STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); - -#endif // STB_SPRINTF_H_INCLUDE - -#ifdef STB_SPRINTF_IMPLEMENTATION - -#include // for va_arg() - -#define stbsp__uint32 unsigned int -#define stbsp__int32 signed int - -#ifdef _MSC_VER -#define stbsp__uint64 unsigned __int64 -#define stbsp__int64 signed __int64 -#else -#define stbsp__uint64 unsigned long long -#define stbsp__int64 signed long long -#endif -#define stbsp__uint16 unsigned short - -#ifndef stbsp__uintptr -#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) -#define stbsp__uintptr stbsp__uint64 -#else -#define stbsp__uintptr stbsp__uint32 -#endif -#endif - -#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) -#if defined(_MSC_VER) && (_MSC_VER < 1900) -#define STB_SPRINTF_MSVC_MODE -#endif -#endif - -#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses -#define STBSP__UNALIGNED(code) -#else -#define STBSP__UNALIGNED(code) code -#endif - -#ifndef STB_SPRINTF_NOFLOAT -// internal float utility functions -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); -#define STBSP__SPECIAL 0x7000 -#endif - -static char stbsp__period = '.'; -static char stbsp__comma = ','; -static struct -{ - short temp; // force next field to be 2-byte aligned - char pair[201]; -} stbsp__digitpair = -{ - 0, - "00010203040506070809101112131415161718192021222324" - "25262728293031323334353637383940414243444546474849" - "50515253545556575859606162636465666768697071727374" - "75767778798081828384858687888990919293949596979899" -}; - -STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) -{ - stbsp__period = pperiod; - stbsp__comma = pcomma; -} - -#define STBSP__LEFTJUST 1 -#define STBSP__LEADINGPLUS 2 -#define STBSP__LEADINGSPACE 4 -#define STBSP__LEADING_0X 8 -#define STBSP__LEADINGZERO 16 -#define STBSP__INTMAX 32 -#define STBSP__TRIPLET_COMMA 64 -#define STBSP__NEGATIVE 128 -#define STBSP__METRIC_SUFFIX 256 -#define STBSP__HALFWIDTH 512 -#define STBSP__METRIC_NOSPACE 1024 -#define STBSP__METRIC_1024 2048 -#define STBSP__METRIC_JEDEC 4096 - -static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) -{ - sign[0] = 0; - if (fl & STBSP__NEGATIVE) { - sign[0] = 1; - sign[1] = '-'; - } else if (fl & STBSP__LEADINGSPACE) { - sign[0] = 1; - sign[1] = ' '; - } else if (fl & STBSP__LEADINGPLUS) { - sign[0] = 1; - sign[1] = '+'; - } -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) -{ - static char hex[] = "0123456789abcdefxp"; - static char hexu[] = "0123456789ABCDEFXP"; - char *bf; - char const *f; - int tlen = 0; - - bf = buf; - f = fmt; - for (;;) { - stbsp__int32 fw, pr, tz; - stbsp__uint32 fl; - - // macros for the callback buffer stuff -#define stbsp__chk_cb_bufL(bytes) \ -{ \ -int len = (int)(bf - buf); \ -if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ -tlen += len; \ -if (0 == (bf = buf = callback(buf, user, len))) \ -goto done; \ -} \ -} -#define stbsp__chk_cb_buf(bytes) \ -{ \ -if (callback) { \ -stbsp__chk_cb_bufL(bytes); \ -} \ -} -#define stbsp__flush_cb() \ -{ \ -stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ -} // flush if there is even one byte in the buffer -#define stbsp__cb_buf_clamp(cl, v) \ -cl = v; \ -if (callback) { \ -int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ -if (cl > lg) \ -cl = lg; \ -} - - // fast copy everything up to the next % (or end of string) - for (;;) { - while (((stbsp__uintptr)f) & 3) { - schk1: - if (f[0] == '%') - goto scandd; - schk2: - if (f[0] == 0) - goto endfmt; - stbsp__chk_cb_buf(1); - *bf++ = f[0]; - ++f; - } - for (;;) { - // Check if the next 4 bytes contain %(0x25) or end of string. - // Using the 'hasless' trick: - // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord - stbsp__uint32 v, c; - v = *(stbsp__uint32 *)f; - c = (~v) & 0x80808080; - if (((v ^ 0x25252525) - 0x01010101) & c) - goto schk1; - if ((v - 0x01010101) & c) - goto schk2; - if (callback) - if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) - goto schk1; -#ifdef STB_SPRINTF_NOUNALIGNED - if(((stbsp__uintptr)bf) & 3) { - bf[0] = f[0]; - bf[1] = f[1]; - bf[2] = f[2]; - bf[3] = f[3]; - } else -#endif - { - *(stbsp__uint32 *)bf = v; - } - bf += 4; - f += 4; - } - } - scandd: - - ++f; - - // ok, we have a percent, read the modifiers first - fw = 0; - pr = -1; - fl = 0; - tz = 0; - - // flags - for (;;) { - switch (f[0]) { - // if we have left justify - case '-': - fl |= STBSP__LEFTJUST; - ++f; - continue; - // if we have leading plus - case '+': - fl |= STBSP__LEADINGPLUS; - ++f; - continue; - // if we have leading space - case ' ': - fl |= STBSP__LEADINGSPACE; - ++f; - continue; - // if we have leading 0x - case '#': - fl |= STBSP__LEADING_0X; - ++f; - continue; - // if we have thousand commas - case '\'': - fl |= STBSP__TRIPLET_COMMA; - ++f; - continue; - // if we have kilo marker (none->kilo->kibi->jedec) - case '$': - if (fl & STBSP__METRIC_SUFFIX) { - if (fl & STBSP__METRIC_1024) { - fl |= STBSP__METRIC_JEDEC; - } else { - fl |= STBSP__METRIC_1024; - } - } else { - fl |= STBSP__METRIC_SUFFIX; - } - ++f; - continue; - // if we don't want space between metric suffix and number - case '_': - fl |= STBSP__METRIC_NOSPACE; - ++f; - continue; - // if we have leading zero - case '0': - fl |= STBSP__LEADINGZERO; - ++f; - goto flags_done; - default: goto flags_done; - } - } - flags_done: - - // get the field width - if (f[0] == '*') { - fw = va_arg(va, stbsp__uint32); - ++f; - } else { - while ((f[0] >= '0') && (f[0] <= '9')) { - fw = fw * 10 + f[0] - '0'; - f++; - } - } - // get the precision - if (f[0] == '.') { - ++f; - if (f[0] == '*') { - pr = va_arg(va, stbsp__uint32); - ++f; - } else { - pr = 0; - while ((f[0] >= '0') && (f[0] <= '9')) { - pr = pr * 10 + f[0] - '0'; - f++; - } - } - } - - // handle integer size overrides - switch (f[0]) { - // are we halfwidth? - case 'h': - fl |= STBSP__HALFWIDTH; - ++f; - if (f[0] == 'h') - ++f; // QUARTERWIDTH - break; - // are we 64-bit (unix style) - case 'l': - fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); - ++f; - if (f[0] == 'l') { - fl |= STBSP__INTMAX; - ++f; - } - break; - // are we 64-bit on intmax? (c99) - case 'j': - fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit on size_t or ptrdiff_t? (c99) - case 'z': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - case 't': - fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; - ++f; - break; - // are we 64-bit (msft style) - case 'I': - if ((f[1] == '6') && (f[2] == '4')) { - fl |= STBSP__INTMAX; - f += 3; - } else if ((f[1] == '3') && (f[2] == '2')) { - f += 3; - } else { - fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); - ++f; - } - break; - default: break; - } - - // handle each replacement - switch (f[0]) { -#define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 - char num[STBSP__NUMSZ]; - char lead[8]; - char tail[8]; - char *s; - char const *h; - stbsp__uint32 l, n, cs; - stbsp__uint64 n64; -#ifndef STB_SPRINTF_NOFLOAT - double fv; -#endif - stbsp__int32 dp; - char const *sn; - - case 's': - // get the string - s = va_arg(va, char *); - if (s == 0) - s = (char *)"null"; - // get the length - sn = s; - for (;;) { - if ((((stbsp__uintptr)sn) & 3) == 0) - break; - lchk: - if (sn[0] == 0) - goto ld; - ++sn; - } - n = 0xffffffff; - if (pr >= 0) { - n = (stbsp__uint32)(sn - s); - if (n >= (stbsp__uint32)pr) - goto ld; - n = ((stbsp__uint32)(pr - n)) >> 2; - } - while (n) { - stbsp__uint32 v = *(stbsp__uint32 *)sn; - if ((v - 0x01010101) & (~v) & 0x80808080UL) - goto lchk; - sn += 4; - --n; - } - goto lchk; - ld: - - l = (stbsp__uint32)(sn - s); - // clamp to precision - if (l > (stbsp__uint32)pr) - l = pr; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - // copy the string in - goto scopy; - - //~ rjf: METADESK ADDITION: %S for MD_String8's - - case 'S': // MD_String8 - { - //- rjf: pull out string - MD_String8 str = va_arg(va, MD_String8); - - //- rjf: get string length - s = (char *)str.str; - sn = (const char *)(str.str + str.size); - l = str.size; - - //- rjf: clamp to precision - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - - goto scopy; - }break; - - //~ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - case 'c': // char - // get the character - s = num + STBSP__NUMSZ - 1; - *s = (char)va_arg(va, int); - l = 1; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - - case 'n': // weird write-bytes specifier - { - int *d = va_arg(va, int *); - *d = tlen + (int)(bf - buf); - } break; - -#ifdef STB_SPRINTF_NOFLOAT - case 'A': // float - case 'a': // hex float - case 'G': // float - case 'g': // float - case 'E': // float - case 'e': // float - case 'f': // float - va_arg(va, double); // eat it - s = (char *)"No float"; - l = 8; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; -#else - case 'A': // hex float - case 'a': // hex float - h = (f[0] == 'A') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) - fl |= STBSP__NEGATIVE; - - s = num + 64; - - stbsp__lead_sign(fl, lead); - - if (dp == -1023) - dp = (n64) ? -1022 : 0; - else - n64 |= (((stbsp__uint64)1) << 52); - n64 <<= (64 - 56); - if (pr < 15) - n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); - // add leading chars - -#ifdef STB_SPRINTF_MSVC_MODE - *s++ = '0'; - *s++ = 'x'; -#else - lead[1 + lead[0]] = '0'; - lead[2 + lead[0]] = 'x'; - lead[0] += 2; -#endif - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - if (pr) - *s++ = stbsp__period; - sn = s; - - // print the bits - n = pr; - if (n > 13) - n = 13; - if (pr > (stbsp__int32)n) - tz = pr - n; - pr = 0; - while (n--) { - *s++ = h[(n64 >> 60) & 15]; - n64 <<= 4; - } - - // print the expo - tail[1] = h[17]; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; - n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - - dp = (int)(s - sn); - l = (int)(s - (num + 64)); - s = num + 64; - cs = 1 + (3 << 24); - goto scopy; - - case 'G': // float - case 'g': // float - h = (f[0] == 'G') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; - else if (pr == 0) - pr = 1; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) - fl |= STBSP__NEGATIVE; - - // clamp the precision and delete extra zeros after clamp - n = pr; - if (l > (stbsp__uint32)pr) - l = pr; - while ((l > 1) && (pr) && (sn[l - 1] == '0')) { - --pr; - --l; - } - - // should we use %e - if ((dp <= -4) || (dp > (stbsp__int32)n)) { - if (pr > (stbsp__int32)l) - pr = l - 1; - else if (pr) - --pr; // when using %e, there is one digit before the decimal - goto doexpfromg; - } - // this is the insane action to get the pr to match %g semantics for %f - if (dp > 0) { - pr = (dp < (stbsp__int32)l) ? l - dp : 0; - } else { - pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); - } - goto dofloatfromg; - - case 'E': // float - case 'e': // float - h = (f[0] == 'E') ? hexu : hex; - fv = va_arg(va, double); - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) - fl |= STBSP__NEGATIVE; - doexpfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - // handle leading chars - *s++ = sn[0]; - - if (pr) - *s++ = stbsp__period; - - // handle after decimal - if ((l - 1) > (stbsp__uint32)pr) - l = pr + 1; - for (n = 1; n < l; n++) - *s++ = sn[n]; - // trailing zeros - tz = pr - (l - 1); - pr = 0; - // dump expo - tail[1] = h[0xe]; - dp -= 1; - if (dp < 0) { - tail[2] = '-'; - dp = -dp; - } else - tail[2] = '+'; -#ifdef STB_SPRINTF_MSVC_MODE - n = 5; -#else - n = (dp >= 100) ? 5 : 4; -#endif - tail[0] = (char)n; - for (;;) { - tail[n] = '0' + dp % 10; - if (n <= 3) - break; - --n; - dp /= 10; - } - cs = 1 + (3 << 24); // how many tens - goto flt_lead; - - case 'f': // float - fv = va_arg(va, double); - doafloat: - // do kilos - if (fl & STBSP__METRIC_SUFFIX) { - double divisor; - divisor = 1000.0f; - if (fl & STBSP__METRIC_1024) - divisor = 1024.0; - while (fl < 0x4000000) { - if ((fv < divisor) && (fv > -divisor)) - break; - fv /= divisor; - fl += 0x1000000; - } - } - if (pr == -1) - pr = 6; // default is 6 - // read the double into a string - if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) - fl |= STBSP__NEGATIVE; - dofloatfromg: - tail[0] = 0; - stbsp__lead_sign(fl, lead); - if (dp == STBSP__SPECIAL) { - s = (char *)sn; - cs = 0; - pr = 0; - goto scopy; - } - s = num + 64; - - // handle the three decimal varieties - if (dp <= 0) { - stbsp__int32 i; - // handle 0.000*000xxxx - *s++ = '0'; - if (pr) - *s++ = stbsp__period; - n = -dp; - if ((stbsp__int32)n > pr) - n = pr; - i = n; - while (i) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - i -= 4; - } - while (i) { - *s++ = '0'; - --i; - } - if ((stbsp__int32)(l + n) > pr) - l = pr - n; - i = l; - while (i) { - *s++ = *sn++; - --i; - } - tz = pr - (n + l); - cs = 1 + (3 << 24); // how many tens did we write (for commas below) - } else { - cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; - if ((stbsp__uint32)dp >= l) { - // handle xxxx000*000.0 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= l) - break; - } - } - if (n < (stbsp__uint32)dp) { - n = dp - n; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (n) { - if ((((stbsp__uintptr)s) & 3) == 0) - break; - *s++ = '0'; - --n; - } - while (n >= 4) { - *(stbsp__uint32 *)s = 0x30303030; - s += 4; - n -= 4; - } - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = '0'; - --n; - } - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) { - *s++ = stbsp__period; - tz = pr; - } - } else { - // handle xxxxx.xxxx000*000 - n = 0; - for (;;) { - if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { - cs = 0; - *s++ = stbsp__comma; - } else { - *s++ = sn[n]; - ++n; - if (n >= (stbsp__uint32)dp) - break; - } - } - cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens - if (pr) - *s++ = stbsp__period; - if ((l - dp) > (stbsp__uint32)pr) - l = pr + dp; - while (n < l) { - *s++ = sn[n]; - ++n; - } - tz = pr - (l - dp); - } - } - pr = 0; - - // handle k,m,g,t - if (fl & STBSP__METRIC_SUFFIX) { - char idx; - idx = 1; - if (fl & STBSP__METRIC_NOSPACE) - idx = 0; - tail[0] = idx; - tail[1] = ' '; - { - if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. - if (fl & STBSP__METRIC_1024) - tail[idx + 1] = "_KMGT"[fl >> 24]; - else - tail[idx + 1] = "_kMGT"[fl >> 24]; - idx++; - // If printing kibits and not in jedec, add the 'i'. - if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { - tail[idx + 1] = 'i'; - idx++; - } - tail[0] = idx; - } - } - }; - - flt_lead: - // get the length that we copied - l = (stbsp__uint32)(s - (num + 64)); - s = num + 64; - goto scopy; -#endif - - case 'B': // upper binary - case 'b': // lower binary - h = (f[0] == 'B') ? hexu : hex; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[0xb]; - } - l = (8 << 4) | (1 << 8); - goto radixnum; - - case 'o': // octal - h = hexu; - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 1; - lead[1] = '0'; - } - l = (3 << 4) | (3 << 8); - goto radixnum; - - case 'p': // pointer - fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; - pr = sizeof(void *) * 2; - fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros - // fall through - to X - - case 'X': // upper hex - case 'x': // lower hex - h = (f[0] == 'X') ? hexu : hex; - l = (4 << 4) | (4 << 8); - lead[0] = 0; - if (fl & STBSP__LEADING_0X) { - lead[0] = 2; - lead[1] = '0'; - lead[2] = h[16]; - } - radixnum: - // get the number - if (fl & STBSP__INTMAX) - n64 = va_arg(va, stbsp__uint64); - else - n64 = va_arg(va, stbsp__uint32); - - s = num + STBSP__NUMSZ; - dp = 0; - // clear tail, and clear leading if value is zero - tail[0] = 0; - if (n64 == 0) { - lead[0] = 0; - if (pr == 0) { - l = 0; - cs = (((l >> 4) & 15)) << 24; - goto scopy; - } - } - // convert to string - for (;;) { - *--s = h[n64 & ((1 << (l >> 8)) - 1)]; - n64 >>= (l >> 8); - if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) - break; - if (fl & STBSP__TRIPLET_COMMA) { - ++l; - if ((l & 15) == ((l >> 4) & 15)) { - l &= ~15; - *--s = stbsp__comma; - } - } - }; - // get the tens and the comma pos - cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - // copy it - goto scopy; - - case 'u': // unsigned - case 'i': - case 'd': // integer - // get the integer and abs it - if (fl & STBSP__INTMAX) { - stbsp__int64 i64 = va_arg(va, stbsp__int64); - n64 = (stbsp__uint64)i64; - if ((f[0] != 'u') && (i64 < 0)) { - n64 = (stbsp__uint64)-i64; - fl |= STBSP__NEGATIVE; - } - } else { - stbsp__int32 i = va_arg(va, stbsp__int32); - n64 = (stbsp__uint32)i; - if ((f[0] != 'u') && (i < 0)) { - n64 = (stbsp__uint32)-i; - fl |= STBSP__NEGATIVE; - } - } - -#ifndef STB_SPRINTF_NOFLOAT - if (fl & STBSP__METRIC_SUFFIX) { - if (n64 < 1024) - pr = 0; - else if (pr == -1) - pr = 1; - fv = (double)(stbsp__int64)n64; - goto doafloat; - } -#endif - - // convert to string - s = num + STBSP__NUMSZ; - l = 0; - - for (;;) { - // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) - char *o = s - 8; - if (n64 >= 100000000) { - n = (stbsp__uint32)(n64 % 100000000); - n64 /= 100000000; - } else { - n = (stbsp__uint32)n64; - n64 = 0; - } - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - do { - s -= 2; - *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - } while (n); - } - while (n) { - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = (char)(n % 10) + '0'; - n /= 10; - } - } - if (n64 == 0) { - if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) - ++s; - break; - } - while (s != o) - if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { - l = 0; - *--s = stbsp__comma; - --o; - } else { - *--s = '0'; - } - } - - tail[0] = 0; - stbsp__lead_sign(fl, lead); - - // get the length that we copied - l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); - if (l == 0) { - *--s = '0'; - l = 1; - } - cs = l + (3 << 24); - if (pr < 0) - pr = 0; - - scopy: - // get fw=leading/trailing space, pr=leading zeros - if (pr < (stbsp__int32)l) - pr = l; - n = pr + lead[0] + tail[0] + tz; - if (fw < (stbsp__int32)n) - fw = n; - fw -= n; - pr -= l; - - // handle right justify and leading zeros - if ((fl & STBSP__LEFTJUST) == 0) { - if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr - { - pr = (fw > pr) ? fw : pr; - fw = 0; - } else { - fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas - } - } - - // copy the spaces and/or zeros - if (fw + pr) { - stbsp__int32 i; - stbsp__uint32 c; - - // copy leading spaces (or when doing %8.4d stuff) - if ((fl & STBSP__LEFTJUST) == 0) - while (fw > 0) { - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = ' '; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leader - sn = lead + 1; - while (lead[0]) { - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy leading zeros - c = cs >> 24; - cs &= 0xffffff; - cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; - while (pr > 0) { - stbsp__cb_buf_clamp(i, pr); - pr -= i; - if ((fl & STBSP__TRIPLET_COMMA) == 0) { - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - } - while (i) { - if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { - cs = 0; - *bf++ = stbsp__comma; - } else - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - } - - // copy leader if there is still one - sn = lead + 1; - while (lead[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, lead[0]); - lead[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy the string - n = l; - while (n) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, n); - n -= i; - STBSP__UNALIGNED(while (i >= 4) { - *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; - bf += 4; - s += 4; - i -= 4; - }) - while (i) { - *bf++ = *s++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy trailing zeros - while (tz) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tz); - tz -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = '0'; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x30303030; - bf += 4; - i -= 4; - } - while (i) { - *bf++ = '0'; - --i; - } - stbsp__chk_cb_buf(1); - } - - // copy tail if there is one - sn = tail + 1; - while (tail[0]) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, tail[0]); - tail[0] -= (char)i; - while (i) { - *bf++ = *sn++; - --i; - } - stbsp__chk_cb_buf(1); - } - - // handle the left justify - if (fl & STBSP__LEFTJUST) - if (fw > 0) { - while (fw) { - stbsp__int32 i; - stbsp__cb_buf_clamp(i, fw); - fw -= i; - while (i) { - if ((((stbsp__uintptr)bf) & 3) == 0) - break; - *bf++ = ' '; - --i; - } - while (i >= 4) { - *(stbsp__uint32 *)bf = 0x20202020; - bf += 4; - i -= 4; - } - while (i--) - *bf++ = ' '; - stbsp__chk_cb_buf(1); - } - } - break; - - default: // unknown, just copy code - s = num + STBSP__NUMSZ - 1; - *s = f[0]; - l = 1; - fw = fl = 0; - lead[0] = 0; - tail[0] = 0; - pr = 0; - dp = 0; - cs = 0; - goto scopy; - } - ++f; - } - endfmt: - - if (!callback) - *bf = 0; - else - stbsp__flush_cb(); - - done: - return tlen + (int)(bf - buf); -} - -// cleanup -#undef STBSP__LEFTJUST -#undef STBSP__LEADINGPLUS -#undef STBSP__LEADINGSPACE -#undef STBSP__LEADING_0X -#undef STBSP__LEADINGZERO -#undef STBSP__INTMAX -#undef STBSP__TRIPLET_COMMA -#undef STBSP__NEGATIVE -#undef STBSP__METRIC_SUFFIX -#undef STBSP__NUMSZ -#undef stbsp__chk_cb_bufL -#undef stbsp__chk_cb_buf -#undef stbsp__flush_cb -#undef stbsp__cb_buf_clamp - -// ============================================================================ -// wrapper functions - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); - va_end(va); - return result; -} - -typedef struct stbsp__context { - char *buf; - int count; - int length; - char tmp[STB_SPRINTF_MIN]; -} stbsp__context; - -static char *stbsp__clamp_callback(const char *buf, void *user, int len) -{ - stbsp__context *c = (stbsp__context *)user; - c->length += len; - - if (len > c->count) - len = c->count; - - if (len) { - if (buf != c->buf) { - const char *s, *se; - char *d; - d = c->buf; - s = buf; - se = buf + len; - do { - *d++ = *s++; - } while (s < se); - } - c->buf += len; - c->count -= len; - } - - if (c->count <= 0) - return c->tmp; - return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can -} - -static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) -{ - stbsp__context * c = (stbsp__context*)user; - (void) sizeof(buf); - - c->length += len; - return c->tmp; // go direct into buffer if you can -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) -{ - stbsp__context c; - - if ( (count == 0) && !buf ) - { - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); - } - else - { - int l; - - c.buf = buf; - c.count = count; - c.length = 0; - - STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); - - // zero-terminate - l = (int)( c.buf - buf ); - if ( l >= count ) // should never be greater, only equal (or less) than count - l = count - 1; - buf[l] = 0; - } - - return c.length; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) -{ - int result; - va_list va; - va_start(va, fmt); - - result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); - va_end(va); - - return result; -} - -STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) -{ - return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); -} - -// ======================================================================= -// low level float utility functions - -#ifndef STB_SPRINTF_NOFLOAT - -// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) -#define STBSP__COPYFP(dest, src) \ -{ \ -int cn; \ -for (cn = 0; cn < 8; cn++) \ -((char *)&dest)[cn] = ((char *)&src)[cn]; \ -} - -// get float info -static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) -{ - double d; - stbsp__int64 b = 0; - - // load value and round at the frac_digits - d = value; - - STBSP__COPYFP(b, d); - - *bits = b & ((((stbsp__uint64)1) << 52) - 1); - *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); - - return (stbsp__int32)((stbsp__uint64) b >> 63); -} - -static double const stbsp__bot[23] = { - 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, - 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 -}; -static double const stbsp__negbot[22] = { - 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, - 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 -}; -static double const stbsp__negboterr[22] = { - -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, - 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, - -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, - 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 -}; -static double const stbsp__top[13] = { - 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 -}; -static double const stbsp__negtop[13] = { - 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 -}; -static double const stbsp__toperr[13] = { - 8388608, - 6.8601809640529717e+028, - -7.253143638152921e+052, - -4.3377296974619174e+075, - -1.5559416129466825e+098, - -3.2841562489204913e+121, - -3.7745893248228135e+144, - -1.7356668416969134e+167, - -3.8893577551088374e+190, - -9.9566444326005119e+213, - 6.3641293062232429e+236, - -5.2069140800249813e+259, - -5.2504760255204387e+282 -}; -static double const stbsp__negtoperr[13] = { - 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, - -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, - 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, - 8.0970921678014997e-317 -}; - -#if defined(_MSC_VER) && (_MSC_VER <= 1200) -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000, - 100000000000, - 1000000000000, - 10000000000000, - 100000000000000, - 1000000000000000, - 10000000000000000, - 100000000000000000, - 1000000000000000000, - 10000000000000000000U -}; -#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) -#else -static stbsp__uint64 const stbsp__powten[20] = { - 1, - 10, - 100, - 1000, - 10000, - 100000, - 1000000, - 10000000, - 100000000, - 1000000000, - 10000000000ULL, - 100000000000ULL, - 1000000000000ULL, - 10000000000000ULL, - 100000000000000ULL, - 1000000000000000ULL, - 10000000000000000ULL, - 100000000000000000ULL, - 1000000000000000000ULL, - 10000000000000000000ULL -}; -#define stbsp__tento19th (1000000000000000000ULL) -#endif - -#define stbsp__ddmulthi(oh, ol, xh, yh) \ -{ \ -double ahi = 0, alo, bhi = 0, blo; \ -stbsp__int64 bt; \ -oh = xh * yh; \ -STBSP__COPYFP(bt, xh); \ -bt &= ((~(stbsp__uint64)0) << 27); \ -STBSP__COPYFP(ahi, bt); \ -alo = xh - ahi; \ -STBSP__COPYFP(bt, yh); \ -bt &= ((~(stbsp__uint64)0) << 27); \ -STBSP__COPYFP(bhi, bt); \ -blo = yh - bhi; \ -ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ -} - -#define stbsp__ddtoS64(ob, xh, xl) \ -{ \ -double ahi = 0, alo, vh, t; \ -ob = (stbsp__int64)ph; \ -vh = (double)ob; \ -ahi = (xh - vh); \ -t = (ahi - xh); \ -alo = (xh - (ahi - t)) - (vh + t); \ -ob += (stbsp__int64)(ahi + alo + xl); \ -} - -#define stbsp__ddrenorm(oh, ol) \ -{ \ -double s; \ -s = oh + ol; \ -ol = ol - (s - oh); \ -oh = s; \ -} - -#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); - -#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); - -static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 -{ - double ph, pl; - if ((power >= 0) && (power <= 22)) { - stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); - } else { - stbsp__int32 e, et, eb; - double p2h, p2l; - - e = power; - if (power < 0) - e = -e; - et = (e * 0x2c9) >> 14; /* %23 */ - if (et > 13) - et = 13; - eb = e - (et * 23); - - ph = d; - pl = 0.0; - if (power < 0) { - if (eb) { - --eb; - stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); - stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); - ph = p2h; - pl = p2l; - } - } else { - if (eb) { - e = eb; - if (eb > 22) - eb = 22; - e -= eb; - stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); - if (e) { - stbsp__ddrenorm(ph, pl); - stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); - stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); - ph = p2h; - pl = p2l; - } - } - if (et) { - stbsp__ddrenorm(ph, pl); - --et; - stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); - stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); - ph = p2h; - pl = p2l; - } - } - } - stbsp__ddrenorm(ph, pl); - *ohi = ph; - *olo = pl; -} - -// given a float value, returns the significant bits in bits, and the position of the -// decimal point in decimal_pos. +/-INF and NAN are specified by special values -// returned in the decimal_pos parameter. -// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 -static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) -{ - double d; - stbsp__int64 bits = 0; - stbsp__int32 expo, e, ng, tens; - - d = value; - STBSP__COPYFP(bits, d); - expo = (stbsp__int32)((bits >> 52) & 2047); - ng = (stbsp__int32)((stbsp__uint64) bits >> 63); - if (ng) - d = -d; - - if (expo == 2047) // is nan or inf? - { - *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; - *decimal_pos = STBSP__SPECIAL; - *len = 3; - return ng; - } - - if (expo == 0) // is zero or denormal - { - if (((stbsp__uint64) bits << 1) == 0) // do zero - { - *decimal_pos = 1; - *start = out; - out[0] = '0'; - *len = 1; - return ng; - } - // find the right expo for denormals - { - stbsp__int64 v = ((stbsp__uint64)1) << 51; - while ((bits & v) == 0) { - --expo; - v >>= 1; - } - } - } - - // find the decimal exponent as well as the decimal bits of the value - { - double ph, pl; - - // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 - tens = expo - 1023; - tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); - - // move the significant bits into position and stick them into an int - stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); - - // get full as much precision from double-double as possible - stbsp__ddtoS64(bits, ph, pl); - - // check if we undershot - if (((stbsp__uint64)bits) >= stbsp__tento19th) - ++tens; - } - - // now do the rounding in integer land - frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); - if ((frac_digits < 24)) { - stbsp__uint32 dg = 1; - if ((stbsp__uint64)bits >= stbsp__powten[9]) - dg = 10; - while ((stbsp__uint64)bits >= stbsp__powten[dg]) { - ++dg; - if (dg == 20) - goto noround; - } - if (frac_digits < dg) { - stbsp__uint64 r; - // add 0.5 at the right position and round - e = dg - frac_digits; - if ((stbsp__uint32)e >= 24) - goto noround; - r = stbsp__powten[e]; - bits = bits + (r / 2); - if ((stbsp__uint64)bits >= stbsp__powten[dg]) - ++tens; - bits /= r; - } - noround:; - } - - // kill long trailing runs of zeros - if (bits) { - stbsp__uint32 n; - for (;;) { - if (bits <= 0xffffffff) - break; - if (bits % 1000) - goto donez; - bits /= 1000; - } - n = (stbsp__uint32)bits; - while ((n % 1000) == 0) - n /= 1000; - bits = n; - donez:; - } - - // convert to string - out += 64; - e = 0; - for (;;) { - stbsp__uint32 n; - char *o = out - 8; - // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) - if (bits >= 100000000) { - n = (stbsp__uint32)(bits % 100000000); - bits /= 100000000; - } else { - n = (stbsp__uint32)bits; - bits = 0; - } - while (n) { - out -= 2; - *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; - n /= 100; - e += 2; - } - if (bits == 0) { - if ((e) && (out[0] == '0')) { - ++out; - --e; - } - break; - } - while (out != o) { - *--out = '0'; - ++e; - } - } - - *decimal_pos = tens; - *start = out; - *len = e; - return ng; -} - -#undef stbsp__ddmulthi -#undef stbsp__ddrenorm -#undef stbsp__ddmultlo -#undef stbsp__ddmultlos -#undef STBSP__SPECIAL -#undef STBSP__COPYFP - -#endif // STB_SPRINTF_NOFLOAT - -// clean up -#undef stbsp__uint16 -#undef stbsp__uint32 -#undef stbsp__int32 -#undef stbsp__uint64 -#undef stbsp__int64 -#undef STBSP__UNALIGNED - -#endif // STB_SPRINTF_IMPLEMENTATION - -/* ------------------------------------------------------------------------------- -This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------- -ALTERNATIVE A - MIT License -Copyright (c) 2017 Sean Barrett -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. ------------------------------------------------------------------------------- -ALTERNATIVE B - Public Domain (www.unlicense.org) -This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, -commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to -this software under copyright law. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ------------------------------------------------------------------------------- -*/ diff --git a/src/metagen/metagen.c b/src/metagen/metagen.c new file mode 100644 index 0000000..5473059 --- /dev/null +++ b/src/metagen/metagen.c @@ -0,0 +1,1144 @@ +// 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 + { + B32 is_multiline = (str8_find_needle(lookup_string, 0, str8_lit("\n"), 0) < lookup_string.size); + if(is_multiline) + { + lookup_string = indented_from_string(mg_arena, lookup_string); + lookup_string = escaped_from_raw_str8(mg_arena, lookup_string); + lookup_string = escaped_from_raw_str8(mg_arena, 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) + { + expansion_str = raw_from_escaped_str8(mg_arena, expansion_str); + 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, raw_from_escaped_str8(arena, 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, raw_from_escaped_str8(arena, 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/src/metagen/metagen.h b/src/metagen/metagen.h new file mode 100644 index 0000000..339397c --- /dev/null +++ b/src/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/src/metagen/metagen_base/metagen_base_arena.c b/src/metagen/metagen_base/metagen_base_arena.c new file mode 100644 index 0000000..c2f85f6 --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_arena.c @@ -0,0 +1,248 @@ +// 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 = 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; +#if ARENA_FREE_LIST + arena->free_size = 0; + arena->free_last = 0; +#endif + 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)) + { + Arena *new_block = 0; + +#if ARENA_FREE_LIST + Arena *prev_block; + for(new_block = arena->free_last, prev_block = 0; new_block != 0; prev_block = new_block, new_block = new_block->prev) + { + if(new_block->res >= AlignPow2(size, align)) + { + if(prev_block) + { + prev_block->prev = new_block->prev; + } + else + { + arena->free_last = new_block->prev; + } + arena->free_size -= new_block->res_size; + AsanUnpoisonMemoryRegion((U8*)new_block + ARENA_HEADER_SIZE, new_block->res_size - ARENA_HEADER_SIZE); + break; + } + } +#endif + + if(new_block == 0) + { + U64 res_size = current->res_size; + U64 cmt_size = current->cmt_size; + if(size + ARENA_HEADER_SIZE > res_size) + { + res_size = AlignPow2(size + ARENA_HEADER_SIZE, align); + cmt_size = AlignPow2(size + ARENA_HEADER_SIZE, align); + } + 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) + { + U64 cmt_pst_aligned = pos_pst + current->cmt_size-1; + cmt_pst_aligned -= cmt_pst_aligned%current->cmt_size; + U64 cmt_pst_clamped = ClampTop(cmt_pst_aligned, current->res); + U64 cmt_size = cmt_pst_clamped - current->cmt; + U8 *cmt_ptr = (U8 *)current + current->cmt; + if(current->flags & ArenaFlag_LargePages) + { + os_commit_large(cmt_ptr, cmt_size); + } + else + { + os_commit(cmt_ptr, 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; + +#if ARENA_FREE_LIST + for(Arena *prev = 0; current->base_pos >= big_pos; current = prev) + { + prev = current->prev; + current->pos = ARENA_HEADER_SIZE; + arena->free_size += current->res_size; + SLLStackPush_N(arena->free_last, current, prev); + AsanPoisonMemoryRegion((U8*)current + ARENA_HEADER_SIZE, current->res_size - ARENA_HEADER_SIZE); + } +#else + for(Arena *prev = 0; current->base_pos >= big_pos; current = prev) + { + prev = current->prev; + os_release(current, current->res); + } +#endif + 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/src/metagen/metagen_base/metagen_base_arena.h b/src/metagen/metagen_base/metagen_base_arena.h new file mode 100644 index 0000000..1fef343 --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_arena.h @@ -0,0 +1,91 @@ +// 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 128 + +//////////////////////////////// +//~ rjf: Types + +typedef U64 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; + U64 cmt_size; + U64 res_size; + U64 base_pos; + U64 pos; + U64 cmt; + U64 res; +#if ARENA_FREE_LIST + U64 free_size; + Arena *free_last; +#endif +}; +StaticAssert(sizeof(Arena) <= ARENA_HEADER_SIZE, arena_header_size_check); + +typedef struct Temp Temp; +struct Temp +{ + Arena *arena; + U64 pos; +}; + +//////////////////////////////// +//~ rjf: Global Defaults + +global U64 arena_default_reserve_size = MB(64); +global U64 arena_default_commit_size = KB(64); +global ArenaFlags arena_default_flags = 0; + +//////////////////////////////// +//~ rjf: Arena Functions + +//- rjf: arena creation/destruction +internal Arena *arena_alloc_(ArenaParams *params); +#define arena_alloc(...) arena_alloc_(&(ArenaParams){.reserve_size = arena_default_reserve_size, .commit_size = arena_default_commit_size, .flags = arena_default_flags, __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/src/metagen/metagen_base/metagen_base_command_line.c b/src/metagen/metagen_base/metagen_base_command_line.c new file mode 100644 index 0000000..b1198a8 --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_command_line.c @@ -0,0 +1,250 @@ +// 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 / (only on Windows) 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 if(operating_system_from_context() == OperatingSystem_Windows && + 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; + } + } + + // rjf: fill argc/argv + parsed.argc = command_line.node_count; + parsed.argv = push_array(arena, char *, parsed.argc); + { + U64 idx = 0; + for(String8Node *n = command_line.first; n != 0; n = n->next) + { + parsed.argv[idx] = (char *)push_str8_copy(arena, n->string).str; + idx += 1; + } + } + + 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/src/metagen/metagen_base/metagen_base_command_line.h b/src/metagen/metagen_base/metagen_base_command_line.h new file mode 100644 index 0000000..e30ee13 --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_command_line.h @@ -0,0 +1,56 @@ +// 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; + U64 argc; + char **argv; +}; + +//////////////////////////////// +//~ 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/src/metagen/metagen_base/metagen_base_context_cracking.h b/src/metagen/metagen_base/metagen_base_context_cracking.h new file mode 100644 index 0000000..5707e89 --- /dev/null +++ b/src/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 9 +#endif + +#if !defined(BUILD_VERSION_PATCH) +# define BUILD_VERSION_PATCH 14 +#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/EpicGamesExt/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/src/metagen/metagen_base/metagen_base_core.c b/src/metagen/metagen_base/metagen_base_core.c new file mode 100644 index 0000000..9d140ce --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_core.c @@ -0,0 +1,604 @@ +// 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_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_set32(U32 val) +{ + return __builtin_popcount(val); +} + +internal U64 +count_bits_set64(U64 val) +{ + return __builtin_popcountll(val); +} + +internal U64 +ctz32(U32 val) +{ + return __builtin_ctz(val); +} + +internal U64 +clz32(U32 val) +{ + return __builtin_clz(val); +} + +internal U64 +ctz64(U64 val) +{ + return __builtin_ctzll(val); +} + +internal U64 +clz64(U64 val) +{ + return __builtin_clzll(val); +} + +#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(Arch arch) +{ + // TODO(rjf): metacode + U64 arch_bitsize = 0; + switch(arch) + { + case Arch_x64: arch_bitsize = 64; break; + case Arch_x86: arch_bitsize = 32; break; + case Arch_arm64: arch_bitsize = 64; break; + case Arch_arm32: arch_bitsize = 32; break; + default: break; + } + return arch_bitsize; +} + +internal U64 +max_instruction_size_from_arch(Arch 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 Arch +arch_from_context(void){ + Arch arch = Arch_Null; +#if ARCH_X64 + arch = Arch_x64; +#elif ARCH_X86 + arch = Arch_x86; +#elif ARCH_ARM64 + arch = Arch_arm64; +#elif ARCH_ARM32 + arch = Arch_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); +} + +internal DateTime +date_time_from_unix_time(U64 unix_time) +{ + DateTime date = {0}; + date.year = 1970; + date.day = 1 + (unix_time / 86400); + date.sec = (U32)unix_time % 60; + date.min = (U32)(unix_time / 60) % 60; + date.hour = (U32)(unix_time / 3600) % 24; + + for(;;) + { + for(date.month = 0; date.month < 12; ++date.month) + { + U64 c = 0; + switch(date.month) + { + case Month_Jan: c = 31; break; + case Month_Feb: + { + if((date.year % 4 == 0) && ((date.year % 100) != 0 || (date.year % 400) == 0)) + { + c = 29; + } + else + { + c = 28; + } + } break; + case Month_Mar: c = 31; break; + case Month_Apr: c = 30; break; + case Month_May: c = 31; break; + case Month_Jun: c = 30; break; + case Month_Jul: c = 31; break; + case Month_Aug: c = 31; break; + case Month_Sep: c = 30; break; + case Month_Oct: c = 31; break; + case Month_Nov: c = 30; break; + case Month_Dec: c = 31; break; + default: InvalidPath; + } + if(date.day <= c) + { + goto exit; + } + date.day -= c; + } + ++date.year; + } + exit:; + + return date; +} + +//////////////////////////////// +//~ 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/src/metagen/metagen_base/metagen_base_core.h b/src/metagen/metagen_base/metagen_base_core.h new file mode 100644 index 0000000..d06d967 --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_core.h @@ -0,0 +1,888 @@ +// 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 + +#if COMPILER_MSVC +# define force_inline __forceinline +#elif COMPILER_CLANG || COMPILER_GCC +# define force_inline __attribute__((always_inline)) +#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 EachIndex(it, count) (U64 it = 0; it < (count); it += 1) +#define EachElement(it, array) (U64 it = 0; it < ArrayCount(array); it += 1) +#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 COMPILER_MSVC +# include +# if ARCH_X64 +# define ins_atomic_u64_eval(x) *((volatile U64 *)(x)) +# 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) *((volatile U32 *)(x)) +# define ins_atomic_u32_inc_eval(x) InterlockedIncrement((volatile LONG *)x) +# 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_u32_add_eval(x,c) InterlockedAdd((volatile LONG *)(x), c) +# else +# error Atomic intrinsics not defined for this compiler / architecture combination. +# endif +#elif COMPILER_CLANG || COMPILER_GCC +# define ins_atomic_u64_eval(x) __atomic_load_n(x, __ATOMIC_SEQ_CST) +# define ins_atomic_u64_inc_eval(x) (__atomic_fetch_add((volatile U64 *)(x), 1, __ATOMIC_SEQ_CST) + 1) +# define ins_atomic_u64_dec_eval(x) (__atomic_fetch_sub((volatile U64 *)(x), 1, __ATOMIC_SEQ_CST) - 1) +# define ins_atomic_u64_eval_assign(x,c) __atomic_exchange_n(x, c, __ATOMIC_SEQ_CST) +# define ins_atomic_u64_add_eval(x,c) (__atomic_fetch_add((volatile U64 *)(x), c, __ATOMIC_SEQ_CST) + (c)) +# define ins_atomic_u64_eval_cond_assign(x,k,c) ({ U64 _new = (c); __atomic_compare_exchange_n((volatile U64 *)(x),&_new,(k),0,__ATOMIC_SEQ_CST,__ATOMIC_SEQ_CST); _new; }) +# define ins_atomic_u32_eval(x) __atomic_load_n(x, __ATOMIC_SEQ_CST) +# define ins_atomic_u32_inc_eval(x) (__atomic_fetch_add((volatile U32 *)(x), 1, __ATOMIC_SEQ_CST) + 1) +# define ins_atomic_u32_add_eval(x,c) (__atomic_fetch_add((volatile U32 *)(x), c, __ATOMIC_SEQ_CST) + (c)) +# define ins_atomic_u32_eval_assign(x,c) __atomic_exchange_n(x, c, __ATOMIC_SEQ_CST) +# define ins_atomic_u32_eval_cond_assign(x,k,c) ({ U32 _new = (c); __atomic_compare_exchange_n((volatile U32 *)(x),&_new,(k),0,__ATOMIC_SEQ_CST,__ATOMIC_SEQ_CST); _new; }) +#else +# error Atomic intrinsics not defined for this compiler / architecture. +#endif + +#if ARCH_64BIT +# define ins_atomic_ptr_eval_cond_assign(x,k,c) (void*)ins_atomic_u64_eval_cond_assign((volatile U64 *)(x), (U64)(k), (U64)(c)) +# define ins_atomic_ptr_eval_assign(x,c) (void*)ins_atomic_u64_eval_assign((volatile U64 *)(x), (U64)(c)) +# define ins_atomic_ptr_eval(x) (void*)ins_atomic_u64_eval((volatile U64 *)x) +#elif ARCH_32BIT +# define ins_atomic_ptr_eval_cond_assign(x,k,c) (void*)ins_atomic_u32_eval_cond_assign((volatile U32 *)(x), (U32)(k), (U32)(c)) +# define ins_atomic_ptr_eval_assign(x,c) (void*)ins_atomic_u32_eval_assign((volatile U32 *)(x), (U32)(c)) +# define ins_atomic_ptr_eval(x) (void*)ins_atomic_u32_eval((volatile U32 *)x) +#else +# error Atomic intrinsics for pointers not defined for this architecture. +#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 ImageType +{ + Image_Null, + Image_CoffPe, + Image_Elf32, + Image_Elf64, + Image_Macho +} ImageType; + +typedef enum Arch +{ + Arch_Null, + Arch_x64, + Arch_x86, + Arch_arm64, + Arch_arm32, + Arch_COUNT, +} +Arch; + +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; +}; + +//////////////////////////////// +//~ Globally Unique Ids + +typedef union Guid Guid; +union Guid +{ + struct + { + U32 data1; + U16 data2; + U16 data3; + U8 data4[8]; + }; + U8 v[16]; +}; +StaticAssert(sizeof(Guid) == 16, g_guid_size_check); + +//////////////////////////////// +//~ Arrays + +typedef struct U16Array U16Array; +struct U16Array +{ + U64 count; + U16 *v; +}; +typedef struct U32Array U32Array; +struct U32Array +{ + U64 count; + U32 *v; +}; +typedef struct U64Array U64Array; +struct U64Array +{ + U64 count; + U64 *v; +}; +typedef struct U128Array U128Array; +struct U128Array +{ + U64 count; + U128 *v; +}; + +//////////////////////////////// +//~ 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); + +#if ARCH_LITTLE_ENDIAN +# define from_be_u16(x) bswap_u16(x) +# define from_be_u32(x) bswap_u32(x) +# define from_be_u64(x) bswap_u64(x) +#else +# define from_be_u16(x) (x) +# define from_be_u32(x) (x) +# define from_be_u64(x) (x) +#endif + +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(Arch arch); +internal U64 max_instruction_size_from_arch(Arch arch); + +internal OperatingSystem operating_system_from_context(void); +internal Arch arch_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); +internal DateTime date_time_from_unix_time(U64 unix_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/src/metagen/metagen_base/metagen_base_entry_point.c b/src/metagen/metagen_base/metagen_base_entry_point.c new file mode 100644 index 0000000..6e9bb7f --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_entry_point.c @@ -0,0 +1,130 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +global U64 global_update_tick_idx = 0; + +internal void +main_thread_base_entry_point(int arguments_count, char **arguments) +{ + Temp scratch = scratch_begin(0, 0); + ThreadNameF("[main thread]"); + + //- rjf: set up telemetry +#if PROFILE_TELEMETRY + local_persist char tm_data[MB(64)]; + tmLoadLibrary(TM_RELEASE); + tmSetMaxThreadCount(256); + tmInitialize(sizeof(tm_data), tm_data); +#endif + + //- rjf: parse command line + String8List command_line_argument_strings = os_string_list_from_argcv(scratch.arena, arguments_count, arguments); + CmdLine cmdline = cmd_line_from_string_list(scratch.arena, command_line_argument_strings); + + //- rjf: begin captures + B32 capture = cmd_line_has_flag(&cmdline, str8_lit("capture")); + if(capture) + { + ProfBeginCapture(arguments[0]); + } + +#if PROFILE_TELEMETRY + tmMessage(0, TMMF_ICON_NOTE, BUILD_TITLE); +#endif + + //- rjf: initialize all included layers +#if defined(ASYNC_H) && !defined(ASYNC_INIT_MANUAL) + async_init(&cmdline); +#endif +#if defined(RDI_FROM_PDB_H) && !defined(P2R_INIT_MANUAL) + p2r_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(DBGI_H) && !defined(DI_INIT_MANUAL) + di_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_GFX_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(FNT_INIT_MANUAL) + fnt_init(); +#endif +#if defined(DBG_ENGINE_CORE_H) && !defined(D_INIT_MANUAL) + d_init(); +#endif +#if defined(RADDBG_CORE_H) && !defined(RD_INIT_MANUAL) + rd_init(&cmdline); +#endif + + //- rjf: call into entry point + entry_point(&cmdline); + + //- rjf: end captures + 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(); +} + +internal U64 +update_tick_idx(void) +{ + U64 result = ins_atomic_u64_eval(&global_update_tick_idx); + return result; +} + +internal B32 +update(void) +{ + ProfTick(0); + ins_atomic_u64_inc_eval(&global_update_tick_idx); +#if OS_FEATURE_GRAPHICAL + B32 result = frame(); +#else + B32 result = 0; +#endif + return result; +} diff --git a/src/metagen/metagen_base/metagen_base_entry_point.h b/src/metagen/metagen_base/metagen_base_entry_point.h new file mode 100644 index 0000000..7b3076b --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_entry_point.h @@ -0,0 +1,12 @@ +// 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(int argc, char **argv); +internal void supplement_thread_base_entry_point(void (*entry_point)(void *params), void *params); +internal U64 update_tick_idx(void); +internal B32 update(void); + +#endif // BASE_ENTRY_POINT_H diff --git a/src/metagen/metagen_base/metagen_base_inc.c b/src/metagen/metagen_base/metagen_base_inc.c new file mode 100644 index 0000000..0c53fbc --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_inc.c @@ -0,0 +1,20 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Base Includes + +#undef MARKUP_LAYER_COLOR +#define MARKUP_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_meta.c" +#include "metagen_base_log.c" +#include "metagen_base_entry_point.c" diff --git a/src/metagen/metagen_base/metagen_base_inc.h b/src/metagen/metagen_base/metagen_base_inc.h new file mode 100644 index 0000000..4ca3f8d --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_inc.h @@ -0,0 +1,24 @@ +// 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_meta.h" +#include "metagen_base_log.h" +#include "metagen_base_entry_point.h" + +#endif // BASE_INC_H diff --git a/src/metagen/metagen_base/metagen_base_log.c b/src/metagen/metagen_base/metagen_base_log.c new file mode 100644 index 0000000..addeddc --- /dev/null +++ b/src/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/src/metagen/metagen_base/metagen_base_log.h b/src/metagen/metagen_base/metagen_base_log.h new file mode 100644 index 0000000..52a297d --- /dev/null +++ b/src/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/src/metagen/metagen_base/metagen_base_markup.c b/src/metagen/metagen_base/metagen_base_markup.c new file mode 100644 index 0000000..033fb49 --- /dev/null +++ b/src/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/src/metagen/metagen_base/metagen_base_markup.h b/src/metagen/metagen_base/metagen_base_markup.h new file mode 100644 index 0000000..0f255ef --- /dev/null +++ b/src/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/src/metagen/metagen_base/metagen_base_math.c b/src/metagen/metagen_base/metagen_base_math.c new file mode 100644 index 0000000..5d51768 --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_math.c @@ -0,0 +1,659 @@ +// 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) ? (r.max - r.min) : 0); 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) ? (r.max - r.min) : 0); 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) ? (r.max - r.min) : 0); 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) ? (r.max - r.min) : 0); 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) ? (r.max - r.min) : 0); 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) ? (r.max.x - r.min.x) : 0)), (S16)(((r.max.y > r.min.y) ? (r.max.y - r.min.y) : 0))}; 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.x - r.min.x) : 0), ((r.max.y > r.min.y) ? (r.max.y - r.min.y) : 0)}; 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.x - r.min.x) : 0), ((r.max.y > r.min.y) ? (r.max.y - r.min.y) : 0)}; 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.x - r.min.x) : 0), ((r.max.y > r.min.y) ? (r.max.y - r.min.y) : 0)}; 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 +rng1u64_list_push(Arena *arena, Rng1U64List *list, Rng1U64 rng) +{ + Rng1U64Node *n = push_array(arena, Rng1U64Node, 1); + MemoryCopyStruct(&n->v, &rng); + SLLQueuePush(list->first, list->last, n); + list->count += 1; +} + +internal void +rng1u64_list_concat(Rng1U64List *list, Rng1U64List *to_concat) +{ + if(to_concat->first) + { + if(list->first) + { + list->last->next = to_concat->first; + list->last = to_concat->last; + } + else + { + list->first = to_concat->first; + list->last = to_concat->last; + } + MemoryZeroStruct(to_concat); + } +} + +internal Rng1U64Array +rng1u64_array_from_list(Arena *arena, Rng1U64List *list) +{ + Rng1U64Array arr = {0}; + arr.count = list->count; + arr.v = push_array_no_zero(arena, Rng1U64, arr.count); + U64 idx = 0; + for(Rng1U64Node *n = list->first; n != 0; n = n->next) + { + arr.v[idx] = n->v; + idx += 1; + } + return arr; +} + +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/src/metagen/metagen_base/metagen_base_math.h b/src/metagen/metagen_base/metagen_base_math.h new file mode 100644 index 0000000..8dc0921 --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_math.h @@ -0,0 +1,675 @@ +// 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 Rng1U64Node Rng1U64Node; +struct Rng1U64Node +{ + Rng1U64Node *next; + Rng1U64 v; +}; + +typedef struct Rng1U64List Rng1U64List; +struct Rng1U64List +{ + U64 count; + Rng1U64Node *first; + Rng1U64Node *last; +}; + +typedef struct Rng1U64Array Rng1U64Array; +struct Rng1U64Array +{ + Rng1U64 *v; + U64 count; +}; + +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 rng1u64_list_push(Arena *arena, Rng1U64List *list, Rng1U64 rng); +internal void rng1u64_list_concat(Rng1U64List *list, Rng1U64List *to_concat); +internal Rng1U64Array rng1u64_array_from_list(Arena *arena, Rng1U64List *list); + +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/src/metagen/metagen_base/metagen_base_meta.c b/src/metagen/metagen_base/metagen_base_meta.c new file mode 100644 index 0000000..73bbb59 --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_meta.c @@ -0,0 +1,422 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +//////////////////////////////// +//~ rjf: Type Info Lookups + +internal Member * +member_from_name(Type *type, String8 name) +{ + Member *member = &member_nil; + if(type->members != 0 && name.size != 0) + { + for(U64 idx = 0; idx < type->count; idx += 1) + { + if(str8_match(type->members[idx].name, name, 0)) + { + member = &type->members[idx]; + break; + } + } + } + return member; +} + +//////////////////////////////// +//~ rjf: Type Info * Instance Operations + +internal void +typed_data_rebase_ptrs(Type *type, String8 data, void *base_ptr) +{ + Temp scratch = scratch_begin(0, 0); + typedef struct RebaseTypeTask RebaseTypeTask; + struct RebaseTypeTask + { + RebaseTypeTask *next; + Type *type; + U8 *ptr; + }; + RebaseTypeTask start_task = {0, type, data.str}; + RebaseTypeTask *first_task = &start_task; + RebaseTypeTask *last_task = first_task; + for(RebaseTypeTask *t = first_task; t != 0; t = t->next) + { + switch(t->type->kind) + { + default:{}break; + case TypeKind_Ptr: + if(!(t->type->flags & TypeFlag_IsExternal)) + { + *(U64 *)t->ptr = ((U64)(*(U8 **)t->ptr - (U8 *)base_ptr)); + }break; + case TypeKind_Array: + { + for(U64 idx = 0; idx < t->type->count; idx += 1) + { + RebaseTypeTask *task = push_array(scratch.arena, RebaseTypeTask, 1); + task->type = t->type->direct; + task->ptr = t->ptr + t->type->direct->size * idx; + SLLQueuePush(first_task, last_task, task); + } + }break; + case TypeKind_Struct: + { + for(U64 idx = 0; idx < t->type->count; idx += 1) + { + Member *member = &t->type->members[idx]; + RebaseTypeTask *task = push_array(scratch.arena, RebaseTypeTask, 1); + task->type = member->type; + task->ptr = t->ptr + member->value; + SLLQueuePush(first_task, last_task, task); + } + }break; + } + } + scratch_end(scratch); +} + +internal String8 +serialized_from_typed_data(Arena *arena, Type *type, String8 data, TypeSerializeParams *params) +{ + Temp scratch = scratch_begin(&arena, 1); + String8List strings = {0}; + str8_serial_begin(scratch.arena, &strings); + { + typedef struct SerializeTypeTask SerializeTypeTask; + struct SerializeTypeTask + { + SerializeTypeTask *next; + Type *type; + U64 count; + U8 *src; + Type *containing_type; + U8 *containing_ptr; + B32 is_post_header; + }; + SerializeTypeTask start_task = {0, type, 1, data.str}; + SerializeTypeTask *first_task = &start_task; + SerializeTypeTask *last_task = first_task; + for(SerializeTypeTask *t = first_task; t != 0; t = t->next) + { + switch(t->type->kind) + { + //- rjf: leaf -> just copy the data directly + default: + if(TypeKind_FirstLeaf <= t->type->kind && t->type->kind <= TypeKind_LastLeaf) + { + str8_serial_push_string(scratch.arena, &strings, str8(t->src, t->type->size*t->count)); + }break; + + //- rjf: pointers -> try to interpret/understand pointer & read/write, otherwise just write as plain data + case TypeKind_Ptr: + { + // rjf: unpack info about this pointer + TypeSerializePtrRefInfo *ptr_ref_info = 0; + for(U64 idx = 0; idx < params->ptr_ref_infos_count; idx += 1) + { + if(params->ptr_ref_infos[idx].type == t->type->direct) + { + ptr_ref_info = ¶ms->ptr_ref_infos[idx]; + break; + } + } + + // rjf: indexification -> subtract base, divide direct size, write index + if(ptr_ref_info != 0 && ptr_ref_info->indexify_base != 0) + { + U64 ptr_value = 0; + MemoryCopy(&ptr_value, t->src, sizeof(ptr_value)); + U64 ptr_write_value = ((U64)((U8 *)ptr_value - (U8 *)ptr_ref_info->indexify_base)/t->type->direct->size); + str8_serial_push_struct(scratch.arena, &strings, &ptr_write_value); + } + + // rjf: offsetification -> subtract base, write offsets + else if(ptr_ref_info != 0 && ptr_ref_info->offsetify_base != 0) + { + U64 ptr_value = 0; + MemoryCopy(&ptr_value, t->src, sizeof(ptr_value)); + U64 ptr_write_value = (U64)((U8 *)ptr_value - (U8 *)ptr_ref_info->offsetify_base); + str8_serial_push_struct(scratch.arena, &strings, &ptr_write_value); + } + + // rjf: size-by-member (pre-header): still potentially dependent on other members which + // delimit our size, so push a new post-header task for pointer. + else if(t->type->count_delimiter_name.size != 0 && !t->is_post_header) + { + SerializeTypeTask *task = push_array(scratch.arena, SerializeTypeTask, 1); + task->type = t->type; + task->count = t->count; + task->src = t->src; + task->containing_type = t->containing_type; + task->containing_ptr = t->containing_ptr; + task->is_post_header = 1; + SLLQueuePush(first_task, last_task, task); + } + + // rjf: size-by-member (post-header): all flat parts of containing struct have been + // iterated, so now we can read the size, & descend to new task to read pointer + // destination contents + else if(t->type->count_delimiter_name.size != 0 && t->is_post_header) + { + // rjf: determine count of this pointer + U64 count = 0; + { + Member *count_member = member_from_name(t->containing_type, t->type->count_delimiter_name); + MemoryCopy(&count, t->containing_ptr + count_member->value, count_member->type->size); + } + + // rjf: push task + SerializeTypeTask *task = push_array(scratch.arena, SerializeTypeTask, 1); + task->type = t->type->direct; + task->count = count; + task->src = *(void **)t->src; + task->containing_type = t->containing_type; + task->containing_ptr = t->containing_ptr; + SLLQueuePush(first_task, last_task, task); + } + + // rjf: catch-all: write pointer value + else + { + str8_serial_push_string(scratch.arena, &strings, str8(t->src, t->type->size*t->count)); + } + }break; + + //- rjf: arrays -> descend to underlying type, + count + case TypeKind_Array: + { + SerializeTypeTask *task = push_array(scratch.arena, SerializeTypeTask, 1); + task->type = t->type->direct; + task->count = t->type->count; + task->src = t->src; + task->containing_type = t->containing_type; + task->containing_ptr = t->containing_ptr; + SLLQueuePush(first_task, last_task, task); + }break; + + //- rjf: struct -> descend to members + case TypeKind_Struct: + { + U64 off = 0; + for(U64 idx = 0; idx < t->count; idx += 1) + { + for(U64 member_idx = 0; member_idx < t->type->count; member_idx += 1) + { + if(t->type->members[member_idx].flags & MemberFlag_DoNotSerialize) + { + continue; + } + SerializeTypeTask *task = push_array(scratch.arena, SerializeTypeTask, 1); + task->type = t->type->members[member_idx].type; + task->count = 1; + task->src = t->src + idx*t->type->size + t->type->members[member_idx].value; + task->containing_type = t->type; + task->containing_ptr = t->src; + SLLQueuePush(first_task, last_task, task); + } + } + }break; + + //- rjf: enum -> descend to basic type interpretation + case TypeKind_Enum: + { + SerializeTypeTask *task = push_array(scratch.arena, SerializeTypeTask, 1); + task->type = t->type->direct; + task->count = t->count; + task->src = t->src; + task->containing_type = t->containing_type; + task->containing_ptr = t->containing_ptr; + SLLQueuePush(first_task, last_task, task); + }break; + } + } + } + String8 result = str8_serial_end(arena, &strings); + scratch_end(scratch); + return result; +} + +internal String8 +deserialized_from_typed_data(Arena *arena, Type *type, String8 data, TypeSerializeParams *params) +{ + String8 result = {0}; + result.size = type->size; + result.str = push_array(arena, U8, result.size); + { + Temp scratch = scratch_begin(&arena, 1); + typedef struct DeserializeTypeTask DeserializeTypeTask; + struct DeserializeTypeTask + { + DeserializeTypeTask *next; + Type *type; + U64 count; + U8 *dst; + Type *containing_type; + U8 *containing_ptr; + B32 is_post_header; + }; + U64 read_off = 0; + DeserializeTypeTask start_task = {0, type, 1, result.str}; + DeserializeTypeTask *first_task = &start_task; + DeserializeTypeTask *last_task = first_task; + for(DeserializeTypeTask *t = first_task; t != 0; t = t->next) + { + U8 *t_src = data.str + read_off; + switch(t->type->kind) + { + //- rjf: leaf -> copy the data directly + default: + if(TypeKind_FirstLeaf <= t->type->kind && t->type->kind <= TypeKind_LastLeaf) + { + MemoryCopy(t->dst, t_src, t->type->size*t->count); + read_off += t->type->size*t->count; + }break; + + //- rjf: pointers -> try to interpret/understand pointer & read/write, otherwise skip + case TypeKind_Ptr: + { + // rjf: unpack info about this pointer + TypeSerializePtrRefInfo *ptr_ref_info = 0; + for(U64 idx = 0; idx < params->ptr_ref_infos_count; idx += 1) + { + if(params->ptr_ref_infos[idx].type == t->type->direct) + { + ptr_ref_info = ¶ms->ptr_ref_infos[idx]; + break; + } + } + + // rjf: indexification -> add base, multiply direct size + if(ptr_ref_info != 0 && ptr_ref_info->indexify_base != 0) + { + U64 ptr_value = 0; + MemoryCopy(&ptr_value, t_src, sizeof(ptr_value)); + U64 ptr_write_value = (ptr_value + (U64)ptr_ref_info->indexify_base) * t->type->direct->size; + MemoryCopy(t->dst, &ptr_write_value, sizeof(ptr_write_value)); + read_off += sizeof(ptr_value); + } + + // rjf: offsetification -> subtract base, write offsets + else if(ptr_ref_info != 0 && ptr_ref_info->offsetify_base != 0) + { + U64 ptr_value = 0; + MemoryCopy(&ptr_value, t_src, sizeof(ptr_value)); + U64 ptr_write_value = ptr_value + (U64)ptr_ref_info->offsetify_base; + MemoryCopy(t->dst, &ptr_write_value, sizeof(ptr_write_value)); + read_off += sizeof(ptr_value); + } + + // rjf: size-by-member (pre-header): still potentially dependent on other members which + // delimit our size, so push a new post-header task for pointer. + else if(t->type->count_delimiter_name.size != 0 && !t->is_post_header) + { + DeserializeTypeTask *task = push_array(scratch.arena, DeserializeTypeTask, 1); + task->type = t->type; + task->count = t->count; + task->dst = t->dst; + task->containing_type = t->containing_type; + task->containing_ptr = t->containing_ptr; + task->is_post_header = 1; + SLLQueuePush(first_task, last_task, task); + } + + // rjf: size-by-member (post-header): all flat parts of containing struct have been + // iterated, so now we can read the size, & descend to new task to read pointer + // destination contents + else if(t->type->count_delimiter_name.size != 0 && t->is_post_header) + { + // rjf: determine count of this pointer + U64 count = 0; + { + Member *count_member = member_from_name(t->containing_type, t->type->count_delimiter_name); + MemoryCopy(&count, t->containing_ptr + count_member->value, count_member->type->size); + } + + // rjf: allocate buffer for pointer destination; write address into pointer value slot + U64 ptr_dest_buffer_size = (count+1)*t->type->direct->size; + U8 *ptr_dest_buffer = push_array(arena, U8, ptr_dest_buffer_size); + MemoryCopy(t->dst, &ptr_dest_buffer, sizeof(ptr_dest_buffer)); + + // rjf: push task + DeserializeTypeTask *task = push_array(scratch.arena, DeserializeTypeTask, 1); + task->type = t->type->direct; + task->count = count; + task->dst = ptr_dest_buffer; + task->containing_type = t->containing_type; + task->containing_ptr = t->containing_ptr; + SLLQueuePush(first_task, last_task, task); + } + + // rjf: catch-all: read pointer value + else + { + MemoryCopy(t->dst, t_src, t->type->size*t->count); + read_off += t->type->size*t->count; + } + }break; + + //- rjf: arrays -> descend to underlying type, + count + case TypeKind_Array: + { + DeserializeTypeTask *task = push_array(scratch.arena, DeserializeTypeTask, 1); + task->type = t->type->direct; + task->count = t->type->count; + task->dst = t->dst; + task->containing_type = t->containing_type; + task->containing_ptr = t->containing_ptr; + SLLQueuePush(first_task, last_task, task); + }break; + + //- rjf: struct -> descend to members + case TypeKind_Struct: + { + for(U64 idx = 0; idx < t->count; idx += 1) + { + for(U64 member_idx = 0; member_idx < t->type->count; member_idx += 1) + { + if(t->type->members[member_idx].flags & MemberFlag_DoNotSerialize) + { + continue; + } + DeserializeTypeTask *task = push_array(scratch.arena, DeserializeTypeTask, 1); + task->type = t->type->members[member_idx].type; + task->count = 1; + task->dst = t->dst + idx*t->type->size + t->type->members[member_idx].value; + task->containing_type = t->type; + task->containing_ptr = t->dst; + SLLQueuePush(first_task, last_task, task); + } + } + }break; + + //- rjf: enum -> descend to basic type interpretation + case TypeKind_Enum: + { + DeserializeTypeTask *task = push_array(scratch.arena, DeserializeTypeTask, 1); + task->type = t->type->direct; + task->count = t->count; + task->dst = t->dst; + task->containing_type = t->containing_type; + task->containing_ptr = t->containing_ptr; + SLLQueuePush(first_task, last_task, task); + }break; + } + } + if(params->advance_out != 0) + { + params->advance_out[0] = read_off; + } + scratch_end(scratch); + } + return result; +} + +internal String8 +deep_copy_from_typed_data(Arena *arena, Type *type, String8 data, TypeSerializeParams *params) +{ + Temp scratch = scratch_begin(&arena, 1); + String8 data_srlz = serialized_from_typed_data(scratch.arena, type, data, params); + String8 data_copy = deserialized_from_typed_data(arena, type, data_srlz, params); + scratch_end(scratch); + return data_copy; +} diff --git a/src/metagen/metagen_base/metagen_base_meta.h b/src/metagen/metagen_base/metagen_base_meta.h new file mode 100644 index 0000000..7c8586c --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_meta.h @@ -0,0 +1,298 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#ifndef BASE_META_H +#define BASE_META_H + +//////////////////////////////// +//~ rjf: Meta Markup Features + +#define EmbedFile(name, path) +#define TweakB32(name, default) (TWEAK_##name) +#define TweakF32(name, default, min, max) (TWEAK_##name) + +//////////////////////////////// +//~ rjf: Tweak Info Tables + +typedef struct TweakB32Info TweakB32Info; +struct TweakB32Info +{ + String8 name; + B32 default_value; + B32 *value_ptr; +}; + +typedef struct TweakF32Info TweakF32Info; +struct TweakF32Info +{ + String8 name; + F32 default_value; + Rng1F32 value_range; + F32 *value_ptr; +}; + +typedef struct TweakB32InfoTable TweakB32InfoTable; +struct TweakB32InfoTable +{ + TweakB32Info *v; + U64 count; +}; + +typedef struct TweakF32InfoTable TweakF32InfoTable; +struct TweakF32InfoTable +{ + TweakF32Info *v; + U64 count; +}; + +typedef struct EmbedInfo EmbedInfo; +struct EmbedInfo +{ + String8 name; + String8 *data; + U128 *hash; +}; + +typedef struct EmbedInfoTable EmbedInfoTable; +struct EmbedInfoTable +{ + EmbedInfo *v; + U64 count; +}; + +//////////////////////////////// +//~ rjf: Type Info Types + +typedef enum TypeKind +{ + TypeKind_Null, + + // rjf: leaves + TypeKind_Void, TypeKind_FirstLeaf = TypeKind_Void, + TypeKind_U8, + TypeKind_U16, + TypeKind_U32, + TypeKind_U64, + TypeKind_S8, + TypeKind_S16, + TypeKind_S32, + TypeKind_S64, + TypeKind_B8, + TypeKind_B16, + TypeKind_B32, + TypeKind_B64, + TypeKind_F32, + TypeKind_F64, TypeKind_LastLeaf = TypeKind_F64, + + // rjf: operators + TypeKind_Ptr, + TypeKind_Array, + + // rjf: user-defined-types + TypeKind_Struct, + TypeKind_Union, + TypeKind_Enum, + + TypeKind_COUNT +} +TypeKind; + +typedef U32 TypeFlags; +enum +{ + TypeFlag_IsExternal = (1<<0), + TypeFlag_IsPlainText = (1<<1), + TypeFlag_IsCodeText = (1<<2), + TypeFlag_IsPathText = (1<<3), +}; + +typedef U32 MemberFlags; +enum +{ + MemberFlag_DoNotSerialize = (1<<0), +}; + +typedef struct Type Type; +typedef struct Member Member; +struct Member +{ + String8 name; + String8 pretty_name; + Type *type; + U64 value; + MemberFlags flags; +}; + +typedef struct Type Type; +struct Type +{ + TypeKind kind; + TypeFlags flags; + U64 size; + Type *direct; + String8 name; + String8 count_delimiter_name; // gathered from surrounding members, turns *->[1] into *->[N] + U64 count; + Member *members; +}; + +//////////////////////////////// +//~ rjf: Type Serialization Parameters + +typedef struct TypeSerializePtrRefInfo TypeSerializePtrRefInfo; +struct TypeSerializePtrRefInfo +{ + Type *type; // pointers to this + void *indexify_base; // can be indexified using this + void *offsetify_base; // can be offsetified using this + void *nil_ptr; // is terminal if matching 0 or this +}; + +typedef struct TypeSerializeParams TypeSerializeParams; +struct TypeSerializeParams +{ + U64 *advance_out; + TypeSerializePtrRefInfo *ptr_ref_infos; + U64 ptr_ref_infos_count; +}; + +//////////////////////////////// +//~ rjf: Type Name -> Type Info + +#define type(T) (&T##__type) + +//////////////////////////////// +//~ rjf: Type Info Table Initializer Helpers + +#define member_lit_comp(S, ti, m, ...) {str8_lit_comp(#m), {0}, (ti), OffsetOf(S, m), __VA_ARGS__} +#define struct_members(S) read_only global Member S##__members[] = +#define struct_type(S, ...) read_only global Type S##__type = {TypeKind_Struct, 0, sizeof(S), &type_nil, str8_lit_comp(#S), {0}, ArrayCount(S##__members), S##__members, __VA_ARGS__} +#define named_struct_type(name, S, ...) read_only global Type name##__type = {TypeKind_Struct, 0, sizeof(S), &type_nil, str8_lit_comp(#name), {0}, ArrayCount(name##__members), name##__members, __VA_ARGS__} +#define ptr_type(name, ti, ...) read_only global Type name = {TypeKind_Ptr, 0, sizeof(void *), (ti), __VA_ARGS__} + +//////////////////////////////// +//~ rjf: Globals + +read_only global Type type_nil = {TypeKind_Null, 0, 0, &type_nil}; +read_only global Member member_nil = {{0}, {0}, &type_nil}; + +//////////////////////////////// +//~ rjf: Built-In Types + +//- rjf: leaves +read_only global Type void__type = {TypeKind_Void, 0, 0, &type_nil, str8_lit_comp("void")}; +read_only global Type U8__type = {TypeKind_U8, 0, sizeof(U8), &type_nil, str8_lit_comp("U8")}; +read_only global Type U16__type = {TypeKind_U16, 0, sizeof(U16), &type_nil, str8_lit_comp("U16")}; +read_only global Type U32__type = {TypeKind_U32, 0, sizeof(U32), &type_nil, str8_lit_comp("U32")}; +read_only global Type U64__type = {TypeKind_U64, 0, sizeof(U64), &type_nil, str8_lit_comp("U64")}; +read_only global Type S8__type = {TypeKind_S8, 0, sizeof(S8), &type_nil, str8_lit_comp("S8")}; +read_only global Type S16__type = {TypeKind_S16, 0, sizeof(S16), &type_nil, str8_lit_comp("S16")}; +read_only global Type S32__type = {TypeKind_S32, 0, sizeof(S32), &type_nil, str8_lit_comp("S32")}; +read_only global Type S64__type = {TypeKind_S64, 0, sizeof(S64), &type_nil, str8_lit_comp("S64")}; +read_only global Type B8__type = {TypeKind_B8, 0, sizeof(B8), &type_nil, str8_lit_comp("B8")}; +read_only global Type B16__type = {TypeKind_B16, 0, sizeof(B16), &type_nil, str8_lit_comp("B16")}; +read_only global Type B32__type = {TypeKind_B32, 0, sizeof(B32), &type_nil, str8_lit_comp("B32")}; +read_only global Type B64__type = {TypeKind_B64, 0, sizeof(B64), &type_nil, str8_lit_comp("B64")}; +read_only global Type F32__type = {TypeKind_F32, 0, sizeof(F32), &type_nil, str8_lit_comp("F32")}; +read_only global Type F64__type = {TypeKind_F64, 0, sizeof(F64), &type_nil, str8_lit_comp("F64")}; +read_only global Type *type_kind_type_table[] = +{ + &type_nil, + type(void), + type(U8), + type(U16), + type(U32), + type(U64), + type(S8), + type(S16), + type(S32), + type(S64), + type(B8), + type(B16), + type(B32), + type(B64), + type(F32), + type(F64), + &type_nil, + &type_nil, + &type_nil, + &type_nil, + &type_nil, +}; + +//- rjf: Rng1U64 +struct_members(Rng1U64) +{ + member_lit_comp(Rng1U64, type(U64), min), + member_lit_comp(Rng1U64, type(U64), max), +}; +struct_type(Rng1U64); + +//- rjf: String8 +ptr_type(String8__str_ptr_type, type(U8), str8_lit_comp("size")); +struct_members(String8) +{ + member_lit_comp(String8, &String8__str_ptr_type, str), + member_lit_comp(String8, type(U64), size), +}; +struct_type(String8); + +//- rjf: String8Node +extern Type String8Node__type; +Type String8Node__ptr_type = {TypeKind_Ptr, 0, sizeof(void *), &String8Node__type}; +Member String8Node__members[] = +{ + {str8_lit_comp("next"), {0}, &String8Node__ptr_type, OffsetOf(String8Node, next)}, + {str8_lit_comp("string"), {0}, type(String8), OffsetOf(String8Node, string)}, +}; +Type String8Node__type = +{ + TypeKind_Struct, + 0, + sizeof(String8Node), + &type_nil, + str8_lit_comp("String8Node"), + {0}, + ArrayCount(String8Node__members), + String8Node__members, +}; + +//- rjf: String8List +Member String8List__members[] = +{ + {str8_lit_comp("first"), {0}, &String8Node__ptr_type, OffsetOf(String8List, first)}, + {str8_lit_comp("last"), {0}, &String8Node__ptr_type, OffsetOf(String8List, last), MemberFlag_DoNotSerialize}, + {str8_lit_comp("node_count"), {0}, type(U64), OffsetOf(String8List, node_count)}, + {str8_lit_comp("total_size"), {0}, type(U64), OffsetOf(String8List, total_size)}, +}; +Type String8List__type = +{ + TypeKind_Struct, + 0, + sizeof(String8List), + &type_nil, + str8_lit_comp("String8List"), + {0}, + ArrayCount(String8List__members), + String8List__members, +}; + +//////////////////////////////// +//~ rjf: Type Info Lookups + +internal Member *member_from_name(Type *type, String8 name); +#define EachMember(T, it) (Member *it = (type(T))->members; it != 0 && it < (type(T))->members + (type(T))->count; it += 1) + +//////////////////////////////// +//~ rjf: Type Info * Instance Operations + +internal void typed_data_rebase_ptrs(Type *type, String8 data, void *base_ptr); +internal String8 serialized_from_typed_data(Arena *arena, Type *type, String8 data, TypeSerializeParams *params); +internal String8 deserialized_from_typed_data(Arena *arena, Type *type, String8 data, TypeSerializeParams *params); +internal String8 deep_copy_from_typed_data(Arena *arena, Type *type, String8 data, TypeSerializeParams *params); +#define struct_rebase_ptrs(T, ptr, base) typed_data_rebase_ptrs(type(T), str8_struct(ptr), (base)) +#define serialized_from_struct(arena, T, ptr, ...) serialized_from_typed_data((arena), type(T), str8_struct(ptr), &(TypeSerializeParams){.ptr_ref_infos = 0, __VA_ARGS__}) +#define struct_from_serialized(arena, T, string, ...) (T *)deserialized_from_typed_data((arena), type(T), (string), &(TypeSerializeParams){.ptr_ref_infos = 0, __VA_ARGS__}).str +#define deep_copy_from_struct(arena, T, ptr, ...) (T *)deep_copy_from_typed_data((arena), type(T), str8_struct(ptr), &(TypeSerializeParams){.ptr_ref_infos = 0, __VA_ARGS__}).str + +#endif // BASE_META_H diff --git a/src/metagen/metagen_base/metagen_base_profile.c b/src/metagen/metagen_base/metagen_base_profile.c new file mode 100644 index 0000000..7ea8904 --- /dev/null +++ b/src/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/src/metagen/metagen_base/metagen_base_profile.h b/src/metagen/metagen_base/metagen_base_profile.h new file mode 100644 index 0000000..c06ebbb --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_profile.h @@ -0,0 +1,96 @@ +// 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) +# define ProfBeginV(...) \ + if (TM_API_PTR) { \ + static tm_uint64 file_id = 0; TM_API_PTR->_tmStaticString(&file_id, __FILE__); \ + Temp scratch = scratch_begin(0,0); \ + String8 string = push_str8f(scratch.arena, __VA_ARGS__); \ + tm_uint64 hash = TM_API_PTR->_tmHash((char*)string.str, string.size); \ + hash = TM_API_PTR->_tmSendDynamicString(hash, (char*)string.str); \ + TM_API_PTR->_tmEnterZoneFast_Core(0, 0, file_id, __LINE__, hash); \ + scratch_end(scratch); \ + } +# define ProfNoteV(...) \ + if (TM_API_PTR) { \ + static tm_uint64 file_id = 0; TM_API_PTR->_tmStaticString(&file_id, __FILE__); \ + Temp scratch = scratch_begin(0,0); \ + String8 string = push_str8f(scratch.arena, __VA_ARGS__); \ + tm_uint64 hash = TM_API_PTR->_tmHash((char*)string.str, string.size); \ + hash = TM_API_PTR->_tmSendDynamicString(hash, (char*)string.str); \ + TM_API_PTR->_tmMessageFast_Core(0, TMMF_ICON_NOTE, file_id, __LINE__, hash); \ + scratch_end(scratch); \ + } +#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) +# define ProfBeginV(...) (0) +# define ProfNoteV(...) (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/src/metagen/metagen_base/metagen_base_strings.c b/src/metagen/metagen_base/metagen_base_strings.c new file mode 100644 index 0000000..9310ab2 --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_strings.c @@ -0,0 +1,2446 @@ +// 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 = str8((U8*)cstr, size); + return result; +} + +internal String16 +str16_cstring_capped(void *cstr, void *cap) +{ + U16 *ptr = (U16 *)cstr; + U16 *opl = (U16 *)cap; + for (;ptr < opl && *ptr != 0; ptr += 1); + U64 size = (U64)(ptr - (U16 *)cstr); + String16 result = str16(cstr, size); + return result; +} + +internal String8 +str8_cstring_capped_reverse(void *raw_start, void *raw_cap) +{ + U8 *start = raw_start; + U8 *ptr = raw_cap; + for(; ptr > start; ) + { + ptr -= 1; + + if (*ptr == '\0') + { + break; + } + } + U64 size = (U64)(ptr - start); + String8 result = str8(start, 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 == 0) + { + result = MemoryMatch(a.str, b.str, b.size); + } + else 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 U64 +str8_find_needle_reverse(String8 string, U64 start_pos, String8 needle, StringMatchFlags flags) +{ + U64 result = 0; + for(S64 i = string.size - start_pos - needle.size; i >= 0; --i) + { + String8 haystack = str8_substr(string, rng_1u64(i, i + needle.size)); + if(str8_match(haystack, needle, flags)) + { + result = (U64)i + needle.size; + break; + } + } + 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 U32 +u32_from_str8(String8 string, U32 radix) +{ + U64 x64 = u64_from_str8(string, radix); + U32 x32 = safe_cast_u32(x64); + return x32; +} + +internal S32 +s32_from_str8(String8 string, U32 radix) +{ + S64 x64 = s64_from_str8(string, radix); + S32 x32 = safe_cast_s32(x64); + return x32; +} + +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 size) +{ + String8 result; + + if(size < KB(1)) + { + result = push_str8f(arena, "%llu Bytes", size); + } + else if(size < MB(1)) + { + result = push_str8f(arena, "%llu.%02llu KiB", size / KB(1), ((size * 100) / KB(1)) % 100); + } + else if(size < GB(1)) + { + result = push_str8f(arena, "%llu.%02llu MiB", size / MB(1), ((size * 100) / MB(1)) % 100); + } + else if(size < TB(1)) + { + result = push_str8f(arena, "%llu.%02llu GiB", size / GB(1), ((size * 100) / GB(1)) % 100); + } + else + { + result = push_str8f(arena, "%llu.%02llu TiB", size / TB(1), ((size * 100) / TB(1)) % 100); + } + + return result; +} + +internal String8 +str8_from_count(Arena *arena, U64 count) +{ + String8 result; + + if(count < 1 * 1000) + { + result = push_str8f(arena, "%llu", count); + } + else if(count < 1000000) + { + U64 frac = ((count * 100) / 1000) % 100; + if(frac > 0) + { + result = push_str8f(arena, "%llu.%02lluK", count / 1000, frac); + } + else + { + result = push_str8f(arena, "%lluK", count / 1000); + } + } + else if(count < 1000000000) + { + U64 frac = ((count * 100) / 1000000) % 100; + if(frac > 0) + { + result = push_str8f(arena, "%llu.%02lluM", count / 1000000, frac); + } + else + { + result = push_str8f(arena, "%lluM", count / 1000000); + } + } + else + { + U64 frac = ((count * 100) * 1000000000) % 100; + if(frac > 0) + { + result = push_str8f(arena, "%llu.%02lluB", count / 1000000000, frac); + } + else + { + result = push_str8f(arena, "%lluB", count / 1000000000, frac); + } + } + + return result; +} + +internal String8 +str8_from_bits_u32(Arena *arena, U32 x) +{ + U8 c0 = 'a' + ((x >> 28) & 0xf); + U8 c1 = 'a' + ((x >> 24) & 0xf); + U8 c2 = 'a' + ((x >> 20) & 0xf); + U8 c3 = 'a' + ((x >> 16) & 0xf); + U8 c4 = 'a' + ((x >> 12) & 0xf); + U8 c5 = 'a' + ((x >> 8) & 0xf); + U8 c6 = 'a' + ((x >> 4) & 0xf); + U8 c7 = 'a' + ((x >> 0) & 0xf); + String8 result = push_str8f(arena, "%c%c%c%c%c%c%c%c", c0, c1, c2, c3, c4, c5, c6, c7); + return result; +} + +internal String8 +str8_from_bits_u64(Arena *arena, U64 x) +{ + U8 c0 = 'a' + ((x >> 60) & 0xf); + U8 c1 = 'a' + ((x >> 56) & 0xf); + U8 c2 = 'a' + ((x >> 52) & 0xf); + U8 c3 = 'a' + ((x >> 48) & 0xf); + U8 c4 = 'a' + ((x >> 44) & 0xf); + U8 c5 = 'a' + ((x >> 40) & 0xf); + U8 c6 = 'a' + ((x >> 36) & 0xf); + U8 c7 = 'a' + ((x >> 32) & 0xf); + U8 c8 = 'a' + ((x >> 28) & 0xf); + U8 c9 = 'a' + ((x >> 24) & 0xf); + U8 ca = 'a' + ((x >> 20) & 0xf); + U8 cb = 'a' + ((x >> 16) & 0xf); + U8 cc = 'a' + ((x >> 12) & 0xf); + U8 cd = 'a' + ((x >> 8) & 0xf); + U8 ce = 'a' + ((x >> 4) & 0xf); + U8 cf = 'a' + ((x >> 0) & 0xf); + String8 result = push_str8f(arena, + "%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", + c0, c1, c2, c3, c4, c5, c6, c7, c8, c9, ca, cb, cc, cd, ce, cf); + 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; + if(string.str[0] == '-') + { + sign = -1.0; + } + else if(string.str[0] == '+') + { + sign = 1.0; + } + + // rjf: gather numerics + U64 num_valid_chars = 0; + char buffer[64]; + B32 exp = 0; + 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] == '.' || string.str[idx] == 'e' || + (exp && (string.str[idx] == '+' || string.str[idx] == '-'))) + { + buffer[num_valid_chars] = string.str[idx]; + num_valid_chars += 1; + exp = 0; + exp = (string.str[idx] == 'e'); + } + } + + // 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_Null:{}break; + 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) +{ + String8 result = str8_zero(); + if(in.size) + { + 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)); + result = str8(str, size); + } + return result; +} + +internal String16 +str16_from_8(Arena *arena, String8 in) +{ + String16 result = str16_zero(); + if(in.size) + { + 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); + result = str16(str, size); + } + return result; +} + +internal String8 +str8_from_32(Arena *arena, String32 in) +{ + String8 result = str8_zero(); + if(in.size) + { + 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)); + result = str8(str, size); + } + return result; +} + +internal String32 +str32_from_8(Arena *arena, String8 in) +{ + String32 result = str32_zero(); + if(in.size) + { + 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); + result = str32(str, size); + } + return result; +} + +//////////////////////////////// +//~ String -> Enum Conversions + +read_only global struct +{ + String8 string; + OperatingSystem os; +} g_os_enum_map[] = +{ + { str8_lit_comp(""), OperatingSystem_Null }, + { str8_lit_comp("Windows"), OperatingSystem_Windows, }, + { str8_lit_comp("Linux"), OperatingSystem_Linux, }, + { str8_lit_comp("Mac"), OperatingSystem_Mac, }, +}; +StaticAssert(ArrayCount(g_os_enum_map) == OperatingSystem_COUNT, g_os_enum_map_count_check); + +internal OperatingSystem +operating_system_from_string(String8 string) +{ + for(U64 i = 0; i < ArrayCount(g_os_enum_map); ++i) + { + if(str8_match(g_os_enum_map[i].string, string, StringMatchFlag_CaseInsensitive)) + { + return g_os_enum_map[i].os; + } + } + return OperatingSystem_Null; +} + +//////////////////////////////// +//~ 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) +{ + String8 result = g_os_enum_map[OperatingSystem_Null].string; + if(os < ArrayCount(g_os_enum_map)) + { + result = g_os_enum_map[os].string; + } + return result; +} + +internal String8 +string_from_arch(Arch 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 < Arch_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); +} + +//////////////////////////////// +//~ Globally UNique Ids + +internal String8 +string_from_guid(Arena *arena, 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; +} + +internal B32 +try_guid_from_string(String8 string, Guid *guid_out) +{ + Temp scratch = scratch_begin(0,0); + B32 is_parsed = 0; + String8List list = str8_split_by_string_chars(scratch.arena, string, str8_lit("-"), StringSplitFlag_KeepEmpties); + if(list.node_count == 5) + { + String8 data1_str = list.first->string; + String8 data2_str = list.first->next->string; + String8 data3_str = list.first->next->next->string; + String8 data4_hi_str = list.first->next->next->next->string; + String8 data4_lo_str = list.first->next->next->next->next->string; + if(str8_is_integer(data1_str, 16) && + str8_is_integer(data2_str, 16) && + str8_is_integer(data3_str, 16) && + str8_is_integer(data4_hi_str, 16) && + str8_is_integer(data4_lo_str, 16)) + { + U64 data1 = u64_from_str8(data1_str, 16); + U64 data2 = u64_from_str8(data2_str, 16); + U64 data3 = u64_from_str8(data3_str, 16); + U64 data4_hi = u64_from_str8(data4_hi_str, 16); + U64 data4_lo = u64_from_str8(data4_lo_str, 16); + if(data1 <= max_U32 && + data2 <= max_U16 && + data3 <= max_U16 && + data4_hi <= max_U16 && + data4_lo <= 0xffffffffffff) + { + guid_out->data1 = (U32)data1; + guid_out->data2 = (U16)data2; + guid_out->data3 = (U16)data3; + U64 data4 = (data4_hi << 48) | data4_lo; + MemoryCopy(&guid_out->data4[0], &data4, sizeof(data4)); + is_parsed = 1; + } + } + } + scratch_end(scratch); + return is_parsed; +} + +internal Guid +guid_from_string(String8 string) +{ + Guid guid = {0}; + try_guid_from_string(string, &guid); + return guid; +} + +//////////////////////////////// +//~ 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; +} + +internal U64 +str8_deserial_read_uleb128(String8 string, U64 off, U64 *value_out) +{ + U64 value = 0; + U64 shift = 0; + U64 cursor = off; + for(;;) + { + U8 byte = 0; + U64 bytes_read = str8_deserial_read_struct(string, cursor, &byte); + + if(bytes_read != sizeof(byte)) + { + break; + } + + U8 val = byte & 0x7fu; + value |= ((U64)val) << shift; + + cursor += bytes_read; + shift += 7u; + + if((byte & 0x80u) == 0) + { + break; + } + } + if(value_out != 0) + { + *value_out = value; + } + U64 bytes_read = cursor - off; + return bytes_read; +} + +internal U64 +str8_deserial_read_sleb128(String8 string, U64 off, S64 *value_out) +{ + U64 value = 0; + U64 shift = 0; + U64 cursor = off; + for(;;) + { + U8 byte; + U64 bytes_read = str8_deserial_read_struct(string, cursor, &byte); + if(bytes_read != sizeof(byte)) + { + break; + } + + U8 val = byte & 0x7fu; + value |= ((U64)val) << shift; + + cursor += bytes_read; + shift += 7u; + + if((byte & 0x80u) == 0) + { + if(shift < sizeof(value) * 8 && (byte & 0x40u) != 0) + { + value |= -(S64)(1ull << shift); + } + break; + } + } + if(value_out != 0) + { + *value_out = value; + } + U64 bytes_read = cursor - off; + return bytes_read; +} + diff --git a/src/metagen/metagen_base/metagen_base_strings.h b/src/metagen/metagen_base/metagen_base_strings.h new file mode 100644 index 0000000..cdcfacf --- /dev/null +++ b/src/metagen/metagen_base/metagen_base_strings.h @@ -0,0 +1,412 @@ +// 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_Null, + 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); +internal String16 str16_cstring_capped(void *cstr, void *cap); +internal String8 str8_cstring_capped_reverse(void *raw_start, void *raw_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 U64 str8_find_needle_reverse(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 U32 u32_from_str8(String8 string, U32 radix); +internal S32 s32_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 size); +internal String8 str8_from_count(Arena *arena, U64 count); +internal String8 str8_from_bits_u32(Arena *arena, U32 x); +internal String8 str8_from_bits_u64(Arena *arena, U64 x); +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); + +//////////////////////////////// +//~ String -> Enum Conversions + +internal OperatingSystem operating_system_from_string(String8 string); + +//////////////////////////////// +//~ 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_arch(Arch 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); + +//////////////////////////////// +//~ Globally Unique Ids + +internal String8 string_from_guid(Arena *arena, Guid guid); +internal B32 try_guid_from_string(String8 string, Guid *guid_out); +internal Guid guid_from_string(String8 string); + +//////////////////////////////// +//~ rjf: Basic Text Indentation + +internal String8 indented_from_string(Arena *arena, String8 string); + +//////////////////////////////// +//~ rjf: Text Escaping + +internal String8 escaped_from_raw_str8(Arena *arena, String8 string); +internal String8 raw_from_escaped_str8(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); +internal U64 str8_deserial_read_uleb128(String8 string, U64 off, U64 *value_out); +internal U64 str8_deserial_read_sleb128(String8 string, U64 off, S64 *value_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_array(string, off, ptr, 1) + +#endif // BASE_STRINGS_H diff --git a/src/metagen/metagen_base/metagen_base_thread_context.c b/src/metagen/metagen_base/metagen_base_thread_context.c new file mode 100644 index 0000000..45ab7af --- /dev/null +++ b/src/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/src/metagen/metagen_base/metagen_base_thread_context.h b/src/metagen/metagen_base/metagen_base_thread_context.h new file mode 100644 index 0000000..5079d6b --- /dev/null +++ b/src/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 countt); + +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/src/metagen/metagen_main.c b/src/metagen/metagen_main.c new file mode 100644 index 0000000..fa8c501 --- /dev/null +++ b/src/metagen/metagen_main.c @@ -0,0 +1,652 @@ +// 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_base/metagen_base_inc.h" +#include "metagen_os/metagen_os_inc.h" +#include "../mdesk/mdesk.h" +#include "metagen.h" + +//- rjf: impls +#include "metagen_base/metagen_base_inc.c" +#include "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) + { + str8_list_pushf(mg_arena, &layer->enums, "%S_%S,\n", enum_member_prefix, n->string); + } + 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) + { + str8_list_pushf(mg_arena, &layer->enums, "X(%S)\\\n", n->string); + } + 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) + { + str8_list_pushf(mg_arena, &layer->structs, "%S;\n", n->string); + } + 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) + { + str8_list_pushf(mg_arena, &layer->c_tables, "%S,\n", n->string); + } + 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) + { + str8_list_pushf(mg_arena, &layer->c_functions, "%S;\n", n->string); + } + 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); + str8_list_push(mg_arena, out, trimmed); + 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_pushf(mg_arena, &layer->h_tables, "read_only global const char* %S =\n", node->string); + str8_list_push (mg_arena, &layer->h_tables, embed_string); + str8_list_pushf(mg_arena, &layer->h_tables, ";\n"); + //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/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c new file mode 100644 index 0000000..55b6e13 --- /dev/null +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.c @@ -0,0 +1,1337 @@ +// 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)); + free(cwdir); + return string; +} + +internal U32 +os_get_process_start_time_unix(void) +{ + Temp scratch = scratch_begin(0,0); + U64 start_time = 0; + pid_t pid = getpid(); + String8 path = push_str8f(scratch.arena, "/proc/%u", pid); + struct stat st; + int err = stat((char*)path.str, &st); + if(err == 0) + { + start_time = st.st_mtime; + } + scratch_end(scratch); + return (U32)start_time; +} + +//////////////////////////////// +//~ 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); + if(result == MAP_FAILED) + { + result = 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); + if(result == MAP_FAILED) + { + result = 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 = gettid(); + 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 && flags & 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; + } + if(flags & (OS_AccessFlag_Write|OS_AccessFlag_Append)) + { + lnx_flags |= O_CREAT; + } + int fd = open((char *)path_copy.str, lnx_flags, 0755); + 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]; + 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 = pread(fd, (U8 *)out_data + total_num_bytes_read, total_num_bytes_left_to_read, rng.min + total_num_bytes_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]; + 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 = pwrite(fd, (U8 *)data + total_num_bytes_written, total_num_bytes_left_to_write, rng.min + total_num_bytes_written); + 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())) + { + int src_fd = (int)src_h.u64[0]; + int dst_fd = (int)dst_h.u64[0]; + 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;) + { + off_t sendfile_off = total_bytes_copied; + int send_result = sendfile(dst_fd, src_fd, &sendfile_off, bytes_left_to_copy); + if(send_result <= 0) + { + break; + } + U64 bytes_copied = (U64)send_result; + bytes_left_to_copy -= bytes_copied; + total_bytes_copied += bytes_copied; + } + } + os_file_close(src_h); + os_file_close(dst_h); + return result; +} + +internal B32 +os_move_file_path(String8 dst, String8 src) +{ + // TODO(rjf) + return 0; +} + +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 B32 +os_folder_path_exists(String8 path) +{ + Temp scratch = scratch_begin(0, 0); + B32 exists = 0; + String8 path_copy = push_str8_copy(scratch.arena, path); + DIR *handle = opendir((char*)path_copy.str); + if(handle) + { + closedir(handle); + exists = 1; + } + scratch_end(scratch); + return exists; +} + +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); + if(base == MAP_FAILED) + { + base = 0; + } + 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, 0755) != -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); + if(base == MAP_FAILED) + { + base = 0; + } + 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; + { + int pthread_result = pthread_create(&entity->thread.handle, 0, os_lnx_thread_entry_point, entity); + 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) +{ + OS_Handle result = {0}; + if (name.size > 0) { + // TODO: we need to allocate shared memory to store sem_t + NotImplemented; + } else { + sem_t *s = mmap(0, sizeof(*s), PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); + AssertAlways(s != MAP_FAILED); + int err = sem_init(s, 0, initial_count); + if (err == 0) { + result.u64[0] = (U64)s; + } + } + return result; +} + +internal void +os_semaphore_release(OS_Handle semaphore) +{ + int err = munmap((void*)semaphore.u64[0], sizeof(sem_t)); + AssertAlways(err == 0); +} + +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) +{ + AssertAlways(endt_us == max_U64); + for (;;) { + int err = sem_wait((sem_t*)semaphore.u64[0]); + if (err == 0) { + break; + } else { + if (errno == EAGAIN) { + continue; + } + } + InvalidPath; + break; + } + return 1; +} + +internal void +os_semaphore_drop(OS_Handle semaphore) +{ + for (;;) { + int err = sem_post((sem_t*)semaphore.u64[0]); + if (err == 0) { + break; + } else { + if (errno == EAGAIN) { + continue; + } + } + InvalidPath; + break; + } +} + +//////////////////////////////// +//~ 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|RTLD_LOCAL); + 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 Guid +os_make_guid(void) +{ + Guid guid = {0}; + getrandom(guid.v, sizeof(guid.v), 0); + 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(argc, argv); +} diff --git a/src/metagen/metagen_os/core/linux/metagen_os_core_linux.h b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.h new file mode 100644 index 0000000..694a782 --- /dev/null +++ b/src/metagen/metagen_os/core/linux/metagen_os_core_linux.h @@ -0,0 +1,137 @@ +// 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 +#include +#include + +pid_t gettid(void); +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/src/metagen/metagen_os/core/metagen_os_core.c b/src/metagen/metagen_os/core/metagen_os_core.c new file mode 100644 index 0000000..79e2c0c --- /dev/null +++ b/src/metagen/metagen_os/core/metagen_os_core.c @@ -0,0 +1,240 @@ +// 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: Process Launcher Helpers + +internal OS_Handle +os_cmd_line_launch(String8 string) +{ + Temp scratch = scratch_begin(0, 0); + U8 split_chars[] = {' '}; + String8List parts = str8_split(scratch.arena, string, split_chars, ArrayCount(split_chars), 0); + OS_Handle handle = {0}; + if(parts.node_count != 0) + { + // rjf: unpack exe part + String8 exe = parts.first->string; + String8 exe_folder = str8_chop_last_slash(exe); + if(exe_folder.size == 0) + { + exe_folder = os_get_current_path(scratch.arena); + } + + // rjf: find stdout delimiter + String8Node *stdout_delimiter_n = 0; + for(String8Node *n = parts.first; n != 0; n = n->next) + { + if(str8_match(n->string, str8_lit(">"), 0)) + { + stdout_delimiter_n = n; + break; + } + } + + // rjf: read stdout path + String8 stdout_path = {0}; + if(stdout_delimiter_n && stdout_delimiter_n->next) + { + stdout_path = stdout_delimiter_n->next->string; + } + + // rjf: open stdout handle + OS_Handle stdout_handle = {0}; + if(stdout_path.size != 0) + { + OS_Handle file = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Read, stdout_path); + os_file_close(file); + stdout_handle = os_file_open(OS_AccessFlag_Write|OS_AccessFlag_Append|OS_AccessFlag_ShareRead|OS_AccessFlag_ShareWrite|OS_AccessFlag_Inherited, stdout_path); + } + + // rjf: form command line + String8List cmdline = {0}; + for(String8Node *n = parts.first; n != stdout_delimiter_n && n != 0; n = n->next) + { + str8_list_push(scratch.arena, &cmdline, n->string); + } + + // rjf: launch + OS_ProcessLaunchParams params = {0}; + params.cmd_line = cmdline; + params.path = exe_folder; + params.inherit_env = 1; + params.stdout_file = stdout_handle; + handle = os_process_launch(¶ms); + + // rjf: close stdout handle + { + if(stdout_path.size != 0) + { + os_file_close(stdout_handle); + } + } + } + scratch_end(scratch); + return handle; +} + +internal OS_Handle +os_cmd_line_launchf(char *fmt, ...) +{ + Temp scratch = scratch_begin(0, 0); + va_list args; + va_start(args, fmt); + String8 string = push_str8fv(scratch.arena, fmt, args); + OS_Handle result = os_cmd_line_launch(string); + va_end(args); + scratch_end(scratch); + return result; +} + diff --git a/src/metagen/metagen_os/core/metagen_os_core.h b/src/metagen/metagen_os/core/metagen_os_core.h new file mode 100644 index 0000000..64b8e1d --- /dev/null +++ b/src/metagen/metagen_os/core/metagen_os_core.h @@ -0,0 +1,335 @@ +// 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; + B32 large_pages_allowed; + 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), + OS_AccessFlag_Inherited = (1<<6), +}; + +//////////////////////////////// +//~ 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: 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: Process Launch Parameters + +typedef struct OS_ProcessLaunchParams OS_ProcessLaunchParams; +struct OS_ProcessLaunchParams +{ + String8List cmd_line; + String8 path; + String8List env; + B32 inherit_env; + B32 debug_subprocesses; + B32 consoleless; + OS_Handle stdout_file; + OS_Handle stderr_file; + OS_Handle stdin_file; +}; + +//////////////////////////////// +//~ 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: Process Launcher Helpers + +internal OS_Handle os_cmd_line_launch(String8 string); +internal OS_Handle os_cmd_line_launchf(char *fmt, ...); + +//////////////////////////////// +//~ 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); +internal U32 os_get_process_start_time_unix(void); + +//////////////////////////////// +//~ 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); +#define os_file_read_struct(f, off, ptr) os_file_read((f), r1u64((off), (off)+sizeof(*(ptr))), (ptr)) +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_file_reserve_size(OS_Handle file, U64 size); +internal B32 os_delete_file_at_path(String8 path); +internal B32 os_copy_file_path(String8 dst, String8 src); +internal B32 os_move_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 B32 os_folder_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 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/src/metagen/metagen_os/core/win32/metagen_os_core_win32.c b/src/metagen/metagen_os/core/win32/metagen_os_core_win32.c new file mode 100644 index 0000000..3e10adc --- /dev/null +++ b/src/metagen/metagen_os/core/win32/metagen_os_core_win32.c @@ -0,0 +1,1785 @@ +// 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; +} + +internal U32 +os_w32_unix_time_from_file_time(FILETIME file_time) +{ + U64 win32_time = ((U64)file_time.dwHighDateTime << 32) | file_time.dwLowDateTime; + U64 unix_time64 = ((win32_time - 0x19DB1DED53E8000ULL) / 10000000); + + Assert(unix_time64 <= max_U32); + U32 unix_time32 = (U32)unix_time64; + + return unix_time32; +} + +//////////////////////////////// +//~ 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; +} + +internal U32 +os_get_process_start_time_unix(void) +{ + HANDLE handle = GetCurrentProcess(); + FILETIME start_time = {0}; + FILETIME exit_time; + FILETIME kernel_time; + FILETIME user_time; + if(GetProcessTimes(handle, &start_time, &exit_time, &kernel_time, &user_time)) + { + return os_w32_unix_time_from_file_time(start_time); + } + return 0; +} + +//////////////////////////////// +//~ 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; + SECURITY_ATTRIBUTES security_attributes = {sizeof(security_attributes), 0, 0}; + 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; access_flags |= FILE_APPEND_DATA; } + if(flags & OS_AccessFlag_Inherited) + { + security_attributes.bInheritHandle = 1; + } + HANDLE file = CreateFileW((WCHAR *)path16.str, access_flags, share_mode, &security_attributes, 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 total_write_size = dim_1u64(rng); + for(;;) + { + void *bytes_src = (U8 *)data + src_off; + U64 bytes_left = total_write_size - src_off; + DWORD write_size = Min(MB(1), bytes_left); + DWORD bytes_written = 0; + OVERLAPPED overlapped = {0}; + overlapped.Offset = (dst_off&0x00000000ffffffffull); + overlapped.OffsetHigh = (dst_off&0xffffffff00000000ull) >> 32; + BOOL success = WriteFile(win_handle, bytes_src, write_size, &bytes_written, &overlapped); + if(success == 0) + { + break; + } + src_off += bytes_written; + dst_off += bytes_written; + if(bytes_left == 0) + { + break; + } + } + return src_off; +} + +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_file_reserve_size(OS_Handle file, U64 size) +{ + HANDLE handle = (HANDLE)file.u64[0]; + + FILE_ALLOCATION_INFO alloc_info = {0}; + alloc_info.AllocationSize.LowPart = size & max_U32; + alloc_info.AllocationSize.HighPart = (size >> 32) & max_U32; + + BOOL is_reserved = SetFileInformationByHandle(handle, FileAllocationInfo, &alloc_info, sizeof(alloc_info)); + return is_reserved; +} + +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 B32 +os_move_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 = MoveFileW((WCHAR*)src16.str, (WCHAR*)dst16.str); + 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(MAX_PATH, path.size * 2) + 1; + String16 path16 = str16_from_8(scratch.arena, path); + WCHAR *buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size); + DWORD path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL); + if(path16_size > buffer_size) + { + arena_pop(scratch.arena, buffer_size); + buffer_size = path16_size + 1; + buffer = push_array_no_zero(scratch.arena, WCHAR, buffer_size); + path16_size = GetFullPathNameW((WCHAR*)path16.str, buffer_size, buffer, NULL); + } + String8 full_path = str8_from_16(arena, str16((U16*)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 B32 +os_folder_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); + } + else + { + Temp scratch = scratch_begin(0, 0); + WCHAR buffer[512] = {0}; + DWORD length = GetLogicalDriveStringsW(sizeof(buffer), buffer); + U64 last_slash_pos = 0; + for(;last_slash_pos < path.size; last_slash_pos = str8_find_needle(path, last_slash_pos+1, str8_lit("/"), StringMatchFlag_SlashInsensitive)); + String8 path_trimmed = str8_prefix(path, last_slash_pos); + 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(scratch.arena, next_drive_string_16); + next_drive_string = str8_chop_last_slash(next_drive_string); + if(str8_match(path_trimmed, next_drive_string, StringMatchFlag_CaseInsensitive)) + { + props.flags |= FilePropertyFlag_IsFolder; + break; + } + } + scratch_end(scratch); + } + 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; + HANDLE zero_handle; + MemoryZeroStruct(&zero_handle); + if(!MemoryMatchStruct(&zero_handle, &w32_iter->handle)) + { + 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); + U32 unix_time = os_w32_unix_time_from_file_time(file_time); + return unix_time; +} + +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 + BOOL inherit_handles = 0; + STARTUPINFOW startup_info = {sizeof(startup_info)}; + if(!os_handle_match(params->stdout_file, os_handle_zero())) + { + HANDLE stdout_handle = (HANDLE)params->stdout_file.u64[0]; + startup_info.hStdOutput = stdout_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + if(!os_handle_match(params->stderr_file, os_handle_zero())) + { + HANDLE stderr_handle = (HANDLE)params->stderr_file.u64[0]; + startup_info.hStdError = stderr_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + if(!os_handle_match(params->stdin_file, os_handle_zero())) + { + HANDLE stdin_handle = (HANDLE)params->stdin_file.u64[0]; + startup_info.hStdInput = stdin_handle; + startup_info.dwFlags |= STARTF_USESTDHANDLES; + inherit_handles = 1; + } + PROCESS_INFORMATION process_info = {0}; + if(CreateProcessW(0, (WCHAR*)cmd16.str, 0, 0, inherit_handles, 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); + CloseHandle(entity->thread.handle); + 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]); + if(entity != 0) + { + CloseHandle(entity->thread.handle); + 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 Guid +os_make_guid(void) +{ + 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; + + WCHAR module_path[MAX_PATH]; + GetModuleFileNameW(NULL, module_path, ArrayCount(module_path)); + PathRemoveFileSpecW(module_path); + + dbg_SymSetOptions(SYMOPT_EXACT_SYMBOLS | SYMOPT_FAIL_CRITICAL_ERRORS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME); + if(dbg_SymInitializeW(process, module_path, 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 Arch 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: 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 allow large pages if we can + B32 large_pages_allowed = 0; + { + 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; + large_pages_allowed = !!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->large_pages_allowed = large_pages_allowed; + info->pid = GetCurrentProcessId(); + } + + //- 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) || + str8_match(arg8, str8_lit("-quiet"), StringMatchFlag_CaseInsensitive)) + { + win32_g_is_quiet = 1; + } + if(str8_match(arg8, str8_lit("--large_pages"), StringMatchFlag_CaseInsensitive) || + str8_match(arg8, str8_lit("-large_pages"), StringMatchFlag_CaseInsensitive)) + { + arena_default_flags = ArenaFlag_LargePages; + arena_default_reserve_size = Max(MB(64), os_w32_state.system_info.large_page_size); + arena_default_commit_size = arena_default_reserve_size; + } + argv[i] = (char *)arg8.str; + } + + //- 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: call into "real" entry point + main_thread_base_entry_point(argc, argv); +} + +#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/src/metagen/metagen_os/core/win32/metagen_os_core_win32.h b/src/metagen/metagen_os/core/win32/metagen_os_core_win32.h new file mode 100644 index 0000000..2afd05f --- /dev/null +++ b/src/metagen/metagen_os/core/win32/metagen_os_core_win32.h @@ -0,0 +1,121 @@ +// 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 + +#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/src/metagen/metagen_os/metagen_os_inc.c b/src/metagen/metagen_os/metagen_os_inc.c new file mode 100644 index 0000000..c719681 --- /dev/null +++ b/src/metagen/metagen_os/metagen_os_inc.c @@ -0,0 +1,10 @@ +// Copyright (c) 2024 Epic Games Tools +// Licensed under the MIT license (https://opensource.org/license/mit/) + +#include "metagen_os/core/metagen_os_core.c" + +#if OS_WINDOWS +# include "metagen_os/core/win32/metagen_os_core_win32.c" +#else +# error OS core layer not implemented for this operating system. +#endif diff --git a/src/metagen/metagen_os/metagen_os_inc.h b/src/metagen/metagen_os/metagen_os_inc.h new file mode 100644 index 0000000..6dd1d9f --- /dev/null +++ b/src/metagen/metagen_os/metagen_os_inc.h @@ -0,0 +1,19 @@ +// 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 + +#include "metagen_os/core/metagen_os_core.h" + +#if OS_WINDOWS +# include "metagen_os/core/win32/metagen_os_core_win32.h" +#else +# error OS core layer not implemented for this operating system. +#endif + +#endif // OS_INC_H diff --git a/src/os/generated/os_core.meta.c b/src/os/generated/os.meta.c similarity index 97% rename from src/os/generated/os_core.meta.c rename to src/os/generated/os.meta.c index c2abe3b..ee0224d 100644 --- a/src/os/generated/os_core.meta.c +++ b/src/os/generated/os.meta.c @@ -1,3 +1,5 @@ +//- GENERATED CODE + char * os_key_display_string[113] = { "Null", diff --git a/src/os/generated/os.meta.h b/src/os/generated/os.meta.h new file mode 100644 index 0000000..7c71b48 --- /dev/null +++ b/src/os/generated/os.meta.h @@ -0,0 +1,124 @@ +//- GENERATED CODE + +#ifndef OS_META_H +#define OS_META_H + +typedef enum OS_Key +{ +OS_Key_Null, +OS_Key_Esc, +OS_Key_F1, +OS_Key_F2, +OS_Key_F3, +OS_Key_F4, +OS_Key_F5, +OS_Key_F6, +OS_Key_F7, +OS_Key_F8, +OS_Key_F9, +OS_Key_F10, +OS_Key_F11, +OS_Key_F12, +OS_Key_F13, +OS_Key_F14, +OS_Key_F15, +OS_Key_F16, +OS_Key_F17, +OS_Key_F18, +OS_Key_F19, +OS_Key_F20, +OS_Key_F21, +OS_Key_F22, +OS_Key_F23, +OS_Key_F24, +OS_Key_Tick, +OS_Key_0, +OS_Key_1, +OS_Key_2, +OS_Key_3, +OS_Key_4, +OS_Key_5, +OS_Key_6, +OS_Key_7, +OS_Key_8, +OS_Key_9, +OS_Key_Minus, +OS_Key_Equal, +OS_Key_Backspace, +OS_Key_Tab, +OS_Key_Q, +OS_Key_W, +OS_Key_E, +OS_Key_R, +OS_Key_T, +OS_Key_Y, +OS_Key_U, +OS_Key_I, +OS_Key_O, +OS_Key_P, +OS_Key_LeftBracket, +OS_Key_RightBracket, +OS_Key_BackSlash, +OS_Key_CapsLock, +OS_Key_A, +OS_Key_S, +OS_Key_D, +OS_Key_F, +OS_Key_G, +OS_Key_H, +OS_Key_J, +OS_Key_K, +OS_Key_L, +OS_Key_Semicolon, +OS_Key_Quote, +OS_Key_Return, +OS_Key_Shift, +OS_Key_Z, +OS_Key_X, +OS_Key_C, +OS_Key_V, +OS_Key_B, +OS_Key_N, +OS_Key_M, +OS_Key_Comma, +OS_Key_Period, +OS_Key_Slash, +OS_Key_Ctrl, +OS_Key_Alt, +OS_Key_Space, +OS_Key_Menu, +OS_Key_ScrollLock, +OS_Key_Pause, +OS_Key_Insert, +OS_Key_Home, +OS_Key_PageUp, +OS_Key_Delete, +OS_Key_End, +OS_Key_PageDown, +OS_Key_Up, +OS_Key_Left, +OS_Key_Down, +OS_Key_Right, +OS_Key_NumLock, +OS_Key_NumSlash, +OS_Key_NumStar, +OS_Key_NumMinus, +OS_Key_NumPlus, +OS_Key_NumPeriod, +OS_Key_Num0, +OS_Key_Num1, +OS_Key_Num2, +OS_Key_Num3, +OS_Key_Num4, +OS_Key_Num5, +OS_Key_Num6, +OS_Key_Num7, +OS_Key_Num8, +OS_Key_Num9, +OS_Key_LeftMouseButton, +OS_Key_MiddleMouseButton, +OS_Key_RightMouseButton, +OS_Key_OS_Key_COUNT, +} OS_Key; + +#endif // OS_META_H diff --git a/src/os/generated/os_core.meta.h b/src/os/generated/os_core.meta.h deleted file mode 100644 index b2f6a63..0000000 --- a/src/os/generated/os_core.meta.h +++ /dev/null @@ -1,121 +0,0 @@ -typedef enum OS_Key -{ - OS_Key_Null, - OS_Key_Esc, - OS_Key_F1, - OS_Key_F2, - OS_Key_F3, - OS_Key_F4, - OS_Key_F5, - OS_Key_F6, - OS_Key_F7, - OS_Key_F8, - OS_Key_F9, - OS_Key_F10, - OS_Key_F11, - OS_Key_F12, - OS_Key_F13, - OS_Key_F14, - OS_Key_F15, - OS_Key_F16, - OS_Key_F17, - OS_Key_F18, - OS_Key_F19, - OS_Key_F20, - OS_Key_F21, - OS_Key_F22, - OS_Key_F23, - OS_Key_F24, - OS_Key_Tick, - OS_Key_0, - OS_Key_1, - OS_Key_2, - OS_Key_3, - OS_Key_4, - OS_Key_5, - OS_Key_6, - OS_Key_7, - OS_Key_8, - OS_Key_9, - OS_Key_Minus, - OS_Key_Equal, - OS_Key_Backspace, - OS_Key_Tab, - OS_Key_Q, - OS_Key_W, - OS_Key_E, - OS_Key_R, - OS_Key_T, - OS_Key_Y, - OS_Key_U, - OS_Key_I, - OS_Key_O, - OS_Key_P, - OS_Key_LeftBracket, - OS_Key_RightBracket, - OS_Key_BackSlash, - OS_Key_CapsLock, - OS_Key_A, - OS_Key_S, - OS_Key_D, - OS_Key_F, - OS_Key_G, - OS_Key_H, - OS_Key_J, - OS_Key_K, - OS_Key_L, - OS_Key_Semicolon, - OS_Key_Quote, - OS_Key_Return, - OS_Key_Shift, - OS_Key_Z, - OS_Key_X, - OS_Key_C, - OS_Key_V, - OS_Key_B, - OS_Key_N, - OS_Key_M, - OS_Key_Comma, - OS_Key_Period, - OS_Key_Slash, - OS_Key_Ctrl, - OS_Key_Alt, - OS_Key_Space, - OS_Key_Menu, - OS_Key_ScrollLock, - OS_Key_Pause, - OS_Key_Insert, - OS_Key_Home, - OS_Key_PageUp, - OS_Key_Delete, - OS_Key_End, - OS_Key_PageDown, - OS_Key_Up, - OS_Key_Left, - OS_Key_Down, - OS_Key_Right, - OS_Key_NumLock, - OS_Key_NumSlash, - OS_Key_NumStar, - OS_Key_NumMinus, - OS_Key_NumPlus, - OS_Key_NumPeriod, - OS_Key_Num0, - OS_Key_Num1, - OS_Key_Num2, - OS_Key_Num3, - OS_Key_Num4, - OS_Key_Num5, - OS_Key_Num6, - OS_Key_Num7, - OS_Key_Num8, - OS_Key_Num9, - OS_Key_LeftMouseButton, - OS_Key_MiddleMouseButton, - OS_Key_RightMouseButton, - OS_Key_COUNT -} -OS_Key; - -extern char * os_key_display_string[113]; - diff --git a/src/os/os_core.c b/src/os/os_core.c index e24588a..232de71 100644 --- a/src/os/os_core.c +++ b/src/os/os_core.c @@ -1,3 +1,3 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //~ -#include "os/generated/os_core.meta.c" +#include "os/generated/os.meta.c" diff --git a/src/os/os_core.h b/src/os/os_core.h index b86b8ed..7818e14 100644 --- a/src/os/os_core.h +++ b/src/os/os_core.h @@ -5,7 +5,7 @@ #define OS_MAX_EVENTS 2048 -#include "os/generated/os_core.meta.h" +#include "os/generated/os.meta.h" typedef struct OS_Handle OS_Handle; struct OS_Handle diff --git a/src/os/os_core.mdesk b/src/os/os_core.mdesk index c9234c5..8cbce86 100644 --- a/src/os/os_core.mdesk +++ b/src/os/os_core.mdesk @@ -125,15 +125,14 @@ OS_KeyTable: /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //~ Generators -@table_gen_enum OS_Key: +@enum OS_Key: { - @expand(OS_KeyTable a) - ` OS_Key_$(a.name),`; - ` OS_Key_COUNT` + @expand(OS_KeyTable a) `$(a.name)` + OS_Key_COUNT, } -@table_gen_data(`char *`) os_key_display_string: +@c_file @data(`char *`) +os_key_display_string: { - @expand(OS_KeyTable a) - `"$(a.display_string)",`; + @expand(OS_KeyTable a) `"$(a.display_string)"`; } \ No newline at end of file diff --git a/src/render/d3d12/generated/render_d3d12.meta.c b/src/render/d3d12/generated/render_d3d12.meta.c new file mode 100644 index 0000000..a7c8eef --- /dev/null +++ b/src/render/d3d12/generated/render_d3d12.meta.c @@ -0,0 +1,16 @@ +//- GENERATED CODE + +const char *r_d3d12_g_vs_kind_source_table[R_D3D12_VS_Kind_COUNT]; +internal void r_d3d12_init_vs_sources() +{ +r_d3d12_g_vs_kind_source_table[R_D3D12_VS_Kind_Triangle] = r_d3d12_g_triangle_shader_src; +r_d3d12_g_vs_kind_source_table[R_D3D12_VS_Kind_Test] = r_d3d12_g_test_shader_src; +} + +const char *r_d3d12_g_ps_kind_source_table[R_D3D12_PS_Kind_COUNT]; +internal void r_d3d12_init_ps_sources() +{ +r_d3d12_g_ps_kind_source_table[R_D3D12_PS_Kind_Triangle] = r_d3d12_g_triangle_shader_src; +r_d3d12_g_ps_kind_source_table[R_D3D12_PS_Kind_Test] = r_d3d12_g_test_shader_src; +} + diff --git a/src/render/d3d12/generated/render_d3d12.meta.h b/src/render/d3d12/generated/render_d3d12.meta.h new file mode 100644 index 0000000..c5cdfa2 --- /dev/null +++ b/src/render/d3d12/generated/render_d3d12.meta.h @@ -0,0 +1,71 @@ +//- GENERATED CODE + +#ifndef RENDER_D3D12_META_H +#define RENDER_D3D12_META_H + +typedef enum R_D3D12_VS_Kind +{ +R_D3D12_VS_Kind_Triangle, +R_D3D12_VS_Kind_Test, +R_D3D12_VS_Kind_COUNT, +} R_D3D12_VS_Kind; + +typedef enum R_D3D12_PS_Kind +{ +R_D3D12_PS_Kind_Triangle, +R_D3D12_PS_Kind_Test, +R_D3D12_PS_Kind_COUNT, +} R_D3D12_PS_Kind; + +read_only global const char* r_d3d12_g_triangle_shader_src = +"" +"\n" +"struct PSInput\n" +"{\n" +" float4 position : SV_POSITION;\n" +" float4 color : COLOR;\n" +"};\n" +"\n" +"PSInput vs_main(float4 position : POSITION, float4 color : COLOR)\n" +"{\n" +" PSInput result;\n" +"\n" +" result.position = position;\n" +" result.color = color;\n" +"\n" +" return result;\n" +"}\n" +"\n" +"float4 ps_main(PSInput input) : SV_TARGET\n" +"{\n" +" return input.color;\n" +"}\n" +"" +; +read_only global const char* r_d3d12_g_test_shader_src = +"" +"\n" +"struct PSInput\n" +"{\n" +" float4 position : SV_POSITION;\n" +" float4 color : COLOR;\n" +"};\n" +"\n" +"PSInput vs_main(float4 position : POSITION, float4 color : COLOR)\n" +"{\n" +" PSInput result;\n" +"\n" +" result.position = position;\n" +" result.color = color;\n" +"\n" +" return result;\n" +"}\n" +"\n" +"float4 ps_main(PSInput input) : SV_TARGET\n" +"{\n" +" return input.color;\n" +"}\n" +"" +; + +#endif // RENDER_D3D12_META_H diff --git a/src/render/d3d12/render_d3d12.c b/src/render/d3d12/render_d3d12.c index 683f6e1..a83075f 100644 --- a/src/render/d3d12/render_d3d12.c +++ b/src/render/d3d12/render_d3d12.c @@ -1,17 +1,34 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //~ +#include +#include "generated/render_d3d12.meta.c" #pragma comment(lib, "d3d12.lib") #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "dxguid.lib") +#pragma comment(lib, "d3dcompiler.lib") /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //~ -#define D3D12_ERROR(msg, ...) LOG("D3D12 ERROR (anton): "); LOG(msg, __VA_ARGS__); Trap(); -#define D3D12_CHECK(hr, msg) if(FAILED((hr))) { D3D12_ERROR(msg, __VA_ARGS__) } +#define D3D12_ERROR(msg, ...) \ + LOG("D3D12 ERROR (anton): "); \ + LOG(msg, __VA_ARGS__); \ + Trap(); +#define D3D12_CHECK(hr, msg) \ + if(FAILED((hr))) \ + { \ + D3D12_ERROR(msg, __VA_ARGS__) \ + } -#define D3D12_RELEASE(res) if( (res) && (res)->lpVtbl) { (res)->lpVtbl->Release((res)); (res) = 0; } +#define D3D12_RELEASE(res) \ + if((res) && (res)->lpVtbl) \ + { \ + (res)->lpVtbl->Release((res)); \ + (res) = 0; \ + } + +#define D3D12_SHADER_CHECK(hr, err_blob) r_d3d12_shader_check((hr), (err_blob)) global R_D3D12_State *r_d3d12_state = 0; global R_D3D12_Command *r_d3d12_cmd = 0; @@ -20,23 +37,77 @@ global R_D3D12_RenderTarget *r_d3d12_rt = 0; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //~ +internal U32 c_strlen(const char * str) +{ + U32 out = 0; + char c = str[0]; + while(c != '\0') + { + //LOG("c: %c ", c); + out += 1; + if(out > 1000000) + { + break; + } + c = *(&str[0] + out); + } + return out; +} + internal void -r_init() +r_d3d12_shader_check(HRESULT hr, ID3D10Blob *err_blob) +{ + if(FAILED(hr)) + { + LOG("Shader compilation failed: %.*s\n", + (int)ID3D10Blob_GetBufferSize(err_blob), + (char*)ID3D10Blob_GetBufferPointer(err_blob)); + ID3D10Blob_Release(err_blob); + } +} + +internal D3D12_SHADER_BYTECODE +r_d3d12_get_shader_bytecode(ID3DBlob *shader_blob) +{ + D3D12_SHADER_BYTECODE out = {0}; + out.pShaderBytecode = ID3D10Blob_GetBufferPointer(shader_blob); + out.BytecodeLength = ID3D10Blob_GetBufferSize(shader_blob); + return out; +} + +internal const char * +r_d3d12_get_vs_shader_source(R_D3D12_VS_Kind kind) +{ + return r_d3d12_g_vs_kind_source_table[kind]; +} + +internal const char * +r_d3d12_get_ps_shader_source(R_D3D12_PS_Kind kind) +{ + return r_d3d12_g_ps_kind_source_table[kind]; +} + + +internal void r_init() { Arena *arena = arena_alloc(); r_d3d12_state = push_array(arena, R_D3D12_State, 1); r_d3d12_state->arena = arena; r_d3d12_state->window_handle = g_win32_window_handle; - - r_d3d12_cmd = push_array(arena, R_D3D12_Command, 1); + + r_d3d12_cmd = push_array(arena, + R_D3D12_Command, + 1); + r_d3d12_cmd->arena = arena; - r_d3d12_rt = push_array(arena, R_D3D12_RenderTarget, 1); + r_d3d12_rt = push_array(arena, + R_D3D12_RenderTarget, 1); r_d3d12_rt->arena = arena; HRESULT hr; - hr = D3D12GetDebugInterface(&IID_ID3D12Debug, (void**)&r_d3d12_state->debug); - if(SUCCEEDED(hr) && r_d3d12_state->debug ) + hr = D3D12GetDebugInterface(&IID_ID3D12Debug, (void **)&r_d3d12_state->debug); + if(SUCCEEDED(hr) && r_d3d12_state->debug) { r_d3d12_state->debug->lpVtbl->EnableDebugLayer(r_d3d12_state->debug); } @@ -44,112 +115,105 @@ r_init() { D3D12_ERROR("Failed to create D3D12 Debug interface \n"); } - - // --------------------------------------------------------------------------- + + // --------------------------------------------------------------------------- // Create device hr = D3D12CreateDevice(0, - D3D_FEATURE_LEVEL_11_0, - &IID_ID3D12Device, + D3D_FEATURE_LEVEL_11_0, + &IID_ID3D12Device, (void **)&r_d3d12_state->device); D3D12_CHECK(hr, "Failed to create device\n"); - // --------------------------------------------------------------------------- // Create command queue D3D12_COMMAND_QUEUE_DESC queue_desc = { - .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, - .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, - .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, - .NodeMask = 0 - }; + .Type = D3D12_COMMAND_LIST_TYPE_DIRECT, + .Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL, + .Flags = D3D12_COMMAND_QUEUE_FLAG_NONE, + .NodeMask = 0 + }; hr = ID3D12Device_CreateCommandQueue(r_d3d12_state->device, - &queue_desc, - &IID_ID3D12CommandQueue, - (void**)&r_d3d12_cmd->queue); - + &queue_desc, + &IID_ID3D12CommandQueue, + (void **)&r_d3d12_cmd->queue); + D3D12_CHECK(hr, "Failed to create command queue\n"); - // --------------------------------------------------------------------------- // Create DXGI factory IDXGIFactory4 *factory; - hr = CreateDXGIFactory1(&IID_IDXGIFactory4, (void**)&factory); + hr = CreateDXGIFactory1(&IID_IDXGIFactory4, (void **)&factory); D3D12_CHECK(hr, "Failed to create factory\n"); // --------------------------------------------------------------------------- // Create Swapchain - DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = { - .Width = WINDOW_WIDTH_PX, - .Height = WINDOW_HEIGHT_PX, - .Format = DXGI_FORMAT_R8G8B8A8_UNORM, - .Stereo = FALSE, - .SampleDesc = {1, 0}, - .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT, - .BufferCount = R_NUM_FRAMES_IN_FLIGHT, - .Scaling = DXGI_SCALING_STRETCH, - .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD, - .AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED, - .Flags = 0 - }; - + DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {.Width = WINDOW_WIDTH_PX, + .Height = WINDOW_HEIGHT_PX, + .Format = DXGI_FORMAT_R8G8B8A8_UNORM, + .Stereo = FALSE, + .SampleDesc = {1, 0}, + .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT, + .BufferCount = R_NUM_FRAMES_IN_FLIGHT, + .Scaling = DXGI_SCALING_STRETCH, + .SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD, + .AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED, + .Flags = 0}; // We create a temporary SwapChain1 that is then upgraded to a SwapChain3. - IDXGISwapChain1* temp_swap_chain; - hr = IDXGIFactory4_CreateSwapChainForHwnd(factory, - (IUnknown*)r_d3d12_cmd->queue, - r_d3d12_state->window_handle, - &swap_chain_desc, - 0, - 0, - &temp_swap_chain); + IDXGISwapChain1 *temp_swap_chain; + hr = IDXGIFactory4_CreateSwapChainForHwnd(factory, + (IUnknown *)r_d3d12_cmd->queue, + r_d3d12_state->window_handle, + &swap_chain_desc, + 0, + 0, + &temp_swap_chain); + D3D12_CHECK(hr, "Failed to create temp swap chain \n"); hr = temp_swap_chain->lpVtbl->QueryInterface(temp_swap_chain, - &IID_IDXGISwapChain3, - (void**)&r_d3d12_state->swapchain); + &IID_IDXGISwapChain3, + (void **)&r_d3d12_state->swapchain); D3D12_CHECK(hr, "Failed to upgrade to swapchain3\n"); temp_swap_chain->lpVtbl->Release(temp_swap_chain); // --------------------------------------------------------------------------- // Create Render target view (RTV) descriptor heap - D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = { - .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV, - .NumDescriptors = R_NUM_FRAMES_IN_FLIGHT, - .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE, + D3D12_DESCRIPTOR_HEAP_DESC rtv_heap_desc = + { + .Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV, + .NumDescriptors = R_NUM_FRAMES_IN_FLIGHT, + .Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE, .NodeMask = 0 }; - - hr = ID3D12Device_CreateDescriptorHeap(r_d3d12_state->device, - &rtv_heap_desc, - &IID_ID3D12DescriptorHeap, - (void**)&r_d3d12_rt->heap); + hr = ID3D12Device_CreateDescriptorHeap(r_d3d12_state->device, + &rtv_heap_desc, + &IID_ID3D12DescriptorHeap, + (void **)&r_d3d12_rt->heap); D3D12_CHECK(hr, "Failed to create descriptor heap\n"); r_d3d12_rt->descriptor_size = r_d3d12_state->device->lpVtbl-> - GetDescriptorHandleIncrementSize(r_d3d12_state->device, + GetDescriptorHandleIncrementSize(r_d3d12_state->device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV); // --------------------------------------------------------------------------- - // Create render target views + // Create render target views // TODO(anton): This is probably not correct D3D12 API, but I might have an old SDK. D3D12_CPU_DESCRIPTOR_HANDLE handle; ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(r_d3d12_rt->heap, &handle); - for (U32 i = 0; i < R_NUM_FRAMES_IN_FLIGHT; i += 1) + for(U32 i = 0; i < R_NUM_FRAMES_IN_FLIGHT; i += 1) { - IDXGISwapChain3_GetBuffer(r_d3d12_state->swapchain, - i, - &IID_ID3D12Resource, - (void**)&r_d3d12_rt->targets[i]); - - ID3D12Device_CreateRenderTargetView(r_d3d12_state->device, - r_d3d12_rt->targets[i], - 0, - handle); + IDXGISwapChain3_GetBuffer(r_d3d12_state->swapchain, + i, + &IID_ID3D12Resource, + (void **)&r_d3d12_rt->targets[i]); + + ID3D12Device_CreateRenderTargetView(r_d3d12_state->device, r_d3d12_rt->targets[i], 0, handle); handle.ptr += r_d3d12_rt->descriptor_size; } @@ -157,126 +221,279 @@ r_init() // --------------------------------------------------------------------------- // Command allocator and command list hr = ID3D12Device_CreateCommandAllocator(r_d3d12_state->device, - D3D12_COMMAND_LIST_TYPE_DIRECT, - &IID_ID3D12CommandAllocator, - (void**)&r_d3d12_cmd->allocator); + D3D12_COMMAND_LIST_TYPE_DIRECT, + &IID_ID3D12CommandAllocator, + (void **)&r_d3d12_cmd->allocator); D3D12_CHECK(hr, "Failed to create command allocator\n"); - hr = ID3D12Device_CreateCommandList(r_d3d12_state->device, - 0, - D3D12_COMMAND_LIST_TYPE_DIRECT, - r_d3d12_cmd->allocator, - 0, - &IID_ID3D12GraphicsCommandList, - (void**)&r_d3d12_cmd->list1); + hr = ID3D12Device_CreateCommandList(r_d3d12_state->device, + 0, + D3D12_COMMAND_LIST_TYPE_DIRECT, + r_d3d12_cmd->allocator, + 0, + &IID_ID3D12GraphicsCommandList, + (void **)&r_d3d12_cmd->list1); D3D12_CHECK(hr, "Failed to create command list\n"); // Put it in a closed state during initialisation. It will be opened at the start of the render function. ID3D12GraphicsCommandList_Close(r_d3d12_cmd->list1); + + // Create the vertex buffer. + { + // Define the geometry for a triangle. + R_D3D12_Vertex triangle_vertices[3]; + F32 r_aspect_ratio = (F32)WINDOW_WIDTH_PX / (F32)WINDOW_HEIGHT_PX; + triangle_vertices[0].pos = (Vec3F32){0.0f, 0.25f * r_aspect_ratio, 0.0f}; + triangle_vertices[1].pos = (Vec3F32){0.25f, -0.25f * r_aspect_ratio, 0.0f}; + triangle_vertices[2].pos = (Vec3F32){-0.25f, -0.25f * r_aspect_ratio, 0.0f}; + triangle_vertices[0].color = (Vec4F32){1.0f, 0.0f, 0.0f, 1.0f}; + triangle_vertices[1].color = (Vec4F32){0.0f, 1.0f, 0.0f, 1.0f}; + triangle_vertices[2].color = (Vec4F32){0.0f, 0.0f, 1.0f, 1.0f}; + + U32 vertex_buffer_size = 3 * sizeof(R_D3D12_Vertex); + + D3D12_RESOURCE_DESC buffer_desc = { + .Dimension = D3D12_RESOURCE_DIMENSION_BUFFER, + .Alignment = 0, + .Width = vertex_buffer_size, + .Height = 1, + .DepthOrArraySize = 1, + .MipLevels = 1, + .Format = DXGI_FORMAT_UNKNOWN, + .SampleDesc = {.Count = 1, .Quality = 0}, + .Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR, + .Flags = D3D12_RESOURCE_FLAG_NONE + }; + + // Note: using upload heaps to transfer static data like vert buffers is not + // recommended. Every time the GPU needs it, the upload heap will be marshalled + // over. Please read up on Default Heap usage. An upload heap is used here for + // code simplicity and because there are very few verts to actually transfer. + D3D12_HEAP_PROPERTIES heap_props = { + .Type = D3D12_HEAP_TYPE_UPLOAD, + .CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN, + .MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN, + .CreationNodeMask = 1, + .VisibleNodeMask = 1 + }; + + ID3D12Device_CreateCommittedResource(r_d3d12_state->device, + &heap_props, + D3D12_HEAP_FLAG_NONE, + &buffer_desc, + D3D12_RESOURCE_STATE_GENERIC_READ, + 0, + &IID_ID3D12Resource, + (void **)&r_d3d12_state->vertex_buffer); + + + // Copy the triangle data to the vertex buffer. + U8 *vtx_data_begin; + D3D12_RANGE read_range = {0, 0}; // We don't intend to read from this resource on the CPU + + hr = ID3D12Resource_Map(r_d3d12_state->vertex_buffer, + 0, // Subresource index (0 for buffers) + &read_range, // Read range (NULL or {0,0} if not reading) + &vtx_data_begin); + D3D12_CHECK(hr, "Failed to map vertex buffer\n"); + memcpy(vtx_data_begin, &triangle_vertices[0], vertex_buffer_size); + ID3D12Resource_Unmap(r_d3d12_state->vertex_buffer, 0, 0); + + // Initialize the vertex buffer view. + r_d3d12_state->vtx_buf_view.BufferLocation = ID3D12Resource_GetGPUVirtualAddress(r_d3d12_state->vertex_buffer); + r_d3d12_state->vtx_buf_view.StrideInBytes = sizeof(R_D3D12_Vertex); + r_d3d12_state->vtx_buf_view.SizeInBytes = vertex_buffer_size; + } + + // Create shader pipeline + { + r_d3d12_init_vs_sources(); + r_d3d12_init_ps_sources(); + + D3D12_ROOT_SIGNATURE_DESC root_sig_desc = {0}; + root_sig_desc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + + ID3DBlob *signature = 0; + ID3DBlob *err_blob = 0; + + hr = D3D12SerializeRootSignature(&root_sig_desc, + D3D_ROOT_SIGNATURE_VERSION_1, + &signature, + &err_blob); + D3D12_CHECK(hr, "Failed to serialize root signature \n"); + hr = ID3D12Device_CreateRootSignature(r_d3d12_state->device, + 0, + ID3D10Blob_GetBufferPointer(signature), + ID3D10Blob_GetBufferSize(signature), + &IID_ID3D12RootSignature, + (void **)&r_d3d12_state->root_signature); + D3D12_CHECK(hr, "Failed to create root signature \n"); + + ID3DBlob *vtx_shader = 0; + ID3DBlob *pxl_shader = 0; + + + U32 compile_flags = 0; // Can be debug flags D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; + + const char *vs_source = r_d3d12_get_vs_shader_source(R_D3D12_VS_Kind_Triangle); + hr = D3DCompile(vs_source, + c_strlen(vs_source), + 0, // Source name (0 since in memory) + 0, // shader macros, not used if 0 + 0, // include handler, not used if 0 + "vs_main", // entry point + "vs_5_0", + compile_flags, + 0, // effect falgs + &vtx_shader, // outputblob + &err_blob); + D3D12_SHADER_CHECK(hr, err_blob); + + const char *ps_source = r_d3d12_get_ps_shader_source(R_D3D12_PS_Kind_Triangle); + hr = D3DCompile(ps_source, + c_strlen(ps_source), + 0, // Source name (0 since in memory) + 0, // shader macros, not used if 0 + 0, // include handler, not used if 0 + "ps_main", // entry point + "ps_5_0", + compile_flags, + 0, // effect flags + &pxl_shader, // outputblob + &err_blob); + D3D12_SHADER_CHECK(hr, err_blob); + + + + + if(pxl_shader) + { + ID3D10Blob_Release(pxl_shader); + } + if(vtx_shader) + { + ID3D10Blob_Release(vtx_shader); + } + if(err_blob) + { + ID3D10Blob_Release(err_blob); + } + + } + + + + // --------------------------------------------------------------------------- // Fence - hr = ID3D12Device_CreateFence(r_d3d12_state->device, - 0, - D3D12_FENCE_FLAG_NONE, - &IID_ID3D12Fence, - (void**)&r_d3d12_state->fence); + hr = ID3D12Device_CreateFence(r_d3d12_state->device, + 0, + D3D12_FENCE_FLAG_NONE, + &IID_ID3D12Fence, + (void **)&r_d3d12_state->fence); D3D12_CHECK(hr, "Failed to create fence\n"); r_d3d12_state->fence_value = 1; - + r_d3d12_state->fence_event = CreateEvent(0, FALSE, FALSE, 0); - if(!r_d3d12_state->fence_event) + if(!r_d3d12_state->fence_event) { HRESULT fence_hr = HRESULT_FROM_WIN32(GetLastError()); D3D12_CHECK(fence_hr, "Failed to create fence event\n"); } - - r_d3d12_wait_for_previous_frame(); + r_d3d12_wait_for_previous_frame(); } -internal void -r_d3d12_wait_for_previous_frame() +internal void r_d3d12_wait_for_previous_frame() { U64 fence_value = r_d3d12_state->fence_value; ID3D12CommandQueue_Signal(r_d3d12_cmd->queue, r_d3d12_state->fence, fence_value); r_d3d12_state->fence_value += 1; if(ID3D12Fence_GetCompletedValue(r_d3d12_state->fence) < fence_value) - { - HRESULT hr = ID3D12Fence_SetEventOnCompletion(r_d3d12_state->fence, fence_value, r_d3d12_state->fence_event); + { + HRESULT hr = ID3D12Fence_SetEventOnCompletion(r_d3d12_state->fence, + fence_value, + r_d3d12_state->fence_event); D3D12_CHECK(hr, "Failed to set fence event on completion\n"); WaitForSingleObject(r_d3d12_state->fence_event, INFINITE); } } -internal void -r_render() +internal void r_render() { - HRESULT hr = ID3D12CommandAllocator_Reset(r_d3d12_cmd->allocator); D3D12_CHECK(hr, "Failed to reset command allocator\n"); hr = ID3D12GraphicsCommandList_Reset(r_d3d12_cmd->list1, r_d3d12_cmd->allocator, 0); D3D12_CHECK(hr, "Failed to reset command list\n"); - + U32 frame_index = IDXGISwapChain3_GetCurrentBackBufferIndex(r_d3d12_state->swapchain); - D3D12_RESOURCE_BARRIER barrier = { - .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, - .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, + D3D12_RESOURCE_BARRIER barrier = + { + .Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, + .Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE, .Transition = { - .pResource = r_d3d12_rt->targets[frame_index], - .StateBefore = D3D12_RESOURCE_STATE_PRESENT, - .StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET, + .pResource = r_d3d12_rt->targets[frame_index], + .StateBefore = D3D12_RESOURCE_STATE_PRESENT, + .StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET, .Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES } }; + // Transition from present to render target ID3D12GraphicsCommandList_ResourceBarrier(r_d3d12_cmd->list1, 1, &barrier); - + D3D12_CPU_DESCRIPTOR_HANDLE rtv_handle; ID3D12DescriptorHeap_GetCPUDescriptorHandleForHeapStart(r_d3d12_rt->heap, &rtv_handle); rtv_handle.ptr += frame_index * r_d3d12_rt->descriptor_size; // Clear render target F32 clearColor[4] = {0.1f, 0.2f, 0.3f, 1.0f}; - ID3D12GraphicsCommandList_ClearRenderTargetView(r_d3d12_cmd->list1, rtv_handle, clearColor, 0, NULL); + ID3D12GraphicsCommandList_ClearRenderTargetView(r_d3d12_cmd->list1, + rtv_handle, + clearColor, + 0, + 0); - // Transition render target back to present state + + + // Transition render target back to present state barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET; barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT; ID3D12GraphicsCommandList_ResourceBarrier(r_d3d12_cmd->list1, 1, &barrier); - // Close command list + // Close command list hr = ID3D12GraphicsCommandList_Close(r_d3d12_cmd->list1); - if (FAILED(hr)) return; + if(FAILED(hr)) + return; - // Execute command list - ID3D12CommandList* commandLists[] = { (ID3D12CommandList*)r_d3d12_cmd->list1 }; - ID3D12CommandQueue_ExecuteCommandLists(r_d3d12_cmd->queue, 1, commandLists); + // Execute command list + ID3D12CommandList *commandLists[] = { (ID3D12CommandList *)r_d3d12_cmd->list1 }; - // Present frame + ID3D12CommandQueue_ExecuteCommandLists(r_d3d12_cmd->queue, + 1, + commandLists); + + // Present frame IDXGISwapChain3_Present(r_d3d12_state->swapchain, 1, 0); r_d3d12_wait_for_previous_frame(); - } -internal void -r_cleanup() +internal void r_cleanup() { r_d3d12_wait_for_previous_frame(); U32 do_report_live_objects = 2; // 1 for all, 2 for only after release to check any dangling. // Report live objects if(do_report_live_objects == 1) - { + { LOG("D3D12 Reporting live objects before cleanup:\n"); if(r_d3d12_state->device) { ID3D12DebugDevice *debug_device = 0; - HRESULT hr = r_d3d12_state->device->lpVtbl->QueryInterface(r_d3d12_state->device, - &IID_ID3D12DebugDevice, - (void **)&debug_device); + HRESULT hr = r_d3d12_state->device->lpVtbl->QueryInterface(r_d3d12_state->device, &IID_ID3D12DebugDevice, (void **)&debug_device); if(SUCCEEDED(hr) && debug_device) { debug_device->lpVtbl->ReportLiveDeviceObjects(debug_device, @@ -285,18 +502,20 @@ r_cleanup() } } } - + // State - D3D12_RELEASE(r_d3d12_state->debug); D3D12_RELEASE(r_d3d12_state->swapchain); D3D12_RELEASE(r_d3d12_state->fence); + D3D12_RELEASE(r_d3d12_state->vertex_buffer); + // RT D3D12_RELEASE(r_d3d12_rt->heap); - for(U32 i = 0; i < R_NUM_FRAMES_IN_FLIGHT; i+=1) + for(U32 i = 0; i < R_NUM_FRAMES_IN_FLIGHT; i += 1) { D3D12_RELEASE(r_d3d12_rt->targets[i]); } + // Cmd D3D12_RELEASE(r_d3d12_cmd->queue); D3D12_RELEASE(r_d3d12_cmd->allocator); @@ -304,13 +523,13 @@ r_cleanup() // Report live objects if(do_report_live_objects >= 1) - { + { LOG("D3D12 Reporting live objects after cleanup (should only be device):\n"); if(r_d3d12_state->device) { ID3D12DebugDevice *debug_device = 0; - HRESULT hr = r_d3d12_state->device->lpVtbl->QueryInterface(r_d3d12_state->device, - &IID_ID3D12DebugDevice, + HRESULT hr = r_d3d12_state->device->lpVtbl->QueryInterface(r_d3d12_state->device, + &IID_ID3D12DebugDevice, (void **)&debug_device); if(SUCCEEDED(hr) && debug_device) { @@ -323,5 +542,4 @@ r_cleanup() D3D12_RELEASE(r_d3d12_state->device); arena_release(r_d3d12_state->arena); - } \ No newline at end of file diff --git a/src/render/d3d12/render_d3d12.h b/src/render/d3d12/render_d3d12.h index 91003c4..d2a647d 100644 --- a/src/render/d3d12/render_d3d12.h +++ b/src/render/d3d12/render_d3d12.h @@ -5,6 +5,7 @@ #include #include +#include "generated/render_d3d12.meta.h" /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //~ @@ -15,6 +16,13 @@ /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //~ +typedef struct R_D3D12_Vertex R_D3D12_Vertex; +struct R_D3D12_Vertex +{ + Vec3F32 pos; + Vec4F32 color; +}; + typedef struct R_D3D12_Command R_D3D12_Command; struct R_D3D12_Command @@ -47,6 +55,10 @@ struct R_D3D12_State U64 fence_value; U64 frame_fence_values[R_NUM_FRAMES_IN_FLIGHT]; HANDLE fence_event; + ID3D12Resource *vertex_buffer; + D3D12_VERTEX_BUFFER_VIEW vtx_buf_view; + ID3D12PipelineState *pipeline; + ID3D12RootSignature *root_signature; }; /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/render/d3d12/render_shaders_d3d12.mdesk b/src/render/d3d12/render_shaders_d3d12.mdesk new file mode 100644 index 0000000..c6b01eb --- /dev/null +++ b/src/render/d3d12/render_shaders_d3d12.mdesk @@ -0,0 +1,108 @@ +//////////////////////////////// +// Pipeline Tables + +@table(name, source) +R_D3D12_VSTable: +{ + {Triangle r_d3d12_g_triangle_shader_src} + {Test r_d3d12_g_test_shader_src} +} + +@table(name, source) +R_D3D12_PSTable: +{ + {Triangle r_d3d12_g_triangle_shader_src} + {Test r_d3d12_g_test_shader_src} +} + +//////////////////////////////// +//~ Triangle shader + +@embed_string r_d3d12_g_triangle_shader_src: +""" +struct PSInput +{ + float4 position : SV_POSITION; + float4 color : COLOR; +}; + +PSInput vs_main(float4 position : POSITION, float4 color : COLOR) +{ + PSInput result; + + result.position = position; + result.color = color; + + return result; +} + +float4 ps_main(PSInput input) : SV_TARGET +{ + return input.color; +} +""" + + +//////////////////////////////// +//~ Test shader + +@embed_string r_d3d12_g_test_shader_src: +""" +struct PSInput +{ + float4 position : SV_POSITION; + float4 color : COLOR; +}; + +PSInput vs_main(float4 position : POSITION, float4 color : COLOR) +{ + PSInput result; + + result.position = position; + result.color = color; + + return result; +} + +float4 ps_main(PSInput input) : SV_TARGET +{ + return input.color; +} +""" + + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//~ Generators + +@enum R_D3D12_VS_Kind: +{ + @expand(R_D3D12_VSTable a) `$(a.name)`, + COUNT, +} + +@enum R_D3D12_PS_Kind: +{ + @expand(R_D3D12_PSTable a) `$(a.name)`, + COUNT, +} + +@gen @c_file +{ + `const char *r_d3d12_g_vs_kind_source_table[R_D3D12_VS_Kind_COUNT];`; + `internal void r_d3d12_init_vs_sources()`; + `{`; + @expand(R_D3D12_VSTable a) `r_d3d12_g_vs_kind_source_table[R_D3D12_VS_Kind_$(a.name)] = $(a.source);` + `}`; + ``; +} + +@gen @c_file +{ + `const char *r_d3d12_g_ps_kind_source_table[R_D3D12_PS_Kind_COUNT];`; + `internal void r_d3d12_init_ps_sources()`; + `{`; + @expand(R_D3D12_PSTable a) `r_d3d12_g_ps_kind_source_table[R_D3D12_PS_Kind_$(a.name)] = $(a.source);` + `}`; + ``; +} + diff --git a/src/render/render_core.h b/src/render/render_core.h index 0a206d8..051af1a 100644 --- a/src/render/render_core.h +++ b/src/render/render_core.h @@ -1,6 +1,29 @@ #ifndef RENDER_CORE_H #define RENDER_CORE_H + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//~ + +#define BATCH_LIST_SIZE 256 + +typedef struct R_Batch R_Batch; +struct R_Batch +{ + U8 *v; // Pointer to batch data + U64 byte_count; +}; + +typedef struct R_BatchList R_BatchList; +struct R_BatchList +{ + R_Batch batch[BATCH_LIST_SIZE]; + U64 batch_count; + U64 byte_count; + U64 byte_per_instance; +}; + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////// //~ diff --git a/src/third_party/stb/stb_sprintf.h b/src/third_party/stb/stb_sprintf.h new file mode 100644 index 0000000..33423cf --- /dev/null +++ b/src/third_party/stb/stb_sprintf.h @@ -0,0 +1,2055 @@ +// ---------------------------------------------------------------------------- +// +// NOTE(rjf): This has been modified to support extra format specifiers +// for the debugger project - this is *not* an unmodified copy of the original +// stb_sprintf v1.10 code. +// +// ---------------------------------------------------------------------------- + +// stb_sprintf - v1.10 - public domain snprintf() implementation +// originally by Jeff Roberts / RAD Game Tools, 2015/10/20 +// http://github.com/nothings/stb +// +// allowed types: sc uidBboXx p AaGgEef n +// lengths : hh h ll j z t I64 I32 I +// +// Contributors: +// Fabian "ryg" Giesen (reformatting) +// github:aganm (attribute format) +// +// Contributors (bugfixes): +// github:d26435 +// github:trex78 +// github:account-login +// Jari Komppa (SI suffixes) +// Rohit Nirmal +// Marcin Wojdyr +// Leonard Ritter +// Stefano Zanotti +// Adam Allison +// Arvid Gerstmann +// Markus Kolb +// +// LICENSE: +// +// See end of file for license information. + +#ifndef STB_SPRINTF_H_INCLUDE +#define STB_SPRINTF_H_INCLUDE + +/* +Single file sprintf replacement. + +Originally written by Jeff Roberts at RAD Game Tools - 2015/10/20. +Hereby placed in public domain. + +This is a full sprintf replacement that supports everything that +the C runtime sprintfs support, including float/double, 64-bit integers, +hex floats, field parameters (%*.*d stuff), length reads backs, etc. + +Why would you need this if sprintf already exists? Well, first off, +it's *much* faster (see below). It's also much smaller than the CRT +versions code-space-wise. We've also added some simple improvements +that are super handy (commas in thousands, callbacks at buffer full, +for example). Finally, the format strings for MSVC and GCC differ +for 64-bit integers (among other small things), so this lets you use +the same format strings in cross platform code. + +It uses the standard single file trick of being both the header file +and the source itself. If you just include it normally, you just get +the header file function definitions. To get the code, you include +it from a C or C++ file and define STB_SPRINTF_IMPLEMENTATION first. + +It only uses va_args macros from the C runtime to do it's work. It +does cast doubles to S64s and shifts and divides U64s, which does +drag in CRT code on most platforms. + +It compiles to roughly 8K with float support, and 4K without. +As a comparison, when using MSVC static libs, calling sprintf drags +in 16K. + +API: +==== +int stbsp_sprintf( char * buf, char const * fmt, ... ) +int stbsp_snprintf( char * buf, int count, char const * fmt, ... ) + Convert an arg list into a buffer. stbsp_snprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintf( char * buf, char const * fmt, va_list va ) +int stbsp_vsnprintf( char * buf, int count, char const * fmt, va_list va ) + Convert a va_list arg list into a buffer. stbsp_vsnprintf always returns + a zero-terminated string (unlike regular snprintf). + +int stbsp_vsprintfcb( STBSP_SPRINTFCB * callback, void * user, char * buf, char const * fmt, va_list va ) + typedef char * STBSP_SPRINTFCB( char const * buf, void * user, int len ); + Convert into a buffer, calling back every STB_SPRINTF_MIN chars. + Your callback can then copy the chars out, print them or whatever. + This function is actually the workhorse for everything else. + The buffer you pass in must hold at least STB_SPRINTF_MIN characters. + // you return the next buffer to use or 0 to stop converting + +void stbsp_set_separators( char comma, char period ) + Set the comma and period characters to use. + +FLOATS/DOUBLES: +=============== +This code uses a internal float->ascii conversion method that uses +doubles with error correction (double-doubles, for ~105 bits of +precision). This conversion is round-trip perfect - that is, an atof +of the values output here will give you the bit-exact double back. + +One difference is that our insignificant digits will be different than +with MSVC or GCC (but they don't match each other either). We also +don't attempt to find the minimum length matching float (pre-MSVC15 +doesn't either). + +If you don't need float or doubles at all, define STB_SPRINTF_NOFLOAT +and you'll save 4K of code space. + +64-BIT INTS: +============ +This library also supports 64-bit integers and you can use MSVC style or +GCC style indicators (%I64d or %lld). It supports the C99 specifiers +for size_t and ptr_diff_t (%jd %zd) as well. + +EXTRAS: +======= +Like some GCCs, for integers and floats, you can use a ' (single quote) +specifier and commas will be inserted on the thousands: "%'d" on 12345 +would print 12,345. + +For integers and floats, you can use a "$" specifier and the number +will be converted to float and then divided to get kilo, mega, giga or +tera and then printed, so "%$d" 1000 is "1.0 k", "%$.2d" 2536000 is +"2.53 M", etc. For byte values, use two $:s, like "%$$d" to turn +2536000 to "2.42 Mi". If you prefer JEDEC suffixes to SI ones, use three +$:s: "%$$$d" -> "2.42 M". To remove the space between the number and the +suffix, add "_" specifier: "%_$d" -> "2.53M". + +In addition to octal and hexadecimal conversions, you can print +integers in binary: "%b" for 256 would print 100. + +PERFORMANCE vs MSVC 2008 32-/64-bit (GCC is even slower than MSVC): +=================================================================== +"%d" across all 32-bit ints (4.8x/4.0x faster than 32-/64-bit MSVC) +"%24d" across all 32-bit ints (4.5x/4.2x faster) +"%x" across all 32-bit ints (4.5x/3.8x faster) +"%08x" across all 32-bit ints (4.3x/3.8x faster) +"%f" across e-10 to e+10 floats (7.3x/6.0x faster) +"%e" across e-10 to e+10 floats (8.1x/6.0x faster) +"%g" across e-10 to e+10 floats (10.0x/7.1x faster) +"%f" for values near e-300 (7.9x/6.5x faster) +"%f" for values near e+300 (10.0x/9.1x faster) +"%e" for values near e-300 (10.1x/7.0x faster) +"%e" for values near e+300 (9.2x/6.0x faster) +"%.320f" for values near e-300 (12.6x/11.2x faster) +"%a" for random values (8.6x/4.3x faster) +"%I64d" for 64-bits with 32-bit values (4.8x/3.4x faster) +"%I64d" for 64-bits > 32-bit values (4.9x/5.5x faster) +"%s%s%s" for 64 char strings (7.1x/7.3x faster) +"...512 char string..." ( 35.0x/32.5x faster!) +*/ + +#if defined(__clang__) +# if defined(__has_feature) && defined(__has_attribute) +# if __has_feature(address_sanitizer) +# if __has_attribute(__no_sanitize__) +# define STBSP__ASAN __attribute__((__no_sanitize__("address"))) +# elif __has_attribute(__no_sanitize_address__) +# define STBSP__ASAN __attribute__((__no_sanitize_address__)) +# elif __has_attribute(__no_address_safety_analysis__) +# define STBSP__ASAN __attribute__((__no_address_safety_analysis__)) +# endif +# endif +# endif +#elif defined(__GNUC__) && (__GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8)) +# if defined(__SANITIZE_ADDRESS__) && __SANITIZE_ADDRESS__ +# define STBSP__ASAN __attribute__((__no_sanitize_address__)) +# endif +#elif defined(_MSC_VER) +# if defined(__SANITIZE_ADDRESS__) +# define STBSP__ASAN __declspec(no_sanitize_address) +# endif +#endif + +#ifndef STBSP__ASAN +#define STBSP__ASAN +#endif + +#ifdef STB_SPRINTF_STATIC +#define STBSP__PUBLICDEC static STBSP__ASAN +#define STBSP__PUBLICDEF static STBSP__ASAN +#else +#ifdef __cplusplus +#define STBSP__PUBLICDEC extern "C" STBSP__ASAN +#define STBSP__PUBLICDEF extern "C" STBSP__ASAN +#else +#define STBSP__PUBLICDEC extern STBSP__ASAN +#define STBSP__PUBLICDEF STBSP__ASAN +#endif +#endif + +#if defined(__has_attribute) +#if __has_attribute(format) +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) __attribute__((format(printf,fmt,va))) +#endif +#endif + +#ifndef STBSP__ATTRIBUTE_FORMAT +#define STBSP__ATTRIBUTE_FORMAT(fmt,va) +#endif + +#ifdef _MSC_VER +#define STBSP__NOTUSED(v) (void)(v) +#else +#define STBSP__NOTUSED(v) (void)sizeof(v) +#endif + +#include // for va_arg(), va_list() +#include // size_t, ptrdiff_t + +#ifndef STB_SPRINTF_MIN +#define STB_SPRINTF_MIN 512 // how many characters per callback +#endif +typedef char *STBSP_SPRINTFCB(const char *buf, void *user, int len); + +#ifndef STB_SPRINTF_DECORATE +#define STB_SPRINTF_DECORATE(name) stbsp_##name // define this before including if you want to change the names +#endif + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsnprintf)(char *buf, int count, char const *fmt, va_list va); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(2,3); +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) STBSP__ATTRIBUTE_FORMAT(3,4); + +STBSP__PUBLICDEC int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va); +STBSP__PUBLICDEC void STB_SPRINTF_DECORATE(set_separators)(char comma, char period); + +#endif // STB_SPRINTF_H_INCLUDE + +#ifdef STB_SPRINTF_IMPLEMENTATION + +#define stbsp__uint32 unsigned int +#define stbsp__int32 signed int + +#ifdef _MSC_VER +#define stbsp__uint64 unsigned __int64 +#define stbsp__int64 signed __int64 +#else +#define stbsp__uint64 unsigned long long +#define stbsp__int64 signed long long +#endif +#define stbsp__uint16 unsigned short + +#ifndef stbsp__uintptr +#if defined(__ppc64__) || defined(__powerpc64__) || defined(__aarch64__) || defined(_M_X64) || defined(__x86_64__) || defined(__x86_64) || defined(__s390x__) +#define stbsp__uintptr stbsp__uint64 +#else +#define stbsp__uintptr stbsp__uint32 +#endif +#endif + +#ifndef STB_SPRINTF_MSVC_MODE // used for MSVC2013 and earlier (MSVC2015 matches GCC) +#if defined(_MSC_VER) && (_MSC_VER < 1900) +#define STB_SPRINTF_MSVC_MODE +#endif +#endif + +#ifdef STB_SPRINTF_NOUNALIGNED // define this before inclusion to force stbsp_sprintf to always use aligned accesses +#define STBSP__UNALIGNED(code) +#else +#define STBSP__UNALIGNED(code) code +#endif + +#ifndef STB_SPRINTF_NOFLOAT +// internal float utility functions +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits); +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value); +#define STBSP__SPECIAL 0x7000 +#endif + +static char stbsp__period = '.'; +static char stbsp__comma = ','; +static struct +{ + short temp; // force next field to be 2-byte aligned + char pair[201]; +} stbsp__digitpair = +{ + 0, + "00010203040506070809101112131415161718192021222324" + "25262728293031323334353637383940414243444546474849" + "50515253545556575859606162636465666768697071727374" + "75767778798081828384858687888990919293949596979899" +}; + +STBSP__PUBLICDEF void STB_SPRINTF_DECORATE(set_separators)(char pcomma, char pperiod) +{ + stbsp__period = pperiod; + stbsp__comma = pcomma; +} + +#define STBSP__LEFTJUST 1 +#define STBSP__LEADINGPLUS 2 +#define STBSP__LEADINGSPACE 4 +#define STBSP__LEADING_0X 8 +#define STBSP__LEADINGZERO 16 +#define STBSP__INTMAX 32 +#define STBSP__TRIPLET_COMMA 64 +#define STBSP__NEGATIVE 128 +#define STBSP__METRIC_SUFFIX 256 +#define STBSP__HALFWIDTH 512 +#define STBSP__METRIC_NOSPACE 1024 +#define STBSP__METRIC_1024 2048 +#define STBSP__METRIC_JEDEC 4096 + +static void stbsp__lead_sign(stbsp__uint32 fl, char *sign) +{ + sign[0] = 0; + if (fl & STBSP__NEGATIVE) { + sign[0] = 1; + sign[1] = '-'; + } else if (fl & STBSP__LEADINGSPACE) { + sign[0] = 1; + sign[1] = ' '; + } else if (fl & STBSP__LEADINGPLUS) { + sign[0] = 1; + sign[1] = '+'; + } +} + +static STBSP__ASAN stbsp__uint32 stbsp__strlen_limited(char const *s, stbsp__uint32 limit) +{ + char const * sn = s; + + // get up to 4-byte alignment + for (;;) { + if (((stbsp__uintptr)sn & 3) == 0) + break; + + if (!limit || *sn == 0) + return (stbsp__uint32)(sn - s); + + ++sn; + --limit; + } + + // scan over 4 bytes at a time to find terminating 0 + // this will intentionally scan up to 3 bytes past the end of buffers, + // but becase it works 4B aligned, it will never cross page boundaries + // (hence the STBSP__ASAN markup; the over-read here is intentional + // and harmless) + while (limit >= 4) { + stbsp__uint32 v = *(stbsp__uint32 *)sn; + // bit hack to find if there's a 0 byte in there + if ((v - 0x01010101) & (~v) & 0x80808080UL) + break; + + sn += 4; + limit -= 4; + } + + // handle the last few characters to find actual size + while (limit && *sn) { + ++sn; + --limit; + } + + return (stbsp__uint32)(sn - s); +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintfcb)(STBSP_SPRINTFCB *callback, void *user, char *buf, char const *fmt, va_list va) +{ + static char hex[] = "0123456789abcdefxp"; + static char hexu[] = "0123456789ABCDEFXP"; + char *bf; + char const *f; + int tlen = 0; + + bf = buf; + f = fmt; + for (;;) { + stbsp__int32 fw, pr, tz; + stbsp__uint32 fl; + + // macros for the callback buffer stuff +#define stbsp__chk_cb_bufL(bytes) \ +{ \ +int len = (int)(bf - buf); \ +if ((len + (bytes)) >= STB_SPRINTF_MIN) { \ +tlen += len; \ +if (0 == (bf = buf = callback(buf, user, len))) \ +goto done; \ +} \ +} +#define stbsp__chk_cb_buf(bytes) \ +{ \ +if (callback) { \ +stbsp__chk_cb_bufL(bytes); \ +} \ +} +#define stbsp__flush_cb() \ +{ \ +stbsp__chk_cb_bufL(STB_SPRINTF_MIN - 1); \ +} // flush if there is even one byte in the buffer +#define stbsp__cb_buf_clamp(cl, v) \ +cl = v; \ +if (callback) { \ +int lg = STB_SPRINTF_MIN - (int)(bf - buf); \ +if (cl > lg) \ +cl = lg; \ +} + + // fast copy everything up to the next % (or end of string) + for (;;) { + while (((stbsp__uintptr)f) & 3) { + schk1: + if (f[0] == '%') + goto scandd; + schk2: + if (f[0] == 0) + goto endfmt; + stbsp__chk_cb_buf(1); + *bf++ = f[0]; + ++f; + } + for (;;) { + // Check if the next 4 bytes contain %(0x25) or end of string. + // Using the 'hasless' trick: + // https://graphics.stanford.edu/~seander/bithacks.html#HasLessInWord + stbsp__uint32 v, c; + v = *(stbsp__uint32 *)f; + c = (~v) & 0x80808080; + if (((v ^ 0x25252525) - 0x01010101) & c) + goto schk1; + if ((v - 0x01010101) & c) + goto schk2; + if (callback) + if ((STB_SPRINTF_MIN - (int)(bf - buf)) < 4) + goto schk1; +#ifdef STB_SPRINTF_NOUNALIGNED + if(((stbsp__uintptr)bf) & 3) { + bf[0] = f[0]; + bf[1] = f[1]; + bf[2] = f[2]; + bf[3] = f[3]; + } else +#endif + { + *(stbsp__uint32 *)bf = v; + } + bf += 4; + f += 4; + } + } + scandd: + + ++f; + + // ok, we have a percent, read the modifiers first + fw = 0; + pr = -1; + fl = 0; + tz = 0; + + // flags + for (;;) { + switch (f[0]) { + // if we have left justify + case '-': + fl |= STBSP__LEFTJUST; + ++f; + continue; + // if we have leading plus + case '+': + fl |= STBSP__LEADINGPLUS; + ++f; + continue; + // if we have leading space + case ' ': + fl |= STBSP__LEADINGSPACE; + ++f; + continue; + // if we have leading 0x + case '#': + fl |= STBSP__LEADING_0X; + ++f; + continue; + // if we have thousand commas + case '\'': + fl |= STBSP__TRIPLET_COMMA; + ++f; + continue; + // if we have kilo marker (none->kilo->kibi->jedec) + case '$': + if (fl & STBSP__METRIC_SUFFIX) { + if (fl & STBSP__METRIC_1024) { + fl |= STBSP__METRIC_JEDEC; + } else { + fl |= STBSP__METRIC_1024; + } + } else { + fl |= STBSP__METRIC_SUFFIX; + } + ++f; + continue; + // if we don't want space between metric suffix and number + case '_': + fl |= STBSP__METRIC_NOSPACE; + ++f; + continue; + // if we have leading zero + case '0': + fl |= STBSP__LEADINGZERO; + ++f; + goto flags_done; + default: goto flags_done; + } + } + flags_done: + + // get the field width + if (f[0] == '*') { + fw = va_arg(va, stbsp__uint32); + ++f; + } else { + while ((f[0] >= '0') && (f[0] <= '9')) { + fw = fw * 10 + f[0] - '0'; + f++; + } + } + // get the precision + if (f[0] == '.') { + ++f; + if (f[0] == '*') { + pr = va_arg(va, stbsp__uint32); + ++f; + } else { + pr = 0; + while ((f[0] >= '0') && (f[0] <= '9')) { + pr = pr * 10 + f[0] - '0'; + f++; + } + } + } + + // handle integer size overrides + switch (f[0]) { + // are we halfwidth? + case 'h': + fl |= STBSP__HALFWIDTH; + ++f; + if (f[0] == 'h') + ++f; // QUARTERWIDTH + break; + // are we 64-bit (unix style) + case 'l': + fl |= ((sizeof(long) == 8) ? STBSP__INTMAX : 0); + ++f; + if (f[0] == 'l') { + fl |= STBSP__INTMAX; + ++f; + } + break; + // are we 64-bit on intmax? (c99) + case 'j': + fl |= (sizeof(size_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit on size_t or ptrdiff_t? (c99) + case 'z': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + case 't': + fl |= (sizeof(ptrdiff_t) == 8) ? STBSP__INTMAX : 0; + ++f; + break; + // are we 64-bit (msft style) + case 'I': + if ((f[1] == '6') && (f[2] == '4')) { + fl |= STBSP__INTMAX; + f += 3; + } else if ((f[1] == '3') && (f[2] == '2')) { + f += 3; + } else { + fl |= ((sizeof(void *) == 8) ? STBSP__INTMAX : 0); + ++f; + } + break; + default: break; + } + + // handle each replacement + switch (f[0]) { +#define STBSP__NUMSZ 512 // big enough for e308 (with commas) or e-307 + char num[STBSP__NUMSZ]; + char lead[8]; + char tail[8]; + char *s; + char const *h; + stbsp__uint32 l, n, cs; + stbsp__uint64 n64; +#ifndef STB_SPRINTF_NOFLOAT + double fv; +#endif + stbsp__int32 dp; + char const *sn; + + case 's': + // get the string + s = va_arg(va, char *); + if (s == 0) + s = (char *)"null"; + // get the length, limited to desired precision + // always limit to ~0u chars since our counts are 32b + l = stbsp__strlen_limited(s, (pr >= 0) ? pr : ~0u); + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + // copy the string in + goto scopy; + + //- + //- + //- + // NOTE(rjf): DEBUGGER PROJECT ADDITION vvv + // + // The following additions are for using the debugger project's base layer + // types in format strings. + // + case 'S': + { + String8 string = va_arg(va, String8); + s = (char *)string.str; + l = (U32)string.size; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + }goto scopy; + + case 'm': + case 'M': + { + static const U64 one_kib = 1ull * 1024; + static const U64 one_mib = 1ull * 1024 * 1024; + static const U64 one_gib = 1ull * 1024 * 1024 * 1024; + static const U64 one_tib = 1ull * 1024 * 1024 * 1024 * 1024; + + U64 size; + if(f[0] == 'M') + { + size = va_arg(va, U64); + } + else + { + size = va_arg(va, U32); + } + + U64 lo = 0; + U64 hi = 0; + char *units = ""; + + if(size < one_kib) + { + hi = size; + units = "Bytes"; + } + else if(size < one_mib) + { + hi = size / one_kib; + lo = ((size * 100) / one_kib) % 100; + units = "KiB"; + } + else if(size < one_gib) + { + hi = size / one_mib; + lo = ((size * 100) / one_mib) % 100; + units = "MiB"; + } + else if(size < one_tib) + { + hi = size / one_gib; + lo = ((size * 100) / one_gib) % 100; + units = "GiB"; + } + else + { + Assert(size <= max_U64/100ull); + hi = size / one_tib; + lo = ((size * 100) / one_tib) % 100; + units = "TiB"; + } + + // format high part + if(hi > 0) + { + s = num; + for(U64 n = hi; n > 0; n /= 10ull) + { + *s = (char)(n % 10ull) + '0'; + ++s; + } + for(S64 i = (S64)(s-num)-1; i >= 0; --i) + { + *bf = num[i]; + ++bf; + } + } + else + { + *bf = '0'; + ++bf; + } + + // format low part + if(lo > 0) + { + *bf = '.'; + ++bf; + + s = num; + for(U64 n = lo; n > 0; n /= 10ull) + { + *s = (char)(n % 10ull) + '0'; + ++s; + } + + U64 lead_zero_count = 3 - (U64)(s-num); + for(U64 i = 1; i < lead_zero_count; ++i) + { + *bf = '0'; + ++bf; + } + + for(S64 i = (S64)(s-num)-1; i >= 0; --i) + { + *bf = num[i]; + ++bf; + } + } + + *bf = ' '; + ++bf; + + // copy units + for(U64 i = 0; units[i] != 0; ++i) + { + *bf = units[i]; + ++bf; + } + }break; + // + // NOTE(rjf): DEBUGGER PROJECT ADDITION ^^^ + //- + //- + //- + + case 'c': // char + // get the character + s = num + STBSP__NUMSZ - 1; + *s = (char)va_arg(va, int); + l = 1; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + + case 'n': // weird write-bytes specifier + { + int *d = va_arg(va, int *); + *d = tlen + (int)(bf - buf); + } break; + +#ifdef STB_SPRINTF_NOFLOAT + case 'A': // float + case 'a': // hex float + case 'G': // float + case 'g': // float + case 'E': // float + case 'e': // float + case 'f': // float + va_arg(va, double); // eat it + s = (char *)"No float"; + l = 8; + lead[0] = 0; + tail[0] = 0; + pr = 0; + cs = 0; + STBSP__NOTUSED(dp); + goto scopy; +#else + case 'A': // hex float + case 'a': // hex float + h = (f[0] == 'A') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_parts((stbsp__int64 *)&n64, &dp, fv)) + fl |= STBSP__NEGATIVE; + + s = num + 64; + + stbsp__lead_sign(fl, lead); + + if (dp == -1023) + dp = (n64) ? -1022 : 0; + else + n64 |= (((stbsp__uint64)1) << 52); + n64 <<= (64 - 56); + if (pr < 15) + n64 += ((((stbsp__uint64)8) << 56) >> (pr * 4)); + // add leading chars + +#ifdef STB_SPRINTF_MSVC_MODE + *s++ = '0'; + *s++ = 'x'; +#else + lead[1 + lead[0]] = '0'; + lead[2 + lead[0]] = 'x'; + lead[0] += 2; +#endif + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + if (pr) + *s++ = stbsp__period; + sn = s; + + // print the bits + n = pr; + if (n > 13) + n = 13; + if (pr > (stbsp__int32)n) + tz = pr - n; + pr = 0; + while (n--) { + *s++ = h[(n64 >> 60) & 15]; + n64 <<= 4; + } + + // print the expo + tail[1] = h[17]; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; + n = (dp >= 1000) ? 6 : ((dp >= 100) ? 5 : ((dp >= 10) ? 4 : 3)); + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + + dp = (int)(s - sn); + l = (int)(s - (num + 64)); + s = num + 64; + cs = 1 + (3 << 24); + goto scopy; + + case 'G': // float + case 'g': // float + h = (f[0] == 'G') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; + else if (pr == 0) + pr = 1; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, (pr - 1) | 0x80000000)) + fl |= STBSP__NEGATIVE; + + // clamp the precision and delete extra zeros after clamp + n = pr; + if (l > (stbsp__uint32)pr) + l = pr; + while ((l > 1) && (pr) && (sn[l - 1] == '0')) { + --pr; + --l; + } + + // should we use %e + if ((dp <= -4) || (dp > (stbsp__int32)n)) { + if (pr > (stbsp__int32)l) + pr = l - 1; + else if (pr) + --pr; // when using %e, there is one digit before the decimal + goto doexpfromg; + } + // this is the insane action to get the pr to match %g semantics for %f + if (dp > 0) { + pr = (dp < (stbsp__int32)l) ? l - dp : 0; + } else { + pr = -dp + ((pr > (stbsp__int32)l) ? (stbsp__int32) l : pr); + } + goto dofloatfromg; + + case 'E': // float + case 'e': // float + h = (f[0] == 'E') ? hexu : hex; + fv = va_arg(va, double); + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr | 0x80000000)) + fl |= STBSP__NEGATIVE; + doexpfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + // handle leading chars + *s++ = sn[0]; + + if (pr) + *s++ = stbsp__period; + + // handle after decimal + if ((l - 1) > (stbsp__uint32)pr) + l = pr + 1; + for (n = 1; n < l; n++) + *s++ = sn[n]; + // trailing zeros + tz = pr - (l - 1); + pr = 0; + // dump expo + tail[1] = h[0xe]; + dp -= 1; + if (dp < 0) { + tail[2] = '-'; + dp = -dp; + } else + tail[2] = '+'; +#ifdef STB_SPRINTF_MSVC_MODE + n = 5; +#else + n = (dp >= 100) ? 5 : 4; +#endif + tail[0] = (char)n; + for (;;) { + tail[n] = '0' + dp % 10; + if (n <= 3) + break; + --n; + dp /= 10; + } + cs = 1 + (3 << 24); // how many tens + goto flt_lead; + + case 'f': // float + fv = va_arg(va, double); + doafloat: + // do kilos + if (fl & STBSP__METRIC_SUFFIX) { + double divisor; + divisor = 1000.0f; + if (fl & STBSP__METRIC_1024) + divisor = 1024.0; + while (fl < 0x4000000) { + if ((fv < divisor) && (fv > -divisor)) + break; + fv /= divisor; + fl += 0x1000000; + } + } + if (pr == -1) + pr = 6; // default is 6 + // read the double into a string + if (stbsp__real_to_str(&sn, &l, num, &dp, fv, pr)) + fl |= STBSP__NEGATIVE; + dofloatfromg: + tail[0] = 0; + stbsp__lead_sign(fl, lead); + if (dp == STBSP__SPECIAL) { + s = (char *)sn; + cs = 0; + pr = 0; + goto scopy; + } + s = num + 64; + + // handle the three decimal varieties + if (dp <= 0) { + stbsp__int32 i; + // handle 0.000*000xxxx + *s++ = '0'; + if (pr) + *s++ = stbsp__period; + n = -dp; + if ((stbsp__int32)n > pr) + n = pr; + i = n; + while (i) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + i -= 4; + } + while (i) { + *s++ = '0'; + --i; + } + if ((stbsp__int32)(l + n) > pr) + l = pr - n; + i = l; + while (i) { + *s++ = *sn++; + --i; + } + tz = pr - (n + l); + cs = 1 + (3 << 24); // how many tens did we write (for commas below) + } else { + cs = (fl & STBSP__TRIPLET_COMMA) ? ((600 - (stbsp__uint32)dp) % 3) : 0; + if ((stbsp__uint32)dp >= l) { + // handle xxxx000*000.0 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= l) + break; + } + } + if (n < (stbsp__uint32)dp) { + n = dp - n; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (n) { + if ((((stbsp__uintptr)s) & 3) == 0) + break; + *s++ = '0'; + --n; + } + while (n >= 4) { + *(stbsp__uint32 *)s = 0x30303030; + s += 4; + n -= 4; + } + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = '0'; + --n; + } + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) { + *s++ = stbsp__period; + tz = pr; + } + } else { + // handle xxxxx.xxxx000*000 + n = 0; + for (;;) { + if ((fl & STBSP__TRIPLET_COMMA) && (++cs == 4)) { + cs = 0; + *s++ = stbsp__comma; + } else { + *s++ = sn[n]; + ++n; + if (n >= (stbsp__uint32)dp) + break; + } + } + cs = (int)(s - (num + 64)) + (3 << 24); // cs is how many tens + if (pr) + *s++ = stbsp__period; + if ((l - dp) > (stbsp__uint32)pr) + l = pr + dp; + while (n < l) { + *s++ = sn[n]; + ++n; + } + tz = pr - (l - dp); + } + } + pr = 0; + + // handle k,m,g,t + if (fl & STBSP__METRIC_SUFFIX) { + char idx; + idx = 1; + if (fl & STBSP__METRIC_NOSPACE) + idx = 0; + tail[0] = idx; + tail[1] = ' '; + { + if (fl >> 24) { // SI kilo is 'k', JEDEC and SI kibits are 'K'. + if (fl & STBSP__METRIC_1024) + tail[idx + 1] = "_KMGT"[fl >> 24]; + else + tail[idx + 1] = "_kMGT"[fl >> 24]; + idx++; + // If printing kibits and not in jedec, add the 'i'. + if (fl & STBSP__METRIC_1024 && !(fl & STBSP__METRIC_JEDEC)) { + tail[idx + 1] = 'i'; + idx++; + } + tail[0] = idx; + } + } + }; + + flt_lead: + // get the length that we copied + l = (stbsp__uint32)(s - (num + 64)); + s = num + 64; + goto scopy; +#endif + + case 'B': // upper binary + case 'b': // lower binary + h = (f[0] == 'B') ? hexu : hex; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[0xb]; + } + l = (8 << 4) | (1 << 8); + goto radixnum; + + case 'o': // octal + h = hexu; + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 1; + lead[1] = '0'; + } + l = (3 << 4) | (3 << 8); + goto radixnum; + + case 'p': // pointer + fl |= (sizeof(void *) == 8) ? STBSP__INTMAX : 0; + pr = sizeof(void *) * 2; + fl &= ~STBSP__LEADINGZERO; // 'p' only prints the pointer with zeros + // fall through - to X + + case 'X': // upper hex + case 'x': // lower hex + h = (f[0] == 'X') ? hexu : hex; + l = (4 << 4) | (4 << 8); + lead[0] = 0; + if (fl & STBSP__LEADING_0X) { + lead[0] = 2; + lead[1] = '0'; + lead[2] = h[16]; + } + radixnum: + // get the number + if (fl & STBSP__INTMAX) + n64 = va_arg(va, stbsp__uint64); + else + n64 = va_arg(va, stbsp__uint32); + + s = num + STBSP__NUMSZ; + dp = 0; + // clear tail, and clear leading if value is zero + tail[0] = 0; + if (n64 == 0) { + lead[0] = 0; + if (pr == 0) { + l = 0; + cs = 0; + goto scopy; + } + } + // convert to string + for (;;) { + *--s = h[n64 & ((1 << (l >> 8)) - 1)]; + n64 >>= (l >> 8); + if (!((n64) || ((stbsp__int32)((num + STBSP__NUMSZ) - s) < pr))) + break; + if (fl & STBSP__TRIPLET_COMMA) { + ++l; + if ((l & 15) == ((l >> 4) & 15)) { + l &= ~15; + *--s = stbsp__comma; + } + } + }; + // get the tens and the comma pos + cs = (stbsp__uint32)((num + STBSP__NUMSZ) - s) + ((((l >> 4) & 15)) << 24); + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + // copy it + goto scopy; + + case 'u': // unsigned + case 'i': + case 'd': // integer + // get the integer and abs it + if (fl & STBSP__INTMAX) { + stbsp__int64 i64 = va_arg(va, stbsp__int64); + n64 = (stbsp__uint64)i64; + if ((f[0] != 'u') && (i64 < 0)) { + n64 = (stbsp__uint64)-i64; + fl |= STBSP__NEGATIVE; + } + } else { + stbsp__int32 i = va_arg(va, stbsp__int32); + n64 = (stbsp__uint32)i; + if ((f[0] != 'u') && (i < 0)) { + n64 = (stbsp__uint32)-i; + fl |= STBSP__NEGATIVE; + } + } + +#ifndef STB_SPRINTF_NOFLOAT + if (fl & STBSP__METRIC_SUFFIX) { + if (n64 < 1024) + pr = 0; + else if (pr == -1) + pr = 1; + fv = (double)(stbsp__int64)n64; + goto doafloat; + } +#endif + + // convert to string + s = num + STBSP__NUMSZ; + l = 0; + + for (;;) { + // do in 32-bit chunks (avoid lots of 64-bit divides even with constant denominators) + char *o = s - 8; + if (n64 >= 100000000) { + n = (stbsp__uint32)(n64 % 100000000); + n64 /= 100000000; + } else { + n = (stbsp__uint32)n64; + n64 = 0; + } + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + do { + s -= 2; + *(stbsp__uint16 *)s = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + } while (n); + } + while (n) { + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = (char)(n % 10) + '0'; + n /= 10; + } + } + if (n64 == 0) { + if ((s[0] == '0') && (s != (num + STBSP__NUMSZ))) + ++s; + break; + } + while (s != o) + if ((fl & STBSP__TRIPLET_COMMA) && (l++ == 3)) { + l = 0; + *--s = stbsp__comma; + --o; + } else { + *--s = '0'; + } + } + + tail[0] = 0; + stbsp__lead_sign(fl, lead); + + // get the length that we copied + l = (stbsp__uint32)((num + STBSP__NUMSZ) - s); + if (l == 0) { + *--s = '0'; + l = 1; + } + cs = l + (3 << 24); + if (pr < 0) + pr = 0; + + scopy: + // get fw=leading/trailing space, pr=leading zeros + if (pr < (stbsp__int32)l) + pr = l; + n = pr + lead[0] + tail[0] + tz; + if (fw < (stbsp__int32)n) + fw = n; + fw -= n; + pr -= l; + + // handle right justify and leading zeros + if ((fl & STBSP__LEFTJUST) == 0) { + if (fl & STBSP__LEADINGZERO) // if leading zeros, everything is in pr + { + pr = (fw > pr) ? fw : pr; + fw = 0; + } else { + fl &= ~STBSP__TRIPLET_COMMA; // if no leading zeros, then no commas + } + } + + // copy the spaces and/or zeros + if (fw + pr) { + stbsp__int32 i; + stbsp__uint32 c; + + // copy leading spaces (or when doing %8.4d stuff) + if ((fl & STBSP__LEFTJUST) == 0) + while (fw > 0) { + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = ' '; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leader + sn = lead + 1; + while (lead[0]) { + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy leading zeros + c = cs >> 24; + cs &= 0xffffff; + cs = (fl & STBSP__TRIPLET_COMMA) ? ((stbsp__uint32)(c - ((pr + cs) % (c + 1)))) : 0; + while (pr > 0) { + stbsp__cb_buf_clamp(i, pr); + pr -= i; + if ((fl & STBSP__TRIPLET_COMMA) == 0) { + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + } + while (i) { + if ((fl & STBSP__TRIPLET_COMMA) && (cs++ == c)) { + cs = 0; + *bf++ = stbsp__comma; + } else + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + } + + // copy leader if there is still one + sn = lead + 1; + while (lead[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, lead[0]); + lead[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy the string + n = l; + while (n) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, n); + n -= i; + STBSP__UNALIGNED(while (i >= 4) { + *(stbsp__uint32 volatile *)bf = *(stbsp__uint32 volatile *)s; + bf += 4; + s += 4; + i -= 4; + }) + while (i) { + *bf++ = *s++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy trailing zeros + while (tz) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tz); + tz -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = '0'; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x30303030; + bf += 4; + i -= 4; + } + while (i) { + *bf++ = '0'; + --i; + } + stbsp__chk_cb_buf(1); + } + + // copy tail if there is one + sn = tail + 1; + while (tail[0]) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, tail[0]); + tail[0] -= (char)i; + while (i) { + *bf++ = *sn++; + --i; + } + stbsp__chk_cb_buf(1); + } + + // handle the left justify + if (fl & STBSP__LEFTJUST) + if (fw > 0) { + while (fw) { + stbsp__int32 i; + stbsp__cb_buf_clamp(i, fw); + fw -= i; + while (i) { + if ((((stbsp__uintptr)bf) & 3) == 0) + break; + *bf++ = ' '; + --i; + } + while (i >= 4) { + *(stbsp__uint32 *)bf = 0x20202020; + bf += 4; + i -= 4; + } + while (i--) + *bf++ = ' '; + stbsp__chk_cb_buf(1); + } + } + break; + + default: // unknown, just copy code + s = num + STBSP__NUMSZ - 1; + *s = f[0]; + l = 1; + fw = fl = 0; + lead[0] = 0; + tail[0] = 0; + pr = 0; + dp = 0; + cs = 0; + goto scopy; + } + ++f; + } + endfmt: + + if (!callback) + *bf = 0; + else + stbsp__flush_cb(); + + done: + return tlen + (int)(bf - buf); +} + +// cleanup +#undef STBSP__LEFTJUST +#undef STBSP__LEADINGPLUS +#undef STBSP__LEADINGSPACE +#undef STBSP__LEADING_0X +#undef STBSP__LEADINGZERO +#undef STBSP__INTMAX +#undef STBSP__TRIPLET_COMMA +#undef STBSP__NEGATIVE +#undef STBSP__METRIC_SUFFIX +#undef STBSP__NUMSZ +#undef stbsp__chk_cb_bufL +#undef stbsp__chk_cb_buf +#undef stbsp__flush_cb +#undef stbsp__cb_buf_clamp + +// ============================================================================ +// wrapper functions + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(sprintf)(char *buf, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + result = STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); + va_end(va); + return result; +} + +typedef struct stbsp__context { + char *buf; + int count; + int length; + char tmp[STB_SPRINTF_MIN]; +} stbsp__context; + +static char *stbsp__clamp_callback(const char *buf, void *user, int len) +{ + stbsp__context *c = (stbsp__context *)user; + c->length += len; + + if (len > c->count) + len = c->count; + + if (len) { + if (buf != c->buf) { + const char *s, *se; + char *d; + d = c->buf; + s = buf; + se = buf + len; + do { + *d++ = *s++; + } while (s < se); + } + c->buf += len; + c->count -= len; + } + + if (c->count <= 0) + return c->tmp; + return (c->count >= STB_SPRINTF_MIN) ? c->buf : c->tmp; // go direct into buffer if you can +} + +static char * stbsp__count_clamp_callback( const char * buf, void * user, int len ) +{ + stbsp__context * c = (stbsp__context*)user; + (void) sizeof(buf); + + c->length += len; + return c->tmp; // go direct into buffer if you can +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE( vsnprintf )( char * buf, int count, char const * fmt, va_list va ) +{ + stbsp__context c; + + if ( (count == 0) && !buf ) + { + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__count_clamp_callback, &c, c.tmp, fmt, va ); + } + else + { + int l; + + c.buf = buf; + c.count = count; + c.length = 0; + + STB_SPRINTF_DECORATE( vsprintfcb )( stbsp__clamp_callback, &c, stbsp__clamp_callback(0,&c,0), fmt, va ); + + // zero-terminate + l = (int)( c.buf - buf ); + if ( l >= count ) // should never be greater, only equal (or less) than count + l = count - 1; + buf[l] = 0; + } + + return c.length; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(snprintf)(char *buf, int count, char const *fmt, ...) +{ + int result; + va_list va; + va_start(va, fmt); + + result = STB_SPRINTF_DECORATE(vsnprintf)(buf, count, fmt, va); + va_end(va); + + return result; +} + +STBSP__PUBLICDEF int STB_SPRINTF_DECORATE(vsprintf)(char *buf, char const *fmt, va_list va) +{ + return STB_SPRINTF_DECORATE(vsprintfcb)(0, 0, buf, fmt, va); +} + +// ======================================================================= +// low level float utility functions + +#ifndef STB_SPRINTF_NOFLOAT + +// copies d to bits w/ strict aliasing (this compiles to nothing on /Ox) +#define STBSP__COPYFP(dest, src) \ +{ \ +int cn; \ +for (cn = 0; cn < 8; cn++) \ +((char *)&dest)[cn] = ((char *)&src)[cn]; \ +} + +// get float info +static stbsp__int32 stbsp__real_to_parts(stbsp__int64 *bits, stbsp__int32 *expo, double value) +{ + double d; + stbsp__int64 b = 0; + + // load value and round at the frac_digits + d = value; + + STBSP__COPYFP(b, d); + + *bits = b & ((((stbsp__uint64)1) << 52) - 1); + *expo = (stbsp__int32)(((b >> 52) & 2047) - 1023); + + return (stbsp__int32)((stbsp__uint64) b >> 63); +} + +static double const stbsp__bot[23] = { + 1e+000, 1e+001, 1e+002, 1e+003, 1e+004, 1e+005, 1e+006, 1e+007, 1e+008, 1e+009, 1e+010, 1e+011, + 1e+012, 1e+013, 1e+014, 1e+015, 1e+016, 1e+017, 1e+018, 1e+019, 1e+020, 1e+021, 1e+022 +}; +static double const stbsp__negbot[22] = { + 1e-001, 1e-002, 1e-003, 1e-004, 1e-005, 1e-006, 1e-007, 1e-008, 1e-009, 1e-010, 1e-011, + 1e-012, 1e-013, 1e-014, 1e-015, 1e-016, 1e-017, 1e-018, 1e-019, 1e-020, 1e-021, 1e-022 +}; +static double const stbsp__negboterr[22] = { + -5.551115123125783e-018, -2.0816681711721684e-019, -2.0816681711721686e-020, -4.7921736023859299e-021, -8.1803053914031305e-022, 4.5251888174113741e-023, + 4.5251888174113739e-024, -2.0922560830128471e-025, -6.2281591457779853e-026, -3.6432197315497743e-027, 6.0503030718060191e-028, 2.0113352370744385e-029, + -3.0373745563400371e-030, 1.1806906454401013e-032, -7.7705399876661076e-032, 2.0902213275965398e-033, -7.1542424054621921e-034, -7.1542424054621926e-035, + 2.4754073164739869e-036, 5.4846728545790429e-037, 9.2462547772103625e-038, -4.8596774326570872e-039 +}; +static double const stbsp__top[13] = { + 1e+023, 1e+046, 1e+069, 1e+092, 1e+115, 1e+138, 1e+161, 1e+184, 1e+207, 1e+230, 1e+253, 1e+276, 1e+299 +}; +static double const stbsp__negtop[13] = { + 1e-023, 1e-046, 1e-069, 1e-092, 1e-115, 1e-138, 1e-161, 1e-184, 1e-207, 1e-230, 1e-253, 1e-276, 1e-299 +}; +static double const stbsp__toperr[13] = { + 8388608, + 6.8601809640529717e+028, + -7.253143638152921e+052, + -4.3377296974619174e+075, + -1.5559416129466825e+098, + -3.2841562489204913e+121, + -3.7745893248228135e+144, + -1.7356668416969134e+167, + -3.8893577551088374e+190, + -9.9566444326005119e+213, + 6.3641293062232429e+236, + -5.2069140800249813e+259, + -5.2504760255204387e+282 +}; +static double const stbsp__negtoperr[13] = { + 3.9565301985100693e-040, -2.299904345391321e-063, 3.6506201437945798e-086, 1.1875228833981544e-109, + -5.0644902316928607e-132, -6.7156837247865426e-155, -2.812077463003139e-178, -5.7778912386589953e-201, + 7.4997100559334532e-224, -4.6439668915134491e-247, -6.3691100762962136e-270, -9.436808465446358e-293, + 8.0970921678014997e-317 +}; + +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + 10000000000000000, + 100000000000000000, + 1000000000000000000, + 10000000000000000000U +}; +#define stbsp__tento19th ((stbsp__uint64)1000000000000000000) +#else +static stbsp__uint64 const stbsp__powten[20] = { + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000ULL, + 100000000000ULL, + 1000000000000ULL, + 10000000000000ULL, + 100000000000000ULL, + 1000000000000000ULL, + 10000000000000000ULL, + 100000000000000000ULL, + 1000000000000000000ULL, + 10000000000000000000ULL +}; +#define stbsp__tento19th (1000000000000000000ULL) +#endif + +#define stbsp__ddmulthi(oh, ol, xh, yh) \ +{ \ +double ahi = 0, alo, bhi = 0, blo; \ +stbsp__int64 bt; \ +oh = xh * yh; \ +STBSP__COPYFP(bt, xh); \ +bt &= ((~(stbsp__uint64)0) << 27); \ +STBSP__COPYFP(ahi, bt); \ +alo = xh - ahi; \ +STBSP__COPYFP(bt, yh); \ +bt &= ((~(stbsp__uint64)0) << 27); \ +STBSP__COPYFP(bhi, bt); \ +blo = yh - bhi; \ +ol = ((ahi * bhi - oh) + ahi * blo + alo * bhi) + alo * blo; \ +} + +#define stbsp__ddtoS64(ob, xh, xl) \ +{ \ +double ahi = 0, alo, vh, t; \ +ob = (stbsp__int64)xh; \ +vh = (double)ob; \ +ahi = (xh - vh); \ +t = (ahi - xh); \ +alo = (xh - (ahi - t)) - (vh + t); \ +ob += (stbsp__int64)(ahi + alo + xl); \ +} + +#define stbsp__ddrenorm(oh, ol) \ +{ \ +double s; \ +s = oh + ol; \ +ol = ol - (s - oh); \ +oh = s; \ +} + +#define stbsp__ddmultlo(oh, ol, xh, xl, yh, yl) ol = ol + (xh * yl + xl * yh); + +#define stbsp__ddmultlos(oh, ol, xh, yl) ol = ol + (xh * yl); + +static void stbsp__raise_to_power10(double *ohi, double *olo, double d, stbsp__int32 power) // power can be -323 to +350 +{ + double ph, pl; + if ((power >= 0) && (power <= 22)) { + stbsp__ddmulthi(ph, pl, d, stbsp__bot[power]); + } else { + stbsp__int32 e, et, eb; + double p2h, p2l; + + e = power; + if (power < 0) + e = -e; + et = (e * 0x2c9) >> 14; /* %23 */ + if (et > 13) + et = 13; + eb = e - (et * 23); + + ph = d; + pl = 0.0; + if (power < 0) { + if (eb) { + --eb; + stbsp__ddmulthi(ph, pl, d, stbsp__negbot[eb]); + stbsp__ddmultlos(ph, pl, d, stbsp__negboterr[eb]); + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__negtop[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__negtop[et], stbsp__negtoperr[et]); + ph = p2h; + pl = p2l; + } + } else { + if (eb) { + e = eb; + if (eb > 22) + eb = 22; + e -= eb; + stbsp__ddmulthi(ph, pl, d, stbsp__bot[eb]); + if (e) { + stbsp__ddrenorm(ph, pl); + stbsp__ddmulthi(p2h, p2l, ph, stbsp__bot[e]); + stbsp__ddmultlos(p2h, p2l, stbsp__bot[e], pl); + ph = p2h; + pl = p2l; + } + } + if (et) { + stbsp__ddrenorm(ph, pl); + --et; + stbsp__ddmulthi(p2h, p2l, ph, stbsp__top[et]); + stbsp__ddmultlo(p2h, p2l, ph, pl, stbsp__top[et], stbsp__toperr[et]); + ph = p2h; + pl = p2l; + } + } + } + stbsp__ddrenorm(ph, pl); + *ohi = ph; + *olo = pl; +} + +// given a float value, returns the significant bits in bits, and the position of the +// decimal point in decimal_pos. +/-INF and NAN are specified by special values +// returned in the decimal_pos parameter. +// frac_digits is absolute normally, but if you want from first significant digits (got %g and %e), or in 0x80000000 +static stbsp__int32 stbsp__real_to_str(char const **start, stbsp__uint32 *len, char *out, stbsp__int32 *decimal_pos, double value, stbsp__uint32 frac_digits) +{ + double d; + stbsp__int64 bits = 0; + stbsp__int32 expo, e, ng, tens; + + d = value; + STBSP__COPYFP(bits, d); + expo = (stbsp__int32)((bits >> 52) & 2047); + ng = (stbsp__int32)((stbsp__uint64) bits >> 63); + if (ng) + d = -d; + + if (expo == 2047) // is nan or inf? + { + *start = (bits & ((((stbsp__uint64)1) << 52) - 1)) ? "NaN" : "Inf"; + *decimal_pos = STBSP__SPECIAL; + *len = 3; + return ng; + } + + if (expo == 0) // is zero or denormal + { + if (((stbsp__uint64) bits << 1) == 0) // do zero + { + *decimal_pos = 1; + *start = out; + out[0] = '0'; + *len = 1; + return ng; + } + // find the right expo for denormals + { + stbsp__int64 v = ((stbsp__uint64)1) << 51; + while ((bits & v) == 0) { + --expo; + v >>= 1; + } + } + } + + // find the decimal exponent as well as the decimal bits of the value + { + double ph, pl; + + // log10 estimate - very specifically tweaked to hit or undershoot by no more than 1 of log10 of all expos 1..2046 + tens = expo - 1023; + tens = (tens < 0) ? ((tens * 617) / 2048) : (((tens * 1233) / 4096) + 1); + + // move the significant bits into position and stick them into an int + stbsp__raise_to_power10(&ph, &pl, d, 18 - tens); + + // get full as much precision from double-double as possible + stbsp__ddtoS64(bits, ph, pl); + + // check if we undershot + if (((stbsp__uint64)bits) >= stbsp__tento19th) + ++tens; + } + + // now do the rounding in integer land + frac_digits = (frac_digits & 0x80000000) ? ((frac_digits & 0x7ffffff) + 1) : (tens + frac_digits); + if ((frac_digits < 24)) { + stbsp__uint32 dg = 1; + if ((stbsp__uint64)bits >= stbsp__powten[9]) + dg = 10; + while ((stbsp__uint64)bits >= stbsp__powten[dg]) { + ++dg; + if (dg == 20) + goto noround; + } + if (frac_digits < dg) { + stbsp__uint64 r; + // add 0.5 at the right position and round + e = dg - frac_digits; + if ((stbsp__uint32)e >= 24) + goto noround; + r = stbsp__powten[e]; + bits = bits + (r / 2); + if ((stbsp__uint64)bits >= stbsp__powten[dg]) + ++tens; + bits /= r; + } + noround:; + } + + // kill long trailing runs of zeros + if (bits) { + stbsp__uint32 n; + for (;;) { + if (bits <= 0xffffffff) + break; + if (bits % 1000) + goto donez; + bits /= 1000; + } + n = (stbsp__uint32)bits; + while ((n % 1000) == 0) + n /= 1000; + bits = n; + donez:; + } + + // convert to string + out += 64; + e = 0; + for (;;) { + stbsp__uint32 n; + char *o = out - 8; + // do the conversion in chunks of U32s (avoid most 64-bit divides, worth it, constant denomiators be damned) + if (bits >= 100000000) { + n = (stbsp__uint32)(bits % 100000000); + bits /= 100000000; + } else { + n = (stbsp__uint32)bits; + bits = 0; + } + while (n) { + out -= 2; + *(stbsp__uint16 *)out = *(stbsp__uint16 *)&stbsp__digitpair.pair[(n % 100) * 2]; + n /= 100; + e += 2; + } + if (bits == 0) { + if ((e) && (out[0] == '0')) { + ++out; + --e; + } + break; + } + while (out != o) { + *--out = '0'; + ++e; + } + } + + *decimal_pos = tens; + *start = out; + *len = e; + return ng; +} + +#undef stbsp__ddmulthi +#undef stbsp__ddrenorm +#undef stbsp__ddmultlo +#undef stbsp__ddmultlos +#undef STBSP__SPECIAL +#undef STBSP__COPYFP + +#endif // STB_SPRINTF_NOFLOAT + +// clean up +#undef stbsp__uint16 +#undef stbsp__uint32 +#undef stbsp__int32 +#undef stbsp__uint64 +#undef stbsp__int64 +#undef STBSP__UNALIGNED + +#endif // STB_SPRINTF_IMPLEMENTATION + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/