Basic array/dict/string parsing finished.
authorethereal <ethereal@ethv.net>
Mon, 27 Jan 2014 01:30:15 +0000 (18:30 -0700)
committerethereal <ethereal@ethv.net>
Mon, 27 Jan 2014 01:30:15 +0000 (18:30 -0700)
src/CMakeLists.txt
src/main.c
src/yacjs.c
src/yacjs.h
src/yacjs_dict.c
src/yacjs_u8s.c [new file with mode: 0644]
src/yacjs_u8s.h [new file with mode: 0644]

index d2c4fa0..f110dec 100644 (file)
@@ -1,3 +1,4 @@
 add_definitions(-W -Wall -std=c99)
+add_definitions(-g)
 
-add_executable(yacjs_test yacjs.c yacjs_dict.c main.c)
+add_executable(yacjs_test yacjs.c yacjs_dict.c yacjs_u8s.c main.c)
index 39f17ab..5cf0aab 100644 (file)
@@ -3,5 +3,14 @@
 #include "yacjs.h"
 
 int main() {
+    struct yacjs_node *root = yacjs_parse("{\"foo\": \"bar\", \"baz\": [\"a\", \"b\", \"c\"]}");
+
+    printf("Result: %p\n", root);
+    printf("foo: %p\n", yacjs_node_dict_get(root, "foo"));
+    printf("foo string: %s\n", yacjs_node_str(yacjs_node_dict_get(root, "foo")));
+
+    struct yacjs_node *baz = yacjs_node_dict_get(root, "baz");
+    printf("baz: %p\n", baz);
+    printf("\tfirst element string: %s\n", yacjs_node_str(yacjs_node_array_elem(baz, 0)));
     return 0;
 }
index 78874ed..f99a38f 100644 (file)
@@ -1,7 +1,9 @@
+#include <stdio.h> // for debugging
 #include <stdlib.h>
 
 #include "yacjs.h"
 #include "yacjs_dict.h"
+#include "yacjs_u8s.h"
 
 struct yacjs_node {
     enum yacjs_node_type type;
@@ -18,8 +20,32 @@ struct yacjs_node {
     } data;
 };
 
+enum token_type {
+    TOKEN_STRING,
+    TOKEN_NUMBER,
+    TOKEN_OPENDICT,
+    TOKEN_CLOSEDICT,
+    TOKEN_OPENARRAY,
+    TOKEN_CLOSEARRAY,
+    TOKEN_COMMA,
+    TOKEN_COLON,
+    TOKEN_NONE,
+    TOKEN_ERROR,
+    TOKEN_TYPES
+};
+
 static enum yacjs_error last_error = YACJS_ERROR_NONE;
 
