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 */
26typedef struct JsonTokens {
27 const char *js;
28 jsmntok_t *tokens;
29 int num_tokens;
30} JsonTokens;
31
32struct ArrayNode;
33typedef struct ArrayNode {
34 var_t *v;
35 int col;
36 int row;
37 struct ArrayNode *next;
38} ArrayNode;
39
40typedef struct ArrayList {
41 ArrayNode *head;
42 ArrayNode *tail;
43} ArrayList;
44
45/**
46 * Process the next token
47 */
48int map_read_next_token(var_p_t dest, JsonTokens *json, int index);
49
50/**
51 * initialise the variable as a map
52 */
53void 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 */
60int 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 */
67int 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 */
74int 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 */
81int 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
91var_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
101int 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
120int 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
125const 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 */
139int 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 */
153var_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 */
171void 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 */
184var_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 */
227var_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 */
239void 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 */
273int 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 */
286void 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
297void 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 */
309int 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 */
340void 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 */
354void 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 */
389char *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 */
408void 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 */
419void 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 */
448var_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 */
466void 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 */
486int 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 */
548int 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 */
571int 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
596void 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 */
626void 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
642void 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