-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathjson.c
executable file
·603 lines (539 loc) · 25.2 KB
/
json.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
// SPDX-License-Identifier: Apache-2.0
// Copyright 2019 Gliim LLC.
// Licensed under Apache License v2. See LICENSE file.
// On the web http://golf-lang.com/ - this file is part of Golf framework.
//
// JSON-related module
//
// Implementation based on JSON standard https://datatracker.ietf.org/doc/html/rfc7159
// See examples of JSON at https://www.json.org/example.html (by douglas@crockford.com)
#include "golf.h"
//
// temporary string binding, zero-out byte with BINDV and restore with UNBINDV. Cannot be nested.
//
static char tb;
#define BINDV(x) tb=*(x); *(x) =0
#define UNBINDV(x) *(x) = tb
//
// end temporary string binding
//
// default number of json nodes allocated, incremented by
#define GG_JSON_NODES 32
// max depth of normalized name
#define GG_JSON_MAX_NESTED 32
// Add json node to list of nodes, if hash is used
// *i is the location of where this element was found, so ec is the location, and if cannot allocate memory, go to endj, which must
// be visible from where GG_ADD_JSON is used. j_tp is the type of node, j_str value, lc is the count in the list of nodes prior to
// this node, l is the list of nodes (normalized name). 'n' here is calculated if needed, which isn't needed if no-hash is used.
// So no-hash performs ultra-fast JSON parsing, with virtually no memory allocated.
// A node after the last has empty name.
#define GG_ADD_JSON(j_tp, j_str, lc, l) { gg_add_json(); char *n = gg_json_fullname (l, lc); nodes[node_c].name = n; nodes[node_c].type = j_tp; nodes[node_c].str = j_str; nodes[node_c].alloced = false; node_c++; };
// Prototypes
static char *gg_json_fullname (json_node *list, gg_num list_c);
static char *gg_json_num(char *val, gg_num *rv);
static void gg_add_json ();
// Variables used by json parser (recursive)
static json_node list[GG_JSON_MAX_NESTED]; // max depth of nested names, used to construct them
static gg_num list_c = -1; // index of current node being traversed, it's index into array of json_node type, increments with {
static gg_num node_tot = 0; //node total alloc'd
static gg_num node_c = 0; //node counter (current)
static gg_jsonn *nodes = NULL; // nodes of normalized json (name, type, value)
static gg_json *jloc = NULL; // final result, super structure for the whole json document
static char nulled = 0; // character that is nulled to bind a string, used in the following parser iteration
static char *errm; // error message
static gg_num ec = -1; // location where error happened (0..size) or -1 if okay
static gg_num depth = 0; // depth of recursion
//
// Get the normalized name of a leaf
// "list" is the array of names leading up to here, list_c is the index of the final name
// val is the value of leaf, type is its type
// returns normalized name for a leaf name in name:value, this is
// returned as an allocated value
// this accounts for any arrays
//
char *gg_json_fullname (json_node *list, gg_num list_c)
{
GG_TRACE("");
if (list_c == 0)
{
char *fulln = gg_malloc (3); // 2 for "" + null
strcpy (fulln, "\"\"");
gg_mem_set_len (gg_mem_get_id(fulln), 3);
return fulln;
} // in case json doc is just a string or gg_number and nothing else, so list_c is 0
gg_num i;
gg_num nlen = 0; // length of normalized name
// first calculate the memory needed to hold normalized name
for (i = 0; i < list_c; i++)
{
if (list[i].index == -1)
{
nlen += 1 + 2 + list[i].name_len; /* 1 for dot, 2 for ", plus length of name (we ignore i==0 and just allocate one byte extra)*/
}
else
{
gg_num a = list[i].index;
gg_num alen; // length of digits in array index
if (a == 0) alen = 1; else { for (alen = 0; a != 0; alen++, a = a / 10){} }
list[i].index_len = alen;
nlen += 1 + 2 + list[i].name_len + 2 + list[i].index_len; /* 1 for dot, 2 for ", 2 for [], plus length of array index*/
}
}
char *fulln = gg_malloc (nlen + 1); // +1 for null
gg_num fullnc = 0; // curr length of normalized name
for (i = 0; i < list_c; i++)
{
// construct normalized name "x"[..]."y"...
// first ."x" or "x" if first
if (i != 0) { memcpy (fulln + fullnc, ".\"", 2); fullnc += 2; } // do not include leading dot, just
// in between nodes
else { memcpy (fulln + fullnc, "\"", 1); fullnc += 1; }
memcpy (fulln + fullnc, list[i].name, list[i].name_len); fullnc += list[i].name_len;
memcpy (fulln + fullnc, "\"", 1); fullnc += 1;
// then if array, add [xxx]
if (jloc->noenum == false && list[i].index != -1)
{
memcpy (fulln + fullnc, "[", 1); fullnc += 1;
// output index number, first check for 0
gg_num al = list[i].index;
if (al == 0) { fulln[fullnc] = '0'; fullnc += 1; }
else
{
// here, get all digits, fill last first, since we're doing moduo 10
gg_num k = 0;
while (al != 0)
{
int r = '0' + (al % 10);
fulln[fullnc + list[i].index_len - 1 - k] = r;
k++;
al = al/10;
}
fullnc += k;
}
// finish with ]
memcpy (fulln + fullnc, "]", 1); fullnc += 1;
}
}
fulln[fullnc] = 0;
gg_mem_set_len (gg_mem_get_id(fulln), fullnc+1);
return fulln;
}
//
// Set the end result of json parsing. 'j' is the json object,
// noenum is true if no [] in normalized path
//
void gg_set_json (gg_json **j, bool noenum)
{
GG_TRACE("");
// get json object
*j = (gg_json*)gg_malloc (sizeof(gg_json));
jloc = *j; // set local processing object
jloc->noenum = noenum;
}
//
// Delete all allocated data for json j
//
void gg_del_json (gg_json **j)
{
GG_TRACE("");
gg_num i;
for (i = 0; i < (*j)->node_c; i++)
{
gg_free ((*j)->nodes[i].name);
// do NOT delete str as that's not allocated memory. If user asks, we lazily copy this and deliver,
// but a lot of times, user does NOT ask, so it saves time
if ((*j)->nodes[i].alloced) gg_free ((*j)->nodes[i].str);
}
if ((*j)->node_c != 0) gg_free ((*j)->nodes);
(*j)->node_c = 0;
gg_free (*j); // delete the entire json structure
*j = NULL;
}
//
// Make sure json nodes always have room allocated for new elements
// As more elements are added, double the storage, up until 4K blocks
//
void gg_add_json ()
{
GG_TRACE("");
static gg_num incby;
if (node_tot == 0) incby = GG_JSON_NODES/2; // must start with half, so that initial block below is GG_JSON_NODES, since
// malloc/realloc choice depends on it
if (node_c >= node_tot)
{
if (incby < 4096) incby *= 2; // initial block is GG_JSON_NODES
node_tot += incby;
if (node_tot == GG_JSON_NODES) nodes = gg_malloc (node_tot*sizeof (gg_jsonn));
else
{
gg_num id = gg_mem_get_id(nodes);
nodes = gg_realloc (id, node_tot*sizeof (gg_jsonn));
}
// initialize nodes to prevent program crashing if developer fails to check the status
gg_num i;
for (i = node_c; i < node_tot; i++) {nodes[i].name = GG_EMPTY_STRING; nodes[i].str = GG_EMPTY_STRING; nodes[i].type = GG_JSON_TYPE_STRING; }
}
}
//
// Returns current error or "" if none.
//
char *gg_json_err()
{
return errm;
}
//
// parse val as JSON text. val is modified - make a copy of val if you still need it.
// len is the length of val, if -1 then we use strlen to figure it out
// curr is the current position from where to start parsing, it's NULL for top call in recursion
// returns -1 if okay, or position of error if not.
// To get error, use gg_json_err()
// if "dec" is 0, do not decode strings, otherwise decode
//
gg_num gg_json_new (char *val, gg_num *curr, gg_num len, char dec)
{
GG_TRACE("");
char root_call = 0; // 1 if this is top call in recursive processing
gg_num c;
gg_num *i;
if (curr == NULL)
{
// this is root call
root_call = 1;
errm = GG_EMPTY_STRING; // no error by default
ec = -1; // exit code by default
depth = 0; // max dept allowed, currently we're in first (root) invocation
list_c = -1; // start with root for arrays
// set byte counter to start from the beginning
c = 0;
i = &c;
gg_num j;
for (j = 0; j < GG_JSON_MAX_NESTED; j++)
{
list[j].index = -1; // array adds 1, so -1 means no array at this level
list[j].name = NULL;
}
// create initial block of normalized nodes
node_c = 0;
node_tot = 0; // both node_c and node_tot must be 0 for allocation to work properly, see gg_add_json
gg_add_json();
} else i = curr; // inherit byte counter from a recursive parent
// check if too many nested
depth ++;
if (depth >= GG_JSON_MAX_NESTED) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_DEPTH); goto endj; }
// various flags for checking the validity of JSON doc, mostly what's expected to be found at any point
char expected_comma_or_end_array = 0;
char expected_comma_or_end_object = 0;
char expected_colon = 0;
char expected_name = 0; // when string is found, it's 1 if it's name, 0 for value. By default, we look for value.
char isarr = 0; // 0 if not in array [], 1 if in array
char isobj = 0; // 0 if not in object, 1 if in object
//
// JSON text is the same as value. So just "123" is a valid JSON text
// A JSON value MUST be an object, array, number, or string, or one of the following three literal names: false null true
//
gg_num vlen = gg_mem_get_len(gg_mem_get_id( val) );
if (len == -1) len = vlen; // len is -1 only in root invocation
else if (len > vlen) gg_report_error ("Memory used is of length [%ld] but only [%ld] allocated", len, vlen);
list_c++; // every time value is about to be found, go one level up (and when found, one down)
// the limit for list_c is GG_JSON_MAX_NESTED -1 so there is always one empty after the last with empty name
// to mark the end if key-count in read-json isn't used
if (list_c >= GG_JSON_MAX_NESTED - 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_DEPTH); goto endj; }
char nchar = 0;
while (*i < len) // initial value of i is determined at the beginning of this function, which is recursive
{
if (nulled != 0) { nchar = nulled; nulled = 0; } else nchar = val[*i];
switch (nchar)
{
// begin object, zero or more name:value inside separated by commas. Names should be unique.
case '{':
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
if (expected_comma_or_end_array == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_ARRAY_EXPECTED); goto endj; }
if (expected_comma_or_end_object == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_OBJECT_EXPECTED); goto endj; }
expected_name = 1;
isobj = 1;
(*i)++;
break;
// end object
case '}':
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
if (expected_comma_or_end_array == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_ARRAY_EXPECTED); goto endj; }
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
expected_comma_or_end_object = 0;
isobj = 0;
list_c --;
(*i)++;
{ goto endj; }
break;
// begin array, zero or more values inside separated by commas. Values can be of different types.
case '[':
{
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
if (expected_comma_or_end_array == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_ARRAY_EXPECTED); goto endj; }
if (expected_comma_or_end_object == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_OBJECT_EXPECTED); goto endj; }
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
isarr = 1;
(*i)++; // get passed [ to find the value that follows
// use previous element because array applies to it
list_c--;
list[list_c].index++; // if index was -1, now it's 0 (first element in array), otherwise increments it
if (gg_json_new (val, i, len,dec) != -1) { goto endj; }
// no incrementing *i because it's done in gg_json_new()
expected_comma_or_end_array = 1;
break;
}
// end array
case ']':
expected_comma_or_end_array = 0;
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
if (expected_comma_or_end_object == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_OBJECT_EXPECTED); goto endj; }
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
isarr = 0;
(*i)++; // get passed ] to find the value that follows
list[list_c].index = -1; // no longer array at this level
list_c++; // increase to put it back where it was before we decreased it in [
list_c --;
{ goto endj; }
break;
// name:value separator
case ':':
{
expected_colon = 0;
if (expected_comma_or_end_array == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_ARRAY_EXPECTED); goto endj; }
if (expected_comma_or_end_object == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_OBJECT_EXPECTED); goto endj; }
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
(*i)++; // get passed : to find the value that follows
if (gg_json_new (val, i, len ,dec) != -1) { goto endj; } // return value if failed
// no incrementing *i because it's done in gg_json()
expected_comma_or_end_object = 1;
break;
}
// value separator
case ',':
{
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
expected_comma_or_end_array = 0;
expected_comma_or_end_object = 0;
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
(*i)++; // get passed : to find the value that follows
// must be within object or array
if (isobj ==1) { expected_name = 1; continue;} // if we're in name:value list of pairs, continue to next name
// if we're in array of values, find the next value
else if (isarr == 1) list[list_c].index++; // this is next array element, advance the index
else { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_UNRECOGNIZED); goto endj; }
if (gg_json_new (val, i, len ,dec) != -1) { goto endj; } // return value if failed
// no incrementing *i because it's done in gg_json_new()
if (isobj == 1) expected_comma_or_end_object = 1;
if (isarr == 1) expected_comma_or_end_array = 1;
break;
}
// white spaces
case ' ':
case '\t':
case '\n':
case '\r':
(*i)++;
continue;
// string
case '"':
{
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
if (expected_comma_or_end_array == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_ARRAY_EXPECTED); goto endj; }
if (expected_comma_or_end_object == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_OBJECT_EXPECTED); goto endj; }
char *end;
// get length of string
end=gg_text_to_utf8 (val+*i, 1, &errm, dec, false);
if (end == NULL) { goto endj; }
int lstr = (end - (val+*i));
BINDV(end); // put 0 at the end, do NOT set nulled because there's double quote
// which we nulled and we don't want to continue from this double quote
// but rather from one byte ahead
char *str = val + *i + 1;
(*i) += lstr; // points to final 0, but *i gets increased in for() to get passed it
if (expected_name == 1)
{
// this is name in an array of names leading up to name:value
list[list_c].name = str;
list[list_c].name_len = lstr - 1;
(*i)++;
expected_name = 0;
expected_colon = 1;
}
else
{
//set node with value
GG_ADD_JSON(GG_JSON_TYPE_STRING, str, list_c, list);
(*i)++; // increase to get passed 0 byte when it returns
list_c --;
{ goto endj; }
}
// no UNBINDV, we modify original string at the point of closing " (we place 0 there)
break;
}
// number
case '-':
case '0' ... '9':
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
if (expected_comma_or_end_array == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_ARRAY_EXPECTED); goto endj; }
if (expected_comma_or_end_object == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_OBJECT_EXPECTED); goto endj; }
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
gg_num rv;
char *r = gg_json_num (val+*i, &rv);
if (r == NULL) { ec = *i; goto endj; } // errm set in gg_json_num()
char *str = val + *i;
BINDV(r); // put 0 at the end
nulled = tb;
if (rv == 1)
{
//set node with value
GG_ADD_JSON(GG_JSON_TYPE_REAL, str, list_c, list);
;
}
else if (rv == 0)
{
//set node with value
GG_ADD_JSON(GG_JSON_TYPE_NUMBER, str, list_c, list);
;
}
else { { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NUMBER); goto endj; }}
(*i) += (r - val-*i);
list_c --;
{ goto endj; }
break;
// true
case 't':
{
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
if (expected_comma_or_end_array == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_ARRAY_EXPECTED); goto endj; }
if (expected_comma_or_end_object == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_OBJECT_EXPECTED); goto endj; }
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
if (strncmp (val+*i, "true", sizeof("true")-1)) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_UNKNOWN); goto endj; }
char *str = val+*i;
BINDV(val+*i+strlen("true")); // put 0 at the end
nulled = tb;
//set node with value
GG_ADD_JSON(GG_JSON_TYPE_BOOL, str, list_c, list);
(*i) += strlen("true"); // get passed value
list_c --;
{ goto endj; }
break;
}
// false
case 'f':
{
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
if (expected_comma_or_end_array == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_ARRAY_EXPECTED); goto endj; }
if (expected_comma_or_end_object == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_OBJECT_EXPECTED); goto endj; }
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
if (strncmp (val+*i, "false", sizeof("false")-1)) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_UNKNOWN); goto endj; }
char *str = val+*i;
BINDV(val+*i+strlen("false")); // put 0 at the end
nulled = tb;
//set node with value
GG_ADD_JSON(GG_JSON_TYPE_BOOL, str, list_c, list);
(*i) += strlen("false"); // get passed value
list_c --;
{ goto endj; }
break;
}
// null
case 'n':
{
if (expected_colon == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COLON_EXPECTED); goto endj; }
if (expected_comma_or_end_array == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_ARRAY_EXPECTED); goto endj; }
if (expected_comma_or_end_object == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_COMMA_END_OBJECT_EXPECTED); goto endj; }
if (expected_name == 1) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NAME_EXPECTED); goto endj; }
if (strncmp (val+*i, "null", sizeof("null")-1)) { ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_UNKNOWN); goto endj; }
char *str = val+*i;
BINDV(val+*i+strlen("null")); // put 0 at the end
nulled = tb;
//set node with value
GG_ADD_JSON(GG_JSON_TYPE_NULL, str, list_c, list);
(*i) += strlen("null"); // get passed value
list_c --;
{ goto endj; }
break;
}
default:
{
ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_UNRECOGNIZED); goto endj; // unrecognized token
}
}
}
list_c --;
endj:
depth--;
if (root_call == 1)
{
// final result, this is root call
jloc->nodes = nodes;
jloc->node_c = node_c;
jloc->node_r = 0; // first to read with read-json
// parser always get json value, and there can be whitespace afterwards. After cleaning it up,
// it must amount to the full document; otherwise there's something left-over that's not json.
while (isspace (val[*i])) (*i)++;
// check if there's something extra not processed, but only if error not already set
if (ec == -1 && *i < len)
{
ec = *i; GG_ERR0; errm = gg_strdup(GG_ERR_JSON_SYNTAX);
}
}
return ec;
}
//
// rv is return value: 0 for number, 1 for double, 2 if bad
// returns pointer to first byte after number or NULL if failed. Sets errm.
//
char *gg_json_num(char *val, gg_num *rv)
{
GG_TRACE("");
gg_num i = 0;
char isdbl = 0;
// get the sign
if (val[i] == '-')
{
i++;
}
// get the int, check if first char is digit and if 0, check there's nothing else
gg_num dig = i;
while (isdigit(val[i])) i++;
gg_num edig = i;
if (!isdigit(val[dig])) { GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NUMBER); return NULL;}
if (val[dig] == '0' && edig != dig+1) { GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NUMBER); return NULL;}
// get the decimal point
if (val[edig] == '.')
{
i = edig + 1;
// fraction start with dot to easily convert to double
while (isdigit(val[i])) i++;
// check there's at least one digit after dot
if (!isdigit(val[edig+1])) { GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NUMBER); return NULL;}
isdbl = 1;
}
// get the exponent
gg_num exp = -1;
if (val[i] == 'e' || val[i] == 'E')
{
i++;
if (val[i] == '+') { i++;}
else if (val[i] == '-') { i++;}
exp = i;
while (isdigit(val[i])) i++;
// check there's at least one digit in exponent
if (!isdigit(val[exp])) { GG_ERR0; errm = gg_strdup(GG_ERR_JSON_NUMBER); return NULL;}
isdbl = 1;
}
if (isdbl == 0)
{
*rv = 0; // number
}
else if (isdbl == 1)
{
*rv = 1;
}
else
{
*rv = 2;
}
return val + i;
}