417 lines
8.5 KiB
C
417 lines
8.5 KiB
C
/*
|
|
* Copyright (c) 2013 Hugh Bailey <obs.jim@gmail.com>
|
|
*
|
|
* 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;
|
|
}
|