From aeaa8487c390100b03e8808f0baef6eab277e13d Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Tue, 1 Aug 2023 20:23:19 +0000 Subject: [PATCH] Add leaky Cytoplasm JSON -> Struct code generator. It is basically complete, I just have to finish cleaning up some of the memory leaks and remove the log messages. --- Cytoplasm/tools/j2s.c | 982 ++++++++++++++++++++++++++++++++++++++++++ TODO.txt | 4 + 2 files changed, 986 insertions(+) create mode 100644 Cytoplasm/tools/j2s.c diff --git a/Cytoplasm/tools/j2s.c b/Cytoplasm/tools/j2s.c new file mode 100644 index 0000000..d64ed9e --- /dev/null +++ b/Cytoplasm/tools/j2s.c @@ -0,0 +1,982 @@ +/* + * Copyright (C) 2022-2023 Jordan Bancino <@jordan:bancino.net> + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation files + * (the "Software"), to deal in the Software without restriction, + * including without limitation the rights to use, copy, modify, merge, + * publish, distribute, sublicense, and/or sell copies of the Software, + * and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_DEPENDENCIES 32 + +static JsonType +TypeToJsonType(char *type) +{ + if (StrEquals(type, "object")) + { + return JSON_OBJECT; + } + else if (StrEquals(type, "array")) + { + return JSON_ARRAY; + } + else if (StrEquals(type, "string")) + { + return JSON_STRING; + } + else if (StrEquals(type, "integer")) + { + return JSON_INTEGER; + } + else if (StrEquals(type, "float")) + { + return JSON_FLOAT; + } + else if (StrEquals(type, "boolean")) + { + return JSON_BOOLEAN; + } + else + { + if (*type == '[' && type[strlen(type) - 1] == ']') + { + return JSON_ARRAY; + } + else + { + return JSON_OBJECT; + } + } +} + +static char * +JsonTypeToStr(JsonType type) +{ + switch (type) + { + case JSON_OBJECT: + return "JSON_OBJECT"; + case JSON_ARRAY: + return "JSON_ARRAY"; + case JSON_STRING: + return "JSON_STRING"; + case JSON_INTEGER: + return "JSON_INTEGER"; + case JSON_FLOAT: + return "JSON_FLOAT"; + case JSON_BOOLEAN: + return "JSON_BOOLEAN"; + case JSON_NULL: + default: + return "JSON_NULL"; + } +} + +int +Main(Array * args) +{ + ArgParseState arg; + int opt; + int ret = EXIT_FAILURE; + + char *schema = NULL; + char *header = NULL; + char *impl = NULL; + + Stream *schemaFile = NULL; + Stream *headerFile = NULL; + Stream *implFile = NULL; + + HashMap *schemaJson = NULL; + + JsonValue *val; + char *type; + JsonValue *typeVal; + + HashMap *types; + + size_t i; + + Graph *dependencyGraph = GraphCreate(MAX_DEPENDENCIES); + HashMap *typeToNode = HashMapCreate(); + Array *requiredTypes = ArrayCreate(); + + Node *sortedNodes; + size_t sortedNodesLen; + Node *node; + + char *guard; + char *headerName; + + if (!requiredTypes) + { + Log(LOG_ERR, "Unable to allocate memory for type name storage"); + goto finish; + } + + Log(LOG_INFO, "j2s: Cytoplasm Json -> C Struct Converter"); + + ArgParseStateInit(&arg); + while ((opt = ArgParse(&arg, args, "s:h:c:")) != -1) + { + switch (opt) + { + case 's': + schema = arg.optArg; + break; + case 'h': + header = arg.optArg; + break; + case 'c': + impl = arg.optArg; + break; + default: + goto finish; + } + } + + if (!schema) + { + Log(LOG_ERR, "Please specify a schema with -s"); + goto finish; + } + + if (!header) + { + Log(LOG_ERR, "Please specify an output header with -h"); + goto finish; + } + + if (!impl) + { + Log(LOG_ERR, "Please specify an output C source file with -c"); + goto finish; + } + + schemaFile = StreamOpen(schema, "r"); + if (!schemaFile) + { + Log(LOG_ERR, "Unable to open '%s' for reading.", schema); + goto finish; + } + + headerFile = StreamOpen(header, "w"); + if (!headerFile) + { + Log(LOG_ERR, "Unable to open '%s' for writing.", header); + goto finish; + } + + implFile = StreamOpen(impl, "w"); + if (!implFile) + { + Log(LOG_ERR, "Unable to open '%s' for writing.", impl); + goto finish; + } + + Log(LOG_INFO, "Schema File: %s", schema); + Log(LOG_INFO, "Output:"); + Log(LOG_INFO, " Header: %s", header); + Log(LOG_INFO, " C Source: %s", impl); + + Log(LOG_NOTICE, "Parsing type schema..."); + schemaJson = JsonDecode(schemaFile); + if (!schemaJson) + { + Log(LOG_ERR, "JSON syntax error."); + goto finish; + } + + StreamClose(schemaFile); + schemaFile = NULL; + + Log(LOG_NOTICE, "Validating type schema..."); + val = HashMapGet(schemaJson, "guard"); + + if (!val) + { + Log(LOG_WARNING, "Guard not specified, using 'J2S_SCHEMA_H', which may not be unique."); + HashMapSet(schemaJson, "guard", JsonValueString("J2S_SCHEMA_H")); + } + else if (JsonValueType(val) != JSON_STRING) + { + Log(LOG_ERR, "Validation error: 'guard' must be a string."); + goto finish; + } + + val = HashMapGet(schemaJson, "header"); + if (!val) + { + Log(LOG_WARNING, "Header name not specified, using 'Schema.h', which is probably wrong."); + HashMapSet(schemaJson, "header", JsonValueString("Schema.h")); + } + else if (JsonValueType(val) != JSON_STRING) + { + Log(LOG_ERR, "Validation error: 'header' must be a string."); + goto finish; + } + + val = HashMapGet(schemaJson, "includes"); + + if (val && JsonValueType(val) != JSON_ARRAY) + { + Log(LOG_ERR, "Validation error: 'includes' must be an array."); + goto finish; + } + + for (i = 0; i < ArraySize(JsonValueAsArray(val)); i++) + { + if (JsonValueType(ArrayGet(JsonValueAsArray(val), 0)) != JSON_STRING) + { + Log(LOG_ERR, "Validation error: 'includes[%lu]' is not a string.", i); + goto finish; + } + } + + val = HashMapGet(schemaJson, "types"); + if (JsonValueType(val) != JSON_OBJECT) + { + Log(LOG_ERR, "Validation error: 'types' must be an object."); + goto finish; + } + + types = JsonValueAsObject(val); + + while (HashMapIterate(types, &type, (void **) &typeVal)) + { + HashMap *typeObj; + JsonValue *typeTypeVal; + char *typeType; + + JsonValue *typeFieldsVal; + HashMap *typeFields; + + char *fieldName; + JsonValue *fieldVal; + HashMap *fieldObj; + + Node *typeNode; + + if (JsonValueType(typeVal) != JSON_OBJECT) + { + Log(LOG_ERR, "Validation error: 'types.%s' must be an object.", type); + goto finish; + } + + typeObj = JsonValueAsObject(typeVal); + typeTypeVal = HashMapGet(typeObj, "type"); + if (JsonValueType(typeTypeVal) != JSON_STRING) + { + Log(LOG_ERR, "Validation error: 'types.%s.type' must be a string.", type); + goto finish; + } + + typeType = JsonValueAsString(typeTypeVal); + + typeNode = HashMapGet(typeToNode, type); + if (!typeNode) + { + typeNode = Malloc(sizeof(Node)); + *typeNode = ArraySize(requiredTypes); + HashMapSet(typeToNode, type, typeNode); + ArrayAdd(requiredTypes, StrDuplicate(type)); + } + + typeFieldsVal = HashMapGet(typeObj, "fields"); + if (JsonValueType(typeFieldsVal) != JSON_OBJECT) + { + Log(LOG_ERR, "Validation error: 'types.%s.fields' must be an object.", type); + goto finish; + } + + typeFields = JsonValueAsObject(typeFieldsVal); + + if (StrEquals(typeType, "struct")) + { + while (HashMapIterate(typeFields, &fieldName, (void **) &fieldVal)) + { + char *fieldType; + JsonValue *requiredVal; + + if (JsonValueType(fieldVal) != JSON_OBJECT) + { + Log(LOG_ERR, "Validation error: 'types.%s.fields.%s' must be an object.", type, fieldName); + goto finish; + } + + fieldObj = JsonValueAsObject(fieldVal); + + fieldType = JsonValueAsString(HashMapGet(fieldObj, "type")); + if (!fieldType) + { + Log(LOG_ERR, "Validation error: 'types.%s.fields.%s.type' is required and must be a string.", type, fieldName); + goto finish; + } + + fieldType = StrDuplicate(fieldType); + if (*fieldType == '[' && fieldType[strlen(fieldType) - 1] == ']') + { + fieldType++; + fieldType[strlen(fieldType) - 1] = '\0'; + } + + if (!StrEquals(fieldType, "object") && + !StrEquals(fieldType, "array") && + !StrEquals(fieldType, "string") && + !StrEquals(fieldType, "integer") && + !StrEquals(fieldType, "float") && + !StrEquals(fieldType, "boolean")) + { + Node *node = HashMapGet(typeToNode, fieldType); + if (!node) + { + node = Malloc(sizeof(Node)); + *node = ArraySize(requiredTypes); + HashMapSet(typeToNode, fieldType, node); + ArrayAdd(requiredTypes, fieldType); + } + + GraphEdgeSet(dependencyGraph, *node, *typeNode, 1); + } + else + { + Free(fieldType); + } + + requiredVal = HashMapGet(fieldObj, "required"); + if (requiredVal && JsonValueType(requiredVal) != JSON_BOOLEAN) + { + Log(LOG_ERR, "Validation error: 'types.%s.fields.%s.required' must be a boolean.", type, fieldName); + goto finish; + } + } + } + else if (StrEquals(typeType, "enum")) + { + while (HashMapIterate(typeFields, &fieldName, (void **) &fieldVal)) + { + char *name; + + if (JsonValueType(fieldVal) != JSON_OBJECT) + { + Log(LOG_ERR, "Validation error: 'types.%s.fields.%s' must be an object.", type, fieldName); + goto finish; + } + + fieldObj = JsonValueAsObject(fieldVal); + + name = JsonValueAsString(HashMapGet(fieldObj, "name")); + if (!name) + { + Log(LOG_ERR, "Validation error: 'types.%s.fields.%s.name' is required and must be a string.", type, fieldName); + goto finish; + } + } + } + else + { + Log(LOG_ERR, "Validation error: 'types.%s.type' must be 'struct' or 'enum'.", type); + goto finish; + } + /* + * TODO: Add "extern" type that doesn't actually generate any code, + * but trusts the user that it has been generated somewhere else. This + * is effectively "importing" types. + */ + } + + Log(LOG_NOTICE, "Resolving referenced types..."); + + sortedNodes = GraphTopologicalSort(dependencyGraph, &sortedNodesLen); + + GraphFree(dependencyGraph); + + for (i = 0; i < sortedNodesLen; i++) + { + char *type = ArrayGet(requiredTypes, sortedNodes[i]); + + if (!type) + { + continue; + } + + if (!HashMapGet(types, type)) + { + Log(LOG_ERR, "No type definition for required type '%s'", type); + goto finish; + } + } + + Log(LOG_NOTICE, "Writing header..."); + + guard = JsonValueAsString(HashMapGet(schemaJson, "guard")); + + StreamPrintf(headerFile, "/* Generated by j2s */\n\n"); + StreamPrintf(headerFile, "#ifndef %s\n", guard); + StreamPrintf(headerFile, "#define %s\n\n", guard); + + StreamPrintf(headerFile, "#include \n"); + StreamPrintf(headerFile, "#include \n"); + + StreamPutc(headerFile, '\n'); + + for (i = 0; i < ArraySize(JsonValueAsArray(HashMapGet(schemaJson, "include"))); i++) + { + char *h = JsonValueAsString(ArrayGet(JsonValueAsArray(HashMapGet(schemaJson, "include")), i)); + + StreamPrintf(headerFile, "#include <%s>\n", h); + } + + StreamPutc(headerFile, '\n'); + + for (i = 0; i < sortedNodesLen; i++) + { + char *type = ArrayGet(requiredTypes, sortedNodes[i]); + char *typeType; + HashMap *fields; + + char *field; + HashMap *fieldDesc; + + if (!type) + { + continue; + } + + typeType = JsonValueAsString(JsonGet(types, 2, type, "type")); + fields = JsonValueAsObject(JsonGet(types, 2, type, "fields")); + + StreamPrintf(headerFile, "typedef %s %s\n{\n", typeType, type); + + if (StrEquals(typeType, "struct")) + { + while (HashMapIterate(fields, &field, (void **) &fieldDesc)) + { + char *fieldType; + char *cType; + + fieldDesc = JsonValueAsObject((JsonValue *) fieldDesc); + + fieldType = JsonValueAsString(HashMapGet(fieldDesc, "type")); + + if (StrEquals(fieldType, "string")) + { + cType = "char *"; + } + else if (StrEquals(fieldType, "integer")) + { + cType = "long"; + } + else if (StrEquals(fieldType, "boolean")) + { + cType = "int"; + } + else if (StrEquals(fieldType, "float")) + { + cType = "double"; + } + else if (StrEquals(fieldType, "object")) + { + cType = "HashMap *"; + } + else if (StrEquals(fieldType, "array") || (*fieldType == '[' && fieldType[strlen(fieldType) - 1] == ']')) + { + cType = "Array *"; + } + else + { + cType = fieldType; + } + + StreamPrintf(headerFile, " %s %s;\n", cType, field); + } + + StreamPrintf(headerFile, "} %s;\n\n", type); + } + else if (StrEquals(typeType, "enum")) + { + /* + * Enums must be copied to an array because we have to know + * where we're at to know whether or not we need the trailing + * comma. + */ + Array *keys = HashMapKeys(fields); + + size_t j; + + for (j = 0; j < ArraySize(keys); j++) + { + char *key = ArrayGet(keys, j); + char *name; + char *value; + + fieldDesc = JsonValueAsObject(HashMapGet(fields, key)); + + name = JsonValueAsString(HashMapGet(fieldDesc, "name")); + value = JsonValueAsString(HashMapGet(fieldDesc, "value")); + + if (value) + { + StreamPrintf(headerFile, " %s = %s", name, value); + } + else + { + StreamPrintf(headerFile, " %s", name); + } + + if (j < ArraySize(keys) - 1) + { + StreamPutc(headerFile, ','); + } + + StreamPutc(headerFile, '\n'); + } + + ArrayFree(keys); + + StreamPrintf(headerFile, "} %s;\n\n", type); + } + } + + Free(sortedNodes); + for (i = 0; i < ArraySize(requiredTypes); i++) + { + Free(ArrayGet(requiredTypes, i)); + } + ArrayFree(requiredTypes); + + while (HashMapIterate(typeToNode, &type, (void **) &node)) + { + Free(node); + } + HashMapFree(typeToNode); + + Log(LOG_INFO, "Writing implementation code..."); + + headerName = JsonValueAsString(HashMapGet(schemaJson, "header")); + + StreamPrintf(implFile, "/* Generated by j2s */\n\n"); + StreamPrintf(implFile, "#include <%s>\n\n", headerName); + + StreamPrintf(implFile, "#include \n"); + StreamPrintf(implFile, "#include \n"); + StreamPrintf(implFile, "#include \n"); + + StreamPutc(implFile, '\n'); + + while (HashMapIterate(types, &type, (void **) &typeVal)) + { + char *typeType = JsonValueAsString(JsonGet(types, 2, type, "type")); + HashMap *fields = JsonValueAsObject(JsonGet(types, 2, type, "fields")); + + Array *keys = HashMapKeys(fields); + + if (StrEquals(typeType, "struct")) + { + StreamPrintf(headerFile, "extern int %sFromJson(HashMap *, %s *, char **);\n", type, type); + StreamPrintf(implFile, "int\n%sFromJson(HashMap *json, %s *out, char **errp)\n{\n", type, type); + StreamPrintf(implFile, " JsonValue *val;\n"); + StreamPrintf(implFile, " int enumParseRes;\n"); + StreamPrintf(implFile, "\n"); + StreamPrintf(implFile, " (void) enumParseRes;\n"); + StreamPrintf(implFile, "\n"); + StreamPrintf(implFile, " if (!json | !out)\n" + " {\n" + " *errp = \"Invalid pointers passed to %sFromJson()\";\n" + " return 0;\n" + " }\n\n" + , type); + for (i = 0; i < ArraySize(keys); i++) + { + char *key = ArrayGet(keys, i); + int required = JsonValueAsBoolean(JsonGet(fields, 2, key, "required")); + char *fieldType = JsonValueAsString(JsonGet(fields, 2, key, "type")); + int isEnum = StrEquals(JsonValueAsString(JsonGet(types, 2, fieldType, "type")), "enum"); + JsonType jsonType = isEnum ? JSON_STRING : TypeToJsonType(fieldType); + char *jsonTypeStr = JsonTypeToStr(jsonType); + + StreamPrintf(implFile, " val = HashMapGet(json, \"%s\");\n", key); + + StreamPrintf(implFile, " if (val)\n {\n"); + StreamPrintf(implFile, " if (JsonValueType(val) != %s)\n {\n", jsonTypeStr); + StreamPrintf(implFile, " *errp = \"%s.%s must be of type %s.\";\n", type, key, fieldType); + StreamPrintf(implFile, " return 0;\n"); + StreamPrintf(implFile, " }\n\n"); + if (StrEquals(fieldType, "array")) + { + StreamPrintf(implFile, " out->%s = JsonValueAsArray(JsonValueDuplicate(val));\n", key); + } + else if (StrEquals(fieldType, "object")) + { + StreamPrintf(implFile, " out->%s = JsonValueAsObject(JsonValueDuplicate(val));\n", key); + } + else if (*fieldType == '[' && fieldType[strlen(fieldType) - 1] == ']') + { + fieldType++; + fieldType[strlen(fieldType) - 1] = '\0'; + isEnum = StrEquals(JsonValueAsString(JsonGet(types, 2, fieldType, "type")), "enum"); + + StreamPrintf(implFile, " out->%s = ArrayCreate();\n", key); + StreamPrintf(implFile, " if (!out->%s)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " *errp = \"Failed to allocate memory for %s.%s.\";\n", type, key); + StreamPrintf(implFile, " return 0;\n"); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, " else\n"); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " Array *arr = JsonValueAsArray(val);\n"); + StreamPrintf(implFile, " size_t i;\n"); + StreamPrintf(implFile, "\n"); + StreamPrintf(implFile, " for (i = 0; i %s, parsed);\n", key); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, " }\n"); + + fieldType[strlen(fieldType)] = ']'; + } + else if (jsonType == JSON_OBJECT) + { + StreamPrintf(implFile, " if (!%sFromJson(JsonValueAsObject(val), &out->%s, errp))\n {\n", fieldType, key); + StreamPrintf(implFile, " return 0;\n"); + StreamPrintf(implFile, " }\n"); + } + else + { + char *func; + + switch (jsonType) + { + case JSON_STRING: + func = "String"; + break; + case JSON_INTEGER: + func = "Integer"; + break; + case JSON_FLOAT: + func = "Float"; + break; + case JSON_BOOLEAN: + func = "Boolean"; + break; + default: + /* Should not happen */ + func = NULL; + break; + } + + if (jsonType == JSON_STRING) + { + if (isEnum) + { + StreamPrintf(implFile, " enumParseRes = %sFromStr(JsonValueAs%s(val));\n", fieldType, func); + StreamPrintf(implFile, " if (enumParseRes == -1)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " *errp = \"Invalid value for %s.%s.\";\n", type, key); + StreamPrintf(implFile, " return 0;\n"); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, " out->%s = enumParseRes;\n", key); + } + else + { + StreamPrintf(implFile, " out->%s = StrDuplicate(JsonValueAs%s(val));\n", key, func); + } + } + else + { + StreamPrintf(implFile, " out->%s = JsonValueAs%s(val);\n", key, func); + } + } + StreamPrintf(implFile, " }\n"); + + if (required) + { + StreamPrintf(implFile, " else\n {\n"); + StreamPrintf(implFile, " *errp = \"%s.%s is required.\";\n", type, key); + StreamPrintf(implFile, " return 0;\n"); + StreamPrintf(implFile, " }\n"); + } + + StreamPutc(implFile, '\n'); + } + StreamPrintf(implFile, " return 1;\n"); + StreamPrintf(implFile, "}\n\n"); + + StreamPrintf(headerFile, "extern HashMap * %sToJson(%s *);\n", type, type); + StreamPrintf(implFile, "HashMap *\n%sToJson(%s *val)\n{\n", type, type); + StreamPrintf(implFile, " HashMap *json;\n"); + StreamPrintf(implFile, "\n"); + StreamPrintf(implFile, " if (!val)\n"); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " return NULL;\n"); + StreamPrintf(implFile, " }\n\n"); + StreamPrintf(implFile, " json = HashMapCreate();\n"); + StreamPrintf(implFile, " if (!json)\n"); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " return NULL;\n"); + StreamPrintf(implFile, " }\n\n"); + for (i = 0; i < ArraySize(keys); i++) + { + char *key = ArrayGet(keys, i); + char *fieldType = JsonValueAsString(JsonGet(fields, 2, key, "type")); + int isEnum = StrEquals(JsonValueAsString(JsonGet(types, 2, fieldType, "type")), "enum"); + + if (StrEquals(fieldType, "array")) + { + StreamPrintf(implFile, " if (val->%s)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " size_t i;\n"); + StreamPrintf(implFile, " Array *jsonArr = ArrayCreate();\n"); + StreamPrintf(implFile, " if (!jsonArr)\n"); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " JsonFree(json);\n"); + StreamPrintf(implFile, " return NULL;\n"); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, " for (i = 0; i < ArraySize(val->%s); i++)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " ArrayAdd(jsonArr, JsonValueDuplicate(ArrayGet(val->%s, i)));\n", key); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, " HashMapSet(json, \"%s\", JsonValueArray(jsonArr))\n", key); + StreamPrintf(implFile, " }\n"); + } + else if (StrEquals(fieldType, "object")) + { + StreamPrintf(implFile, " HashMapSet(json, \"%s\", JsonValueObject(JsonDuplicate(val->%s)));\n", key, key); + } + else if (*fieldType == '[' && fieldType[strlen(fieldType) - 1] == ']') + { + fieldType++; + fieldType[strlen(fieldType) - 1] = '\0'; + isEnum = StrEquals(JsonValueAsString(JsonGet(types, 2, fieldType, "type")), "enum"); + + StreamPrintf(implFile, " if (val->%s)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " size_t i;\n"); + StreamPrintf(implFile, " Array *jsonArr = ArrayCreate();\n"); + StreamPrintf(implFile, " if (!jsonArr)\n"); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " JsonFree(json);\n"); + StreamPrintf(implFile, " return NULL;\n"); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, " for (i = 0; i < ArraySize(val->%s); i++)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " ArrayAdd(jsonArr, JsonValueObject(%sToJson(ArrayGet(val->%s, i))));\n", fieldType, key); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, " HashMapSet(json, \"%s\", JsonValueArray(jsonArr));\n", key); + StreamPrintf(implFile, " }\n"); + + fieldType[strlen(fieldType)] = ']'; + } + else + { + if (HashMapGet(types, fieldType)) + { + if (isEnum) + { + StreamPrintf(implFile, " HashMapSet(json, \"%s\", JsonValueString(%sToStr(val->%s)));\n", key, fieldType, key); + } + else + { + StreamPrintf(implFile, " HashMapSet(json, \"%s\", JsonValueObject(%sToJson(&val->%s)));\n", key, fieldType, key); + } + } + else + { + *fieldType = toupper(*fieldType); + StreamPrintf(implFile, " HashMapSet(json, \"%s\", JsonValue%s(val->%s));\n", key, fieldType, key); + *fieldType = tolower(*fieldType); + } + } + } + StreamPrintf(implFile, " return json;\n"); + StreamPrintf(implFile, "}\n\n"); + + StreamPrintf(headerFile, "extern void %sFree(%s *);\n", type, type); + StreamPrintf(implFile, "void\n%sFree(%s *val)\n{\n", type, type); + StreamPrintf(implFile, " if (!val)\n"); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " return;\n"); + StreamPrintf(implFile, " }\n\n"); + for (i = 0; i < ArraySize(keys); i++) + { + char *key = ArrayGet(keys, i); + char *fieldType = JsonValueAsString(JsonGet(fields, 2, key, "type")); + int isEnum = StrEquals(JsonValueAsString(JsonGet(types, 2, fieldType, "type")), "enum"); + + if (StrEquals(fieldType, "array")) + { + StreamPrintf(implFile, " if (val->%s)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " size_t i;\n"); + StreamPrintf(implFile, " for (i = 0; i < ArraySize(val->%s); i++)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " JsonValueFree(ArrayGet(val->%s, i));\n", key); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, " ArrayFree(val->%s);\n", key); + StreamPrintf(implFile, " }\n"); + } + else if (StrEquals(fieldType, "object")) + { + StreamPrintf(implFile, " JsonFree(val->%s);\n", key); + } + else if (StrEquals(fieldType, "string")) + { + StreamPrintf(implFile, " Free(val->%s);\n", key); + } + else if (*fieldType == '[' && fieldType[strlen(fieldType) - 1] == ']') + { + fieldType++; + fieldType[strlen(fieldType) - 1] = '\0'; + isEnum = StrEquals(JsonValueAsString(JsonGet(types, 2, fieldType, "type")), "enum"); + + StreamPrintf(implFile, " if (val->%s)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " size_t i;\n"); + StreamPrintf(implFile, " for (i = 0; i < ArraySize(val->%s); i++)\n", key); + StreamPrintf(implFile, " {\n"); + StreamPrintf(implFile, " %sFree(ArrayGet(val->%s, i));\n", fieldType, key); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, " ArrayFree(val->%s);\n", key); + StreamPrintf(implFile, " }\n"); + + fieldType[strlen(fieldType)] = ']'; + } + else + { + /* Ignore primitives but call the appropriate free method on declared types */ + if (!isEnum && HashMapGet(types, fieldType)) + { + StreamPrintf(implFile, " %sFree(&val->%s);\n", fieldType, key); + } + } + } + StreamPrintf(implFile, "}\n\n"); + + StreamPutc(headerFile, '\n'); + } + else if (StrEquals(typeType, "enum")) + { + StreamPrintf(headerFile, "extern int %sFromStr(char *);\n", type); + StreamPrintf(implFile, "int\n%sFromStr(char *str)\n{\n", type); + for (i = 0; i < ArraySize(keys); i++) + { + char *key = ArrayGet(keys, i); + + if (i > 0) + { + StreamPrintf(implFile, " else if"); + } + else + { + StreamPrintf(implFile, " if"); + } + + StreamPrintf(implFile, " (StrEquals(str, \"%s\"))\n {\n", key); + StreamPrintf(implFile, " return %s;\n", JsonValueAsString(JsonGet(fields, 2, key, "name"))); + StreamPrintf(implFile, " }\n"); + } + + StreamPrintf(implFile, " else\n {\n"); + StreamPrintf(implFile, " return -1;\n"); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, "}\n\n"); + + StreamPrintf(headerFile, "extern char * %sToStr(%s);\n", type, type); + StreamPrintf(implFile, "char *\n%sToStr(%s val)\n{\n", type, type); + StreamPrintf(implFile, " switch (val)\n {\n"); + for (i = 0; i < ArraySize(keys); i++) + { + char *key = ArrayGet(keys, i); + char *name = JsonValueAsString(JsonGet(fields, 2, key, "name")); + + StreamPrintf(implFile, " case %s:\n", name); + StreamPrintf(implFile, " return \"%s\";\n", key); + } + StreamPrintf(implFile, " default:\n"); + StreamPrintf(implFile, " return NULL;\n"); + StreamPrintf(implFile, " }\n"); + StreamPrintf(implFile, "}\n\n"); + StreamPutc(headerFile, '\n'); + + ArrayFree(keys); + } + + } + + StreamPrintf(headerFile, "#endif /* %s */\n", guard); + + ret = EXIT_SUCCESS; + +finish: + + StreamClose(schemaFile); + StreamClose(headerFile); + StreamClose(implFile); + + JsonFree(schemaJson); + + return ret; +} diff --git a/TODO.txt b/TODO.txt index cb50355..e26784e 100644 --- a/TODO.txt +++ b/TODO.txt @@ -40,6 +40,10 @@ Milestone: v0.4.0 [x] Database corruption [ ] File descriptor exhaustion [ ] Random memory corruption after many requests. +[ ] j2s + [ ] How to set default value of true on boolean? + - defaults probably need to be set at parser level, not + enough to set it after. [ ] Refactor MatrixErrorCreate() to take a custom message or NULL to use the default. This will make debugging a lot easier.