From 90fc1de709d5e4da4a20e0a5461fdca01cb986f0 Mon Sep 17 00:00:00 2001 From: ryanfleury Date: Wed, 20 Jan 2021 11:19:34 -0700 Subject: [PATCH] Initial upload --- .gitignore | 12 + build.bat | 50 + project.4coder | 44 + samples/old_style_custom_layer.c | 47 + samples/output_parse/examples/example.md | 8 + samples/output_parse/examples/example2.md | 20 + .../examples/output/parsed_example.txt | 73 + .../examples/output/parsed_example2.txt | 282 ++ samples/output_parse/output_parse.c | 86 + .../example_site/blog1.md | 62 + .../example_site/blog2.md | 17 + .../example_site/blog_main.md | 3 + .../example_site/gen.bat | 5 + .../example_site/generated/blog1.html | 91 + .../example_site/generated/blog2.html | 43 + .../example_site/generated/blog_main.html | 68 + .../example_site/generated/site.js | 61 + .../example_site/generated/style.css | 6 + .../example_site/site.js | 61 + .../example_site/site_info.md | 34 + .../static_site_generator.c | 570 ++++ source/md.c | 16 + source/md.h | 780 +++++ source/md_impl.c | 2773 +++++++++++++++++ source/md_malloc.c | 19 + source/md_posix.c | 5 + source/md_win32.c | 95 + tests/cpp_build_test.cpp | 9 + tests/sanity_tests.c | 403 +++ tests/unicode_test.c | 26 + 30 files changed, 5769 insertions(+) create mode 100644 .gitignore create mode 100644 build.bat create mode 100644 project.4coder create mode 100644 samples/old_style_custom_layer.c create mode 100644 samples/output_parse/examples/example.md create mode 100644 samples/output_parse/examples/example2.md create mode 100644 samples/output_parse/examples/output/parsed_example.txt create mode 100644 samples/output_parse/examples/output/parsed_example2.txt create mode 100644 samples/output_parse/output_parse.c create mode 100644 samples/static_site_generator/example_site/blog1.md create mode 100644 samples/static_site_generator/example_site/blog2.md create mode 100644 samples/static_site_generator/example_site/blog_main.md create mode 100644 samples/static_site_generator/example_site/gen.bat create mode 100644 samples/static_site_generator/example_site/generated/blog1.html create mode 100644 samples/static_site_generator/example_site/generated/blog2.html create mode 100644 samples/static_site_generator/example_site/generated/blog_main.html create mode 100644 samples/static_site_generator/example_site/generated/site.js create mode 100644 samples/static_site_generator/example_site/generated/style.css create mode 100644 samples/static_site_generator/example_site/site.js create mode 100644 samples/static_site_generator/example_site/site_info.md create mode 100644 samples/static_site_generator/static_site_generator.c create mode 100644 source/md.c create mode 100644 source/md.h create mode 100644 source/md_impl.c create mode 100644 source/md_malloc.c create mode 100644 source/md_posix.c create mode 100644 source/md_win32.c create mode 100644 tests/cpp_build_test.cpp create mode 100644 tests/sanity_tests.c create mode 100644 tests/unicode_test.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86c2c0b --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.dll +*.exe +*.ilk +*.obj +*.out +*.zip +*.pdb +*.dbg +*.lib +*.exp +build +build/* \ No newline at end of file diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..554e5d4 --- /dev/null +++ b/build.bat @@ -0,0 +1,50 @@ +@echo off + +echo ~~~ Metadesk Build ~~~ +set compile_flags= /nologo /Zi /FC /MP /I..\source\ + +if not exist build mkdir build +pushd build +echo. +echo ~~~ Build All Samples ~~~ +cl %compile_flags% ..\samples\old_style_custom_layer.c +cl %compile_flags% ..\samples\static_site_generator\static_site_generator.c +cl %compile_flags% ..\samples\output_parse\output_parse.c +echo. +echo ~~~ Build All Tests ~~~ +cl %compile_flags% ..\tests\sanity_tests.c +cl %compile_flags% ..\tests\unicode_test.c +cl %compile_flags% ..\tests\cpp_build_test.cpp +popd + +echo. +echo ~~~ Running Sanity Tests ~~~ +pushd build +sanity_tests.exe +popd + +echo. +echo ~~~ Running Static Site Generator Sample ~~~ +pushd samples +pushd static_site_generator +pushd example_site +if not exist generated mkdir generated +pushd generated +..\..\..\..\build\static_site_generator.exe --siteinfo ..\site_info.md --pagedir ..\ +popd +popd +popd +popd + +echo. +echo ~~~ Running Output Parse Sample ~~~ +pushd samples +pushd output_parse +pushd examples +if not exist output mkdir output +pushd output +..\..\..\..\build\output_parse.exe ..\example.md ..\example2.md +popd +popd +popd +popd diff --git a/project.4coder b/project.4coder new file mode 100644 index 0000000..6a1ee26 --- /dev/null +++ b/project.4coder @@ -0,0 +1,44 @@ +version(1); + +project_name = "metadesk"; + +patterns = +{ + "*.c", + "*.cpp", + "*.md", + "*.h", + "*.bat", + "*.sh", + "*.4coder", + "*.txt", +}; + +blacklist_patterns = +{ + ".*", +}; + +load_paths = +{ + { + { {"."}, .recursive = true, .relative = true }, .os = "win" + }, +}; + +command_list = +{ + { + .name = "build", + .out = "*compilation*", + .footer_panel = true, + .save_dirty_files = true, + .cursor_at_end = false, + .cmd = + { + { "build.bat", .os = "win" }, + }, + }, +}; + +fkey_command[1] = "build"; diff --git a/samples/old_style_custom_layer.c b/samples/old_style_custom_layer.c new file mode 100644 index 0000000..ca8f82a --- /dev/null +++ b/samples/old_style_custom_layer.c @@ -0,0 +1,47 @@ +// Sample code for old Data Desk style custom layer, adapted to Metadesk. + +#include "md.h" +#include "md.c" + +static void +Initialize(void) +{ + // Called when the program starts. +} + +static void +TopLevel(MD_Node *node) +{ + // Called for every top-level node that is parsed from passed files. +} + +static void +CleanUp(void) +{ + // Runs before the program ends. +} + +int main(int argument_count, char **arguments) +{ + // NOTE(rjf): Parse all the files passed in via command line. + MD_Node *first = MD_NilNode(); + MD_Node *last = MD_NilNode(); + for(int i = 1; i < argument_count; i += 1) + { + MD_Node *root = MD_ParseWholeFile(MD_S8CString(arguments[i])); + MD_PushSibling(&first, &last, MD_NilNode(), root); + } + + // NOTE(rjf): Call "custom layer" back. + Initialize(); + for(MD_EachNode(root, first)) + { + for(MD_EachNode(node, root->first_child)) + { + TopLevel(node); + } + } + CleanUp(); + + return 0; +} \ No newline at end of file diff --git a/samples/output_parse/examples/example.md b/samples/output_parse/examples/example.md new file mode 100644 index 0000000..ab805ff --- /dev/null +++ b/samples/output_parse/examples/example.md @@ -0,0 +1,8 @@ +@struct foo : { + x : ([MAX_PATH]char), +} + +@struct @test_tag bar : { + y : float, + z: i32, +} diff --git a/samples/output_parse/examples/example2.md b/samples/output_parse/examples/example2.md new file mode 100644 index 0000000..9c7a8e3 --- /dev/null +++ b/samples/output_parse/examples/example2.md @@ -0,0 +1,20 @@ +@sets a_set : { + named_set : {1, 3, 7}, + {not, named, set}, + @tag_with_params(p1, p2, p3) { e1, e2, e3 }, + @tag_with_params(p1, p2, p3) @tag2_with_params(p, pp, ppp) { e1, e2, e3 }, +} + +@empty_set empty : { +} + +@tagged_unnamed_set { + a_label: {unnamed_set_element} +} + +@showcase_mixed_set_scoping { + @symbol_label + +: (1 2 3], + @symbol_label + *: [x y z), +} \ No newline at end of file diff --git a/samples/output_parse/examples/output/parsed_example.txt b/samples/output_parse/examples/output/parsed_example.txt new file mode 100644 index 0000000..81c2568 --- /dev/null +++ b/samples/output_parse/examples/output/parsed_example.txt @@ -0,0 +1,73 @@ +Node { + Kind: Label, + Flags: 00001100000100000000000000000000, + Flag Names: BraceLeft, BraceRight, Identifier, + String: foo, + Whole String: foo, + Tag: @struct + Node { + Kind: Label, + Flags: 11000001000100000000000000000000, + Flag Names: ParenLeft, ParenRight, BeforeComma, Identifier, + String: x, + Whole String: x, + Node { + Kind: UnnamedSet, + Flags: 00110000000000000000000000000000, + Flag Names: BracketLeft, BracketRight, + Node { + Kind: Label, + Flags: 00000000000100000000000000000000, + Flag Names: Identifier, + String: MAX_PATH, + Whole String: MAX_PATH, + } + } + Node { + Kind: Label, + Flags: 00000000000100000000000000000000, + Flag Names: Identifier, + String: char, + Whole String: char, + } + } +} + +Node { + Kind: Label, + Flags: 00001100000100000000000000000000, + Flag Names: BraceLeft, BraceRight, Identifier, + String: bar, + Whole String: bar, + Tag: @struct + Tag: @test_tag + Node { + Kind: Label, + Flags: 00000000000100000000000000000000, + Flag Names: Identifier, + String: y, + Whole String: y, + Node { + Kind: Label, + Flags: 00000001000100000000000000000000, + Flag Names: BeforeComma, Identifier, + String: float, + Whole String: float, + } + } + Node { + Kind: Label, + Flags: 00000000000100000000000000000000, + Flag Names: Identifier, + String: z, + Whole String: z, + Node { + Kind: Label, + Flags: 00000001000100000000000000000000, + Flag Names: BeforeComma, Identifier, + String: i32, + Whole String: i32, + } + } +} + diff --git a/samples/output_parse/examples/output/parsed_example2.txt b/samples/output_parse/examples/output/parsed_example2.txt new file mode 100644 index 0000000..c36b41e --- /dev/null +++ b/samples/output_parse/examples/output/parsed_example2.txt @@ -0,0 +1,282 @@ +Node { + Kind: Label, + Flags: 00001100000100000000000000000000, + Flag Names: BraceLeft, BraceRight, Identifier, + String: a_set, + Whole String: a_set, + Tag: @sets + Node { + Kind: Label, + Flags: 00001101000100000000000000000000, + Flag Names: BraceLeft, BraceRight, BeforeComma, Identifier, + String: named_set, + Whole String: named_set, + Node { + Kind: Label, + Flags: 00000001001000000000000000000000, + Flag Names: BeforeComma, Numeric, + String: 1, + Whole String: 1, + } + Node { + Kind: Label, + Flags: 00000001011000000000000000000000, + Flag Names: BeforeComma, AfterComma, Numeric, + String: 3, + Whole String: 3, + } + Node { + Kind: Label, + Flags: 00000000011000000000000000000000, + Flag Names: AfterComma, Numeric, + String: 7, + Whole String: 7, + } + } + Node { + Kind: UnnamedSet, + Flags: 00001101010000000000000000000000, + Flag Names: BraceLeft, BraceRight, BeforeComma, AfterComma, + Node { + Kind: Label, + Flags: 00000001000100000000000000000000, + Flag Names: BeforeComma, Identifier, + String: not, + Whole String: not, + } + Node { + Kind: Label, + Flags: 00000001010100000000000000000000, + Flag Names: BeforeComma, AfterComma, Identifier, + String: named, + Whole String: named, + } + Node { + Kind: Label, + Flags: 00000000010100000000000000000000, + Flag Names: AfterComma, Identifier, + String: set, + Whole String: set, + } + } + Node { + Kind: UnnamedSet, + Flags: 00001101010000000000000000000000, + Flag Names: BraceLeft, BraceRight, BeforeComma, AfterComma, + Tag: @tag_with_params + Tag Children{ + Node { + Kind: Label, + Flags: 00000001000100000000000000000000, + Flag Names: BeforeComma, Identifier, + String: p1, + Whole String: p1, + } + Node { + Kind: Label, + Flags: 00000001010100000000000000000000, + Flag Names: BeforeComma, AfterComma, Identifier, + String: p2, + Whole String: p2, + } + Node { + Kind: Label, + Flags: 00000000010100000000000000000000, + Flag Names: AfterComma, Identifier, + String: p3, + Whole String: p3, + } + } + Node { + Kind: Label, + Flags: 00000001000100000000000000000000, + Flag Names: BeforeComma, Identifier, + String: e1, + Whole String: e1, + } + Node { + Kind: Label, + Flags: 00000001010100000000000000000000, + Flag Names: BeforeComma, AfterComma, Identifier, + String: e2, + Whole String: e2, + } + Node { + Kind: Label, + Flags: 00000000010100000000000000000000, + Flag Names: AfterComma, Identifier, + String: e3, + Whole String: e3, + } + } + Node { + Kind: UnnamedSet, + Flags: 00001101010000000000000000000000, + Flag Names: BraceLeft, BraceRight, BeforeComma, AfterComma, + Tag: @tag_with_params + Tag Children{ + Node { + Kind: Label, + Flags: 00000001000100000000000000000000, + Flag Names: BeforeComma, Identifier, + String: p1, + Whole String: p1, + } + Node { + Kind: Label, + Flags: 00000001010100000000000000000000, + Flag Names: BeforeComma, AfterComma, Identifier, + String: p2, + Whole String: p2, + } + Node { + Kind: Label, + Flags: 00000000010100000000000000000000, + Flag Names: AfterComma, Identifier, + String: p3, + Whole String: p3, + } + } + Tag: @tag2_with_params + Tag Children{ + Node { + Kind: Label, + Flags: 00000001000100000000000000000000, + Flag Names: BeforeComma, Identifier, + String: p, + Whole String: p, + } + Node { + Kind: Label, + Flags: 00000001010100000000000000000000, + Flag Names: BeforeComma, AfterComma, Identifier, + String: pp, + Whole String: pp, + } + Node { + Kind: Label, + Flags: 00000000010100000000000000000000, + Flag Names: AfterComma, Identifier, + String: ppp, + Whole String: ppp, + } + } + Node { + Kind: Label, + Flags: 00000001000100000000000000000000, + Flag Names: BeforeComma, Identifier, + String: e1, + Whole String: e1, + } + Node { + Kind: Label, + Flags: 00000001010100000000000000000000, + Flag Names: BeforeComma, AfterComma, Identifier, + String: e2, + Whole String: e2, + } + Node { + Kind: Label, + Flags: 00000000010100000000000000000000, + Flag Names: AfterComma, Identifier, + String: e3, + Whole String: e3, + } + } +} + +Node { + Kind: Label, + Flags: 00001100000100000000000000000000, + Flag Names: BraceLeft, BraceRight, Identifier, + String: empty, + Whole String: empty, + Tag: @empty_set +} + +Node { + Kind: UnnamedSet, + Flags: 00001100000000000000000000000000, + Flag Names: BraceLeft, BraceRight, + Tag: @tagged_unnamed_set + Node { + Kind: Label, + Flags: 00001100000100000000000000000000, + Flag Names: BraceLeft, BraceRight, Identifier, + String: a_label, + Whole String: a_label, + Node { + Kind: Label, + Flags: 00000000000100000000000000000000, + Flag Names: Identifier, + String: unnamed_set_element, + Whole String: unnamed_set_element, + } + } +} + +Node { + Kind: UnnamedSet, + Flags: 00001100000000000000000000000000, + Flag Names: BraceLeft, BraceRight, + Tag: @showcase_mixed_set_scoping + Node { + Kind: Label, + Flags: 10010001000000000000000000000000, + Flag Names: ParenLeft, BracketRight, BeforeComma, + String: +, + Whole String: +, + Tag: @symbol_label + Node { + Kind: Label, + Flags: 00000000001000000000000000000000, + Flag Names: Numeric, + String: 1, + Whole String: 1, + } + Node { + Kind: Label, + Flags: 00000000001000000000000000000000, + Flag Names: Numeric, + String: 2, + Whole String: 2, + } + Node { + Kind: Label, + Flags: 00000000001000000000000000000000, + Flag Names: Numeric, + String: 3, + Whole String: 3, + } + } + Node { + Kind: Label, + Flags: 01100001010000000000000000000000, + Flag Names: ParenRight, BracketLeft, BeforeComma, AfterComma, + String: *, + Whole String: *, + Tag: @symbol_label + Node { + Kind: Label, + Flags: 00000000000100000000000000000000, + Flag Names: Identifier, + String: x, + Whole String: x, + } + Node { + Kind: Label, + Flags: 00000000000100000000000000000000, + Flag Names: Identifier, + String: y, + Whole String: y, + } + Node { + Kind: Label, + Flags: 00000000000100000000000000000000, + Flag Names: Identifier, + String: z, + Whole String: z, + } + } +} + diff --git a/samples/output_parse/output_parse.c b/samples/output_parse/output_parse.c new file mode 100644 index 0000000..4a2ed6d --- /dev/null +++ b/samples/output_parse/output_parse.c @@ -0,0 +1,86 @@ +// Sample code to print out the data from MD_ParseWholeFile +#include "md.h" +#include "md.c" + +#define INDENT_SPACES 2 +static void Print(FILE* file, int indent_count, char* fmt, ...) { + for(int i = 0; i < indent_count*INDENT_SPACES; i += 1) + { + fprintf(file, " "); + } + va_list args; + va_start(args, fmt); + vfprintf(file, fmt, args); + va_end(args); +} + +static void PrintNode(MD_Node* node, FILE* file, int indent_count) { + Print(file, indent_count, "Node {\n"); + Print(file, indent_count+1, "Kind: %.*s,\n", MD_StringExpand(MD_StringFromNodeKind(node->kind))); + + int flags_bits = sizeof(node->flags)*8; + char binary_flags[sizeof(node->flags)*8+1]; + binary_flags[flags_bits] = '\0'; + int flag_index = 0; + MD_NodeFlags flags = node->flags; + for (int i = 0; i < flags_bits; i++) { + binary_flags[i] = (flags&1) ? '1' : '0'; + flag_index++; + flags >>= 1; + } + + Print(file, indent_count+1, "Flags: %s,\n", binary_flags); + Print(file, indent_count+1, "Flag Names: ", binary_flags); + MD_String8List flags_list = MD_StringListFromNodeFlags(node->flags); + MD_String8 flag_names = MD_JoinStringListWithSeparator(flags_list, MD_S8CString(", ")); + fprintf(file, "%.*s,\n", MD_StringExpand(flag_names)); + + if(node->string.size > 0) Print(file, indent_count+1, "String: %.*s,\n", MD_StringExpand(node->string)); + if(node->whole_string.size > 0) Print(file, indent_count+1, "Whole String: %.*s,\n", MD_StringExpand(node->whole_string)); + if (node->first_tag->kind != MD_NodeKind_Nil) { + for (MD_EachNode(tag, node->first_tag)) { + Print(file, indent_count+1, "Tag: @%.*s\n", MD_StringExpand(tag->string)); + if (tag->first_child->kind != MD_NodeKind_Nil) { + Print(file, indent_count+2, "Tag Children{\n"); + for (MD_EachNode(arg, tag->first_child)) { + PrintNode(arg, file, indent_count+3); + } + Print(file, indent_count+2, "}\n"); + } + } + } + + for(MD_EachNode(child, node->first_child)) { + PrintNode(child, file, indent_count+1); + } + Print(file, indent_count, "}\n"); +} + +int main(int argument_count, char **arguments) +{ + // NOTE(pmh): Parse all the files passed in via command line. + MD_Node *first = MD_NilNode(); + MD_Node *last = MD_NilNode(); + for(int i = 1; i < argument_count; i += 1) + { + MD_Node *root = MD_ParseWholeFile(MD_S8CString(arguments[i])); + MD_PushSibling(&first, &last, MD_NilNode(), root); + } + + for(MD_EachNode(root, first)) + { + MD_String8 code_filename = MD_TrimExtension(MD_TrimFolder(root->filename)); + MD_String8 info_filename = MD_PushStringF("parsed_%.*s.txt", MD_StringExpand(code_filename)); + printf("Parse Input -> Output: %.*s -> %.*s\n", MD_StringExpand(code_filename), MD_StringExpand(info_filename)); + + FILE* file = fopen((char *)info_filename.str, "w"); + for(MD_EachNode(node, root->first_child)) + { + PrintNode(node, file, 0); + fprintf(file, "\n"); + } + fclose(file); + } + + return 0; +} \ No newline at end of file diff --git a/samples/static_site_generator/example_site/blog1.md b/samples/static_site_generator/example_site/blog1.md new file mode 100644 index 0000000..75a15d5 --- /dev/null +++ b/samples/static_site_generator/example_site/blog1.md @@ -0,0 +1,62 @@ +title: "Test Blog #1" +desc: "This is my test blog." +date: (2020/11/30) +index: {"blog"} +parent: "blog_main" + +@subtitle "Section 1" + +""" +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ut ornare neque, vitae finibus leo. Praesent pulvinar, urna id lobortis iaculis, velit turpis luctus tortor, quis malesuada neque neque vitae turpis. Phasellus id molestie elit. Sed elementum ipsum a ligula semper, at varius augue rutrum. Pellentesque fringilla, orci nec fringilla finibus, turpis lacus vehicula elit, nec bibendum magna est quis massa. Aliquam efficitur facilisis nibh, ut sodales sapien scelerisque nec. Integer justo sem, pellentesque et ante imperdiet, interdum placerat sem. + +Nunc hendrerit lobortis commodo. Morbi felis quam, fermentum vitae libero vitae, fermentum sodales quam. Nulla bibendum tellus quis lorem sollicitudin pretium. Etiam commodo ex eget aliquet porta. In sit amet dui eleifend, mattis sem nec, tristique erat. Morbi malesuada fringilla bibendum. Integer odio ex, cursus a sodales quis, vehicula non dolor. + +Praesent vitae pharetra felis. Curabitur non ex non nunc pretium feugiat eu sit amet turpis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean blandit ante leo. Vivamus iaculis mi vel sem tincidunt maximus. Aenean elementum ipsum non laoreet semper. Morbi non felis interdum lectus pulvinar mollis id non purus. Nullam eu ipsum ut turpis aliquam feugiat at non tellus. Suspendisse ornare erat quis enim ullamcorper, at rhoncus nulla suscipit. +""" + +@img {"test.png", "My funny image."} + +@subtitle "Section 2" + +""" +Aliquam quis diam at sem interdum imperdiet. Nunc imperdiet ligula tempus nibh semper mattis. Suspendisse fringilla molestie semper. Sed felis dolor, vehicula et tempus sed, porttitor finibus lacus. Pellentesque egestas ex finibus, facilisis tellus suscipit, accumsan turpis. Ut imperdiet vitae nisl eget vestibulum. Donec eu bibendum erat, id maximus magna. Vivamus quis rhoncus justo. Morbi eget commodo lorem, vulputate varius ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer accumsan, sem vel rutrum cursus, odio enim sodales leo, sit amet rutrum odio purus at nisi. Fusce iaculis ante id nunc volutpat tincidunt. Duis nec tincidunt ipsum. Fusce vitae odio ac velit sollicitudin maximus. Vestibulum ante dui, varius auctor ante eu, gravida laoreet justo. + +Vivamus dignissim mauris nec turpis convallis, vitae pellentesque sapien faucibus. Phasellus eu euismod elit, sit amet vehicula diam. Curabitur sit amet leo magna. In est erat, congue vel euismod id, venenatis vitae mauris. Vestibulum sit amet leo eget leo ornare feugiat. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vivamus at efficitur ex, a mattis lorem. Maecenas blandit tincidunt tortor vel cursus. In in purus a nisi pretium venenatis in ut lacus. Maecenas venenatis rutrum laoreet. Donec aliquam leo vel risus cursus, placerat pretium lacus fringilla. Suspendisse ut porttitor mauris. +""" + +@youtube {"BjdY1eXB6cY"} + +@subtitle "Section 3" + +@list +{ + "hello", + "world", + "foo", + @list + { + "a", + "b", + "c", + }, + "another list item", +} + +@subtitle "Section 4" + +@code +""" +char *html_tag = "p"; +if(DD_NodeHasTag(node, DD_S8Lit("title"))) +{ + html_tag = "h1"; +} +else if(DD_NodeHasTag(node, DD_S8Lit("subtitle"))) +{ + html_tag = "h2"; +} +else if(DD_NodeHasTag(node, DD_S8Lit("code"))) +{ + html_tag = "pre"; +} +""" \ No newline at end of file diff --git a/samples/static_site_generator/example_site/blog2.md b/samples/static_site_generator/example_site/blog2.md new file mode 100644 index 0000000..c3f6f30 --- /dev/null +++ b/samples/static_site_generator/example_site/blog2.md @@ -0,0 +1,17 @@ +title: "Hello, Again!" +desc: "This is another test blog." +date: (2020/11/28) +index: {"blog"} +parent: "blog_main" + +@subtitle "Section 1" + +""" +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ut ornare neque, vitae finibus leo. Praesent pulvinar, urna id lobortis iaculis, velit turpis luctus tortor, quis malesuada neque neque vitae turpis. Phasellus id molestie elit. Sed elementum ipsum a ligula semper, at varius augue rutrum. Pellentesque fringilla, orci nec fringilla finibus, turpis lacus vehicula elit, nec bibendum magna est quis massa. Aliquam efficitur facilisis nibh, ut sodales sapien scelerisque nec. Integer justo sem, pellentesque et ante imperdiet, interdum placerat sem. + +Nunc hendrerit lobortis commodo. Morbi felis quam, fermentum vitae libero vitae, fermentum sodales quam. Nulla bibendum tellus quis lorem sollicitudin pretium. Etiam commodo ex eget aliquet porta. In sit amet dui eleifend, mattis sem nec, tristique erat. Morbi malesuada fringilla bibendum. Integer odio ex, cursus a sodales quis, vehicula non dolor. + +Praesent vitae pharetra felis. Curabitur non ex non nunc pretium feugiat eu sit amet turpis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean blandit ante leo. Vivamus iaculis mi vel sem tincidunt maximus. Aenean elementum ipsum non laoreet semper. Morbi non felis interdum lectus pulvinar mollis id non purus. Nullam eu ipsum ut turpis aliquam feugiat at non tellus. Suspendisse ornare erat quis enim ullamcorper, at rhoncus nulla suscipit. +""" + +@img {"test.png", "My funny image."} diff --git a/samples/static_site_generator/example_site/blog_main.md b/samples/static_site_generator/example_site/blog_main.md new file mode 100644 index 0000000..22b8b35 --- /dev/null +++ b/samples/static_site_generator/example_site/blog_main.md @@ -0,0 +1,3 @@ +title: "Blog" + +@lister {"blog"} diff --git a/samples/static_site_generator/example_site/gen.bat b/samples/static_site_generator/example_site/gen.bat new file mode 100644 index 0000000..3c5c105 --- /dev/null +++ b/samples/static_site_generator/example_site/gen.bat @@ -0,0 +1,5 @@ +@echo off +if not exist generated mkdir generated +pushd generated +..\..\..\build\static_site_generator.exe --siteinfo ..\site_info.md --pagedir ..\ +popd \ No newline at end of file diff --git a/samples/static_site_generator/example_site/generated/blog1.html b/samples/static_site_generator/example_site/generated/blog1.html new file mode 100644 index 0000000..ea7006b --- /dev/null +++ b/samples/static_site_generator/example_site/generated/blog1.html @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + +Test Blog #1 | Example Site + + + +
+

Example Site

+Test Link 1 +Test Link 2 +Test Link 3 +
+
+

Test Blog #1

This is my test blog.

30 November 2020

Section 1

+

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ut ornare neque, vitae finibus leo. Praesent pulvinar, urna id lobortis iaculis, velit turpis luctus tortor, quis malesuada neque neque vitae turpis. Phasellus id molestie elit. Sed elementum ipsum a ligula semper, at varius augue rutrum. Pellentesque fringilla, orci nec fringilla finibus, turpis lacus vehicula elit, nec bibendum magna est quis massa. Aliquam efficitur facilisis nibh, ut sodales sapien scelerisque nec. Integer justo sem, pellentesque et ante imperdiet, interdum placerat sem.

+

Nunc hendrerit lobortis commodo. Morbi felis quam, fermentum vitae libero vitae, fermentum sodales quam. Nulla bibendum tellus quis lorem sollicitudin pretium. Etiam commodo ex eget aliquet porta. In sit amet dui eleifend, mattis sem nec, tristique erat. Morbi malesuada fringilla bibendum. Integer odio ex, cursus a sodales quis, vehicula non dolor.

+

Praesent vitae pharetra felis. Curabitur non ex non nunc pretium feugiat eu sit amet turpis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean blandit ante leo. Vivamus iaculis mi vel sem tincidunt maximus. Aenean elementum ipsum non laoreet semper. Morbi non felis interdum lectus pulvinar mollis id non purus. Nullam eu ipsum ut turpis aliquam feugiat at non tellus. Suspendisse ornare erat quis enim ullamcorper, at rhoncus nulla suscipit. +

+
+

Section 2

+

+Aliquam quis diam at sem interdum imperdiet. Nunc imperdiet ligula tempus nibh semper mattis. Suspendisse fringilla molestie semper. Sed felis dolor, vehicula et tempus sed, porttitor finibus lacus. Pellentesque egestas ex finibus, facilisis tellus suscipit, accumsan turpis. Ut imperdiet vitae nisl eget vestibulum. Donec eu bibendum erat, id maximus magna. Vivamus quis rhoncus justo. Morbi eget commodo lorem, vulputate varius ipsum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Integer accumsan, sem vel rutrum cursus, odio enim sodales leo, sit amet rutrum odio purus at nisi. Fusce iaculis ante id nunc volutpat tincidunt. Duis nec tincidunt ipsum. Fusce vitae odio ac velit sollicitudin maximus. Vestibulum ante dui, varius auctor ante eu, gravida laoreet justo.

+

Vivamus dignissim mauris nec turpis convallis, vitae pellentesque sapien faucibus. Phasellus eu euismod elit, sit amet vehicula diam. Curabitur sit amet leo magna. In est erat, congue vel euismod id, venenatis vitae mauris. Vestibulum sit amet leo eget leo ornare feugiat. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vivamus at efficitur ex, a mattis lorem. Maecenas blandit tincidunt tortor vel cursus. In in purus a nisi pretium venenatis in ut lacus. Maecenas venenatis rutrum laoreet. Donec aliquam leo vel risus cursus, placerat pretium lacus fringilla. Suspendisse ut porttitor mauris. +

+ +

Section 3

+ +

Section 4

+
+char *html_tag = "p";
+if(DD_NodeHasTag(node, DD_S8Lit("title")))
+{
+    html_tag = "h1";
+}
+else if(DD_NodeHasTag(node, DD_S8Lit("subtitle")))
+{
+    html_tag = "h2";
+}
+else if(DD_NodeHasTag(node, DD_S8Lit("code")))
+{
+    html_tag = "pre";
+}
+
+
+ + + + diff --git a/samples/static_site_generator/example_site/generated/blog2.html b/samples/static_site_generator/example_site/generated/blog2.html new file mode 100644 index 0000000..55a3c73 --- /dev/null +++ b/samples/static_site_generator/example_site/generated/blog2.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + +Hello, Again! | Example Site + + + +
+

Example Site

+Test Link 1 +Test Link 2 +Test Link 3 +
+
+

Hello, Again!

This is another test blog.

28 November 2020

Section 1

+

+Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ut ornare neque, vitae finibus leo. Praesent pulvinar, urna id lobortis iaculis, velit turpis luctus tortor, quis malesuada neque neque vitae turpis. Phasellus id molestie elit. Sed elementum ipsum a ligula semper, at varius augue rutrum. Pellentesque fringilla, orci nec fringilla finibus, turpis lacus vehicula elit, nec bibendum magna est quis massa. Aliquam efficitur facilisis nibh, ut sodales sapien scelerisque nec. Integer justo sem, pellentesque et ante imperdiet, interdum placerat sem.

+

Nunc hendrerit lobortis commodo. Morbi felis quam, fermentum vitae libero vitae, fermentum sodales quam. Nulla bibendum tellus quis lorem sollicitudin pretium. Etiam commodo ex eget aliquet porta. In sit amet dui eleifend, mattis sem nec, tristique erat. Morbi malesuada fringilla bibendum. Integer odio ex, cursus a sodales quis, vehicula non dolor.

+

Praesent vitae pharetra felis. Curabitur non ex non nunc pretium feugiat eu sit amet turpis. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean blandit ante leo. Vivamus iaculis mi vel sem tincidunt maximus. Aenean elementum ipsum non laoreet semper. Morbi non felis interdum lectus pulvinar mollis id non purus. Nullam eu ipsum ut turpis aliquam feugiat at non tellus. Suspendisse ornare erat quis enim ullamcorper, at rhoncus nulla suscipit. +

+
+
+ + + + diff --git a/samples/static_site_generator/example_site/generated/blog_main.html b/samples/static_site_generator/example_site/generated/blog_main.html new file mode 100644 index 0000000..75b8ea3 --- /dev/null +++ b/samples/static_site_generator/example_site/generated/blog_main.html @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + +Blog | Example Site + + + +
+

Example Site

+Test Link 1 +Test Link 2 +Test Link 3 +
+
+

Blog

+
+ + + + diff --git a/samples/static_site_generator/example_site/generated/site.js b/samples/static_site_generator/example_site/generated/site.js new file mode 100644 index 0000000..d38215e --- /dev/null +++ b/samples/static_site_generator/example_site/generated/site.js @@ -0,0 +1,61 @@ +function StringFitsFilter(str, filter) +{ + let match = true; + let filter_upper = filter.toUpperCase(); + let filter_substrings = filter_upper.split(/[ _*]+/); + let str_upper = str.toUpperCase(); + let minimum_index = 0; + + for(let i = 0; i < filter_substrings.length; ++i) + { + if(filter_substrings[i].length > 0) + { + let index_of_substring = str_upper.indexOf(filter_substrings[i], minimum_index); + if(index_of_substring < 0 || index_of_substring < minimum_index) + { + match = false; + break; + } + minimum_index = index_of_substring + filter_substrings[i].length - 1; + } + } + + return match; +} + +function UpdateListByFilter(menu_id, filter_id) +{ + let ul = document.getElementById(menu_id); + let li = ul.getElementsByTagName("li"); + let input = document.getElementById(filter_id); + let filter = input.value.toUpperCase(); + for(let i = 0; i < li.length; i++) + { + if(filter.length > 0) + { + let a = li[i].getElementsByTagName("a")[0]; + if(StringFitsFilter(a.innerHTML, filter)) + { + li[i].style.display = ""; + } + else + { + li[i].style.display = "none"; + } + } + else + { + li[i].style.display = ""; + } + } +} + +function SearchInput(event, lister_idx) +{ + UpdateListByFilter("lister_"+lister_idx, "lister_search_"+lister_idx); +} + +function SearchKeyDown(event, lister_idx) +{ + UpdateListByFilter("lister_"+lister_idx, "lister_search_"+lister_idx); +} \ No newline at end of file diff --git a/samples/static_site_generator/example_site/generated/style.css b/samples/static_site_generator/example_site/generated/style.css new file mode 100644 index 0000000..4200426 --- /dev/null +++ b/samples/static_site_generator/example_site/generated/style.css @@ -0,0 +1,6 @@ + +* +{ + padding: 0; + margins: 0; +} diff --git a/samples/static_site_generator/example_site/site.js b/samples/static_site_generator/example_site/site.js new file mode 100644 index 0000000..d38215e --- /dev/null +++ b/samples/static_site_generator/example_site/site.js @@ -0,0 +1,61 @@ +function StringFitsFilter(str, filter) +{ + let match = true; + let filter_upper = filter.toUpperCase(); + let filter_substrings = filter_upper.split(/[ _*]+/); + let str_upper = str.toUpperCase(); + let minimum_index = 0; + + for(let i = 0; i < filter_substrings.length; ++i) + { + if(filter_substrings[i].length > 0) + { + let index_of_substring = str_upper.indexOf(filter_substrings[i], minimum_index); + if(index_of_substring < 0 || index_of_substring < minimum_index) + { + match = false; + break; + } + minimum_index = index_of_substring + filter_substrings[i].length - 1; + } + } + + return match; +} + +function UpdateListByFilter(menu_id, filter_id) +{ + let ul = document.getElementById(menu_id); + let li = ul.getElementsByTagName("li"); + let input = document.getElementById(filter_id); + let filter = input.value.toUpperCase(); + for(let i = 0; i < li.length; i++) + { + if(filter.length > 0) + { + let a = li[i].getElementsByTagName("a")[0]; + if(StringFitsFilter(a.innerHTML, filter)) + { + li[i].style.display = ""; + } + else + { + li[i].style.display = "none"; + } + } + else + { + li[i].style.display = ""; + } + } +} + +function SearchInput(event, lister_idx) +{ + UpdateListByFilter("lister_"+lister_idx, "lister_search_"+lister_idx); +} + +function SearchKeyDown(event, lister_idx) +{ + UpdateListByFilter("lister_"+lister_idx, "lister_search_"+lister_idx); +} \ No newline at end of file diff --git a/samples/static_site_generator/example_site/site_info.md b/samples/static_site_generator/example_site/site_info.md new file mode 100644 index 0000000..ac48cf9 --- /dev/null +++ b/samples/static_site_generator/example_site/site_info.md @@ -0,0 +1,34 @@ +title: "Example Site" +desc: "My example site, used to demonstrate the static site generator example for Data Desk." +canonical_url: "https://example.site" +author: "Ryan Fleury" +twitter_handle: "@ryanjfleury" + +header: +""" +
+

Example Site

+Test Link 1 +Test Link 2 +Test Link 3 +
+""" + +footer: +""" + +""" + +style: +""" +* +{ + padding: 0; + margins: 0; +} +""" diff --git a/samples/static_site_generator/static_site_generator.c b/samples/static_site_generator/static_site_generator.c new file mode 100644 index 0000000..e07a162 --- /dev/null +++ b/samples/static_site_generator/static_site_generator.c @@ -0,0 +1,570 @@ +#include "md.h" +#include "md.c" + +typedef struct SiteInfo SiteInfo; +struct SiteInfo +{ + MD_Node *title; + MD_Node *desc; + MD_Node *canonical_url; + MD_Node *author; + MD_Node *twitter_handle; + MD_Node *link_dictionary; + MD_Node *header; + MD_Node *footer; + MD_Node *style; +}; + +typedef struct PageInfo PageInfo; +struct PageInfo +{ + MD_Node *title; + MD_Node *desc; + MD_Node *date; + MD_Node *parent; + MD_Node *header_image; +}; + +static PageInfo ParsePageInfo(MD_Node *page); +static SiteInfo ParseSiteInfo(MD_Node *site); +static MD_String8 MakeDateString(MD_Node *date); +static void GeneratePageContent(MD_NodeTable *index_table, SiteInfo *site_info, PageInfo *page_info, FILE *file, MD_Node *node); + +int main(int argument_count, char **arguments) +{ + + //~ NOTE(rjf): Parse command line arguments. + MD_String8 site_info_path = {0}; + MD_String8 page_dir_path = {0}; + MD_CommandLine cmdln = MD_CommandLine_Start(argument_count, arguments); + if(!MD_CommandLine_FlagString(&cmdln, MD_S8Lit("--siteinfo"), &site_info_path) || + !MD_CommandLine_FlagString(&cmdln, MD_S8Lit("--pagedir"), &page_dir_path)) + { + fprintf(stderr, "USAGE: %s --siteinfo --pagedir ...\n", arguments[0]); + goto end; + } + + //~ NOTE(rjf): Load JS. + MD_String8 js_string = MD_LoadEntireFile(MD_PushStringF("%.*s/site.js", MD_StringExpand(page_dir_path))); + + //~ NOTE(rjf): Parse site info. + SiteInfo site_info = {0}; + { + printf("Parsing site metadata at \"%.*s\"...\n", MD_StringExpand(site_info_path)); + MD_Node *site_info_file = MD_ParseWholeFile(site_info_path); + site_info = ParseSiteInfo(site_info_file); + } + + //~ NOTE(rjf): Parse pages. + MD_Node *first_root = MD_NilNode(); + MD_Node *last_root = MD_NilNode(); + { + printf("Searching for site pages at \"%.*s\"...\n", MD_StringExpand(page_dir_path)); + MD_FileInfo file_info = {0}; + for(MD_FileIter it = {0}; MD_FileIterIncrement(&it, page_dir_path, &file_info);) + { + if(MD_StringMatch(MD_ExtensionFromPath(file_info.filename), MD_S8Lit("md"), MD_StringMatchFlag_CaseInsensitive) && + !MD_StringMatch(MD_TrimFolder(MD_TrimExtension(file_info.filename)), + MD_TrimFolder(MD_TrimExtension(site_info_path)), + MD_StringMatchFlag_CaseInsensitive | + MD_StringMatchFlag_SlashInsensitive)) + { + printf("Processing site page at \"%.*s\"...\n", MD_StringExpand(file_info.filename)); + MD_String8 folder = MD_FolderFromPath(page_dir_path); + MD_String8 path = MD_PushStringF("%.*s/%.*s", + MD_StringExpand(folder), + MD_StringExpand(file_info.filename)); + MD_PushSibling(&first_root, &last_root, MD_NilNode(), MD_ParseWholeFile(path)); + } + } + } + + //~ NOTE(rjf): Generate index table. + MD_NodeTable index_table = {0}; + { + for(MD_EachNode(root, first_root)) + { + for(MD_EachNode(node, root->first_child)) + { + if(!MD_NodeIsNil(node->first_child) && MD_StringMatch(node->string, MD_S8Lit("index"), MD_StringMatchFlag_CaseInsensitive)) + { + for(MD_EachNode(index_string, node->first_child)) + { + MD_NodeTable_Insert(&index_table, MD_NodeTableCollisionRule_Chain, index_string->string, root); + } + goto end_index_build; + } + } + end_index_build:; + } + } + + //~ NOTE(rjf): Generate stylesheet. + if(site_info.style) + { + FILE *file = fopen("style.css", "w"); + if(file) + { + fprintf(file, "%.*s", MD_StringExpand(site_info.style->string)); + fclose(file); + } + } + + //~ NOTE(rjf): Generate JS. + { + FILE *file = fopen("site.js", "w"); + if(file) + { + fprintf(file, "%.*s", MD_StringExpand(js_string)); + fclose(file); + } + } + + //~ NOTE(rjf): Generate files for all roots. + for(MD_EachNode(root, first_root)) + { + PageInfo page_info = ParsePageInfo(root); + + MD_String8 name_without_extension = MD_TrimFolder(MD_TrimExtension(root->filename)); + FILE *file = fopen(MD_PushStringF("%.*s.html", MD_StringExpand(name_without_extension)).str, "w"); + if(file) + { + fprintf(file, "\n"); + fprintf(file, "\n"); + + MD_String8 site_title = !MD_NodeIsNil(site_info.title) ? site_info.title->string : MD_S8Lit(""); + MD_String8 title = !MD_NodeIsNil(page_info.title) ? page_info.title->string : MD_S8Lit(""); + MD_String8 url = !MD_NodeIsNil(site_info.canonical_url) ? site_info.canonical_url->string : MD_S8Lit(""); + MD_String8 author = !MD_NodeIsNil(site_info.author) ? site_info.author->string : MD_S8Lit(""); + MD_String8 twitter_handle = !MD_NodeIsNil(site_info.twitter_handle) ? site_info.twitter_handle->string : MD_S8Lit(""); + + // NOTE(rjf): Generate heading. + { + fprintf(file, "\n"); + fprintf(file, "\n"); + fprintf(file, "\n", MD_StringExpand(author)); + fprintf(file, "\n", MD_StringExpand(title)); + fprintf(file, "\n", MD_StringExpand(title)); + fprintf(file, "\n", MD_StringExpand(url)); + fprintf(file, "\n"); + fprintf(file, "\n", MD_StringExpand(url)); + fprintf(file, "\n", MD_StringExpand(site_title)); + fprintf(file, "\n"); + fprintf(file, "\n", MD_StringExpand(twitter_handle)); + fprintf(file, "\n"); + fprintf(file, "\n"); + if(title.size > 0) + { + if(site_title.size > 0) + { + fprintf(file, "%.*s | %.*s\n", MD_StringExpand(title), MD_StringExpand(site_title)); + } + else + { + fprintf(file, "%.*s\n", MD_StringExpand(title)); + } + } + else if(site_title.size > 0) + { + fprintf(file, "%.*s\n", MD_StringExpand(site_title)); + } + fprintf(file, "\n"); + } + + // NOTE(rjf): Generate body. + { + fprintf(file, "\n"); + + // NOTE(rjf): Generate header. + if(site_info.header) + { + fprintf(file, "%.*s", MD_StringExpand(site_info.header->string)); + } + + fprintf(file, "
\n"); + + // NOTE(rjf): Parent page back button. + if(page_info.parent) + { + fprintf(file, "", MD_StringExpand(page_info.parent->string)); + } + + // NOTE(rjf): Banner. + if(page_info.header_image) + { + fprintf(file, "
", + MD_StringExpand(page_info.header_image->string)); + } + + // NOTE(rjf): Title. + if(title.size > 0) + { + fprintf(file, "

%.*s

", MD_StringExpand(title)); + } + + // NOTE(rjf): Main description/subtitle. + if(page_info.desc) + { + fprintf(file, "

%.*s

", MD_StringExpand(page_info.desc->string)); + } + + // NOTE(rjf): Date. + if(page_info.date) + { + MD_String8 date_string = MakeDateString(page_info.date); + if(date_string.size > 0) + { + fprintf(file, "

%.*s

", MD_StringExpand(date_string)); + } + } + + // NOTE(rjf): The rest of the page content should be generated from the page nodes. + for(MD_EachNode(node, root->first_child)) + { + GeneratePageContent(&index_table, &site_info, &page_info, file, node); + } + + fprintf(file, "
\n"); + + // NOTE(rjf): Generate footer. + if(site_info.footer) + { + fprintf(file, "%.*s", MD_StringExpand(site_info.footer->string)); + } + + fprintf(file, "\n"); + } + + fprintf(file, "\n"); + fclose(file); + } + } + + end:; + return 0; +} + +static PageInfo +ParsePageInfo(MD_Node *page) +{ + PageInfo info = {0}; + for(MD_EachNode(node, page->first_child)) + { + if(!MD_NodeIsNil(node->first_child)) + { + if(MD_StringMatch(node->string, MD_S8Lit("title"), MD_StringMatchFlag_CaseInsensitive)) + { + info.title = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("desc"), MD_StringMatchFlag_CaseInsensitive)) + { + info.desc = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("date"), MD_StringMatchFlag_CaseInsensitive)) + { + info.date = node; + } + else if(MD_StringMatch(node->string, MD_S8Lit("parent"), MD_StringMatchFlag_CaseInsensitive)) + { + info.parent = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("header_image"), MD_StringMatchFlag_CaseInsensitive)) + { + info.header_image = node->first_child; + } + } + } + return info; +} + +static SiteInfo +ParseSiteInfo(MD_Node *site) +{ + SiteInfo info = {0}; + for(MD_EachNode(node, site->first_child)) + { + if(!MD_NodeIsNil(node->first_child)) + { + if(MD_StringMatch(node->string, MD_S8Lit("title"), MD_StringMatchFlag_CaseInsensitive)) + { + info.title = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("desc"), MD_StringMatchFlag_CaseInsensitive)) + { + info.desc = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("canonical_url"), MD_StringMatchFlag_CaseInsensitive)) + { + info.canonical_url = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("author"), MD_StringMatchFlag_CaseInsensitive)) + { + info.author = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("twitter_handle"), MD_StringMatchFlag_CaseInsensitive)) + { + info.twitter_handle = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("link_dictionary"), MD_StringMatchFlag_CaseInsensitive)) + { + info.link_dictionary = node; + } + else if(MD_StringMatch(node->string, MD_S8Lit("header"), MD_StringMatchFlag_CaseInsensitive)) + { + info.header = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("footer"), MD_StringMatchFlag_CaseInsensitive)) + { + info.footer = node->first_child; + } + else if(MD_StringMatch(node->string, MD_S8Lit("style"), MD_StringMatchFlag_CaseInsensitive)) + { + info.style = node->first_child; + } + } + } + return info; +} + +static MD_String8 +MakeDateString(MD_Node *date) +{ + MD_String8 result = {0}; + if(date) + { + MD_Node *year = 0; + MD_Node *month = 0; + MD_Node *day = 0; + + for(MD_EachNode(child, date->first_child)) + { + if(child->flags & MD_NodeFlag_Numeric) + { + if (year == 0) year = child; + else if (month == 0) month = child; + else if (day == 0) day = child; + else + { + break; + } + } + } + + if(year && month && day) + { + char *month_names[] = + { + "January", "February", "March", "April", "May", "June", "July", "August", + "September", "October", "November", "December", + }; + int month_idx = MD_I64FromString(month->string)-1; + if(month_idx >= 0 && month_idx < sizeof(month_names)/sizeof(month_names[0])) + { + result = MD_PushStringF("%.*s %s %.*s", + MD_StringExpand(day->string), + month_names[month_idx], + MD_StringExpand(year->string)); + } + } + } + + return result; +} + +static void +GeneratePageContent(MD_NodeTable *index_table, SiteInfo *site_info, PageInfo *page_info, FILE *file, MD_Node *node) +{ + + //~ NOTE(rjf): Text blobs + if(MD_NodeIsNil(node->first_child) && + (node->flags & MD_NodeFlag_StringLiteral || + node->flags & MD_NodeFlag_CharLiteral)) + { + char *html_tag = "p"; + char *style = "paragraph"; + if(MD_NodeHasTag(node, MD_S8Lit("title"))) + { + html_tag = "h1"; + style = "title"; + } + else if(MD_NodeHasTag(node, MD_S8Lit("subtitle"))) + { + html_tag = "h2"; + style = "subtitle"; + } + else if(MD_NodeHasTag(node, MD_S8Lit("code"))) + { + html_tag = "pre"; + style = "code"; + } + + MD_String8 splits[] = + { + MD_S8Lit("\n\n"), + }; + MD_String8List strlist = MD_SplitString(node->string, sizeof(splits)/sizeof(splits[0]), splits); + + for(MD_String8Node *strnode = strlist.first; strnode; strnode = strnode->next) + { + fprintf(file, "<%s class=\"%s\">", html_tag, style); + for(MD_u64 i = 0; i < strnode->string.size; i += 1) + { + if(strnode->string.str[i] == '@') + { + MD_ParseResult parse = MD_ParseOneNode(node->filename, MD_StringSubstring(strnode->string, i, strnode->string.size)); + if(!MD_NodeIsNil(parse.node)) + { + if(MD_NodeHasTag(node, MD_S8Lit("i"))) + { + fprintf(file, "%.*s", MD_StringExpand(parse.node->string)); + } + else if(MD_NodeHasTag(node, MD_S8Lit("b"))) + { + fprintf(file, "%.*s", MD_StringExpand(parse.node->string)); + } + else if(MD_NodeHasTag(node, MD_S8Lit("code"))) + { + fprintf(file, "%.*s", MD_StringExpand(parse.node->string)); + } + else if(MD_NodeHasTag(node, MD_S8Lit("link"))) + { + MD_Node *text = MD_ChildFromIndex(parse.node, 0); + MD_Node *link = MD_ChildFromIndex(parse.node, 1); + fprintf(file, "%.*s", + MD_StringExpand(link->string), + MD_StringExpand(text->string)); + } + } + i += parse.bytes_parsed - 1; + } + else + { + MD_b32 dict_word = 0; + if(site_info->link_dictionary) + { + MD_Node *text = MD_NilNode(); + MD_Node *link = MD_NilNode(); + + for(MD_EachNode(dict_link, site_info->link_dictionary->first_child)) + { + text = MD_ChildFromIndex(dict_link, 0); + link = MD_ChildFromIndex(dict_link, 1); + MD_String8 substring = MD_StringSubstring(strnode->string, i, i+text->string.size); + if(MD_StringMatch(substring, text->string, 0)) + { + fprintf(file, "%.*s", + MD_StringExpand(link->string), + MD_StringExpand(text->string)); + dict_word = 1; + i += text->string.size-1; + break; + } + } + } + + if(!dict_word) + { + fprintf(file, "%c", strnode->string.str[i]); + } + } + } + fprintf(file, "\n", html_tag); + } + } + + if(!MD_NodeIsNil(node->first_child)) + { + if(MD_NodeHasTag(node, MD_S8Lit("list"))) + { + fprintf(file, "
    \n"); + for(MD_EachNode(child, node->first_child)) + { + if(MD_NodeIsNil(child->first_child)) + { + fprintf(file, "
  • \n"); + } + GeneratePageContent(index_table, site_info, page_info, file, child); + if(MD_NodeIsNil(child->first_child)) + { + fprintf(file, "
  • \n"); + } + } + fprintf(file, "
\n"); + } + else if(MD_NodeHasTag(node, MD_S8Lit("img"))) + { + MD_Node *src = MD_ChildFromIndex(node, 0); + MD_Node *alt = MD_ChildFromIndex(node, 1); + fprintf(file, "
\n", MD_StringExpand(src->string)); + } + else if(MD_NodeHasTag(node, MD_S8Lit("youtube"))) + { + MD_Node *id = MD_ChildFromIndex(node, 0); + fprintf(file, "\n", + MD_StringExpand(id->string)); + } + else if(MD_NodeHasTag(node, MD_S8Lit("lister"))) + { + static int lister_idx = 0; + fprintf(file, "", lister_idx, lister_idx, lister_idx); + fprintf(file, "\n"); + } + } +} diff --git a/source/md.c b/source/md.c new file mode 100644 index 0000000..58d09e8 --- /dev/null +++ b/source/md.c @@ -0,0 +1,16 @@ +//~ Metadesk Library + +// TODO(allen): Test including this after + +// NOTE(allen): Include the OS specific functions +#if MD_OS_WINDOWS +# include "md_win32.c" +#elif MD_OS_LINUX +# include "md_posix.c" +#else +# error No default implementation for this OS +#endif + +#include "md_malloc.c" + +#include "md_impl.c" diff --git a/source/md.h b/source/md.h new file mode 100644 index 0000000..dc5969b --- /dev/null +++ b/source/md.h @@ -0,0 +1,780 @@ +//~ Metadesk Library + +// TODO List +// +// - Expression/Type helper +// - Parsing things as a type, getting basic type info +// - Radix for MD_I64FromString +// - Freeing calls, allow hooking the allocator with an arena or +// something so that someone can use this for work at an application/game's +// runtime +// - Outputting anything to MD or C code ideally would do something +// smart with auto-indentation, since some people have wanted readable +// layout (if they aren't using 4coder with virtual whitespace basically) +// - Building with Clang +// - Split out C-related stuff into helper language layers +// - Helpers for parsing NodeFlags, figuring out which nodes in a set are +// separated by a semicolon, something like MD_SeekNodeWithFlags(node) -> node ? + +// NOTE(allen): "Plugin" functionality +// +// MD_b32 MD_IMPL_FileIterIncrement(MD_FileIter*, MD_String8, MD_FileInfo*) - optional +// void* MD_IMPL_Alloc(void*,MD_u64) - required +// void* MD_IMPL_GetCtx(void) - optional +// +// TODO(allen): Commentary about this system somewhere easy to discover when +// you go digging. + +#ifndef MD_H +#define MD_H + +// NOTE(rjf): Compiler cracking from the 4th dimension + +#if 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(__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 +# 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(__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 + +// NOTE(allen): Review @rjf; Building in C++ +// Added language cracking. Handy for a few pesky problems that can't be solved +// strictly within the intersection of C & C++ +#if defined(__cplusplus) +# define MD_LANG_CPP 1 +#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 + +#define MD_FUNCTION +#define MD_GLOBAL static + +// NOTE(allen): Review @rjf; Building in C++ +// In order to link to C functions from C++ code, we need to mark them as using +// C linkage. In particular I mean FindFirstFileA, FindNextFileA right now. +// We don't necessarily need to apply this to the DD functions if the user is +// building from source, so I haven't done that. + +#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 + +#include +#include +#include +#include +// NOTE(allen): Review @rjf; Building in C++ +// In C++ compiler I have to include this to get memset and memcpy to compile +#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 int8_t MD_b8; +typedef int16_t MD_b16; +typedef int32_t MD_b32; +typedef int64_t MD_b64; +typedef float MD_f32; +typedef double MD_f64; + +//~ Basic UTF-8 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 MD_u32 MD_StringMatchFlags; +enum +{ + MD_StringMatchFlag_CaseInsensitive = (1<<0), + MD_StringMatchFlag_RightSideSloppy = (1<<1), + MD_StringMatchFlag_FindLast = (1<<2), + MD_StringMatchFlag_SlashInsensitive = (1<<3), +}; + +typedef struct MD_UnicodeConsume MD_UnicodeConsume; +struct MD_UnicodeConsume +{ + MD_u32 codepoint; + MD_u32 advance; +}; + +typedef enum MD_WordStyle +{ + MD_WordStyle_UpperCamelCase, + MD_WordStyle_LowerCamelCase, + MD_WordStyle_UpperCase, + MD_WordStyle_LowerCase, +} +MD_WordStyle; + +//~ Node types that are used to build all ASTs. + +typedef enum MD_NodeKind +{ + MD_NodeKind_Nil, + MD_NodeKind_File, + MD_NodeKind_Label, + MD_NodeKind_UnnamedSet, + MD_NodeKind_Tag, + MD_NodeKind_MAX, +} +MD_NodeKind; + +typedef MD_u32 MD_NodeFlags; +enum +{ + MD_NodeFlag_ParenLeft = (1<<0), + MD_NodeFlag_ParenRight = (1<<1), + MD_NodeFlag_BracketLeft = (1<<2), + MD_NodeFlag_BracketRight = (1<<3), + MD_NodeFlag_BraceLeft = (1<<4), + MD_NodeFlag_BraceRight = (1<<5), + + MD_NodeFlag_BeforeSemicolon = (1<<6), + MD_NodeFlag_BeforeComma = (1<<7), + + MD_NodeFlag_AfterSemicolon = (1<<8), + MD_NodeFlag_AfterComma = (1<<9), + + MD_NodeFlag_Numeric = (1<<10), + MD_NodeFlag_Identifier = (1<<11), + MD_NodeFlag_StringLiteral = (1<<12), + MD_NodeFlag_CharLiteral = (1<<13), +}; + +typedef MD_u32 MD_NodeMatchFlags; +enum +{ + MD_NodeMatchFlag_Tags = (1<<0), + MD_NodeMatchFlag_TagArguments = (1<<1), +}; + +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 whole_string; + MD_u64 string_hash; + + // Source code location information. + MD_String8 filename; + MD_u8 *file_contents; + MD_u8 *at; +}; + +//~ Code Location Info. + +typedef struct MD_CodeLoc MD_CodeLoc; +struct MD_CodeLoc +{ + MD_String8 filename; + int line; + int column; +}; + +//~ Warning Levels + +typedef enum MD_MessageKind +{ + MD_MessageKind_Error, + MD_MessageKind_Warning, +} +MD_MessageKind; + +//~ String-To-Node table + +typedef enum MD_NodeTableCollisionRule +{ + MD_NodeTableCollisionRule_Chain, + MD_NodeTableCollisionRule_Overwrite, +} +MD_NodeTableCollisionRule; + +typedef struct MD_NodeTableSlot MD_NodeTableSlot; +struct MD_NodeTableSlot +{ + MD_NodeTableSlot *next; + MD_u64 hash; + MD_Node *node; +}; + +typedef struct MD_NodeTable MD_NodeTable; +struct MD_NodeTable +{ + MD_u64 table_size; + MD_NodeTableSlot **table; +}; + +//~ Token kinds. + +typedef enum MD_TokenKind +{ + MD_TokenKind_Nil, + + MD_TokenKind_RegularMin, + + // A group of characters that begins with an underscore or alphabetic character, + // and consists of numbers, alphabetic characters, or underscores after that. + MD_TokenKind_Identifier, + + // A group of characters beginning with a numeric character or a '-', and then + // consisting of only numbers, alphabetic characters, or '.'s after that. + MD_TokenKind_NumericLiteral, + + // A group of arbitrary characters, grouped together by a " character, OR by a + // """ symbol at the beginning and end of the group. String literals beginning with + // " are to only be specified on a single line, but """ strings can exist across + // many lines. + MD_TokenKind_StringLiteral, + + // A group of arbitrary characters, grouped together by a ' character at the beginning, + // and a ' character at the end. + MD_TokenKind_CharLiteral, + + // A group of symbolic characters, where symbolic characters means any of the following: + // ~!@#$%^&*()-+=[{]}:;<>,./?|\ + // + // Groups of multiple characters are only allowed in specific circumstances. Most of these + // are only 1 character long, but some groups are allowed: + // + // "<<", ">>", "<=", ">=", "+=", "-=", "*=", "/=", "::", ":=", "==", "&=", "|=", "->" + MD_TokenKind_Symbol, + + MD_TokenKind_RegularMax, + + MD_TokenKind_Comment, + + MD_TokenKind_WhitespaceMin, + MD_TokenKind_Whitespace, + MD_TokenKind_Newline, + MD_TokenKind_WhitespaceMax, + + MD_TokenKind_MAX, +} +MD_TokenKind; + +//~ Token type. +typedef struct MD_Token MD_Token; +struct MD_Token +{ + MD_TokenKind kind; + MD_String8 string; + MD_String8 outer_string; +}; + +//~ Token groups. +typedef MD_u32 MD_TokenGroups; +enum{ + MD_TokenGroup_Comment = (1 << 0), + MD_TokenGroup_Whitespace = (1 << 1), + MD_TokenGroup_Regular = (1 << 2) +}; + +//~ Parsing State + +typedef struct MD_Error MD_Error; +struct MD_Error +{ + MD_Error *next; + MD_String8 string; + MD_String8 filename; + MD_Node *node; +}; + +typedef struct MD_ParseCtx MD_ParseCtx; +struct MD_ParseCtx +{ + MD_Node *first_root; + MD_Node *last_root; + MD_Error *first_error; + MD_Error *last_error; + MD_u8 *at; + MD_String8 filename; + MD_String8 file_contents; +}; + +typedef struct MD_ParseResult MD_ParseResult; +struct MD_ParseResult +{ + MD_Node *node; + MD_Error *first_error; + MD_u64 bytes_parsed; +}; + +//~ Expression and Type-Expression parser helper types. + +typedef enum MD_ExprKind +{ + // VERY_IMPORTANT_NOTE(rjf): If this enum is ever changed, ensure that + // it is kept in-sync with the MD_ExprPrecFromExprKind and the following + // functions: + // + // MD_BinaryExprKindFromNode + // MD_PreUnaryExprKindFromNode + // MD_PostUnaryExprKindFromNode + + MD_ExprKind_Nil, + + // NOTE(rjf): Atom + MD_ExprKind_Atom, + + // NOTE(rjf): Access + MD_ExprKind_Dot, + MD_ExprKind_Arrow, + MD_ExprKind_Call, + MD_ExprKind_Subscript, + MD_ExprKind_Dereference, + MD_ExprKind_Reference, + + // NOTE(rjf): Arithmetic + MD_ExprKind_Add, + MD_ExprKind_Subtract, + MD_ExprKind_Multiply, + MD_ExprKind_Divide, + MD_ExprKind_Mod, + + // NOTE(rjf): Comparison + MD_ExprKind_IsEqual, + MD_ExprKind_IsNotEqual, + MD_ExprKind_LessThan, + MD_ExprKind_GreaterThan, + MD_ExprKind_LessThanEqualTo, + MD_ExprKind_GreaterThanEqualTo, + + // NOTE(rjf): Bools + MD_ExprKind_BoolAnd, + MD_ExprKind_BoolOr, + MD_ExprKind_BoolNot, + + // NOTE(rjf): Bitwise + MD_ExprKind_BitAnd, + MD_ExprKind_BitOr, + MD_ExprKind_BitNot, + MD_ExprKind_BitXor, + MD_ExprKind_LeftShift, + MD_ExprKind_RightShift, + + // NOTE(rjf): Unary numeric + MD_ExprKind_Negative, + + // NOTE(rjf): Type + MD_ExprKind_Pointer, + MD_ExprKind_Array, + + MD_ExprKind_MAX, +} +MD_ExprKind; + +typedef MD_i32 MD_ExprPrec; + +typedef struct MD_Expr MD_Expr; +struct MD_Expr +{ + MD_Node *node; + MD_ExprKind kind; + MD_Expr *parent; + MD_Expr *sub[2]; +}; + +//~ Command line parsing helper types. +typedef struct MD_CommandLine MD_CommandLine; +struct MD_CommandLine +{ + // TODO(rjf): Linked-list vs. array? + MD_String8 *arguments; + int argument_count; +}; + +//~ 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_u64 state; +}; + +//~ Basic Utilities +#define MD_Assert(c) if (!(c)) { *(volatile MD_u64 *)0 = 0; } +#define MD_ArrayCount(a) (sizeof(a) / sizeof((a)[0])) +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_CharIsSymbol(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_CorrectSlash(MD_u8 c); + +//~ Strings +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 + +MD_FUNCTION MD_String8 MD_S8Range(MD_u8 *str, MD_u8 *opl); + +MD_FUNCTION MD_String8 MD_StringSubstring(MD_String8 str, MD_u64 min, MD_u64 max); +MD_FUNCTION MD_String8 MD_StringSkip(MD_String8 str, MD_u64 min); +MD_FUNCTION MD_String8 MD_StringChop(MD_String8 str, MD_u64 nmax); +MD_FUNCTION MD_String8 MD_StringPrefix(MD_String8 str, MD_u64 size); +MD_FUNCTION MD_String8 MD_StringSuffix(MD_String8 str, MD_u64 size); + +MD_FUNCTION MD_b32 MD_StringMatch(MD_String8 a, MD_String8 b, MD_StringMatchFlags flags); +MD_FUNCTION MD_u64 MD_FindSubstring(MD_String8 str, MD_String8 substring, + MD_u64 start_pos, MD_StringMatchFlags flags); +MD_FUNCTION MD_u64 MD_FindLastSubstring(MD_String8 str, MD_String8 substring, MD_StringMatchFlags flags); + +MD_FUNCTION MD_String8 MD_TrimExtension(MD_String8 string); +MD_FUNCTION MD_String8 MD_TrimFolder(MD_String8 string); +MD_FUNCTION MD_String8 MD_ExtensionFromPath(MD_String8 string); +MD_FUNCTION MD_String8 MD_FolderFromPath(MD_String8 string); + +MD_FUNCTION MD_String8 MD_PushStringCopy(MD_String8 string); +MD_FUNCTION MD_String8 MD_PushStringFV(char *fmt, va_list args); +MD_FUNCTION MD_String8 MD_PushStringF(char *fmt, ...); + +#define MD_StringExpand(s) (int)(s).size, (s).str + +MD_FUNCTION void MD_PushStringToList(MD_String8List *list, MD_String8 string); +MD_FUNCTION void MD_PushStringListToList(MD_String8List *list, MD_String8List *to_push); +// TODO(rjf): Just simplify to a single splitter +MD_FUNCTION MD_String8List MD_SplitString(MD_String8 string, int split_count, MD_String8 *splits); +MD_FUNCTION MD_String8List MD_SplitStringByString(MD_String8 string, MD_String8 split); +MD_FUNCTION MD_String8List MD_SplitStringByCharacter(MD_String8 string, MD_u8 character); +MD_FUNCTION MD_String8 MD_JoinStringList(MD_String8List list); +MD_FUNCTION MD_String8 MD_JoinStringListWithSeparator(MD_String8List list, MD_String8 separator); +// TODO(rjf): Radix +MD_FUNCTION MD_i64 MD_I64FromString(MD_String8 string); +MD_FUNCTION MD_f64 MD_F64FromString(MD_String8 string); +MD_FUNCTION MD_u64 MD_HashString(MD_String8 string); +MD_FUNCTION MD_u64 MD_CalculateCStringLength(char *cstr); + +MD_FUNCTION MD_String8 MD_StyledStringFromString(MD_String8 string, MD_WordStyle word_style, MD_String8 separator); + +//~ Enum/Flag Strings +MD_FUNCTION MD_String8 MD_StringFromNodeKind(MD_NodeKind kind); +MD_FUNCTION MD_String8List MD_StringListFromNodeFlags(MD_NodeFlags flags); + +//~ Unicode Conversions +MD_FUNCTION MD_UnicodeConsume MD_CodepointFromUtf8(MD_u8 *str, MD_u64 max); +MD_FUNCTION MD_UnicodeConsume MD_CodepointFromUtf16(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_String16 str); +MD_FUNCTION MD_String16 MD_S16FromS8(MD_String8 str); +MD_FUNCTION MD_String8 MD_S8FromS32(MD_String32 str); +MD_FUNCTION MD_String32 MD_S32FromS8(MD_String8 str); + +//~ String-To-Node-List Table +MD_FUNCTION MD_NodeTableSlot *MD_NodeTable_Lookup(MD_NodeTable *table, MD_String8 string); +MD_FUNCTION MD_b32 MD_NodeTable_Insert(MD_NodeTable *table, MD_NodeTableCollisionRule collision_rule, MD_String8 string, MD_Node *node); + +//~ Parsing +MD_FUNCTION MD_Token MD_ZeroToken(void); +MD_FUNCTION MD_b32 MD_TokenKindIsWhitespace(MD_TokenKind kind); +MD_FUNCTION MD_b32 MD_TokenKindIsComment(MD_TokenKind kind); +MD_FUNCTION MD_b32 MD_TokenKindIsRegular(MD_TokenKind kind); +MD_FUNCTION MD_ParseCtx MD_Parse_InitializeCtx(MD_String8 filename, MD_String8 contents); + +MD_FUNCTION void MD_Parse_Bump(MD_ParseCtx *ctx, MD_Token token); +MD_FUNCTION void MD_Parse_BumpNext(MD_ParseCtx *ctx); +MD_FUNCTION MD_Token MD_Parse_LexNext(MD_ParseCtx *ctx); +MD_FUNCTION MD_Token MD_Parse_PeekSkipSome(MD_ParseCtx *ctx, MD_TokenGroups skip_groups); +MD_FUNCTION MD_b32 MD_Parse_TokenMatch(MD_Token token, MD_String8 string, MD_StringMatchFlags flags); +MD_FUNCTION MD_b32 MD_Parse_Require(MD_ParseCtx *ctx, MD_String8 string); +MD_FUNCTION MD_b32 MD_Parse_RequireKind(MD_ParseCtx *ctx, MD_TokenKind kind, MD_Token *out_token); +MD_FUNCTION MD_ParseResult MD_ParseOneNode (MD_String8 filename, MD_String8 contents); +MD_FUNCTION MD_Node * MD_ParseWholeString (MD_String8 filename, MD_String8 contents); +MD_FUNCTION MD_Node * MD_ParseWholeFile (MD_String8 filename); + +//~ Tree/List Building +MD_FUNCTION MD_b32 MD_NodeIsNil(MD_Node *node); +MD_FUNCTION MD_Node *MD_NilNode(void); +MD_FUNCTION MD_Node *MD_MakeNodeFromToken(MD_NodeKind kind, MD_String8 filename, MD_u8 *file, MD_u8 *at, MD_Token token); +MD_FUNCTION MD_Node *MD_MakeNodeFromString(MD_NodeKind kind, MD_String8 filename, MD_u8 *file, MD_u8 *at, MD_String8 string); +MD_FUNCTION void MD_PushSibling(MD_Node **first, MD_Node **last, MD_Node *parent, MD_Node *new_sibling); +MD_FUNCTION void MD_PushChild(MD_Node *parent, MD_Node *new_child); +MD_FUNCTION void MD_PushTag(MD_Node *node, MD_Node *tag); + +//~ Introspection Helpers +MD_FUNCTION MD_Node * MD_NodeFromString(MD_Node *first, MD_Node *last, MD_String8 string); +MD_FUNCTION MD_Node * MD_NodeFromIndex(MD_Node *first, MD_Node *last, int n); +MD_FUNCTION int MD_IndexFromNode(MD_Node *node); +MD_FUNCTION MD_Node * MD_NextNodeSibling(MD_Node *last, MD_String8 string); +MD_FUNCTION MD_Node * MD_ChildFromString(MD_Node *node, MD_String8 child_string); +MD_FUNCTION MD_Node * MD_TagFromString(MD_Node *node, MD_String8 tag_string); +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, int n); +MD_FUNCTION MD_b32 MD_NodeHasTag(MD_Node *node, MD_String8 tag_string); +MD_FUNCTION MD_CodeLoc MD_CodeLocFromNode(MD_Node *node); +MD_FUNCTION MD_i64 MD_ChildCountFromNode(MD_Node *node); +MD_FUNCTION MD_i64 MD_TagCountFromNode(MD_Node *node); +MD_FUNCTION MD_i64 MD_ChildCountFromNodeAndString(MD_Node *node, MD_String8 string, MD_StringMatchFlags flags); +MD_FUNCTION MD_i64 MD_TagCountFromNodeAndString(MD_Node *node, MD_String8 string, MD_StringMatchFlags flags); +// NOTE(rjf): For-Loop Helper +#define MD_EachNode(it, first) MD_Node *it = (first); !MD_NodeIsNil(it); it = it->next + +//~ Error/Warning Helpers +MD_FUNCTION void MD_NodeMessage(MD_Node *node, MD_MessageKind kind, MD_String8 str); +MD_FUNCTION void MD_NodeError(MD_Node *node, MD_String8 str); +MD_FUNCTION void MD_NodeWarning(MD_Node *node, MD_String8 str); +MD_FUNCTION void MD_NodeMessageF(MD_Node *node, MD_MessageKind kind, char *fmt, ...); +MD_FUNCTION void MD_NodeErrorF(MD_Node *node, char *fmt, ...); +MD_FUNCTION void MD_NodeWarningF(MD_Node *node, char *fmt, ...); + +//~ Tree Comparison/Verification +MD_FUNCTION MD_b32 MD_NodeMatch(MD_Node *a, MD_Node *b, MD_StringMatchFlags str_flags, MD_NodeMatchFlags node_flags); +MD_FUNCTION MD_b32 MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_StringMatchFlags str_flags, MD_NodeMatchFlags node_flags); + +//~ Expression and Type-Expression Helper +MD_FUNCTION MD_Expr * MD_NilExpr(void); +MD_FUNCTION MD_b32 MD_ExprIsNil(MD_Expr *expr); +MD_FUNCTION MD_ExprKind MD_PreUnaryExprKindFromNode(MD_Node *node); +MD_FUNCTION MD_ExprKind MD_BinaryExprKindFromNode(MD_Node *node); +MD_FUNCTION MD_ExprPrec MD_ExprPrecFromExprKind(MD_ExprKind kind); +MD_FUNCTION MD_Expr * MD_MakeExpr(MD_Node *node, MD_ExprKind kind, MD_Expr *left, MD_Expr *right); +MD_FUNCTION MD_Expr * MD_ParseAsExpr(MD_Node *first, MD_Node *last); +MD_FUNCTION MD_Expr * MD_ParseAsType(MD_Node *first, MD_Node *last); +MD_FUNCTION MD_i64 MD_EvaluateExpr_I64(MD_Expr *expr); +MD_FUNCTION MD_f64 MD_EvaluateExpr_F64(MD_Expr *expr); +MD_FUNCTION MD_b32 MD_ExprMatch(MD_Expr *a, MD_Expr *b, MD_StringMatchFlags str_flags); +MD_FUNCTION MD_b32 MD_ExprDeepMatch(MD_Expr *a, MD_Expr *b, MD_StringMatchFlags str_flags); + +//~ Generation +MD_FUNCTION void MD_OutputTree(FILE *file, MD_Node *node); +MD_FUNCTION void MD_OutputExpr(FILE *file, MD_Expr *expr); +MD_FUNCTION void MD_OutputTree_C_String(FILE *file, MD_Node *node); +MD_FUNCTION void MD_OutputTree_C_Struct(FILE *file, MD_Node *node); +MD_FUNCTION void MD_OutputTree_C_Decl(FILE *file, MD_Node *node); +MD_FUNCTION void MD_Output_C_DeclByNameAndType(FILE *file, MD_String8 name, MD_Expr *type); +MD_FUNCTION void MD_OutputType_C_LHS(FILE *file, MD_Expr *type); +MD_FUNCTION void MD_OutputType_C_RHS(FILE *file, MD_Expr *type); + +//~ Command Line Argument Helper +MD_FUNCTION MD_CommandLine MD_CommandLine_Start(int argument_count, char **arguments); +MD_FUNCTION MD_b32 MD_CommandLine_Flag(MD_CommandLine *cmdln, MD_String8 string); +MD_FUNCTION MD_b32 MD_CommandLine_FlagStrings(MD_CommandLine *cmdln, MD_String8 string, int out_count, MD_String8 *out); +MD_FUNCTION MD_b32 MD_CommandLine_FlagIntegers(MD_CommandLine *cmdln, MD_String8 string, int out_count, MD_i64 *out); +MD_FUNCTION MD_b32 MD_CommandLine_FlagString(MD_CommandLine *cmdln, MD_String8 string, MD_String8 *out); +MD_FUNCTION MD_b32 MD_CommandLine_FlagInteger(MD_CommandLine *cmdln, MD_String8 string, MD_i64 *out); +MD_FUNCTION MD_b32 MD_CommandLine_Increment(MD_CommandLine *cmdln, MD_String8 **string_ptr); + +//~ File System +MD_FUNCTION MD_String8 MD_LoadEntireFile(MD_String8 filename); +MD_FUNCTION MD_b32 MD_FileIterIncrement(MD_FileIter *it, MD_String8 path, MD_FileInfo *out_info); + +#endif // MD_H \ No newline at end of file diff --git a/source/md_impl.c b/source/md_impl.c new file mode 100644 index 0000000..6d72dce --- /dev/null +++ b/source/md_impl.c @@ -0,0 +1,2773 @@ +#define MD_FUNCTION_IMPL MD_FUNCTION +#define MD_PRIVATE_FUNCTION_IMPL MD_FUNCTION_IMPL + +//~ + +// NOTE(allen): Review @rjf; Building in C++ +// While very latest version of C++ have designated initializers +// I would like to be able to build on more simple versions, so I +// ditched the designated initializers in favor of the extra work +// of maintaining order based initializers. +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 + {0}, // string + {0}, // whole_string + 0xdeadffffffffffull, + {(MD_u8*)"`NIL DD NODE`", 13}, + 0, + 0, +}; + +MD_PRIVATE_FUNCTION_IMPL void +_MD_MemoryZero(void *memory, MD_u64 size) +{ + memset(memory, 0, size); +} + +MD_PRIVATE_FUNCTION_IMPL void +_MD_MemoryCopy(void *dest, void *src, MD_u64 size) +{ + memcpy(dest, src, size); +} + +MD_PRIVATE_FUNCTION_IMPL void +_MD_WriteStringToBuffer(MD_String8 string, MD_u64 max, void *dest) +{ + MD_u64 write_size = string.size; + if(write_size > max-1) write_size = max-1; + _MD_MemoryCopy(dest, string.str, write_size); + ((MD_u8 *)dest)[write_size] = 0; +} + +MD_PRIVATE_FUNCTION_IMPL void * +_MD_AllocZero(void *ctx, MD_u64 size) +{ +#if !defined(MD_IMPL_Alloc) +# error Missing implementation detail MD_IMPL_Alloc +#else + void *result = MD_IMPL_Alloc(ctx, size); + _MD_MemoryZero(result, size); + return(result); +#endif +} + +#define _MD_PushArray(ctx, type, count) (type *)_MD_AllocZero((ctx), sizeof(type)*(count)) + +MD_PRIVATE_FUNCTION_IMPL void* +_MD_GetCtx(void) +{ +#if !defined(MD_IMPL_GetCtx) + return(0); +#else + return(MD_IMPL_GetCtx()); +#endif +} + + +MD_FUNCTION_IMPL MD_b32 +MD_CharIsAlpha(MD_u8 c) +{ + return MD_CharIsAlphaUpper(c) || MD_CharIsAlphaLower(c); +} + +MD_FUNCTION_IMPL MD_b32 +MD_CharIsAlphaUpper(MD_u8 c) +{ + return c >= 'A' && c <= 'Z'; +} + +MD_FUNCTION_IMPL MD_b32 +MD_CharIsAlphaLower(MD_u8 c) +{ + return c >= 'a' && c <= 'z'; +} + +MD_FUNCTION_IMPL MD_b32 +MD_CharIsDigit(MD_u8 c) +{ + return (c >= '0' && c <= '9'); +} + +MD_FUNCTION_IMPL MD_b32 +MD_CharIsSymbol(MD_u8 c) +{ + return (c == '~' || c == '!' || c == '@' || c == '#' || c == '$' || + c == '%' || c == '^' || c == '&' || c == '*' || c == '(' || + c == ')' || c == '-' || c == '=' || c == '+' || c == '[' || + c == ']' || c == '{' || c == '}' || c == ':' || c == ';' || + c == ',' || c == '<' || c == '.' || c == '>' || c == '/' || + c == '?' || c == '|' || c == '\\'); +} + +MD_FUNCTION_IMPL MD_b32 +MD_CharIsSpace(MD_u8 c) +{ + return c == ' ' || c == '\r' || c == '\t' || c == '\f' || c == '\v'; +} + +MD_FUNCTION_IMPL MD_u8 +MD_CharToUpper(MD_u8 c) +{ + return (c >= 'a' && c <= 'z') ? ('A' + (c - 'a')) : c; +} + +MD_FUNCTION_IMPL MD_u8 +MD_CharToLower(MD_u8 c) +{ + return (c >= 'A' && c <= 'Z') ? ('a' + (c - 'A')) : c; +} + +MD_FUNCTION_IMPL MD_u8 +MD_CorrectSlash(MD_u8 c) +{ + return (c == '\\' ? '/' : c); +} + +MD_FUNCTION_IMPL MD_String8 +MD_S8(MD_u8 *str, MD_u64 size) +{ + MD_String8 string; + string.str = str; + string.size = size; + return string; +} + +MD_FUNCTION_IMPL MD_String8 +MD_S8Range(MD_u8 *str, MD_u8 *opl) +{ + MD_String8 string; + string.str = str; + string.size = (MD_u64)(opl - str); + return string; +} + +MD_FUNCTION_IMPL MD_String8 +MD_StringSubstring(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_IMPL MD_String8 +MD_StringSkip(MD_String8 str, MD_u64 min) +{ + return MD_StringSubstring(str, min, str.size); +} + +MD_FUNCTION_IMPL MD_String8 +MD_StringChop(MD_String8 str, MD_u64 nmax) +{ + return MD_StringSubstring(str, 0, str.size - nmax); +} + +MD_FUNCTION_IMPL MD_String8 +MD_StringPrefix(MD_String8 str, MD_u64 size) +{ + return MD_StringSubstring(str, 0, size); +} + +MD_FUNCTION_IMPL MD_String8 +MD_StringSuffix(MD_String8 str, MD_u64 size) +{ + return MD_StringSubstring(str, str.size - size, size); +} + +MD_FUNCTION_IMPL MD_b32 +MD_StringMatch(MD_String8 a, MD_String8 b, MD_StringMatchFlags 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_CorrectSlash(a.str[i]) == MD_CorrectSlash(b.str[i])); + } + if(match == 0) + { + result = 0; + break; + } + } + } + return result; +} + +MD_FUNCTION_IMPL MD_u64 +MD_FindSubstring(MD_String8 str, MD_String8 substring, MD_u64 start_pos, MD_StringMatchFlags 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_StringSubstring(str, i, i+substring.size); + if(MD_StringMatch(substr_from_str, substring, flags)) + { + found_idx = i; + found = 1; + if(!(flags & MD_StringMatchFlag_FindLast)) + { + break; + } + } + } + } + return found_idx; +} + +MD_FUNCTION_IMPL MD_u64 +MD_FindLastSubstring(MD_String8 str, MD_String8 substring, MD_StringMatchFlags flags) +{ + return MD_FindSubstring(str, substring, 0, flags | MD_StringMatchFlag_FindLast); +} + +MD_FUNCTION_IMPL MD_String8 +MD_TrimExtension(MD_String8 string) +{ + MD_u64 period_pos = MD_FindLastSubstring(string, MD_S8Lit("."), 0); + if(period_pos < string.size) + { + string.size = period_pos; + } + return string; +} + +MD_FUNCTION_IMPL MD_String8 +MD_TrimFolder(MD_String8 string) +{ + MD_u64 slash_pos = MD_FindLastSubstring(string, MD_S8Lit("/"), MD_StringMatchFlag_SlashInsensitive); + if(slash_pos < string.size) + { + string.str += slash_pos+1; + string.size -= slash_pos+1; + } + return string; +} + +MD_FUNCTION_IMPL MD_String8 +MD_ExtensionFromPath(MD_String8 string) +{ + MD_u64 period_pos = MD_FindLastSubstring(string, MD_S8Lit("."), 0); + if(period_pos < string.size) + { + string.str += period_pos+1; + string.size -= period_pos+1; + } + return string; +} + +MD_FUNCTION_IMPL MD_String8 +MD_FolderFromPath(MD_String8 string) +{ + MD_u64 slash_pos = MD_FindLastSubstring(string, MD_S8Lit("/"), MD_StringMatchFlag_SlashInsensitive); + if(slash_pos < string.size) + { + string.size = slash_pos; + } + return string; +} + +MD_FUNCTION_IMPL MD_String8 +MD_PushStringCopy(MD_String8 string) +{ + MD_String8 res; + res.size = string.size; + res.str = _MD_PushArray(_MD_GetCtx(), MD_u8, string.size+1); + _MD_MemoryCopy(res.str, string.str, string.size); + return res; +} + +MD_FUNCTION_IMPL MD_String8 +MD_PushStringFV(char *fmt, va_list args) +{ + MD_String8 result = {0}; + va_list args2; + va_copy(args2, args); + MD_u64 needed_bytes = vsnprintf(0, 0, fmt, args)+1; + result.str = _MD_PushArray(_MD_GetCtx(), MD_u8, needed_bytes); + result.size = needed_bytes-1; + vsnprintf((char*)result.str, needed_bytes, fmt, args2); + return result; +} + +MD_FUNCTION_IMPL MD_String8 +MD_PushStringF(char *fmt, ...) +{ + MD_String8 result = {0}; + va_list args; + va_start(args, fmt); + result = MD_PushStringFV(fmt, args); + va_end(args); + return result; +} + +MD_FUNCTION_IMPL void +MD_PushStringToList(MD_String8List *list, MD_String8 string) +{ + list->node_count += 1; + list->total_size += string.size; + + MD_String8Node *node = _MD_PushArray(_MD_GetCtx(), MD_String8Node, 1); + node->next = 0; + node->string = string; + if(list->last == 0) + { + list->first = list->last = node; + } + else + { + list->last->next = node; + list->last = list->last->next; + } +} + +MD_FUNCTION_IMPL void +MD_PushStringListToList(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_MemoryZero(to_push, sizeof(*to_push)); +} + +MD_FUNCTION_IMPL MD_String8List +MD_SplitString(MD_String8 string, int split_count, MD_String8 *splits) +{ + MD_String8List list = {0}; + + 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_PushStringToList(&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_PushStringToList(&list, split_string); + break; + } + } + + return list; +} + +MD_FUNCTION_IMPL MD_String8List +MD_SplitStringByString(MD_String8 string, MD_String8 split) +{ + return MD_SplitString(string, 1, &split); +} + +MD_FUNCTION_IMPL MD_String8List +MD_SplitStringByCharacter(MD_String8 string, MD_u8 character) +{ + return MD_SplitStringByString(string, MD_S8(&character, 1)); +} + +MD_FUNCTION_IMPL MD_String8 +MD_JoinStringList(MD_String8List list) +{ + MD_String8 string = {0}; + string.size = list.total_size; + string.str = _MD_PushArray(_MD_GetCtx(), MD_u8, string.size); + MD_u64 write_pos = 0; + for(MD_String8Node *node = list.first; node; node = node->next) + { + _MD_MemoryCopy(string.str + write_pos, node->string.str, node->string.size); + write_pos += node->string.size; + } + return string; +} + +MD_FUNCTION_IMPL MD_String8 +MD_JoinStringListWithSeparator(MD_String8List list, MD_String8 separator) +{ + if (list.node_count == 0) + { + return MD_S8Lit(""); + } + MD_String8 string = {0}; + string.size = list.total_size + (list.node_count - 1)*separator.size; + string.str = _MD_PushArray(_MD_GetCtx(), MD_u8, string.size); + MD_u64 write_pos = 0; + for(MD_String8Node *node = list.first; node; node = node->next) + { + _MD_MemoryCopy(string.str + write_pos, node->string.str, node->string.size); + write_pos += node->string.size; + if (node != list.last){ + _MD_MemoryCopy(string.str + write_pos, separator.str, separator.size); + write_pos += separator.size; + } + } + return string; +} + +MD_FUNCTION_IMPL MD_i64 +MD_I64FromString(MD_String8 string) +{ + char str[64]; + _MD_WriteStringToBuffer(string, sizeof(str), str); + return atoll(str); +} + +MD_FUNCTION_IMPL MD_f64 +MD_F64FromString(MD_String8 string) +{ + char str[64]; + _MD_WriteStringToBuffer(string, sizeof(str), str); + return atof(str); +} + +MD_FUNCTION_IMPL MD_u64 +MD_HashString(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; +} + +MD_FUNCTION_IMPL MD_u64 +MD_CalculateCStringLength(char *cstr) +{ + MD_u64 i = 0; + for(; cstr[i]; i += 1); + return i; +} + +MD_FUNCTION_IMPL MD_String8 +MD_StyledStringFromString(MD_String8 string, MD_WordStyle word_style, MD_String8 separator) +{ + MD_String8 result = {0}; + + MD_String8List words = {0}; + + 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 = {0}; + + 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_PushStringToList(&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_PushArray(_MD_GetCtx(), 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_WordStyle_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_WordStyle_LowerCamelCase: + { + result.str[write_pos] = node == words.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_WordStyle_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_WordStyle_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; +} + +MD_FUNCTION_IMPL MD_String8 +MD_StringFromNodeKind(MD_NodeKind kind) +{ + // NOTE(rjf): Must be kept in sync with MD_NodeKind enum. + static char *cstrs[MD_NodeKind_MAX] = + { + "Nil", + "File", + "Label", + "UnnamedSet", + "Tag", + }; + return MD_S8CString(cstrs[kind]); +} + +MD_FUNCTION_IMPL MD_String8List +MD_StringListFromNodeFlags(MD_NodeFlags flags) +{ + // NOTE(rjf): Must be kept in sync with MD_NodeFlags enum. + static char *flag_cstrs[] = + { + "ParenLeft", + "ParenRight", + "BracketLeft", + "BracketRight", + "BraceLeft", + "BraceRight", + + "BeforeSemicolon", + "BeforeComma", + + "AfterSemicolon", + "AfterComma", + + "Numeric", + "Identifier", + "StringLiteral", + "CharLiteral", + }; + + MD_String8List list = {0}; + 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_PushStringToList(&list, MD_S8CString(flag_cstrs[i])); + } + } + return list; +} + +//////////////////////////////// +// NOTE(allen): Unicode + +MD_GLOBAL MD_u8 dd_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_IMPL MD_UnicodeConsume +MD_CodepointFromUtf8(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_UnicodeConsume result = {~((MD_u32)0), 1}; + MD_u8 byte = str[0]; + MD_u8 byte_class = dd_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 (dd_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 (dd_utf8_class[cont_byte[0] >> 3] == 0 && + dd_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 (dd_utf8_class[cont_byte[0] >> 3] == 0 && + dd_utf8_class[cont_byte[1] >> 3] == 0 && + dd_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_IMPL MD_UnicodeConsume +MD_CodepointFromUtf16(MD_u16 *out, MD_u64 max) +{ + MD_UnicodeConsume 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_String16 in) +{ + MD_u64 cap = in.size*3; + MD_u8 *str = _MD_PushArray(_MD_GetCtx(), MD_u8, cap + 1); + MD_u16 *ptr = in.str; + MD_u16 *opl = ptr + in.size; + MD_u64 size = 0; + MD_UnicodeConsume consume; + for (;ptr < opl;) + { + consume = MD_CodepointFromUtf16(ptr, opl - ptr); + ptr += consume.advance; + size += MD_Utf8FromCodepoint(str + size, consume.codepoint); + } + str[size] = 0; + return(MD_S8(str, size)); +} + +MD_FUNCTION MD_String16 +MD_S16FromS8(MD_String8 in) +{ + MD_u64 cap = in.size*2; + MD_u16 *str = _MD_PushArray(_MD_GetCtx(), MD_u16, (cap + 1)); + MD_u8 *ptr = in.str; + MD_u8 *opl = ptr + in.size; + MD_u64 size = 0; + MD_UnicodeConsume consume; + for (;ptr < opl;) + { + consume = MD_CodepointFromUtf8(ptr, opl - ptr); + ptr += consume.advance; + size += MD_Utf16FromCodepoint(str + size, consume.codepoint); + } + str[size] = 0; + MD_String16 result = {str, size}; + return(result); +} + +MD_FUNCTION MD_String8 +MD_S8FromS32(MD_String32 in) +{ + MD_u64 cap = in.size*4; + MD_u8 *str = _MD_PushArray(_MD_GetCtx(), MD_u8, cap + 1); + MD_u32 *ptr = in.str; + MD_u32 *opl = ptr + in.size; + MD_u64 size = 0; + MD_UnicodeConsume consume; + for (;ptr < opl; ptr += 1) + { + size += MD_Utf8FromCodepoint(str + size, *ptr); + } + str[size] = 0; + return(MD_S8(str, size)); +} + +MD_FUNCTION MD_String32 +MD_S32FromS8(MD_String8 in) +{ + MD_u64 cap = in.size; + MD_u32 *str = _MD_PushArray(_MD_GetCtx(), MD_u32, (cap + 1)); + MD_u8 *ptr = in.str; + MD_u8 *opl = ptr + in.size; + MD_u64 size = 0; + MD_UnicodeConsume consume; + for (;ptr < opl;) + { + consume = MD_CodepointFromUtf8(ptr, opl - ptr); + ptr += consume.advance; + str[size] = consume.codepoint; + size += 1; + } + str[size] = 0; + MD_String32 result = {str, size}; + return(result); +} + + +MD_PRIVATE_FUNCTION_IMPL void +_MD_NodeTable_Initialize(MD_NodeTable *table) +{ + if(table->table_size == 0) + { + table->table_size = 4096; + table->table = _MD_PushArray(_MD_GetCtx(), MD_NodeTableSlot *, table->table_size); + } +} + +MD_FUNCTION_IMPL MD_NodeTableSlot * +MD_NodeTable_Lookup(MD_NodeTable *table, MD_String8 string) +{ + _MD_NodeTable_Initialize(table); + + MD_NodeTableSlot *slot = 0; + MD_u64 hash = MD_HashString(string); + MD_u64 index = hash % table->table_size; + for(MD_NodeTableSlot *candidate = table->table[index]; candidate; candidate = candidate->next) + { + if(candidate->hash == hash) + { + slot = candidate; + break; + } + } + return slot; +} + +MD_FUNCTION_IMPL MD_b32 +MD_NodeTable_Insert(MD_NodeTable *table, MD_NodeTableCollisionRule collision_rule, MD_String8 string, MD_Node *node) +{ + _MD_NodeTable_Initialize(table); + + MD_NodeTableSlot *slot = 0; + MD_u64 hash = MD_HashString(string); + MD_u64 index = hash % table->table_size; + + for(MD_NodeTableSlot *candidate = table->table[index]; candidate; candidate = candidate->next) + { + if(candidate->hash == hash) + { + slot = candidate; + break; + } + } + + if(slot == 0 || (slot != 0 && collision_rule == MD_NodeTableCollisionRule_Chain)) + { + slot = _MD_PushArray(_MD_GetCtx(), MD_NodeTableSlot, 1); + if(slot) + { + slot->next = table->table[index]; + table->table[index] = slot; + } + } + + if(slot) + { + slot->node = node; + slot->hash = hash; + } + + return !!slot; +} + +MD_FUNCTION_IMPL MD_Token +MD_ZeroToken(void) +{ + MD_Token token = {0}; + return token; +} + +MD_FUNCTION_IMPL MD_b32 +MD_TokenKindIsWhitespace(MD_TokenKind kind) +{ + return kind > MD_TokenKind_WhitespaceMin && kind < MD_TokenKind_WhitespaceMax; +} + +MD_FUNCTION_IMPL MD_b32 +MD_TokenKindIsComment(MD_TokenKind kind) +{ + return(kind == MD_TokenKind_Comment); +} + +MD_FUNCTION_IMPL MD_b32 +MD_TokenKindIsRegular(MD_TokenKind kind) +{ + return(kind > MD_TokenKind_RegularMin && kind < MD_TokenKind_RegularMax); +} + +MD_FUNCTION_IMPL MD_ParseCtx +MD_Parse_InitializeCtx(MD_String8 filename, MD_String8 contents) +{ + MD_ParseCtx ctx = {0}; + ctx.first_root = ctx.last_root = MD_NilNode(); + ctx.at = contents.str; + ctx.file_contents = contents; + ctx.filename = filename; + return ctx; +} + +MD_PRIVATE_FUNCTION_IMPL void +_MD_PushNodeToList(MD_Node **firstp, MD_Node **lastp, MD_Node *parent, MD_Node *node) +{ + if(!MD_NodeIsNil(node)) + { + MD_Node *first = *firstp; + MD_Node *last = *lastp; + if(MD_NodeIsNil(last)) + { + first = last = node; + node->next = node->prev = MD_NilNode(); + } + else + { + last->next = node; + node->next = MD_NilNode(); + node->prev = last; + last = last->next; + } + *firstp = first; + *lastp = last; + node->parent = parent; + } +} + +MD_FUNCTION_IMPL void +MD_Parse_Bump(MD_ParseCtx *ctx, MD_Token token) +{ + ctx->at = token.outer_string.str + token.outer_string.size; +} + +MD_FUNCTION_IMPL void +MD_Parse_BumpNext(MD_ParseCtx *ctx) +{ + MD_Parse_Bump(ctx, MD_Parse_LexNext(ctx)); +} + +MD_FUNCTION_IMPL MD_Token +MD_Parse_LexNext(MD_ParseCtx *ctx) +{ + MD_Token token = {0}; + + MD_u8 *one_past_last = ctx->file_contents.str + ctx->file_contents.size; + MD_u8 *first = ctx->at; + if (first < one_past_last) + { + MD_u8 *at = first; + MD_u32 skip_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] == '/') + { + at += 2; + token.kind = MD_TokenKind_Comment; + MD_TokenizerScan(*at != '\n'); + } + else if (at[1] == '*') + { + at += 2; + token.kind = MD_TokenKind_Comment; + 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 (token.kind == MD_TokenKind_Nil) goto symbol_lex; + }break; + + // NOTE(rjf): "Bundle-of-tokens" strings (`stuff` or ```stuff```) + // In practice no different than a regular string, but provides an + // alternate syntax which will allow tools like 4coder to treat the + // contents as regular tokens. + case '`': + { + token.kind = MD_TokenKind_StringLiteral; + if (at + 2 < one_past_last && at[1] == '`' && at[2] == '`') + { + skip_chop_n = 3; + at += 3; + MD_TokenizerScan(!(at + 2 < one_past_last && at[0] == '`' && at[1] == '`' && at[2] == '`')); + at += 3; + } + else + { + // TODO(allen): escape sequences? + skip_chop_n = 1; + at += 1; + MD_TokenizerScan(*at != '\n' && *at != '`'); + if (*at == '`') at += 1; + } + }break; + + // NOTE(allen): Strings + case '"': + { + token.kind = MD_TokenKind_StringLiteral; + if (at + 2 < one_past_last && at[1] == '"' && at[2] == '"') + { + skip_chop_n = 3; + at += 3; + MD_TokenizerScan(!(at + 2 < one_past_last && at[0] == '"' && at[1] == '"' && at[2] == '"')); + at += 3; + } + else + { + // TODO(allen): escape sequences? + skip_chop_n = 1; + at += 1; + MD_TokenizerScan(*at != '\n' && *at != '"'); + if (*at == '"') at += 1; + } + }break; + + case '\'': + { + if (at + 2 < one_past_last && at[1] == '\'' && at[2] == '\'') + { + token.kind = MD_TokenKind_StringLiteral; + skip_chop_n = 3; + at += 3; + MD_TokenizerScan(!(at + 2 < one_past_last && at[0] == '\'' && at[1] == '\'' && at[2] == '\'')); + at += 3; + } + else + { + token.kind = MD_TokenKind_CharLiteral; + // TODO(allen): escape sequences? + skip_chop_n = 1; + at += 1; + MD_TokenizerScan(*at != '\n' && *at != '\''); + if (*at == '\'') at += 1; + } + }break; + + // NOTE(allen): Identifiers, Numbers, Operators + default: + { + if (MD_CharIsAlpha(*at)) + { + token.kind = MD_TokenKind_Identifier; + at += 1; + MD_TokenizerScan(MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '_'); + } + + else if (MD_CharIsDigit(*at) || + (at + 1 < one_past_last && at[0] == '-' && MD_CharIsDigit(at[1]))) + { + token.kind = MD_TokenKind_NumericLiteral; + at += 1; + MD_TokenizerScan(MD_CharIsAlpha(*at) || MD_CharIsDigit(*at) || *at == '.'); + } + + else if (MD_CharIsSymbol(*at)) + { + symbol_lex: + token.kind = MD_TokenKind_Symbol; + at += 1; + } + }break; + } + + token.outer_string = MD_S8Range(first, at); + token.string = MD_StringSubstring(token.outer_string, skip_chop_n, token.outer_string.size - skip_chop_n); + + ctx->at = at; + } + + return token; +} + +MD_FUNCTION_IMPL MD_Token +MD_Parse_PeekSkipSome(MD_ParseCtx *ctx, MD_TokenGroups skip_groups) +{ + MD_ParseCtx ctx_restore = *ctx; + + MD_b32 skip_comment = (skip_groups & MD_TokenGroup_Comment); + MD_b32 skip_whitespace = (skip_groups & MD_TokenGroup_Whitespace); + MD_b32 skip_regular = (skip_groups & MD_TokenGroup_Regular); + + MD_Token result = {0}; + + loop: + { + result = MD_Parse_LexNext(ctx); + if ((skip_comment && MD_TokenKindIsComment(result.kind)) || + (skip_whitespace && MD_TokenKindIsWhitespace(result.kind)) || + (skip_regular && MD_TokenKindIsRegular(result.kind))){ + MD_Parse_Bump(ctx, result); + goto loop; + } + } + + { + // TODO(allen): I'm not a fan of what this implies. + *ctx = ctx_restore; + } + + return result; +} + +MD_FUNCTION_IMPL MD_b32 +MD_Parse_TokenMatch(MD_Token token, MD_String8 string, MD_StringMatchFlags flags) +{ + return MD_StringMatch(token.string, string, flags); +} + +MD_FUNCTION_IMPL MD_b32 +MD_Parse_Require(MD_ParseCtx *ctx, MD_String8 string) +{ + int result = 0; + + MD_Token token_any = MD_Parse_PeekSkipSome(ctx, 0); + if(MD_StringMatch(token_any.string, string, 0)) + { + result = 1; + MD_Parse_Bump(ctx, token_any); + goto end; + } + + MD_Token token_regular = MD_Parse_PeekSkipSome(ctx, MD_TokenGroup_Comment|MD_TokenGroup_Whitespace); + if(MD_StringMatch(token_regular.string, string, 0)) + { + result = 1; + MD_Parse_Bump(ctx, token_regular); + goto end; + } + + end:; + return result; +} + +MD_FUNCTION_IMPL MD_b32 +MD_Parse_RequireKind(MD_ParseCtx *ctx, MD_TokenKind kind, MD_Token *out_token) +{ + int result = 0; + + MD_TokenGroups skip_groups = MD_TokenGroup_Comment|MD_TokenGroup_Whitespace; + if (MD_TokenKindIsWhitespace(kind)) + { + skip_groups &= ~MD_TokenGroup_Whitespace; + } + if (MD_TokenKindIsComment(kind)) + { + skip_groups &= ~MD_TokenGroup_Comment; + } + + MD_Token token = MD_Parse_PeekSkipSome(ctx, skip_groups); + if(token.kind == kind) + { + result = 1; + MD_Parse_Bump(ctx, token); + if(out_token) + { + *out_token = token; + } + } + return result; +} + +MD_PRIVATE_FUNCTION_IMPL void +_MD_Error(MD_ParseCtx *ctx, char *fmt, ...) +{ + MD_Error *error = _MD_PushArray(_MD_GetCtx(), MD_Error, 1); + error->filename = ctx->filename; + va_list args; + va_start(args, fmt); + error->string = MD_PushStringFV(fmt, args); + va_end(args); +} + +MD_PRIVATE_FUNCTION_IMPL MD_Node * +_MD_MakeNode(MD_NodeKind kind, MD_String8 string, MD_String8 whole_string, MD_String8 filename, + MD_u8 *file_contents, MD_u8 *at) +{ + MD_Node *node = _MD_PushArray(_MD_GetCtx(), MD_Node, 1); + if(node) + { + node->kind = kind; + node->string = string; + node->whole_string = whole_string; + node->next = node->prev = node->parent = node->first_child = node->last_child = node->first_tag = node->last_tag = MD_NilNode(); + node->filename = filename; + node->file_contents = file_contents; + node->at = at; + } + else + { + node = MD_NilNode(); + } + return node; +} + +MD_PRIVATE_FUNCTION_IMPL MD_Node * +_MD_MakeNodeFromToken_Ctx(MD_ParseCtx *ctx, MD_NodeKind kind, MD_Token token) +{ + return _MD_MakeNode(kind, token.string, token.outer_string, ctx->filename, ctx->file_contents.str, ctx->at); +} + +MD_PRIVATE_FUNCTION_IMPL MD_Node * +_MD_MakeNodeFromString_Ctx(MD_ParseCtx *ctx, MD_NodeKind kind, MD_String8 string) +{ + return _MD_MakeNode(kind, string, string, ctx->filename, ctx->file_contents.str, ctx->at); +} + +typedef MD_u32 _MD_ParseSetFlags; +enum +{ + _MD_ParseSetFlag_Paren = (1<<0), + _MD_ParseSetFlag_Brace = (1<<1), + _MD_ParseSetFlag_Bracket = (1<<2), + _MD_ParseSetFlag_Implicit = (1<<3), +}; + +MD_PRIVATE_FUNCTION_IMPL MD_ParseResult _MD_ParseOneNode(MD_ParseCtx *ctx); +MD_PRIVATE_FUNCTION_IMPL void _MD_ParseSet(MD_ParseCtx *ctx, MD_Node *parent, _MD_ParseSetFlags flags, MD_Node **first_out, MD_Node **last_out); +MD_PRIVATE_FUNCTION_IMPL void _MD_ParseTagList(MD_ParseCtx *ctx, MD_Node **first_out, MD_Node **last_out); + +MD_PRIVATE_FUNCTION_IMPL void +_MD_SetNodeFlagsByToken(MD_Node *node, MD_Token token) +{ +#define Flag(_kind, _flag) if(token.kind == _kind) { node->flags |= _flag; } + Flag(MD_TokenKind_Identifier, MD_NodeFlag_Identifier); + Flag(MD_TokenKind_NumericLiteral, MD_NodeFlag_Numeric); + Flag(MD_TokenKind_StringLiteral, MD_NodeFlag_StringLiteral); + Flag(MD_TokenKind_CharLiteral, MD_NodeFlag_CharLiteral); +#undef Flag +} + +MD_PRIVATE_FUNCTION_IMPL MD_ParseResult +_MD_ParseOneNode(MD_ParseCtx *ctx) +{ + MD_u8 *at_first = ctx->at; + + MD_ParseResult result = {0}; + result.node = MD_NilNode(); + + MD_Token token = {0}; + + MD_Node *first_tag = 0; + MD_Node *last_tag = 0; + _MD_ParseTagList(ctx, &first_tag, &last_tag); + + // NOTE(rjf): Unnamed Sets + MD_TokenGroups skip_groups = MD_TokenGroup_Whitespace|MD_TokenGroup_Comment; + if(MD_Parse_TokenMatch(MD_Parse_PeekSkipSome(ctx, skip_groups), MD_S8Lit("("), 0) || + MD_Parse_TokenMatch(MD_Parse_PeekSkipSome(ctx, skip_groups), MD_S8Lit("{"), 0) || + MD_Parse_TokenMatch(MD_Parse_PeekSkipSome(ctx, skip_groups), MD_S8Lit("["), 0)) + { + result.node = _MD_MakeNodeFromString_Ctx(ctx, MD_NodeKind_UnnamedSet, MD_S8Lit("")); + _MD_ParseSet(ctx, result.node, + _MD_ParseSetFlag_Paren | + _MD_ParseSetFlag_Brace | + _MD_ParseSetFlag_Bracket, + &result.node->first_child, + &result.node->last_child); + goto end_parse; + } + + // NOTE(rjf): Labels + else if(MD_Parse_RequireKind(ctx, MD_TokenKind_Identifier, &token) || + MD_Parse_RequireKind(ctx, MD_TokenKind_NumericLiteral, &token) || + MD_Parse_RequireKind(ctx, MD_TokenKind_StringLiteral, &token) || + MD_Parse_RequireKind(ctx, MD_TokenKind_CharLiteral, &token) || + MD_Parse_RequireKind(ctx, MD_TokenKind_Symbol, &token)) + { + result.node = MD_MakeNodeFromToken(MD_NodeKind_Label, ctx->filename, ctx->file_contents.str, ctx->at, token); + _MD_SetNodeFlagsByToken(result.node, token); + // NOTE(rjf): Children + if(MD_Parse_Require(ctx, MD_S8Lit(":"))) + { + _MD_ParseSet(ctx, result.node, + _MD_ParseSetFlag_Paren | + _MD_ParseSetFlag_Brace | + _MD_ParseSetFlag_Bracket | + _MD_ParseSetFlag_Implicit, + &result.node->first_child, + &result.node->last_child); + } + goto end_parse; + } + + end_parse:; + if(!MD_NodeIsNil(result.node)) + { + result.bytes_parsed = (MD_u64)(ctx->at - at_first); + 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; + } + } + return result; +} + +MD_PRIVATE_FUNCTION_IMPL void +_MD_ParseSet(MD_ParseCtx *ctx, MD_Node *parent, _MD_ParseSetFlags flags, + MD_Node **first_out, MD_Node **last_out) +{ + MD_Node *first = MD_NilNode(); + MD_Node *last = MD_NilNode(); + + MD_b32 brace = 0; + MD_b32 paren = 0; + MD_b32 bracket = 0; + MD_b32 terminate_with_separator = !!(flags & _MD_ParseSetFlag_Implicit); + + if(flags & _MD_ParseSetFlag_Brace && MD_Parse_Require(ctx, MD_S8Lit("{"))) + { + parent->flags |= MD_NodeFlag_BraceLeft; + brace = 1; + terminate_with_separator = 0; + } + else if(flags & _MD_ParseSetFlag_Paren && MD_Parse_Require(ctx, MD_S8Lit("("))) + { + parent->flags |= MD_NodeFlag_ParenLeft; + paren = 1; + terminate_with_separator = 0; + } + else if(flags & _MD_ParseSetFlag_Bracket && MD_Parse_Require(ctx, MD_S8Lit("["))) + { + parent->flags |= MD_NodeFlag_BracketLeft; + bracket = 1; + terminate_with_separator = 0; + } + + // NOTE(rjf): Parse children. + if(brace || paren || bracket || terminate_with_separator) + { + MD_NodeFlags next_child_flags = 0; + for(MD_u64 child_idx = 0;; child_idx += 1) + { + if(brace) + { + if(MD_Parse_Require(ctx, MD_S8Lit("}"))) + { + parent->flags |= MD_NodeFlag_BraceRight; + goto end_parse; + } + } + else if(paren || bracket) + { + if(flags & _MD_ParseSetFlag_Paren && MD_Parse_Require(ctx, MD_S8Lit(")"))) + { + parent->flags |= MD_NodeFlag_ParenRight; + goto end_parse; + } + else if(flags & _MD_ParseSetFlag_Bracket && MD_Parse_Require(ctx, MD_S8Lit("]"))) + { + parent->flags |= MD_NodeFlag_BracketRight; + goto end_parse; + } + } + else + { + MD_Token peek = MD_Parse_PeekSkipSome(ctx, MD_TokenGroup_Whitespace | MD_TokenGroup_Comment); + if(MD_Parse_TokenMatch(peek, MD_S8Lit("}"), 0) || + MD_Parse_TokenMatch(peek, MD_S8Lit(")"), 0) || + MD_Parse_TokenMatch(peek, MD_S8Lit("]"), 0)) + { + goto end_parse; + } + } + + MD_ParseResult parse = _MD_ParseOneNode(ctx); + MD_Node *child = parse.node; + child->flags |= next_child_flags; + next_child_flags = 0; + if(MD_NodeIsNil(child)) + { + goto end_parse; + } + else + { + _MD_PushNodeToList(&first, &last, parent, child); + } + + // NOTE(rjf): Separators. + { + MD_b32 result = 0; + if(MD_Parse_Require(ctx, MD_S8Lit(","))) + { + result |= 1; + child->flags |= MD_NodeFlag_BeforeComma; + next_child_flags |= MD_NodeFlag_AfterComma; + } + else if(MD_Parse_Require(ctx, MD_S8Lit(";"))) + { + result |= 1; + child->flags |= MD_NodeFlag_BeforeSemicolon; + next_child_flags |= MD_NodeFlag_AfterSemicolon; + } + result |= !!MD_Parse_Require(ctx, MD_S8Lit("\n")); + if(result && terminate_with_separator) + { + goto end_parse; + } + } + } + } + + end_parse:; + *first_out = first; + *last_out = last; +} + +MD_PRIVATE_FUNCTION_IMPL void +_MD_ParseTagList(MD_ParseCtx *ctx, MD_Node **first_out, MD_Node **last_out) +{ + MD_Node *first = MD_NilNode(); + MD_Node *last = MD_NilNode(); + + for(;MD_Parse_Require(ctx, MD_S8Lit("@"));) + { + MD_Token name = {0}; + if(MD_Parse_RequireKind(ctx, MD_TokenKind_Identifier, &name)) + { + MD_Node *tag = _MD_MakeNodeFromToken_Ctx(ctx, MD_NodeKind_Tag, name); + _MD_ParseSet(ctx, tag, _MD_ParseSetFlag_Paren, &tag->first_child, &tag->last_child); + _MD_PushNodeToList(&first, &last, MD_NilNode(), tag); + } + else + { + break; + } + } + + *first_out = first; + *last_out = last; +} + +MD_FUNCTION_IMPL MD_ParseResult +MD_ParseOneNode(MD_String8 filename, MD_String8 contents) +{ + MD_ParseCtx ctx = MD_Parse_InitializeCtx(filename, contents); + return _MD_ParseOneNode(&ctx); +} + +MD_FUNCTION MD_Node * +MD_ParseWholeString(MD_String8 filename, MD_String8 contents) +{ + MD_Node *root = MD_MakeNodeFromString(MD_NodeKind_File, filename, contents.str, contents.str, MD_PushStringF("`DD Parsed From \"%.*s\"`", MD_StringExpand(filename))); + if(contents.size > 0) + { + MD_ParseCtx ctx = MD_Parse_InitializeCtx(filename, contents); + for(MD_u64 i = 0; i < contents.size;) + { + MD_ParseResult parse = _MD_ParseOneNode(&ctx); + _MD_PushNodeToList(&root->first_child, &root->last_child, root, parse.node); + i += parse.bytes_parsed; + if(parse.bytes_parsed == 0) + { + break; + } + } + } + return root; +} + +MD_FUNCTION_IMPL MD_Node * +MD_ParseWholeFile(MD_String8 filename) +{ + return MD_ParseWholeString(filename, MD_LoadEntireFile(filename)); +} + +MD_FUNCTION_IMPL MD_b32 +MD_NodeIsNil(MD_Node *node) +{ + return node == 0 || node == &_md_nil_node || node->kind == MD_NodeKind_Nil; +} + +MD_FUNCTION_IMPL MD_Node * +MD_NilNode(void) { return &_md_nil_node; } + +MD_FUNCTION_IMPL MD_Node * +MD_MakeNodeFromToken(MD_NodeKind kind, MD_String8 filename, MD_u8 *file, MD_u8 *at, MD_Token token) +{ + return _MD_MakeNode(kind, token.string, token.outer_string, filename, file, at); +} + +MD_FUNCTION_IMPL MD_Node * +MD_MakeNodeFromString(MD_NodeKind kind, MD_String8 filename, MD_u8 *file, MD_u8 *at, MD_String8 string) +{ + return _MD_MakeNode(kind, string, string, filename, file, at); +} + +MD_FUNCTION_IMPL void +MD_PushSibling(MD_Node **first, MD_Node **last, MD_Node *parent, MD_Node *new_sibling) +{ + _MD_PushNodeToList(first, last, parent, new_sibling); +} + +MD_FUNCTION_IMPL void +MD_PushChild(MD_Node *parent, MD_Node *new_child) +{ + _MD_PushNodeToList(&parent->first_child, &parent->last_child, parent, new_child); +} + +MD_FUNCTION_IMPL void +MD_PushTag(MD_Node *node, MD_Node *tag) +{ + _MD_PushNodeToList(&node->first_tag, &node->last_tag, node, tag); +} + +MD_FUNCTION_IMPL MD_b32 +MD_NodeMatch(MD_Node *a, MD_Node *b, MD_StringMatchFlags str_flags, MD_NodeMatchFlags node_flags) +{ + MD_b32 result = 0; + if(a->kind == b->kind && MD_StringMatch(a->string, b->string, str_flags)) + { + result = 1; + if(a->kind != MD_NodeKind_Tag && node_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, str_flags, 0)) + { + if(node_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, str_flags, node_flags)) + { + result = 0; + goto end; + } + } + } + } + else + { + result = 0; + goto end; + } + } + } + } + end:; + return result; +} + +MD_FUNCTION_IMPL MD_b32 +MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_StringMatchFlags str_flags, MD_NodeMatchFlags node_flags) +{ + MD_b32 result = MD_NodeMatch(a, b, str_flags, node_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, str_flags, node_flags)) + { + result = 0; + goto end; + } + } + } + end:; + return result; +} + +MD_FUNCTION_IMPL MD_Node * +MD_NodeFromString(MD_Node *first, MD_Node *last, MD_String8 string) +{ + MD_Node *result = MD_NilNode(); + for(MD_Node *node = first; !MD_NodeIsNil(node); node = node->next) + { + if(MD_StringMatch(string, node->string, 0)) + { + result = node; + break; + } + } + return result; +} + +MD_FUNCTION_IMPL MD_Node * +MD_NodeFromIndex(MD_Node *first, MD_Node *last, 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_IMPL int +MD_IndexFromNode(MD_Node *node) +{ + int idx = 0; + if(node && !MD_NodeIsNil(node)) + { + for(MD_Node *last = node->prev; last; last = last->prev, idx += 1); + } + return idx; +} + +MD_FUNCTION_IMPL MD_Node * +MD_NextNodeSibling(MD_Node *last, MD_String8 string) +{ + MD_Node *result = MD_NilNode(); + if(last) + { + for(MD_Node *node = last->next; node; node = node->next) + { + if(MD_StringMatch(string, node->string, 0)) + { + result = node; + break; + } + } + } + return result; +} + +MD_FUNCTION_IMPL MD_Node * +MD_ChildFromString(MD_Node *node, MD_String8 child_string) +{ + return MD_NodeFromString(node->first_child, node->last_child, child_string); +} + +MD_FUNCTION_IMPL MD_Node * +MD_TagFromString(MD_Node *node, MD_String8 tag_string) +{ + return MD_NodeFromString(node->first_tag, node->last_tag, tag_string); +} + +MD_FUNCTION_IMPL MD_Node * +MD_ChildFromIndex(MD_Node *node, int n) +{ + return MD_NodeFromIndex(node->first_child, node->last_child, n); +} + +MD_FUNCTION_IMPL MD_Node * +MD_TagFromIndex(MD_Node *node, int n) +{ + return MD_NodeFromIndex(node->first_child, node->last_child, n); +} + +MD_FUNCTION_IMPL MD_Node * +MD_TagArgFromIndex(MD_Node *node, MD_String8 tag_string, int n) +{ + MD_Node *tag = MD_TagFromString(node, tag_string); + return MD_ChildFromIndex(tag, n); +} + +MD_FUNCTION_IMPL MD_b32 +MD_NodeHasTag(MD_Node *node, MD_String8 tag_string) +{ + return !MD_NodeIsNil(MD_TagFromString(node, tag_string)); +} + +MD_FUNCTION_IMPL MD_CodeLoc +MD_CodeLocFromNode(MD_Node *node) +{ + MD_CodeLoc loc; + loc.filename = node->filename; + loc.line = 1; + loc.column = 1; + for(MD_u64 i = 0; node->file_contents[i]; i += 1) + { + if(node->file_contents[i] == '\n') + { + loc.line += 1; + loc.column = 1; + } + else + { + loc.column += 1; + } + if(node->file_contents + i == node->at) + { + break; + } + } + return loc; +} + +MD_FUNCTION_IMPL MD_i64 +MD_ChildCountFromNode(MD_Node *node) +{ + MD_i64 result = 0; + for(MD_EachNode(child, node->first_child)) + { + result += 1; + } + return result; +} + +MD_FUNCTION_IMPL MD_i64 +MD_TagCountFromNode(MD_Node *node) +{ + MD_i64 result = 0; + for(MD_EachNode(tag, node->first_tag)) + { + result += 1; + } + return result; +} + +MD_FUNCTION_IMPL MD_i64 +MD_ChildCountFromNodeAndString(MD_Node *node, MD_String8 string, MD_StringMatchFlags flags) +{ + MD_i64 result = 0; + for(MD_EachNode(child, node->first_child)) + { + if(MD_StringMatch(child->string, string, flags)) + { + result += 1; + } + } + return result; +} + +MD_FUNCTION_IMPL MD_i64 +MD_TagCountFromNodeAndString(MD_Node *node, MD_String8 string, MD_StringMatchFlags flags) +{ + MD_i64 result = 0; + for(MD_EachNode(tag, node->first_tag)) + { + if(MD_StringMatch(tag->string, string, flags)) + { + result += 1; + } + } + return result; +} + +MD_FUNCTION_IMPL void +MD_NodeMessage(MD_Node *node, MD_MessageKind kind, MD_String8 str) +{ + char *kind_name = kind == MD_MessageKind_Error ? "error" : "warning"; + MD_CodeLoc loc = MD_CodeLocFromNode(node); + fprintf(stderr, "%.*s(%i:%i): %s: %.*s\n", + MD_StringExpand(loc.filename), + loc.line, loc.column, + kind_name, + MD_StringExpand(str)); +} + +MD_FUNCTION_IMPL void +MD_NodeError(MD_Node *node, MD_String8 str) +{ + MD_NodeMessage(node, MD_MessageKind_Error, str); +} + +MD_FUNCTION_IMPL void +MD_NodeWarning(MD_Node *node, MD_String8 str) +{ + MD_NodeMessage(node, MD_MessageKind_Warning, str); +} + +MD_FUNCTION_IMPL void +MD_NodeMessageF(MD_Node *node, MD_MessageKind kind, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + MD_NodeMessage(node, kind ,MD_PushStringFV(fmt, args)); + va_end(args); +} + +MD_FUNCTION_IMPL void +MD_NodeErrorF(MD_Node *node, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + MD_NodeError(node, MD_PushStringFV(fmt, args)); + va_end(args); +} + +MD_FUNCTION_IMPL void +MD_NodeWarningF(MD_Node *node, char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + MD_NodeWarning(node, MD_PushStringFV(fmt, args)); + va_end(args); +} + +MD_GLOBAL MD_Expr _md_nil_expr = +{ + &_md_nil_node, + MD_ExprKind_Nil, + &_md_nil_expr, + {&_md_nil_expr, &_md_nil_expr }, +}; + +MD_FUNCTION_IMPL MD_Expr * +MD_NilExpr(void) +{ + return &_md_nil_expr; +} + +MD_FUNCTION_IMPL MD_b32 +MD_ExprIsNil(MD_Expr *expr) +{ + return expr == 0 || expr == &_md_nil_expr || expr->kind == MD_ExprKind_Nil; +} + +MD_FUNCTION_IMPL MD_ExprKind +MD_PreUnaryExprKindFromNode(MD_Node *node) +{ + MD_ExprKind kind = MD_ExprKind_Nil; +#define MD_ExprStr(str, _kind) if(MD_StringMatch(node->string, MD_S8Lit(str), 0)) {kind = _kind; goto end;} + if(!MD_NodeIsNil(node->first_child)) + { + if(node->flags & MD_NodeFlag_ParenLeft && + node->flags & MD_NodeFlag_ParenRight) + { + kind = MD_ExprKind_Call; + } + else if(node->flags & MD_NodeFlag_BracketLeft && + node->flags & MD_NodeFlag_BracketRight) + { + kind = MD_ExprKind_Subscript; + } + } + else + { + MD_ExprStr("-", MD_ExprKind_Negative); + MD_ExprStr("~", MD_ExprKind_BitNot); + MD_ExprStr("!", MD_ExprKind_BoolNot); + MD_ExprStr("*", MD_ExprKind_Dereference); + MD_ExprStr("&", MD_ExprKind_Reference); + } + end:; +#undef MD_ExprStr + return kind; +} + +MD_FUNCTION_IMPL MD_ExprKind +MD_PostUnaryExprKindFromNode(MD_Node *node) +{ + MD_ExprKind kind = MD_ExprKind_Nil; + if(!MD_NodeIsNil(node->first_child)) + { + if(node->flags & MD_NodeFlag_ParenLeft && + node->flags & MD_NodeFlag_ParenRight) + { + kind = MD_ExprKind_Call; + } + else if(node->flags & MD_NodeFlag_BracketLeft && + node->flags & MD_NodeFlag_BracketRight) + { + kind = MD_ExprKind_Subscript; + } + } + return kind; +} + +MD_FUNCTION_IMPL MD_ExprKind +MD_BinaryExprKindFromNode(MD_Node *node) +{ + MD_ExprKind kind = MD_ExprKind_Nil; +#define MD_ExprStr(str, _kind) if(MD_StringMatch(node->string, MD_S8Lit(str), 0)) {kind = _kind; goto end;} + if(node->kind == MD_NodeKind_Label && MD_NodeIsNil(node->first_child)) + { + MD_ExprStr(".", MD_ExprKind_Dot); + MD_ExprStr("->", MD_ExprKind_Arrow); + MD_ExprStr("+", MD_ExprKind_Add); + MD_ExprStr("-", MD_ExprKind_Subtract); + MD_ExprStr("*", MD_ExprKind_Multiply); + MD_ExprStr("/", MD_ExprKind_Divide); + MD_ExprStr("%", MD_ExprKind_Mod); + MD_ExprStr("==", MD_ExprKind_IsEqual); + MD_ExprStr("!=", MD_ExprKind_IsNotEqual); + MD_ExprStr("<", MD_ExprKind_LessThan); + MD_ExprStr(">", MD_ExprKind_GreaterThan); + MD_ExprStr("<=", MD_ExprKind_LessThanEqualTo); + MD_ExprStr(">=", MD_ExprKind_GreaterThanEqualTo); + MD_ExprStr("&&", MD_ExprKind_BoolAnd); + MD_ExprStr("||", MD_ExprKind_BoolOr); + MD_ExprStr("!", MD_ExprKind_BoolNot); + MD_ExprStr("&", MD_ExprKind_BitAnd); + MD_ExprStr("|", MD_ExprKind_BitOr); + MD_ExprStr("^", MD_ExprKind_BitXor); + MD_ExprStr("<<", MD_ExprKind_LeftShift); + MD_ExprStr(">>", MD_ExprKind_RightShift); + } + end:; +#undef MD_ExprStr + return kind; +} + +MD_FUNCTION_IMPL MD_ExprPrec +MD_ExprPrecFromExprKind(MD_ExprKind kind) +{ + // 0: Invalid + // 12: (unary) - ~ ! + // 11: . -> () [] + // 10: * / % + // 9: + - + // 8: << >> + // 7: < <= > >= + // 6: == != + // 5: (bitwise) & + // 4: ^ + // 3: | + // 2: && + // 1: || + MD_ExprPrec kind_to_prec[] = + { + +0, // MD_ExprKind_Nil + +0, // MD_ExprKind_Atom + + +11, // MD_ExprKind_Dot + +11, // MD_ExprKind_Arrow + +11, // MD_ExprKind_Call + +11, // MD_ExprKind_Subscript + +12, // MD_ExprKind_Dereference + +12, // MD_ExprKind_Reference + + +9, // MD_ExprKind_Add + +9, // MD_ExprKind_Subtract + +10, // MD_ExprKind_Multiply + +10, // MD_ExprKind_Divide + +10, // MD_ExprKind_Mod + + +6, // MD_ExprKind_IsEqual + +6, // MD_ExprKind_IsNotEqual + +7, // MD_ExprKind_LessThan + +7, // MD_ExprKind_GreaterThan + +7, // MD_ExprKind_LessThanEqualTo + +7, // MD_ExprKind_GreaterThanEqualTo + + +2, // MD_ExprKind_BoolAnd + +1, // MD_ExprKind_BoolOr + +12, // MD_ExprKind_BoolNot + +5, // MD_ExprKind_BitAnd + +3, // MD_ExprKind_BitOr + +12, // MD_ExprKind_BitNot + +4, // MD_ExprKind_BitXor + +8, // MD_ExprKind_LeftShift + +8, // MD_ExprKind_RightShift + + +12, // MD_ExprKind_Negative + +0, // MD_ExprKind_Pointer + +0, // MD_ExprKind_Array + }; + return kind_to_prec[kind]; +} + +MD_FUNCTION_IMPL MD_Expr * +MD_MakeExpr(MD_Node *node, MD_ExprKind kind, MD_Expr *left, MD_Expr *right) +{ + MD_Expr *expr = _MD_PushArray(_MD_GetCtx(), MD_Expr, 1); + if(expr) + { + if(left == 0) left = MD_NilExpr(); + if(right == 0) right = MD_NilExpr(); + expr->node = node; + expr->kind = kind; + expr->sub[0] = left; + expr->sub[1] = right; + } + else + { + expr = MD_NilExpr(); + } + return expr; +} + +typedef struct _MD_NodeParseCtx _MD_NodeParseCtx; +struct _MD_NodeParseCtx +{ + MD_Node *at; + MD_Node *last; + MD_Node *one_past_last; +}; + +MD_PRIVATE_FUNCTION_IMPL MD_b32 +_MD_NodeParse_ConsumeAtom(_MD_NodeParseCtx *ctx, MD_Node **out) +{ + MD_b32 result = 0; + if(ctx->at->kind == MD_NodeKind_Label && + MD_NodeIsNil(ctx->at->first_child)) + { + result = 1; + if(out) + { + *out = ctx->at; + } + ctx->at = ctx->at->next; + } + return result; +} + +MD_PRIVATE_FUNCTION_IMPL MD_b32 +_MD_NodeParse_ConsumeSet(_MD_NodeParseCtx *ctx, MD_Node **out) +{ + MD_b32 result = 0; + if(!MD_NodeIsNil(ctx->at->first_child)) + { + if(out) + { + *out = ctx->at; + } + ctx->at = ctx->at->next; + result = 1; + } + return result; +} + +MD_PRIVATE_FUNCTION_IMPL MD_b32 +_MD_NodeParse_ConsumeLiteral(_MD_NodeParseCtx *ctx, MD_Node **out) +{ + MD_b32 result = 0; + if(ctx->at->flags & MD_NodeFlag_StringLiteral || + ctx->at->flags & MD_NodeFlag_CharLiteral || + ctx->at->flags & MD_NodeFlag_Numeric) + { + result = 1; + if(out) + { + *out = ctx->at; + } + ctx->at = ctx->at->next; + } + return result; +} + +MD_PRIVATE_FUNCTION_IMPL MD_b32 +_MD_NodeParse_Consume(_MD_NodeParseCtx *ctx, MD_String8 string, MD_Node **out) +{ + MD_b32 result = 0; + if(MD_StringMatch(ctx->at->string, string, 0)) + { + result = 1; + if(out) + { + *out = ctx->at; + } + ctx->at = ctx->at->next; + } + return result; +} + +MD_PRIVATE_FUNCTION_IMPL void +_MD_NodeParse_Next(_MD_NodeParseCtx *ctx) +{ + ctx->at = ctx->at->next; +} + +MD_PRIVATE_FUNCTION_IMPL MD_Expr * +_MD_ParseExpr_(_MD_NodeParseCtx *ctx, int precedence_in); + +MD_PRIVATE_FUNCTION_IMPL MD_Expr * +_MD_ParseExpr(_MD_NodeParseCtx *ctx); + +MD_PRIVATE_FUNCTION_IMPL MD_Expr * +_MD_ParseUnaryExpr(_MD_NodeParseCtx *ctx) +{ + MD_Expr *result = MD_NilExpr(); + MD_Node *set = 0; + MD_Node *node = 0; + + // NOTE(rjf): Sub-expression + if(_MD_NodeParse_ConsumeSet(ctx, &set)) + { + result = MD_ParseAsExpr(set->first_child, set->last_child); + } + + // NOTE(rjf): Literal + else if(_MD_NodeParse_ConsumeLiteral(ctx, &node)) + { + result = MD_MakeExpr(node, MD_ExprKind_Atom, 0, 0); + } + + // NOTE(rjf): Negative + else if(_MD_NodeParse_Consume(ctx, MD_S8Lit("-"), &node)) + { + result = MD_MakeExpr(node, MD_ExprKind_Negative, 0, _MD_ParseExpr(ctx)); + } + + // NOTE(rjf): Bitwise Negate + else if(_MD_NodeParse_Consume(ctx, MD_S8Lit("~"), &node)) + { + result = MD_MakeExpr(node, MD_ExprKind_BitNot, 0, _MD_ParseExpr(ctx)); + } + + // NOTE(rjf): Boolean Negate + else if(_MD_NodeParse_Consume(ctx, MD_S8Lit("!"), &node)) + { + result = MD_MakeExpr(node, MD_ExprKind_BoolNot, 0, _MD_ParseExpr(ctx)); + } + + return result; +} + +MD_PRIVATE_FUNCTION_IMPL MD_Expr * +_MD_ParseExpr_(_MD_NodeParseCtx *ctx, int precedence_in) +{ + MD_Expr *expr = _MD_ParseUnaryExpr(ctx); + if(MD_ExprIsNil(expr)) + { + goto end_parse; + } + + MD_ExprKind expr_kind = MD_BinaryExprKindFromNode(ctx->at); + if(expr_kind != MD_ExprKind_Nil) + { + for(int precedence = MD_ExprPrecFromExprKind(expr_kind); + precedence >= precedence_in; + precedence -= 1) + { + for(;;) + { + MD_Node *op_node = ctx->at; + expr_kind = MD_BinaryExprKindFromNode(ctx->at); + int operator_precedence = MD_ExprPrecFromExprKind(expr_kind); + if(operator_precedence != precedence) + { + break; + } + + if(expr_kind == MD_ExprKind_Nil) + { + break; + } + + _MD_NodeParse_Next(ctx); + + MD_Expr *right = _MD_ParseExpr_(ctx, precedence+1); + if(MD_ExprIsNil(right)) + { + // TODO(rjf): Error: "Expected right-hand-side of binary expression." + goto end_parse; + } + + MD_Expr *left = expr; + expr = MD_MakeExpr(op_node, expr_kind, left, right); + expr->sub[0] = left; + expr->sub[1] = right; + } + } + } + + end_parse:; + return expr; +} + +MD_PRIVATE_FUNCTION_IMPL MD_Expr * +_MD_ParseExpr(_MD_NodeParseCtx *ctx) +{ + return _MD_ParseExpr_(ctx, 1); +} + +MD_FUNCTION_IMPL MD_Expr * +MD_ParseAsExpr(MD_Node *first, MD_Node *last) +{ + _MD_NodeParseCtx ctx_ = { first, last, last->next }; + _MD_NodeParseCtx *ctx = &ctx_; + return _MD_ParseExpr(ctx); +} + +MD_FUNCTION_IMPL MD_Expr * +MD_ParseAsType(MD_Node *first, MD_Node *last) +{ + MD_Expr *expr = MD_NilExpr(); + MD_Expr *last_expr = expr; + _MD_NodeParseCtx ctx_ = { first, last, last->next }; + _MD_NodeParseCtx *ctx = &ctx_; +#define _MD_PushType(x) if(MD_ExprIsNil(last_expr)) { expr = last_expr = x; } else { last_expr = last_expr->sub[0] = x; } + MD_Node *set = 0; + MD_Node *ptr = 0; + MD_Node *base_type = 0; + for(;;) + { + if(_MD_NodeParse_Consume(ctx, MD_S8Lit("*"), &ptr)) + { + MD_Expr *t = MD_MakeExpr(ptr, MD_ExprKind_Pointer, MD_NilExpr(), MD_NilExpr()); + _MD_PushType(t); + } + else if(_MD_NodeParse_ConsumeSet(ctx, &set)) + { + MD_Expr *t = MD_MakeExpr(set, MD_ExprKind_Array, MD_NilExpr(), MD_NilExpr()); + _MD_PushType(t); + } + else if(_MD_NodeParse_ConsumeAtom(ctx, &base_type)) + { + MD_Expr *t = MD_MakeExpr(base_type, MD_ExprKind_Atom, MD_NilExpr(), MD_NilExpr()); + _MD_PushType(t); + } + else + { + break; + } + } +#undef _MD_PushType + return expr; +} + +MD_FUNCTION_IMPL MD_i64 +MD_EvaluateExpr_I64(MD_Expr *expr) +{ + MD_i64 result = 0; + switch(expr->kind) + { +#define _MD_BinaryOp(name, op) case MD_ExprKind_##name: { result = MD_EvaluateExpr_I64(expr->sub[0]) op MD_EvaluateExpr_I64(expr->sub[1]); }break + _MD_BinaryOp(Add, +); + _MD_BinaryOp(Subtract, -); + _MD_BinaryOp(Multiply, *); + _MD_BinaryOp(Divide, /); +#undef _MD_BinaryOp + case MD_ExprKind_Atom: { result = MD_I64FromString(expr->node->string); }break; + default: break; + } + return result; +} + +MD_FUNCTION_IMPL MD_f64 +MD_EvaluateExpr_F64(MD_Expr *expr) +{ + MD_f64 result = 0; + switch(expr->kind) + { +#define _MD_BinaryOp(name, op) case MD_ExprKind_##name: { result = MD_EvaluateExpr_I64(expr->sub[0]) op MD_EvaluateExpr_F64(expr->sub[1]); }break + _MD_BinaryOp(Add, +); + _MD_BinaryOp(Subtract, -); + _MD_BinaryOp(Multiply, *); + _MD_BinaryOp(Divide, /); +#undef _MD_BinaryOp + case MD_ExprKind_Atom: { result = MD_F64FromString(expr->node->string); }break; + default: break; + } + return result; +} + +MD_FUNCTION_IMPL MD_b32 +MD_ExprMatch(MD_Expr *a, MD_Expr *b, MD_StringMatchFlags str_flags) +{ + MD_b32 result = 0; + if(a->kind == b->kind) + { + result = 1; + if(a->kind == MD_ExprKind_Atom) + { + result = MD_StringMatch(a->node->string, b->node->string, str_flags); + } + } + return result; +} + +MD_FUNCTION_IMPL MD_b32 +MD_ExprDeepMatch(MD_Expr *a, MD_Expr *b, MD_StringMatchFlags str_flags) +{ + MD_b32 result = MD_ExprMatch(a, b, str_flags); + if(result && !MD_ExprIsNil(a) && !MD_ExprIsNil(b)) + { + result = (MD_ExprDeepMatch(a->sub[0], b->sub[0], str_flags) && + MD_ExprDeepMatch(a->sub[1], b->sub[1], str_flags)); + } + return result; +} + +MD_FUNCTION_IMPL void +MD_OutputTree(FILE *file, MD_Node *node) +{ + for(MD_Node *tag = node->first_tag; !MD_NodeIsNil(tag); tag = tag->next) + { + fprintf(file, "@%.*s", MD_StringExpand(tag->string)); + if(!MD_NodeIsNil(tag->first_child)) + { + fprintf(file, "("); + for(MD_Node *child = tag->first_child; !MD_NodeIsNil(child); child = child->next) + { + MD_OutputTree(file, child); + fprintf(file, ", "); + } + fprintf(file, ")\n"); + } + else + { + fprintf(file, " "); + } + } + + fprintf(file, "%.*s", MD_StringExpand(node->whole_string)); + if(!MD_NodeIsNil(node->first_child)) + { + fprintf(file, ":\n{\n"); + for(MD_Node *child = node->first_child; !MD_NodeIsNil(child); child = child->next) + { + MD_OutputTree(file, child); + fprintf(file, ",\n"); + } + fprintf(file, "}\n"); + } + else + { + fprintf(file, " "); + } +} + +MD_FUNCTION_IMPL void +MD_OutputExpr(FILE *file, MD_Expr *expr) +{ + +} + +MD_FUNCTION_IMPL void +MD_OutputTree_C_String(FILE *file, MD_Node *node) +{ + fprintf(file, "\""); + for(MD_u64 i = 0; i < node->string.size; i += 1) + { + if(node->string.str[i] == '\n') + { + fprintf(file, "\\n\"\n\""); + } + else + { + fprintf(file, "%c", node->string.str[i]); + } + } + fprintf(file, "\""); +} + +MD_FUNCTION_IMPL void +MD_OutputTree_C_Struct(FILE *file, MD_Node *node) +{ + if(node) + { + fprintf(file, "typedef struct %.*s %.*s;\n", + MD_StringExpand(node->string), + MD_StringExpand(node->string)); + fprintf(file, "struct %.*s\n{\n", MD_StringExpand(node->string)); + for(MD_Node *child = node->first_child; !MD_NodeIsNil(child); child = child->next) + { + MD_OutputTree_C_Decl(file, child); + fprintf(file, ";\n"); + } + fprintf(file, "};\n\n"); + } +} + +MD_FUNCTION_IMPL void +MD_OutputTree_C_Decl(FILE *file, MD_Node *node) +{ + if(node) + { + // TODO(allen): MD_ParseAsType? + MD_Expr *type = MD_ParseAsType(node->first_child, node->last_child); + MD_Output_C_DeclByNameAndType(file, node->string, type); + } +} + +MD_FUNCTION_IMPL void +MD_Output_C_DeclByNameAndType(FILE *file, MD_String8 name, MD_Expr *type) +{ + MD_OutputType_C_LHS(file, type); + fprintf(file, " %.*s", MD_StringExpand(name)); + MD_OutputType_C_RHS(file, type); +} + +MD_PRIVATE_FUNCTION_IMPL MD_b32 +_MD_OutputType_C_NeedsParens(MD_Expr *type) +{ + MD_b32 result = 0; + if (type->kind == MD_ExprKind_Pointer && + type->sub[0]->kind == MD_ExprKind_Array) + { + result = 1; + } + return(result); +} + +MD_FUNCTION_IMPL void +MD_OutputType_C_LHS(FILE *file, MD_Expr *type) +{ + switch (type->kind) + { + case MD_ExprKind_Atom: + { + MD_Node *node = type->node; + fprintf(file, "%.*s", MD_StringExpand(node->whole_string)); + }break; + + case MD_ExprKind_Pointer: + { + MD_OutputType_C_LHS(file, type->sub[0]); + if (_MD_OutputType_C_NeedsParens(type)) + { + fprintf(file, "("); + } + fprintf(file, "*"); + }break; + + case MD_ExprKind_Array: + { + MD_OutputType_C_LHS(file, type->sub[0]); + if (_MD_OutputType_C_NeedsParens(type)) + { + fprintf(file, "("); + } + }break; + + default: + { + fprintf(file, "{ unexpected MD_ExprKind (%i) in type info for node \"%.*s\" }", + type->kind, + MD_StringExpand(type->node->whole_string)); + }break; + } +} + +MD_FUNCTION_IMPL void +MD_OutputType_C_RHS(FILE *file, MD_Expr *type) +{ + switch (type->kind) + { + case MD_ExprKind_Atom: + {}break; + + case MD_ExprKind_Pointer: + { + if (_MD_OutputType_C_NeedsParens(type)) + { + fprintf(file, ")"); + } + MD_OutputType_C_RHS(file, type->sub[0]); + }break; + + case MD_ExprKind_Array: + { + if (_MD_OutputType_C_NeedsParens(type)) + { + fprintf(file, ")"); + } + fprintf(file, "["); + // TODO(allen): MD_OutputExpr_C(file type->sub[1]); + fprintf(file, "\"C expressions not implemented\""); + fprintf(file, "]"); + MD_OutputType_C_RHS(file, type->sub[0]); + }break; + + default: + {}break; + } +} + +MD_FUNCTION_IMPL MD_CommandLine +MD_CommandLine_Start(int argument_count, char **arguments) +{ + MD_CommandLine cmdln = {0}; + cmdln.arguments = _MD_PushArray(_MD_GetCtx(), MD_String8, argument_count-1); + for(int i = 1; i < argument_count; i += 1) + { + cmdln.arguments[i-1] = MD_PushStringF("%s", arguments[i]); + } + cmdln.argument_count = argument_count-1; + return cmdln; +} + +MD_FUNCTION_IMPL MD_b32 +MD_CommandLine_Flag(MD_CommandLine *cmdln, MD_String8 string) +{ + MD_b32 result = 0; + for(int i = 0; i < cmdln->argument_count; i += 1) + { + if(MD_StringMatch(string, cmdln->arguments[i], 0)) + { + result = 1; + cmdln->arguments[i].str = 0; + cmdln->arguments[i].size = 0; + break; + } + } + return result; +} + +MD_FUNCTION_IMPL MD_b32 +MD_CommandLine_FlagStrings(MD_CommandLine *cmdln, MD_String8 string, int out_count, MD_String8 *out) +{ + MD_b32 result = 0; + for(int i = 0; i < cmdln->argument_count; i += 1) + { + if(MD_StringMatch(string, cmdln->arguments[i], 0)) + { + cmdln->arguments[i].str = 0; + cmdln->arguments[i].size = 0; + if(cmdln->argument_count > i + out_count) + { + for(int out_idx = 0; out_idx < out_count; out_idx += 1) + { + out[out_idx] = cmdln->arguments[i+out_idx+1]; + cmdln->arguments[i+out_idx+1].str = 0; + cmdln->arguments[i+out_idx+1].size = 0; + } + result = 1; + break; + } + } + } + return result; +} + +MD_FUNCTION_IMPL MD_b32 +MD_CommandLine_FlagIntegers(MD_CommandLine *cmdln, MD_String8 string, int out_count, MD_i64 *out) +{ + MD_b32 result = 0; + for(int i = 0; i < cmdln->argument_count; i += 1) + { + if(MD_StringMatch(string, cmdln->arguments[i], 0)) + { + cmdln->arguments[i].str = 0; + cmdln->arguments[i].size = 0; + if(cmdln->argument_count > i + out_count) + { + for(int out_idx = 0; out_idx < out_count; out_idx += 1) + { + out[out_idx] = MD_I64FromString(cmdln->arguments[i+out_idx+1]); + cmdln->arguments[i+out_idx+1].str = 0; + cmdln->arguments[i+out_idx+1].size = 0; + } + result = 1; + break; + } + } + } + return result; +} + +MD_FUNCTION_IMPL MD_b32 +MD_CommandLine_FlagString(MD_CommandLine *cmdln, MD_String8 string, MD_String8 *out) +{ + return MD_CommandLine_FlagStrings(cmdln, string, 1, out); +} + +MD_FUNCTION_IMPL MD_b32 +MD_CommandLine_FlagInteger(MD_CommandLine *cmdln, MD_String8 string, MD_i64 *out) +{ + return MD_CommandLine_FlagIntegers(cmdln, string, 1, out); +} + +MD_FUNCTION_IMPL MD_b32 +MD_CommandLine_Increment(MD_CommandLine *cmdln, MD_String8 **string_ptr) +{ + MD_b32 result = 0; + MD_String8 *string = *string_ptr; + if(string == 0) + { + for(int i = 0; i < cmdln->argument_count; i += 1) + { + if(cmdln->arguments[i].str) + { + string = &cmdln->arguments[i]; + break; + } + } + } + else + { + int idx = (int)(string - cmdln->arguments); + string = 0; + for(int i = idx+1; i < cmdln->argument_count; i += 1) + { + if(cmdln->arguments[i].str) + { + string = &cmdln->arguments[i]; + break; + } + } + } + *string_ptr = string; + result = !!string; + return result; +} + +MD_FUNCTION_IMPL MD_String8 +MD_LoadEntireFile(MD_String8 filename) +{ + MD_String8 file_contents = {0}; + FILE *file = fopen((char*)MD_PushStringCopy(filename).str, "r"); + if(file) + { + fseek(file, 0, SEEK_END); + MD_u64 file_size = ftell(file); + fseek(file, 0, SEEK_SET); + file_contents.str = _MD_PushArray(_MD_GetCtx(), MD_u8, file_size+1); + if(file_contents.str) + { + file_contents.size = file_size; + fread(file_contents.str, 1, file_size, file); + } + fclose(file); + } + return file_contents; +} + +MD_FUNCTION_IMPL MD_b32 +MD_FileIterIncrement(MD_FileIter *it, MD_String8 path, MD_FileInfo *out_info) +{ +#if !defined(MD_IMPL_FileIterIncrement) + return(0); +#else + return(MD_IMPL_FileIterIncrement(it, path, out_info)); +#endif +} diff --git a/source/md_malloc.c b/source/md_malloc.c new file mode 100644 index 0000000..bcda86d --- /dev/null +++ b/source/md_malloc.c @@ -0,0 +1,19 @@ +//////////////////////////////// +// TODO(allen): Write commentary for all of this. + +#define MD_IMPL_Alloc(ctx,size) MD_MALLOC_Alloc(ctx,size) + +static void* +MD_MALLOC_Alloc(void *ctx, MD_u64 size) +{ + MD_Assert(ctx == MD_MALLOC_Alloc); + return(malloc(size)); +} + +#define MD_IMPL_GetCtx() MD_MALLOC_GetCtx() + +static void* +MD_MALLOC_GetCtx(void) +{ + return(MD_MALLOC_Alloc); +} diff --git a/source/md_posix.c b/source/md_posix.c new file mode 100644 index 0000000..52b3483 --- /dev/null +++ b/source/md_posix.c @@ -0,0 +1,5 @@ +static MD_b32 +_MD_OS_IMPL_FileIter_Increment(MD_FileIter *it, MD_String8 path, MD_FileInfo *out_info) +{ + +} \ No newline at end of file diff --git a/source/md_win32.c b/source/md_win32.c new file mode 100644 index 0000000..7fd910d --- /dev/null +++ b/source/md_win32.c @@ -0,0 +1,95 @@ +//////////////////////////////// +// TODO(allen): Write commentary for all of this. + +#define MAX_PATH 260 +typedef unsigned long DWORD; +typedef unsigned short WORD; +typedef int BOOL; +typedef void *HANDLE; +typedef char CHAR; +typedef const CHAR *LPCSTR; + +typedef struct _FILETIME _FILETIME; +struct _FILETIME +{ + DWORD dwLowDateTime; + DWORD dwHighDateTime; +}; +typedef _FILETIME FILETIME; +typedef _FILETIME *PFILETIME; +typedef _FILETIME *LPFILETIME; + +typedef struct _WIN32_FIND_DATAA _WIN32_FIND_DATAA; +struct _WIN32_FIND_DATAA +{ +#define FILE_ATTRIBUTE_DIRECTORY 0x10 + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + CHAR cFileName[MAX_PATH]; + CHAR cAlternateFileName[14]; + DWORD dwFileType; + DWORD dwCreatorType; + WORD wFinderFlags; +}; + +typedef _WIN32_FIND_DATAA WIN32_FIND_DATAA; +typedef _WIN32_FIND_DATAA *PWIN32_FIND_DATAA; +typedef _WIN32_FIND_DATAA *LPWIN32_FIND_DATAA; + +MD_C_LINKAGE_BEGIN + +HANDLE FindFirstFileA(LPCSTR lpFileName, LPWIN32_FIND_DATAA lpFindFileData); +BOOL FindNextFileA(HANDLE hFindFile, LPWIN32_FIND_DATAA lpFindFileData); + +MD_C_LINKAGE_END + + +#pragma comment(lib, "User32.lib") + +#define MD_IMPL_FileIterIncrement MD_WIN32_FileIterIncrement + +static MD_b32 +MD_WIN32_FileIterIncrement(MD_FileIter *it, MD_String8 path, MD_FileInfo *out_info) +{ + MD_b32 result = 0; + + WIN32_FIND_DATAA find_data = {0}; + HANDLE state = *(HANDLE *)(&it->state); + if(state == 0) + { + MD_b32 need_star = 0; + if(path.str[path.size-1] == '/' || + path.str[path.size-1] == '\\') + { + need_star = 1; + } + MD_String8 cpath = need_star ? MD_PushStringF("%.*s*", MD_StringExpand(path)) : path; + state = FindFirstFileA((char*)cpath.str, &find_data); + result = !!state; + } + else + { + result = !!FindNextFileA(state, &find_data); + } + + it->state = *(MD_u64 *)(&state); + if(result) + { + out_info->flags = 0; + if(find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + { + out_info->flags |= MD_FileFlag_Directory; + } + out_info->filename = MD_PushStringF("%s", find_data.cFileName); + out_info->file_size = ((((MD_u64)find_data.nFileSizeHigh) << 32) | + ((MD_u64)find_data.nFileSizeLow)); + } + + return result; +} diff --git a/tests/cpp_build_test.cpp b/tests/cpp_build_test.cpp new file mode 100644 index 0000000..a45abb1 --- /dev/null +++ b/tests/cpp_build_test.cpp @@ -0,0 +1,9 @@ +#include "md.h" +#include "md.c" + +int main(void) +{ + + + return 0; +} diff --git a/tests/sanity_tests.c b/tests/sanity_tests.c new file mode 100644 index 0000000..c3dc3fd --- /dev/null +++ b/tests/sanity_tests.c @@ -0,0 +1,403 @@ +#include "md.h" +#include "md.c" + +static struct +{ + int number_of_tests; + int number_passed; +} +test_ctx; + +static void +BeginTest(char *name) +{ + int length = MD_CalculateCStringLength(name); + int spaces = 25 - length; + if(spaces < 0) + { + spaces = 0; + } + printf("\"%s\" %.*s [", name, spaces, "------------------------------"); + test_ctx.number_of_tests = 0; + test_ctx.number_passed = 0; +} + +static void +TestResult(MD_b32 result) +{ + test_ctx.number_of_tests += 1; + test_ctx.number_passed += !!result; + printf(result ? "." : "X"); +} + +static void +EndTest(void) +{ + int spaces = 10 - test_ctx.number_of_tests; + if(spaces < 0) { spaces = 0; } + printf("]%.*s ", spaces, " "); + printf("[%i/%i] %i passed, %i tests, ", + test_ctx.number_passed, test_ctx.number_of_tests, + test_ctx.number_passed, test_ctx.number_of_tests); + if(test_ctx.number_of_tests == test_ctx.number_passed) + { + printf("SUCCESS ( )"); + } + else + { + printf("FAILED (X)"); + } + printf("\n"); +} + +#define Test(name) for(int _i_ = (BeginTest(name), 0); !_i_; _i_ += 1, EndTest()) + +static MD_Node * +MakeTestNode(MD_NodeKind kind, MD_String8 string) +{ + return MD_MakeNodeFromString(kind, MD_S8Lit("`TEST_NODE`"), 0, 0, string); +} + +static MD_Expr * +AtomExpr(char *str) +{ + return MD_MakeExpr(MakeTestNode(MD_NodeKind_Label, MD_S8CString(str)), MD_ExprKind_Atom, MD_NilExpr(), MD_NilExpr()); +} + +static MD_Expr * +BinOpExpr(MD_ExprKind kind, MD_Expr *left, MD_Expr *right) +{ + return MD_MakeExpr(MD_NilNode(), kind, left, right); +} + +static MD_Expr * +TypeExpr(MD_ExprKind kind, MD_Expr *sub) +{ + return MD_MakeExpr(MD_NilNode(), kind, sub, MD_NilExpr()); +} + +static MD_b32 +MatchParsedWithNode(MD_String8 string, MD_Node *tree) +{ + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), string); + return MD_NodeDeepMatch(tree, parse.node, 0, MD_NodeMatchFlag_Tags | MD_NodeMatchFlag_TagArguments); +} + +static MD_b32 +MatchParsedWithExpr(MD_String8 string, MD_Expr *expr) +{ + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), string); + MD_Expr *parse_expr = MD_ParseAsExpr(parse.node->first_child, parse.node->last_child); + return MD_ExprDeepMatch(expr, parse_expr, 0); +} + +static MD_b32 +MatchParsedWithType(MD_String8 string, MD_Expr *expr) +{ + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), string); + MD_Expr *parse_expr = MD_ParseAsType(parse.node->first_child, parse.node->last_child); + return MD_ExprDeepMatch(expr, parse_expr, 0); +} + +static MD_b32 +TokenMatch(MD_Token token, MD_String8 string, MD_TokenKind kind) +{ + return MD_StringMatch(string, token.string, 0) && token.kind == kind; +} + +int main(void) +{ + + Test("Lexer") + { + MD_String8 string = MD_S8Lit("abc def 123 456 123_456 abc123 123abc"); + MD_ParseCtx ctx = MD_Parse_InitializeCtx(MD_S8Lit(""), string); + TestResult(TokenMatch(MD_Parse_LexNext(&ctx), MD_S8Lit("abc"), MD_TokenKind_Identifier)); + TestResult(TokenMatch(MD_Parse_LexNext(&ctx), MD_S8Lit(" "), MD_TokenKind_Whitespace)); + TestResult(TokenMatch(MD_Parse_LexNext(&ctx), MD_S8Lit("def"), MD_TokenKind_Identifier)); + TestResult(TokenMatch(MD_Parse_LexNext(&ctx), MD_S8Lit(" "), MD_TokenKind_Whitespace)); + TestResult(TokenMatch(MD_Parse_LexNext(&ctx), MD_S8Lit("123"), MD_TokenKind_NumericLiteral)); + TestResult(TokenMatch(MD_Parse_LexNext(&ctx), MD_S8Lit(" "), MD_TokenKind_Whitespace)); + TestResult(TokenMatch(MD_Parse_LexNext(&ctx), MD_S8Lit("456"), MD_TokenKind_NumericLiteral)); + TestResult(TokenMatch(MD_Parse_LexNext(&ctx), MD_S8Lit(" "), MD_TokenKind_Whitespace)); + // TODO(rjf): Enable once numeric literal lexing is fixed + //TestResult(TokenMatch(MD_Parse_LexNext(&ctx), MD_S8Lit("123_456"), MD_TokenKind_NumericLiteral)); + } + + Test("Empty Sets") + { + TestResult(MatchParsedWithNode(MD_S8Lit("{}"), MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")))); + TestResult(MatchParsedWithNode(MD_S8Lit("()"), MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")))); + TestResult(MatchParsedWithNode(MD_S8Lit("[]"), MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")))); + TestResult(MatchParsedWithNode(MD_S8Lit("[)"), MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")))); + TestResult(MatchParsedWithNode(MD_S8Lit("(]"), MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")))); + } + + Test("Simple Unnamed Sets") + { + { + MD_String8 string = MD_S8Lit("{a, b, c}"); + MD_Node *tree = MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("a"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("b"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("c"))); + TestResult(MatchParsedWithNode(string, tree)); + } + { + MD_String8 string = MD_S8Lit("(1 2 3 4 5)"); + MD_Node *tree = MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("1"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("2"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("3"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("4"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("5"))); + TestResult(MatchParsedWithNode(string, tree)); + } + { + MD_String8 string = MD_S8Lit("{a}"); + MD_Node *tree = MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("a"))); + TestResult(MatchParsedWithNode(string, tree)); + } + } + + Test("Simple Named Sets") + { + MD_String8 string = MD_S8Lit("simple_set: {a, b, c}"); + MD_Node *tree = MakeTestNode(MD_NodeKind_Label, MD_S8Lit("simple_set")); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("a"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("b"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("c"))); + TestResult(MatchParsedWithNode(string, tree)); + } + + Test("Nested Sets") + { + { + MD_String8 string = MD_S8Lit("{a b:{1 2 3} c}"); + MD_Node *tree = MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("a"))); + { + MD_Node *sub = MakeTestNode(MD_NodeKind_Label, MD_S8Lit("b")); + MD_PushChild(sub, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("1"))); + MD_PushChild(sub, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("2"))); + MD_PushChild(sub, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("3"))); + MD_PushChild(tree, sub); + } + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("c"))); + TestResult(MatchParsedWithNode(string, tree)); + } + + { + MD_String8 string = MD_S8Lit("foo: { (size: u64) -> *void }"); + MD_Node *tree = MakeTestNode(MD_NodeKind_Label, MD_S8Lit("foo")); + MD_Node *params = MakeTestNode(MD_NodeKind_UnnamedSet, MD_S8Lit("")); + MD_Node *size = MakeTestNode(MD_NodeKind_Label, MD_S8Lit("size")); + MD_PushChild(size, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("u64"))); + MD_PushChild(params, size); + MD_PushChild(tree, params); + // TODO(rjf): This test will fail once we have digraphs implemented. Adjust the separate + // "-" and ">" set members, and combine them to form a single "->" set member. + // { + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("-"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit(">"))); + // } + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("*"))); + MD_PushChild(tree, MakeTestNode(MD_NodeKind_Label, MD_S8Lit("void"))); + TestResult(MatchParsedWithNode(string, tree)); + } + } + + Test("Non-Sets") + { + TestResult(MatchParsedWithNode(MD_S8Lit("foo"), MakeTestNode(MD_NodeKind_Label, MD_S8Lit("foo")))); + TestResult(MatchParsedWithNode(MD_S8Lit("123"), MakeTestNode(MD_NodeKind_Label, MD_S8Lit("123")))); + TestResult(MatchParsedWithNode(MD_S8Lit("+"), MakeTestNode(MD_NodeKind_Label, MD_S8Lit("+")))); + } + + Test("Set Border Flags") + { + { + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("(0, 100)")); + TestResult(parse.node->flags & MD_NodeFlag_ParenLeft && + parse.node->flags & MD_NodeFlag_ParenRight); + } + + { + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("(0, 100]")); + TestResult(parse.node->flags & MD_NodeFlag_ParenLeft && + parse.node->flags & MD_NodeFlag_BracketRight); + } + + { + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("[0, 100)")); + TestResult(parse.node->flags & MD_NodeFlag_BracketLeft && + parse.node->flags & MD_NodeFlag_ParenRight); + } + + { + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("[0, 100]")); + TestResult(parse.node->flags & MD_NodeFlag_BracketLeft && + parse.node->flags & MD_NodeFlag_BracketRight); + } + + { + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("{0, 100}")); + TestResult(parse.node->flags & MD_NodeFlag_BraceLeft && + parse.node->flags & MD_NodeFlag_BraceRight); + } + } + + Test("Node Separator Flags") + { + { + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("(a, b)")); + TestResult(parse.node->first_child->flags & MD_NodeFlag_BeforeComma); + } + { + MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("(a; b)")); + TestResult(parse.node->first_child->flags & MD_NodeFlag_BeforeSemicolon); + } + { + // TODO(rjf): Enable this once we have digraphs. + // MD_ParseResult parse = MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("(a -> b)")); + // TestResult(parse.node->first_child->flags & MD_NodeFlag_BeforeArrow); + } + } + + Test("Node Text Flags") + { + TestResult(MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("123")).node->flags & + MD_NodeFlag_Numeric); + TestResult(MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("123_456_789")).node->flags & + MD_NodeFlag_Numeric); + TestResult(MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("abc")).node->flags & + MD_NodeFlag_Identifier); + TestResult(MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("\"foo\"")).node->flags & + MD_NodeFlag_StringLiteral); + TestResult(MD_ParseOneNode(MD_S8Lit(""), MD_S8Lit("'foo'")).node->flags & + MD_NodeFlag_CharLiteral); + } + + Test("Expression Evaluation") + { + // NOTE(rjf): 5 + 3 + { + MD_Expr *expr = BinOpExpr(MD_ExprKind_Add, AtomExpr("5"), AtomExpr("3")); + TestResult(MD_EvaluateExpr_I64(expr) == 8); + } + + // NOTE(rjf): 5 - 3 + { + MD_Expr *expr = BinOpExpr(MD_ExprKind_Subtract, AtomExpr("5"), AtomExpr("3")); + TestResult(MD_EvaluateExpr_I64(expr) == 2); + } + + // NOTE(rjf): 5 * 3 + { + MD_Expr *expr = BinOpExpr(MD_ExprKind_Multiply, AtomExpr("5"), AtomExpr("3")); + TestResult(MD_EvaluateExpr_I64(expr) == 15); + } + + // NOTE(rjf): 10 / 2 + { + MD_Expr *expr = BinOpExpr(MD_ExprKind_Divide, AtomExpr("10"), AtomExpr("2")); + TestResult(MD_EvaluateExpr_I64(expr) == 5); + } + + // NOTE(rjf): (3 + 4) * (2 + 6) + { + MD_Expr *left = BinOpExpr(MD_ExprKind_Add, AtomExpr("3"), AtomExpr("4")); + MD_Expr *right = BinOpExpr(MD_ExprKind_Add, AtomExpr("2"), AtomExpr("6")); + MD_Expr *expr = BinOpExpr(MD_ExprKind_Multiply, left, right); + TestResult(MD_EvaluateExpr_I64(expr) == 56); + } + } + + Test("Expression Parsing") + { + { + MD_String8 string = MD_S8Lit("(1 + 2)"); + MD_Expr *expr = BinOpExpr(MD_ExprKind_Add, AtomExpr("1"), AtomExpr("2")); + TestResult(MatchParsedWithExpr(string, expr)); + } + { + MD_String8 string = MD_S8Lit("((3 + 4) * (2 + 6))"); + MD_Expr *left = BinOpExpr(MD_ExprKind_Add, AtomExpr("3"), AtomExpr("4")); + MD_Expr *right = BinOpExpr(MD_ExprKind_Add, AtomExpr("2"), AtomExpr("6")); + MD_Expr *expr = BinOpExpr(MD_ExprKind_Multiply, left, right); + TestResult(MatchParsedWithExpr(string, expr)); + } + { + MD_String8 string = MD_S8Lit("(1*2+3)"); + MD_Expr *left = BinOpExpr(MD_ExprKind_Multiply, AtomExpr("1"), AtomExpr("2")); + MD_Expr *expr = BinOpExpr(MD_ExprKind_Add, left, AtomExpr("3")); + TestResult(MatchParsedWithExpr(string, expr)); + } + } + + Test("Type Parsing") + { + { + MD_String8 string = MD_S8Lit("(i32)"); + MD_Expr *expr = AtomExpr("i32"); + TestResult(MatchParsedWithType(string, expr)); + } + { + MD_String8 string = MD_S8Lit("(*i32)"); + MD_Expr *expr = TypeExpr(MD_ExprKind_Pointer, AtomExpr("i32")); + TestResult(MatchParsedWithType(string, expr)); + } + { + MD_String8 string = MD_S8Lit("(**i32)"); + MD_Expr *expr = TypeExpr(MD_ExprKind_Pointer, TypeExpr(MD_ExprKind_Pointer, AtomExpr("i32"))); + TestResult(MatchParsedWithType(string, expr)); + } + { + MD_String8 string = MD_S8Lit("(*void)"); + MD_Expr *expr = TypeExpr(MD_ExprKind_Pointer, AtomExpr("void")); + TestResult(MatchParsedWithType(string, expr)); + } + } + + Test("Style Strings") + { + { + MD_String8 str = MD_StyledStringFromString(MD_S8Lit("THIS_IS_A_TEST"), MD_WordStyle_UpperCamelCase, MD_S8Lit(" ")); + TestResult(MD_StringMatch(str, MD_S8Lit("This Is A Test"), 0)); + } + { + MD_String8 str = MD_StyledStringFromString(MD_S8Lit("this_is_a_test"), MD_WordStyle_UpperCamelCase, MD_S8Lit(" ")); + TestResult(MD_StringMatch(str, MD_S8Lit("This Is A Test"), 0)); + } + { + MD_String8 str = MD_StyledStringFromString(MD_S8Lit("ThisIsATest"), MD_WordStyle_UpperCamelCase, MD_S8Lit(" ")); + TestResult(MD_StringMatch(str, MD_S8Lit("This Is A Test"), 0)); + } + { + MD_String8 str = MD_StyledStringFromString(MD_S8Lit("Here is another test."), MD_WordStyle_UpperCamelCase, MD_S8Lit("")); + TestResult(MD_StringMatch(str, MD_S8Lit("HereIsAnotherTest."), 0)); + } + } + + Test("Enum Strings") + { + TestResult(MD_StringMatch(MD_StringFromNodeKind(MD_NodeKind_Label), MD_S8Lit("Label"), 0)); + TestResult(MD_StringMatch(MD_StringFromNodeKind(MD_NodeKind_UnnamedSet), MD_S8Lit("UnnamedSet"), 0)); + MD_String8List list = MD_StringListFromNodeFlags(MD_NodeFlag_CharLiteral | MD_NodeFlag_ParenLeft | MD_NodeFlag_BeforeSemicolon); + MD_b32 match = 1; + for(MD_String8Node *node = list.first; node; node = node->next) + { + if(!MD_StringMatch(node->string, MD_S8Lit("CharLiteral"), 0) && + !MD_StringMatch(node->string, MD_S8Lit("ParenLeft"), 0) && + !MD_StringMatch(node->string, MD_S8Lit("BeforeSemicolon"), 0)) + { + match = 0; + break; + } + } + TestResult(match); + } + + return 0; +} \ No newline at end of file diff --git a/tests/unicode_test.c b/tests/unicode_test.c new file mode 100644 index 0000000..623df3c --- /dev/null +++ b/tests/unicode_test.c @@ -0,0 +1,26 @@ +#include "md.h" +#include "md.c" + +void run_test_on_string(MD_String8 string) +{ + MD_String16 s16 = MD_S16FromS8(string); + MD_String8 s8_ts16 = MD_S8FromS16(s16); + MD_Assert(MD_StringMatch(s8_ts16, string, 0)); + + MD_String32 s32 = MD_S32FromS8(string); + MD_String8 s8_ts32 = MD_S8FromS32(s32); + MD_Assert(MD_StringMatch(s8_ts32, string, 0)); +} + +int main(void) +{ + // TODO(allen): throw more at this. + + char test_string_c[] = "Foo bar; test the unicode\n\t\0Etc"; + MD_String8 test_string = MD_S8(test_string_c, sizeof(test_string_c) - 1); + + run_test_on_string(test_string); + + return 0; +} +