1 /* Copyright 2016, UCAR/Unidata.
2    See the LICENSE file for more information.
3 */
4 
5 #ifndef NCJSON_INC
6 #define NCJSON_INC 1
7 
8 #define NCJ_DICT     1
9 #define NCJ_LIST     2
10 #define NCJ_WORD     3
11 #define NCJ_NUMBER   4
12 #define NCJ_BOOLEAN  5
13 
14 /* Don't bother with unions */
15 typedef struct NCjson {
16     long sort;
17     char* word; /* string or (!boolean && !number) */
18     long long num; /* number || boolean (0=>false; !0=>true)*/
19     NCjson* list;
20 } NCjson;
21 
22 #define NCJ_LBRACKET '['
23 #define NCJ_RBRACKET ']'
24 #define NCJ_LBRACE '{'
25 #define NCJ_RBRACE '}'
26 #define NCJ_COLON ':'
27 #define NCJ_COMMA ','
28 #define NCJ_QUOTE '"'
29 #define NCJ_TRUE "true"
30 #define NCJ_FALSE "false"
31 
32 #define NCJ_WORD "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-$"
33 
34 /*//////////////////////////////////////////////////*/
35 
36 typedef struct NCJparser {
37     char* text;
38     char* pos;
39     char* yytext;
40     int errno;
41     struct {
42         char* yytext;
43 	int token;
44     } pushback;
45 } NCJparser;
46 
47 static int
NCjsonparse(char * text,NCjson ** treep)48 NCjsonparse(char* text, NCjson** treep)
49 {
50     int status = NCJ_OK;
51     size_t len;
52     NCJparser parser = NULL;
53     NCjson* tree = NULL;
54     if(text == NULL) {status = NCJ_EINVAL; goto done;}
55     parser = calloc(1,sizeof(NCJparser));
56     if(parser == NULL) {status = NCJ_ENOMEM; goto done;}
57     len = strlen(text);
58     parser->text = (char*)malloc(len+1+1);
59     if(parser->text == NULL) {status = NCJ_ENOMEM; goto done;}
60     strcpy(parser->text,text);
61     parser->text[len] = '\0';
62     parser->text[len+1] = '\0';
63     tree = NCJparseR(parser);
64 done:
65     if(parser != NULL) {
66 	nullfree(parser->text);
67 	nullfree(parser->yytext);
68 	free(parser);
69     }
70     if(status != NCJ_OK) {
71 	if(tree != NULL) NCjsonfree(tree);
72     } else
73 	if(treep) *treep = tree;
74     return status;
75 }
76 
77 static int
NCJyytext(NCJparser * parser,char * start,ptrdiff_t pdlen)78 NCJyytext(NCJparser* parser, char* start, ptrdiff_t pdlen)
79 {
80     size_t len = (size_t)pdlen;
81     if(parser->yytext == NULL)
82 	parser->yytext = (char*)malloc(len+1);
83     else
84 	parser->yytext = (char*) realloc(parser->yytext,len+1);
85     if(parser->yytext == NULL) return NCJ_ENOMEM;
86     memcpy(parser->yytext,start,len);
87     parser->yytext[len] = NCJ_NUL;
88     return NCJ_OK;
89 }
90 
91 static void
NCJpushback(NCJparser * parser,int token)92 NCJpushback(NCJparser* parser, int token)
93 {
94     parser->pushback.token = token;
95     parser->pushback.yytext = strdup(parser->yytext);
96 }
97 
98 static int
NCJlex(NCJparser * parser)99 NCJlex(NCJparser* parser)
100 {
101     int c;
102     int token = NCJ_NUL;
103     char* start;
104     char* next;
105 
106     if(parser->pushback.token != NCJ_NOTOKEN) {
107 	token = parser->pushback.token;
108 	NCJyytext(parser,parser->pushback.yytext,strlen(parser->pushback.yytext));
109 	nullfree(parser->pushback.yytext);
110 	parser->pushback.yytext = NULL;
111 	parser->pushback.token = NCJ_NOTOKEN;
112 	return token;
113     }
114 
115     c = *parser->pos;
116     if(c == NCJ_NUL) {
117 	token = NCJ_NUL;
118     } else if(strchr(NCJ_WORD, c) != NULL) {
119 	size_t len;
120         start = parser->pos;
121         next = start + 1;
122 	for(;;) {
123 	    c = *parser->pos++;
124 	    if(strchr(NCJ_WHITESPACE,c) != NULL || c == NCJ_NUL) break;
125 	    last++;
126 	}
127 	if(!NCJyytext(parser,start,(next - start))) goto done;
128 	token = NCJ_WORD;
129     } else if(c == NCJ_QUOTE) {
130 	parser->pos++;
131 	start = parser->pos;
132 	next = start+1;
133 	for(;;) {
134 	    c = *parser->pos++;
135 	    if(c == NCJ_QUOTE || c == NCJ_NUL) break;
136 	    last++;
137 	}
138 	if(c == NCJ_NUL) {
139 	    parser->errno = NCJ_ESTRING;
140 	    token = NCJ_ERR;
141 	    goto done;
142 	}
143 	if(!NCJyytext(parser,start,(next - start))) goto done;
144 	token = NCJ_STRING;
145     } else { /* single char token */
146 	token = *parser->pos++;
147     }
148 done:
149     if(parser->errno) token = NCJ_ERR;
150     return token;
151 }
152 
153 /* Simple recursive descent */
154 
155 static int
NCJparseR(NCJparser * parser,NCjson ** listp)156 NCJparseR(NCJparser* parser, NCjson** listp)
157 {
158     int token = NCJ_ERR;
159     NCjson* list = NULL;
160     if((token = NCJlex(parser)) == NCJ_ERR) goto done;
161     switch (token) {
162     case NCJ_NUL;
163 	break;
164     case NCJ_WORD:
165         NCJappend(NCJparseAtomic(parser,token),listp);
166 	break;
167     case NCJ_LBRACE:
168 	NCJappend(NCJparseMap(parser,locallist),listp);
169 	break;
170     case NCJ_LBRACKET:
171         NCJappend(NCJparseArray(parser,NULL),)
172     case NCJ_STRING:
173         return NCJparseAtomic(parser,token);
174     default:
175 	parser->errno = NCJ_EBADTOKEN;
176     }
177     return NULL;
178 }
179 
180 static NCjson*
NCJparseAtomic(NCJparser * parser,int kind)181 NCJparseAtomic(NCJparser* parser, int kind)
182 {
183     /* assert (kind == NCJ_WORD || kind = NCJ_QUOTE) */
184     NCjson* node;
185     if((node = NCJmakenode(parser)) == NULL)
186 	{parser->errno = NCJ_ENOMEM; goto done;}
187     if(kind == NCJ_STRING)
188         node->sort = NCJ_WORD;
189         node->word = strdup(parser->yytext);
190     } else {
191 	/* Try to convert to number or boolean; last resort is word */
192         size_t count = (last - start) + 1;
193 	int nread = 0;
194 	int ncvt = sscan(parser->yytext,
195 			 "%L",&node->num,&nread);
196 	if(ncvt == 1 && nread == count) {
197 	    node->sort = NCJ_NUMBER;
198 	} else if(strcasecmp(parser->yytext,NCJ_TRUE)==0) {
199 	    node->sort = NCJ_BOOLEAN;
200 	    node->num = 1;
201 	} else if(strcasecmp(parser->yytext,NCJ_FALSE)==0) {
202 	    node->sort = NCJ_BOOLEAN;
203 	    node->num = 0;
204 	} else {
205 	    node->word = strdup(parser->yytext);
206 	    node->sort = NCJ_WORD;
207         }
208     }
209 done:
210     return node;
211 }
212 
213 static NCjson*
214 NCJparseArray(NCJparser* parser)
215 {
216     NCjson* head = NULL;
217     NCjson* last = NULL;
218     int token = NCJ_ERR;
219 #if 0
220     if((node = NCJmakenode(parser)) == NULL) goto done;
221 #endif
222     loop:
223     for(;;) {
224         if((token = NCJlex(parser)) == NCJ_ERR) goto done;
225         switch (token) {
226         case NCJ_NUL;
227             break;
228         case NCJ_RBRACKET:
229 	    break loop;
230         default:
231 	    NCJpushback(parser,token);
232             NCjson* o = NCJparseR(parser);
233             tokens.nextToken();
234             if(tokens.ttype == NCJ_EOF) break;
235             else if(tokens.ttype == RBRACKET) tokens.pushBack();
236             else if(tokens.ttype != COMMA)
237                 throw new IOException("Missing comma in list");
238             array.add(o);
239         }
240     }
241     return array;
242 }
243 
244 static NCjson parseMap(StreamTokenizer tokens)
245 {
246     assert (tokens.ttype == LBRACE);
247     Map<char*, Object> map = new LinkedHashMap<>();  /* Keep insertion order */
248     loop:
249     for(; ; ) {
250         int token = tokens.nextToken();
251         switch (token) {
252         case NCJ_NCJ_EOL:
253             break; /* ignore */
254         case NCJ_NCJ_EOF:
255             throw new IOException("Unexpected eof");
256         case NCJ_RBRACE:
257             break loop;
258         default:
259             tokens.pushBack();
260             NCjson name = parseR(tokens);
261             if(tokens.ttype == NCJ_EOF) break;
262             if(name instanceof char*
263                     || name instanceof Long
264                     || name instanceof Boolean) {
265                   /*ok*/
266             } else
267                 throw new IOException("Unexpected map name type: " + name);
268             if(tokens.nextToken() != COLON)
269                 throw new IOException("Expected ':'; found: " + tokens.ttype);
270             NCjson o = parseR(tokens);
271             tokens.nextToken();
272             if(tokens.ttype == NCJ_EOF) break;
273             else if(tokens.ttype == RBRACE) tokens.pushBack();
274             else if(tokens.ttype != COMMA)
275                 throw new IOException("Missing comma in list");
276             map.put(name.tochar*(), o);
277         }
278     }
279     return map;
280 }
281 }
282 
283 static char* tochar*(Object o) {return tochar*(o,"");}
284 
285 static char* tochar*(Object o, char* demark)
286 {
287 char*Builder buf = new char*Builder();
288 tochar*R(o, buf, demark, 0);
289 return buf.tochar*();
290 }
291 
292 static static void tochar*R(Object o, char*Builder buf, char* demark, int indent)
293 {
294 boolean first = true;
295 if(o instanceof List) {
296     List<Object> list = (List<Object>) o;
297     if(list.size()== 0) {
298         buf.append(LBRACKET);
299         buf.append(RBRACKET);
300     } else {
301         buf.append(LBRACKET);
302         buf.append('\n');
303         for(int i=0;i<list.size();i++) {
304             NCjson e = list.get(i);
305             buf.append(indent(indent));
306             tochar*R(e, buf, demark, indent + 2);
307             if(i < list.size()-1) buf.append(",");
308             buf.append("\n");
309         }
310         buf.append(indent(indent));
311         buf.append(RBRACKET);
312     }
313 } else if(o instanceof Map) {
314     Map<char*, Object> map = (Map<char*, Object>) o;
315     if(map.size() == 0) {
316         buf.append(LBRACE);
317         buf.append(RBRACE);
318     } else {
319         buf.append(LBRACE);
320         buf.append('\n');
321         int i = 0;
322         for(Map.Entry<char*, Object> e : map.entrySet()) {
323             buf.append(indent(indent + 2));
324             buf.append(QUOTE);
325             buf.append(e.getKey().replace("\"","\\\""));
326             buf.append(QUOTE);
327             buf.append(' ');
328             buf.append(COLON);
329             buf.append(' ');
330             tochar*R(e.getValue(), buf, demark, indent + 2);
331             if(i < map.size() - 1) buf.append(",");
332             buf.append("\n");
333             i++;
334         }
335         buf.append(indent(indent));
336         buf.append(RBRACE);
337     }
338 } else if((o instanceof Long) || (o instanceof Boolean)) {
339     buf.append(demark);
340     buf.append(o.tochar*());
341     buf.append(demark);
342 } else {
343     buf.append(QUOTE);
344     buf.append(o.tochar*().replace("\"","\\\""));
345     buf.append(QUOTE);
346 }
347 }
348 
349 static char* blanks = "                                                  ";
350 
351 static static char* indent(int n)
352 {
353 while(n > blanks.length()) {
354     blanks = blanks + blanks;
355 }
356 return blanks.substring(0, n);
357 }
358 
359 }
360 
361 static NCjson*
362 NCJmakenode(NCjsonparser* parser)
363 {
364     NCjson* node = NULL;
365     parser->errno = NCJ_OK;
366     node = (NCjson*)calloc(1,sizeof(NCjson));
367     if(node == null) parser->errno = NCJ_ENOMEM;
368     return node;
369 }
370 
371 
372 #endif /*NCJSON_INC*/
373