aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--am/grecs.m44
-rw-r--r--configure.ac2
-rw-r--r--src/.gitignore5
-rw-r--r--src/Make-inst.am2
-rw-r--r--src/Make-shared.am7
-rw-r--r--src/Make-static.am4
-rw-r--r--src/Make.am12
-rw-r--r--src/json-gram.y371
-rw-r--r--src/json-lex.l204
-rw-r--r--src/json.h68
-rw-r--r--src/yytrans1
-rw-r--r--tests/.gitignore1
-rw-r--r--tests/Makefile.am10
-rw-r--r--tests/json.c286
-rw-r--r--tests/json00.at75
-rw-r--r--tests/json01.at89
-rw-r--r--tests/testsuite.at6
17 files changed, 1141 insertions, 6 deletions
diff --git a/am/grecs.m4 b/am/grecs.m4
index 9ba234d..5e558cb 100644
--- a/am/grecs.m4
+++ b/am/grecs.m4
@@ -70,6 +70,7 @@ AC_DEFUN([_GRECS_SET_OPTIONS],
# instead of pointers to the value and locus.
# sockaddr-list Sockaddr type keeps a singly-linked list of addresses
# returned by getaddrinfo.
+# json Compile JSON support
#
# The pp-setup-file argument supplies the pathname of the preprocessor
# setup file in the source tree. It is ignored if std-pp-setup option is
@@ -182,6 +183,9 @@ AC_DEFUN([GRECS_SETUP],[
AM_CONDITIONAL([GRECS_COND_SOCKADDR_LIST],
_GRECS_IF_OPTION_SET([sockaddr-list],[true],[false]))
+ AM_CONDITIONAL([GRECS_COND_JSON],
+ _GRECS_IF_OPTION_SET([json],[true],[false]))
+
AC_SUBST([GRECS_SOCKADDR_LIST])
_GRECS_IF_OPTION_SET([sockaddr-list],[GRECS_SOCKADDR_LIST=1],
[GRECS_SOCKADDR_LIST=0])
diff --git a/configure.ac b/configure.ac
index ebd7145..bcd81a0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -37,6 +37,6 @@ AC_HEADER_STDC
# Grecs subsystem
-GRECS_SETUP(., [install tests git2chg all-parsers])
+GRECS_SETUP(., [install tests git2chg all-parsers json])
AC_OUTPUT
diff --git a/src/.gitignore b/src/.gitignore
index 322178c..11738ff 100644
--- a/src/.gitignore
+++ b/src/.gitignore
@@ -18,3 +18,8 @@ grecs.h
Make-inst.in
Make-shared.in
Make-static.in
+json-gram.c
+json-gram.h
+json-gram.output
+json-lex.c
+
diff --git a/src/Make-inst.am b/src/Make-inst.am
index daf1c6f..f9cae0e 100644
--- a/src/Make-inst.am
+++ b/src/Make-inst.am
@@ -17,6 +17,6 @@
include Make.am
lib_LTLIBRARIES=libgrecs.la
libgrecs_la_SOURCES = $(GRECS_SRC)
-include_HEADERS = grecs.h grecsopt.h wordsplit.h
+include_HEADERS = $(GRECS_HDR)
m4datadir = $(datadir)/aclocal
dist_m4data_DATA = libgrecs.m4
diff --git a/src/Make-shared.am b/src/Make-shared.am
index 292218b..8f698c4 100644
--- a/src/Make-shared.am
+++ b/src/Make-shared.am
@@ -16,9 +16,10 @@
include Make.am
noinst_LTLIBRARIES=libgrecs.la
libgrecs_la_SOURCES = $(GRECS_SRC)
+
if GRECS_COND_INSTALLHEADERS
grecsincludedir = @GRECS_INCLUDE_DIR@
- grecsinclude_HEADERS = grecs.h grecsopt.h wordsplit.h
+ grecsinclude_HEADERS = $(GRECS_HDR)
else
- noinst_HEADERS += grecs.h grecsopt.h wordsplit.h
-endif \ No newline at end of file
+ noinst_HEADERS += $(GRECS_HDR)
+endif
diff --git a/src/Make-static.am b/src/Make-static.am
index 63b6338..4050e47 100644
--- a/src/Make-static.am
+++ b/src/Make-static.am
@@ -16,4 +16,6 @@
include Make.am
noinst_LIBRARIES=libgrecs.a
libgrecs_a_SOURCES = $(GRECS_SRC)
-noinst_HEADERS += grecs.h grecsopt.h wordsplit.h
+noinst_HEADERS += $(GRECS_HDR)
+
+
diff --git a/src/Make.am b/src/Make.am
index 11ef6df..15e0075 100644
--- a/src/Make.am
+++ b/src/Make.am
@@ -39,6 +39,11 @@ if GRECS_COND_GIT_PARSER
PARSER_DEFS += -DENABLE_GIT_PARSER
endif
+if GRECS_COND_JSON
+ GRECS_JSON = json-gram.y json-lex.l
+ GRECS_EXTRA_JSON = json-gram.h
+endif
+
GRECS_SRC = \
asprintf.c\
cidr.c\
@@ -64,11 +69,17 @@ GRECS_SRC = \
txtacc.c\
version.c\
wordsplit.c\
+ $(GRECS_JSON)\
$(GRECS_PARSER_BIND)\
$(GRECS_PARSER_DHCPD)\
$(GRECS_PARSER_GIT)\
$(GRECS_PARSER_META1)
+GRECS_HDR = grecs.h grecsopt.h wordsplit.h
+if GRECS_COND_JSON
+ GRECS_HDR += json.h
+endif
+
if GRECS_COND_SOCKADDR_LIST
GRECS_SRC += sockaddr.c
endif
@@ -80,6 +91,7 @@ EXTRA_DIST=\
grecs.hin\
$(GRECS_EXTRA_BIND)\
$(GRECS_EXTRA_DHCPD)\
+ $(GRECS_EXTRA_JSON)\
$(GRECS_EXTRA_META1)\
$(PP_SETUP_FILE)\
Make.am Make-inst.am Make-shared.am Make-static.am
diff --git a/src/json-gram.y b/src/json-gram.y
new file mode 100644
index 0000000..e92ad35
--- /dev/null
+++ b/src/json-gram.y
@@ -0,0 +1,371 @@
+%{
+/* This file is part of Grecs.
+ Copyright (C) 2012-2015 Sergey Poznyakoff.
+
+ Grecs is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ Grecs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Grecs. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include "grecs.h"
+#include <grecs-locus.h>
+#include <string.h>
+#include "json-gram.h"
+#include "json.h"
+
+struct json_value *json_return_obj;
+
+extern int yylex(void);
+static int yyerror(char const *s);
+
+static void
+pairfree(void *ptr)
+{
+ struct json_pair *p = ptr;
+ free(p->k);
+ json_value_free(p->v);
+ free(p);
+}
+
+static void
+objfree(void *ptr)
+{
+ struct json_value *o = ptr;
+ json_value_free(o);
+}
+
+%}
+
+%error-verbose
+%locations
+
+%token <n> T_NUMBER
+%token <s> T_STRING
+%token <b> T_BOOL
+%token T_NULL T_ERR
+
+%type <a> array
+%type <list> objects objlist pairs pairlist
+%type <p> pair
+%type <obj> object
+%type <o> assoc
+
+%union {
+ int b;
+ double n;
+ char *s;
+ struct json_array *a;
+ struct grecs_symtab *o;
+ struct json_value *obj;
+ struct grecs_list *list;
+ struct json_pair *p;
+}
+%%
+
+input : object
+ {
+ json_return_obj = $1;
+ }
+ ;
+
+object : T_NUMBER
+ {
+ $$ = json_value_create(json_number);
+ $$->v.n = $1;
+ }
+ | T_STRING
+ {
+ $$ = json_value_create(json_string);
+ $$->v.s = $1;
+ }
+ | T_BOOL
+ {
+ $$ = json_value_create(json_bool);
+ $$->v.b = $1;
+ }
+ | T_NULL
+ {
+ $$ = json_value_create(json_null);
+ }
+ | array
+ {
+ $$ = json_value_create(json_arr);
+ $$->v.a = $1;
+ }
+ | assoc
+ {
+ $$ = json_value_create(json_object);
+ $$->v.o = $1;
+ }
+ ;
+
+array : '[' objects ']'
+ {
+ struct json_array *a = grecs_malloc(sizeof(*a));
+ if (!$2) {
+ a->oc = 0;
+ a->ov = NULL;
+ } else {
+ size_t i;
+ struct grecs_list_entry *ep;
+ a->oc = $2->count;
+ a->ov = grecs_calloc(a->oc, sizeof(a->ov));
+ for (i = 0, ep = $2->head; ep; i++, ep = ep->next) {
+ struct json_value *p = ep->data;
+ a->ov[i] = p;
+ }
+ }
+ $$ = a;
+ }
+ ;
+
+objects : /* empty */
+ {
+ $$ = NULL;
+ }
+ | objlist
+ ;
+
+objlist : object
+ {
+ $$ = grecs_list_create();
+ $$->free_entry = objfree;
+ grecs_list_append($$, $1);
+ }
+ | objlist ',' object
+ {
+ grecs_list_append($1, $3);
+ }
+ ;
+
+assoc : '{' pairs '}'
+ {
+ struct grecs_symtab *s;
+
+ s = json_assoc_create();
+ if ($2) {
+ struct grecs_list_entry *ep;
+ for (ep = $2->head; ep; ep = ep->next) {
+ struct json_pair *p = ep->data;
+ int install = 1;
+ grecs_symtab_lookup_or_install(s, p, &install);
+ if (install) {
+ p->k = NULL;
+ p->v = NULL;
+ }
+ }
+ grecs_list_free($2);
+ }
+ $$ = s;
+ }
+ ;
+
+pairs : /* empty */
+ {
+ $$ = NULL;
+ }
+ | pairlist
+ ;
+
+pairlist: pair
+ {
+ $$ = grecs_list_create();
+ $$->free_entry = pairfree;
+ grecs_list_append($$, $1);
+ }
+ | pairlist ',' pair
+ {
+ grecs_list_append($1, $3);
+ }
+ ;
+
+pair : T_STRING ':' object
+ {
+ struct json_pair *p = grecs_malloc(sizeof(*p));
+ p->k = $1;
+ p->v = $3;
+ $$ = p;
+ }
+ ;
+%%
+
+static int
+yyerror(char const *s)
+{
+ jsonlex_diag(s);
+ return 0;
+}
+
+struct json_value *
+json_value_create(int type)
+{
+ struct json_value *obj = grecs_zalloc(sizeof(*obj));
+ obj->type = type;
+ return obj;
+}
+
+void
+json_value_free(struct json_value *obj)
+{
+ size_t i;
+
+ if (!obj)
+ return;
+
+ switch (obj->type) {
+ case json_null:
+ case json_bool:
+ case json_number:
+ break;
+ case json_string:
+ free(obj->v.s);
+ break;
+ case json_arr:
+ for (i = 0; i < obj->v.a->oc; i++)
+ json_value_free(obj->v.a->ov[i]);
+ free(obj->v.a);
+ break;
+ case json_object:
+ grecs_symtab_free(obj->v.o);
+ }
+ free(obj);
+}
+
+static unsigned
+json_st_hash(void *data, unsigned long n_buckets)
+{
+ struct json_pair *p = data;
+ return grecs_hash_string(p->k, n_buckets);
+}
+
+static int
+json_st_cmp(const void *a, const void *b)
+{
+ struct json_pair const *pa = a;
+ struct json_pair const *pb = b;
+ return strcmp(pa->k, pb->k);
+}
+
+static int
+json_st_copy(void *a, void *b)
+{
+ struct json_pair *pa = a;
+ struct json_pair *pb = b;
+ memcpy(pa, pb, sizeof(*pa));
+ return 0;
+}
+
+static void
+json_st_free(void *ptr)
+{
+ struct json_pair *p = ptr;
+ free(p->k);
+ json_value_free(p->v);
+ free(p);
+}
+
+struct grecs_symtab *
+json_assoc_create()
+{
+ return grecs_symtab_create(sizeof(struct json_pair),
+ json_st_hash,
+ json_st_cmp,
+ json_st_copy,
+ NULL,
+ json_st_free);
+}
+
+struct json_value *
+json_parse_string(char const *input, size_t len)
+{
+ jsonlex_setup(input, len);
+ if (yyparse()) {
+ /* FIXME: error recovery */
+ return NULL;
+ }
+ return json_return_obj;
+}
+
+struct json_value *
+json_value_lookup(struct json_value *obj, const char *ident)
+{
+ char *qbuf = NULL;
+ size_t qlen = 0;
+
+ while (obj && *ident) {
+ char const *p;
+ char *q;
+ size_t l;
+
+ for (p = ident; *p; p++) {
+ if (*p == '\\')
+ ++p;
+ else if (*p == '.')
+ break;
+ }
+ l = p - ident + 1;
+ if (l > qlen) {
+ qlen = l;
+ qbuf = grecs_realloc(qbuf, qlen);
+ }
+ q = qbuf;
+ while (*ident) {
+ if (*ident == '\\') {
+ int c;
+ ++ident;
+ if (json_unescape(*ident, &c))
+ *q++ = *ident++;
+ else
+ *q++ = c;
+ } else if (*ident == '.') {
+ ++ident;
+ break;
+ } else
+ *q++ = *ident++;
+ }
+ *q = 0;
+
+ switch (obj->type) {
+ case json_null:
+ case json_bool:
+ case json_number:
+ case json_string:
+ obj = NULL;
+ break;
+ case json_arr:
+ l = strtoul(qbuf, &q, 10);
+ if (*q == 0 && l < obj->v.a->oc)
+ obj = obj->v.a->ov[l];
+ else
+ obj = NULL;
+ break;
+ case json_object: {
+ struct json_pair key, *match;
+ key.k = qbuf;
+ match = grecs_symtab_lookup_or_install(obj->v.o,
+ &key, NULL);
+ obj = match ? match->v : NULL;
+ }
+ }
+ }
+ if (*ident)
+ obj = NULL;
+ free(qbuf);
+ return obj;
+}
+
+
+
+
+
diff --git a/src/json-lex.l b/src/json-lex.l
new file mode 100644
index 0000000..2a08fd0
--- /dev/null
+++ b/src/json-lex.l
@@ -0,0 +1,204 @@
+%option noinput
+%option nounput
+%top {
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+}
+%{
+/* This file is part of Grecs.
+ Copyright (C) 2012-2015 Sergey Poznyakoff.
+
+ Grecs is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ Grecs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Grecs. If not, see <http://www.gnu.org/licenses/>. */
+
+#include "grecs.h"
+#include <grecs-locus.h>
+#include "json-gram.h"
+#include "json.h"
+
+static char const *input_ptr;
+static size_t input_size;
+struct grecs_locus_point json_current_locus_point; /* Input location */
+
+char const *json_err_diag;
+struct grecs_locus json_err_locus;
+
+#undef YY_INPUT
+#define YY_INPUT(buf,result,max_size) \
+ do { \
+ size_t n = (max_size > input_size) ? input_size : max_size; \
+ if (n) { \
+ memcpy(buf, input_ptr, n); \
+ input_ptr += n; \
+ input_size -= n; \
+ } \
+ result = n; \
+ } while(0)
+
+#define YY_USER_ACTION do { \
+ if (YYSTATE == 0) { \
+ yylloc.beg = json_current_locus_point; \
+ yylloc.beg.col++; \
+ } \
+ json_current_locus_point.col += yyleng; \
+ yylloc.end = json_current_locus_point; \
+ } while (0);
+
+void
+jsonlex_setup(char const *s, size_t l)
+{
+ input_ptr = s;
+ input_size = l;
+ json_current_locus_point.file = NULL;
+ json_current_locus_point.line = 1;
+ json_current_locus_point.col = 0;
+ json_err_diag = NULL;
+ yy_flex_debug = 0;
+}
+
+void
+jsonlex_diag(const char *s)
+{
+ if (!json_err_diag) {
+ json_err_diag = s;
+ json_err_locus = yylloc;
+ }
+}
+
+static int
+utf8_wctomb(char *u)
+{
+ unsigned int wc = strtoul(u, NULL, 16);
+ int count;
+ char r[6];
+
+ if (wc < 0x80)
+ count = 1;
+ else if (wc < 0x800)
+ count = 2;
+ else if (wc < 0x10000)
+ count = 3;
+ else if (wc < 0x200000)
+ count = 4;
+ else if (wc < 0x4000000)
+ count = 5;
+ else if (wc <= 0x7fffffff)
+ count = 6;
+ else
+ return -1;
+
+ switch (count) {
+ /* Note: code falls through cases! */
+ case 6:
+ r[5] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0x4000000;
+ case 5:
+ r[4] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0x200000;
+ case 4:
+ r[3] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0x10000;
+ case 3:
+ r[2] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0x800;
+ case 2:
+ r[1] = 0x80 | (wc & 0x3f);
+ wc = wc >> 6;
+ wc |= 0xc0;
+ case 1:
+ r[0] = wc;
+ }
+ grecs_line_acc_grow(r, count);
+ return count;
+}
+
+int
+json_unescape(int c, int *o)
+{
+ static char transtab[] = "\\\\\"\"//b\bf\fn\nr\rt\t";
+ char *p;
+
+ for (p = transtab; *p; p += 2) {
+ if (p[0] == c) {
+ *o = p[1];
+ return 0;
+ }
+ }
+ return -1;
+}
+
+#define YY_SKIP_YYWRAP 1
+static int
+yywrap()
+{
+ return 1;
+}
+%}
+D [0-9]
+X [0-9a-fA-F]
+%x STR
+%%
+"-"?{D}{D}*(.{D}{D}*)?([eE][-+]?{D}{D}*)? {
+ yylval.n = strtod(yytext, NULL);
+ return T_NUMBER;
+ }
+\"[^\\\"]*\" { grecs_line_begin();
+ grecs_line_add(yytext + 1, yyleng - 2);
+ yylval.s = grecs_line_finish();
+ return T_STRING; }
+\"[^\\\"]*\\{X}{4} { BEGIN(STR);
+ grecs_line_begin();
+ grecs_line_add(yytext + 1, yyleng - 5);
+ utf8_wctomb(yytext + yyleng - 4);
+ }
+\"[^\\\"]*\\. { int c;
+ BEGIN(STR);
+ grecs_line_begin();
+ grecs_line_acc_grow(yytext + 1, yyleng - 3);
+ if (json_unescape(yytext[yyleng - 1], &c)) {
+ jsonlex_diag("invalid UTF-8 codepoint");
+ return T_ERR;
+ }
+ grecs_line_acc_grow_char(c);
+ }
+<STR>[^\\\"]*\" { BEGIN(INITIAL);
+ if (yyleng > 1)
+ grecs_line_acc_grow(yytext, yyleng - 1);
+ yylval.s = grecs_line_finish();
+ return T_STRING; }
+<STR>[^\\\"]*\\{X}{4} {
+ grecs_line_add(yytext, yyleng - 5);
+ utf8_wctomb(yytext + yyleng - 4);
+}
+<STR>[^\\\"]*\\. {
+ int c;
+ grecs_line_acc_grow(yytext, yyleng - 2);
+ if (json_unescape(yytext[yyleng - 1], &c)) {
+ jsonlex_diag("invalid UTF-8 codepoint");
+ return T_ERR;
+ }
+ grecs_line_acc_grow_char(c); }
+
+null { return T_NULL; }
+true { yylval.b = 1; return T_BOOL; }
+false { yylval.b = 0; return T_BOOL; }
+"{"|"}"|"["|"]"|":"|"," return yytext[0];
+[ \t]* ;
+\n grecs_locus_point_advance_line(json_current_locus_point);
+. { jsonlex_diag("bogus character");
+ return T_ERR; }
diff --git a/src/json.h b/src/json.h
new file mode 100644
index 0000000..e089692
--- /dev/null
+++ b/src/json.h
@@ -0,0 +1,68 @@
+/* This file is part of Grecs.
+ Copyright (C) 2012-2015 Sergey Poznyakoff.
+
+ Grecs is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 3, or (at your option)
+ any later version.
+
+ Grecs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Grecs. If not, see <http://www.gnu.org/licenses/>. */
+
+#include <grecs.h>
+
+enum json_value_type
+{
+ json_null,
+ json_bool,
+ json_number,
+ json_string,
+ json_arr,
+ json_object
+};
+
+struct json_value;
+struct json_array {
+ size_t oc;
+ struct json_value **ov;
+};
+
+struct json_value {
+ enum json_value_type type;
+ union {
+ int b; /* json_bool */
+ double n; /* json_number */
+ char *s; /* json_string */
+ struct json_array *a; /* json_arr */
+ struct grecs_symtab *o; /* json_object */
+ } v;
+};
+
+struct json_pair {
+ char *k;
+ struct json_value *v;
+};
+
+extern char const *json_err_diag;
+extern struct grecs_locus json_err_locus;
+extern struct json_value *json_return_obj;
+
+void jsonlex_setup(char const *s, size_t l);
+void jsonlex_diag(const char *s);
+int json_unescape(int c, int *o);
+
+struct json_value *json_value_create(int type);
+struct grecs_symtab *json_assoc_create(void);
+void json_value_free(struct json_value *obj);
+
+struct json_value *json_parse_string(char const *input, size_t len);
+
+struct json_value *json_value_lookup(struct json_value *obj,
+ const char *ident);
+
+
diff --git a/src/yytrans b/src/yytrans
index 9e3c0df..f8f893d 100644
--- a/src/yytrans
+++ b/src/yytrans
@@ -19,3 +19,4 @@
grecs grecs_grecs_
meta1 grecs_meta1_
bind grecs_bind_
+json grecs_json_ \ No newline at end of file
diff --git a/tests/.gitignore b/tests/.gitignore
index 22061b6..d16cf06 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -6,6 +6,7 @@ gcffmt
gcfpeek
gcfset
gcfver
+json
package.m4
testsuite
testsuite.log
diff --git a/tests/Makefile.am b/tests/Makefile.am
index ed0ecef..7014b17 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -44,6 +44,10 @@ if GRECS_COND_GIT_PARSER
PARSER_DEFS += ENABLE_GIT_PARSER
endif
+if GRECS_COND_JSON
+ PARSER_DEFS += ENABLE_JSON
+endif
+
## ------------ ##
## package.m4. ##
## ------------ ##
@@ -88,6 +92,8 @@ TESTSUITE_AT = \
incl02.at\
incl03.at\
join.at\
+ json00.at\
+ json01.at\
locus00.at\
locus01.at\
locus02.at\
@@ -144,6 +150,10 @@ noinst_PROGRAMS = \
gcfver\
wsp
+if GRECS_COND_JSON
+ noinst_PROGRAMS += json
+endif
+
LDADD = @GRECS_LDADD@ $(LIBINTL)
AM_CPPFLAGS = @GRECS_INCLUDES@ @GRECS_HOST_PROJECT_INCLUDES@
diff --git a/tests/json.c b/tests/json.c
new file mode 100644
index 0000000..f2c2380
--- /dev/null
+++ b/tests/json.c
@@ -0,0 +1,286 @@
+/* grecs - Gray's Extensible Configuration System
+ Copyright (C) 2007-2012, 2015 Sergey Poznyakoff
+
+ Grecs is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the
+ Free Software Foundation; either version 3 of the License, or (at your
+ option) any later version.
+
+ Grecs is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with Grecs. If not, see <http://www.gnu.org/licenses/>. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <errno.h>
+#include "grecs.h"
+#include "json.h"
+
+size_t indent;
+int pretty_print;
+int precision = -1;
+
+static void
+usage(const char *arg, FILE *fp, int code)
+{
+ fprintf(fp,
+ "usage: %s [-file=FILE][-indent=N][-precision=N] [expr]\n",
+ arg);
+ exit(code);
+}
+
+static void
+json_indent(FILE *fp, size_t level)
+{
+ level *= indent;
+ while (level--)
+ fputc(' ', fp);
+}
+
+static void
+json_format_delim(FILE *fp, size_t level)
+{
+ fputc(',', fp);
+ if (indent) {
+ fputc('\n', fp);
+ json_indent(fp, level);
+ } else
+ fputc(' ', fp);
+}
+
+static int
+escape(char c, char *o)
+{
+ static char transtab[] = "\\\\\"\"//b\bf\fn\nr\rt\t";
+ char *p;
+
+ for (p = transtab; *p; p += 2) {
+ if (p[1] == c) {
+ *o = p[0];
+ return 0;
+ }
+ }
+ return -1;
+}
+
+static void
+json_format_string(FILE *fp, const char *s)
+{
+ fputc('"', fp);
+ for (; *s; s++) {
+ char c;
+ if (!escape(*s, &c)) {
+ fputc('\\', fp);
+ fputc(c, fp);
+ } else
+ fputc(*s, fp);
+ }
+ fputc('"', fp);
+}
+
+static int
+collect_keypairs(void *sym, void *data)
+{
+ struct json_pair *p = sym;
+ struct json_pair ***kp = data;
+ **kp = p;
+ ++*kp;
+ return 0;
+}
+
+static void json_format_value(FILE *fp, struct json_value *obj, size_t level);
+
+static int
+keypair_cmp_name(const void *a, const void *b)
+{
+ struct json_pair const * const *kpa = a;
+ struct json_pair const * const *kpb = b;
+ return strcmp((*kpa)->k, (*kpb)->k);
+}
+
+static void
+json_format_obj(FILE *fp, struct json_value *obj, size_t level)
+{
+ size_t count, i;
+ struct json_pair **keypairs, **kp;
+
+ count = grecs_symtab_count_entries(obj->v.o);
+ keypairs = grecs_calloc(count, sizeof(*keypairs));
+ kp = keypairs;
+ grecs_symtab_enumerate(obj->v.o, collect_keypairs, &kp);
+ qsort(keypairs, count, sizeof(*keypairs), keypair_cmp_name);
+
+ fputc('{', fp);
+ if (count) {
+ if (indent)
+ fputc('\n', fp);
+ for (i = 0; i < count; i++) {
+ (i ? json_format_delim : json_indent)(fp, level);
+ json_format_string(fp, keypairs[i]->k);
+ fputc(':', fp);
+ if (indent)
+ fputc(' ', fp);
+ json_format_value(fp, keypairs[i]->v, level);
+ }
+ if (indent) {
+ fputc('\n', fp);
+ json_indent(fp, level-1);
+ }
+ }
+ fputc('}', fp);
+}
+
+static void
+json_format_array(FILE *fp, struct json_value *obj, size_t level)
+{
+ size_t i;
+
+ fputc('[', fp);
+ if (obj->v.a->oc) {
+ if (indent)
+ fputc('\n', fp);
+ for (i = 0; i < obj->v.a->oc; i++) {
+ (i ? json_format_delim : json_indent)(fp, level);
+ json_format_value(fp, obj->v.a->ov[i], level);
+ }
+ if (indent) {
+ fputc('\n', fp);
+ json_indent(fp, level-1);
+ }
+ }
+ fputc(']', fp);
+}
+
+static void
+json_format_value(FILE *fp, struct json_value *obj, size_t level)
+{
+ if (!obj) {
+ fprintf(fp, "null");
+ return;
+ }
+
+ ++level;
+ switch (obj->type) {
+ case json_null:
+ fprintf(fp, "null");
+ break;
+
+ case json_bool:
+ fprintf(fp, "%s", obj->v.b ? "true" : "false");
+ break;
+
+ case json_number:
+ if (precision == -1)
+ fprintf(fp, "%e", obj->v.n);
+ else
+ fprintf(fp, "%.*f", precision, obj->v.n);
+ break;
+
+ case json_string:
+ json_format_string(fp, obj->v.s);
+ break;
+
+ case json_arr:
+ json_format_array(fp, obj, level);
+ break;
+
+ case json_object:
+ json_format_obj(fp, obj, level);
+ break;
+ }
+}
+
+
+int
+main(int argc, char **argv)
+{
+ char *progname = argv[0];
+ char *file = NULL;
+ char *input;
+ size_t size;
+ struct json_value *obj;
+ char *key = NULL;
+
+ while (--argc) {
+ char *arg = *++argv;
+ if (strncmp(arg, "-file=", 6) == 0)
+ file = arg + 6;
+ else if (strncmp(arg, "-indent=", 8) == 0)
+ indent = atoi(arg + 8);
+ else if (strncmp(arg, "-search=", 8) == 0)
+ key = arg + 8;
+ else if (strncmp(arg, "-precision=", 11) == 0)
+ precision = atoi(arg + 11);
+ else if (arg[0] == '-')
+ usage(progname, stderr, 1);
+ else
+ break;
+ }
+
+ if (file) {
+ struct stat st;
+ int fd;
+ ssize_t n;
+
+ if (argc != 0)
+ usage(progname, stderr, 1);
+
+ fd = open(file, O_RDONLY);
+ if (fd == -1) {
+ perror(file);
+ return 2;
+ }
+ if (fstat(fd, &st)) {
+ perror("fstat");
+ return 2;
+ }
+ size = (size_t) st.st_size;
+ if (size != st.st_size)
+ abort();
+ input = grecs_malloc(size + 1);
+ n = read(fd, input, size);
+ if (n == -1) {
+ perror("read");
+ return 2;
+ }
+ if (n != size) {
+ fprintf(stderr, "%s: short read from %s\n",
+ progname, file);
+ return 2;
+ }
+ input[n] = 0;
+ close(fd);
+ } else if (argc == 1) {
+ if (file)
+ usage(progname, stderr, 1);
+ input = *argv;
+ size = strlen(input);
+ } else
+ usage(progname, stderr, 1);
+
+ obj = json_parse_string(input, size);
+ if (!obj) {
+ json_err_locus.beg.file = json_err_locus.end.file =
+ file ? file : "input";
+ grecs_error(&json_err_locus, 0, "%s", json_err_diag);
+ return 3;
+ }
+ if (key) {
+ struct json_value *p = json_value_lookup(obj, key);
+ if (!p)
+ return 4;
+ obj = p;
+ }
+ json_format_value(stdout, obj, 0);
+ putchar('\n');
+ return 0;
+}
diff --git a/tests/json00.at b/tests/json00.at
new file mode 100644
index 0000000..5d39c47
--- /dev/null
+++ b/tests/json00.at
@@ -0,0 +1,75 @@
+# This file is part of Grecs -*- Autotest -*-
+# Copyright (C) 2015 Sergey Poznyakoff
+#
+# Grecs is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# Grecs is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Grecs. If not, see <http://www.gnu.org/licenses/>.
+AT_SETUP([Parser])
+AT_KEYWORDS([json json00 parser])
+
+AT_DATA([input.json],[[
+{
+ "firstName": "John",
+ "lastName": "Smith",
+ "isAlive": true,
+ "age": 25,
+ "address": {
+ "streetAddress": "21 2nd Street",
+ "city": "New York",
+ "state": "NY",
+ "postalCode": "10021-3100"
+ },
+ "phoneNumbers": [
+ {
+ "type": "home",
+ "number": "212 555-1234"
+ },
+ {
+