diff --git a/build.bat b/build.bat index 2462dab..646fc42 100644 --- a/build.bat +++ b/build.bat @@ -12,6 +12,7 @@ cl %compile_flags% ..\samples\static_site_generator\static_site_generator.c cl %compile_flags% ..\samples\output_parse\output_parse.c cl %compile_flags% ..\samples\c_code_generation.c cl %compile_flags% ..\samples\node_errors\node_errors.c +cl %compile_flags% ..\samples\namespaced_types\namespaced_types.c echo. echo ~~~ Build All Tests ~~~ cl %compile_flags% ..\tests\sanity_tests.c @@ -63,3 +64,12 @@ echo ~~~ Running Error Generation Sample ~~~ pushd build node_errors.exe %~dp0\samples\node_errors\node_errors.md popd + +echo. +echo ~~~ Running Namespace Type Versioning Sample ~~~ +pushd build +namespaced_types.exe ..\samples\namespaced_types\spec.md ..\samples\namespaced_types\generated\converter.c +cl %compile_flags% ..\samples\namespaced_types\conversion_test.c +conversion_test.exe +popd + diff --git a/build.sh b/build.sh index aa1f111..658fb4a 100755 --- a/build.sh +++ b/build.sh @@ -24,6 +24,7 @@ $CC $compile_flags ../samples/static_site_generator/static_site_generator.c -o s $CC $compile_flags ../samples/output_parse/output_parse.c -o output_parse $CC $compile_flags ../samples/c_code_generation.c -o c_code_generation $CC $compile_flags ../samples/node_errors/node_errors.c -o node_errors +$CC $compile_flags ../samples/namespaced_types/namespaced_types.c -o namespaced_types echo echo ~~~ Build All Tests ~~~ $CC $compile_flags ../tests/sanity_tests.c -o sanity_tests @@ -63,3 +64,12 @@ echo ~~~ Running Error Generation Sample ~~~ pushd build ./node_errors ../samples/node_errors/node_errors.md popd + +echo +echo ~~~ Running Namespace Type Versioning Sample ~~~ +pushd build +./namespaced_types ../samples/namespaced_types/spec.md ../samples/namespaced_types/generated/converter.c +$CC $compile_flags ../samples/namespaced_types/conversion_test.c -o conversion_test +./conversion_test +popd + diff --git a/build_with_clang.bat b/build_with_clang.bat index 155b415..c6d77cd 100644 --- a/build_with_clang.bat +++ b/build_with_clang.bat @@ -14,6 +14,7 @@ clang %compile_flags% ..\samples\static_site_generator\static_site_generator.c - clang %compile_flags% ..\samples\output_parse\output_parse.c -o output_parse.exe clang %compile_flags% ..\samples\c_code_generation.c -o c_code_generation.exe clang %compile_flags% ..\samples\node_errors\node_errors.c -o node_errors.exe +clang %compile_flags% ..\samples\namespaced_types\namespaced_types.c -o namespaced_types.exe echo. echo ~~~ Build All Tests ~~~ clang %compile_flags% ..\tests\sanity_tests.c -o sanity_tests.exe @@ -65,3 +66,13 @@ echo ~~~ Running Error Generation Sample ~~~ pushd build node_errors.exe %~dp0\samples\node_errors\node_errors.md popd + +echo. +echo ~~~ Running Namespace Type Versioning Sample ~~~ +pushd build +namespaced_types.exe ..\samples\namespaced_types\spec.md ..\samples\namespaced_types\generated\converter.c +clang %compile_flags% ..\samples\namespaced_types\conversion_test.c -o conversion_test.exe +conversion_test.exe +popd + + diff --git a/samples/namespaced_types/conversion_test.c b/samples/namespaced_types/conversion_test.c new file mode 100644 index 0000000..2911e4e --- /dev/null +++ b/samples/namespaced_types/conversion_test.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include "generated/converter.c" + +int main(void) +{ + v1_Entry v1_entry = {0}; + + v1_entry.to_remove = 12; + + v1_entry.kind = v1_EntryKind_C; + + v1_entry.flags = v1_EntryFlag_B | v1_EntryFlag_C; + + v1_entry.color.components.r = 255; + v1_entry.color.components.g = 128; + v1_entry.color.components.b = 1; + v1_entry.color.components.a = 42; + + v1_entry.p.x = 2; + v1_entry.p.y = 3; + + Entry entry = EntryFromV1(v1_entry); + + assert(entry.kind == EntryKind_C); + assert(entry.flags == (EntryFlag_B | EntryFlag_C)); + assert(entry.color.components.r == 255); + assert(entry.color.components.g == 128); + assert(entry.color.components.b == 1); + assert(entry.color.components.a == 42); + assert(v1_entry.p.x == 2); + assert(v1_entry.p.y == 3); + + printf("Conversion test success!\n"); + + return 0; +} diff --git a/samples/namespaced_types/namespaced_types.c b/samples/namespaced_types/namespaced_types.c new file mode 100644 index 0000000..b48b9f1 --- /dev/null +++ b/samples/namespaced_types/namespaced_types.c @@ -0,0 +1,370 @@ +#include "md.h" +#include "md.c" + +static MD_Map MapFromChildren(MD_Node *node) +{ + MD_Map result = {0}; + for(MD_EachNodeRef(child, node->first_child)) + { + MD_StringMap_Insert(&result, MD_MapCollisionRule_Chain, child->string, child); + } + return result; +} + +static void OutputType_C_LHS_Namespace(FILE *file, MD_Map *user_defined_types, MD_String8 prefix, MD_Expr *type) +{ + switch (type->kind) + { + case MD_ExprKind_Atom: + { + MD_Node *node = type->node; + + if(MD_StringMap_Lookup(user_defined_types, type->node->string)) + { + fprintf(file, "%.*s", MD_StringExpand(prefix)); + } + fprintf(file, "%.*s", MD_StringExpand(node->whole_string)); + }break; + + case MD_ExprKind_Pointer: + { + OutputType_C_LHS_Namespace(file, user_defined_types, prefix, type->sub[0]); + if (_MD_OutputType_C_NeedsParens(type)) + { + fprintf(file, "("); + } + fprintf(file, "*"); + }break; + + case MD_ExprKind_Array: + { + OutputType_C_LHS_Namespace(file, user_defined_types, prefix, type->sub[0]); + if (_MD_OutputType_C_NeedsParens(type)) + { + fprintf(file, "("); + } + }break; + + case MD_ExprKind_Volatile: { fprintf(file, "volatile "); }break; + case MD_ExprKind_Const: { fprintf(file, "const "); }break; + + default: + { + fprintf(file, "{ unexpected MD_ExprKind (%i) in type info for node \"%.*s\" }", + type->kind, + MD_StringExpand(type->node->whole_string)); + }break; + } +} + +static void OutputPrefixedType(FILE *f, MD_Map *user_defined_types, MD_String8 prefix, MD_Node *node, MD_u32 depth) +{ +#define I() fprintf(f, "%*s", 4*depth, ""); + if(MD_NodeHasTag(node, MD_S8Lit("enum"))) + { + I(); fprintf(f, "typedef enum\n"); + I(); fprintf(f, "{\n"); + for(MD_EachNode(enumerand, node->first_child)) + { + I(); fprintf(f, " %.*s%.*s_%.*s,\n", MD_StringExpand(prefix), + MD_StringExpand(node->string), MD_StringExpand(enumerand->string)); + } + I(); fprintf(f, "} %.*s%.*s;\n\n", MD_StringExpand(prefix), MD_StringExpand(node->string)); + } + else if(MD_NodeHasTag(node, MD_S8Lit("flags"))) + { + I(); fprintf(f, "typedef enum\n"); + I(); fprintf(f, "{\n"); + + MD_String8 singular_flag_type_name = node->string; + if(MD_StringMatch(MD_StringSuffix(singular_flag_type_name, 5), MD_S8Lit("Flags"), + MD_StringMatchFlag_CaseInsensitive)) + { + singular_flag_type_name = MD_StringChop(singular_flag_type_name, 1); + } + MD_u32 v = 0; + for(MD_EachNode(enumerand, node->first_child)) + { + I(); fprintf(f, " %.*s%.*s_%.*s = (1<<%d),\n", MD_StringExpand(prefix), + MD_StringExpand(singular_flag_type_name), MD_StringExpand(enumerand->string), v); + v += 1; + } + I(); fprintf(f, "} %.*s%.*s;\n\n", MD_StringExpand(prefix), MD_StringExpand(node->string)); + } + else if(MD_NodeHasTag(node, MD_S8Lit("union")) || MD_NodeHasTag(node, MD_S8Lit("struct"))) + { + MD_String8 aggregate_kind = MD_NodeHasTag(node, MD_S8Lit("union")) ? MD_S8Lit("union") : MD_S8Lit("struct"); + + if(depth == 0) + { + fprintf(f, "typedef %.*s %.*s%.*s %.*s%.*s;\n", + MD_StringExpand(aggregate_kind), + MD_StringExpand(prefix), MD_StringExpand(node->string), + MD_StringExpand(prefix), MD_StringExpand(node->string)); + } + + if(depth == 0) + { + I(); fprintf(f, "%.*s %.*s%.*s\n", MD_StringExpand(aggregate_kind), + MD_StringExpand(prefix), MD_StringExpand(node->string)); + } + else + { + I(); fprintf(f, "%.*s\n", MD_StringExpand(aggregate_kind)); + } + + I(); fprintf(f, "{\n"); + for(MD_EachNode(member, node->first_child)) + { + OutputPrefixedType(f, user_defined_types, prefix, member, depth+1); + } + if(depth == 0) + { + I(); fprintf(f, "};\n\n"); + } + else + { + I(); fprintf(f, "} %.*s;\n", MD_StringExpand(node->string)); + } + } + else + { + MD_Expr *type = MD_ParseAsType(node->first_child, node->last_child); + + I(); + OutputType_C_LHS_Namespace(f, user_defined_types, prefix, type); + fprintf(f, " %.*s", MD_StringExpand(node->string)); + MD_OutputType_C_RHS(f, type); + + fprintf(f, ";\n"); + } +#undef I +} + +static void AppendConversionCode(FILE *f, MD_Map *user_defined_types, MD_Node *new_element, MD_Node *old_element, MD_String8 member_path) +{ + MD_Map new_element_map = MapFromChildren(new_element); + + if(MD_NodeHasTag(new_element, MD_S8Lit("struct"))) + { + MD_String8 extended_member_path = MD_PushStringF("%.*s.%.*s", MD_StringExpand(member_path), + MD_StringExpand(old_element->string)); + + for(MD_EachNode(member, old_element->first_child)) + { + MD_MapSlot *slot = MD_StringMap_Lookup(&new_element_map, member->string); + if(slot) + { + AppendConversionCode(f, user_defined_types, member, slot->value, extended_member_path); + } + else + { + fprintf(f, " // TODO: What to do with %.*s\n", MD_StringExpand(old_element->string)); + } + } + } + else if(MD_NodeIsNil(new_element->first_tag)) + { + if(MD_ChildCountFromNode(new_element) == 1) + { + MD_String8 type_name = new_element->first_child->string; + if(MD_NodeDeepMatch(new_element, old_element, 0, 0)) + { + MD_String8 extended_member_path = MD_PushStringF("%.*s.%.*s", MD_StringExpand(member_path), + MD_StringExpand(old_element->string)); + + if(MD_StringMap_Lookup(user_defined_types, type_name)) + { + fprintf(f, " result%.*s = %.*sFromV1(v%.*s);\n", + MD_StringExpand(extended_member_path), MD_StringExpand(type_name), + MD_StringExpand(extended_member_path)); + } + else + { // NOTE(mal): Assumes unknown types are base types + fprintf(f, " result%.*s = v%.*s;\n", + MD_StringExpand(extended_member_path), MD_StringExpand(extended_member_path)); + } + } + else + { + fprintf(f, " // TODO: What to do with %.*s %.*s\n", + MD_StringExpand(old_element->string), MD_StringExpand(type_name)); + } + } + else + { + fprintf(f, " // TODO: What to do with %.*s\n", MD_StringExpand(old_element->string)); + } + } + else + { + fprintf(stderr, "Missing code to process tag @%.*s\n", MD_StringExpand(new_element->first_tag->string)); + exit(1); + } +} + +int main(int argument_count, char **arguments) +{ + if(argument_count != 3) + { + fprintf(stderr, "USAGE: %s spec_file_name.md out_file_name.c\n", arguments[0]); + return -1; + } + + MD_ParseResult spec = MD_ParseWholeFile(MD_S8CString(arguments[1])); + if(spec.first_error) + { + for(MD_Error *error = spec.first_error; error; error = error->next) + { + MD_NodeMessage(error->node, error->kind, error->string); + } + return -1; + } + + FILE *f = fopen(arguments[2], "wb"); + if(f == 0) + { + fprintf(stderr, "Unable to open destination file \"%s\"\n", arguments[2]); + return -1; + } + + MD_Map ns_map = MapFromChildren(spec.namespaces); + + // NOTE(mal): Old types get "v1_" as a prefix + fprintf(f, "// V1\n"); + MD_Node *v1 = MD_StringMap_Lookup(&ns_map, MD_S8Lit("v1"))->value; + MD_Map v1_map = MapFromChildren(v1); + for(MD_EachNodeRef(node, v1->first_child)) + { + OutputPrefixedType(f, &v1_map, MD_S8Lit("v1_"), node, 0); + } + + // NOTE(mal): New types don't get a prefix + fprintf(f, "// V2\n"); + MD_Map empty_map = {0}; + + MD_Node *v2 = MD_StringMap_Lookup(&ns_map, MD_S8Lit("v2"))->value; + for(MD_EachNodeRef(node, v2->first_child)) + { + OutputPrefixedType(f, &empty_map, MD_S8Lit(""), node, 0); + } + + // NOTE(mal): Routines that map old into new + fprintf(f, "// V1->V2\n"); + MD_Map v2_map = MapFromChildren(v2); + for(MD_EachNodeRef(node, v1->first_child)) + { + MD_MapSlot *slot = MD_StringMap_Lookup(&v2_map, node->string); + MD_Node *v2_type = slot->value; + MD_Map children_map = MapFromChildren(v2_type); + + fprintf(f, "static %.*s %.*sFromV1(v1_%.*s v)\n{\n", + MD_StringExpand(node->string), MD_StringExpand(node->string), MD_StringExpand(node->string)); + + if(MD_NodeHasTag(node, MD_S8Lit("enum"))) + { + fprintf(f, " %.*s result = 0;\n", MD_StringExpand(node->string)); + fprintf(f, " switch(v)\n {\n"); + + for(MD_EachNode(enumerand, node->first_child)) + { + fprintf(f, " case v1_%.*s_%.*s: ", + MD_StringExpand(node->string), MD_StringExpand(enumerand->string)); + MD_MapSlot *slot = MD_StringMap_Lookup(&children_map, enumerand->string); + if(slot) + { + MD_Node *v2_enumerand = slot->value; + fprintf(f, "result = %.*s_%.*s; break;\n", + MD_StringExpand(node->string), MD_StringExpand(v2_enumerand->string)); + } + else + { + fprintf(f, "assert(!\"Enumerand v1_%.*s_%.*s is no longer allowed\\n\");\n", + MD_StringExpand(node->string), MD_StringExpand(enumerand->string)); + } + } + fprintf(f, " default: assert(!\"Illegal value for enum v1_%.*s\\n\"); break;\n", MD_StringExpand(node->string)); + fprintf(f, " }\n"); + } + else if(MD_NodeHasTag(node, MD_S8Lit("flags"))) + { + fprintf(f, " %.*s result = 0;\n", MD_StringExpand(node->string)); + + MD_String8 singular_flag_type_name = node->string; + if(MD_StringMatch(MD_StringSuffix(singular_flag_type_name, 5), MD_S8Lit("Flags"), + MD_StringMatchFlag_CaseInsensitive)) + { + singular_flag_type_name = MD_StringChop(singular_flag_type_name, 1); + } + + for(MD_EachNode(flag, node->first_child)) + { + fprintf(f, " if(v & v1_%.*s_%.*s) ", + MD_StringExpand(singular_flag_type_name), MD_StringExpand(flag->string)); + MD_MapSlot *slot = MD_StringMap_Lookup(&children_map, flag->string); + if(slot) + { + fprintf(f, "result |= %.*s_%.*s;\n", + MD_StringExpand(singular_flag_type_name), MD_StringExpand(flag->string)); + } + else + { + fprintf(f, "assert(!\"Flag v1_%.*s is no longer allowed\\n\");\n", MD_StringExpand(flag->string)); + } + } + } + else if(MD_NodeHasTag(node, MD_S8Lit("union"))) + { + fprintf(f, " %.*s result = {0};\n", MD_StringExpand(node->string)); + + MD_Node *authoritative_member = v2_type->first_child; + for(MD_EachNode(member, v2_type->first_child)) + { + if(MD_NodeHasTag(member, MD_S8Lit("authoritative"))) + { + authoritative_member = member; + break; + } + } + + MD_Map v1_children_map = MapFromChildren(node); + MD_MapSlot *v1_member_slot = MD_StringMap_Lookup(&v1_children_map, authoritative_member->string); + if(v1_member_slot) + { + AppendConversionCode(f, &v2_map, authoritative_member, v1_member_slot->value, MD_S8Lit("")); + } + else + { + fprintf(stderr, "Can't generate conversion for union %.*s", MD_StringExpand(node->string)); + } + } + else if(MD_NodeHasTag(node, MD_S8Lit("struct"))) + { + fprintf(f, " %.*s result = {0};\n", MD_StringExpand(node->string)); + MD_Map v1_children_map = MapFromChildren(node); + for(MD_EachNode(member, v2_type->first_child)) + { + MD_MapSlot *v1_member_slot = MD_StringMap_Lookup(&v1_children_map, member->string); + if(v1_member_slot) + { + AppendConversionCode(f, &v2_map, member, v1_member_slot->value, MD_S8Lit("")); + } + else + { + fprintf(stderr, "Can't generate conversion for struct member %.*s", MD_StringExpand(node->string)); + } + } + } + else + { + fprintf(stderr, "Missing code to process tag @%.*s\n", MD_StringExpand(node->first_tag->string)); + return -1; + } + fprintf(f, " return result;\n"); + fprintf(f, "}\n\n"); + fflush(f); + } + + fclose(f); + return 0; +} diff --git a/samples/namespaced_types/spec.md b/samples/namespaced_types/spec.md new file mode 100644 index 0000000..98078f9 --- /dev/null +++ b/samples/namespaced_types/spec.md @@ -0,0 +1,52 @@ +#namespace v1 +@enum EntryKind : { A, B, C, E, } +@flags EntryFlags : { A, B, C, } + +@union Color : { + @struct components : { + r : uint8_t; + g : uint8_t; + b : uint8_t; + a : uint8_t; + }; + raw : ([4]uint8_t); +} + +@struct Entry : { + to_remove : uint16_t; + kind : EntryKind; + flags : EntryFlags; + color : Color; + @struct p : { + x : float; + y : float; + } +} + +#namespace v2 +@enum EntryKind : { A, B, B2, C, } +@flags EntryFlags : { B, C, D, } + +@union Color : { + // NOTE(mal): In the general case, it's impossible to decide automatically which member should be used + // to port data across versions of a union. That's why I've introduced @authoritative here + @authoritative + @struct components : { + b : uint8_t; + g : uint8_t; + r : uint8_t; + a : uint8_t; + } + raw : ([4]uint8_t); +} + +@struct Entry : { + kind : EntryKind; + color : Color; + @struct p : { + x : float; + y : float; + z : float; + }; + flags : EntryFlags; +}