+static void destroy_helper(struct yacjs_node *node);
+static void skip_whitespace(const char ** const ptr);
+static const char *next_token(const char ** const ptr, int *length,
+    enum token_type *type);
+static const char *peek_token(const char ** const ptr, int *length,
+    enum token_type *type);
+static struct yacjs_node *parse_any(const char **string);
+static struct yacjs_node *parse_dict_contents(const char **string);
+static struct yacjs_node *parse_array_contents(const char **string);
+
 enum yacjs_error yacjs_last_error() {
     enum yacjs_error err = last_error;
     last_error = YACJS_ERROR_NONE;
@@ -27,7 +53,34 @@ enum yacjs_error yacjs_last_error() {
 }
 
 struct yacjs_node *yacjs_parse(const char *string) {
-    return NULL;
+    struct yacjs_node *ret = parse_any(&string);
+    // ensure there's nothing afterwards.
+    skip_whitespace(&string);
+    if(string[0] != 0) {
+        yacjs_destroy(ret);
+        last_error = YACJS_ERROR_PARSE;
+        return NULL;
+    }
+    return ret;
+}
+
+void yacjs_destroy(struct yacjs_node *node) {
+    if(!node) return;
+    destroy_helper(node);
+    free(node);
+}
+
+static void destroy_helper(struct yacjs_node *node) {
+    if(node->type == YACJS_NODE_ARRAY) {
+        for(int i = 0; i < node->data.array.entries_count; i ++) {
+            yacjs_destroy(node->data.array.entries + i);
+        }
+        free(node->data.array.entries);
+    }
+    else if(node->type == YACJS_NODE_DICT) {
+        yacjs_dict_destroy(node->data.dict,
+            (yacjs_dict_visitor)destroy_helper);
+    }
 }
 
 enum yacjs_node_type yacjs_node_type(struct yacjs_node *node) {
@@ -53,7 +106,7 @@ int64_t yacjs_node_num(struct yacjs_node *node) {
 double yacjs_node_float(struct yacjs_node *node) {
     if(node->type != YACJS_NODE_FLOAT) {
         last_error = YACJS_ERROR_TYPE;
-        return 1/0.0;
+        return 1/0.0; // NaN
     }
     return node->data.fp;
 }
@@ -89,3 +142,204 @@ struct yacjs_node *yacjs_node_dict_get(struct yacjs_node *node,
 
     return yacjs_dict_get(node->data.dict, key);
 }
+
+static void skip_whitespace(const char ** const ptr) {
+    while((**ptr == ' ' || **ptr == '\t') && **ptr != 0) {
+        *ptr = yacjs_u8s_next(*ptr);
+    }
+}
+
+static const char *next_token(const char ** const ptr, int *length,
+    enum token_type *type) {
+
+    *length = 0;
+    // skip whitespace
+    skip_whitespace(ptr);
+    if(*ptr == 0) {
+        *type = TOKEN_NONE;
+        return NULL;
+    }
+
+    if(**ptr == '"') {
+        // TODO: parse string PROPERLY
+        const char *start = ++(*ptr);
+        *length = 0;
+        while(**ptr != '"' && **ptr != 0) {
+            (*ptr) ++, (*length) ++;
+        }
+        if(**ptr == 0) return NULL;
+        // skip closing quotes
+        else (*ptr)++;
+
+        *type = TOKEN_STRING;
+        return start;
+    }
+    else if(**ptr == '{') {
+        *type = TOKEN_OPENDICT;
+        return (*ptr)++;
+    }
+    else if(**ptr == '}') {
+        *type = TOKEN_CLOSEDICT;
+        return (*ptr)++;
+    }
+    else if(**ptr == '[') {
+        *type = TOKEN_OPENARRAY;
+        return (*ptr)++;
+    }
+    else if(**ptr == ']') {
+        *type = TOKEN_CLOSEARRAY;
+        return (*ptr)++;
+    }
+    else if(**ptr == ',') {
+        *type = TOKEN_COMMA;
+        return (*ptr)++;
+    }
+    else if(**ptr == ':') {
+        *type = TOKEN_COLON;
+        return (*ptr)++;
+    }
+
+    *type = TOKEN_ERROR;
+
+    return NULL;
+}
+
+static const char *peek_token(const char ** const ptr, int *length,
+    enum token_type *type) {
+
+    const char *s = *ptr;
+
+    return next_token(&s, length, type);
+}
+
+static struct yacjs_node *parse_any(const char **string) {
+    enum token_type type;
+    const char *s;
+    int len;
+
+    s = next_token(string, &len, &type);
+    if(type == TOKEN_OPENDICT) return parse_dict_contents(string);
+    else if(type == TOKEN_OPENARRAY) return parse_array_contents(string);
+    else if(type == TOKEN_STRING) {
+        struct yacjs_node *build = malloc(sizeof(*build));
+        build->type = YACJS_NODE_STRING;
+        build->data.string = yacjs_u8s_strndup(s, len);
+        return build;
+    }
+    else if(type == TOKEN_NUMBER) {
+        struct yacjs_node *build = malloc(sizeof(*build));
+        build->type = YACJS_NODE_NUMBER;
+        build->data.number = strtoll(s, NULL, 0);
+        return build;
+    }
+
+    printf("Unknown token type %i\n", type);
+
+    return NULL;
+}
+
+static struct yacjs_node *parse_dict_contents(const char **string) {
+    enum token_type type;
+    const char *s;
+    int len;
+
+    struct yacjs_node *result = malloc(sizeof(*result));
+    result->type = YACJS_NODE_DICT;
+    result->data.dict = yacjs_dict_make();
+
+    while((s = next_token(string, &len, &type))) {
+        // closing dictionary token
+        if(type == TOKEN_CLOSEDICT) break;
+        // we expect a string here if it's not a closing dictionary
+        else if(type != TOKEN_STRING) {
+            return NULL;
+        }
+
+        char *ds = yacjs_u8s_strndup(s, len);
+
+        // expect a colon after the name
+        next_token(string, &len, &type);
+        if(type != TOKEN_COLON) {
+            return NULL;
+        }
+
+        struct yacjs_node *value = parse_any(string);
+
+        if(value == NULL) {
+            // TODO: leaked memory
+            return NULL;
+        }
+
+        yacjs_dict_set(result->data.dict, ds, value);
+        free(ds);
+
+        peek_token(string, &len, &type);
+        if(type == TOKEN_COMMA) {
+            // NOTE: this means things like {"a":1,} are accepted
+            // eat the comma
+            next_token(string, &len, &type);
+        }
+    }
+    if(!s) {
+        // TODO: leaked memory
+        return NULL;
+    }
+
+    return result;
+}
+
+static struct yacjs_node *parse_array_contents(const char **string) {
+    enum token_type type;
+    const char *s;
+    int len;
+
+    struct yacjs_node *result = malloc(sizeof(*result));
+    result->type = YACJS_NODE_ARRAY;
+    result->data.array.entries = NULL;
+    result->data.array.entries_size = 0;
+    result->data.array.entries_count = 0;
+
+    while((s = peek_token(string, &len, &type))) {
+        // closing dictionary token
+        if(type == TOKEN_CLOSEARRAY) {
+            // eat first
+            next_token(string, &len, &type);
+            break;
+        }
+        struct yacjs_node *next = parse_any(string);
+        if(!next) {
+            // TODO: leaked memory
+            return NULL;
+        }
+
+        if(result->data.array.entries_size
+            == result->data.array.entries_count) {
+
+            void *nmem = realloc(result->data.array.entries,
+                result->data.array.entries_size * 2 + 1);
+            if(nmem == NULL) {
+                last_error = YACJS_ERROR_MEMORY;
+                return NULL;
+            }
+
+            result->data.array.entries_size *= 2;
+            result->data.array.entries_size ++;
+            result->data.array.entries = nmem;
+        }
+        result->data.array.entries[result->data.array.entries_count++] = *next;
+        free(next);
+
+        peek_token(string, &len, &type);
+        if(type == TOKEN_COMMA) {
+            // NOTE: this means things like [1,2,] are accepted
+            // eat the comma
+            next_token(string, &len, &type);
+        }
+    }
+    if(!s) {
+        // TODO: leaked memory
+        return NULL;
+    }
+
+    return result;
+}
index bf6dfa2..b3884fb 100644 (file)
@@ -26,6 +26,7 @@ enum yacjs_error {
 enum yacjs_error yacjs_last_error();
 
 struct yacjs_node *yacjs_parse(const char *string);
+void yacjs_destroy(struct yacjs_node *node);
 
 enum yacjs_node_type yacjs_node_type(struct yacjs_node *node);
 const char *yacjs_node_str(struct yacjs_node *node);
index 1d1606d..d3d87db 100644 (file)
@@ -41,6 +41,7 @@ void yacjs_dict_destroy(struct yacjs_dict *dict, yacjs_dict_visitor visitor) {
             }
         }
     }
+    free(dict->entries);
     free(dict);
 }
 
@@ -105,4 +106,5 @@ static void insert_helper(struct yacjs_dict *dict, uint64_t hash,
         dict->entries[in].value = value;
         break;
     }
+    dict->entries_count ++;
 }
