Initial import
authorPetri Lehtinen <petri@digip.org>
Fri, 6 Feb 2009 18:26:27 +0000 (20:26 +0200)
committerPetri Lehtinen <petri@digip.org>
Tue, 12 May 2009 18:44:01 +0000 (21:44 +0300)
.gitignore [new file with mode: 0644]
include/jansson.h [new file with mode: 0644]
src/Makefile [new file with mode: 0644]
src/dump.c [new file with mode: 0644]
src/hashtable.c [new file with mode: 0644]
src/hashtable.h [new file with mode: 0644]
src/load.c [new file with mode: 0644]
src/value.c [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..e0292b1
--- /dev/null
@@ -0,0 +1,2 @@
+*.o
+*.a
diff --git a/include/jansson.h b/include/jansson.h
new file mode 100644 (file)
index 0000000..d423bdd
--- /dev/null
@@ -0,0 +1,92 @@
+#ifndef JANSSON_H
+#define JANSSON_H
+
+#include <stdio.h>
+#include <stdint.h>
+
+/* types */
+
+typedef enum {
+    JSON_OBJECT,
+    JSON_ARRAY,
+    JSON_STRING,
+    JSON_NUMBER,
+    JSON_TRUE,
+    JSON_FALSE,
+    JSON_NULL
+} json_type;
+
+typedef struct {
+    json_type type;
+    unsigned long refcount;
+} json_t;
+
+#define json_typeof(json)      ((json)->type)
+#define json_is_object(json)   (json && json_typeof(json) == JSON_OBJECT)
+#define json_is_array(json)    (json && json_typeof(json) == JSON_ARRAY)
+#define json_is_string(json)   (json && json_typeof(json) == JSON_STRING)
+#define json_is_number(json)   (json && json_typeof(json) == JSON_NUMBER)
+#define json_is_true(json)     (json && json_typeof(json) == JSON_TRUE)
+#define json_is_false(json)    (json && json_typeof(json) == JSON_FALSE)
+#define json_is_null(json)     (json && json_typeof(json) == JSON_NULL)
+
+/* construction, destruction, reference counting */
+
+json_t *json_object(void);
+json_t *json_array(void);
+json_t *json_string(const char *value);
+json_t *json_number(double value);
+json_t *json_true(void);
+json_t *json_false(void);
+json_t *json_null(void);
+
+json_t *json_clone(json_t *json);
+
+static inline json_t *json_incref(json_t *json)
+{
+    if(json)
+        ++json->refcount;
+    return json;
+}
+
+/* do not call json_delete directly */
+void json_delete(json_t *json);
+
+static inline void json_decref(json_t *json)
+{
+    if(json && --json->refcount == 0)
+        json_delete(json);
+}
+
+
+/* getters, setters, manipulation */
+
+json_t *json_object_get(const json_t *object, const char *key);
+int json_object_set(json_t *object, const char *key, json_t *value);
+int json_object_del(json_t *object, const char *key);
+
+unsigned int json_array_size(const json_t *array);
+json_t *json_array_get(const json_t *array, unsigned int index);
+int json_array_set(json_t *array, unsigned int index, json_t *value);
+int json_array_append(json_t *array, json_t *value);
+
+const char *json_string_value(const json_t *json);
+double json_number_value(const json_t *json);
+
+
+/* loading, printing */
+
+const char *json_get_error(void);
+
+json_t *json_load(const char *path);
+json_t *json_loads(const char *input);
+json_t *json_loadf(FILE *input);
+
+#define JSON_INDENT(n)   (n & 0xFF)
+#define JSON_SORT_KEYS   0x100
+
+int json_dump(const json_t *json, const char *path, uint32_t flags);
+char *json_dumps(const json_t *json, uint32_t flags);
+int json_dumpf(const json_t *json, FILE *output, uint32_t flags);
+
+#endif
diff --git a/src/Makefile b/src/Makefile
new file mode 100644 (file)
index 0000000..ebc67ad
--- /dev/null
@@ -0,0 +1,14 @@
+CFLAGS = -I../include -std=c99 -Wall -Wextra -Werror -g -O0
+
+OBJS = dump.o load.o value.o hashtable.o
+
+LIB = libjansson.a
+
+all: $(LIB)
+
+$(LIB): $(OBJS)
+       ar crsv $@ $^
+
+clean:
+       rm -f -- $(OBJS)
+       rm -f -- $(LIB)
diff --git a/src/dump.c b/src/dump.c
new file mode 100644 (file)
index 0000000..4af0958
--- /dev/null
@@ -0,0 +1,46 @@
+#include <jansson.h>
+
+char *json_dumps(const json_t *json, uint32_t flags)
+{
+    (void)flags;
+
+    switch(json_typeof(json)) {
+        case JSON_NULL:
+            printf("null");
+            break;
+
+        case JSON_TRUE:
+            printf("true");
+            break;
+
+        case JSON_FALSE:
+            printf("false");
+            break;
+
+        case JSON_NUMBER:
+            printf("%f", json_number_value(json));
+            break;
+
+        case JSON_STRING:
+            printf("%s", json_string_value(json));
+            break;
+
+        case JSON_ARRAY: {
+            int i, n = json_array_size(json);
+            printf("[");
+            for(i = 0; i < n; ++i) {
+                json_dumps(json_array_get(json, i), 0);
+                if(i < n - 1)
+                    printf(", ");
+            }
+            printf("]");
+            break;
+        }
+
+        default:
+            printf("<object>");
+            break;
+    }
+    return NULL;
+}
+
diff --git a/src/hashtable.c b/src/hashtable.c
new file mode 100644 (file)
index 0000000..f95c849
--- /dev/null
@@ -0,0 +1,326 @@
+/*
+ * Copyright (c) 2009 Petri Lehtinen <petri@digip.org>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#include <stdlib.h>
+#include "hashtable.h"
+
+#define container_of(ptr_, type_, member_)                      \
+    ((type_ *)((char *)ptr_ - (size_t)&((type_ *)0)->member_))
+
+typedef struct list_t {
+  struct list_t *prev;
+  struct list_t *next;
+} list_t;
+
+typedef struct {
+    void *key;
+    void *value;
+    unsigned int hash;
+    list_t list;
+} pair_t;
+
+#define list_to_pair(list_)  container_of(list_, pair_t, list)
+
+typedef struct {
+    list_t *first;
+    list_t *last;
+} bucket_t;
+
+struct hashtable {
+    unsigned int size;
+    bucket_t *buckets;
+    unsigned int num_buckets;  /* index to primes[] */
+    list_t list;
+
+    key_hash_fn hash_key;
+    key_cmp_fn cmp_keys;  /* returns non-zero for equal keys */
+    free_fn free_key;
+    free_fn free_value;
+};
+
+static inline void list_init(list_t *list)
+{
+    list->next = list;
+    list->prev = list;
+}
+
+static inline void list_insert(list_t *list, list_t *node)
+{
+    node->next = list;
+    node->prev = list->prev;
+    list->prev->next = node;
+    list->prev = node;
+}
+
+static inline void list_remove(list_t *list)
+{
+    list->prev->next = list->next;
+    list->next->prev = list->prev;
+}
+
+static inline int bucket_is_empty(hashtable_t *hashtable, bucket_t *bucket)
+{
+    return bucket->first == &hashtable->list && bucket->first == bucket->last;
+}
+
+static void insert_to_bucket(hashtable_t *hashtable, bucket_t *bucket,
+                             list_t *list)
+{
+    if(bucket_is_empty(hashtable, bucket))
+    {
+        list_insert(&hashtable->list, list);
+        bucket->first = bucket->last = list;
+    }
+    else
+    {
+        list_insert(bucket->first, list);
+        bucket->first = list;
+    }
+}
+
+static unsigned int primes[] = {
+    5, 13, 23, 53, 97, 193, 389, 769, 1543, 3079, 6151, 12289, 24593,
+    49157, 98317, 196613, 393241, 786433, 1572869, 3145739, 6291469,
+    12582917, 25165843, 50331653, 100663319, 201326611, 402653189,
+    805306457, 1610612741
+};
+static const unsigned int num_primes = sizeof(primes) / sizeof(unsigned int);
+
+static inline unsigned int num_buckets(hashtable_t *hashtable)
+{
+    return primes[hashtable->num_buckets];
+}
+
+
+static pair_t *hashtable_find_pair(hashtable_t *hashtable, bucket_t *bucket,
+                                   const void *key, unsigned int hash)
+{
+    list_t *list;
+    pair_t *pair;
+
+    if(bucket_is_empty(hashtable, bucket))
+        return NULL;
+
+    list = bucket->first;
+    while(1)
+    {
+        pair = list_to_pair(list);
+        if(pair->hash == hash && hashtable->cmp_keys(pair->key, key))
+            return pair;
+
+        if(list == bucket->last)
+            break;
+
+        list = list->next;
+    }
+
+    return NULL;
+}
+
+/* returns 0 on success, -1 if key was not found */
+static int hashtable_do_del(hashtable_t *hashtable,
+                            const void *key, unsigned int hash)
+{
+    pair_t *pair;
+    bucket_t *bucket;
+    unsigned int index;
+
+    index = hash % num_buckets(hashtable);
+    bucket = &hashtable->buckets[index];
+
+    pair = hashtable_find_pair(hashtable, bucket, key, hash);
+    if(!pair)
+        return -1;
+
+    if(&pair->list == bucket->first && &pair->list == bucket->last)
+        bucket->first = bucket->last = &hashtable->list;
+
+    else if(&pair->list == bucket->first)
+        bucket->first = pair->list.next;
+
+    else if(&pair->list == bucket->last)
+        bucket->last = pair->list.prev;
+
+    list_remove(&pair->list);
+
+    if(hashtable->free_key)
+        hashtable->free_key(pair->key);
+    if(hashtable->free_value)
+        hashtable->free_value(pair->value);
+
+    free(pair);
+    hashtable->size--;
+
+    return 0;
+}
+
+static int hashtable_do_rehash(hashtable_t *hashtable)
+{
+    list_t *list, *next;
+    pair_t *pair;
+    unsigned int i, index, new_size;
+
+    free(hashtable->buckets);
+
+    hashtable->num_buckets++;
+    new_size = num_buckets(hashtable);
+
+    hashtable->buckets = malloc(new_size * sizeof(bucket_t));
+    if(!hashtable->buckets)
+        return -1;
+
+    for(i = 0; i < num_buckets(hashtable); i++)
+    {
+        hashtable->buckets[i].first = hashtable->buckets[i].last =
+            &hashtable->list;
+    }
+
+    list = hashtable->list.next;
+    list_init(&hashtable->list);
+
+    for(; list != &hashtable->list; list = next) {
+        next = list->next;
+        pair = list_to_pair(list);
+        index = pair->hash % new_size;
+        insert_to_bucket(hashtable, &hashtable->buckets[index], &pair->list);
+    }
+
+    return 0;
+}
+
+
+hashtable_t *hashtable_new(key_hash_fn hash_key, key_cmp_fn cmp_keys,
+                           free_fn free_key, free_fn free_value)
+{
+    unsigned int i;
+    hashtable_t *hashtable = malloc(sizeof(hashtable_t));
+    if(!hashtable)
+        return NULL;
+
+    hashtable->size = 0;
+    hashtable->num_buckets = 0;  /* index to primes[] */
+    hashtable->buckets = malloc(num_buckets(hashtable) * sizeof(bucket_t));
+    if(!hashtable->buckets)
+    {
+        free(hashtable);
+        return NULL;
+    }
+    list_init(&hashtable->list);
+
+    hashtable->hash_key = hash_key;
+    hashtable->cmp_keys = cmp_keys;
+    hashtable->free_key = free_key;
+    hashtable->free_value = free_value;
+
+    for(i = 0; i < num_buckets(hashtable); i++)
+    {
+        hashtable->buckets[i].first = hashtable->buckets[i].last =
+            &hashtable->list;
+    }
+
+    return hashtable;
+}
+
+void hashtable_free(hashtable_t *hashtable)
+{
+    list_t *list, *next;
+    pair_t *pair;
+    for(list = hashtable->list.next; list != &hashtable->list; list = next)
+    {
+        next = list->next;
+        pair = list_to_pair(list);
+        if(hashtable->free_key)
+            hashtable->free_key(pair->key);
+        if(hashtable->free_value)
+            hashtable->free_value(pair->value);
+        free(pair);
+    }
+
+    free(hashtable->buckets);
+    free(hashtable);
+}
+
+int hashtable_set(hashtable_t *hashtable, void *key, void *value)
+{
+    pair_t *pair;
+    bucket_t *bucket;
+    unsigned int hash, index;
+
+    hash = hashtable->hash_key(key);
+
+    /* if the key already exists, delete it */
+    hashtable_do_del(hashtable, key, hash);
+
+    /* rehash if the load ratio exceeds 1 */
+    if(hashtable->size >= num_buckets(hashtable))
+        if(hashtable_do_rehash(hashtable))
+            return -1;
+
+    pair = malloc(sizeof(pair_t));
+    if(!pair)
+        return -1;
+
+    pair->key = key;
+    pair->value = value;
+    pair->hash = hash;
+
+    index = hash % num_buckets(hashtable);
+    bucket = &hashtable->buckets[index];
+
+    list_init(&pair->list);
+    insert_to_bucket(hashtable, bucket, &pair->list);
+
+    hashtable->size++;
+    return 0;
+}
+
+void *hashtable_get(hashtable_t *hashtable, const void *key)
+{
+    pair_t *pair;
+    unsigned int hash;
+    bucket_t *bucket;
+
+    hash = hashtable->hash_key(key);
+    bucket = &hashtable->buckets[hash % num_buckets(hashtable)];
+
+    pair = hashtable_find_pair(hashtable, bucket, key, hash);
+    if(!pair)
+        return NULL;
+
+    return pair->value;
+}
+
+int hashtable_del(hashtable_t *hashtable, const void *key)
+{
+    unsigned int hash = hashtable->hash_key(key);
+    return hashtable_do_del(hashtable, key, hash);
+}
+
+void *hashtable_iter(hashtable_t *hashtable)
+{
+    return hashtable_iter_next(hashtable, &hashtable->list);
+}
+
+void *hashtable_iter_next(hashtable_t *hashtable, void *iter)
+{
+    list_t *list = (list_t *)iter;
+    if(list->next == &hashtable->list)
+        return NULL;
+    return list->next;
+}
+
+void *hashtable_iter_key(void *iter)
+{
+    pair_t *pair = list_to_pair((list_t *)iter);
+    return pair->key;
+}
+
+void *hashtable_iter_value(void *iter)
+{
+    pair_t *pair = list_to_pair((list_t *)iter);
+    return pair->value;
+}
diff --git a/src/hashtable.h b/src/hashtable.h
new file mode 100644 (file)
index 0000000..6d66383
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (c) 2009 Petri Lehtinen <petri@digip.org>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the MIT license. See LICENSE for details.
+ */
+
+#ifndef HASHTABLE_H
+#define HASHTABLE_H
+
+typedef struct hashtable hashtable_t;
+
+typedef unsigned int (*key_hash_fn)(const void *key);
+typedef int (*key_cmp_fn)(const void *key1, const void *key2);
+typedef void (*free_fn)(void *key);
+
+/**
+ * hashtable_new - Create a hashtable object
+ *
+ * @hash_key: The key hashing function
+ * @cmp_keys: The key compare function. Returns non-zero for equal and
+ *     zero for unequal unequal keys
+ * @free_key: If non-NULL, called for a key that is no longer referenced.
+ * @free_value: If non-NULL, called for a value that is no longer referenced.
+ *
+ * Returns a new hashtable object that should be freed with
+ * hashtable_free when it's no longer used.
+ */
+hashtable_t *hashtable_new(key_hash_fn hash_key, key_cmp_fn cmp_keys,
+                           free_fn free_key, free_fn free_value);
+
+/**
+ * hashtable_free - Destroy a hashtable object
+ *
+ * @hashtable: The hashtable
+ */
+void hashtable_free(hashtable_t *hashtable);
+
+/**
+ * hashtable_set - Add/modify value in hashtable
+ *
+ * @hashtable: The hashtable object
+ * @key: The key
+ * @value: The value
+ *
+ * If a value with the given key already exists, its value is replaced
+ * with the new value.
+ *
+ * Key and value are "stealed" in the sense that hashtable frees them
+ * automatically when they are no longer used. The freeing is
+ * accomplished by calling free_key and free_value functions that were
+ * supplied to hashtable_new. In case one or both of the free
+ * functions is NULL, the corresponding item is not "stealed".
+ *
+ * Returns 0 on success, -1 on failure (out of memory).
+ */
+int hashtable_set(hashtable_t *hashtable, void *key, void *value);
+
+/**
+ * hashtable_get - Get a value associated with a key
+ *
+ * @hashtable: The hashtable object
+ * @key: The key
+ *
+ * Returns value if it is found, or NULL otherwise.
+ */
+void *hashtable_get(hashtable_t *hashtable, const void *key);
+
+/**
+ * hashtable_del - Remove a value from the hashtable
+ *
+ * @hashtable: The hashtable object
+ * @key: The key
+ *
+ * Returns 0 on success, or -1 if the key was not found.
+ */
+int hashtable_del(hashtable_t *hashtable, const void *key);
+
+/**
+ * hashtable_iter - Iterate over hashtable
+ *
+ * @hashtable: The hashtable object
+ *
+ * Returns an opaque iterator to the first element in the hashtable.
+ * The iterator should be passed to hashtable_iter_* functions.
+ * The hashtable items are not iterated over in any particular order.
+ *
+ * There's no need to free the iterator in any way. The iterator is
+ * valid as long as the item that is referenced by the iterator is not
+ * deleted. Other values may be added or deleted. In particular,
+ * hashtable_iter_next() may be called on an iterator, and after that
+ * the key/value pair pointed by the old iterator may be deleted.
+ */
+void *hashtable_iter(hashtable_t *hashtable);
+
+/**
+ * hashtable_iter_next - Advance an iterator
+ *
+ * @hashtable: The hashtable object
+ * @iter: The iterator
+ *
+ * Returns a new iterator pointing to the next element in the
+ * hashtable or NULL if the whole hastable has been iterated over.
+ */
+void *hashtable_iter_next(hashtable_t *hashtable, void *iter);
+
+/**
+ * hashtable_iter_key - Retrieve the key pointed by an iterator
+ *
+ * @iter: The iterator
+ */
+void *hashtable_iter_key(void *iter);
+
+/**
+ * hashtable_iter_value - Retrieve the value pointed by an iterator
+ *
+ * @iter: The iterator
+ */
+void *hashtable_iter_value(void *iter);
+
+#endif
diff --git a/src/load.c b/src/load.c
new file mode 100644 (file)
index 0000000..ace963a
--- /dev/null
@@ -0,0 +1,443 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include <jansson.h>
+
+
+#define JSON_TOKEN_INVALID         -1
+#define JSON_TOKEN_EOF              0
+#define JSON_TOKEN_STRING         256
+#define JSON_TOKEN_NUMBER         257
+#define JSON_TOKEN_TRUE           258
+#define JSON_TOKEN_FALSE          259
+#define JSON_TOKEN_NULL           260
+
+typedef struct {
+    const char *input;
+    const char *start;
+    int token;
+    int line, column;
+    union {
+        char *string;
+        double number;
+    } value;
+} json_lex;
+
+
+/*** error reporting ***/
+
+static __thread char *json_error_msg = NULL;
+
+static void json_set_error(const json_lex *lex, const char *msg)
+{
+    free(json_error_msg);
+    if(*lex->start)
+        asprintf(&json_error_msg, "%s near '%.*s' on line %d", msg,
+                 (int)(lex->input - lex->start), lex->start, lex->line);
+    else
+        asprintf(&json_error_msg, "%s near end of file", msg);
+}
+
+const char *json_get_error(void)
+{
+    if(!json_error_msg)
+        json_error_msg = strdup("success");
+    return json_error_msg;
+}
+
+
+/*** lexical analyzer ***/
+
+static void json_scan_string(json_lex *lex)
+{
+    /* skip the " */
+    const char *p = lex->input + 1;
+    char *t;
+
+    lex->token = JSON_TOKEN_INVALID;
+
+    while(*p != '"') {
+        if(*p == '\0') {
+            /* unterminated string literal */
+            goto out;
+        }
+
+        if(0 <= *p && *p <= 31) {
+            /* control character */
+            goto out;
+        }
+        else if(*p == '\\') {
+            p++;
+            if(*p == 'u') {
+                p++;
+                for(int i = 0; i < 4; i++, p++) {
+                    if(!isxdigit(*p))
+                        goto out;
+                }
+            }
+            if(*p == '"' || *p == '\\' || *p == '/' || *p == 'b' ||
+               *p == 'f' || *p == 'n' || *p == 'r' || *p == 't')
+                p++;
+            else
+                goto out;
+        }
+        else
+            p++;
+    }
+
+    /* the actual value is at most of the same length as the source
+       string */
+    lex->value.string = malloc(p - lex->start);
+    if(!lex->value.string) {
+        /* this is not very nice, since TOKEN_INVALID is returned */
+        goto out;
+    }
+
+    /* the target */
+    t = lex->value.string;
+
+    p = lex->input + 1;
+    while(*p != '"') {
+        if(*p == '\\') {
+            p++;
+            if(*p == 'u') {
+                /* TODO: \uXXXX not supported yet */
+                free(lex->value.string);
+                lex->value.string = NULL;
+                goto out;
+            } else {
+                switch(*p) {
+                    case '"': case '\\': case '/':
+                        *t = *p; break;
+                    case 'b': *t = '\b'; break;
+                    case 'f': *t = '\f'; break;
+                    case 'n': *t = '\n'; break;
+                    case 'r': *t = '\r'; break;
+                    case 't': *t = '\t'; break;
+                    default: assert(0);
+                }
+            }
+        }
+        else
+            *t = *p;
+
+        t++;
+        p++;
+    }
+    /* skip the " */
+    p++;
+
+    *t = '\0';
+    lex->token = JSON_TOKEN_STRING;
+
+out:
+    lex->input = p;
+}
+
+static void json_scan_number(json_lex *lex)
+{
+    const char *p = lex->input;
+    char *end;
+
+    lex->token = JSON_TOKEN_INVALID;
+
+    if(*p == '-')
+        p++;
+
+    if(*p == '0')
+        p++;
+    else /* *p != '0' */ {
+        p++;
+        while(isdigit(*p))
+            p++;
+    }
+
+    if(*p == '.') {
+        p++;
+        if(!isdigit(*(p++)))
+            goto out;
+
+        while(isdigit(*p))
+            p++;
+    }
+
+    if(*p == 'E' || *p == 'e') {
+        p++;
+        if(*p == '+' || *p == '-')
+            p++;
+
+        if(!isdigit(*(p++)))
+            goto out;
+
+        while(isdigit(*p))
+            p++;
+    }
+
+    lex->token = JSON_TOKEN_NUMBER;
+
+    lex->value.number = strtod(lex->start, &end);
+    assert(end == p);
+
+out:
+    lex->input = p;
+}
+
+static int json_lex_scan(json_lex *lex)
+{
+    char c;
+
+    if(lex->token == JSON_TOKEN_STRING) {
+      free(lex->value.string);
+      lex->value.string = NULL;
+    }
+
+    while(isspace(*lex->input)) {
+        if(*lex->input == '\n')
+            lex->line++;
+
+        lex->input++;
+    }
+
+    lex->start = lex->input;
+    c = *lex->input;
+
+    if(c == '\0')
+        lex->token = JSON_TOKEN_EOF;
+
+    else if(c == '{' || c == '}' || c == '[' || c == ']' ||
+            c == ':' || c == ',') {
+        lex->token = c;
+        lex->input++;
+    }
+
+    else if(c == '"')
+        json_scan_string(lex);
+
+    else if(isdigit(c) || c == '-')
+        json_scan_number(lex);
+
+    else if(isalpha(c)) {
+        /* eat up the whole identifier for clearer error messages */
+        int len;
+
+        while(isalpha(*lex->input))
+            lex->input++;
+        len = lex->input - lex->start;
+
+        if(strncmp(lex->start, "true", len) == 0)
+            lex->token = JSON_TOKEN_TRUE;
+        else if(strncmp(lex->start, "false", len) == 0)
+            lex->token = JSON_TOKEN_FALSE;
+        else if(strncmp(lex->start, "null", len) == 0)
+            lex->token = JSON_TOKEN_NULL;
+        else
+            lex->token = JSON_TOKEN_INVALID;
+    }
+
+    else {
+        lex->token = JSON_TOKEN_INVALID;
+        lex->input++;
+    }
+
+    return lex->token;
+}
+
+static int json_lex_init(json_lex *lex, const char *input)
+{
+    lex->input = input;
+    lex->token = JSON_TOKEN_INVALID;
+    lex->line = 1;
+
+    json_lex_scan(lex);
+    return 0;
+}
+
+static void json_lex_close(json_lex *lex)
+{
+    if(lex->token == JSON_TOKEN_STRING)
+        free(lex->value.string);
+}
+
+
+/*** parser ***/
+
+static json_t *json_parse(json_lex *lex);
+
+static json_t *json_parse_object(json_lex *lex)
+{
+    json_t *object = json_object();
+    if(!object)
+        return NULL;
+
+    json_lex_scan(lex);
+    while(1) {
+        char *key;
+        json_t *value;
+
+        if(lex->token != JSON_TOKEN_STRING) {
+            json_set_error(lex, "string expected");
+            goto error;
+        }
+
+        key = strdup(lex->value.string);
+        if(!key)
+            return NULL;
+
+        json_lex_scan(lex);
+        if(lex->token != ':') {
+            json_set_error(lex, "':' expected");
+            goto error;
+        }
+
+        json_lex_scan(lex);
+
+        value = json_parse(lex);
+        if(!value)
+            goto error;
+
+        if(json_object_set(object, key, value)) {
+            json_decref(value);
+            goto error;
+        }
+
+        json_decref(value);
+        free(key);
+
+        if(lex->token != ',')
+            break;
+
+        json_lex_scan(lex);
+    }
+
+    if(lex->token != '}') {
+        json_set_error(lex, "'}' expected");
+        goto error;
+    }
+
+    return object;
+
+error:
+    json_decref(object);
+    return NULL;
+}
+
+static json_t *json_parse_array(json_lex *lex)
+{
+    json_t *array = json_array();
+    if(!array)
+        return NULL;
+
+    json_lex_scan(lex);
+    if(lex->token != ']') {
+        while(1) {
+            json_t *elem = json_parse(lex);
+            if(!elem)
+                goto error;
+
+            if(json_array_append(array, elem)) {
+                json_decref(elem);
+                goto error;
+            }
+            json_decref(elem);
+
+            if(lex->token != ',')
+                break;
+
+            json_lex_scan(lex);
+        }
+    }
+
+    if(lex->token != ']') {
+        json_set_error(lex, "']' expected");
+        goto error;
+    }
+
+    return array;
+
+error:
+    json_decref(array);
+    return NULL;
+}
+
+static json_t *json_parse(json_lex *lex)
+{
+    json_t *json;
+
+    switch(lex->token) {
+        case JSON_TOKEN_STRING: {
+            json = json_string(lex->value.string);
+            break;
+        }
+
+        case JSON_TOKEN_NUMBER: {
+            json = json_number(lex->value.number);
+            break;
+        }
+
+        case JSON_TOKEN_TRUE:
+            json = json_true();
+            break;
+
+        case JSON_TOKEN_FALSE:
+            json = json_false();
+            break;
+
+        case JSON_TOKEN_NULL:
+            json = json_null();
+            break;
+
+        case '{':
+            json = json_parse_object(lex);
+            break;
+
+        case '[':
+            json = json_parse_array(lex);
+            break;
+
+        case JSON_TOKEN_INVALID:
+            json_set_error(lex, "invalid token");
+            return NULL;
+
+        default:
+            json_set_error(lex, "unexpected token");
+            return NULL;
+    }
+
+    if(!json)
+        return NULL;
+
+    json_lex_scan(lex);
+    return json;
+}
+
+json_t *json_loads(const char *string)
+{
+    json_lex lex;
+    json_t *result = NULL;
+
+    if(json_lex_init(&lex, string))
+        return NULL;
+
+    if(lex.token != '[' && lex.token != '{') {
+        json_set_error(&lex, "'[' or '{' expected");
+        goto out;
+    }
+
+    result = json_parse(&lex);
+    if(!result)
+        goto out;
+
+    if(lex.token != JSON_TOKEN_EOF) {
+        json_set_error(&lex, "end of file expected");
+        json_decref(result);
+        result = NULL;
+    }
+
+out:
+    json_lex_close(&lex);
+    return result;
+}
diff --git a/src/value.c b/src/value.c
new file mode 100644 (file)
index 0000000..6ab44a6
--- /dev/null
@@ -0,0 +1,317 @@
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <string.h>
+
+#include <jansson.h>
+#include "hashtable.h"
+
+#define max(a, b)  ((a) > (b) ? (a) : (b))
+
+#define container_of(ptr_, type_, member_)  \
+    ((type_ *)((char *)ptr_ - (size_t)&((type_ *)0)->member_))
+
+typedef struct {
+    json_t json;
+    hashtable_t *hashtable;
+} json_object_t;
+
+typedef struct {
+    json_t json;
+    unsigned int size;
+    unsigned int entries;
+    json_t **table;
+} json_array_t;
+
+typedef struct {
+    json_t json;
+    char *value;
+} json_string_t;
+
+typedef struct {
+    json_t json;
+    double value;
+} json_number_t;
+
+#define json_to_object(json_)  container_of(json_, json_object_t, json)
+#define json_to_array(json_)   container_of(json_, json_array_t, json)
+#define json_to_string(json_)  container_of(json_, json_string_t, json)
+#define json_to_number(json_)  container_of(json_, json_number_t, json)
+
+static inline void json_init(json_t *json, json_type type)
+{
+    json->type = type;
+    json->refcount = 1;
+}
+
+
+/*** object ***/
+
+static unsigned int hash_string(const void *key)
+{
+    const char *str = (const char *)key;
+    unsigned int hash = 5381;
+    unsigned int c;
+
+    while((c = (unsigned int)*str))
+    {
+        hash = ((hash << 5) + hash) + c;
+        str++;
+    }
+
+    return hash;
+}
+
+static int string_equal(const void *key1, const void *key2)
+{
+    return strcmp((const char *)key1, (const char *)key2) == 0;
+}
+
+static void value_decref(void *value)
+{
+    json_decref((json_t *)value);
+}
+
+json_t *json_object(void)
+{
+    json_object_t *object = malloc(sizeof(json_object_t));
+    if(!object)
+        return NULL;
+    json_init(&object->json, JSON_OBJECT);
+
+    object->hashtable =
+      hashtable_new(hash_string, string_equal, free, value_decref);
+    if(!object->hashtable)
+    {
+        free(object);
+        return NULL;
+    }
+    return &object->json;
+}
+
+static void json_delete_object(json_object_t *object)
+{
+    hashtable_free(object->hashtable);
+    free(object);
+}
+
+json_t *json_object_get(const json_t *json, const char *key)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json))
+        return NULL;
+
+    return hashtable_get(object->hashtable, key);
+}
+
+int json_object_del(json_t *json, const char *key)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json))
+        return -1;
+
+    object = json_to_object(json);
+    return hashtable_del(object->hashtable, key);
+}
+
+int json_object_set(json_t *json, const char *key, json_t *value)
+{
+    json_object_t *object;
+
+    if(!json_is_object(json))
+        return -1;
+
+    object = json_to_object(json);
+    return hashtable_set(object->hashtable, strdup(key), json_incref(value));
+}
+
+
+/*** array ***/
+
+json_t *json_array(void)
+{
+    json_array_t *array = malloc(sizeof(json_array_t));
+    if(!array)
+      return NULL;
+    json_init(&array->json, JSON_ARRAY);
+
+    array->entries = 0;
+    array->size = 0;
+    array->table = NULL;
+
+    return &array->json;
+}
+
+static void json_delete_array(json_array_t *array)
+{
+    unsigned int i;
+
+    for(i = 0; i < array->entries; i++)
+        json_decref(array->table[i]);
+
+    free(array->table);
+    free(array);
+}
+
+unsigned int json_array_size(const json_t *json)
+{
+    if(!json_is_array(json))
+        return 0;
+
+    return json_to_array(json)->entries;
+}
+
+json_t *json_array_get(const json_t *json, unsigned int index)
+{
+    json_array_t *array;
+    if(!json_is_array(json))
+        return NULL;
+    array = json_to_array(json);
+
+    if(index >= array->size)
+        return NULL;
+
+    return array->table[index];
+}
+
+int json_array_set(json_t *json, unsigned int index, json_t *value)
+{
+    json_array_t *array;
+    if(!json_is_array(json))
+        return -1;
+    array = json_to_array(json);
+
+    if(index >= array->size)
+        return -1;
+
+    array->table[index] = json_incref(value);
+    return 0;
+}
+
+int json_array_append(json_t *json, json_t *value)
+{
+    json_array_t *array;
+    if(!json_is_array(json))
+        return -1;
+    array = json_to_array(json);
+
+    if(array->entries == array->size) {
+        array->size = max(8, array->size * 2);
+        array->table = realloc(array->table, array->size * sizeof(json_t *));
+        if(!array->table)
+            return -1;
+    }
+
+    array->table[array->entries] = json_incref(value);
+    array->entries++;
+
+    return 0;
+}
+
+
+/*** string ***/
+
+json_t *json_string(const char *value)
+{
+    json_string_t *string = malloc(sizeof(json_string_t));
+    if(!string)
+       return NULL;
+    json_init(&string->json, JSON_STRING);
+
+    string->value = strdup(value);
+    return &string->json;
+}
+
+const char *json_string_value(const json_t *json)
+{
+    if(!json_is_string(json))
+        return NULL;
+
+    return json_to_string(json)->value;
+}
+
+static void json_delete_string(json_string_t *string)
+{
+    free(string->value);
+    free(string);
+}
+
+json_t *json_number(double value)
+{
+    json_number_t *number = malloc(sizeof(json_number_t));
+    if(!number)
+       return NULL;
+    json_init(&number->json, JSON_NUMBER);
+
+    number->value = value;
+    return &number->json;
+}
+
+
+/*** number ***/
+
+double json_number_value(const json_t *json)
+{
+    if(!json_is_number(json))
+        return 0.0;
+
+    return json_to_number(json)->value;
+}
+
+static void json_delete_number(json_number_t *number)
+{
+    free(number);
+}
+
+
+/*** simple values ***/
+
+json_t *json_true(void)
+{
+    static json_t the_true = {
+        .type = JSON_TRUE,
+        .refcount = 1
+    };
+    return json_incref(&the_true);
+}
+
+
+json_t *json_false(void)
+{
+    static json_t the_false = {
+        .type = JSON_FALSE,
+        .refcount = 1
+    };
+    return json_incref(&the_false);
+}
+
+
+json_t *json_null(void)
+{
+    static json_t the_null = {
+        .type = JSON_NULL,
+        .refcount = 1
+    };
+    return json_incref(&the_null);
+}
+
+
+/*** deletion ***/
+
+void json_delete(json_t *json)
+{
+    if(json_is_object(json))
+        json_delete_object(json_to_object(json));
+
+    else if(json_is_array(json))
+        json_delete_array(json_to_array(json));
+
+    else if(json_is_string(json))
+        json_delete_string(json_to_string(json));
+
+    else if(json_is_number(json))
+        json_delete_number(json_to_number(json));
+
+    /* json_delete is not called for true, false or null */
+}