/* * Copyright (c) 2013 Hugh Bailey * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "dstr.h" #include "text-lookup.h" #include "lexer.h" #include "platform.h" /* ------------------------------------------------------------------------- */ struct text_leaf { char *lookup, *value; }; static inline void text_leaf_destroy(struct text_leaf *leaf) { if (leaf) { bfree(leaf->lookup); bfree(leaf->value); bfree(leaf); } } /* ------------------------------------------------------------------------- */ struct text_node { struct dstr str; struct text_node *first_subnode; struct text_leaf *leaf; struct text_node *next; }; static void text_node_destroy(struct text_node *node) { struct text_node *subnode; if (!node) return; subnode = node->first_subnode; while (subnode) { struct text_node *destroy_node = subnode; subnode = subnode->next; text_node_destroy(destroy_node); } dstr_free(&node->str); if (node->leaf) text_leaf_destroy(node->leaf); bfree(node); } static struct text_node *text_node_bychar(struct text_node *node, char ch) { struct text_node *subnode = node->first_subnode; while (subnode) { if (!dstr_is_empty(&subnode->str) && subnode->str.array[0] == ch) return subnode; subnode = subnode->next; } return NULL; } static struct text_node *text_node_byname(struct text_node *node, const char *name) { struct text_node *subnode = node->first_subnode; while (subnode) { if (astrcmpi_n(subnode->str.array, name, subnode->str.len) == 0) return subnode; subnode = subnode->next; } return NULL; } /* ------------------------------------------------------------------------- */ struct text_lookup { struct dstr language; struct text_node *top; }; static void lookup_createsubnode(const char *lookup_val, struct text_leaf *leaf, struct text_node *node) { struct text_node *new = bzalloc(sizeof(struct text_node)); new->leaf = leaf; new->next = node->first_subnode; dstr_copy(&new->str, lookup_val); node->first_subnode = new; } static void lookup_splitnode(const char *lookup_val, size_t len, struct text_leaf *leaf, struct text_node *node) { struct text_node *split = bzalloc(sizeof(struct text_node)); dstr_copy(&split->str, node->str.array + len); split->leaf = node->leaf; split->first_subnode = node->first_subnode; node->first_subnode = split; dstr_resize(&node->str, len); if (lookup_val[len] != 0) { node->leaf = NULL; lookup_createsubnode(lookup_val + len, leaf, node); } else { node->leaf = leaf; } } static inline void lookup_replaceleaf(struct text_node *node, struct text_leaf *leaf) { text_leaf_destroy(node->leaf); node->leaf = leaf; } static void lookup_addstring(const char *lookup_val, struct text_leaf *leaf, struct text_node *node) { struct text_node *child; /* value already exists, so replace */ if (!lookup_val || !*lookup_val) { lookup_replaceleaf(node, leaf); return; } child = text_node_bychar(node, *lookup_val); if (child) { size_t len; for (len = 0; len < child->str.len; len++) { char val1 = child->str.array[len], val2 = lookup_val[len]; if (val1 >= 'A' && val1 <= 'Z') val1 += 0x20; if (val2 >= 'A' && val2 <= 'Z') val2 += 0x20; if (val1 != val2) break; } if (len == child->str.len) { lookup_addstring(lookup_val + len, leaf, child); return; } else { lookup_splitnode(lookup_val, len, leaf, child); } } else { lookup_createsubnode(lookup_val, leaf, node); } } static void lookup_getstringtoken(struct lexer *lex, struct strref *token) { const char *temp = lex->offset; bool was_backslash = false; while (*temp != 0 && *temp != '\n') { if (!was_backslash) { if (*temp == '\\') { was_backslash = true; } else if (*temp == '"') { temp++; break; } } else { was_backslash = false; } ++temp; } token->len += (size_t)(temp - lex->offset); if (*token->array == '"') { token->array++; token->len--; if (*(temp - 1) == '"') token->len--; } lex->offset = temp; } static bool lookup_gettoken(struct lexer *lex, struct strref *str) { struct base_token temp; base_token_clear(&temp); strref_clear(str); while (lexer_getbasetoken(lex, &temp, PARSE_WHITESPACE)) { char ch = *temp.text.array; if (!str->array) { /* comments are designated with a #, and end at LF */ if (ch == '#') { while (ch != '\n' && ch != 0) ch = *(++lex->offset); } else if (temp.type == BASETOKEN_WHITESPACE) { strref_copy(str, &temp.text); break; } else { strref_copy(str, &temp.text); if (ch == '"') { lookup_getstringtoken(lex, str); break; } else if (ch == '=') { break; } } } else { if (temp.type == BASETOKEN_WHITESPACE || *temp.text.array == '=') { lex->offset -= temp.text.len; break; } if (ch == '#') { lex->offset--; break; } str->len += temp.text.len; } } return (str->len != 0); } static inline bool lookup_goto_nextline(struct lexer *p) { struct strref val; bool success = true; strref_clear(&val); while (true) { if (!lookup_gettoken(p, &val)) { success = false; break; } if (*val.array == '\n') break; } return success; } static char *convert_string(const char *str, size_t len) { struct dstr out; out.array = bstrdup_n(str, len); out.capacity = len + 1; out.len = len; dstr_replace(&out, "\\n", "\n"); dstr_replace(&out, "\\t", "\t"); dstr_replace(&out, "\\r", "\r"); dstr_replace(&out, "\\\"", "\""); return out.array; } static void lookup_addfiledata(struct text_lookup *lookup, const char *file_data) { struct lexer lex; struct strref name, value; lexer_init(&lex); lexer_start(&lex, file_data); strref_clear(&name); strref_clear(&value); while (lookup_gettoken(&lex, &name)) { struct text_leaf *leaf; bool got_eq = false; if (*name.array == '\n') continue; getval: if (!lookup_gettoken(&lex, &value)) break; if (*value.array == '\n') continue; else if (!got_eq && *value.array == '=') { got_eq = true; goto getval; } leaf = bmalloc(sizeof(struct text_leaf)); leaf->lookup = bstrdup_n(name.array, name.len); leaf->value = convert_string(value.array, value.len); lookup_addstring(leaf->lookup, leaf, lookup->top); if (!lookup_goto_nextline(&lex)) break; } lexer_free(&lex); } static inline bool lookup_getstring(const char *lookup_val, const char **out, struct text_node *node) { struct text_node *child; char ch; if (!node) return false; child = text_node_byname(node, lookup_val); if (!child) return false; lookup_val += child->str.len; ch = *lookup_val; if (ch) return lookup_getstring(lookup_val, out, child); if (!child->leaf) return false; *out = child->leaf->value; return true; } /* ------------------------------------------------------------------------- */ lookup_t *text_lookup_create(const char *path) { struct text_lookup *lookup = bzalloc(sizeof(struct text_lookup)); if (!text_lookup_add(lookup, path)) { bfree(lookup); lookup = NULL; } return lookup; } bool text_lookup_add(lookup_t *lookup, const char *path) { struct dstr file_str; char *temp = NULL; FILE *file; file = os_fopen(path, "rb"); if (!file) return false; os_fread_utf8(file, &temp); dstr_init_move_array(&file_str, temp); fclose(file); if (!file_str.array) return false; if (!lookup->top) lookup->top = bzalloc(sizeof(struct text_node)); dstr_replace(&file_str, "\r", " "); lookup_addfiledata(lookup, file_str.array); dstr_free(&file_str); return true; } void text_lookup_destroy(lookup_t *lookup) { if (lookup) { dstr_free(&lookup->language); text_node_destroy(lookup->top); bfree(lookup); } } bool text_lookup_getstr(lookup_t *lookup, const char *lookup_val, const char **out) { if (lookup) return lookup_getstring(lookup_val, out, lookup->top); return false; }