| /* Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <ctype.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| #ifndef true |
| #define true 1 |
| #define false 0 |
| #endif |
| |
| #define CFC_NEED_BASE_STRUCT_DEF |
| #include "CFCBase.h" |
| #include "CFCParcel.h" |
| #include "CFCFileSpec.h" |
| #include "CFCVersion.h" |
| #include "CFCUtil.h" |
| |
| struct CFCParcel { |
| CFCBase base; |
| char *name; |
| char *nickname; |
| CFCVersion *version; |
| CFCFileSpec *file_spec; |
| char *prefix; |
| char *Prefix; |
| char *PREFIX; |
| char *privacy_sym; |
| int is_required; |
| char **inherited_parcels; |
| size_t num_inherited_parcels; |
| char **struct_syms; |
| size_t num_struct_syms; |
| CFCPrereq **prereqs; |
| size_t num_prereqs; |
| }; |
| |
| #define JSON_STRING 1 |
| #define JSON_HASH 2 |
| #define JSON_NULL 3 |
| |
| typedef struct JSONNode { |
| int type; |
| char *string; |
| struct JSONNode **kids; |
| size_t num_kids; |
| } JSONNode; |
| |
| static void |
| S_set_prereqs(CFCParcel *self, JSONNode *node, const char *path); |
| |
| static JSONNode* |
| S_parse_json_for_parcel(const char *json); |
| |
| static JSONNode* |
| S_parse_json_hash(const char **json); |
| |
| static JSONNode* |
| S_parse_json_string(const char **json); |
| |
| static JSONNode* |
| S_parse_json_null(const char **json); |
| |
| static void |
| S_skip_whitespace(const char **json); |
| |
| static void |
| S_destroy_json(JSONNode *node); |
| |
| static CFCParcel **registry = NULL; |
| static size_t num_registered = 0; |
| |
| CFCParcel* |
| CFCParcel_fetch(const char *name) { |
| for (size_t i = 0; i < num_registered ; i++) { |
| CFCParcel *existing = registry[i]; |
| if (strcmp(existing->name, name) == 0) { |
| return existing; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void |
| CFCParcel_register(CFCParcel *self) { |
| const char *name = self->name; |
| const char *nickname = self->nickname; |
| |
| for (size_t i = 0; i < num_registered ; i++) { |
| CFCParcel *other = registry[i]; |
| |
| if (strcmp(other->name, name) == 0) { |
| CFCUtil_die("Parcel '%s' already registered", name); |
| } |
| if (strcmp(other->nickname, nickname) == 0) { |
| CFCUtil_die("Parcel with nickname '%s' already registered", |
| nickname); |
| } |
| } |
| |
| size_t size = (num_registered + 2) * sizeof(CFCParcel*); |
| registry = (CFCParcel**)REALLOCATE(registry, size); |
| registry[num_registered++] = (CFCParcel*)CFCBase_incref((CFCBase*)self); |
| registry[num_registered] = NULL; |
| } |
| |
| CFCParcel** |
| CFCParcel_all_parcels(void) { |
| if (registry == NULL) { |
| registry = (CFCParcel**)MALLOCATE(sizeof(CFCParcel*)); |
| registry[0] = NULL; |
| } |
| |
| return registry; |
| } |
| |
| void |
| CFCParcel_reap_singletons(void) { |
| for (size_t i = 0; i < num_registered; i++) { |
| CFCBase_decref((CFCBase*)registry[i]); |
| } |
| FREEMEM(registry); |
| num_registered = 0; |
| registry = NULL; |
| } |
| |
| static int |
| S_validate_name_or_nickname(const char *orig) { |
| const char *ptr = orig; |
| for (; *ptr != 0; ptr++) { |
| if (!isalpha(*ptr)) { return false; } |
| } |
| return true; |
| } |
| |
| static const CFCMeta CFCPARCEL_META = { |
| "Clownfish::CFC::Model::Parcel", |
| sizeof(CFCParcel), |
| (CFCBase_destroy_t)CFCParcel_destroy |
| }; |
| |
| CFCParcel* |
| CFCParcel_new(const char *name, const char *nickname, CFCVersion *version, |
| CFCFileSpec *file_spec) { |
| CFCParcel *self = (CFCParcel*)CFCBase_allocate(&CFCPARCEL_META); |
| return CFCParcel_init(self, name, nickname, version, file_spec); |
| } |
| |
| CFCParcel* |
| CFCParcel_init(CFCParcel *self, const char *name, const char *nickname, |
| CFCVersion *version, CFCFileSpec *file_spec) { |
| // Validate name. |
| if (!name || !S_validate_name_or_nickname(name)) { |
| CFCUtil_die("Invalid name: '%s'", name ? name : "[NULL]"); |
| } |
| self->name = CFCUtil_strdup(name); |
| |
| // Validate or derive nickname. |
| if (nickname) { |
| if (!S_validate_name_or_nickname(nickname)) { |
| CFCUtil_die("Invalid nickname: '%s'", nickname); |
| } |
| self->nickname = CFCUtil_strdup(nickname); |
| } |
| else { |
| // Default nickname to name. |
| self->nickname = CFCUtil_strdup(name); |
| } |
| |
| // Default to version v0. |
| if (version) { |
| self->version = (CFCVersion*)CFCBase_incref((CFCBase*)version); |
| } |
| else { |
| self->version = CFCVersion_new("v0"); |
| } |
| |
| // Set file_spec. |
| self->file_spec = (CFCFileSpec*)CFCBase_incref((CFCBase*)file_spec); |
| |
| // Derive prefix, Prefix, PREFIX. |
| size_t nickname_len = strlen(self->nickname); |
| size_t prefix_len = nickname_len ? nickname_len + 1 : 0; |
| size_t amount = prefix_len + 1; |
| self->prefix = (char*)MALLOCATE(amount); |
| self->Prefix = (char*)MALLOCATE(amount); |
| self->PREFIX = (char*)MALLOCATE(amount); |
| memcpy(self->Prefix, self->nickname, nickname_len); |
| if (nickname_len) { |
| self->Prefix[nickname_len] = '_'; |
| self->Prefix[nickname_len + 1] = '\0'; |
| } |
| else { |
| self->Prefix[nickname_len] = '\0'; |
| } |
| for (size_t i = 0; i < amount; i++) { |
| self->prefix[i] = tolower(self->Prefix[i]); |
| self->PREFIX[i] = toupper(self->Prefix[i]); |
| } |
| self->prefix[prefix_len] = '\0'; |
| self->Prefix[prefix_len] = '\0'; |
| self->PREFIX[prefix_len] = '\0'; |
| |
| // Derive privacy symbol. |
| size_t privacy_sym_len = nickname_len + 4; |
| self->privacy_sym = (char*)MALLOCATE(privacy_sym_len + 1); |
| memcpy(self->privacy_sym, "CFP_", 4); |
| for (size_t i = 0; i < nickname_len; i++) { |
| self->privacy_sym[i+4] = toupper(self->nickname[i]); |
| } |
| self->privacy_sym[privacy_sym_len] = '\0'; |
| |
| // Initialize flags. |
| self->is_required = false; |
| |
| // Initialize arrays. |
| self->inherited_parcels = (char**)CALLOCATE(1, sizeof(char*)); |
| self->num_inherited_parcels = 0; |
| self->struct_syms = (char**)CALLOCATE(1, sizeof(char*)); |
| self->num_struct_syms = 0; |
| self->prereqs = (CFCPrereq**)CALLOCATE(1, sizeof(CFCPrereq*)); |
| self->num_prereqs = 0; |
| |
| return self; |
| } |
| |
| static CFCParcel* |
| S_new_from_json(const char *json, const char *path, CFCFileSpec *file_spec) { |
| JSONNode *parsed = S_parse_json_for_parcel(json); |
| if (!parsed) { |
| CFCUtil_die("Invalid JSON parcel definition in '%s'", path); |
| } |
| if (parsed->type != JSON_HASH) { |
| CFCUtil_die("Parcel definition must be a hash in '%s'", path); |
| } |
| const char *name = NULL; |
| const char *nickname = NULL; |
| CFCVersion *version = NULL; |
| JSONNode *prereqs = NULL; |
| for (size_t i = 0, max = parsed->num_kids; i < max; i += 2) { |
| JSONNode *key = parsed->kids[i]; |
| JSONNode *value = parsed->kids[i + 1]; |
| if (key->type != JSON_STRING) { |
| CFCUtil_die("JSON parsing error (filepath '%s')", path); |
| } |
| if (strcmp(key->string, "name") == 0) { |
| if (value->type != JSON_STRING) { |
| CFCUtil_die("'name' must be a string (filepath %s)", path); |
| } |
| name = value->string; |
| } |
| else if (strcmp(key->string, "nickname") == 0) { |
| if (value->type != JSON_STRING) { |
| CFCUtil_die("'nickname' must be a string (filepath %s)", |
| path); |
| } |
| nickname = value->string; |
| } |
| else if (strcmp(key->string, "version") == 0) { |
| if (value->type != JSON_STRING) { |
| CFCUtil_die("'version' must be a string (filepath %s)", |
| path); |
| } |
| version = CFCVersion_new(value->string); |
| } |
| else if (strcmp(key->string, "prerequisites") == 0) { |
| if (value->type != JSON_HASH) { |
| CFCUtil_die("'prerequisites' must be a hash (filepath %s)", |
| path); |
| } |
| prereqs = value; |
| } |
| else { |
| CFCUtil_die("Unrecognized key: '%s' (filepath '%s')", |
| key->string, path); |
| } |
| } |
| if (!name) { |
| CFCUtil_die("Missing required key 'name' (filepath '%s')", path); |
| } |
| if (!version) { |
| CFCUtil_die("Missing required key 'version' (filepath '%s')", path); |
| } |
| CFCParcel *self = CFCParcel_new(name, nickname, version, file_spec); |
| if (prereqs) { |
| S_set_prereqs(self, prereqs, path); |
| } |
| CFCBase_decref((CFCBase*)version); |
| |
| S_destroy_json(parsed); |
| return self; |
| } |
| |
| static void |
| S_set_prereqs(CFCParcel *self, JSONNode *node, const char *path) { |
| size_t num_prereqs = node->num_kids / 2; |
| CFCPrereq **prereqs |
| = (CFCPrereq**)MALLOCATE((num_prereqs + 1) * sizeof(CFCPrereq*)); |
| |
| for (size_t i = 0; i < num_prereqs; ++i) { |
| JSONNode *key = node->kids[2*i]; |
| if (key->type != JSON_STRING) { |
| CFCUtil_die("Prereq key must be a string (filepath '%s')", path); |
| } |
| const char *name = key->string; |
| |
| JSONNode *value = node->kids[2*i+1]; |
| CFCVersion *version = NULL; |
| if (value->type == JSON_STRING) { |
| version = CFCVersion_new(value->string); |
| } |
| else if (value->type != JSON_NULL) { |
| CFCUtil_die("Invalid prereq value (filepath '%s')", path); |
| } |
| |
| prereqs[i] = CFCPrereq_new(name, version); |
| |
| CFCBase_decref((CFCBase*)version); |
| } |
| prereqs[num_prereqs] = NULL; |
| |
| // Assume that prereqs are empty. |
| FREEMEM(self->prereqs); |
| self->prereqs = prereqs; |
| self->num_prereqs = num_prereqs; |
| } |
| |
| CFCParcel* |
| CFCParcel_new_from_json(const char *json, CFCFileSpec *file_spec) { |
| return S_new_from_json(json, "[NULL]", file_spec); |
| } |
| |
| CFCParcel* |
| CFCParcel_new_from_file(const char *path, CFCFileSpec *file_spec) { |
| size_t len; |
| char *json = CFCUtil_slurp_text(path, &len); |
| CFCParcel *self = S_new_from_json(json, path, file_spec); |
| FREEMEM(json); |
| return self; |
| } |
| |
| void |
| CFCParcel_destroy(CFCParcel *self) { |
| FREEMEM(self->name); |
| FREEMEM(self->nickname); |
| CFCBase_decref((CFCBase*)self->version); |
| CFCBase_decref((CFCBase*)self->file_spec); |
| FREEMEM(self->prefix); |
| FREEMEM(self->Prefix); |
| FREEMEM(self->PREFIX); |
| FREEMEM(self->privacy_sym); |
| CFCUtil_free_string_array(self->inherited_parcels); |
| CFCUtil_free_string_array(self->struct_syms); |
| for (size_t i = 0; self->prereqs[i]; ++i) { |
| CFCBase_decref((CFCBase*)self->prereqs[i]); |
| } |
| FREEMEM(self->prereqs); |
| CFCBase_destroy((CFCBase*)self); |
| } |
| |
| int |
| CFCParcel_equals(CFCParcel *self, CFCParcel *other) { |
| if (strcmp(self->name, other->name)) { return false; } |
| if (strcmp(self->nickname, other->nickname)) { return false; } |
| if (CFCVersion_compare_to(self->version, other->version) != 0) { |
| return false; |
| } |
| if (CFCParcel_included(self) != CFCParcel_included(other)) { |
| return false; |
| } |
| return true; |
| } |
| |
| const char* |
| CFCParcel_get_name(CFCParcel *self) { |
| return self->name; |
| } |
| |
| const char* |
| CFCParcel_get_nickname(CFCParcel *self) { |
| return self->nickname; |
| } |
| |
| CFCVersion* |
| CFCParcel_get_version(CFCParcel *self) { |
| return self->version; |
| } |
| |
| const char* |
| CFCParcel_get_prefix(CFCParcel *self) { |
| return self->prefix; |
| } |
| |
| const char* |
| CFCParcel_get_Prefix(CFCParcel *self) { |
| return self->Prefix; |
| } |
| |
| const char* |
| CFCParcel_get_PREFIX(CFCParcel *self) { |
| return self->PREFIX; |
| } |
| |
| const char* |
| CFCParcel_get_privacy_sym(CFCParcel *self) { |
| return self->privacy_sym; |
| } |
| |
| const char* |
| CFCParcel_get_cfp_path(CFCParcel *self) { |
| return self->file_spec |
| ? CFCFileSpec_get_path(self->file_spec) |
| : NULL; |
| } |
| |
| const char* |
| CFCParcel_get_source_dir(CFCParcel *self) { |
| return self->file_spec |
| ? CFCFileSpec_get_source_dir(self->file_spec) |
| : NULL; |
| } |
| |
| int |
| CFCParcel_included(CFCParcel *self) { |
| return self->file_spec ? CFCFileSpec_included(self->file_spec) : false; |
| } |
| |
| int |
| CFCParcel_required(CFCParcel *self) { |
| return self->is_required; |
| } |
| |
| void |
| CFCParcel_add_inherited_parcel(CFCParcel *self, CFCParcel *inherited) { |
| const char *name = CFCParcel_get_name(self); |
| const char *inh_name = CFCParcel_get_name(inherited); |
| |
| if (strcmp(name, inh_name) == 0) { return; } |
| |
| for (size_t i = 0; self->inherited_parcels[i]; ++i) { |
| const char *other_name = self->inherited_parcels[i]; |
| if (strcmp(other_name, inh_name) == 0) { return; } |
| } |
| |
| size_t num_parcels = self->num_inherited_parcels; |
| self->inherited_parcels |
| = (char**)REALLOCATE(self->inherited_parcels, |
| (num_parcels + 2) * sizeof(char*)); |
| self->inherited_parcels[num_parcels] = CFCUtil_strdup(inh_name); |
| self->inherited_parcels[num_parcels+1] = NULL; |
| self->num_inherited_parcels = num_parcels + 1; |
| } |
| |
| CFCParcel** |
| CFCParcel_inherited_parcels(CFCParcel *self) { |
| CFCParcel **parcels |
| = (CFCParcel**)CALLOCATE(self->num_inherited_parcels + 1, |
| sizeof(CFCParcel*)); |
| |
| for (size_t i = 0; self->inherited_parcels[i]; ++i) { |
| parcels[i] = CFCParcel_fetch(self->inherited_parcels[i]); |
| } |
| |
| return parcels; |
| } |
| |
| CFCPrereq** |
| CFCParcel_get_prereqs(CFCParcel *self) { |
| return self->prereqs; |
| } |
| |
| CFCParcel** |
| CFCParcel_prereq_parcels(CFCParcel *self) { |
| CFCParcel **parcels |
| = (CFCParcel**)CALLOCATE(self->num_prereqs + 1, sizeof(CFCParcel*)); |
| |
| for (size_t i = 0; self->prereqs[i]; ++i) { |
| const char *name = CFCPrereq_get_name(self->prereqs[i]); |
| parcels[i] = CFCParcel_fetch(name); |
| } |
| |
| return parcels; |
| } |
| |
| void |
| CFCParcel_check_prereqs(CFCParcel *self) { |
| // This is essentially a depth-first search of the dependency graph. |
| // It might be possible to skip indirect dependencies, at least if |
| // they're not part of the inheritance chain. But for now, all |
| // dependencies are marked recursively. |
| |
| if (self->is_required) { return; } |
| self->is_required = true; |
| |
| const char *name = CFCParcel_get_name(self); |
| |
| for (int i = 0; self->prereqs[i]; ++i) { |
| CFCPrereq *prereq = self->prereqs[i]; |
| |
| const char *req_name = CFCPrereq_get_name(prereq); |
| CFCParcel *req_parcel = CFCParcel_fetch(req_name); |
| if (!req_parcel) { |
| // TODO: Add include path to error message. |
| CFCUtil_die("Parcel '%s' required by '%s' not found", req_name, |
| name); |
| } |
| |
| CFCVersion *version = req_parcel->version; |
| CFCVersion *req_version = CFCPrereq_get_version(prereq); |
| if (CFCVersion_compare_to(version, req_version) < 0) { |
| const char *vstring = CFCVersion_get_vstring(version); |
| const char *req_vstring = CFCVersion_get_vstring(req_version); |
| CFCUtil_die("Version %s of parcel '%s' required by '%s' is lower" |
| " than required version %s", |
| vstring, req_name, name, req_vstring); |
| } |
| |
| CFCParcel_check_prereqs(req_parcel); |
| } |
| } |
| |
| int |
| CFCParcel_has_prereq(CFCParcel *self, CFCParcel *parcel) { |
| const char *name = CFCParcel_get_name(parcel); |
| |
| if (strcmp(CFCParcel_get_name(self), name) == 0) { |
| return true; |
| } |
| |
| for (int i = 0; self->prereqs[i]; ++i) { |
| const char *prereq_name = CFCPrereq_get_name(self->prereqs[i]); |
| if (strcmp(prereq_name, name) == 0) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void |
| CFCParcel_add_struct_sym(CFCParcel *self, const char *struct_sym) { |
| size_t num_struct_syms = self->num_struct_syms + 1; |
| size_t size = (num_struct_syms + 1) * sizeof(char*); |
| char **struct_syms = (char**)REALLOCATE(self->struct_syms, size); |
| struct_syms[num_struct_syms-1] = CFCUtil_strdup(struct_sym); |
| struct_syms[num_struct_syms] = NULL; |
| self->struct_syms = struct_syms; |
| self->num_struct_syms = num_struct_syms; |
| } |
| |
| static CFCParcel* |
| S_lookup_struct_sym(CFCParcel *self, const char *struct_sym) { |
| for (size_t i = 0; self->struct_syms[i]; ++i) { |
| if (strcmp(self->struct_syms[i], struct_sym) == 0) { |
| return self; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| CFCParcel* |
| CFCParcel_lookup_struct_sym(CFCParcel *self, const char *struct_sym) { |
| CFCParcel *parcel = S_lookup_struct_sym(self, struct_sym); |
| |
| for (size_t i = 0; self->prereqs[i]; ++i) { |
| const char *prereq_name = CFCPrereq_get_name(self->prereqs[i]); |
| CFCParcel *prereq_parcel = CFCParcel_fetch(prereq_name); |
| CFCParcel *maybe_parcel |
| = S_lookup_struct_sym(prereq_parcel, struct_sym); |
| |
| if (maybe_parcel) { |
| if (parcel) { |
| CFCUtil_die("Type '%s' is ambigious", struct_sym); |
| } |
| parcel = maybe_parcel; |
| } |
| } |
| |
| return parcel; |
| } |
| |
| int |
| CFCParcel_is_cfish(CFCParcel *self) { |
| return !strcmp(self->prefix, "cfish_"); |
| } |
| |
| /**************************************************************************/ |
| |
| struct CFCPrereq { |
| CFCBase base; |
| char *name; |
| CFCVersion *version; |
| }; |
| |
| static const CFCMeta CFCPREREQ_META = { |
| "Clownfish::CFC::Model::Prereq", |
| sizeof(CFCPrereq), |
| (CFCBase_destroy_t)CFCPrereq_destroy |
| }; |
| |
| CFCPrereq* |
| CFCPrereq_new(const char *name, CFCVersion *version) { |
| CFCPrereq *self = (CFCPrereq*)CFCBase_allocate(&CFCPREREQ_META); |
| return CFCPrereq_init(self, name, version); |
| } |
| |
| CFCPrereq* |
| CFCPrereq_init(CFCPrereq *self, const char *name, CFCVersion *version) { |
| // Validate name. |
| if (!name || !S_validate_name_or_nickname(name)) { |
| CFCUtil_die("Invalid name: '%s'", name ? name : "[NULL]"); |
| } |
| self->name = CFCUtil_strdup(name); |
| |
| // Default to version v0. |
| if (version) { |
| self->version = (CFCVersion*)CFCBase_incref((CFCBase*)version); |
| } |
| else { |
| self->version = CFCVersion_new("v0"); |
| } |
| |
| return self; |
| } |
| |
| void |
| CFCPrereq_destroy(CFCPrereq *self) { |
| FREEMEM(self->name); |
| CFCBase_decref((CFCBase*)self->version); |
| CFCBase_destroy((CFCBase*)self); |
| } |
| |
| const char* |
| CFCPrereq_get_name(CFCPrereq *self) { |
| return self->name; |
| } |
| |
| CFCVersion* |
| CFCPrereq_get_version(CFCPrereq *self) { |
| return self->version; |
| } |
| |
| /***************************************************************************** |
| * The hack JSON parser coded up below is only meant to parse Clownfish parcel |
| * file content. It is limited in its capabilities because so little is legal |
| * in a .cfp file. |
| */ |
| |
| static JSONNode* |
| S_parse_json_for_parcel(const char *json) { |
| if (!json) { |
| return NULL; |
| } |
| S_skip_whitespace(&json); |
| if (*json != '{') { |
| return NULL; |
| } |
| JSONNode *parsed = S_parse_json_hash(&json); |
| S_skip_whitespace(&json); |
| if (*json != '\0') { |
| S_destroy_json(parsed); |
| parsed = NULL; |
| } |
| return parsed; |
| } |
| |
| static void |
| S_append_kid(JSONNode *node, JSONNode *child) { |
| size_t size = (node->num_kids + 2) * sizeof(JSONNode*); |
| node->kids = (JSONNode**)realloc(node->kids, size); |
| node->kids[node->num_kids++] = child; |
| node->kids[node->num_kids] = NULL; |
| } |
| |
| static JSONNode* |
| S_parse_json_hash(const char **json) { |
| const char *text = *json; |
| S_skip_whitespace(&text); |
| if (*text != '{') { |
| return NULL; |
| } |
| text++; |
| JSONNode *node = (JSONNode*)calloc(1, sizeof(JSONNode)); |
| node->type = JSON_HASH; |
| while (1) { |
| // Parse key. |
| S_skip_whitespace(&text); |
| if (*text == '}') { |
| text++; |
| break; |
| } |
| else if (*text == '"') { |
| JSONNode *key = S_parse_json_string(&text); |
| S_skip_whitespace(&text); |
| if (!key || *text != ':') { |
| S_destroy_json(node); |
| return NULL; |
| } |
| text++; |
| S_append_kid(node, key); |
| } |
| else { |
| S_destroy_json(node); |
| return NULL; |
| } |
| |
| // Parse value. |
| S_skip_whitespace(&text); |
| JSONNode *value = NULL; |
| if (*text == '"') { |
| value = S_parse_json_string(&text); |
| } |
| else if (*text == '{') { |
| value = S_parse_json_hash(&text); |
| } |
| else if (*text == 'n') { |
| value = S_parse_json_null(&text); |
| } |
| if (!value) { |
| S_destroy_json(node); |
| return NULL; |
| } |
| S_append_kid(node, value); |
| |
| // Parse comma. |
| S_skip_whitespace(&text); |
| if (*text == ',') { |
| text++; |
| } |
| else if (*text == '}') { |
| text++; |
| break; |
| } |
| else { |
| S_destroy_json(node); |
| return NULL; |
| } |
| } |
| |
| // Move pointer. |
| *json = text; |
| |
| return node; |
| } |
| |
| // Parse a double quoted string. Don't allow escapes. |
| static JSONNode* |
| S_parse_json_string(const char **json) { |
| const char *text = *json; |
| if (*text != '\"') { |
| return NULL; |
| } |
| text++; |
| const char *start = text; |
| while (*text != '"') { |
| if (*text == '\\' || *text == '\0') { |
| return NULL; |
| } |
| text++; |
| } |
| JSONNode *node = (JSONNode*)calloc(1, sizeof(JSONNode)); |
| node->type = JSON_STRING; |
| node->string = CFCUtil_strndup(start, text - start); |
| |
| // Move pointer. |
| text++; |
| *json = text; |
| |
| return node; |
| } |
| |
| // Parse a JSON null value. |
| static JSONNode* |
| S_parse_json_null(const char **json) { |
| static const char null_str[] = "null"; |
| |
| if (strncmp(*json, null_str, sizeof(null_str) - 1) != 0) { |
| return NULL; |
| } |
| |
| JSONNode *node = (JSONNode*)calloc(1, sizeof(JSONNode)); |
| node->type = JSON_NULL; |
| |
| // Move pointer. |
| *json += sizeof(null_str) - 1; |
| |
| return node; |
| } |
| |
| static void |
| S_skip_whitespace(const char **json) { |
| while (isspace(json[0][0])) { *json = *json + 1; } |
| } |
| |
| static void |
| S_destroy_json(JSONNode *node) { |
| if (!node) { |
| return; |
| } |
| if (node->kids) { |
| for (size_t i = 0; node->kids[i] != NULL; i++) { |
| S_destroy_json(node->kids[i]); |
| } |
| } |
| free(node->string); |
| free(node->kids); |
| free(node); |
| } |
| |