diff options
-rw-r--r-- | am/grecs.m4 | 4 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | src/.gitignore | 5 | ||||
-rw-r--r-- | src/Make-inst.am | 2 | ||||
-rw-r--r-- | src/Make-shared.am | 7 | ||||
-rw-r--r-- | src/Make-static.am | 4 | ||||
-rw-r--r-- | src/Make.am | 12 | ||||
-rw-r--r-- | src/json-gram.y | 371 | ||||
-rw-r--r-- | src/json-lex.l | 204 | ||||
-rw-r--r-- | src/json.h | 68 | ||||
-rw-r--r-- | src/yytrans | 1 | ||||
-rw-r--r-- | tests/.gitignore | 1 | ||||
-rw-r--r-- | tests/Makefile.am | 10 | ||||
-rw-r--r-- | tests/json.c | 286 | ||||
-rw-r--r-- | tests/json00.at | 75 | ||||
-rw-r--r-- | tests/json01.at | 89 | ||||
-rw-r--r-- | tests/testsuite.at | 6 |
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" + }, + { + |