From 9c0d1b58fd370bed9442ad8859d9c6e1f02cc643 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Fri, 18 Dec 2015 17:33:16 +0200 Subject: Add basic JSON support functions. * src/Make-inst.am: Add json.h * src/Make-shared.am: Likewise. * src/Make-static.am: Likewise. * src/Make.am: Likewise. * src/json-gram.y: New file. * src/json-lex.l: New file. * src/json.h: New file. * src/yytrans: Translate json prefixes. * src/.gitignore: Update. * tests/json.c: New file. * tests/json00.at: New file. * tests/json01.at: New file. * tests/Makefile.am: Add new tests; build json * tests/testsuite.at: Add new tests. * tests/.gitignore: Update. * am/grecs.m4: New flag "json" * configure.ac (GRECS_SETUP): Require json * src/Make-inst.am (include_HEADERS): Assign GRECS_HDR value. * src/Make-shared.a [GRECS_COND_INSTALLHEADERS] (grecsinclude_HEADERS) [!GRECS_COND_INSTALLHEADERS] (noinst_HEADERS): Likewise. * src/Make-static.am (noinst_HEADERS): Likewise. * src/Make.am [GRECS_COND_JSON]: Define GRECS_JSON and GRECS_EXTRA_JSON. --- src/.gitignore | 5 + src/Make-inst.am | 2 +- src/Make-shared.am | 7 +- src/Make-static.am | 4 +- src/Make.am | 12 ++ src/json-gram.y | 371 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/json-lex.l | 204 +++++++++++++++++++++++++++++ src/json.h | 68 ++++++++++ src/yytrans | 1 + 9 files changed, 669 insertions(+), 5 deletions(-) create mode 100644 src/json-gram.y create mode 100644 src/json-lex.l create mode 100644 src/json.h (limited to 'src') 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 . */ + +#ifdef HAVE_CONFIG_H +# include +#endif +#include "grecs.h" +#include +#include +#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 T_NUMBER +%token T_STRING +%token T_BOOL +%token T_NULL T_ERR + +%type array +%type objects objlist pairs pairlist +%type

pair +%type object +%type 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 +#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 . */ + +#include "grecs.h" +#include +#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); + } +[^\\\"]*\" { BEGIN(INITIAL); + if (yyleng > 1) + grecs_line_acc_grow(yytext, yyleng - 1); + yylval.s = grecs_line_finish(); + return T_STRING; } +[^\\\"]*\\{X}{4} { + grecs_line_add(yytext, yyleng - 5); + utf8_wctomb(yytext + yyleng - 4); +} +[^\\\"]*\\. { + 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 . */ + +#include + +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 -- cgit v1.2.1