1 | // This file is part of SmallBASIC |
2 | // |
3 | // Support for dictionary/associative array variables |
4 | // eg: dim foo: key="blah": foo(key) = "something": ? foo(key) |
5 | // |
6 | // This program is distributed under the terms of the GPL v2.0 or later |
7 | // Download the GNU Public License (GPL) from www.gnu.org |
8 | // |
9 | // Copyright(C) 2010-2019 Chris Warren-Smith. [http://tinyurl.com/ja2ss] |
10 | |
11 | #include "common/sys.h" |
12 | #include "common/pproc.h" |
13 | #include "common/hashmap.h" |
14 | #include "include/var_map.h" |
15 | |
16 | #define BUFFER_GROW_SIZE 64 |
17 | #define BUFFER_PADDING 10 |
18 | #define TOKEN_GROW_SIZE 16 |
19 | #define JSMN_STATIC |
20 | |
21 | #include "lib/jsmn/jsmn.h" |
22 | |
23 | /** |
24 | * Container for map_from_str |
25 | */ |
26 | typedef struct JsonTokens { |
27 | const char *js; |
28 | jsmntok_t *tokens; |
29 | int num_tokens; |
30 | } JsonTokens; |
31 | |
32 | struct ArrayNode; |
33 | typedef struct ArrayNode { |
34 | var_t *v; |
35 | int col; |
36 | int row; |
37 | struct ArrayNode *next; |
38 | } ArrayNode; |
39 | |
40 | typedef struct ArrayList { |
41 | ArrayNode *head; |
42 | ArrayNode *tail; |
43 | } ArrayList; |
44 | |
45 | /** |
46 | * Process the next token |
47 | */ |
48 | int map_read_next_token(var_p_t dest, JsonTokens *json, int index); |
49 | |
50 | /** |
51 | * initialise the variable as a map |
52 | */ |
53 | void map_init(var_p_t map) { |
54 | hashmap_create(map, 0); |
55 | } |
56 | |
57 | /** |
58 | * Compare one MAP to another. see v_compare comments for return spec. |
59 | */ |
60 | int map_compare(const var_p_t var_a, const var_p_t var_b) { |
61 | return 0; |
62 | } |
63 | |
64 | /** |
65 | * Return true if the structure is empty |
66 | */ |
67 | int map_is_empty(const var_p_t var_p) { |
68 | return (var_p->v.m.map == NULL); |
69 | } |
70 | |
71 | /** |
72 | * Return the contents of the structure as an integer |
73 | */ |
74 | int map_to_int(const var_p_t var_p) { |
75 | return map_length(var_p); |
76 | } |
77 | |
78 | /** |
79 | * Return the number of elements |
80 | */ |
81 | int map_length(const var_p_t var_p) { |
82 | int result; |
83 | if (var_p->type == V_MAP) { |
84 | result = var_p->v.m.count; |
85 | } else { |
86 | result = 0; |
87 | } |
88 | return result; |
89 | } |
90 | |
91 | var_p_t map_get(var_p_t base, const char *name) { |
92 | var_p_t result; |
93 | if (base != NULL && base->type == V_MAP) { |
94 | result = hashmap_get(base, name); |
95 | } else { |
96 | result = NULL; |
97 | } |
98 | return result; |
99 | } |
100 | |
101 | int map_get_bool(var_p_t base, const char *name) { |
102 | int result = 0; |
103 | var_p_t var = map_get(base, name); |
104 | if (var != NULL) { |
105 | switch (var->type) { |
106 | case V_INT: |
107 | result = (var->v.i != 0); |
108 | break; |
109 | case V_NUM: |
110 | result = (var->v.n != 0); |
111 | break; |
112 | case V_STR: |
113 | result = (strncasecmp(var->v.p.ptr, "true" , 4)); |
114 | break; |
115 | } |
116 | } |
117 | return result; |
118 | } |
119 | |
120 | int map_get_int(var_p_t base, const char *name, int def) { |
121 | var_p_t var = map_get(base, name); |
122 | return var != NULL ? v_igetval(var) : def; |
123 | } |
124 | |
125 | const char *map_get_str(var_p_t base, const char *name) { |
126 | char *result; |
127 | var_p_t var = map_get(base, name); |
128 | if (var != NULL && var->type == V_STR) { |
129 | result = var->v.p.ptr; |
130 | } else { |
131 | result = NULL; |
132 | } |
133 | return result; |
134 | } |
135 | |
136 | /** |
137 | * return the element at the nth position |
138 | */ |
139 | int map_elem_cb(hashmap_cb *cb, var_p_t key, var_p_t value) { |
140 | int result; |
141 | if (cb->count++ == cb->index) { |
142 | cb->var = key; |
143 | result = 1; |
144 | } else { |
145 | result = 0; |
146 | } |
147 | return result; |
148 | } |
149 | |
150 | /** |
151 | * return the element key at the nth position |
152 | */ |
153 | var_p_t map_elem_key(const var_p_t var_p, int index) { |
154 | var_p_t result; |
155 | if (var_p->type == V_MAP) { |
156 | hashmap_cb cb; |
157 | cb.count = 0; |
158 | cb.index = index; |
159 | cb.var = NULL; |
160 | hashmap_foreach(var_p, map_elem_cb, &cb); |
161 | result = cb.var; |
162 | } else { |
163 | result = NULL; |
164 | } |
165 | return result; |
166 | } |
167 | |
168 | /** |
169 | * Free the map data |
170 | */ |
171 | void map_free(var_p_t var_p) { |
172 | if (var_p->type == V_MAP) { |
173 | hashmap_destroy(var_p); |
174 | v_init(var_p); |
175 | } |
176 | } |
177 | |
178 | /** |
179 | * Returns the final element eg z in foo.x.y.z |
180 | * |
181 | * Scan byte code for node kwTYPE_UDS_EL and attach as field elements |
182 | * if they don't already exist. |
183 | */ |
184 | var_p_t map_resolve_fields(var_p_t base, var_p_t *parent) { |
185 | var_p_t field = NULL; |
186 | if (code_peek() == kwTYPE_UDS_EL) { |
187 | code_skipnext(); |
188 | if (code_peek() != kwTYPE_STR) { |
189 | err_stackmess(); |
190 | return NULL; |
191 | } else { |
192 | code_skipnext(); |
193 | } |
194 | |
195 | if (base->type == V_REF) { |
196 | base = base->v.ref; |
197 | } |
198 | if (base->type != V_MAP) { |
199 | if (v_is_nonzero(base)) { |
200 | err_typemismatch(); |
201 | return NULL; |
202 | } else { |
203 | hashmap_create(base, 0); |
204 | } |
205 | } |
206 | |
207 | // evaluate the variable 'key' name |
208 | int len = code_getstrlen(); |
209 | const char *key = (const char *)&prog_source[prog_ip]; |
210 | prog_ip += len; |
211 | field = hashmap_putc(base, key, len); |
212 | if (parent != NULL) { |
213 | *parent = base; |
214 | } |
215 | |
216 | // evaluate the next sub-element |
217 | field = map_resolve_fields(field, parent); |
218 | } else { |
219 | field = base; |
220 | } |
221 | return field; |
222 | } |
223 | |
224 | /** |
225 | * Adds a new variable onto the map |
226 | */ |
227 | var_p_t map_add_var(var_p_t base, const char *name, int value) { |
228 | var_p_t key = v_new(); |
229 | v_setstr(key, name); |
230 | var_p_t var = hashmap_putv(base, key); |
231 | v_setint(var, value); |
232 | return var; |
233 | } |
234 | |
235 | /** |
236 | * Return the variable in base keyed by key, if not found then creates |
237 | * an empty variable that will be returned in a further call |
238 | */ |
239 | void map_get_value(var_p_t base, var_p_t var_key, var_p_t *result) { |
240 | if (base->type == V_ARRAY && v_asize(base)) { |
241 | // convert the non-empty array to a map |
242 | var_t *clone = v_clone(base); |
243 | |
244 | hashmap_create(base, 0); |
245 | for (int i = 0; i < v_asize(clone); i++) { |
246 | const var_t *element = v_elem(clone, i); |
247 | var_p_t key = v_new(); |
248 | v_setint(key, i); |
249 | var_p_t value = hashmap_putv(base, key); |
250 | v_set(value, element); |
251 | } |
252 | |
253 | // free the clone |
254 | v_free(clone); |
255 | v_detach(clone); |
256 | } else if (base->type != V_MAP) { |
257 | if (v_is_nonzero(base)) { |
258 | *result = v_new(); |
259 | v_set(*result, base); |
260 | return; |
261 | } else { |
262 | hashmap_create(base, 0); |
263 | } |
264 | } |
265 | |
266 | v_tostr(var_key); |
267 | *result = hashmap_put(base, var_key->v.p.ptr, v_strlen(var_key)); |
268 | } |
269 | |
270 | /** |
271 | * Traverse the root to copy into dest |
272 | */ |
273 | int map_set_cb(hashmap_cb *cb, var_p_t var_key, var_p_t value) { |
274 | if (var_key->type != V_STR || var_key->v.p.ptr[0] != MAP_TMP_FIELD[0]) { |
275 | var_p_t key = v_new(); |
276 | v_set(key, var_key); |
277 | var_p_t var = hashmap_putv(cb->var, key); |
278 | v_set(var, value); |
279 | } |
280 | return 0; |
281 | } |
282 | |
283 | /** |
284 | * Copy values from one structure to another |
285 | */ |
286 | void map_set(var_p_t dest, const var_p_t src) { |
287 | if (dest != src && src->type == V_MAP) { |
288 | hashmap_cb cb; |
289 | cb.var = dest; |
290 | hashmap_create(dest, src->v.m.count); |
291 | hashmap_foreach(src, map_set_cb, &cb); |
292 | dest->v.m.count = src->v.m.count; |
293 | dest->v.m.id = src->v.m.id; |
294 | } |
295 | } |
296 | |
297 | void map_set_int(var_p_t base, const char *name, var_int_t n) { |
298 | var_p_t var = map_get(base, name); |
299 | if (var != NULL) { |
300 | v_setint(var, n); |
301 | } else { |
302 | map_add_var(base, name, n); |
303 | } |
304 | } |
305 | |
306 | /** |
307 | * Helper for map_to_str |
308 | */ |
309 | int map_to_str_cb(hashmap_cb *cb, var_p_t v_key, var_p_t v_var) { |
310 | char *key = v_str(v_key); |
311 | char *value = v_str(v_var); |
312 | int required = strlen(cb->buffer) + strlen(key) + strlen(value) + BUFFER_PADDING; |
313 | if (required >= cb->count) { |
314 | cb->count = required + BUFFER_GROW_SIZE; |
315 | cb->buffer = realloc(cb->buffer, cb->count); |
316 | } |
317 | if (!cb->start) { |
318 | strcat(cb->buffer, "," ); |
319 | } |
320 | cb->start = 0; |
321 | strcat(cb->buffer, "\"" ); |
322 | strcat(cb->buffer, key); |
323 | strcat(cb->buffer, "\"" ); |
324 | strcat(cb->buffer, ":" ); |
325 | if (v_var->type == V_STR) { |
326 | strcat(cb->buffer, "\"" ); |
327 | } |
328 | strcat(cb->buffer, value); |
329 | if (v_var->type == V_STR) { |
330 | strcat(cb->buffer, "\"" ); |
331 | } |
332 | free(key); |
333 | free(value); |
334 | return 0; |
335 | } |
336 | |
337 | /** |
338 | * Print the array element, growing the buffer as needed |
339 | */ |
340 | void array_append_elem(hashmap_cb *cb, var_t *elem) { |
341 | char *value = v_str(elem); |
342 | int required = strlen(cb->buffer) + strlen(value) + BUFFER_PADDING; |
343 | if (required >= cb->count) { |
344 | cb->count = required + BUFFER_GROW_SIZE; |
345 | cb->buffer = realloc(cb->buffer, cb->count); |
346 | } |
347 | strcat(cb->buffer, value); |
348 | free(value); |
349 | } |
350 | |
351 | /** |
352 | * print the array variable |
353 | */ |
354 | void array_to_str(hashmap_cb *cb, var_t *var) { |
355 | strcpy(cb->buffer, "[" ); |
356 | if (v_maxdim(var) == 2) { |
357 | // NxN |
358 | int rows = ABS(v_ubound(var, 0) - v_lbound(var, 0)) + 1; |
359 | int cols = ABS(v_ubound(var, 1) - v_lbound(var, 1)) + 1; |
360 | |
361 | for (int i = 0; i < rows; i++) { |
362 | for (int j = 0; j < cols; j++) { |
363 | int pos = i * cols + j; |
364 | var_t *elem = v_elem(var, pos); |
365 | array_append_elem(cb, elem); |
366 | if (j != cols - 1) { |
367 | strcat(cb->buffer, "," ); |
368 | } |
369 | } |
370 | if (i != rows - 1) { |
371 | strcat(cb->buffer, ";" ); |
372 | } |
373 | } |
374 | } else { |
375 | for (int i = 0; i < v_asize(var); i++) { |
376 | var_t *elem = v_elem(var, i); |
377 | array_append_elem(cb, elem); |
378 | if (i != v_asize(var) - 1) { |
379 | strcat(cb->buffer, "," ); |
380 | } |
381 | } |
382 | } |
383 | strcat(cb->buffer, "]" ); |
384 | } |
385 | |
386 | /** |
387 | * Return the contents of the structure as a string |
388 | */ |
389 | char *map_to_str(const var_p_t var_p) { |
390 | hashmap_cb cb; |
391 | cb.count = BUFFER_GROW_SIZE; |
392 | cb.buffer = malloc(cb.count); |
393 | |
394 | if (var_p->type == V_MAP) { |
395 | cb.start = 1; |
396 | strcpy(cb.buffer, "{" ); |
397 | hashmap_foreach(var_p, map_to_str_cb, &cb); |
398 | strcat(cb.buffer, "}" ); |
399 | } else if (var_p->type == V_ARRAY) { |
400 | array_to_str(&cb, var_p); |
401 | } |
402 | return cb.buffer; |
403 | } |
404 | |
405 | /** |
406 | * Print the contents of the structure |
407 | */ |
408 | void map_write(const var_p_t var_p, int method, intptr_t handle) { |
409 | if (var_p->type == V_MAP || var_p->type == V_ARRAY) { |
410 | char *buffer = map_to_str(var_p); |
411 | pv_write(buffer, method, handle); |
412 | free(buffer); |
413 | } |
414 | } |
415 | |
416 | /** |
417 | * Process the next primative value |
418 | */ |
419 | void map_set_primative(var_p_t dest, const char *s, int len) { |
420 | int value = 0; |
421 | int fract = 0; |
422 | int text = 0; |
423 | int sign = 1; |
424 | for (int i = 0; i < len && !text; i++) { |
425 | int n = s[i] - '0'; |
426 | if (n >= 0 && n <= 9) { |
427 | value = value * 10 + n; |
428 | } else if (!fract && s[i] == '.') { |
429 | fract = 1; |
430 | } else if (s[i] == '-' && sign) { |
431 | sign = -1; |
432 | } else { |
433 | text = 1; |
434 | } |
435 | } |
436 | if (text) { |
437 | v_setstrn(dest, s, len); |
438 | } else if (fract) { |
439 | v_setreal(dest, atof(s)); |
440 | } else { |
441 | v_setint(dest, sign * value); |
442 | } |
443 | } |
444 | |
445 | /** |
446 | * Adds a node to the array list |
447 | */ |
448 | var_t *map_array_list_add(ArrayList *list, int row, int col) { |
449 | if (list->head == NULL) { |
450 | list->head = malloc(sizeof(ArrayNode)); |
451 | list->tail = list->head; |
452 | } else { |
453 | list->tail->next = malloc(sizeof(ArrayNode)); |
454 | list->tail = list->tail->next; |
455 | } |
456 | list->tail->next = NULL; |
457 | list->tail->row = row; |
458 | list->tail->col = col; |
459 | list->tail->v = v_new(); |
460 | return list->tail->v; |
461 | } |
462 | |
463 | /** |
464 | * Builds the array from the ArrayNode list |
465 | */ |
466 | void map_build_array(var_p_t dest, ArrayNode *node_next, int rows, int cols) { |
467 | if (rows > 1) { |
468 | v_tomatrix(dest, rows, cols); |
469 | } else { |
470 | v_toarray1(dest, cols); |
471 | } |
472 | while (node_next) { |
473 | int pos = node_next->row * cols + node_next->col; |
474 | v_set(v_elem(dest, pos), node_next->v); |
475 | v_free(node_next->v); |
476 | v_detach(node_next->v); |
477 | ArrayNode *next = node_next->next; |
478 | free(node_next); |
479 | node_next = next; |
480 | } |
481 | } |
482 | |
483 | /** |
484 | * Creates an array variable |
485 | */ |
486 | int map_create_array(var_p_t dest, JsonTokens *json, int end_position, int index) { |
487 | int i = index; |
488 | int rows = 0; |
489 | int cols = 0; |
490 | int curcol = 0; |
491 | ArrayList list; |
492 | |
493 | list.head = NULL; |
494 | list.tail = NULL; |
495 | |
496 | while (i < json->num_tokens) { |
497 | jsmntok_t token = json->tokens[i]; |
498 | if (token.start > end_position) { |
499 | break; |
500 | } |
501 | var_t *elem = map_array_list_add(&list, rows, curcol++); |
502 | if (token.type == JSMN_PRIMITIVE && json->tokens[0].type == JSMN_ARRAY) { |
503 | int len = token.end - token.start; |
504 | const char *str = json->js + token.start; |
505 | const char *delim = memchr(str, ';', len); |
506 | if (delim != NULL) { |
507 | if ((delim - str) > 0) { |
508 | map_set_primative(elem, str, delim - str); |
509 | if (curcol > cols) { |
510 | cols = curcol; |
511 | } |
512 | } |
513 | while (delim != NULL) { |
514 | rows++; |
515 | curcol = 0; |
516 | len -= (delim - str) + 1; |
517 | if (len > 0) { |
518 | // text exists beyond ';' |
519 | str = ++delim; |
520 | if (*str != ';') { |
521 | elem = map_array_list_add(&list, rows, curcol++); |
522 | map_set_primative(elem, str, len); |
523 | } |
524 | delim = memchr(str, ';', len); |
525 | } else { |
526 | // no more text, just count the new row |
527 | delim = NULL; |
528 | } |
529 | } |
530 | } else { |
531 | map_set_primative(elem, json->js + token.start, token.end - token.start); |
532 | } |
533 | i++; |
534 | } else { |
535 | i = map_read_next_token(elem, json, i); |
536 | } |
537 | if (curcol > cols) { |
538 | cols = curcol; |
539 | } |
540 | } |
541 | map_build_array(dest, list.head, rows+1, cols); |
542 | return i; |
543 | } |
544 | |
545 | /** |
546 | * Creates a map variable |
547 | */ |
548 | int map_create(var_p_t dest, JsonTokens *json, int end_position, int index) { |
549 | hashmap_create(dest, 0); |
550 | int i = index; |
551 | while (i < json->num_tokens) { |
552 | jsmntok_t token = json->tokens[i]; |
553 | if (token.start > end_position) { |
554 | break; |
555 | } else if (token.type == JSMN_STRING || token.type == JSMN_PRIMITIVE) { |
556 | var_p_t key = v_new(); |
557 | map_set_primative(key, json->js + token.start, token.end - token.start); |
558 | var_p_t value = hashmap_putv(dest, key); |
559 | i = map_read_next_token(value, json, i + 1); |
560 | } else { |
561 | err_array(); |
562 | break; |
563 | } |
564 | } |
565 | return i; |
566 | } |
567 | |
568 | /** |
569 | * Process the next token |
570 | */ |
571 | int map_read_next_token(var_p_t dest, JsonTokens *json, int index) { |
572 | int next; |
573 | jsmntok_t token = json->tokens[index]; |
574 | switch (token.type) { |
575 | case JSMN_OBJECT: |
576 | next = map_create(dest, json, token.end, index + 1); |
577 | break; |
578 | case JSMN_ARRAY: |
579 | next = map_create_array(dest, json, token.end, index + 1); |
580 | break; |
581 | case JSMN_PRIMITIVE: |
582 | map_set_primative(dest, json->js + token.start, token.end - token.start); |
583 | next = index + 1; |
584 | break; |
585 | case JSMN_STRING: |
586 | v_setstrn(dest, json->js + token.start, token.end - token.start); |
587 | next = index + 1; |
588 | break; |
589 | default: |
590 | next = -1; |
591 | break; |
592 | } |
593 | return next; |
594 | } |
595 | |
596 | void map_parse_str(const char *js, size_t len, var_p_t dest) { |
597 | int num_tokens = TOKEN_GROW_SIZE; |
598 | jsmntok_t *tokens = malloc(sizeof(jsmntok_t) * num_tokens); |
599 | int result; |
600 | jsmn_parser parser; |
601 | |
602 | jsmn_init(&parser); |
603 | for (result = jsmn_parse(&parser, js, len, tokens, num_tokens); |
604 | result == JSMN_ERROR_NOMEM; |
605 | result = jsmn_parse(&parser, js, len, tokens, num_tokens)) { |
606 | num_tokens += TOKEN_GROW_SIZE; |
607 | tokens = realloc(tokens, sizeof(jsmntok_t) * num_tokens); |
608 | } |
609 | if (result < 0) { |
610 | err_array(); |
611 | } else if (result > 0) { |
612 | v_init(dest); |
613 | |
614 | JsonTokens json; |
615 | json.tokens = tokens; |
616 | json.js = js; |
617 | json.num_tokens = parser.toknext; |
618 | map_read_next_token(dest, &json, 0); |
619 | } |
620 | free(tokens); |
621 | } |
622 | |
623 | /** |
624 | * Initialise a map from a string |
625 | */ |
626 | void map_from_str(var_p_t dest) { |
627 | var_t arg; |
628 | v_init(&arg); |
629 | eval(&arg); |
630 | if (!prog_error) { |
631 | if (arg.type != V_STR) { |
632 | v_set(dest, &arg); |
633 | } else { |
634 | map_parse_str(arg.v.p.ptr, arg.v.p.length, dest); |
635 | } |
636 | } |
637 | v_free(&arg); |
638 | } |
639 | |
640 | // array <- CODEARRAY(x1,y1...[;x2,y2...]) |
641 | // dynamic arrays created with the [] operators |
642 | void map_from_codearray(var_p_t dest) { |
643 | int rows = 0; |
644 | int cols = 0; |
645 | int curcol = 0; |
646 | int ready = 0; |
647 | int seps = 0; |
648 | ArrayList list; |
649 | |
650 | list.head = NULL; |
651 | list.tail = NULL; |
652 | |
653 | do { |
654 | switch (code_peek()) { |
655 | case kwTYPE_SEP: |
656 | seps++; |
657 | code_skipnext(); |
658 | if (code_peek() == ';') { |
659 | // next row |
660 | rows++; |
661 | curcol = 0; |
662 | } else if (++curcol > cols) { |
663 | // next col |
664 | cols = curcol; |
665 | } |
666 | code_skipnext(); |
667 | break; |
668 | case kwTYPE_LEVEL_END: |
669 | ready = 1; |
670 | break; |
671 | default: |
672 | eval(map_array_list_add(&list, rows, curcol)); |
673 | } |
674 | } while (!ready && !prog_error); |
675 | |
676 | if (!seps && list.head == NULL) { |
677 | v_toarray1(dest, 0); |
678 | } else { |
679 | map_build_array(dest, list.head, rows+1, cols+1); |
680 | } |
681 | } |
682 | |