Initial import
This commit is contained in:
commit
17a69c2d66
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
*.o
|
||||
*.a
|
92
include/jansson.h
Normal file
92
include/jansson.h
Normal file
@ -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
|
14
src/Makefile
Normal file
14
src/Makefile
Normal file
@ -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)
|
46
src/dump.c
Normal file
46
src/dump.c
Normal file
@ -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;
|
||||
}
|
||||
|
326
src/hashtable.c
Normal file
326
src/hashtable.c
Normal file
@ -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;
|
||||
}
|
121
src/hashtable.h
Normal file
121
src/hashtable.h
Normal file
@ -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
|
443
src/load.c
Normal file
443
src/load.c
Normal file
@ -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;
|
||||
}
|
317
src/value.c
Normal file
317
src/value.c
Normal file
@ -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 */
|
||||
}
|
Loading…
Reference in New Issue
Block a user