diff --git a/samples/parse_check.c b/samples/parse_check.c index b342992..da00371 100644 --- a/samples/parse_check.c +++ b/samples/parse_check.c @@ -18,7 +18,9 @@ int main(int argument_count, char **arguments) { for(MD_EachNode(node, root->first_child)) { - MD_DebugOutputTree(stdout, node, 0); + MD_String8List strs = MD_DebugStringListFromNode(arena, node, 0, MD_S8Lit(" "), MD_GenerateFlags_Tree); + MD_String8 str = MD_S8ListJoin(arena, &strs, 0); + fprintf(stdout, "%.*s\n", MD_S8VArg(str)); } } diff --git a/source/md.c b/source/md.c index 9a998b3..80d895d 100644 --- a/source/md.c +++ b/source/md.c @@ -1308,6 +1308,34 @@ MD_PathChopLastSlash(MD_String8 string) return string; } +MD_FUNCTION_IMPL MD_String8 +MD_S8SkipWhitespace(MD_String8 string) +{ + for(MD_u64 i = 0; i < string.size; i += 1) + { + if(!MD_CharIsSpace(string.str[i])) + { + string = MD_S8Skip(string, i); + break; + } + } + return string; +} + +MD_FUNCTION_IMPL MD_String8 +MD_S8ChopWhitespace(MD_String8 string) +{ + for(MD_u64 i = string.size-1; i < string.size; i -= 1) + { + if(!MD_CharIsSpace(string.str[i])) + { + string = MD_S8Prefix(string, i+1); + break; + } + } + return string; +} + //~ Numeric Strings MD_FUNCTION_IMPL MD_u64 @@ -2879,62 +2907,388 @@ MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags) return result; } -//~ Generation +//~ String Generation -MD_FUNCTION_IMPL void -MD_DebugOutputTree(FILE *file, MD_Node *node, int indent_spaces) +MD_FUNCTION_IMPL MD_String8List +MD_DebugStringListFromNode(MD_Arena *arena, MD_Node *node, int indent, MD_String8 indent_string, MD_GenerateFlags flags) { -#define MD_PrintIndent() do { for(int i = 0; i < indent_spaces; i += 1) fprintf(file, " "); } while(0) - for(MD_Node *tag = node->first_tag; !MD_NodeIsNil(tag); tag = tag->next) + MD_String8List list = {0}; +#define MD_PrintIndent(_indent_level) do\ +{\ +for(int i = 0; i < (_indent_level); i += 1)\ +{\ +MD_S8ListPush(arena, &list, indent_string);\ +}\ +}while(0) + + //- rjf: prev-comment + // TODO(rjf): @node_comments + if(flags & MD_GenerateFlag_Comments && node->prev_comment.size != 0) { - MD_PrintIndent(); - fprintf(file, "@%.*s", MD_S8VArg(tag->string)); - if(!MD_NodeIsNil(tag->first_child)) + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("/*\n")); + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, node->prev_comment); + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("*/\n")); + } + + //- rjf: tags of node + if(flags & MD_GenerateFlag_Tags) + { + for(MD_EachNode(tag, node->first_tag)) { - fprintf(file, "("); - for(MD_Node *child = tag->first_child; !MD_NodeIsNil(child); child = child->next) + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("@")); + MD_S8ListPush(arena, &list, tag->string); + if(flags & MD_GenerateFlag_TagArguments && !MD_NodeIsNil(tag->first_child)) { - MD_DebugOutputTree(file, child, 0); - fprintf(file, ", "); + int tag_arg_indent = indent + 1 + tag->string.size + 1; + MD_S8ListPush(arena, &list, MD_S8Lit("(")); + for(MD_EachNode(child, tag->first_child)) + { + int child_indent = tag_arg_indent; + if(MD_NodeIsNil(child->prev)) + { + child_indent = 0; + } + MD_String8List child_strings = MD_DebugStringListFromNode(arena, child, child_indent, MD_S8Lit(" "), flags); + MD_S8ListConcat(&list, &child_strings); + if(!MD_NodeIsNil(child->next)) + { + MD_S8ListPush(arena, &list, MD_S8Lit(",\n")); + } + } + MD_S8ListPush(arena, &list, MD_S8Lit(")\n")); + } + else + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); } - fprintf(file, ")\n"); } - else if(!MD_NodeIsNil(tag->next)) - { - fprintf(file, " "); - } - } - if(!MD_NodeIsNil(node->first_tag)) - { - fprintf(file, "\n"); } - if(node->raw_string.size > 0) + //- rjf: node kind + if(flags & MD_GenerateFlag_NodeKind) { - MD_PrintIndent(); - fprintf(file, "%.*s", MD_S8VArg(node->raw_string)); + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("// kind: \"")); + MD_S8ListPush(arena, &list, MD_StringFromNodeKind(node->kind)); + MD_S8ListPush(arena, &list, MD_S8Lit("\"\n")); } - if(!MD_NodeIsNil(node->first_child)) + //- rjf: node flags + // TODO(rjf): @node_comments + if(flags & MD_GenerateFlag_NodeFlags) { - if(node->raw_string.size > 0) - { - fprintf(file, ":\n"); - } - MD_PrintIndent(); - fprintf(file, "{\n"); - for(MD_Node *child = node->first_child; !MD_NodeIsNil(child); child = child->next) - { - MD_DebugOutputTree(file, child, indent_spaces+2); - } - MD_PrintIndent(); - fprintf(file, "}\n"); + MD_PrintIndent(indent); + MD_ArenaTemp scratch = MD_GetScratch(&arena, 1); + MD_String8List flag_strs = MD_StringListFromNodeFlags(scratch.arena, node->flags); + MD_StringJoin join = { MD_S8Lit(""), MD_S8Lit("|"), MD_S8Lit("") }; + MD_String8 flag_str = MD_S8ListJoin(arena, flag_strs, &join); + MD_S8ListPush(arena, &list, MD_S8Lit("// flags: \"")); + MD_S8ListPush(arena, &list, flag_str); + MD_S8ListPush(arena, &list, MD_S8Lit("\"\n")); + MD_ReleaseScratch(scratch); } - else + + //- rjf: node string hash + if(flags & MD_GenerateFlag_StringHash) { - fprintf(file, "\n"); + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Fmt(arena, "// string hash: 0x%llx\n", node->string_hash)); } + + if(flags & MD_GenerateFlag_Location) + { + MD_PrintIndent(indent); + MD_CodeLoc loc = MD_CodeLocFromNode(node); + MD_String8 string = MD_S8Fmt(arena, "// location: %.*s:%i:%i\n", MD_S8VArg(loc.filename), (int)loc.line, (int)loc.column); + MD_S8ListPush(arena, &list, string); + } + + //- rjf: name of node + if(node->string.size != 0) + { + MD_PrintIndent(indent); + if(node->kind == MD_NodeKind_File) + { + MD_S8ListPush(arena, &list, MD_S8Lit("`")); + MD_S8ListPush(arena, &list, node->string); + MD_S8ListPush(arena, &list, MD_S8Lit("`")); + } + else + { + MD_S8ListPush(arena, &list, node->raw_string); + } + } + + //- rjf: children list + if(flags & MD_GenerateFlag_Children && !MD_NodeIsNil(node->first_child)) + { + if(node->string.size != 0) + { + MD_S8ListPush(arena, &list, MD_S8Lit(":\n")); + } + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("{\n")); + for(MD_EachNode(child, node->first_child)) + { + MD_String8List child_strings = MD_DebugStringListFromNode(arena, child, indent+1, indent_string, flags); + MD_S8ListConcat(&list, &child_strings); + MD_S8ListPush(arena, &list, MD_S8Lit(",\n")); + } + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("}")); + } + + //- rjf: next-comment + if(flags & MD_GenerateFlag_Comments && node->next_comment.size != 0) + { + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("\n/*\n")); + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, node->next_comment); + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("*/\n")); + } + #undef MD_PrintIndent + return list; +} + +MD_FUNCTION_IMPL MD_String8List +MD_ReconstructedStringListFromNode(MD_Arena *arena, MD_Node *node, int indent, MD_String8 indent_string, MD_GenerateFlags flags) +{ + // TODO(rjf): // TODO(rjf): // TODO(rjf): + // TODO(rjf): // TODO(rjf): // TODO(rjf): + // TODO(rjf): cleanup pass + // TODO(rjf): // TODO(rjf): // TODO(rjf): + // TODO(rjf): // TODO(rjf): // TODO(rjf): + + MD_CodeLoc node_loc = MD_CodeLocFromNode(node); + + MD_String8List list = {0}; +#define MD_PrintIndent(_indent_level) do\ +{\ +for(int i = 0; i < (_indent_level); i += 1)\ +{\ +MD_S8ListPush(arena, &list, indent_string);\ +}\ +}while(0) + + //- rjf: prev-comment + // TODO(rjf): @node_comments + if(flags & MD_GenerateFlag_Comments && node->prev_comment.size != 0) + { + MD_String8 comment = MD_S8SkipWhitespace(MD_S8ChopWhitespace(node->prev_comment)); + MD_b32 requires_multiline = MD_S8FindSubstring(comment, MD_S8Lit("\n"), 0, 0) < comment.size; + MD_PrintIndent(indent); + if(requires_multiline) + { + MD_S8ListPush(arena, &list, MD_S8Lit("/*\n")); + } + else + { + MD_S8ListPush(arena, &list, MD_S8Lit("// ")); + } + MD_S8ListPush(arena, &list, comment); + if(requires_multiline) + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n*/\n")); + } + else + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); + } + } + + //- rjf: tags of node + if(flags & MD_GenerateFlag_Tags) + { + for(MD_EachNode(tag, node->first_tag)) + { + MD_PrintIndent(indent); + MD_S8ListPush(arena, &list, MD_S8Lit("@")); + MD_S8ListPush(arena, &list, tag->string); + if(flags & MD_GenerateFlag_TagArguments && !MD_NodeIsNil(tag->first_child)) + { + int tag_arg_indent = indent + 1 + tag->string.size + 1; + MD_S8ListPush(arena, &list, MD_S8Lit("(")); + MD_u32 last_line = MD_CodeLocFromNode(tag).line; + for(MD_EachNode(child, tag->first_child)) + { + MD_CodeLoc child_loc = MD_CodeLocFromNode(child); + if(child_loc.line != last_line) + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); + MD_PrintIndent(indent); + } + last_line = child_loc.line; + + int child_indent = tag_arg_indent; + if(MD_NodeIsNil(child->prev)) + { + child_indent = 0; + } + MD_String8List child_strings = MD_ReconstructedStringListFromNode(arena, child, child_indent, MD_S8Lit(" "), flags); + MD_S8ListConcat(&list, &child_strings); + if(!MD_NodeIsNil(child->next)) + { + MD_S8ListPush(arena, &list, MD_S8Lit(",\n")); + } + } + MD_S8ListPush(arena, &list, MD_S8Lit(")\n")); + } + else + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); + } + } + } + + //- rjf: name of node + if(node->string.size != 0) + { + MD_PrintIndent(indent); + if(node->kind == MD_NodeKind_File) + { + MD_S8ListPush(arena, &list, MD_S8Lit("`")); + MD_S8ListPush(arena, &list, node->string); + MD_S8ListPush(arena, &list, MD_S8Lit("`")); + } + else + { + MD_S8ListPush(arena, &list, node->raw_string); + } + } + + //- rjf: children list + if(flags & MD_GenerateFlag_Children && !MD_NodeIsNil(node->first_child)) + { + if(node->string.size != 0) + { + MD_S8ListPush(arena, &list, MD_S8Lit(":")); + } + MD_PrintIndent(indent); + + // rjf: figure out opener/closer symbols + MD_u8 opener_char = 0; + MD_u8 closer_char = 0; + if(node->flags & MD_NodeFlag_HasParenLeft) + { + opener_char = '('; + } + else if(node->flags & MD_NodeFlag_HasBracketLeft) + { + opener_char = '['; + } + else if(node->flags & MD_NodeFlag_HasBraceLeft) + { + opener_char = '{'; + } + if(node->flags & MD_NodeFlag_HasParenRight) + { + closer_char = ')'; + } + else if(node->flags & MD_NodeFlag_HasBracketRight) + { + closer_char = ']'; + } + else if(node->flags & MD_NodeFlag_HasBraceRight) + { + closer_char = '}'; + } + + MD_b32 multiline = 0; + for(MD_EachNode(child, node->first_child)) + { + MD_CodeLoc child_loc = MD_CodeLocFromNode(child); + if(child_loc.line != node_loc.line) + { + multiline = 1; + break; + } + } + + if(opener_char != 0) + { + if(multiline) + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); + MD_PrintIndent(indent); + } + MD_S8ListPush(arena, &list, MD_S8(&opener_char, 1)); + } + MD_u32 last_line = node_loc.line; + for(MD_EachNode(child, node->first_child)) + { + int child_indent = 0; + MD_CodeLoc child_loc = MD_CodeLocFromNode(child); + if(child_loc.line != last_line) + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); + MD_PrintIndent(indent); + child_indent = indent+1; + } + last_line = child_loc.line; + MD_String8List child_strings = MD_ReconstructedStringListFromNode(arena, child, child_indent, indent_string, flags); + MD_S8ListConcat(&list, &child_strings); + } + MD_PrintIndent(indent); + if(closer_char != 0) + { + if(last_line != node_loc.line) + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); + MD_PrintIndent(indent); + } + MD_S8ListPush(arena, &list, MD_S8(&closer_char, 1)); + } + } + + //- rjf: trailing separator symbols + if(node->flags & MD_NodeFlag_IsBeforeSemicolon) + { + MD_S8ListPush(arena, &list, MD_S8Lit(";")); + } + else if(node->flags & MD_NodeFlag_IsBeforeComma) + { + MD_S8ListPush(arena, &list, MD_S8Lit(",")); + } + + //- rjf: next-comment + // TODO(rjf): @node_comments + if(flags & MD_GenerateFlag_Comments && node->next_comment.size != 0) + { + MD_String8 comment = MD_S8SkipWhitespace(MD_S8ChopWhitespace(node->next_comment)); + MD_b32 requires_multiline = MD_S8FindSubstring(comment, MD_S8Lit("\n"), 0, 0) < comment.size; + MD_PrintIndent(indent); + if(requires_multiline) + { + MD_S8ListPush(arena, &list, MD_S8Lit("/*\n")); + } + else + { + MD_S8ListPush(arena, &list, MD_S8Lit("// ")); + } + MD_S8ListPush(arena, &list, comment); + if(requires_multiline) + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n*/\n")); + } + else + { + MD_S8ListPush(arena, &list, MD_S8Lit("\n")); + } + } + +#undef MD_PrintIndent + return list; } //~ Command Line Argument Helper diff --git a/source/md.h b/source/md.h index 70c2788..5386f7b 100644 --- a/source/md.h +++ b/source/md.h @@ -44,6 +44,9 @@ ** */ +// TODO(rjf): implicitly-delimited sets are not having their separator flags +// appropriately set + //~ Set default values for controls #if !defined(MD_DEFAULT_FILE_ITER) # define MD_DEFAULT_FILE_ITER 1 @@ -505,6 +508,9 @@ struct MD_Node MD_u64 string_hash; // Comments. + // TODO(rjf): @node_comments these are pretty under-powered... should they + // just be nodes, maybe? Would that allow for some cool stuff, like comment + // hierarchies? MD_String8 prev_comment; MD_String8 next_comment; @@ -651,6 +657,26 @@ struct MD_ParseResult MD_MessageList errors; }; +//~ String Generation Types + +typedef MD_u32 MD_GenerateFlags; +enum +{ + MD_GenerateFlag_Tags = (1<<0), + MD_GenerateFlag_TagArguments = (1<<1), + MD_GenerateFlag_Children = (1<<2), + MD_GenerateFlag_Comments = (1<<3), + MD_GenerateFlag_NodeKind = (1<<4), + MD_GenerateFlag_NodeFlags = (1<<5), + MD_GenerateFlag_StringHash = (1<<6), + MD_GenerateFlag_Location = (1<<7), + + MD_GenerateFlags_Tree = (MD_GenerateFlag_Tags | + MD_GenerateFlag_TagArguments | + MD_GenerateFlag_Children), + MD_GenerateFlags_All = 0xffffffff, +}; + //~ Command line parsing helper types. typedef struct MD_CmdLineOption MD_CmdLineOption; @@ -846,7 +872,7 @@ MD_FUNCTION MD_String16 MD_S16FromS8(MD_Arena *arena, MD_String8 str); MD_FUNCTION MD_String8 MD_S8FromS32(MD_Arena *arena, MD_String32 str); MD_FUNCTION MD_String32 MD_S32FromS8(MD_Arena *arena, MD_String8 str); -//~ File Name Strings +//~ String Skipping/Chopping Helpers // This is intended for removing extensions. MD_FUNCTION MD_String8 MD_PathChopLastPeriod(MD_String8 string); @@ -860,6 +886,9 @@ MD_FUNCTION MD_String8 MD_PathSkipLastPeriod(MD_String8 string); // This is intended for getting the folder string from a full path. MD_FUNCTION MD_String8 MD_PathChopLastSlash(MD_String8 string); +MD_FUNCTION MD_String8 MD_S8SkipWhitespace(MD_String8 string); +MD_FUNCTION MD_String8 MD_S8ChopWhitespace(MD_String8 string); + //~ Numeric Strings MD_FUNCTION MD_u64 MD_U64FromString(MD_String8 string, MD_u32 radix); @@ -962,9 +991,10 @@ MD_FUNCTION void MD_PrintNodeMessageFmt(FILE *out, MD_Node *node, MD_MessageKind MD_FUNCTION MD_b32 MD_NodeMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags); MD_FUNCTION MD_b32 MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags); -//~ Generation +//~ String Generation -MD_FUNCTION void MD_DebugOutputTree(FILE *file, MD_Node *node, int indent_spaces); +MD_FUNCTION MD_String8List MD_DebugStringListFromNode(MD_Arena *arena, MD_Node *node, int indent, MD_String8 indent_string, MD_GenerateFlags flags); +MD_FUNCTION MD_String8List MD_ReconstructedStringListFromNode(MD_Arena *arena, MD_Node *node, int indent, MD_String8 indent_string, MD_GenerateFlags flags); //~ Command Line Argument Helper diff --git a/tests/sanity_tests.c b/tests/sanity_tests.c index 40b07f1..6534336 100644 --- a/tests/sanity_tests.c +++ b/tests/sanity_tests.c @@ -898,5 +898,33 @@ int main(void) } } + Test("Debug Strings") + { + { + MD_String8 code = MD_S8Lit("@foo @bar @baz a: { b c d e f }"); + MD_String8 expected = MD_S8Lit("@foo\n@bar\n@baz\na:\n{\n b,\n c,\n d,\n e,\n f,\n}"); + MD_ParseResult parse = MD_ParseOneNode(arena, code, 0); + MD_String8List actual_strings = MD_DebugStringListFromNode(arena, parse.node, 0, MD_S8Lit(" "), MD_GenerateFlags_Tree); + MD_String8 actual = MD_S8ListJoin(arena, actual_strings, 0); + TestResult(MD_S8Match(expected, actual, 0)); + } + { + MD_String8 code = MD_S8Lit("@foo(1, 2, 3) a: { x y }"); + MD_String8 expected = MD_S8Lit("@foo(1,\n 2,\n 3)\na:\n{\n x,\n y,\n}"); + MD_ParseResult parse = MD_ParseOneNode(arena, code, 0); + MD_String8List actual_strings = MD_DebugStringListFromNode(arena, parse.node, 0, MD_S8Lit(" "), MD_GenerateFlags_Tree); + MD_String8 actual = MD_S8ListJoin(arena, actual_strings, 0); + TestResult(MD_S8Match(expected, actual, 0)); + } + { + MD_String8 code = MD_S8Lit("// foo\na"); + MD_String8 expected = MD_S8Lit("/*\n foo\n*/\na"); + MD_ParseResult parse = MD_ParseOneNode(arena, code, 0); + MD_String8List actual_strings = MD_DebugStringListFromNode(arena, parse.node, 0, MD_S8Lit(" "), MD_GenerateFlags_Tree|MD_GenerateFlag_Comments); + MD_String8 actual = MD_S8ListJoin(arena, actual_strings, 0); + TestResult(MD_S8Match(expected, actual, 0)); + } + } + return 0; }