diff --git a/src/yacjs_u8s.c b/src/yacjs_u8s.c
new file mode 100644 (file)
index 0000000..d64c2bd
--- /dev/null
@@ -0,0 +1,91 @@
+#include <stdlib.h>
+#include <string.h>
+
+#include "yacjs_u8s.h"
+
+U8S_NAME(cptr) U8S_NAME(next)(U8S_NAME(cptr) str) {
+    // non-extended character?
+    if((*str & 0xc0) == 0x0) return str+1;
+    // the annoying case, in the middle of a character
+    if((*str & 0xc0) == 0x80) {
+        while((*str & 0xc0) == 0x80) str ++;
+        return str;
+    }
+    // beginning of extended character. We know what to do!
+    // two-byte?
+    if((*str & 0xe0) == 0xc0) return str+2;
+    // three-byte?
+    if((*str & 0xf0) == 0xe0) return str+3;
+    // four-byte?
+    if((*str & 0xf8) == 0xf0) return str+4;
+    // five-byte?
+    if((*str & 0xfc) == 0xf8) return str+5;
+    // six-byte?
+    if((*str & 0xfe) == 0xfc) return str+6;
+
+    // This should never happen, it's a malformed byte.
+    return str+1;
+}
+
+size_t U8S_NAME(strlen)(U8S_NAME(cptr) str) {
+    size_t result = 0;
+    while(*str != 0) {
+        str = U8S_NAME(next)(str);
+        result ++;
+    }
+    return result;
+}
+
+size_t U8S_NAME(strlenb)(U8S_NAME(cptr) str) {
+    size_t result = 0;
+    while(*str != 0) str ++, result ++;
+    return result;
+}
+
+size_t U8S_NAME(strnlenb)(U8S_NAME(cptr) str, size_t bufsiz) {
+    size_t result = 0;
+    while(*str != 0 && result < bufsiz) str ++, result ++;
+    return result;
+}
+
+U8S_NAME(ptr) U8S_NAME(strcpy)(U8S_NAME(ptr) target, U8S_NAME(cptr) src) {
+    do {
+        *(target ++) = *(src ++);
+    } while(*src != 0);
+    *target = 0;
+    return target;
+}
+
+U8S_NAME(ptr) U8S_NAME(strncpy)(U8S_NAME(ptr) target, U8S_NAME(cptr) src,
+    size_t bufsiz) {
+
+    size_t used = 0;
+    while(*src != 0) {
+        U8S_NAME(cptr) next = U8S_NAME(next)(src);
+        ptrdiff_t len = next-src;
+        if(used+len >= bufsiz) break;
+        memcpy(target + used, src, len);
+        used += len, src += len;
+    }
+    target[used] = 0;
+    return target;
+}
+
+int U8S_NAME(strcmp)(U8S_NAME(cptr) a, U8S_NAME(cptr) b) {
+    return U8S_NAME(strncmp)(a, b, (unsigned)-1);
+}
+
+int U8S_NAME(strncmp)(U8S_NAME(cptr) a, U8S_NAME(cptr) b, size_t bufsiz) {
+    return strncmp(a, b, bufsiz);
+}
+
+U8S_NAME(ptr) U8S_NAME(strdup)(U8S_NAME(cptr) s) {
+    return U8S_NAME(strcpy)(malloc(U8S_NAME(strlenb(s))), s);
+}
+
+U8S_NAME(ptr) U8S_NAME(strndup)(U8S_NAME(cptr) s, size_t bufsiz) {
+    return U8S_NAME(strncpy)(
+        malloc(U8S_NAME(strnlenb)(s, bufsiz)+1),
+        s,
+        bufsiz+1);
+}
diff --git a/src/yacjs_u8s.h b/src/yacjs_u8s.h
new file mode 100644 (file)
index 0000000..6414f43
--- /dev/null
@@ -0,0 +1,38 @@
+#ifndef YACJS_U8S_H
+#define YACJS_U8S_H
+
+#include <stddef.h>
+
+#define U8S_NAME(n) yacjs_u8s_ ## n
+
+typedef char * U8S_NAME(ptr);
+typedef const char * U8S_NAME(cptr);
+
+/* Get beginning of next UTF-8 character. */
+U8S_NAME(cptr) U8S_NAME(next)(U8S_NAME(cptr) str);
+/* Get length of UTF-8 string in characters. */
+size_t U8S_NAME(strlen)(U8S_NAME(cptr) str);
+/* Get length of UTF-8 string in bytes. */
+size_t U8S_NAME(strlenb)(U8S_NAME(cptr) str);
+/* Get length of UTF-8 string in bytes. */
+size_t U8S_NAME(strnlenb)(U8S_NAME(cptr) str, size_t bufsiz);
+/* Copy UTF-8 string into target. */
+U8S_NAME(ptr) U8S_NAME(strcpy)(U8S_NAME(ptr) target, U8S_NAME(cptr) src);
+/* Copy at most bufsiz bytes of the source UTF-8 string into target, respecting
+    UTF-8 character boundaries, and ensuring that the target is
+    NULL-terminated. */
+U8S_NAME(ptr) U8S_NAME(strncpy)(U8S_NAME(ptr) target, U8S_NAME(cptr) src,
+    size_t bufsiz);
+/* Compare two NULL-terminated UTF-8 strings. */
+int U8S_NAME(strcmp)(U8S_NAME(cptr) a, U8S_NAME(cptr) b);
+/* Compare at most the first bufsiz bytes of two possibly non-NULL-terminated
+    UTF-8 strings. */
+int U8S_NAME(strncmp)(U8S_NAME(cptr) a, U8S_NAME(cptr) b, size_t bufsiz);
+/* Create a copy of a NULL-terminated UTF-8 string with memory allocated via
+    malloc(). */
+U8S_NAME(ptr) U8S_NAME(strdup)(U8S_NAME(cptr) s);
+/* Create a copy of a NULL-terminated UTF-8 string with memory allocated via
+    malloc(). */
+U8S_NAME(ptr) U8S_NAME(strndup)(U8S_NAME(cptr) s, size_t bufsiz);
+
+#endif