aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPetri Lehtinen <petri@digip.org>2009-02-06 20:26:27 +0200
committerPetri Lehtinen <petri@digip.org>2009-05-12 21:44:01 +0300
commit17a69c2d66a86757877c3d4159e999da3a5434bf (patch)
tree442e1af51d4cc120c59b23da11f9f97355000cd0
downloadjansson-17a69c2d66a86757877c3d4159e999da3a5434bf.zip
jansson-17a69c2d66a86757877c3d4159e999da3a5434bf.tar.gz
jansson-17a69c2d66a86757877c3d4159e999da3a5434bf.tar.bz2
Initial import
-rw-r--r--.gitignore2
-rw-r--r--include/jansson.h92
-rw-r--r--src/Makefile14
-rw-r--r--src/dump.c46
-rw-r--r--src/hashtable.c326
-rw-r--r--src/hashtable.h121
-rw-r--r--src/load.c443
-rw-r--r--src/value.c317
8 files changed, 1361 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e0292b1
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.o
+*.a
diff --git a/include/jansson.h b/include/jansson.h
new file mode 100644
index 0000000..d423bdd
--- /dev/null
+++ b/include/jansson.h
@@ -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
index 0000000..ebc67ad
--- /dev/null
+++ b/src/Makefile
@@ -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
index 0000000..4af0958
--- /dev/null
+++ b/src/dump.c
@@ -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
index 0000000..f95c849
--- /dev/null
+++ b/src/hashtable.c
@@ -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
index 0000000..6d66383
--- /dev/null
+++ b/src/hashtable.h
@@ -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
index 0000000..ace963a
--- /dev/null
+++ b/src/load.c
@@ -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
index 0000000..6ab44a6
--- /dev/null
+++ b/src/value.c
@@ -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 */
+}