/* * Mr. 4th Dimention - Allen Webster * * 25.02.2016 * * Document data structure and generator for 4coder documentation. * */ // TOP internal char* get_null_terminated_version(String str){ char *ptr = 0; if (str.size > 0){ if (terminate_with_null(&str)){ ptr = str.str; } else{ String b = str_alloc(str.size + 1); copy(&b, str); terminate_with_null(&b); ptr = b.str; } } return(ptr); } //////////////////////////////// struct Enriched_Text{ String fname; String source; }; internal Enriched_Text load_enriched_text(char *directory, char *filename){ Enriched_Text result = {0}; char *fname = fm_str(directory, "/", filename); result.fname = str_alloc(str_size(fname) + 1); fm_align(); copy(&result.fname, fname); terminate_with_null(&result.fname); result.source = file_dump(fname); return(result); } //////////////////////////////// typedef u32 Mangle_Rule; enum{ MangleRule_None, MangleRule_MacroSig, MangleRule_ToLower, }; internal Mangle_Rule get_mangle_rule(String mangle){ Mangle_Rule result = MangleRule_None; if (match(mangle, "macro sig")){ result = MangleRule_MacroSig; } else if (match(mangle, "to lower")){ result = MangleRule_ToLower; } return(result); } internal String apply_mangle_rule(String name, u32 mangle_rule){ String result = {0}; switch (mangle_rule){ case MangleRule_MacroSig: { result = str_alloc(name.size + 5); fm_align(); copy(&result, name); to_upper(&result); append(&result, "_SIG"); terminate_with_null(&result); }break; case MangleRule_ToLower: { result = str_alloc(name.size + 1); fm_align(); copy(&result, name); to_lower(&result); terminate_with_null(&result); }break; default: { result = name; }break; } return(result); } //////////////////////////////// enum{ Doc_Root, Doc_Section, Doc_Error, Doc_Todo, Doc_Include, Doc_DocList, Doc_DocFull, Doc_TableOfContents, Doc_PlainOldText, Doc_Version, Doc_Style, Doc_DocumentLink, Doc_Link, Doc_Image, Doc_Video, Doc_BeginParagraph, Doc_EndParagraph, Doc_List, Doc_Item, // Doc_COUNT, }; struct Document_Item{ Document_Item *next; Document_Item *parent; i32 type; struct{ Document_Item *first_child; Document_Item *last_child; String name; String id; b32 show_title; } section; union{ struct{ String unit; u32 mangle_rule; } unit_elements; struct{ String string; String string2; } string; struct{ String name; struct Abstract_Item *document; } include; }; }; global Document_Item null_document_item = {0}; //////////////////////////////// internal void set_item_string(String *out, String text){ *out = str_alloc(text.size + 1); fm_align(); copy(out, text); terminate_with_null(out); } //////////////////////////////// struct Basic_Node{ Basic_Node *next; }; #define NodeGetData(node, T) ((T*) ((node)+1)) struct Basic_List{ Basic_Node *head; Basic_Node *tail; u32 count; }; internal void clear_list(Basic_List *list){ memset(list, 0, sizeof(*list)); } internal void* push_item_on_list(Basic_List *list, i32 item_size){ i32 mem_size = item_size + sizeof(Basic_Node); void *mem = fm__push(mem_size); Assert(mem != 0); memset(mem, 0, mem_size); Basic_Node *node = (Basic_Node*)mem; if (list->head == 0){ list->head = node; list->tail = node; } else{ list->tail->next = node; list->tail = node; } ++list->count; void *result = (node + 1); return(result); } //////////////////////////////// enum{ ItemType_Document, ItemType_Image, ItemType_GenericFile, ItemType_MetaUnit, // ItemType_COUNT, }; struct Abstract_Item{ i32 item_type; char *name; // Document value members Document_Item *root_item; // Image value members char *source_file; char *extension; float w_h_ratio; float h_w_ratio; Basic_List img_instantiations; // Meta parse members Meta_Unit *unit; }; global Abstract_Item null_abstract_item = {0}; internal Abstract_Item* get_item_by_name(Basic_List list, String name){ Abstract_Item *result = 0; for (Basic_Node *node = list.head; node != 0; node = node->next){ Abstract_Item *item = NodeGetData(node, Abstract_Item); if (match(item->name, name)){ result = item; break; } } return(result); } internal Abstract_Item* create_abstract_item(Basic_List *list, char *name){ Abstract_Item *result = 0; Abstract_Item *lookup = get_item_by_name(*list, make_string_slowly(name)); if (lookup == 0){ result = (Abstract_Item*)push_item_on_list(list, sizeof(*result)); } return(result); } struct Abstract_Item_Array{ Abstract_Item **items; u32 count; }; internal Abstract_Item_Array get_abstract_item_array(Basic_List *list){ Abstract_Item_Array result = {0}; result.items = (Abstract_Item**)fm_push_array(Abstract_Item*, list->count); result.count = list->count; u32 i = 0; for (Basic_Node *node = list->head; node != 0; node = node->next){ result.items[i++] = NodeGetData(node, Abstract_Item); } return(result); } //////////////////////////////// struct Document_System{ char *code_dir; char *asset_dir; char *src_dir; Basic_List doc_list; Basic_List img_list; Basic_List file_list; Basic_List meta_list; Basic_List unresolved_includes; }; internal Document_System create_document_system(char *code_dir, char *asset_dir, char *src_dir){ Document_System system = {0}; system.code_dir = code_dir; system.asset_dir = asset_dir; system.src_dir = src_dir; return(system); } internal void create_unresolved_include(Document_System *doc_system, Document_Item *include_item){ Document_Item **new_item = (Document_Item**)push_item_on_list(&doc_system->unresolved_includes, sizeof(*new_item)); *new_item = include_item; } //////////////////////////////// enum{ MetaResult_DidParse, MetaResult_AlreadyExists, MetaResult_FailedToParse, }; internal u32 create_meta_unit(Document_System *doc_system, String name_str, String file_str){ u32 result = MetaResult_DidParse; char *name = get_null_terminated_version(name_str); char *file = get_null_terminated_version(file_str); Abstract_Item *item = create_abstract_item(&doc_system->meta_list, name); if (item != 0){ Meta_Unit *unit = fm_push_array(Meta_Unit, 1); *unit = compile_meta_unit(doc_system->code_dir, file, ExpandArray(meta_keywords)); if (unit->count != 0){ result = true; item->item_type = ItemType_MetaUnit; item->name = name; item->unit = unit; } else{ result = MetaResult_FailedToParse; } } else{ result = MetaResult_AlreadyExists; } return(result); } internal Abstract_Item* add_generic_file(Document_System *system, char *source_file, char *extension, char *name){ Abstract_Item *item = create_abstract_item(&system->file_list, name); if (item){ char *full_file = fm_str(system->asset_dir, "/", source_file); item->item_type = ItemType_GenericFile; item->extension = extension; item->source_file = full_file; item->name = name; } return(item); } internal Abstract_Item* add_image_description(Document_System *system, char *source_file, char *extension, char *name){ Abstract_Item *item = create_abstract_item(&system->img_list, name); if (item != 0){ char *full_file = fm_str(system->asset_dir, "/", source_file); item->item_type = ItemType_Image; item->name = name; item->extension = extension; item->source_file = full_file; i32 w = 0, h = 0, comp = 0; i32 stbi_r = stbi_info(full_file, &w, &h, &comp); if (!stbi_r){ fprintf(stdout, "Did not find file %s\n", full_file); item->w_h_ratio = 1.f; item->h_w_ratio = 1.f; } else{ item->w_h_ratio = ((float)w/(float)h); item->h_w_ratio = ((float)h/(float)w); } } return(item); } //////////////////////////////// struct Image_Instantiation{ i32 w, h; }; internal Image_Instantiation* get_image_instantiation(Basic_List list, i32 w, i32 h){ Image_Instantiation *result = 0; for (Basic_Node *node = list.head; node != 0; node = node->next){ Image_Instantiation *instantiation = NodeGetData(node, Image_Instantiation); if (instantiation->w == w && instantiation->h == h){ result = instantiation; break; } } return(result); } internal void add_image_instantiation(Basic_List *list, i32 w, i32 h){ Image_Instantiation *instantiation = (Image_Instantiation*)push_item_on_list(list, sizeof(*instantiation)); instantiation->w = w; instantiation->h = h; } //////////////////////////////// struct Document_Builder{ Abstract_Item *doc; Document_Item *item_stack[512]; i32 item_top; }; internal Document_Builder begin_document_description(Document_System *system, char *title, char *name, b32 show_title){ Document_Builder builder = {0}; Abstract_Item *doc = create_abstract_item(&system->doc_list, name); if (doc != 0){ builder.doc = doc; *doc = null_abstract_item; doc->item_type = ItemType_Document; doc->name = name; doc->root_item = fm_push_array(Document_Item, 1); *doc->root_item = null_document_item; Document_Item *item = doc->root_item; set_item_string(&item->section.name, make_string_slowly(name)); item->section.show_title = show_title; item->type = Doc_Root; builder.item_stack[builder.item_top] = doc->root_item; } return(builder); } internal void append_child(Document_Item *parent, Document_Item *item){ if (parent->section.last_child == 0){ parent->section.first_child = item; } else{ parent->section.last_child->next = item; } parent->section.last_child = item; item->parent = parent; } #define PUSH true internal void doc_push(Document_Builder *builder, Document_Item *item){ Assert(builder->item_top + 1 < ArrayCount(builder->item_stack)); builder->item_stack[++builder->item_top] = item; } internal Document_Item* doc_get_item_top(Document_Builder *builder){ Assert(builder->item_top < ArrayCount(builder->item_stack)); Document_Item *parent = builder->item_stack[builder->item_top]; return(parent); } internal Document_Item* doc_new_item(Document_Builder *builder, u32 type, b32 push = false){ Document_Item *parent = doc_get_item_top(builder); Document_Item *item = fm_push_array(Document_Item, 1); *item = null_document_item; item->type = type; append_child(parent, item); if (push){ doc_push(builder, item); } return(item); } internal Document_Item* doc_new_item_strings(Document_Builder *builder, u32 type, String s1, String s2, b32 push = false){ Document_Item *item = doc_new_item(builder, type); if (s1.size > 0){ set_item_string(&item->string.string, s1); } if (s2.size > 0){ set_item_string(&item->string.string2, s2); } if (push){ doc_push(builder, item); } return(item); } internal Document_Item* doc_new_item_documentation(Document_Builder *builder, u32 type, String unit, Mangle_Rule mangle_rule, b32 push = false){ Document_Item *item = doc_new_item(builder, type); set_item_string(&item->unit_elements.unit, unit); item->unit_elements.mangle_rule = mangle_rule; if (push){ doc_push(builder, item); } return(item); } internal void begin_section(Document_Builder *builder, char *title, char *id){ Document_Item *item = doc_new_item(builder, Doc_Section, PUSH); set_item_string(&item->section.name, make_string_slowly(title)); item->section.show_title = true; if (id != 0){ set_item_string(&item->section.id, make_lit_string(id)); } } #define doc_end(b) doc_pop(b) #define begin_style(b,t) doc_new_item_strings(b, Doc_Style, t, null_string, PUSH) #define begin_link(b,t) doc_new_item_strings(b, Doc_Link, t, null_string, PUSH); #define begin_list(b) doc_new_item(b, Doc_List, PUSH) #define begin_item(b) doc_new_item(b, Doc_Item, PUSH) internal void add_include(Document_System *doc_system, Document_Builder *builder, String text){ Document_Item *item = doc_new_item(builder, Doc_Include); set_item_string(&item->include.name, text); create_unresolved_include(doc_system, item); } #define add_error(b,t) doc_new_item_strings(b, Doc_Error, t, null_string) #define add_todo(b) doc_new_item(b, Doc_Todo) #define add_doc_list(b,u,m) doc_new_item_documentation(b, Doc_DocList, u, m) #define add_doc_full(b,u,m) doc_new_item_documentation(b, Doc_DocFull, u, m) #define add_table_of_contents(b) doc_new_item(b, Doc_TableOfContents) #define add_plain_old_text(b,t) doc_new_item_strings(b, Doc_PlainOldText, t, null_string); #define add_version(b) doc_new_item(b, Doc_Version) #define add_document_link(b,t) doc_new_item_strings(b, Doc_DocumentLink, t, null_string) #define add_image(b,t,e) doc_new_item_strings(b, Doc_Image, t, e) #define add_video(b,t) doc_new_item_strings(b, Doc_Video, t, null_string) #define add_begin_paragraph(b) doc_new_item(b, Doc_BeginParagraph) #define add_end_paragraph(b) doc_new_item(b, Doc_EndParagraph) internal void doc_pop(Document_Builder *builder){ if (builder->item_top > 0){ --builder->item_top; } else{ add_error(builder, make_lit_string("unbalanced groups -- extra end")); } } internal void end_document_description(Document_Builder *builder){ b32 closing_error = (builder->item_top != 0); if (closing_error){ add_error(builder, make_lit_string("unbalanced groups -- extra begin")); } for (;builder->item_top > 0;){ doc_end(builder); } } //////////////////////////////// internal void report_error_missing_body(Document_Builder *builder, String command_body){ char space[512]; String error_string = make_fixed_width_string(space); append(&error_string, "missing body for "); append(&error_string, command_body); add_error(builder, error_string); } //////////////////////////////// enum Command_Types{ Cmd_BackSlash, Cmd_End, Cmd_Section, Cmd_Style, Cmd_List, Cmd_Item, Cmd_Link, Cmd_DocumentLink, Cmd_Image, Cmd_Video, Cmd_Version, Cmd_TableOfContents, Cmd_Todo, Cmd_Include, Cmd_MetaParse, Cmd_DocList, Cmd_DocFull, // never below this Cmd_COUNT, }; global b32 did_enriched_commands = false; global String enriched_commands_global_array[Cmd_COUNT]; internal String* get_enriched_commands(){ if (!did_enriched_commands){ did_enriched_commands = true; enriched_commands_global_array[Cmd_BackSlash] = make_lit_string("\\"); enriched_commands_global_array[Cmd_End] = make_lit_string("END"); enriched_commands_global_array[Cmd_Section] = make_lit_string("SECTION"); enriched_commands_global_array[Cmd_Style] = make_lit_string("STYLE"); enriched_commands_global_array[Cmd_List] = make_lit_string("LIST"); enriched_commands_global_array[Cmd_Item] = make_lit_string("ITEM"); enriched_commands_global_array[Cmd_Link] = make_lit_string("LINK"); enriched_commands_global_array[Cmd_DocumentLink] = make_lit_string("DOC_LINK"); enriched_commands_global_array[Cmd_Image] = make_lit_string("IMAGE"); enriched_commands_global_array[Cmd_Video] = make_lit_string("VIDEO"); enriched_commands_global_array[Cmd_Version] = make_lit_string("VERSION"); enriched_commands_global_array[Cmd_TableOfContents] = make_lit_string("TABLE_OF_CONTENTS"); enriched_commands_global_array[Cmd_Todo] = make_lit_string("TODO"); enriched_commands_global_array[Cmd_Include] = make_lit_string("INCLUDE"); enriched_commands_global_array[Cmd_MetaParse] = make_lit_string("META_PARSE"); enriched_commands_global_array[Cmd_DocList] = make_lit_string("DOC_LIST"); enriched_commands_global_array[Cmd_DocFull] = make_lit_string("DOC_FULL"); } return(enriched_commands_global_array); } internal u32 get_enriched_commands_count(){ return(ArrayCount(enriched_commands_global_array)); } internal b32 extract_command_body(String l, i32 *i_in_out, String *body_text_out){ b32 has_body = false; i32 i = *i_in_out; for (; i < l.size; ++i){ if (!char_is_whitespace(l.str[i])){ break; } } if (l.str[i] == '{'){ i32 body_start = i + 1; i32 body_end = 0; for (++i; i < l.size; ++i){ if (l.str[i] == '}'){ has_body = true; body_end = i; ++i; break; } } if (has_body){ *i_in_out = i; String body_text = substr(l, body_start, body_end - body_start); *body_text_out = skip_chop_whitespace(body_text); } } return(has_body); } internal Abstract_Item* make_document_from_text(Document_System *doc_system, char *title, char *name, Enriched_Text *text){ String source = text->source; Document_Builder builder = begin_document_description(doc_system, title, name, false); if (source.str == 0){ char space[512]; String str = make_fixed_width_string(space); copy(&str, "could not open source file "); copy(&str, text->fname); add_error(&builder, str); } else{ for (String line = get_first_double_line(source); line.str; line = get_next_double_line(source, line)){ String l = skip_chop_whitespace(line); if (l.size == 0) continue; add_begin_paragraph(&builder); i32 start = 0, i = 0; for (; i < l.size; ++i){ char ch = l.str[i]; if (ch == '\\'){ add_plain_old_text(&builder, substr(l, start, i - start)); i32 command_start = i + 1; i32 command_end = command_start; for (; command_end < l.size; ++command_end){ if (!char_is_alpha_numeric(l.str[command_end])){ break; } } if (command_end == command_start){ if (command_end < l.size && l.str[command_end] == '\\'){ ++command_end; } } String command_string = substr(l, command_start, command_end - command_start); String *enriched_commands = get_enriched_commands(); u32 enriched_commands_count = get_enriched_commands_count(); i = command_end; i32 match_index = 0; if (!string_set_match(enriched_commands, enriched_commands_count, command_string, &match_index)){ match_index = -1; } switch (match_index){ case Cmd_BackSlash: { add_plain_old_text(&builder, make_lit_string("\\")); }break; case Cmd_End: { for (Document_Item *top = doc_get_item_top(&builder); top->type == Doc_Item; top = doc_get_item_top(&builder)){ doc_end(&builder); } doc_end(&builder); }break; case Cmd_Section: { String body_text = {0}; if (extract_command_body(l, &i, &body_text)){ String extra_text = {0}; extract_command_body(l, &i, &extra_text); char *title = get_null_terminated_version(body_text); char *id = get_null_terminated_version(extra_text); begin_section(&builder, title, id); } else{ report_error_missing_body(&builder, command_string); } }break; case Cmd_Style: { String body_text = {0}; if (extract_command_body(l, &i, &body_text)){ begin_style(&builder, body_text); } else{ report_error_missing_body(&builder, command_string); } }break; case Cmd_List: { begin_list(&builder); }break; case Cmd_Item: { Document_Item *top = doc_get_item_top(&builder); if (top->type == Doc_Item){ doc_end(&builder); } begin_item(&builder); }break; case Cmd_Link: { String body_text = {0}; if (extract_command_body(l, &i, &body_text)){ begin_link(&builder, body_text); } else{ report_error_missing_body(&builder, command_string); } }break; // TODO(allen): upgrade this bs case Cmd_DocumentLink: { String body_text = {0}; if (extract_command_body(l, &i, &body_text)){ add_document_link(&builder, body_text); } else{ report_error_missing_body(&builder, command_string); } }break; case Cmd_Image: { String body_text = {0}; if (extract_command_body(l, &i, &body_text)){ String size_parameter = {0}; extract_command_body(l, &i, &size_parameter); add_image(&builder, body_text, size_parameter); } else{ report_error_missing_body(&builder, command_string); } }break; case Cmd_Video: { String body_text = {0}; if (extract_command_body(l, &i, &body_text)){ add_video(&builder, body_text); } else{ report_error_missing_body(&builder, command_string); } }break; case Cmd_Version: { add_version(&builder); }break; case Cmd_TableOfContents: { add_table_of_contents(&builder); }break; case Cmd_Todo: { add_todo(&builder); }break; case Cmd_Include: { String body_text = {0}; if (extract_command_body(l, &i, &body_text)){ add_include(doc_system, &builder, body_text); } else{ report_error_missing_body(&builder, command_string); } }break; case Cmd_MetaParse: { String name = {0}; String file = {0}; if (extract_command_body(l, &i, &name)){ if (extract_command_body(l, &i, &file)){ u32 result = create_meta_unit(doc_system, name, file); if (result == MetaResult_FailedToParse){ char space[512]; String str = make_fixed_width_string(space); append(&str, "parse failed for "); append(&str, file); add_error(&builder, str); } } else{ report_error_missing_body(&builder, command_string); } } else{ report_error_missing_body(&builder, command_string); } }break; case Cmd_DocList: case Cmd_DocFull: { String name = {0}; if (extract_command_body(l, &i, &name)){ String mangle = {0}; extract_command_body(l, &i, &mangle); u32 mangle_rule = MangleRule_None; if (match_part(mangle, "mangle:")){ String mangle_name = substr_tail(mangle, sizeof("mangle:")-1); mangle_name = skip_chop_whitespace(mangle_name); mangle_rule = get_mangle_rule(mangle_name); } if (match_index == Cmd_DocList){ add_doc_list(&builder, name, mangle_rule); } else{ add_doc_full(&builder, name, mangle_rule); } } else{ report_error_missing_body(&builder, command_string); } }break; default: { char space[512]; String error = make_fixed_width_string(space); append(&error, "unrecognized command "); append(&error, command_string); add_error(&builder, error); }break; } start = i; } } if (start != i){ add_plain_old_text(&builder, substr(l, start, i - start)); } add_end_paragraph(&builder); } } end_document_description(&builder); return(builder.doc); } //////////////////////////////// struct Unresolved_Include_Array{ Document_Item **items; u32 count; }; internal Unresolved_Include_Array get_unresolved_includes(Document_System *doc_system){ Unresolved_Include_Array result = {0}; Basic_List *list = &doc_system->unresolved_includes; result.items = (Document_Item**)fm_push_array(Document_Item*, list->count); result.count = list->count; u32 i = 0; for (Basic_Node *node = list->head; node != 0; node = node->next){ result.items[i++] = *NodeGetData(node, Document_Item*); } return(result); } internal void resolve_all_includes(Document_System *doc_system){ for (;doc_system->unresolved_includes.count > 0;){ Unresolved_Include_Array includes = get_unresolved_includes(doc_system); clear_list(&doc_system->unresolved_includes); Document_Item **item_ptr = includes.items; for (u32 i = 0; i < includes.count; ++i, ++item_ptr){ Document_Item *item = *item_ptr; Assert(item->include.document == 0); Abstract_Item *inc_doc = get_item_by_name(doc_system->doc_list, item->include.name); if (inc_doc == 0){ String source_text = item->include.name; Enriched_Text *text = fm_push_array(Enriched_Text, 1); *text = load_enriched_text(doc_system->src_dir, source_text.str); inc_doc = make_document_from_text(doc_system, source_text.str, source_text.str, text); } item->include.document = inc_doc; } } } //////////////////////////////// // HTML Document Generation #define HTML_BACK_COLOR "#FAFAFA" #define HTML_TEXT_COLOR "#0D0D0D" #define HTML_CODE_BACK "#DFDFDF" #define HTML_EXAMPLE_BACK "#EFEFDF" #define HTML_POP_COLOR_1 "#309030" #define HTML_POP_BACK_1 "#E0FFD0" #define HTML_VISITED_LINK "#A0C050" #define HTML_POP_COLOR_2 "#005000" #define HTML_CODE_STYLE "font-family: \"Courier New\", Courier, monospace; text-align: left;" #define HTML_CODE_BLOCK_STYLE(back) \ "margin-top: 3mm; margin-bottom: 3mm; font-size: .95em; " \ "background: "back"; padding: 0.25em;" #define HTML_DESCRIPT_SECTION_STYLE HTML_CODE_BLOCK_STYLE(HTML_CODE_BACK) #define HTML_EXAMPLE_CODE_STYLE HTML_CODE_BLOCK_STYLE(HTML_EXAMPLE_BACK) #define HTML_DOC_HEAD_OPEN "
"); } internal void output_end_paragraph(String *out){ append(out, "
"); } internal void output_begin_list(String *out){ append(out,"