scan fails with strings containing nulls
[jimtcl.git] / jim.c
blob4af6eee95f99e90d1d6c390174f3c7ef36dc2902
2 /* Jim - A small embeddable Tcl interpreter
4 * Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
5 * Copyright 2005 Clemens Hintze <c.hintze@gmx.net>
6 * Copyright 2005 patthoyts - Pat Thoyts <patthoyts@users.sf.net>
7 * Copyright 2008,2009 oharboe - Øyvind Harboe - oyvind.harboe@zylin.com
8 * Copyright 2008 Andrew Lunn <andrew@lunn.ch>
9 * Copyright 2008 Duane Ellis <openocd@duaneellis.com>
10 * Copyright 2008 Uwe Klein <uklein@klein-messgeraete.de>
11 * Copyright 2008 Steve Bennett <steveb@workware.net.au>
12 * Copyright 2009 Nico Coesel <ncoesel@dealogic.nl>
13 * Copyright 2009 Zachary T Welch zw@superlucidity.net
14 * Copyright 2009 David Brownell
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions
18 * are met:
20 * 1. Redistributions of source code must retain the above copyright
21 * notice, this list of conditions and the following disclaimer.
22 * 2. Redistributions in binary form must reproduce the above
23 * copyright notice, this list of conditions and the following
24 * disclaimer in the documentation and/or other materials
25 * provided with the distribution.
27 * THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
28 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
29 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
30 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
31 * JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
32 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
33 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
36 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
38 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 * The views and conclusions contained in the software and documentation
41 * are those of the authors and should not be interpreted as representing
42 * official policies, either expressed or implied, of the Jim Tcl Project.
43 **/
44 #define JIM_OPTIMIZATION /* comment to avoid optimizations and reduce size */
46 #include <stdio.h>
47 #include <stdlib.h>
49 #include <string.h>
50 #include <stdarg.h>
51 #include <ctype.h>
52 #include <limits.h>
53 #include <assert.h>
54 #include <errno.h>
55 #include <time.h>
56 #include <setjmp.h>
58 #include <unistd.h>
59 #include <sys/time.h>
61 #include "jim.h"
62 #include "jimautoconf.h"
63 #include "utf8.h"
65 #ifdef HAVE_BACKTRACE
66 #include <execinfo.h>
67 #endif
69 /* For INFINITY, even if math functions are not enabled */
70 #include <math.h>
72 /*#define DEBUG_SHOW_SCRIPT*/
73 /*#define DEBUG_SHOW_SCRIPT_TOKENS*/
74 /*#define DEBUG_SHOW_SUBST*/
75 /*#define DEBUG_SHOW_EXPR*/
76 /*#define JIM_DEBUG_GC*/
77 /*#define JIM_DEBUG_COMMAND*/
79 #if defined(DEBUG_SHOW_SCRIPT) || defined(DEBUG_SHOW_SCRIPT_TOKENS) || defined(DEBUG_SHOW_EXPR) || defined(DEBUG_SHOW_SUBST)
80 static const char *tt_name(int type);
81 #endif
83 /* -----------------------------------------------------------------------------
84 * Global variables
85 * ---------------------------------------------------------------------------*/
87 /* A shared empty string for the objects string representation.
88 * Jim_InvalidateStringRep knows about it and doesn't try to free it. */
89 static char JimEmptyStringRep[] = "";
91 /* -----------------------------------------------------------------------------
92 * Required prototypes of not exported functions
93 * ---------------------------------------------------------------------------*/
94 static void JimChangeCallFrameId(Jim_Interp *interp, Jim_CallFrame *cf);
95 static void JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int flags);
96 static int ListSetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int listindex, Jim_Obj *newObjPtr,
97 int flags);
98 static Jim_Obj *Jim_ExpandDictSugar(Jim_Interp *interp, Jim_Obj *objPtr);
99 static void SetDictSubstFromAny(Jim_Interp *interp, Jim_Obj *objPtr);
100 static void JimSetFailedEnumResult(Jim_Interp *interp, const char *arg, const char *badtype,
101 const char *prefix, const char *const *tablePtr, const char *name);
102 static void JimDeleteLocalProcs(Jim_Interp *interp);
103 static int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, const char *filename, int linenr,
104 int argc, Jim_Obj *const *argv);
105 static int JimEvalObjVector(Jim_Interp *interp, int objc, Jim_Obj *const *objv,
106 const char *filename, int linenr);
107 static int JimGetWideNoErr(Jim_Interp *interp, Jim_Obj *objPtr, jim_wide * widePtr);
108 static int JimSign(jim_wide w);
110 static const Jim_HashTableType JimVariablesHashTableType;
112 /* Fast access to the int (wide) value of an object which is known to be of int type */
113 #define JimWideValue(objPtr) (objPtr)->internalRep.wideValue
115 static int utf8_tounicode_case(const char *s, int *uc, int upper)
117 int l = utf8_tounicode(s, uc);
118 if (upper) {
119 *uc = utf8_upper(*uc);
121 return l;
124 /* These can be used in addition to JIM_CASESENS/JIM_NOCASE */
125 #define JIM_CHARSET_SCAN 2
126 #define JIM_CHARSET_GLOB 0
129 * pattern points to a string like "[^a-z\ub5]"
131 * The pattern may contain trailing chars, which are ignored.
133 * The pattern is matched against unicode char 'c'.
135 * If (flags & JIM_NOCASE), case is ignored when matching.
136 * If (flags & JIM_CHARSET_SCAN), the considers ^ and ] special at the start
137 * of the charset, per scan, rather than glob/string match.
139 * If the unicode char 'c' matches that set, returns a pointer to the ']' character,
140 * or the null character if the ']' is missing.
142 * Returns NULL on no match.
144 static const char *JimCharsetMatch(const char *pattern, int c, int flags)
146 int not = 0;
147 int pchar;
148 int match = 0;
149 int nocase = 0;
151 if (flags & JIM_NOCASE) {
152 nocase++;
153 c = utf8_upper(c);
156 if (flags & JIM_CHARSET_SCAN) {
157 if (*pattern == '^') {
158 not++;
159 pattern++;
162 /* Special case. If the first char is ']', it is part of the set */
163 if (*pattern == ']') {
164 goto first;
168 while (*pattern && *pattern != ']') {
169 /* Exact match */
170 if (pattern[0] == '\\') {
171 first:
172 pattern += utf8_tounicode_case(pattern, &pchar, nocase);
174 else {
175 /* Is this a range? a-z */
176 int start;
177 int end;
179 pattern += utf8_tounicode_case(pattern, &start, nocase);
180 if (pattern[0] == '-' && pattern[1]) {
181 /* skip '-' */
182 pattern += utf8_tounicode(pattern, &pchar);
183 pattern += utf8_tounicode_case(pattern, &end, nocase);
185 /* Handle reversed range too */
186 if ((c >= start && c <= end) || (c >= end && c <= start)) {
187 match = 1;
189 continue;
191 pchar = start;
194 if (pchar == c) {
195 match = 1;
198 if (not) {
199 match = !match;
202 return match ? pattern : NULL;
205 /* Glob-style pattern matching. */
207 /* Note: string *must* be valid UTF-8 sequences
208 * slen is a char length, not byte counts.
210 static int GlobMatch(const char *pattern, const char *string, int nocase)
212 int c;
213 int pchar;
214 while (*pattern) {
215 switch (pattern[0]) {
216 case '*':
217 while (pattern[1] == '*') {
218 pattern++;
220 pattern++;
221 if (!pattern[0]) {
222 return 1; /* match */
224 while (*string) {
225 /* Recursive call - Does the remaining pattern match anywhere? */
226 if (GlobMatch(pattern, string, nocase))
227 return 1; /* match */
228 string += utf8_tounicode(string, &c);
230 return 0; /* no match */
232 case '?':
233 if (!*string)
234 return 0; /* no match */
235 string += utf8_tounicode(string, &c);
236 break;
238 case '[': {
239 string += utf8_tounicode(string, &c);
240 pattern = JimCharsetMatch(pattern + 1, c, nocase ? JIM_NOCASE : 0);
241 if (!pattern) {
242 return 0;
244 if (!*pattern) {
245 /* Ran out of pattern (no ']') */
246 continue;
248 break;
250 case '\\':
251 if (pattern[1]) {
252 pattern++;
254 /* fall through */
255 default:
256 string += utf8_tounicode_case(string, &c, nocase);
257 utf8_tounicode_case(pattern, &pchar, nocase);
258 if (pchar != c) {
259 return 0;
261 break;
263 pattern += utf8_tounicode_case(pattern, &pchar, nocase);
264 if (!*string) {
265 while (*pattern == '*') {
266 pattern++;
268 break;
271 if (!*pattern && !*string) {
272 return 1;
274 return 0;
277 static int JimStringMatch(Jim_Interp *interp, Jim_Obj *patternObj, const char *string, int nocase)
279 return GlobMatch(Jim_GetString(patternObj, NULL), string, nocase);
283 * string comparison works on binary data.
285 * Note that the lengths are byte lengths, not char lengths.
287 static int JimStringCompare(const char *s1, int l1, const char *s2, int l2)
289 if (l1 < l2) {
290 return memcmp(s1, s2, l1) <= 0 ? -1 : 1;
292 else if (l2 < l1) {
293 return memcmp(s1, s2, l2) >= 0 ? 1 : -1;
295 else {
296 return JimSign(memcmp(s1, s2, l1));
301 * No-case version.
303 * If maxchars is -1, compares to end of string.
304 * Otherwise compares at most 'maxchars' characters.
306 static int JimStringCompareNoCase(const char *s1, const char *s2, int maxchars)
308 while (*s1 && *s2 && maxchars) {
309 int c1, c2;
310 s1 += utf8_tounicode_case(s1, &c1, 1);
311 s2 += utf8_tounicode_case(s2, &c2, 1);
312 if (c1 != c2) {
313 return JimSign(c1 - c2);
315 maxchars--;
317 if (!maxchars) {
318 return 0;
320 /* One string or both terminated */
321 if (*s1) {
322 return 1;
324 if (*s2) {
325 return -1;
327 return 0;
330 /* Search 's1' inside 's2', starting to search from char 'index' of 's2'.
331 * The index of the first occurrence of s1 in s2 is returned.
332 * If s1 is not found inside s2, -1 is returned. */
333 static int JimStringFirst(const char *s1, int l1, const char *s2, int l2, int idx)
335 int i;
336 int l1bytelen;
338 if (!l1 || !l2 || l1 > l2) {
339 return -1;
341 if (idx < 0)
342 idx = 0;
343 s2 += utf8_index(s2, idx);
345 l1bytelen = utf8_index(s1, l1);
347 for (i = idx; i <= l2 - l1; i++) {
348 int c;
349 if (memcmp(s2, s1, l1bytelen) == 0) {
350 return i;
352 s2 += utf8_tounicode(s2, &c);
354 return -1;
357 static int JimStringLast(const char *s1, int l1, const char *s2, int l2)
359 #ifdef JIM_UTF8
360 int i = 0;
361 /* It is too hard to search backwards with utf-8, so just keep using JimStringFirst()
362 * until we find the last instance
364 int result = -1;
365 /* Search is inclusive of l2 */
366 l2++;
367 while ((i = JimStringFirst(s1, l1, s2, l2, i)) >= 0) {
368 int c;
369 result = i;
370 i += utf8_tounicode(s2 + i, &c);
372 return result;
373 #else
374 const char *p;
376 if (!l1 || !l2 || l1 > l2)
377 return -1;
379 /* Now search for the needle */
380 for (p = s2 + l2 - 1; p != s2 - 1; p--) {
381 if (*p == *s1 && memcmp(s1, p, l1) == 0) {
382 return p - s2;
385 return -1;
386 #endif
389 int Jim_WideToString(char *buf, jim_wide wideValue)
391 const char *fmt = "%" JIM_WIDE_MODIFIER;
393 return sprintf(buf, fmt, wideValue);
397 * After an strtol()/strtod()-like conversion,
398 * check whether something was converted and that
399 * the only thing left is white space.
401 * Returns JIM_OK or JIM_ERR.
403 static int JimCheckConversion(const char *str, const char *endptr)
405 if (str[0] == '\0' || str == endptr) {
406 return JIM_ERR;
409 if (endptr[0] != '\0') {
410 while (*endptr) {
411 if (!isspace(UCHAR(*endptr))) {
412 return JIM_ERR;
414 endptr++;
417 return JIM_OK;
420 int Jim_StringToWide(const char *str, jim_wide * widePtr, int base)
422 char *endptr;
424 *widePtr = strtoull(str, &endptr, base);
426 return JimCheckConversion(str, endptr);
429 int Jim_DoubleToString(char *buf, double doubleValue)
431 int len;
432 char *buf0 = buf;
434 len = sprintf(buf, "%.12g", doubleValue);
436 /* Add a final ".0" if it's a number. But not
437 * for NaN or InF */
438 while (*buf) {
439 if (*buf == '.' || isalpha(UCHAR(*buf))) {
440 /* inf -> Inf, nan -> Nan */
441 if (*buf == 'i' || *buf == 'n') {
442 *buf = toupper(UCHAR(*buf));
444 if (*buf == 'I') {
445 /* Infinity -> Inf */
446 buf[3] = '\0';
447 len = buf - buf0 + 3;
449 return len;
451 buf++;
454 *buf++ = '.';
455 *buf++ = '0';
456 *buf = '\0';
458 return len + 2;
461 int Jim_StringToDouble(const char *str, double *doublePtr)
463 char *endptr;
465 /* Callers can check for underflow via ERANGE */
466 errno = 0;
468 *doublePtr = strtod(str, &endptr);
470 return JimCheckConversion(str, endptr);
473 static jim_wide JimPowWide(jim_wide b, jim_wide e)
475 jim_wide i, res = 1;
477 if ((b == 0 && e != 0) || (e < 0))
478 return 0;
479 for (i = 0; i < e; i++) {
480 res *= b;
482 return res;
485 /* -----------------------------------------------------------------------------
486 * Special functions
487 * ---------------------------------------------------------------------------*/
489 /* Note that 'interp' may be NULL if not available in the
490 * context of the panic. It's only useful to get the error
491 * file descriptor, it will default to stderr otherwise. */
492 void Jim_Panic(Jim_Interp *interp, const char *fmt, ...)
494 va_list ap;
496 va_start(ap, fmt);
498 * Send it here first.. Assuming STDIO still works
500 fprintf(stderr, JIM_NL "JIM INTERPRETER PANIC: ");
501 vfprintf(stderr, fmt, ap);
502 fprintf(stderr, JIM_NL JIM_NL);
503 va_end(ap);
505 #ifdef HAVE_BACKTRACE
507 void *array[40];
508 int size, i;
509 char **strings;
511 size = backtrace(array, 40);
512 strings = backtrace_symbols(array, size);
513 for (i = 0; i < size; i++)
514 fprintf(stderr, "[backtrace] %s" JIM_NL, strings[i]);
515 fprintf(stderr, "[backtrace] Include the above lines and the output" JIM_NL);
516 fprintf(stderr, "[backtrace] of 'nm <executable>' in the bug report." JIM_NL);
518 #endif
520 abort();
523 /* -----------------------------------------------------------------------------
524 * Memory allocation
525 * ---------------------------------------------------------------------------*/
527 void *Jim_Alloc(int size)
529 return malloc(size);
532 void Jim_Free(void *ptr)
534 free(ptr);
537 void *Jim_Realloc(void *ptr, int size)
539 return realloc(ptr, size);
542 char *Jim_StrDup(const char *s)
544 return strdup(s);
547 char *Jim_StrDupLen(const char *s, int l)
549 char *copy = Jim_Alloc(l + 1);
551 memcpy(copy, s, l + 1);
552 copy[l] = 0; /* Just to be sure, original could be substring */
553 return copy;
556 /* -----------------------------------------------------------------------------
557 * Time related functions
558 * ---------------------------------------------------------------------------*/
560 /* Returns microseconds of CPU used since start. */
561 static jim_wide JimClock(void)
563 struct timeval tv;
565 gettimeofday(&tv, NULL);
566 return (jim_wide) tv.tv_sec * 1000000 + tv.tv_usec;
569 /* -----------------------------------------------------------------------------
570 * Hash Tables
571 * ---------------------------------------------------------------------------*/
573 /* -------------------------- private prototypes ---------------------------- */
574 static int JimExpandHashTableIfNeeded(Jim_HashTable *ht);
575 static unsigned int JimHashTableNextPower(unsigned int size);
576 static int JimInsertHashEntry(Jim_HashTable *ht, const void *key);
578 /* -------------------------- hash functions -------------------------------- */
580 /* Thomas Wang's 32 bit Mix Function */
581 unsigned int Jim_IntHashFunction(unsigned int key)
583 key += ~(key << 15);
584 key ^= (key >> 10);
585 key += (key << 3);
586 key ^= (key >> 6);
587 key += ~(key << 11);
588 key ^= (key >> 16);
589 return key;
592 /* Generic hash function (we are using to multiply by 9 and add the byte
593 * as Tcl) */
594 unsigned int Jim_GenHashFunction(const unsigned char *buf, int len)
596 unsigned int h = 0;
598 while (len--)
599 h += (h << 3) + *buf++;
600 return h;
603 /* ----------------------------- API implementation ------------------------- */
605 /* reset a hashtable already initialized with ht_init().
606 * NOTE: This function should only called by ht_destroy(). */
607 static void JimResetHashTable(Jim_HashTable *ht)
609 ht->table = NULL;
610 ht->size = 0;
611 ht->sizemask = 0;
612 ht->used = 0;
613 ht->collisions = 0;
616 /* Initialize the hash table */
617 int Jim_InitHashTable(Jim_HashTable *ht, const Jim_HashTableType *type, void *privDataPtr)
619 JimResetHashTable(ht);
620 ht->type = type;
621 ht->privdata = privDataPtr;
622 return JIM_OK;
625 /* Resize the table to the minimal size that contains all the elements,
626 * but with the invariant of a USER/BUCKETS ration near to <= 1 */
627 int Jim_ResizeHashTable(Jim_HashTable *ht)
629 int minimal = ht->used;
631 if (minimal < JIM_HT_INITIAL_SIZE)
632 minimal = JIM_HT_INITIAL_SIZE;
633 return Jim_ExpandHashTable(ht, minimal);
636 /* Expand or create the hashtable */
637 int Jim_ExpandHashTable(Jim_HashTable *ht, unsigned int size)
639 Jim_HashTable n; /* the new hashtable */
640 unsigned int realsize = JimHashTableNextPower(size), i;
642 /* the size is invalid if it is smaller than the number of
643 * elements already inside the hashtable */
644 if (ht->used >= size)
645 return JIM_ERR;
647 Jim_InitHashTable(&n, ht->type, ht->privdata);
648 n.size = realsize;
649 n.sizemask = realsize - 1;
650 n.table = Jim_Alloc(realsize * sizeof(Jim_HashEntry *));
652 /* Initialize all the pointers to NULL */
653 memset(n.table, 0, realsize * sizeof(Jim_HashEntry *));
655 /* Copy all the elements from the old to the new table:
656 * note that if the old hash table is empty ht->size is zero,
657 * so Jim_ExpandHashTable just creates an hash table. */
658 n.used = ht->used;
659 for (i = 0; i < ht->size && ht->used > 0; i++) {
660 Jim_HashEntry *he, *nextHe;
662 if (ht->table[i] == NULL)
663 continue;
665 /* For each hash entry on this slot... */
666 he = ht->table[i];
667 while (he) {
668 unsigned int h;
670 nextHe = he->next;
671 /* Get the new element index */
672 h = Jim_HashKey(ht, he->key) & n.sizemask;
673 he->next = n.table[h];
674 n.table[h] = he;
675 ht->used--;
676 /* Pass to the next element */
677 he = nextHe;
680 assert(ht->used == 0);
681 Jim_Free(ht->table);
683 /* Remap the new hashtable in the old */
684 *ht = n;
685 return JIM_OK;
688 /* Add an element to the target hash table */
689 int Jim_AddHashEntry(Jim_HashTable *ht, const void *key, void *val)
691 int idx;
692 Jim_HashEntry *entry;
694 /* Get the index of the new element, or -1 if
695 * the element already exists. */
696 if ((idx = JimInsertHashEntry(ht, key)) == -1)
697 return JIM_ERR;
699 /* Allocates the memory and stores key */
700 entry = Jim_Alloc(sizeof(*entry));
701 entry->next = ht->table[idx];
702 ht->table[idx] = entry;
704 /* Set the hash entry fields. */
705 Jim_SetHashKey(ht, entry, key);
706 Jim_SetHashVal(ht, entry, val);
707 ht->used++;
708 return JIM_OK;
711 /* Add an element, discarding the old if the key already exists */
712 int Jim_ReplaceHashEntry(Jim_HashTable *ht, const void *key, void *val)
714 Jim_HashEntry *entry;
716 /* Try to add the element. If the key
717 * does not exists Jim_AddHashEntry will suceed. */
718 if (Jim_AddHashEntry(ht, key, val) == JIM_OK)
719 return JIM_OK;
720 /* It already exists, get the entry */
721 entry = Jim_FindHashEntry(ht, key);
722 /* Free the old value and set the new one */
723 Jim_FreeEntryVal(ht, entry);
724 Jim_SetHashVal(ht, entry, val);
725 return JIM_OK;
728 /* Search and remove an element */
729 int Jim_DeleteHashEntry(Jim_HashTable *ht, const void *key)
731 unsigned int h;
732 Jim_HashEntry *he, *prevHe;
734 if (ht->size == 0)
735 return JIM_ERR;
736 h = Jim_HashKey(ht, key) & ht->sizemask;
737 he = ht->table[h];
739 prevHe = NULL;
740 while (he) {
741 if (Jim_CompareHashKeys(ht, key, he->key)) {
742 /* Unlink the element from the list */
743 if (prevHe)
744 prevHe->next = he->next;
745 else
746 ht->table[h] = he->next;
747 Jim_FreeEntryKey(ht, he);
748 Jim_FreeEntryVal(ht, he);
749 Jim_Free(he);
750 ht->used--;
751 return JIM_OK;
753 prevHe = he;
754 he = he->next;
756 return JIM_ERR; /* not found */
759 /* Destroy an entire hash table */
760 int Jim_FreeHashTable(Jim_HashTable *ht)
762 unsigned int i;
764 /* Free all the elements */
765 for (i = 0; i < ht->size && ht->used > 0; i++) {
766 Jim_HashEntry *he, *nextHe;
768 if ((he = ht->table[i]) == NULL)
769 continue;
770 while (he) {
771 nextHe = he->next;
772 Jim_FreeEntryKey(ht, he);
773 Jim_FreeEntryVal(ht, he);
774 Jim_Free(he);
775 ht->used--;
776 he = nextHe;
779 /* Free the table and the allocated cache structure */
780 Jim_Free(ht->table);
781 /* Re-initialize the table */
782 JimResetHashTable(ht);
783 return JIM_OK; /* never fails */
786 Jim_HashEntry *Jim_FindHashEntry(Jim_HashTable *ht, const void *key)
788 Jim_HashEntry *he;
789 unsigned int h;
791 if (ht->size == 0)
792 return NULL;
793 h = Jim_HashKey(ht, key) & ht->sizemask;
794 he = ht->table[h];
795 while (he) {
796 if (Jim_CompareHashKeys(ht, key, he->key))
797 return he;
798 he = he->next;
800 return NULL;
803 Jim_HashTableIterator *Jim_GetHashTableIterator(Jim_HashTable *ht)
805 Jim_HashTableIterator *iter = Jim_Alloc(sizeof(*iter));
807 iter->ht = ht;
808 iter->index = -1;
809 iter->entry = NULL;
810 iter->nextEntry = NULL;
811 return iter;
814 Jim_HashEntry *Jim_NextHashEntry(Jim_HashTableIterator *iter)
816 while (1) {
817 if (iter->entry == NULL) {
818 iter->index++;
819 if (iter->index >= (signed)iter->ht->size)
820 break;
821 iter->entry = iter->ht->table[iter->index];
823 else {
824 iter->entry = iter->nextEntry;
826 if (iter->entry) {
827 /* We need to save the 'next' here, the iterator user
828 * may delete the entry we are returning. */
829 iter->nextEntry = iter->entry->next;
830 return iter->entry;
833 return NULL;
836 /* ------------------------- private functions ------------------------------ */
838 /* Expand the hash table if needed */
839 static int JimExpandHashTableIfNeeded(Jim_HashTable *ht)
841 /* If the hash table is empty expand it to the intial size,
842 * if the table is "full" dobule its size. */
843 if (ht->size == 0)
844 return Jim_ExpandHashTable(ht, JIM_HT_INITIAL_SIZE);
845 if (ht->size == ht->used)
846 return Jim_ExpandHashTable(ht, ht->size * 2);
847 return JIM_OK;
850 /* Our hash table capability is a power of two */
851 static unsigned int JimHashTableNextPower(unsigned int size)
853 unsigned int i = JIM_HT_INITIAL_SIZE;
855 if (size >= 2147483648U)
856 return 2147483648U;
857 while (1) {
858 if (i >= size)
859 return i;
860 i *= 2;
864 /* Returns the index of a free slot that can be populated with
865 * an hash entry for the given 'key'.
866 * If the key already exists, -1 is returned. */
867 static int JimInsertHashEntry(Jim_HashTable *ht, const void *key)
869 unsigned int h;
870 Jim_HashEntry *he;
872 /* Expand the hashtable if needed */
873 if (JimExpandHashTableIfNeeded(ht) == JIM_ERR)
874 return -1;
875 /* Compute the key hash value */
876 h = Jim_HashKey(ht, key) & ht->sizemask;
877 /* Search if this slot does not already contain the given key */
878 he = ht->table[h];
879 while (he) {
880 if (Jim_CompareHashKeys(ht, key, he->key))
881 return -1;
882 he = he->next;
884 return h;
887 /* ----------------------- StringCopy Hash Table Type ------------------------*/
889 static unsigned int JimStringCopyHTHashFunction(const void *key)
891 return Jim_GenHashFunction(key, strlen(key));
894 static const void *JimStringCopyHTKeyDup(void *privdata, const void *key)
896 int len = strlen(key);
897 char *copy = Jim_Alloc(len + 1);
899 JIM_NOTUSED(privdata);
901 memcpy(copy, key, len);
902 copy[len] = '\0';
903 return copy;
906 static void *JimStringKeyValCopyHTValDup(void *privdata, const void *val)
908 int len = strlen(val);
909 char *copy = Jim_Alloc(len + 1);
911 JIM_NOTUSED(privdata);
913 memcpy(copy, val, len);
914 copy[len] = '\0';
915 return copy;
918 static int JimStringCopyHTKeyCompare(void *privdata, const void *key1, const void *key2)
920 JIM_NOTUSED(privdata);
922 return strcmp(key1, key2) == 0;
925 static void JimStringCopyHTKeyDestructor(void *privdata, const void *key)
927 JIM_NOTUSED(privdata);
929 Jim_Free((void *)key); /* ATTENTION: const cast */
932 static void JimStringKeyValCopyHTValDestructor(void *privdata, void *val)
934 JIM_NOTUSED(privdata);
936 Jim_Free((void *)val); /* ATTENTION: const cast */
939 #if 0
940 static Jim_HashTableType JimStringCopyHashTableType = {
941 JimStringCopyHTHashFunction, /* hash function */
942 JimStringCopyHTKeyDup, /* key dup */
943 NULL, /* val dup */
944 JimStringCopyHTKeyCompare, /* key compare */
945 JimStringCopyHTKeyDestructor, /* key destructor */
946 NULL /* val destructor */
948 #endif
950 /* This is like StringCopy but does not auto-duplicate the key.
951 * It's used for intepreter's shared strings. */
952 static const Jim_HashTableType JimSharedStringsHashTableType = {
953 JimStringCopyHTHashFunction, /* hash function */
954 NULL, /* key dup */
955 NULL, /* val dup */
956 JimStringCopyHTKeyCompare, /* key compare */
957 JimStringCopyHTKeyDestructor, /* key destructor */
958 NULL /* val destructor */
961 /* This is like StringCopy but also automatically handle dynamic
962 * allocated C strings as values. */
963 static const Jim_HashTableType JimStringKeyValCopyHashTableType = {
964 JimStringCopyHTHashFunction, /* hash function */
965 JimStringCopyHTKeyDup, /* key dup */
966 JimStringKeyValCopyHTValDup, /* val dup */
967 JimStringCopyHTKeyCompare, /* key compare */
968 JimStringCopyHTKeyDestructor, /* key destructor */
969 JimStringKeyValCopyHTValDestructor, /* val destructor */
972 typedef struct AssocDataValue
974 Jim_InterpDeleteProc *delProc;
975 void *data;
976 } AssocDataValue;
978 static void JimAssocDataHashTableValueDestructor(void *privdata, void *data)
980 AssocDataValue *assocPtr = (AssocDataValue *) data;
982 if (assocPtr->delProc != NULL)
983 assocPtr->delProc((Jim_Interp *)privdata, assocPtr->data);
984 Jim_Free(data);
987 static const Jim_HashTableType JimAssocDataHashTableType = {
988 JimStringCopyHTHashFunction, /* hash function */
989 JimStringCopyHTKeyDup, /* key dup */
990 NULL, /* val dup */
991 JimStringCopyHTKeyCompare, /* key compare */
992 JimStringCopyHTKeyDestructor, /* key destructor */
993 JimAssocDataHashTableValueDestructor /* val destructor */
996 /* -----------------------------------------------------------------------------
997 * Stack - This is a simple generic stack implementation. It is used for
998 * example in the 'expr' expression compiler.
999 * ---------------------------------------------------------------------------*/
1000 void Jim_InitStack(Jim_Stack *stack)
1002 stack->len = 0;
1003 stack->maxlen = 0;
1004 stack->vector = NULL;
1007 void Jim_FreeStack(Jim_Stack *stack)
1009 Jim_Free(stack->vector);
1012 int Jim_StackLen(Jim_Stack *stack)
1014 return stack->len;
1017 void Jim_StackPush(Jim_Stack *stack, void *element)
1019 int neededLen = stack->len + 1;
1021 if (neededLen > stack->maxlen) {
1022 stack->maxlen = neededLen < 20 ? 20 : neededLen * 2;
1023 stack->vector = Jim_Realloc(stack->vector, sizeof(void *) * stack->maxlen);
1025 stack->vector[stack->len] = element;
1026 stack->len++;
1029 void *Jim_StackPop(Jim_Stack *stack)
1031 if (stack->len == 0)
1032 return NULL;
1033 stack->len--;
1034 return stack->vector[stack->len];
1037 void *Jim_StackPeek(Jim_Stack *stack)
1039 if (stack->len == 0)
1040 return NULL;
1041 return stack->vector[stack->len - 1];
1044 void Jim_FreeStackElements(Jim_Stack *stack, void (*freeFunc) (void *ptr))
1046 int i;
1048 for (i = 0; i < stack->len; i++)
1049 freeFunc(stack->vector[i]);
1052 /* -----------------------------------------------------------------------------
1053 * Parser
1054 * ---------------------------------------------------------------------------*/
1056 /* Token types */
1057 #define JIM_TT_NONE 0 /* No token returned */
1058 #define JIM_TT_STR 1 /* simple string */
1059 #define JIM_TT_ESC 2 /* string that needs escape chars conversion */
1060 #define JIM_TT_VAR 3 /* var substitution */
1061 #define JIM_TT_DICTSUGAR 4 /* Syntax sugar for [dict get], $foo(bar) */
1062 #define JIM_TT_CMD 5 /* command substitution */
1063 /* Note: Keep these three together for TOKEN_IS_SEP() */
1064 #define JIM_TT_SEP 6 /* word separator. arg is # of tokens. -ve if {*} */
1065 #define JIM_TT_EOL 7 /* line separator */
1066 #define JIM_TT_EOF 8 /* end of script */
1068 #define JIM_TT_LINE 9 /* special 'start-of-line' token. arg is # of arguments to the command. -ve if {*} */
1069 #define JIM_TT_WORD 10 /* special 'start-of-word' token. arg is # of tokens to combine. -ve if {*} */
1071 /* Additional token types needed for expressions */
1072 #define JIM_TT_SUBEXPR_START 11
1073 #define JIM_TT_SUBEXPR_END 12
1074 #define JIM_TT_EXPR_INT 13
1075 #define JIM_TT_EXPR_DOUBLE 14
1077 /* Operator token types start here */
1078 #define JIM_TT_EXPR_OP 15
1080 #define TOKEN_IS_SEP(type) (type >= JIM_TT_SEP && type <= JIM_TT_EOF)
1082 /* Parser states */
1083 #define JIM_PS_DEF 0 /* Default state */
1084 #define JIM_PS_QUOTE 1 /* Inside "" */
1085 #define JIM_PS_DICTSUGAR 2 /* Tokenising abc(def) into 4 separate tokens */
1087 /* Parser context structure. The same context is used both to parse
1088 * Tcl scripts and lists. */
1089 struct JimParserCtx
1091 const char *prg; /* Program text */
1092 const char *p; /* Pointer to the point of the program we are parsing */
1093 int len; /* Left length of 'prg' */
1094 int linenr; /* Current line number */
1095 const char *tstart;
1096 const char *tend; /* Returned token is at tstart-tend in 'prg'. */
1097 int tline; /* Line number of the returned token */
1098 int tt; /* Token type */
1099 int eof; /* Non zero if EOF condition is true. */
1100 int state; /* Parser state */
1101 int comment; /* Non zero if the next chars may be a comment. */
1102 char missing; /* At end of parse, ' ' if complete, '{' if braces incomplete, '"' if quotes incomplete */
1105 #define JimParserEof(c) ((c)->eof)
1106 #define JimParserTstart(c) ((c)->tstart)
1107 #define JimParserTend(c) ((c)->tend)
1108 #define JimParserTtype(c) ((c)->tt)
1109 #define JimParserTline(c) ((c)->tline)
1111 static int JimParseScript(struct JimParserCtx *pc);
1112 static int JimParseSep(struct JimParserCtx *pc);
1113 static int JimParseEol(struct JimParserCtx *pc);
1114 static int JimParseCmd(struct JimParserCtx *pc);
1115 static int JimParseVar(struct JimParserCtx *pc);
1116 static int JimParseBrace(struct JimParserCtx *pc);
1117 static int JimParseStr(struct JimParserCtx *pc);
1118 static int JimParseComment(struct JimParserCtx *pc);
1119 static Jim_Obj *JimParserGetTokenObj(Jim_Interp *interp, struct JimParserCtx *pc);
1121 /* Initialize a parser context.
1122 * 'prg' is a pointer to the program text, linenr is the line
1123 * number of the first line contained in the program. */
1124 static void JimParserInit(struct JimParserCtx *pc, const char *prg, int len, int linenr)
1126 pc->prg = prg;
1127 pc->p = prg;
1128 pc->len = len;
1129 pc->tstart = NULL;
1130 pc->tend = NULL;
1131 pc->tline = 0;
1132 pc->tt = JIM_TT_NONE;
1133 pc->eof = 0;
1134 pc->state = JIM_PS_DEF;
1135 pc->linenr = linenr;
1136 pc->comment = 1;
1137 pc->missing = ' ';
1140 static int JimParseScript(struct JimParserCtx *pc)
1142 while (1) { /* the while is used to reiterate with continue if needed */
1143 if (!pc->len) {
1144 pc->tstart = pc->p;
1145 pc->tend = pc->p - 1;
1146 pc->tline = pc->linenr;
1147 pc->tt = JIM_TT_EOL;
1148 pc->eof = 1;
1149 return JIM_OK;
1151 switch (*(pc->p)) {
1152 case '\\':
1153 if (*(pc->p + 1) == '\n')
1154 return JimParseSep(pc);
1155 else {
1156 pc->comment = 0;
1157 return JimParseStr(pc);
1159 break;
1160 case ' ':
1161 case '\t':
1162 case '\r':
1163 if (pc->state == JIM_PS_DEF)
1164 return JimParseSep(pc);
1165 else {
1166 pc->comment = 0;
1167 return JimParseStr(pc);
1169 break;
1170 case '\n':
1171 case ';':
1172 pc->comment = 1;
1173 if (pc->state == JIM_PS_DEF)
1174 return JimParseEol(pc);
1175 else
1176 return JimParseStr(pc);
1177 break;
1178 case '[':
1179 pc->comment = 0;
1180 return JimParseCmd(pc);
1181 break;
1182 case '$':
1183 pc->comment = 0;
1184 if (JimParseVar(pc) == JIM_ERR) {
1185 pc->tstart = pc->tend = pc->p++;
1186 pc->len--;
1187 pc->tline = pc->linenr;
1188 pc->tt = JIM_TT_STR;
1189 return JIM_OK;
1191 else
1192 return JIM_OK;
1193 break;
1194 case '#':
1195 if (pc->comment) {
1196 JimParseComment(pc);
1197 continue;
1199 else {
1200 return JimParseStr(pc);
1202 default:
1203 pc->comment = 0;
1204 return JimParseStr(pc);
1205 break;
1207 return JIM_OK;
1211 static int JimParseSep(struct JimParserCtx *pc)
1213 pc->tstart = pc->p;
1214 pc->tline = pc->linenr;
1215 while (*pc->p == ' ' || *pc->p == '\t' || *pc->p == '\r' ||
1216 (*pc->p == '\\' && *(pc->p + 1) == '\n')) {
1217 if (*pc->p == '\\') {
1218 pc->p++;
1219 pc->len--;
1220 pc->linenr++;
1222 pc->p++;
1223 pc->len--;
1225 pc->tend = pc->p - 1;
1226 pc->tt = JIM_TT_SEP;
1227 return JIM_OK;
1230 static int JimParseEol(struct JimParserCtx *pc)
1232 pc->tstart = pc->p;
1233 pc->tline = pc->linenr;
1234 while (*pc->p == ' ' || *pc->p == '\n' || *pc->p == '\t' || *pc->p == '\r' || *pc->p == ';') {
1235 if (*pc->p == '\n')
1236 pc->linenr++;
1237 pc->p++;
1238 pc->len--;
1240 pc->tend = pc->p - 1;
1241 pc->tt = JIM_TT_EOL;
1242 return JIM_OK;
1245 /* Todo. Don't stop if ']' appears inside {} or quoted.
1246 * Also should handle the case of puts [string length "]"] */
1247 static int JimParseCmd(struct JimParserCtx *pc)
1249 int level = 1;
1250 int blevel = 0;
1252 pc->tstart = ++pc->p;
1253 pc->len--;
1254 pc->tline = pc->linenr;
1255 while (pc->len) {
1256 if (*pc->p == '[' && blevel == 0) {
1257 level++;
1259 else if (*pc->p == ']' && blevel == 0) {
1260 level--;
1261 if (!level)
1262 break;
1264 else if (*pc->p == '\\' && pc->len > 1) {
1265 pc->p++;
1266 pc->len--;
1267 if (*pc->p == '\n')
1268 pc->linenr++;
1270 else if (*pc->p == '{') {
1271 blevel++;
1273 else if (*pc->p == '}') {
1274 if (blevel != 0)
1275 blevel--;
1277 else if (*pc->p == '\n')
1278 pc->linenr++;
1279 pc->p++;
1280 pc->len--;
1282 pc->tend = pc->p - 1;
1283 pc->tt = JIM_TT_CMD;
1284 if (*pc->p == ']') {
1285 pc->p++;
1286 pc->len--;
1288 return JIM_OK;
1291 static int JimParseVar(struct JimParserCtx *pc)
1293 int brace = 0, stop = 0;
1294 int ttype = JIM_TT_VAR;
1296 pc->tstart = ++pc->p;
1297 pc->len--; /* skip the $ */
1298 pc->tline = pc->linenr;
1299 if (*pc->p == '{') {
1300 pc->tstart = ++pc->p;
1301 pc->len--;
1302 brace = 1;
1304 if (brace) {
1305 while (!stop) {
1306 if (*pc->p == '}' || pc->len == 0) {
1307 pc->tend = pc->p - 1;
1308 stop = 1;
1309 if (pc->len == 0)
1310 break;
1312 else if (*pc->p == '\n')
1313 pc->linenr++;
1314 pc->p++;
1315 pc->len--;
1318 else {
1319 while (!stop) {
1320 /* Skip double colon, but not single colon! */
1321 if (pc->p[0] == ':' && pc->len > 1 && pc->p[1] == ':') {
1322 pc->p += 2;
1323 pc->len -= 2;
1324 continue;
1326 if (!((*pc->p >= 'a' && *pc->p <= 'z') ||
1327 (*pc->p >= 'A' && *pc->p <= 'Z') ||
1328 (*pc->p >= '0' && *pc->p <= '9') || *pc->p == '_'))
1329 stop = 1;
1330 else {
1331 pc->p++;
1332 pc->len--;
1335 /* Parse [dict get] syntax sugar. */
1336 if (*pc->p == '(') {
1337 int count = 1;
1338 const char *paren = pc->p;
1340 while (count && pc->len) {
1341 pc->p++;
1342 pc->len--;
1343 if (*pc->p == '\\' && pc->len >= 2) {
1344 pc->p += 2;
1345 pc->len -= 2;
1347 else if (*pc->p == '(') {
1348 count++;
1350 else if (*pc->p == ')') {
1351 count--;
1354 if (count == 0) {
1355 if (*pc->p != '\0') {
1356 pc->p++;
1357 pc->len--;
1359 ttype = JIM_TT_DICTSUGAR;
1361 else {
1362 /* Missing '(', so back up */
1363 pc->len += pc->p - paren;
1364 pc->p = paren;
1367 pc->tend = pc->p - 1;
1369 /* Check if we parsed just the '$' character.
1370 * That's not a variable so an error is returned
1371 * to tell the state machine to consider this '$' just
1372 * a string. */
1373 if (pc->tstart == pc->p) {
1374 pc->p--;
1375 pc->len++;
1376 return JIM_ERR;
1378 pc->tt = ttype;
1379 return JIM_OK;
1382 static int JimParseBrace(struct JimParserCtx *pc)
1384 int level = 1;
1386 pc->tstart = ++pc->p;
1387 pc->len--;
1388 pc->tline = pc->linenr;
1389 while (1) {
1390 if (*pc->p == '\\' && pc->len >= 2) {
1391 pc->p++;
1392 pc->len--;
1393 if (*pc->p == '\n')
1394 pc->linenr++;
1396 else if (*pc->p == '{') {
1397 level++;
1399 else if (pc->len == 0 || *pc->p == '}') {
1400 if (pc->len == 0) {
1401 pc->missing = '{';
1403 level--;
1404 if (pc->len == 0 || level == 0) {
1405 pc->tend = pc->p - 1;
1406 if (pc->len != 0) {
1407 pc->p++;
1408 pc->len--;
1410 pc->tt = JIM_TT_STR;
1411 return JIM_OK;
1414 else if (*pc->p == '\n') {
1415 pc->linenr++;
1417 pc->p++;
1418 pc->len--;
1420 return JIM_OK; /* unreached */
1423 static int JimParseStr(struct JimParserCtx *pc)
1425 int newword = (pc->tt == JIM_TT_SEP || pc->tt == JIM_TT_EOL ||
1426 pc->tt == JIM_TT_NONE || pc->tt == JIM_TT_STR);
1427 if (newword && *pc->p == '{') {
1428 return JimParseBrace(pc);
1430 else if (newword && *pc->p == '"') {
1431 pc->state = JIM_PS_QUOTE;
1432 pc->p++;
1433 pc->len--;
1435 pc->tstart = pc->p;
1436 pc->tline = pc->linenr;
1437 while (1) {
1438 if (pc->len == 0) {
1439 if (pc->state == JIM_PS_QUOTE) {
1440 pc->missing = '"';
1442 pc->tend = pc->p - 1;
1443 pc->tt = JIM_TT_ESC;
1444 return JIM_OK;
1446 switch (*pc->p) {
1447 case '\\':
1448 if (pc->state == JIM_PS_DEF && *(pc->p + 1) == '\n') {
1449 pc->tend = pc->p - 1;
1450 pc->tt = JIM_TT_ESC;
1451 return JIM_OK;
1453 if (pc->len >= 2) {
1454 if (*(pc->p + 1) == '\n') {
1455 pc->linenr++;
1457 pc->p++;
1458 pc->len--;
1460 break;
1461 case '(':
1462 /* If the following token is not '$' just keep going */
1463 if (pc->len > 1 && pc->p[1] != '$') {
1464 break;
1466 case ')':
1467 /* Only need a separate ')' token if the previous was a var */
1468 if (*pc->p == '(' || pc->tt == JIM_TT_VAR) {
1469 if (pc->p == pc->tstart) {
1470 /* At the start of the token, so just return this char */
1471 pc->p++;
1472 pc->len--;
1474 pc->tend = pc->p - 1;
1475 pc->tt = JIM_TT_ESC;
1476 return JIM_OK;
1478 break;
1480 case '$':
1481 case '[':
1482 pc->tend = pc->p - 1;
1483 pc->tt = JIM_TT_ESC;
1484 return JIM_OK;
1485 case ' ':
1486 case '\t':
1487 case '\n':
1488 case '\r':
1489 case ';':
1490 if (pc->state == JIM_PS_DEF) {
1491 pc->tend = pc->p - 1;
1492 pc->tt = JIM_TT_ESC;
1493 return JIM_OK;
1495 else if (*pc->p == '\n') {
1496 pc->linenr++;
1498 break;
1499 case '"':
1500 if (pc->state == JIM_PS_QUOTE) {
1501 pc->tend = pc->p - 1;
1502 pc->tt = JIM_TT_ESC;
1503 pc->p++;
1504 pc->len--;
1505 pc->state = JIM_PS_DEF;
1506 return JIM_OK;
1508 break;
1510 pc->p++;
1511 pc->len--;
1513 return JIM_OK; /* unreached */
1516 int JimParseComment(struct JimParserCtx *pc)
1518 while (*pc->p) {
1519 if (*pc->p == '\n') {
1520 pc->linenr++;
1521 if (*(pc->p - 1) != '\\') {
1522 pc->p++;
1523 pc->len--;
1524 return JIM_OK;
1527 pc->p++;
1528 pc->len--;
1530 return JIM_OK;
1533 /* xdigitval and odigitval are helper functions for JimEscape() */
1534 static int xdigitval(int c)
1536 if (c >= '0' && c <= '9')
1537 return c - '0';
1538 if (c >= 'a' && c <= 'f')
1539 return c - 'a' + 10;
1540 if (c >= 'A' && c <= 'F')
1541 return c - 'A' + 10;
1542 return -1;
1545 static int odigitval(int c)
1547 if (c >= '0' && c <= '7')
1548 return c - '0';
1549 return -1;
1552 /* Perform Tcl escape substitution of 's', storing the result
1553 * string into 'dest'. The escaped string is guaranteed to
1554 * be the same length or shorted than the source string.
1555 * Slen is the length of the string at 's', if it's -1 the string
1556 * length will be calculated by the function.
1558 * The function returns the length of the resulting string. */
1559 static int JimEscape(char *dest, const char *s, int slen)
1561 char *p = dest;
1562 int i, len;
1564 if (slen == -1)
1565 slen = strlen(s);
1567 for (i = 0; i < slen; i++) {
1568 switch (s[i]) {
1569 case '\\':
1570 switch (s[i + 1]) {
1571 case 'a':
1572 *p++ = 0x7;
1573 i++;
1574 break;
1575 case 'b':
1576 *p++ = 0x8;
1577 i++;
1578 break;
1579 case 'f':
1580 *p++ = 0xc;
1581 i++;
1582 break;
1583 case 'n':
1584 *p++ = 0xa;
1585 i++;
1586 break;
1587 case 'r':
1588 *p++ = 0xd;
1589 i++;
1590 break;
1591 case 't':
1592 *p++ = 0x9;
1593 i++;
1594 break;
1595 case 'u':
1596 /* A unicode sequence. Expect 1-4 hex chars and convert to utf-8.
1597 * An invalid sequence means simple an escaped 'u'
1600 int val = 0;
1601 int k;
1603 i++;
1605 for (k = 0; k < 4; k++) {
1606 int c = xdigitval(s[i + k + 1]);
1607 if (c == -1) {
1608 break;
1610 val = (val << 4) | c;
1612 if (k) {
1613 /* Got a valid unicode sequence, so convert to utf-8 */
1614 i += k;
1615 p += utf8_fromunicode(p, val);
1616 break;
1618 /* Not a valid codepoint, just an escaped u */
1619 *p++ = 'u';
1621 break;
1622 case 'v':
1623 *p++ = 0xb;
1624 i++;
1625 break;
1626 case '\0':
1627 *p++ = '\\';
1628 i++;
1629 break;
1630 case '\n':
1631 *p++ = ' ';
1632 i++;
1633 break;
1634 default:
1635 if (s[i + 1] == 'x') {
1636 int val = 0;
1637 int c = xdigitval(s[i + 2]);
1639 if (c == -1) {
1640 *p++ = 'x';
1641 i++;
1642 break;
1644 val = c;
1645 c = xdigitval(s[i + 3]);
1646 if (c == -1) {
1647 *p++ = val;
1648 i += 2;
1649 break;
1651 val = (val * 16) + c;
1652 *p++ = val;
1653 i += 3;
1654 break;
1656 else if (s[i + 1] >= '0' && s[i + 1] <= '7') {
1657 int val = 0;
1658 int c = odigitval(s[i + 1]);
1660 val = c;
1661 c = odigitval(s[i + 2]);
1662 if (c == -1) {
1663 *p++ = val;
1664 i++;
1665 break;
1667 val = (val * 8) + c;
1668 c = odigitval(s[i + 3]);
1669 if (c == -1) {
1670 *p++ = val;
1671 i += 2;
1672 break;
1674 val = (val * 8) + c;
1675 *p++ = val;
1676 i += 3;
1678 else {
1679 *p++ = s[i + 1];
1680 i++;
1682 break;
1684 break;
1685 default:
1686 *p++ = s[i];
1687 break;
1690 len = p - dest;
1691 *p = '\0';
1692 return len;
1695 /* Returns a dynamically allocated copy of the current token in the
1696 * parser context. The function performs conversion of escapes if
1697 * the token is of type JIM_TT_ESC.
1699 * Note that after the conversion, tokens that are grouped with
1700 * braces in the source code, are always recognizable from the
1701 * identical string obtained in a different way from the type.
1703 * For example the string:
1705 * {*}$a
1707 * will return as first token "*", of type JIM_TT_STR
1709 * While the string:
1711 * *$a
1713 * will return as first token "*", of type JIM_TT_ESC
1715 static Jim_Obj *JimParserGetTokenObj(Jim_Interp *interp, struct JimParserCtx *pc)
1717 const char *start, *end;
1718 char *token;
1719 int len;
1721 start = JimParserTstart(pc);
1722 end = JimParserTend(pc);
1723 if (start > end) {
1724 len = 0;
1725 token = Jim_Alloc(1);
1726 token[0] = '\0';
1728 else {
1729 len = (end - start) + 1;
1730 token = Jim_Alloc(len + 1);
1731 if (JimParserTtype(pc) != JIM_TT_ESC) {
1732 /* No escape conversion needed? Just copy it. */
1733 memcpy(token, start, len);
1734 token[len] = '\0';
1736 else {
1737 /* Else convert the escape chars. */
1738 len = JimEscape(token, start, len);
1742 return Jim_NewStringObjNoAlloc(interp, token, len);
1745 /* Parses the given string to determine if it represents a complete script.
1747 * This is useful for interactive shells implementation, for [info complete]
1748 * and is used by source/Jim_EvalFile().
1750 * If 'stateCharPtr' != NULL, the function stores ' ' on complete script,
1751 * '{' on scripts incomplete missing one or more '}' to be balanced.
1752 * '"' on scripts incomplete missing a '"' char.
1754 * If the script is complete, 1 is returned, otherwise 0.
1756 int Jim_ScriptIsComplete(const char *s, int len, char *stateCharPtr)
1758 struct JimParserCtx parser;
1760 JimParserInit(&parser, s, len, 1);
1761 while (!JimParserEof(&parser)) {
1762 JimParseScript(&parser);
1764 if (stateCharPtr) {
1765 *stateCharPtr = parser.missing;
1767 return parser.missing == ' ';
1770 /* -----------------------------------------------------------------------------
1771 * Tcl Lists parsing
1772 * ---------------------------------------------------------------------------*/
1773 static int JimParseListSep(struct JimParserCtx *pc);
1774 static int JimParseListStr(struct JimParserCtx *pc);
1776 static int JimParseList(struct JimParserCtx *pc)
1778 if (pc->len == 0) {
1779 pc->tstart = pc->tend = pc->p;
1780 pc->tline = pc->linenr;
1781 pc->tt = JIM_TT_EOL;
1782 pc->eof = 1;
1783 return JIM_OK;
1785 switch (*pc->p) {
1786 case ' ':
1787 case '\n':
1788 case '\t':
1789 case '\r':
1790 if (pc->state == JIM_PS_DEF)
1791 return JimParseListSep(pc);
1792 else
1793 return JimParseListStr(pc);
1794 break;
1795 default:
1796 return JimParseListStr(pc);
1797 break;
1799 return JIM_OK;
1802 int JimParseListSep(struct JimParserCtx *pc)
1804 pc->tstart = pc->p;
1805 pc->tline = pc->linenr;
1806 while (*pc->p == ' ' || *pc->p == '\t' || *pc->p == '\r' || *pc->p == '\n') {
1807 if (*pc->p == '\n') {
1808 pc->linenr++;
1810 pc->p++;
1811 pc->len--;
1813 pc->tend = pc->p - 1;
1814 pc->tt = JIM_TT_SEP;
1815 return JIM_OK;
1818 int JimParseListStr(struct JimParserCtx *pc)
1820 int newword = (pc->tt == JIM_TT_SEP || pc->tt == JIM_TT_EOL || pc->tt == JIM_TT_NONE);
1822 if (newword && *pc->p == '{') {
1823 return JimParseBrace(pc);
1825 else if (newword && *pc->p == '"') {
1826 pc->state = JIM_PS_QUOTE;
1827 pc->p++;
1828 pc->len--;
1830 pc->tstart = pc->p;
1831 pc->tline = pc->linenr;
1832 while (1) {
1833 if (pc->len == 0) {
1834 pc->tend = pc->p - 1;
1835 pc->tt = JIM_TT_ESC;
1836 return JIM_OK;
1838 switch (*pc->p) {
1839 case '\\':
1840 if (--pc->len == 0) {
1841 /* Trailing newline */
1842 pc->tt = JIM_TT_ESC;
1843 pc->tend = pc->p;
1844 return JIM_OK;
1846 pc->p++;
1847 break;
1848 case ' ':
1849 case '\t':
1850 case '\n':
1851 case '\r':
1852 if (pc->state == JIM_PS_DEF) {
1853 pc->tend = pc->p - 1;
1854 pc->tt = JIM_TT_ESC;
1855 return JIM_OK;
1857 else if (*pc->p == '\n') {
1858 pc->linenr++;
1860 break;
1861 case '"':
1862 if (pc->state == JIM_PS_QUOTE) {
1863 pc->tend = pc->p - 1;
1864 pc->tt = JIM_TT_ESC;
1865 pc->p++;
1866 pc->len--;
1867 pc->state = JIM_PS_DEF;
1868 return JIM_OK;
1870 break;
1872 pc->p++;
1873 pc->len--;
1875 return JIM_OK; /* unreached */
1878 /* -----------------------------------------------------------------------------
1879 * Jim_Obj related functions
1880 * ---------------------------------------------------------------------------*/
1882 /* Return a new initialized object. */
1883 Jim_Obj *Jim_NewObj(Jim_Interp *interp)
1885 Jim_Obj *objPtr;
1887 /* -- Check if there are objects in the free list -- */
1888 if (interp->freeList != NULL) {
1889 /* -- Unlink the object from the free list -- */
1890 objPtr = interp->freeList;
1891 interp->freeList = objPtr->nextObjPtr;
1893 else {
1894 /* -- No ready to use objects: allocate a new one -- */
1895 objPtr = Jim_Alloc(sizeof(*objPtr));
1898 /* Object is returned with refCount of 0. Every
1899 * kind of GC implemented should take care to don't try
1900 * to scan objects with refCount == 0. */
1901 objPtr->refCount = 0;
1902 /* All the other fields are left not initialized to save time.
1903 * The caller will probably want to set them to the right
1904 * value anyway. */
1906 /* -- Put the object into the live list -- */
1907 objPtr->prevObjPtr = NULL;
1908 objPtr->nextObjPtr = interp->liveList;
1909 if (interp->liveList)
1910 interp->liveList->prevObjPtr = objPtr;
1911 interp->liveList = objPtr;
1913 return objPtr;
1916 /* Free an object. Actually objects are never freed, but
1917 * just moved to the free objects list, where they will be
1918 * reused by Jim_NewObj(). */
1919 void Jim_FreeObj(Jim_Interp *interp, Jim_Obj *objPtr)
1921 /* Check if the object was already freed, panic. */
1922 if (objPtr->refCount != 0) {
1923 Jim_Panic(interp, "!!!Object %p freed with bad refcount %d, type=%s", objPtr,
1924 objPtr->refCount, objPtr->typePtr ? objPtr->typePtr->name : "<none>");
1927 /* Free the internal representation */
1928 Jim_FreeIntRep(interp, objPtr);
1929 /* Free the string representation */
1930 if (objPtr->bytes != NULL) {
1931 if (objPtr->bytes != JimEmptyStringRep)
1932 Jim_Free(objPtr->bytes);
1934 /* Unlink the object from the live objects list */
1935 if (objPtr->prevObjPtr)
1936 objPtr->prevObjPtr->nextObjPtr = objPtr->nextObjPtr;
1937 if (objPtr->nextObjPtr)
1938 objPtr->nextObjPtr->prevObjPtr = objPtr->prevObjPtr;
1939 if (interp->liveList == objPtr)
1940 interp->liveList = objPtr->nextObjPtr;
1941 /* Link the object into the free objects list */
1942 objPtr->prevObjPtr = NULL;
1943 objPtr->nextObjPtr = interp->freeList;
1944 if (interp->freeList)
1945 interp->freeList->prevObjPtr = objPtr;
1946 interp->freeList = objPtr;
1947 objPtr->refCount = -1;
1950 /* Invalidate the string representation of an object. */
1951 void Jim_InvalidateStringRep(Jim_Obj *objPtr)
1953 if (objPtr->bytes != NULL) {
1954 if (objPtr->bytes != JimEmptyStringRep)
1955 Jim_Free(objPtr->bytes);
1957 objPtr->bytes = NULL;
1960 #define Jim_SetStringRep(o, b, l) \
1961 do { (o)->bytes = b; (o)->length = l; } while (0)
1963 /* Set the initial string representation for an object.
1964 * Does not try to free an old one. */
1965 void Jim_InitStringRep(Jim_Obj *objPtr, const char *bytes, int length)
1967 if (length == 0) {
1968 objPtr->bytes = JimEmptyStringRep;
1969 objPtr->length = 0;
1971 else {
1972 objPtr->bytes = Jim_Alloc(length + 1);
1973 objPtr->length = length;
1974 memcpy(objPtr->bytes, bytes, length);
1975 objPtr->bytes[length] = '\0';
1979 /* Duplicate an object. The returned object has refcount = 0. */
1980 Jim_Obj *Jim_DuplicateObj(Jim_Interp *interp, Jim_Obj *objPtr)
1982 Jim_Obj *dupPtr;
1984 dupPtr = Jim_NewObj(interp);
1985 if (objPtr->bytes == NULL) {
1986 /* Object does not have a valid string representation. */
1987 dupPtr->bytes = NULL;
1989 else {
1990 Jim_InitStringRep(dupPtr, objPtr->bytes, objPtr->length);
1992 if (objPtr->typePtr != NULL) {
1993 if (objPtr->typePtr->dupIntRepProc == NULL) {
1994 dupPtr->internalRep = objPtr->internalRep;
1996 else {
1997 objPtr->typePtr->dupIntRepProc(interp, objPtr, dupPtr);
1999 dupPtr->typePtr = objPtr->typePtr;
2001 else {
2002 dupPtr->typePtr = NULL;
2004 return dupPtr;
2007 /* Return the string representation for objPtr. If the object
2008 * string representation is invalid, calls the method to create
2009 * a new one starting from the internal representation of the object. */
2010 const char *Jim_GetString(Jim_Obj *objPtr, int *lenPtr)
2012 if (objPtr->bytes == NULL) {
2013 /* Invalid string repr. Generate it. */
2014 if (objPtr->typePtr->updateStringProc == NULL) {
2015 Jim_Panic(NULL, "UpdateStringProc called against '%s' type.", objPtr->typePtr->name);
2017 objPtr->typePtr->updateStringProc(objPtr);
2019 if (lenPtr)
2020 *lenPtr = objPtr->length;
2021 return objPtr->bytes;
2024 /* Just returns the length of the object's string rep */
2025 int Jim_Length(Jim_Obj *objPtr)
2027 int len;
2029 Jim_GetString(objPtr, &len);
2030 return len;
2033 static void FreeDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
2034 static void DupDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
2036 static const Jim_ObjType dictSubstObjType = {
2037 "dict-substitution",
2038 FreeDictSubstInternalRep,
2039 DupDictSubstInternalRep,
2040 NULL,
2041 JIM_TYPE_NONE,
2044 static void FreeInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
2046 Jim_DecrRefCount(interp, (Jim_Obj *)objPtr->internalRep.twoPtrValue.ptr2);
2049 static const Jim_ObjType interpolatedObjType = {
2050 "interpolated",
2051 FreeInterpolatedInternalRep,
2052 NULL,
2053 NULL,
2054 JIM_TYPE_NONE,
2057 /* -----------------------------------------------------------------------------
2058 * String Object
2059 * ---------------------------------------------------------------------------*/
2060 static void DupStringInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
2061 static int SetStringFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
2063 static const Jim_ObjType stringObjType = {
2064 "string",
2065 NULL,
2066 DupStringInternalRep,
2067 NULL,
2068 JIM_TYPE_REFERENCES,
2071 static void DupStringInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
2073 JIM_NOTUSED(interp);
2075 /* This is a bit subtle: the only caller of this function
2076 * should be Jim_DuplicateObj(), that will copy the
2077 * string representaion. After the copy, the duplicated
2078 * object will not have more room in teh buffer than
2079 * srcPtr->length bytes. So we just set it to length. */
2080 dupPtr->internalRep.strValue.maxLength = srcPtr->length;
2082 dupPtr->internalRep.strValue.charLength = srcPtr->internalRep.strValue.charLength;
2085 static int SetStringFromAny(Jim_Interp *interp, Jim_Obj *objPtr)
2087 /* Get a fresh string representation. */
2088 (void)Jim_GetString(objPtr, NULL);
2089 /* Free any other internal representation. */
2090 Jim_FreeIntRep(interp, objPtr);
2091 /* Set it as string, i.e. just set the maxLength field. */
2092 objPtr->typePtr = &stringObjType;
2093 objPtr->internalRep.strValue.maxLength = objPtr->length;
2094 /* Don't know the utf-8 length yet */
2095 objPtr->internalRep.strValue.charLength = -1;
2096 return JIM_OK;
2100 * Returns the length of the object string in chars, not bytes.
2102 * These may be different for a utf-8 string.
2104 int Jim_Utf8Length(Jim_Interp *interp, Jim_Obj *objPtr)
2106 #ifdef JIM_UTF8
2107 if (objPtr->typePtr != &stringObjType)
2108 SetStringFromAny(interp, objPtr);
2110 if (objPtr->internalRep.strValue.charLength < 0) {
2111 objPtr->internalRep.strValue.charLength = utf8_strlen(objPtr->bytes, objPtr->length);
2113 return objPtr->internalRep.strValue.charLength;
2114 #else
2115 return Jim_Length(objPtr);
2116 #endif
2120 * Check that the name does not contain embedded nulls.
2122 * Variable and procedure names are maniplated as null terminated strings, so
2123 * don't allow names with embedded nulls.
2125 int Jim_ValidName(Jim_Interp *interp, const char *type, Jim_Obj *nameObjPtr)
2127 /* Variable names and proc names can't contain embedded nulls */
2128 int len;
2129 const char *str = Jim_GetString(nameObjPtr, &len);
2130 if (memchr(str, '\0', len)) {
2131 Jim_SetResultFormatted(interp, "%s name contains embedded null", type);
2132 return JIM_ERR;
2134 return JIM_OK;
2137 /* len is in bytes -- see also Jim_NewStringObjUtf8() */
2138 Jim_Obj *Jim_NewStringObj(Jim_Interp *interp, const char *s, int len)
2140 Jim_Obj *objPtr = Jim_NewObj(interp);
2142 /* Need to find out how many bytes the string requires */
2143 if (len == -1)
2144 len = strlen(s);
2145 /* Alloc/Set the string rep. */
2146 if (len == 0) {
2147 objPtr->bytes = JimEmptyStringRep;
2148 objPtr->length = 0;
2150 else {
2151 objPtr->bytes = Jim_Alloc(len + 1);
2152 objPtr->length = len;
2153 memcpy(objPtr->bytes, s, len);
2154 objPtr->bytes[len] = '\0';
2157 /* No typePtr field for the vanilla string object. */
2158 objPtr->typePtr = NULL;
2159 return objPtr;
2162 /* charlen is in characters -- see also Jim_NewStringObj() */
2163 Jim_Obj *Jim_NewStringObjUtf8(Jim_Interp *interp, const char *s, int charlen)
2165 #ifdef JIM_UTF8
2166 /* Need to find out how many bytes the string requires */
2167 int bytelen = utf8_index(s, charlen);
2169 Jim_Obj *objPtr = Jim_NewStringObj(interp, s, bytelen);
2171 /* Remember the utf8 length, so set the type */
2172 objPtr->typePtr = &stringObjType;
2173 objPtr->internalRep.strValue.maxLength = bytelen;
2174 objPtr->internalRep.strValue.charLength = charlen;
2176 return objPtr;
2177 #else
2178 return Jim_NewStringObj(interp, s, charlen);
2179 #endif
2182 /* This version does not try to duplicate the 's' pointer, but
2183 * use it directly. */
2184 Jim_Obj *Jim_NewStringObjNoAlloc(Jim_Interp *interp, char *s, int len)
2186 Jim_Obj *objPtr = Jim_NewObj(interp);
2188 if (len == -1)
2189 len = strlen(s);
2190 Jim_SetStringRep(objPtr, s, len);
2191 objPtr->typePtr = NULL;
2192 return objPtr;
2195 /* Low-level string append. Use it only against objects
2196 * of type "string". */
2197 static void StringAppendString(Jim_Obj *objPtr, const char *str, int len)
2199 int needlen;
2201 if (len == -1)
2202 len = strlen(str);
2203 needlen = objPtr->length + len;
2204 if (objPtr->internalRep.strValue.maxLength < needlen ||
2205 objPtr->internalRep.strValue.maxLength == 0) {
2206 needlen *= 2;
2207 /* Inefficient to malloc() for less than 8 bytes */
2208 if (needlen < 7) {
2209 needlen = 7;
2211 if (objPtr->bytes == JimEmptyStringRep) {
2212 objPtr->bytes = Jim_Alloc(needlen + 1);
2214 else {
2215 objPtr->bytes = Jim_Realloc(objPtr->bytes, needlen + 1);
2217 objPtr->internalRep.strValue.maxLength = needlen;
2219 memcpy(objPtr->bytes + objPtr->length, str, len);
2220 objPtr->bytes[objPtr->length + len] = '\0';
2221 if (objPtr->internalRep.strValue.charLength >= 0) {
2222 /* Update the utf-8 char length */
2223 objPtr->internalRep.strValue.charLength += utf8_strlen(objPtr->bytes + objPtr->length, len);
2225 objPtr->length += len;
2228 /* Higher level API to append strings to objects. */
2229 void Jim_AppendString(Jim_Interp *interp, Jim_Obj *objPtr, const char *str, int len)
2231 if (Jim_IsShared(objPtr))
2232 Jim_Panic(interp, "Jim_AppendString called with shared object");
2233 if (objPtr->typePtr != &stringObjType)
2234 SetStringFromAny(interp, objPtr);
2235 StringAppendString(objPtr, str, len);
2238 void Jim_AppendObj(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *appendObjPtr)
2240 int len;
2241 const char *str;
2243 str = Jim_GetString(appendObjPtr, &len);
2244 Jim_AppendString(interp, objPtr, str, len);
2247 void Jim_AppendStrings(Jim_Interp *interp, Jim_Obj *objPtr, ...)
2249 va_list ap;
2251 if (objPtr->typePtr != &stringObjType)
2252 SetStringFromAny(interp, objPtr);
2253 va_start(ap, objPtr);
2254 while (1) {
2255 char *s = va_arg(ap, char *);
2257 if (s == NULL)
2258 break;
2259 Jim_AppendString(interp, objPtr, s, -1);
2261 va_end(ap);
2264 int Jim_StringEqObj(Jim_Obj *aObjPtr, Jim_Obj *bObjPtr)
2266 const char *aStr, *bStr;
2267 int aLen, bLen;
2269 if (aObjPtr == bObjPtr)
2270 return 1;
2271 aStr = Jim_GetString(aObjPtr, &aLen);
2272 bStr = Jim_GetString(bObjPtr, &bLen);
2273 if (aLen != bLen)
2274 return 0;
2275 return JimStringCompare(aStr, aLen, bStr, bLen) == 0;
2278 int Jim_StringMatchObj(Jim_Interp *interp, Jim_Obj *patternObjPtr, Jim_Obj *objPtr, int nocase)
2280 return JimStringMatch(interp, patternObjPtr, Jim_GetString(objPtr, NULL), nocase);
2283 int Jim_StringCompareObj(Jim_Interp *interp, Jim_Obj *firstObjPtr, Jim_Obj *secondObjPtr, int nocase)
2285 const char *s1, *s2;
2286 int l1, l2;
2288 s1 = Jim_GetString(firstObjPtr, &l1);
2289 s2 = Jim_GetString(secondObjPtr, &l2);
2291 if (nocase) {
2292 return JimStringCompareNoCase(s1, s2, -1);
2294 return JimStringCompare(s1, l1, s2, l2);
2297 /* Convert a range, as returned by Jim_GetRange(), into
2298 * an absolute index into an object of the specified length.
2299 * This function may return negative values, or values
2300 * bigger or equal to the length of the list if the index
2301 * is out of range. */
2302 static int JimRelToAbsIndex(int len, int idx)
2304 if (idx < 0)
2305 return len + idx;
2306 return idx;
2309 /* Convert a pair of index as normalize by JimRelToAbsIndex(),
2310 * into a range stored in *firstPtr, *lastPtr, *rangeLenPtr, suitable
2311 * for implementation of commands like [string range] and [lrange].
2313 * The resulting range is guaranteed to address valid elements of
2314 * the structure. */
2315 static void JimRelToAbsRange(int len, int first, int last,
2316 int *firstPtr, int *lastPtr, int *rangeLenPtr)
2318 int rangeLen;
2320 if (first > last) {
2321 rangeLen = 0;
2323 else {
2324 rangeLen = last - first + 1;
2325 if (rangeLen) {
2326 if (first < 0) {
2327 rangeLen += first;
2328 first = 0;
2330 if (last >= len) {
2331 rangeLen -= (last - (len - 1));
2332 last = len - 1;
2336 if (rangeLen < 0)
2337 rangeLen = 0;
2339 *firstPtr = first;
2340 *lastPtr = last;
2341 *rangeLenPtr = rangeLen;
2344 Jim_Obj *Jim_StringRangeObj(Jim_Interp *interp,
2345 Jim_Obj *strObjPtr, Jim_Obj *firstObjPtr, Jim_Obj *lastObjPtr)
2347 int first, last;
2348 const char *str;
2349 int len, rangeLen;
2350 int bytelen;
2352 if (Jim_GetIndex(interp, firstObjPtr, &first) != JIM_OK ||
2353 Jim_GetIndex(interp, lastObjPtr, &last) != JIM_OK)
2354 return NULL;
2355 str = Jim_GetString(strObjPtr, &bytelen);
2356 len = Jim_Utf8Length(interp, strObjPtr);
2357 first = JimRelToAbsIndex(len, first);
2358 last = JimRelToAbsIndex(len, last);
2359 JimRelToAbsRange(len, first, last, &first, &last, &rangeLen);
2360 if (len == bytelen) {
2361 /* ASCII optimisation */
2362 return Jim_NewStringObj(interp, str + first, rangeLen);
2364 return Jim_NewStringObjUtf8(interp, str + utf8_index(str, first), rangeLen);
2367 static Jim_Obj *JimStringToLower(Jim_Interp *interp, Jim_Obj *strObjPtr)
2369 char *buf, *p;
2370 int len;
2371 const char *str;
2373 if (strObjPtr->typePtr != &stringObjType) {
2374 SetStringFromAny(interp, strObjPtr);
2377 str = Jim_GetString(strObjPtr, &len);
2379 buf = p = Jim_Alloc(len + 1);
2380 while (*str) {
2381 int c;
2382 str += utf8_tounicode(str, &c);
2383 p += utf8_fromunicode(p, utf8_lower(c));
2385 *p = 0;
2386 return Jim_NewStringObjNoAlloc(interp, buf, len);
2389 static Jim_Obj *JimStringToUpper(Jim_Interp *interp, Jim_Obj *strObjPtr)
2391 char *buf, *p;
2392 int len;
2393 const char *str;
2395 if (strObjPtr->typePtr != &stringObjType) {
2396 SetStringFromAny(interp, strObjPtr);
2399 str = Jim_GetString(strObjPtr, &len);
2401 buf = p = Jim_Alloc(len + 1);
2402 while (*str) {
2403 int c;
2404 str += utf8_tounicode(str, &c);
2405 p += utf8_fromunicode(p, utf8_upper(c));
2407 *p = 0;
2408 return Jim_NewStringObjNoAlloc(interp, buf, len);
2411 static const char *trim_left(const char *str, const char *trimchars)
2413 return str + strspn(str, trimchars);
2416 /* Note that trim_right() always trims null characters */
2417 static void trim_right(char *str, const char *trimchars)
2419 char *p = str + strlen(str) - 1;
2420 char *end = str - 1;
2422 while (p != end) {
2423 if (*p && strchr(trimchars, *p) == NULL) {
2424 break;
2426 p--;
2428 p[1] = 0;
2431 static const char default_trim_chars[] = " \t\n\r";
2433 static Jim_Obj *JimStringTrim(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *trimcharsObjPtr)
2435 char *buf;
2436 const char *trimchars = default_trim_chars;
2438 if (strObjPtr->typePtr != &stringObjType) {
2439 SetStringFromAny(interp, strObjPtr);
2441 if (trimcharsObjPtr) {
2442 trimchars = Jim_GetString(trimcharsObjPtr, NULL);
2445 buf = Jim_Alloc(strObjPtr->length + 1);
2446 strcpy(buf, trim_left(strObjPtr->bytes, trimchars));
2447 trim_right(buf, trimchars);
2449 return Jim_NewStringObjNoAlloc(interp, buf, -1);
2452 static Jim_Obj *JimStringTrimLeft(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *trimcharsObjPtr)
2454 const char *str = Jim_GetString(strObjPtr, NULL);
2455 const char *trimchars = default_trim_chars;
2457 if (trimcharsObjPtr) {
2458 trimchars = Jim_GetString(trimcharsObjPtr, NULL);
2461 return Jim_NewStringObj(interp, trim_left(str, trimchars), -1);
2464 static Jim_Obj *JimStringTrimRight(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *trimcharsObjPtr)
2466 char *buf;
2467 const char *trimchars = default_trim_chars;
2469 if (trimcharsObjPtr) {
2470 trimchars = Jim_GetString(trimcharsObjPtr, NULL);
2474 if (strObjPtr->typePtr != &stringObjType) {
2475 SetStringFromAny(interp, strObjPtr);
2478 buf = Jim_StrDup(strObjPtr->bytes);
2479 trim_right(buf, trimchars);
2481 return Jim_NewStringObjNoAlloc(interp, buf, -1);
2485 static int JimStringIs(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *strClass, int strict)
2487 static const char * const strclassnames[] = {
2488 "integer", "alpha", "alnum", "ascii", "digit",
2489 "double", "lower", "upper", "space", "xdigit",
2490 "control", "print", "graph", "punct",
2491 NULL
2493 enum {
2494 STR_IS_INTEGER, STR_IS_ALPHA, STR_IS_ALNUM, STR_IS_ASCII, STR_IS_DIGIT,
2495 STR_IS_DOUBLE, STR_IS_LOWER, STR_IS_UPPER, STR_IS_SPACE, STR_IS_XDIGIT,
2496 STR_IS_CONTROL, STR_IS_PRINT, STR_IS_GRAPH, STR_IS_PUNCT
2498 int strclass;
2499 int len;
2500 int i;
2501 const char *str;
2502 int (*isclassfunc)(int c) = NULL;
2504 if (Jim_GetEnum(interp, strClass, strclassnames, &strclass, "class", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) {
2505 return JIM_ERR;
2508 str = Jim_GetString(strObjPtr, &len);
2509 if (len == 0) {
2510 Jim_SetResultInt(interp, !strict);
2511 return JIM_OK;
2514 switch (strclass) {
2515 case STR_IS_INTEGER:
2517 jim_wide w;
2518 Jim_SetResultInt(interp, JimGetWideNoErr(interp, strObjPtr, &w) == JIM_OK);
2519 return JIM_OK;
2522 case STR_IS_DOUBLE:
2524 double d;
2525 Jim_SetResultInt(interp, Jim_GetDouble(interp, strObjPtr, &d) == JIM_OK && errno != ERANGE);
2526 return JIM_OK;
2529 case STR_IS_ALPHA: isclassfunc = isalpha; break;
2530 case STR_IS_ALNUM: isclassfunc = isalnum; break;
2531 case STR_IS_ASCII: isclassfunc = isascii; break;
2532 case STR_IS_DIGIT: isclassfunc = isdigit; break;
2533 case STR_IS_LOWER: isclassfunc = islower; break;
2534 case STR_IS_UPPER: isclassfunc = isupper; break;
2535 case STR_IS_SPACE: isclassfunc = isspace; break;
2536 case STR_IS_XDIGIT: isclassfunc = isxdigit; break;
2537 case STR_IS_CONTROL: isclassfunc = iscntrl; break;
2538 case STR_IS_PRINT: isclassfunc = isprint; break;
2539 case STR_IS_GRAPH: isclassfunc = isgraph; break;
2540 case STR_IS_PUNCT: isclassfunc = ispunct; break;
2541 default:
2542 return JIM_ERR;
2545 for (i = 0; i < len; i++) {
2546 if (!isclassfunc(str[i])) {
2547 Jim_SetResultInt(interp, 0);
2548 return JIM_OK;
2551 Jim_SetResultInt(interp, 1);
2552 return JIM_OK;
2555 /* -----------------------------------------------------------------------------
2556 * Compared String Object
2557 * ---------------------------------------------------------------------------*/
2559 /* This is strange object that allows to compare a C literal string
2560 * with a Jim object in very short time if the same comparison is done
2561 * multiple times. For example every time the [if] command is executed,
2562 * Jim has to check if a given argument is "else". This comparions if
2563 * the code has no errors are true most of the times, so we can cache
2564 * inside the object the pointer of the string of the last matching
2565 * comparison. Because most C compilers perform literal sharing,
2566 * so that: char *x = "foo", char *y = "foo", will lead to x == y,
2567 * this works pretty well even if comparisons are at different places
2568 * inside the C code. */
2570 static const Jim_ObjType comparedStringObjType = {
2571 "compared-string",
2572 NULL,
2573 NULL,
2574 NULL,
2575 JIM_TYPE_REFERENCES,
2578 /* The only way this object is exposed to the API is via the following
2579 * function. Returns true if the string and the object string repr.
2580 * are the same, otherwise zero is returned.
2582 * Note: this isn't binary safe, but it hardly needs to be.*/
2583 int Jim_CompareStringImmediate(Jim_Interp *interp, Jim_Obj *objPtr, const char *str)
2585 if (objPtr->typePtr == &comparedStringObjType && objPtr->internalRep.ptr == str)
2586 return 1;
2587 else {
2588 const char *objStr = Jim_GetString(objPtr, NULL);
2590 if (strcmp(str, objStr) != 0)
2591 return 0;
2592 if (objPtr->typePtr != &comparedStringObjType) {
2593 Jim_FreeIntRep(interp, objPtr);
2594 objPtr->typePtr = &comparedStringObjType;
2596 objPtr->internalRep.ptr = (char *)str; /*ATTENTION: const cast */
2597 return 1;
2601 static int qsortCompareStringPointers(const void *a, const void *b)
2603 char *const *sa = (char *const *)a;
2604 char *const *sb = (char *const *)b;
2606 return strcmp(*sa, *sb);
2610 /* -----------------------------------------------------------------------------
2611 * Source Object
2613 * This object is just a string from the language point of view, but
2614 * in the internal representation it contains the filename and line number
2615 * where this given token was read. This information is used by
2616 * Jim_EvalObj() if the object passed happens to be of type "source".
2618 * This allows to propagate the information about line numbers and file
2619 * names and give error messages with absolute line numbers.
2621 * Note that this object uses shared strings for filenames, and the
2622 * pointer to the filename together with the line number is taken into
2623 * the space for the "inline" internal representation of the Jim_Object,
2624 * so there is almost memory zero-overhead.
2626 * Also the object will be converted to something else if the given
2627 * token it represents in the source file is not something to be
2628 * evaluated (not a script), and will be specialized in some other way,
2629 * so the time overhead is also null.
2630 * ---------------------------------------------------------------------------*/
2632 static void FreeSourceInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
2633 static void DupSourceInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
2635 static const Jim_ObjType sourceObjType = {
2636 "source",
2637 FreeSourceInternalRep,
2638 DupSourceInternalRep,
2639 NULL,
2640 JIM_TYPE_REFERENCES,
2643 void FreeSourceInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
2645 Jim_ReleaseSharedString(interp, objPtr->internalRep.sourceValue.fileName);
2648 void DupSourceInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
2650 dupPtr->internalRep.sourceValue.fileName =
2651 Jim_GetSharedString(interp, srcPtr->internalRep.sourceValue.fileName);
2652 dupPtr->internalRep.sourceValue.lineNumber = dupPtr->internalRep.sourceValue.lineNumber;
2653 dupPtr->typePtr = &sourceObjType;
2656 static void JimSetSourceInfo(Jim_Interp *interp, Jim_Obj *objPtr,
2657 const char *fileName, int lineNumber)
2659 if (Jim_IsShared(objPtr))
2660 Jim_Panic(interp, "JimSetSourceInfo called with shared object");
2661 if (objPtr->typePtr != NULL)
2662 Jim_Panic(interp, "JimSetSourceInfo called with typePtr != NULL");
2663 objPtr->internalRep.sourceValue.fileName = Jim_GetSharedString(interp, fileName);
2664 objPtr->internalRep.sourceValue.lineNumber = lineNumber;
2665 objPtr->typePtr = &sourceObjType;
2668 /* -----------------------------------------------------------------------------
2669 * Script Object
2670 * ---------------------------------------------------------------------------*/
2672 static const Jim_ObjType scriptLineObjType = {
2673 "scriptline",
2674 NULL,
2675 NULL,
2676 NULL,
2680 static Jim_Obj *JimNewScriptLineObj(Jim_Interp *interp, int argc, int line)
2682 Jim_Obj *objPtr;
2684 objPtr = Jim_NewObj(interp);
2685 objPtr->typePtr = &scriptLineObjType;
2686 objPtr->bytes = JimEmptyStringRep;
2687 objPtr->internalRep.scriptLineValue.argc = argc;
2688 objPtr->internalRep.scriptLineValue.line = line;
2690 return objPtr;
2693 #define JIM_CMDSTRUCT_EXPAND -1
2695 static void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
2696 static void DupScriptInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
2697 static int SetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
2699 static const Jim_ObjType scriptObjType = {
2700 "script",
2701 FreeScriptInternalRep,
2702 DupScriptInternalRep,
2703 NULL,
2704 JIM_TYPE_REFERENCES,
2707 /* The ScriptToken structure represents every token into a scriptObj.
2708 * Every token contains an associated Jim_Obj that can be specialized
2709 * by commands operating on it. */
2710 typedef struct ScriptToken
2712 int type;
2713 Jim_Obj *objPtr;
2714 } ScriptToken;
2716 /* This is the script object internal representation. An array of
2717 * ScriptToken structures, including a pre-computed representation of the
2718 * command length and arguments.
2720 * For example the script:
2722 * puts hello
2723 * set $i $x$y [foo]BAR
2725 * will produce a ScriptObj with the following Tokens:
2727 * LIN 2
2728 * ESC puts
2729 * ESC hello
2730 * LIN 4
2731 * ESC set
2732 * VAR i
2733 * WRD 2
2734 * VAR x
2735 * VAR y
2736 * WRD 2
2737 * CMD foo
2738 * ESC BAR
2740 * "puts hello" has two args (LIN 2), composed of single tokens.
2741 * (Note that the WRD token is omitted for the common case of a single token.)
2743 * "set $i $x$y [foo]BAR" has four (LIN 4) args, the first word
2744 * has 1 token (ESC SET), and the last has two tokens (WRD 2 CMD foo ESC BAR)
2746 * The precomputation of the command structure makes Jim_Eval() faster,
2747 * and simpler because there aren't dynamic lengths / allocations.
2749 * -- {expand}/{*} handling --
2751 * Expand is handled in a special way.
2753 * If a "word" begins with {*}, the word token count is -ve.
2755 * For example the command:
2757 * list {*}{a b}
2759 * Will produce the following cmdstruct array:
2761 * LIN 2
2762 * ESC list
2763 * WRD -1
2764 * STR a b
2766 * Note that the 'LIN' token also contains the source information for the
2767 * first word of the line for error reporting purposes
2769 * -- the substFlags field of the structure --
2771 * The scriptObj structure is used to represent both "script" objects
2772 * and "subst" objects. In the second case, the there are no LIN and WRD
2773 * tokens. Instead SEP and EOL tokens are added as-is.
2774 * In addition, the field 'substFlags' is used to represent the flags used to turn
2775 * the string into the internal representation used to perform the
2776 * substitution. If this flags are not what the application requires
2777 * the scriptObj is created again. For example the script:
2779 * subst -nocommands $string
2780 * subst -novariables $string
2782 * Will recreate the internal representation of the $string object
2783 * two times.
2785 typedef struct ScriptObj
2787 int len; /* Length as number of tokens. */
2788 ScriptToken *token; /* Tokens array. */
2789 int substFlags; /* flags used for the compilation of "subst" objects */
2790 int inUse; /* Used to share a ScriptObj. Currently
2791 only used by Jim_EvalObj() as protection against
2792 shimmering of the currently evaluated object. */
2793 const char *fileName;
2794 int line; /* Line number of the first line */
2795 } ScriptObj;
2797 void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
2799 int i;
2800 struct ScriptObj *script = (void *)objPtr->internalRep.ptr;
2802 script->inUse--;
2803 if (script->inUse != 0)
2804 return;
2805 for (i = 0; i < script->len; i++) {
2806 Jim_DecrRefCount(interp, script->token[i].objPtr);
2808 Jim_Free(script->token);
2809 if (script->fileName) {
2810 Jim_ReleaseSharedString(interp, script->fileName);
2812 Jim_Free(script);
2815 void DupScriptInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
2817 JIM_NOTUSED(interp);
2818 JIM_NOTUSED(srcPtr);
2820 /* Just returns an simple string. */
2821 dupPtr->typePtr = NULL;
2824 /* A simple parser token.
2825 * All the simple tokens for the script point into the same script string rep.
2827 typedef struct
2829 const char *token; /* Pointer to the start of the token */
2830 int len; /* Length of this token */
2831 int type; /* Token type */
2832 int line; /* Line number */
2833 } ParseToken;
2835 /* A list of parsed tokens representing a script.
2836 * Tokens are added to this list as the script is parsed.
2837 * It grows as needed.
2839 typedef struct
2841 /* Start with a statically allocated list of tokens which will be expanded with realloc if needed */
2842 ParseToken *list; /* Array of tokens */
2843 int size; /* Current size of the list */
2844 int count; /* Number of entries used */
2845 ParseToken static_list[20]; /* Small initial token space to avoid allocation */
2846 } ParseTokenList;
2848 static void ScriptTokenListInit(ParseTokenList *tokenlist)
2850 tokenlist->list = tokenlist->static_list;
2851 tokenlist->size = sizeof(tokenlist->static_list) / sizeof(ParseToken);
2852 tokenlist->count = 0;
2855 static void ScriptTokenListFree(ParseTokenList *tokenlist)
2857 if (tokenlist->list != tokenlist->static_list) {
2858 Jim_Free(tokenlist->list);
2863 * Adds the new token to the tokenlist.
2864 * The token has the given length, type and line number.
2865 * The token list is resized as necessary.
2867 static void ScriptAddToken(ParseTokenList *tokenlist, const char *token, int len, int type,
2868 int line)
2870 ParseToken *t;
2872 if (tokenlist->count == tokenlist->size) {
2873 /* Resize the list */
2874 tokenlist->size *= 2;
2875 if (tokenlist->list != tokenlist->static_list) {
2876 tokenlist->list =
2877 Jim_Realloc(tokenlist->list, tokenlist->size * sizeof(*tokenlist->list));
2879 else {
2880 /* The list needs to become allocated */
2881 tokenlist->list = Jim_Alloc(tokenlist->size * sizeof(*tokenlist->list));
2882 memcpy(tokenlist->list, tokenlist->static_list,
2883 tokenlist->count * sizeof(*tokenlist->list));
2886 t = &tokenlist->list[tokenlist->count++];
2887 t->token = token;
2888 t->len = len;
2889 t->type = type;
2890 t->line = line;
2893 /* Counts the number of adjoining non-separator.
2895 * Returns -ve if the first token is the expansion
2896 * operator (in which case the count doesn't include
2897 * that token).
2899 static int JimCountWordTokens(ParseToken *t)
2901 int expand = 1;
2902 int count = 0;
2904 /* Is the first word {*} or {expand}? */
2905 if (t->type == JIM_TT_STR && !TOKEN_IS_SEP(t[1].type)) {
2906 if ((t->len == 1 && *t->token == '*') || (t->len == 6 && strncmp(t->token, "expand", 6) == 0)) {
2907 /* Create an expand token */
2908 expand = -1;
2909 t++;
2913 /* Now count non-separator words */
2914 while (!TOKEN_IS_SEP(t->type)) {
2915 t++;
2916 count++;
2919 return count * expand;
2923 * Create a script/subst object from the given token.
2925 static Jim_Obj *JimMakeScriptObj(Jim_Interp *interp, const ParseToken *t)
2927 Jim_Obj *objPtr;
2929 if (t->type == JIM_TT_ESC) {
2930 /* Convert the escape chars. */
2931 int len = t->len;
2932 char *str = Jim_Alloc(len + 1);
2933 len = JimEscape(str, t->token, len);
2934 objPtr = Jim_NewStringObjNoAlloc(interp, str, len);
2936 else {
2937 /* REVIST: Strictly, JIM_TT_STR should replace <backslash><newline><whitespace>
2938 * with a single space. This is currently not done.
2940 objPtr = Jim_NewStringObj(interp, t->token, t->len);
2942 return objPtr;
2946 * Takes a tokenlist and creates the allocated list of script tokens
2947 * in script->token, of length script->len.
2949 * Unnecessary tokens are discarded, and LINE and WORD tokens are inserted
2950 * as required.
2952 * Also sets script->line to the line number of the first token
2954 static void ScriptObjAddTokens(Jim_Interp *interp, struct ScriptObj *script,
2955 ParseTokenList *tokenlist)
2957 int i;
2958 struct ScriptToken *token;
2959 /* Number of tokens so far for the current command */
2960 int lineargs = 0;
2961 /* This is the first token for the current command */
2962 ScriptToken *linefirst;
2963 int count;
2964 int linenr;
2966 #ifdef DEBUG_SHOW_SCRIPT_TOKENS
2967 printf("==== Tokens ====\n");
2968 for (i = 0; i < tokenlist->count; i++) {
2969 printf("[%2d]@%d %s '%.*s'\n", i, tokenlist->list[i].line, tt_name(tokenlist->list[i].type),
2970 tokenlist->list[i].len, tokenlist->list[i].token);
2972 #endif
2974 /* May need up to one extra script token for each EOL in the worst case */
2975 count = tokenlist->count;
2976 for (i = 0; i < tokenlist->count; i++) {
2977 if (tokenlist->list[i].type == JIM_TT_EOL) {
2978 count++;
2981 linenr = script->line = tokenlist->list[0].line;
2983 token = script->token = Jim_Alloc(sizeof(ScriptToken) * count);
2985 /* This is the first token for the current command */
2986 linefirst = token++;
2988 for (i = 0; i < tokenlist->count; ) {
2989 /* Look ahead to find out how many tokens make up the next word */
2990 int wordtokens;
2992 /* Skip any leading separators */
2993 while (tokenlist->list[i].type == JIM_TT_SEP) {
2994 i++;
2997 wordtokens = JimCountWordTokens(tokenlist->list + i);
2999 if (wordtokens == 0) {
3000 /* None, so at end of line */
3001 if (lineargs) {
3002 linefirst->type = JIM_TT_LINE;
3003 linefirst->objPtr = JimNewScriptLineObj(interp, lineargs, linenr);
3004 Jim_IncrRefCount(linefirst->objPtr);
3006 /* Reset for new line */
3007 lineargs = 0;
3008 linefirst = token++;
3010 i++;
3011 continue;
3013 else if (wordtokens != 1) {
3014 /* More than 1, or {expand}, so insert a WORD token */
3015 token->type = JIM_TT_WORD;
3016 token->objPtr = Jim_NewIntObj(interp, wordtokens);
3017 Jim_IncrRefCount(token->objPtr);
3018 token++;
3019 if (wordtokens < 0) {
3020 /* Skip the expand token */
3021 i++;
3022 wordtokens = -wordtokens - 1;
3023 lineargs--;
3027 lineargs++;
3028 linenr = tokenlist->list[i].line;
3030 /* Add each non-separator word token to the line */
3031 while (wordtokens--) {
3032 const ParseToken *t = &tokenlist->list[i++];
3034 if (t->type == JIM_TT_SEP) {
3035 continue;
3038 token->type = t->type;
3039 token->objPtr = JimMakeScriptObj(interp, t);
3040 Jim_IncrRefCount(token->objPtr);
3042 /* Every object is initially a string, but the
3043 * internal type may be specialized during execution of the
3044 * script. */
3045 if (script->fileName) {
3046 JimSetSourceInfo(interp, token->objPtr, script->fileName, t->line);
3048 token++;
3052 if (lineargs == 0) {
3053 token--;
3056 script->len = token - script->token;
3058 assert(script->len < count);
3060 #ifdef DEBUG_SHOW_SCRIPT
3061 printf("==== Script ====\n");
3062 for (i = 0; i < script->len; i++) {
3063 const ScriptToken *t = &script->token[i];
3064 printf("[%2d] %s %s\n", i, tt_name(t->type), Jim_GetString(t->objPtr, NULL));
3066 #endif
3071 * Similar to ScriptObjAddTokens(), but for subst objects.
3073 static void SubstObjAddTokens(Jim_Interp *interp, struct ScriptObj *script,
3074 ParseTokenList *tokenlist)
3076 int i;
3077 struct ScriptToken *token;
3079 token = script->token = Jim_Alloc(sizeof(ScriptToken) * tokenlist->count);
3081 for (i = 0; i < tokenlist->count; i++) {
3082 const ParseToken *t = &tokenlist->list[i];
3084 /* Create a token for 't' */
3085 token->type = t->type;
3086 token->objPtr = JimMakeScriptObj(interp, t);
3087 Jim_IncrRefCount(token->objPtr);
3088 token++;
3091 script->len = i;
3094 /* This method takes the string representation of an object
3095 * as a Tcl script, and generates the pre-parsed internal representation
3096 * of the script. */
3097 int SetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
3099 int scriptTextLen;
3100 const char *scriptText = Jim_GetString(objPtr, &scriptTextLen);
3101 struct JimParserCtx parser;
3102 struct ScriptObj *script = Jim_Alloc(sizeof(*script));
3103 ParseTokenList tokenlist;
3105 /* Try to get information about filename / line number */
3106 if (objPtr->typePtr == &sourceObjType) {
3107 script->fileName = Jim_GetSharedString(interp, objPtr->internalRep.sourceValue.fileName);
3108 script->line = objPtr->internalRep.sourceValue.lineNumber;
3110 else {
3111 script->fileName = NULL;
3112 script->line = 1;
3115 /* Initially parse the script into tokens (in tokenlist) */
3116 ScriptTokenListInit(&tokenlist);
3118 JimParserInit(&parser, scriptText, scriptTextLen, script->line);
3119 while (!JimParserEof(&parser)) {
3120 JimParseScript(&parser);
3121 ScriptAddToken(&tokenlist, parser.tstart, parser.tend - parser.tstart + 1, parser.tt,
3122 parser.tline);
3124 /* Add a final EOF token */
3125 ScriptAddToken(&tokenlist, scriptText + scriptTextLen, 0, JIM_TT_EOF, 0);
3127 /* Create the "real" script tokens from the initial token list */
3128 script->substFlags = 0;
3129 script->inUse = 1;
3130 ScriptObjAddTokens(interp, script, &tokenlist);
3132 /* No longer need the token list */
3133 ScriptTokenListFree(&tokenlist);
3135 if (!script->fileName) {
3136 script->fileName = Jim_GetSharedString(interp, "");
3139 /* Free the old internal rep and set the new one. */
3140 Jim_FreeIntRep(interp, objPtr);
3141 Jim_SetIntRepPtr(objPtr, script);
3142 objPtr->typePtr = &scriptObjType;
3144 return JIM_OK;
3147 ScriptObj *Jim_GetScript(Jim_Interp *interp, Jim_Obj *objPtr)
3149 struct ScriptObj *script = Jim_GetIntRepPtr(objPtr);
3151 if (objPtr->typePtr != &scriptObjType || script->substFlags) {
3152 SetScriptFromAny(interp, objPtr);
3154 return (ScriptObj *) Jim_GetIntRepPtr(objPtr);
3157 /* -----------------------------------------------------------------------------
3158 * Commands
3159 * ---------------------------------------------------------------------------*/
3160 static void JimIncrCmdRefCount(Jim_Cmd *cmdPtr)
3162 cmdPtr->inUse++;
3165 static void JimDecrCmdRefCount(Jim_Interp *interp, Jim_Cmd *cmdPtr)
3167 if (--cmdPtr->inUse == 0) {
3168 if (cmdPtr->cmdProc == NULL) {
3169 Jim_DecrRefCount(interp, cmdPtr->argListObjPtr);
3170 Jim_DecrRefCount(interp, cmdPtr->bodyObjPtr);
3171 if (cmdPtr->staticVars) {
3172 Jim_FreeHashTable(cmdPtr->staticVars);
3173 Jim_Free(cmdPtr->staticVars);
3176 else if (cmdPtr->delProc != NULL) {
3177 /* If it was a C coded command, call the delProc if any */
3178 cmdPtr->delProc(interp, cmdPtr->privData);
3180 Jim_Free(cmdPtr);
3184 /* Commands HashTable Type.
3186 * Keys are dynamic allocated strings, Values are Jim_Cmd structures. */
3187 static void Jim_CommandsHT_ValDestructor(void *interp, void *val)
3189 JimDecrCmdRefCount(interp, val);
3192 static const Jim_HashTableType JimCommandsHashTableType = {
3193 JimStringCopyHTHashFunction, /* hash function */
3194 JimStringCopyHTKeyDup, /* key dup */
3195 NULL, /* val dup */
3196 JimStringCopyHTKeyCompare, /* key compare */
3197 JimStringCopyHTKeyDestructor, /* key destructor */
3198 Jim_CommandsHT_ValDestructor /* val destructor */
3201 /* ------------------------- Commands related functions --------------------- */
3203 int Jim_CreateCommand(Jim_Interp *interp, const char *cmdName,
3204 Jim_CmdProc cmdProc, void *privData, Jim_DelCmdProc delProc)
3206 Jim_Cmd *cmdPtr;
3208 if (Jim_DeleteHashEntry(&interp->commands, cmdName) != JIM_ERR) {
3209 /* Command existed so incr proc epoch */
3210 Jim_InterpIncrProcEpoch(interp);
3213 cmdPtr = Jim_Alloc(sizeof(*cmdPtr));
3215 /* Store the new details for this proc */
3216 cmdPtr->delProc = delProc;
3217 cmdPtr->cmdProc = cmdProc;
3218 cmdPtr->privData = privData;
3219 cmdPtr->inUse = 1;
3221 Jim_AddHashEntry(&interp->commands, cmdName, cmdPtr);
3223 /* There is no need to increment the 'proc epoch' because
3224 * creation of a new procedure can never affect existing
3225 * cached commands. We don't do negative caching. */
3226 return JIM_OK;
3229 static int Jim_CreateProcedure(Jim_Interp *interp, const char *cmdName,
3230 Jim_Obj *argListObjPtr, Jim_Obj *staticsListObjPtr, Jim_Obj *bodyObjPtr,
3231 int leftArity, int optionalArgs, int args, int rightArity)
3233 Jim_Cmd *cmdPtr;
3235 cmdPtr = Jim_Alloc(sizeof(*cmdPtr));
3236 cmdPtr->cmdProc = NULL; /* Not a C coded command */
3237 cmdPtr->argListObjPtr = argListObjPtr;
3238 cmdPtr->bodyObjPtr = bodyObjPtr;
3239 Jim_IncrRefCount(argListObjPtr);
3240 Jim_IncrRefCount(bodyObjPtr);
3241 cmdPtr->leftArity = leftArity;
3242 cmdPtr->optionalArgs = optionalArgs;
3243 cmdPtr->args = args;
3244 cmdPtr->rightArity = rightArity;
3245 cmdPtr->staticVars = NULL;
3246 cmdPtr->inUse = 1;
3248 /* Create the statics hash table. */
3249 if (staticsListObjPtr) {
3250 int len, i;
3252 len = Jim_ListLength(interp, staticsListObjPtr);
3253 if (len != 0) {
3254 cmdPtr->staticVars = Jim_Alloc(sizeof(Jim_HashTable));
3255 Jim_InitHashTable(cmdPtr->staticVars, &JimVariablesHashTableType, interp);
3256 for (i = 0; i < len; i++) {
3257 Jim_Obj *objPtr = 0, *initObjPtr = 0, *nameObjPtr = 0;
3258 Jim_Var *varPtr;
3259 int subLen;
3261 Jim_ListIndex(interp, staticsListObjPtr, i, &objPtr, JIM_NONE);
3262 /* Check if it's composed of two elements. */
3263 subLen = Jim_ListLength(interp, objPtr);
3264 if (subLen == 1 || subLen == 2) {
3265 /* Try to get the variable value from the current
3266 * environment. */
3267 Jim_ListIndex(interp, objPtr, 0, &nameObjPtr, JIM_NONE);
3268 if (subLen == 1) {
3269 initObjPtr = Jim_GetVariable(interp, nameObjPtr, JIM_NONE);
3270 if (initObjPtr == NULL) {
3271 Jim_SetResultFormatted(interp,
3272 "variable for initialization of static \"%#s\" not found in the local context",
3273 nameObjPtr);
3274 goto err;
3277 else {
3278 Jim_ListIndex(interp, objPtr, 1, &initObjPtr, JIM_NONE);
3280 if (Jim_ValidName(interp, "static variable", nameObjPtr) != JIM_OK) {
3281 goto err;
3284 varPtr = Jim_Alloc(sizeof(*varPtr));
3285 varPtr->objPtr = initObjPtr;
3286 Jim_IncrRefCount(initObjPtr);
3287 varPtr->linkFramePtr = NULL;
3288 if (Jim_AddHashEntry(cmdPtr->staticVars,
3289 Jim_GetString(nameObjPtr, NULL), varPtr) != JIM_OK) {
3290 Jim_SetResultFormatted(interp,
3291 "static variable name \"%#s\" duplicated in statics list", nameObjPtr);
3292 Jim_DecrRefCount(interp, initObjPtr);
3293 Jim_Free(varPtr);
3294 goto err;
3297 else {
3298 Jim_SetResultFormatted(interp, "too many fields in static specifier \"%#s\"",
3299 objPtr);
3300 goto err;
3306 /* Add the new command */
3308 /* It may already exist, so we try to delete the old one.
3309 * Note that reference count means that it won't be deleted yet if
3310 * it exists in the call stack
3312 if (Jim_DeleteHashEntry(&interp->commands, cmdName) != JIM_ERR) {
3313 /* There was an old procedure with the same name, this requires
3314 * a 'proc epoch' update. */
3315 Jim_InterpIncrProcEpoch(interp);
3317 /* If a procedure with the same name didn't existed there is no need
3318 * to increment the 'proc epoch' because creation of a new procedure
3319 * can never affect existing cached commands. We don't do
3320 * negative caching. */
3321 Jim_AddHashEntry(&interp->commands, cmdName, cmdPtr);
3323 /* Unlike Tcl, set the name of the proc as the result */
3324 Jim_SetResultString(interp, cmdName, -1);
3325 return JIM_OK;
3327 err:
3328 Jim_FreeHashTable(cmdPtr->staticVars);
3329 Jim_Free(cmdPtr->staticVars);
3330 Jim_DecrRefCount(interp, argListObjPtr);
3331 Jim_DecrRefCount(interp, bodyObjPtr);
3332 Jim_Free(cmdPtr);
3333 return JIM_ERR;
3336 int Jim_DeleteCommand(Jim_Interp *interp, const char *cmdName)
3338 if (Jim_DeleteHashEntry(&interp->commands, cmdName) == JIM_ERR)
3339 return JIM_ERR;
3340 Jim_InterpIncrProcEpoch(interp);
3341 return JIM_OK;
3344 int Jim_RenameCommand(Jim_Interp *interp, const char *oldName, const char *newName)
3346 Jim_HashEntry *he;
3348 /* Does it exist? */
3349 he = Jim_FindHashEntry(&interp->commands, oldName);
3350 if (he == NULL) {
3351 Jim_SetResultFormatted(interp, "can't %s \"%s\": command doesn't exist",
3352 newName[0] ? "rename" : "delete", oldName);
3353 return JIM_ERR;
3356 if (newName[0] == '\0') /* Delete! */
3357 return Jim_DeleteCommand(interp, oldName);
3359 /* rename */
3360 if (Jim_FindHashEntry(&interp->commands, newName)) {
3361 Jim_SetResultFormatted(interp, "can't rename to \"%s\": command already exists", newName);
3362 return JIM_ERR;
3365 /* Add the new name first */
3366 JimIncrCmdRefCount(he->val);
3367 Jim_AddHashEntry(&interp->commands, newName, he->val);
3369 /* Now remove the old name */
3370 Jim_DeleteHashEntry(&interp->commands, oldName);
3372 /* Increment the epoch */
3373 Jim_InterpIncrProcEpoch(interp);
3374 return JIM_OK;
3377 /* -----------------------------------------------------------------------------
3378 * Command object
3379 * ---------------------------------------------------------------------------*/
3381 static int SetCommandFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
3383 static const Jim_ObjType commandObjType = {
3384 "command",
3385 NULL,
3386 NULL,
3387 NULL,
3388 JIM_TYPE_REFERENCES,
3391 int SetCommandFromAny(Jim_Interp *interp, Jim_Obj *objPtr)
3393 Jim_HashEntry *he;
3394 const char *cmdName;
3396 /* Get the string representation */
3397 cmdName = Jim_GetString(objPtr, NULL);
3398 /* Lookup this name into the commands hash table */
3399 he = Jim_FindHashEntry(&interp->commands, cmdName);
3400 if (he == NULL)
3401 return JIM_ERR;
3403 /* Free the old internal repr and set the new one. */
3404 Jim_FreeIntRep(interp, objPtr);
3405 objPtr->typePtr = &commandObjType;
3406 objPtr->internalRep.cmdValue.procEpoch = interp->procEpoch;
3407 objPtr->internalRep.cmdValue.cmdPtr = (void *)he->val;
3408 return JIM_OK;
3411 /* This function returns the command structure for the command name
3412 * stored in objPtr. It tries to specialize the objPtr to contain
3413 * a cached info instead to perform the lookup into the hash table
3414 * every time. The information cached may not be uptodate, in such
3415 * a case the lookup is performed and the cache updated. */
3416 Jim_Cmd *Jim_GetCommand(Jim_Interp *interp, Jim_Obj *objPtr, int flags)
3418 if ((objPtr->typePtr != &commandObjType ||
3419 objPtr->internalRep.cmdValue.procEpoch != interp->procEpoch) &&
3420 SetCommandFromAny(interp, objPtr) == JIM_ERR) {
3421 if (flags & JIM_ERRMSG) {
3422 Jim_SetResultFormatted(interp, "invalid command name \"%#s\"", objPtr);
3424 return NULL;
3426 return objPtr->internalRep.cmdValue.cmdPtr;
3429 /* -----------------------------------------------------------------------------
3430 * Variables
3431 * ---------------------------------------------------------------------------*/
3433 /* Variables HashTable Type.
3435 * Keys are dynamic allocated strings, Values are Jim_Var structures. */
3436 static void JimVariablesHTValDestructor(void *interp, void *val)
3438 Jim_Var *varPtr = (void *)val;
3440 Jim_DecrRefCount(interp, varPtr->objPtr);
3441 Jim_Free(val);
3444 static const Jim_HashTableType JimVariablesHashTableType = {
3445 JimStringCopyHTHashFunction, /* hash function */
3446 JimStringCopyHTKeyDup, /* key dup */
3447 NULL, /* val dup */
3448 JimStringCopyHTKeyCompare, /* key compare */
3449 JimStringCopyHTKeyDestructor, /* key destructor */
3450 JimVariablesHTValDestructor /* val destructor */
3453 /* -----------------------------------------------------------------------------
3454 * Variable object
3455 * ---------------------------------------------------------------------------*/
3457 #define JIM_DICT_SUGAR 100 /* Only returned by SetVariableFromAny() */
3459 static int SetVariableFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
3461 static const Jim_ObjType variableObjType = {
3462 "variable",
3463 NULL,
3464 NULL,
3465 NULL,
3466 JIM_TYPE_REFERENCES,
3469 /* Return true if the string "str" looks like syntax sugar for [dict]. I.e.
3470 * is in the form "varname(key)". */
3471 static int Jim_NameIsDictSugar(const char *str, int len)
3473 if (len && str[len - 1] == ')' && strchr(str, '(') != NULL)
3474 return 1;
3475 return 0;
3478 /* This method should be called only by the variable API.
3479 * It returns JIM_OK on success (variable already exists),
3480 * JIM_ERR if it does not exists, JIM_DICT_SUGAR if it's not
3481 * a variable name, but syntax glue for [dict] i.e. the last
3482 * character is ')' */
3483 int SetVariableFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
3485 Jim_HashEntry *he;
3486 const char *varName;
3487 int len;
3488 Jim_CallFrame *framePtr = interp->framePtr;
3490 /* Check if the object is already an uptodate variable */
3491 if (objPtr->typePtr == &variableObjType &&
3492 objPtr->internalRep.varValue.callFrameId == framePtr->id) {
3493 return JIM_OK; /* nothing to do */
3496 if (objPtr->typePtr == &dictSubstObjType) {
3497 return JIM_DICT_SUGAR;
3500 /* Get the string representation */
3501 varName = Jim_GetString(objPtr, &len);
3503 /* Make sure it's not syntax glue to get/set dict. */
3504 if (Jim_NameIsDictSugar(varName, len)) {
3505 return JIM_DICT_SUGAR;
3508 if (varName[0] == ':' && varName[1] == ':') {
3509 framePtr = interp->topFramePtr;
3510 he = Jim_FindHashEntry(&framePtr->vars, varName + 2);
3511 if (he == NULL) {
3512 return JIM_ERR;
3515 else {
3516 /* Lookup this name into the variables hash table */
3517 he = Jim_FindHashEntry(&framePtr->vars, varName);
3518 if (he == NULL) {
3519 /* Try with static vars. */
3520 if (framePtr->staticVars == NULL)
3521 return JIM_ERR;
3522 if (!(he = Jim_FindHashEntry(framePtr->staticVars, varName)))
3523 return JIM_ERR;
3526 /* Free the old internal repr and set the new one. */
3527 Jim_FreeIntRep(interp, objPtr);
3528 objPtr->typePtr = &variableObjType;
3529 objPtr->internalRep.varValue.callFrameId = framePtr->id;
3530 objPtr->internalRep.varValue.varPtr = (void *)he->val;
3531 return JIM_OK;
3534 /* -------------------- Variables related functions ------------------------- */
3535 static int JimDictSugarSet(Jim_Interp *interp, Jim_Obj *ObjPtr, Jim_Obj *valObjPtr);
3536 static Jim_Obj *JimDictSugarGet(Jim_Interp *interp, Jim_Obj *ObjPtr, int flags);
3538 /* For now that's dummy. Variables lookup should be optimized
3539 * in many ways, with caching of lookups, and possibly with
3540 * a table of pre-allocated vars in every CallFrame for local vars.
3541 * All the caching should also have an 'epoch' mechanism similar
3542 * to the one used by Tcl for procedures lookup caching. */
3544 int Jim_SetVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, Jim_Obj *valObjPtr)
3546 const char *name;
3547 Jim_Var *var;
3548 int err;
3550 if ((err = SetVariableFromAny(interp, nameObjPtr)) != JIM_OK) {
3551 Jim_CallFrame *framePtr = interp->framePtr;
3553 /* Check for [dict] syntax sugar. */
3554 if (err == JIM_DICT_SUGAR)
3555 return JimDictSugarSet(interp, nameObjPtr, valObjPtr);
3557 if (Jim_ValidName(interp, "variable", nameObjPtr) != JIM_OK) {
3558 return JIM_ERR;
3561 /* New variable to create */
3562 name = Jim_GetString(nameObjPtr, NULL);
3564 var = Jim_Alloc(sizeof(*var));
3565 var->objPtr = valObjPtr;
3566 Jim_IncrRefCount(valObjPtr);
3567 var->linkFramePtr = NULL;
3568 /* Insert the new variable */
3569 if (name[0] == ':' && name[1] == ':') {
3570 /* Into to the top evel frame */
3571 framePtr = interp->topFramePtr;
3572 Jim_AddHashEntry(&framePtr->vars, name + 2, var);
3574 else {
3575 Jim_AddHashEntry(&framePtr->vars, name, var);
3577 /* Make the object int rep a variable */
3578 Jim_FreeIntRep(interp, nameObjPtr);
3579 nameObjPtr->typePtr = &variableObjType;
3580 nameObjPtr->internalRep.varValue.callFrameId = framePtr->id;
3581 nameObjPtr->internalRep.varValue.varPtr = var;
3583 else {
3584 var = nameObjPtr->internalRep.varValue.varPtr;
3585 if (var->linkFramePtr == NULL) {
3586 Jim_IncrRefCount(valObjPtr);
3587 Jim_DecrRefCount(interp, var->objPtr);
3588 var->objPtr = valObjPtr;
3590 else { /* Else handle the link */
3591 Jim_CallFrame *savedCallFrame;
3593 savedCallFrame = interp->framePtr;
3594 interp->framePtr = var->linkFramePtr;
3595 err = Jim_SetVariable(interp, var->objPtr, valObjPtr);
3596 interp->framePtr = savedCallFrame;
3597 if (err != JIM_OK)
3598 return err;
3601 return JIM_OK;
3604 int Jim_SetVariableStr(Jim_Interp *interp, const char *name, Jim_Obj *objPtr)
3606 Jim_Obj *nameObjPtr;
3607 int result;
3609 nameObjPtr = Jim_NewStringObj(interp, name, -1);
3610 Jim_IncrRefCount(nameObjPtr);
3611 result = Jim_SetVariable(interp, nameObjPtr, objPtr);
3612 Jim_DecrRefCount(interp, nameObjPtr);
3613 return result;
3616 int Jim_SetGlobalVariableStr(Jim_Interp *interp, const char *name, Jim_Obj *objPtr)
3618 Jim_CallFrame *savedFramePtr;
3619 int result;
3621 savedFramePtr = interp->framePtr;
3622 interp->framePtr = interp->topFramePtr;
3623 result = Jim_SetVariableStr(interp, name, objPtr);
3624 interp->framePtr = savedFramePtr;
3625 return result;
3628 int Jim_SetVariableStrWithStr(Jim_Interp *interp, const char *name, const char *val)
3630 Jim_Obj *nameObjPtr, *valObjPtr;
3631 int result;
3633 nameObjPtr = Jim_NewStringObj(interp, name, -1);
3634 valObjPtr = Jim_NewStringObj(interp, val, -1);
3635 Jim_IncrRefCount(nameObjPtr);
3636 Jim_IncrRefCount(valObjPtr);
3637 result = Jim_SetVariable(interp, nameObjPtr, valObjPtr);
3638 Jim_DecrRefCount(interp, nameObjPtr);
3639 Jim_DecrRefCount(interp, valObjPtr);
3640 return result;
3643 int Jim_SetVariableLink(Jim_Interp *interp, Jim_Obj *nameObjPtr,
3644 Jim_Obj *targetNameObjPtr, Jim_CallFrame *targetCallFrame)
3646 const char *varName;
3647 int len;
3649 varName = Jim_GetString(nameObjPtr, &len);
3651 if (Jim_NameIsDictSugar(varName, len)) {
3652 Jim_SetResultString(interp, "Dict key syntax invalid as link source", -1);
3653 return JIM_ERR;
3656 /* Check for an existing variable or link */
3657 if (SetVariableFromAny(interp, nameObjPtr) == JIM_OK) {
3658 Jim_Var *varPtr = nameObjPtr->internalRep.varValue.varPtr;
3660 if (varPtr->linkFramePtr == NULL) {
3661 Jim_SetResultFormatted(interp, "variable \"%#s\" already exists", nameObjPtr);
3662 return JIM_ERR;
3665 /* It exists, but is a link, so delete the link */
3666 varPtr->linkFramePtr = NULL;
3669 /* Check for cycles. */
3670 if (interp->framePtr == targetCallFrame) {
3671 Jim_Obj *objPtr = targetNameObjPtr;
3672 Jim_Var *varPtr;
3674 /* Cycles are only possible with 'uplevel 0' */
3675 while (1) {
3676 if (Jim_StringEqObj(objPtr, nameObjPtr)) {
3677 Jim_SetResultString(interp, "can't upvar from variable to itself", -1);
3678 return JIM_ERR;
3680 if (SetVariableFromAny(interp, objPtr) != JIM_OK)
3681 break;
3682 varPtr = objPtr->internalRep.varValue.varPtr;
3683 if (varPtr->linkFramePtr != targetCallFrame)
3684 break;
3685 objPtr = varPtr->objPtr;
3689 /* Perform the binding */
3690 Jim_SetVariable(interp, nameObjPtr, targetNameObjPtr);
3691 /* We are now sure 'nameObjPtr' type is variableObjType */
3692 nameObjPtr->internalRep.varValue.varPtr->linkFramePtr = targetCallFrame;
3693 return JIM_OK;
3696 /* Return the Jim_Obj pointer associated with a variable name,
3697 * or NULL if the variable was not found in the current context.
3698 * The same optimization discussed in the comment to the
3699 * 'SetVariable' function should apply here.
3701 * If JIM_UNSHARED is set and the variable is an array element (dict sugar)
3702 * in a dictionary which is shared, the array variable value is duplicated first.
3703 * This allows the array element to be updated (e.g. append, lappend) without
3704 * affecting other references to the dictionary.
3706 Jim_Obj *Jim_GetVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, int flags)
3708 switch (SetVariableFromAny(interp, nameObjPtr)) {
3709 case JIM_OK:{
3710 Jim_Var *varPtr = nameObjPtr->internalRep.varValue.varPtr;
3712 if (varPtr->linkFramePtr == NULL) {
3713 return varPtr->objPtr;
3715 else {
3716 Jim_Obj *objPtr;
3718 /* The variable is a link? Resolve it. */
3719 Jim_CallFrame *savedCallFrame = interp->framePtr;
3721 interp->framePtr = varPtr->linkFramePtr;
3722 objPtr = Jim_GetVariable(interp, varPtr->objPtr, flags);
3723 interp->framePtr = savedCallFrame;
3724 if (objPtr) {
3725 return objPtr;
3727 /* Error, so fall through to the error message */
3730 break;
3732 case JIM_DICT_SUGAR:
3733 /* [dict] syntax sugar. */
3734 return JimDictSugarGet(interp, nameObjPtr, flags);
3736 if (flags & JIM_ERRMSG) {
3737 Jim_SetResultFormatted(interp, "can't read \"%#s\": no such variable", nameObjPtr);
3739 return NULL;
3742 Jim_Obj *Jim_GetGlobalVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, int flags)
3744 Jim_CallFrame *savedFramePtr;
3745 Jim_Obj *objPtr;
3747 savedFramePtr = interp->framePtr;
3748 interp->framePtr = interp->topFramePtr;
3749 objPtr = Jim_GetVariable(interp, nameObjPtr, flags);
3750 interp->framePtr = savedFramePtr;
3752 return objPtr;
3755 Jim_Obj *Jim_GetVariableStr(Jim_Interp *interp, const char *name, int flags)
3757 Jim_Obj *nameObjPtr, *varObjPtr;
3759 nameObjPtr = Jim_NewStringObj(interp, name, -1);
3760 Jim_IncrRefCount(nameObjPtr);
3761 varObjPtr = Jim_GetVariable(interp, nameObjPtr, flags);
3762 Jim_DecrRefCount(interp, nameObjPtr);
3763 return varObjPtr;
3766 Jim_Obj *Jim_GetGlobalVariableStr(Jim_Interp *interp, const char *name, int flags)
3768 Jim_CallFrame *savedFramePtr;
3769 Jim_Obj *objPtr;
3771 savedFramePtr = interp->framePtr;
3772 interp->framePtr = interp->topFramePtr;
3773 objPtr = Jim_GetVariableStr(interp, name, flags);
3774 interp->framePtr = savedFramePtr;
3776 return objPtr;
3779 /* Unset a variable.
3780 * Note: On success unset invalidates all the variable objects created
3781 * in the current call frame incrementing. */
3782 int Jim_UnsetVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, int flags)
3784 const char *name;
3785 Jim_Var *varPtr;
3786 int retval;
3788 retval = SetVariableFromAny(interp, nameObjPtr);
3789 if (retval == JIM_DICT_SUGAR) {
3790 /* [dict] syntax sugar. */
3791 return JimDictSugarSet(interp, nameObjPtr, NULL);
3793 else if (retval == JIM_OK) {
3794 varPtr = nameObjPtr->internalRep.varValue.varPtr;
3796 /* If it's a link call UnsetVariable recursively */
3797 if (varPtr->linkFramePtr) {
3798 Jim_CallFrame *savedCallFrame;
3800 savedCallFrame = interp->framePtr;
3801 interp->framePtr = varPtr->linkFramePtr;
3802 retval = Jim_UnsetVariable(interp, varPtr->objPtr, JIM_NONE);
3803 interp->framePtr = savedCallFrame;
3805 else {
3806 Jim_CallFrame *framePtr = interp->framePtr;
3808 name = Jim_GetString(nameObjPtr, NULL);
3809 if (name[0] == ':' && name[1] == ':') {
3810 framePtr = interp->topFramePtr;
3811 name += 2;
3813 retval = Jim_DeleteHashEntry(&framePtr->vars, name);
3814 if (retval == JIM_OK) {
3815 /* Change the callframe id, invalidating var lookup caching */
3816 JimChangeCallFrameId(interp, framePtr);
3820 if (retval != JIM_OK && (flags & JIM_ERRMSG)) {
3821 Jim_SetResultFormatted(interp, "can't unset \"%#s\": no such variable", nameObjPtr);
3823 return retval;
3826 /* ---------- Dict syntax sugar (similar to array Tcl syntax) -------------- */
3828 /* Given a variable name for [dict] operation syntax sugar,
3829 * this function returns two objects, the first with the name
3830 * of the variable to set, and the second with the rispective key.
3831 * For example "foo(bar)" will return objects with string repr. of
3832 * "foo" and "bar".
3834 * The returned objects have refcount = 1. The function can't fail. */
3835 static void JimDictSugarParseVarKey(Jim_Interp *interp, Jim_Obj *objPtr,
3836 Jim_Obj **varPtrPtr, Jim_Obj **keyPtrPtr)
3838 const char *str, *p;
3839 char *t;
3840 int len, keyLen, nameLen;
3841 Jim_Obj *varObjPtr, *keyObjPtr;
3843 str = Jim_GetString(objPtr, &len);
3845 p = strchr(str, '(');
3846 if (p == NULL) {
3847 Jim_Panic(interp, "JimDictSugarParseVarKey() called for non-dict-sugar (%s)", str);
3849 p++;
3850 keyLen = len - ((p - str) + 1);
3851 nameLen = (p - str) - 1;
3852 /* Create the objects with the variable name and key. */
3853 t = Jim_Alloc(nameLen + 1);
3854 memcpy(t, str, nameLen);
3855 t[nameLen] = '\0';
3856 varObjPtr = Jim_NewStringObjNoAlloc(interp, t, nameLen);
3858 t = Jim_Alloc(keyLen + 1);
3859 memcpy(t, p, keyLen);
3860 t[keyLen] = '\0';
3861 keyObjPtr = Jim_NewStringObjNoAlloc(interp, t, keyLen);
3863 Jim_IncrRefCount(varObjPtr);
3864 Jim_IncrRefCount(keyObjPtr);
3865 *varPtrPtr = varObjPtr;
3866 *keyPtrPtr = keyObjPtr;
3869 /* Helper of Jim_SetVariable() to deal with dict-syntax variable names.
3870 * Also used by Jim_UnsetVariable() with valObjPtr = NULL. */
3871 static int JimDictSugarSet(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *valObjPtr)
3873 int err;
3875 SetDictSubstFromAny(interp, objPtr);
3877 err = Jim_SetDictKeysVector(interp, objPtr->internalRep.dictSubstValue.varNameObjPtr,
3878 &objPtr->internalRep.dictSubstValue.indexObjPtr, 1, valObjPtr);
3880 if (err == JIM_OK) {
3881 /* Don't keep an extra ref to the result */
3882 Jim_SetEmptyResult(interp);
3884 else {
3885 if (!valObjPtr) {
3886 /* Better error message for unset a(2) where a exists but a(2) doesn't */
3887 if (Jim_GetVariable(interp, objPtr->internalRep.dictSubstValue.varNameObjPtr, JIM_NONE)) {
3888 Jim_SetResultFormatted(interp, "can't unset \"%#s\": no such element in array",
3889 objPtr);
3890 return err;
3893 /* Make the error more informative and Tcl-compatible */
3894 Jim_SetResultFormatted(interp, "can't %s \"%#s\": variable isn't array",
3895 (valObjPtr ? "set" : "unset"), objPtr);
3897 return err;
3901 * Expands the array variable (dict sugar) and returns the result, or NULL on error.
3903 * If JIM_UNSHARED is set and the dictionary is shared, it will be duplicated
3904 * and stored back to the variable before expansion.
3906 static Jim_Obj *JimDictExpandArrayVariable(Jim_Interp *interp, Jim_Obj *varObjPtr,
3907 Jim_Obj *keyObjPtr, int flags)
3909 Jim_Obj *dictObjPtr;
3910 Jim_Obj *resObjPtr = NULL;
3911 int ret;
3913 dictObjPtr = Jim_GetVariable(interp, varObjPtr, JIM_ERRMSG);
3914 if (!dictObjPtr) {
3915 return NULL;
3918 ret = Jim_DictKey(interp, dictObjPtr, keyObjPtr, &resObjPtr, JIM_NONE);
3919 if (ret != JIM_OK) {
3920 resObjPtr = NULL;
3921 if (ret < 0) {
3922 Jim_SetResultFormatted(interp,
3923 "can't read \"%#s(%#s)\": variable isn't array", varObjPtr, keyObjPtr);
3925 else {
3926 Jim_SetResultFormatted(interp,
3927 "can't read \"%#s(%#s)\": no such element in array", varObjPtr, keyObjPtr);
3930 else if ((flags & JIM_UNSHARED) && Jim_IsShared(dictObjPtr)) {
3931 dictObjPtr = Jim_DuplicateObj(interp, dictObjPtr);
3932 if (Jim_SetVariable(interp, varObjPtr, dictObjPtr) != JIM_OK) {
3933 /* This can probably never happen */
3934 Jim_Panic(interp, "SetVariable failed for JIM_UNSHARED");
3936 /* We know that the key exists. Get the result in the now-unshared dictionary */
3937 Jim_DictKey(interp, dictObjPtr, keyObjPtr, &resObjPtr, JIM_NONE);
3940 return resObjPtr;
3943 /* Helper of Jim_GetVariable() to deal with dict-syntax variable names */
3944 static Jim_Obj *JimDictSugarGet(Jim_Interp *interp, Jim_Obj *objPtr, int flags)
3946 Jim_Obj *varObjPtr, *keyObjPtr, *resObjPtr;
3949 JimDictSugarParseVarKey(interp, objPtr, &varObjPtr, &keyObjPtr);
3951 resObjPtr = JimDictExpandArrayVariable(interp, varObjPtr, keyObjPtr, flags);
3953 Jim_DecrRefCount(interp, varObjPtr);
3954 Jim_DecrRefCount(interp, keyObjPtr);
3956 return resObjPtr;
3959 /* --------- $var(INDEX) substitution, using a specialized object ----------- */
3961 void FreeDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
3963 Jim_DecrRefCount(interp, objPtr->internalRep.dictSubstValue.varNameObjPtr);
3964 Jim_DecrRefCount(interp, objPtr->internalRep.dictSubstValue.indexObjPtr);
3967 void DupDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
3969 JIM_NOTUSED(interp);
3971 dupPtr->internalRep.dictSubstValue.varNameObjPtr =
3972 srcPtr->internalRep.dictSubstValue.varNameObjPtr;
3973 dupPtr->internalRep.dictSubstValue.indexObjPtr = srcPtr->internalRep.dictSubstValue.indexObjPtr;
3974 dupPtr->typePtr = &dictSubstObjType;
3977 /* Note: The object *must* be in dict-sugar format */
3978 static void SetDictSubstFromAny(Jim_Interp *interp, Jim_Obj *objPtr)
3980 if (objPtr->typePtr != &dictSubstObjType) {
3981 Jim_Obj *varObjPtr, *keyObjPtr;
3983 if (objPtr->typePtr == &interpolatedObjType) {
3984 /* An interpolated object in dict-sugar form */
3986 const ScriptToken *token = objPtr->internalRep.twoPtrValue.ptr1;
3988 varObjPtr = token[0].objPtr;
3989 keyObjPtr = objPtr->internalRep.twoPtrValue.ptr2;
3991 Jim_IncrRefCount(varObjPtr);
3992 Jim_IncrRefCount(keyObjPtr);
3994 else {
3995 JimDictSugarParseVarKey(interp, objPtr, &varObjPtr, &keyObjPtr);
3998 Jim_FreeIntRep(interp, objPtr);
3999 objPtr->typePtr = &dictSubstObjType;
4000 objPtr->internalRep.dictSubstValue.varNameObjPtr = varObjPtr;
4001 objPtr->internalRep.dictSubstValue.indexObjPtr = keyObjPtr;
4005 /* This function is used to expand [dict get] sugar in the form
4006 * of $var(INDEX). The function is mainly used by Jim_EvalObj()
4007 * to deal with tokens of type JIM_TT_DICTSUGAR. objPtr points to an
4008 * object that is *guaranteed* to be in the form VARNAME(INDEX).
4009 * The 'index' part is [subst]ituted, and is used to lookup a key inside
4010 * the [dict]ionary contained in variable VARNAME. */
4011 static Jim_Obj *Jim_ExpandDictSugar(Jim_Interp *interp, Jim_Obj *objPtr)
4013 Jim_Obj *resObjPtr = NULL;
4014 Jim_Obj *substKeyObjPtr = NULL;
4016 SetDictSubstFromAny(interp, objPtr);
4018 if (Jim_SubstObj(interp, objPtr->internalRep.dictSubstValue.indexObjPtr,
4019 &substKeyObjPtr, JIM_NONE)
4020 != JIM_OK) {
4021 return NULL;
4023 Jim_IncrRefCount(substKeyObjPtr);
4024 resObjPtr =
4025 JimDictExpandArrayVariable(interp, objPtr->internalRep.dictSubstValue.varNameObjPtr,
4026 substKeyObjPtr, 0);
4027 Jim_DecrRefCount(interp, substKeyObjPtr);
4029 return resObjPtr;
4032 /* -----------------------------------------------------------------------------
4033 * CallFrame
4034 * ---------------------------------------------------------------------------*/
4036 static Jim_CallFrame *JimCreateCallFrame(Jim_Interp *interp)
4038 Jim_CallFrame *cf;
4040 if (interp->freeFramesList) {
4041 cf = interp->freeFramesList;
4042 interp->freeFramesList = cf->nextFramePtr;
4044 else {
4045 cf = Jim_Alloc(sizeof(*cf));
4046 cf->vars.table = NULL;
4049 cf->id = interp->callFrameEpoch++;
4050 cf->parentCallFrame = NULL;
4051 cf->argv = NULL;
4052 cf->argc = 0;
4053 cf->procArgsObjPtr = NULL;
4054 cf->procBodyObjPtr = NULL;
4055 cf->nextFramePtr = NULL;
4056 cf->staticVars = NULL;
4057 if (cf->vars.table == NULL)
4058 Jim_InitHashTable(&cf->vars, &JimVariablesHashTableType, interp);
4059 return cf;
4062 /* Used to invalidate every caching related to callframe stability. */
4063 static void JimChangeCallFrameId(Jim_Interp *interp, Jim_CallFrame *cf)
4065 cf->id = interp->callFrameEpoch++;
4068 #define JIM_FCF_NONE 0 /* no flags */
4069 #define JIM_FCF_NOHT 1 /* don't free the hash table */
4070 static void JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int flags)
4072 if (cf->procArgsObjPtr)
4073 Jim_DecrRefCount(interp, cf->procArgsObjPtr);
4074 if (cf->procBodyObjPtr)
4075 Jim_DecrRefCount(interp, cf->procBodyObjPtr);
4076 if (!(flags & JIM_FCF_NOHT))
4077 Jim_FreeHashTable(&cf->vars);
4078 else {
4079 int i;
4080 Jim_HashEntry **table = cf->vars.table, *he;
4082 for (i = 0; i < JIM_HT_INITIAL_SIZE; i++) {
4083 he = table[i];
4084 while (he != NULL) {
4085 Jim_HashEntry *nextEntry = he->next;
4086 Jim_Var *varPtr = (void *)he->val;
4088 Jim_DecrRefCount(interp, varPtr->objPtr);
4089 Jim_Free(he->val);
4090 Jim_Free((void *)he->key); /* ATTENTION: const cast */
4091 Jim_Free(he);
4092 table[i] = NULL;
4093 he = nextEntry;
4096 cf->vars.used = 0;
4098 cf->nextFramePtr = interp->freeFramesList;
4099 interp->freeFramesList = cf;
4102 /* -----------------------------------------------------------------------------
4103 * References
4104 * ---------------------------------------------------------------------------*/
4105 #ifdef JIM_REFERENCES
4107 /* References HashTable Type.
4109 * Keys are jim_wide integers, dynamically allocated for now but in the
4110 * future it's worth to cache this 8 bytes objects. Values are poitners
4111 * to Jim_References. */
4112 static void JimReferencesHTValDestructor(void *interp, void *val)
4114 Jim_Reference *refPtr = (void *)val;
4116 Jim_DecrRefCount(interp, refPtr->objPtr);
4117 if (refPtr->finalizerCmdNamePtr != NULL) {
4118 Jim_DecrRefCount(interp, refPtr->finalizerCmdNamePtr);
4120 Jim_Free(val);
4123 static unsigned int JimReferencesHTHashFunction(const void *key)
4125 /* Only the least significant bits are used. */
4126 const jim_wide *widePtr = key;
4127 unsigned int intValue = (unsigned int)*widePtr;
4129 return Jim_IntHashFunction(intValue);
4132 static const void *JimReferencesHTKeyDup(void *privdata, const void *key)
4134 void *copy = Jim_Alloc(sizeof(jim_wide));
4136 JIM_NOTUSED(privdata);
4138 memcpy(copy, key, sizeof(jim_wide));
4139 return copy;
4142 static int JimReferencesHTKeyCompare(void *privdata, const void *key1, const void *key2)
4144 JIM_NOTUSED(privdata);
4146 return memcmp(key1, key2, sizeof(jim_wide)) == 0;
4149 static void JimReferencesHTKeyDestructor(void *privdata, const void *key)
4151 JIM_NOTUSED(privdata);
4153 Jim_Free((void *)key);
4156 static const Jim_HashTableType JimReferencesHashTableType = {
4157 JimReferencesHTHashFunction, /* hash function */
4158 JimReferencesHTKeyDup, /* key dup */
4159 NULL, /* val dup */
4160 JimReferencesHTKeyCompare, /* key compare */
4161 JimReferencesHTKeyDestructor, /* key destructor */
4162 JimReferencesHTValDestructor /* val destructor */
4165 /* -----------------------------------------------------------------------------
4166 * Reference object type and References API
4167 * ---------------------------------------------------------------------------*/
4169 /* The string representation of references has two features in order
4170 * to make the GC faster. The first is that every reference starts
4171 * with a non common character '<', in order to make the string matching
4172 * faster. The second is that the reference string rep is 42 characters
4173 * in length, this allows to avoid to check every object with a string
4174 * repr < 42, and usually there aren't many of these objects. */
4176 #define JIM_REFERENCE_SPACE (35+JIM_REFERENCE_TAGLEN)
4178 static int JimFormatReference(char *buf, Jim_Reference *refPtr, jim_wide id)
4180 const char *fmt = "<reference.<%s>.%020" JIM_WIDE_MODIFIER ">";
4182 sprintf(buf, fmt, refPtr->tag, id);
4183 return JIM_REFERENCE_SPACE;
4186 static void UpdateStringOfReference(struct Jim_Obj *objPtr);
4188 static const Jim_ObjType referenceObjType = {
4189 "reference",
4190 NULL,
4191 NULL,
4192 UpdateStringOfReference,
4193 JIM_TYPE_REFERENCES,
4196 void UpdateStringOfReference(struct Jim_Obj *objPtr)
4198 int len;
4199 char buf[JIM_REFERENCE_SPACE + 1];
4200 Jim_Reference *refPtr;
4202 refPtr = objPtr->internalRep.refValue.refPtr;
4203 len = JimFormatReference(buf, refPtr, objPtr->internalRep.refValue.id);
4204 objPtr->bytes = Jim_Alloc(len + 1);
4205 memcpy(objPtr->bytes, buf, len + 1);
4206 objPtr->length = len;
4209 /* returns true if 'c' is a valid reference tag character.
4210 * i.e. inside the range [_a-zA-Z0-9] */
4211 static int isrefchar(int c)
4213 return (c == '_' || isalnum(c));
4216 static int SetReferenceFromAny(Jim_Interp *interp, Jim_Obj *objPtr)
4218 jim_wide wideValue;
4219 int i, len;
4220 const char *str, *start, *end;
4221 char refId[21];
4222 Jim_Reference *refPtr;
4223 Jim_HashEntry *he;
4225 /* Get the string representation */
4226 str = Jim_GetString(objPtr, &len);
4227 /* Check if it looks like a reference */
4228 if (len < JIM_REFERENCE_SPACE)
4229 goto badformat;
4230 /* Trim spaces */
4231 start = str;
4232 end = str + len - 1;
4233 while (*start == ' ')
4234 start++;
4235 while (*end == ' ' && end > start)
4236 end--;
4237 if (end - start + 1 != JIM_REFERENCE_SPACE)
4238 goto badformat;
4239 /* <reference.<1234567>.%020> */
4240 if (memcmp(start, "<reference.<", 12) != 0)
4241 goto badformat;
4242 if (start[12 + JIM_REFERENCE_TAGLEN] != '>' || end[0] != '>')
4243 goto badformat;
4244 /* The tag can't contain chars other than a-zA-Z0-9 + '_'. */
4245 for (i = 0; i < JIM_REFERENCE_TAGLEN; i++) {
4246 if (!isrefchar(start[12 + i]))
4247 goto badformat;
4249 /* Extract info from the reference. */
4250 memcpy(refId, start + 14 + JIM_REFERENCE_TAGLEN, 20);
4251 refId[20] = '\0';
4252 /* Try to convert the ID into a jim_wide */
4253 if (Jim_StringToWide(refId, &wideValue, 10) != JIM_OK)
4254 goto badformat;
4255 /* Check if the reference really exists! */
4256 he = Jim_FindHashEntry(&interp->references, &wideValue);
4257 if (he == NULL) {
4258 Jim_SetResultFormatted(interp, "invalid reference id \"%#s\"", objPtr);
4259 return JIM_ERR;
4261 refPtr = he->val;
4262 /* Free the old internal repr and set the new one. */
4263 Jim_FreeIntRep(interp, objPtr);
4264 objPtr->typePtr = &referenceObjType;
4265 objPtr->internalRep.refValue.id = wideValue;
4266 objPtr->internalRep.refValue.refPtr = refPtr;
4267 return JIM_OK;
4269 badformat:
4270 Jim_SetResultFormatted(interp, "expected reference but got \"%#s\"", objPtr);
4271 return JIM_ERR;
4274 /* Returns a new reference pointing to objPtr, having cmdNamePtr
4275 * as finalizer command (or NULL if there is no finalizer).
4276 * The returned reference object has refcount = 0. */
4277 Jim_Obj *Jim_NewReference(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *tagPtr, Jim_Obj *cmdNamePtr)
4279 struct Jim_Reference *refPtr;
4280 jim_wide wideValue = interp->referenceNextId;
4281 Jim_Obj *refObjPtr;
4282 const char *tag;
4283 int tagLen, i;
4285 /* Perform the Garbage Collection if needed. */
4286 Jim_CollectIfNeeded(interp);
4288 refPtr = Jim_Alloc(sizeof(*refPtr));
4289 refPtr->objPtr = objPtr;
4290 Jim_IncrRefCount(objPtr);
4291 refPtr->finalizerCmdNamePtr = cmdNamePtr;
4292 if (cmdNamePtr)
4293 Jim_IncrRefCount(cmdNamePtr);
4294 Jim_AddHashEntry(&interp->references, &wideValue, refPtr);
4295 refObjPtr = Jim_NewObj(interp);
4296 refObjPtr->typePtr = &referenceObjType;
4297 refObjPtr->bytes = NULL;
4298 refObjPtr->internalRep.refValue.id = interp->referenceNextId;
4299 refObjPtr->internalRep.refValue.refPtr = refPtr;
4300 interp->referenceNextId++;
4301 /* Set the tag. Trimmed at JIM_REFERENCE_TAGLEN. Everything
4302 * that does not pass the 'isrefchar' test is replaced with '_' */
4303 tag = Jim_GetString(tagPtr, &tagLen);
4304 if (tagLen > JIM_REFERENCE_TAGLEN)
4305 tagLen = JIM_REFERENCE_TAGLEN;
4306 for (i = 0; i < JIM_REFERENCE_TAGLEN; i++) {
4307 if (i < tagLen && isrefchar(tag[i]))
4308 refPtr->tag[i] = tag[i];
4309 else
4310 refPtr->tag[i] = '_';
4312 refPtr->tag[JIM_REFERENCE_TAGLEN] = '\0';
4313 return refObjPtr;
4316 Jim_Reference *Jim_GetReference(Jim_Interp *interp, Jim_Obj *objPtr)
4318 if (objPtr->typePtr != &referenceObjType && SetReferenceFromAny(interp, objPtr) == JIM_ERR)
4319 return NULL;
4320 return objPtr->internalRep.refValue.refPtr;
4323 int Jim_SetFinalizer(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *cmdNamePtr)
4325 Jim_Reference *refPtr;
4327 if ((refPtr = Jim_GetReference(interp, objPtr)) == NULL)
4328 return JIM_ERR;
4329 Jim_IncrRefCount(cmdNamePtr);
4330 if (refPtr->finalizerCmdNamePtr)
4331 Jim_DecrRefCount(interp, refPtr->finalizerCmdNamePtr);
4332 refPtr->finalizerCmdNamePtr = cmdNamePtr;
4333 return JIM_OK;
4336 int Jim_GetFinalizer(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj **cmdNamePtrPtr)
4338 Jim_Reference *refPtr;
4340 if ((refPtr = Jim_GetReference(interp, objPtr)) == NULL)
4341 return JIM_ERR;
4342 *cmdNamePtrPtr = refPtr->finalizerCmdNamePtr;
4343 return JIM_OK;
4346 /* -----------------------------------------------------------------------------
4347 * References Garbage Collection
4348 * ---------------------------------------------------------------------------*/
4350 /* This the hash table type for the "MARK" phase of the GC */
4351 static const Jim_HashTableType JimRefMarkHashTableType = {
4352 JimReferencesHTHashFunction, /* hash function */
4353 JimReferencesHTKeyDup, /* key dup */
4354 NULL, /* val dup */
4355 JimReferencesHTKeyCompare, /* key compare */
4356 JimReferencesHTKeyDestructor, /* key destructor */
4357 NULL /* val destructor */
4360 /* Performs the garbage collection. */
4361 int Jim_Collect(Jim_Interp *interp)
4363 Jim_HashTable marks;
4364 Jim_HashTableIterator *htiter;
4365 Jim_HashEntry *he;
4366 Jim_Obj *objPtr;
4367 int collected = 0;
4369 /* Avoid recursive calls */
4370 if (interp->lastCollectId == -1) {
4371 /* Jim_Collect() already running. Return just now. */
4372 return 0;
4374 interp->lastCollectId = -1;
4376 /* Mark all the references found into the 'mark' hash table.
4377 * The references are searched in every live object that
4378 * is of a type that can contain references. */
4379 Jim_InitHashTable(&marks, &JimRefMarkHashTableType, NULL);
4380 objPtr = interp->liveList;
4381 while (objPtr) {
4382 if (objPtr->typePtr == NULL || objPtr->typePtr->flags & JIM_TYPE_REFERENCES) {
4383 const char *str, *p;
4384 int len;
4386 /* If the object is of type reference, to get the
4387 * Id is simple... */
4388 if (objPtr->typePtr == &referenceObjType) {
4389 Jim_AddHashEntry(&marks, &objPtr->internalRep.refValue.id, NULL);
4390 #ifdef JIM_DEBUG_GC
4391 printf("MARK (reference): %d refcount: %d" JIM_NL,
4392 (int)objPtr->internalRep.refValue.id, objPtr->refCount);
4393 #endif
4394 objPtr = objPtr->nextObjPtr;
4395 continue;
4397 /* Get the string repr of the object we want
4398 * to scan for references. */
4399 p = str = Jim_GetString(objPtr, &len);
4400 /* Skip objects too little to contain references. */
4401 if (len < JIM_REFERENCE_SPACE) {
4402 objPtr = objPtr->nextObjPtr;
4403 continue;
4405 /* Extract references from the object string repr. */
4406 while (1) {
4407 int i;
4408 jim_wide id;
4409 char buf[21];
4411 if ((p = strstr(p, "<reference.<")) == NULL)
4412 break;
4413 /* Check if it's a valid reference. */
4414 if (len - (p - str) < JIM_REFERENCE_SPACE)
4415 break;
4416 if (p[41] != '>' || p[19] != '>' || p[20] != '.')
4417 break;
4418 for (i = 21; i <= 40; i++)
4419 if (!isdigit(UCHAR(p[i])))
4420 break;
4421 /* Get the ID */
4422 memcpy(buf, p + 21, 20);
4423 buf[20] = '\0';
4424 Jim_StringToWide(buf, &id, 10);
4426 /* Ok, a reference for the given ID
4427 * was found. Mark it. */
4428 Jim_AddHashEntry(&marks, &id, NULL);
4429 #ifdef JIM_DEBUG_GC
4430 printf("MARK: %d" JIM_NL, (int)id);
4431 #endif
4432 p += JIM_REFERENCE_SPACE;
4435 objPtr = objPtr->nextObjPtr;
4438 /* Run the references hash table to destroy every reference that
4439 * is not referenced outside (not present in the mark HT). */
4440 htiter = Jim_GetHashTableIterator(&interp->references);
4441 while ((he = Jim_NextHashEntry(htiter)) != NULL) {
4442 const jim_wide *refId;
4443 Jim_Reference *refPtr;
4445 refId = he->key;
4446 /* Check if in the mark phase we encountered
4447 * this reference. */
4448 if (Jim_FindHashEntry(&marks, refId) == NULL) {
4449 #ifdef JIM_DEBUG_GC
4450 printf("COLLECTING %d" JIM_NL, (int)*refId);
4451 #endif
4452 collected++;
4453 /* Drop the reference, but call the
4454 * finalizer first if registered. */
4455 refPtr = he->val;
4456 if (refPtr->finalizerCmdNamePtr) {
4457 char *refstr = Jim_Alloc(JIM_REFERENCE_SPACE + 1);
4458 Jim_Obj *objv[3], *oldResult;
4460 JimFormatReference(refstr, refPtr, *refId);
4462 objv[0] = refPtr->finalizerCmdNamePtr;
4463 objv[1] = Jim_NewStringObjNoAlloc(interp, refstr, 32);
4464 objv[2] = refPtr->objPtr;
4465 Jim_IncrRefCount(objv[0]);
4466 Jim_IncrRefCount(objv[1]);
4467 Jim_IncrRefCount(objv[2]);
4469 /* Drop the reference itself */
4470 Jim_DeleteHashEntry(&interp->references, refId);
4472 /* Call the finalizer. Errors ignored. */
4473 oldResult = interp->result;
4474 Jim_IncrRefCount(oldResult);
4475 Jim_EvalObjVector(interp, 3, objv);
4476 Jim_SetResult(interp, oldResult);
4477 Jim_DecrRefCount(interp, oldResult);
4479 Jim_DecrRefCount(interp, objv[0]);
4480 Jim_DecrRefCount(interp, objv[1]);
4481 Jim_DecrRefCount(interp, objv[2]);
4483 else {
4484 Jim_DeleteHashEntry(&interp->references, refId);
4488 Jim_FreeHashTableIterator(htiter);
4489 Jim_FreeHashTable(&marks);
4490 interp->lastCollectId = interp->referenceNextId;
4491 interp->lastCollectTime = time(NULL);
4492 return collected;
4495 #define JIM_COLLECT_ID_PERIOD 5000
4496 #define JIM_COLLECT_TIME_PERIOD 300
4498 void Jim_CollectIfNeeded(Jim_Interp *interp)
4500 jim_wide elapsedId;
4501 int elapsedTime;
4503 elapsedId = interp->referenceNextId - interp->lastCollectId;
4504 elapsedTime = time(NULL) - interp->lastCollectTime;
4507 if (elapsedId > JIM_COLLECT_ID_PERIOD || elapsedTime > JIM_COLLECT_TIME_PERIOD) {
4508 Jim_Collect(interp);
4511 #endif
4513 /* -----------------------------------------------------------------------------
4514 * Interpreter related functions
4515 * ---------------------------------------------------------------------------*/
4517 Jim_Interp *Jim_CreateInterp(void)
4519 Jim_Interp *i = Jim_Alloc(sizeof(*i));
4521 i->errorLine = 0;
4522 i->errorFileName = Jim_StrDup("");
4523 i->addStackTrace = 0;
4524 i->numLevels = 0;
4525 i->maxNestingDepth = JIM_MAX_NESTING_DEPTH;
4526 i->returnCode = JIM_OK;
4527 i->returnLevel = 0;
4528 i->exitCode = 0;
4529 i->procEpoch = 0;
4530 i->callFrameEpoch = 0;
4531 i->liveList = i->freeList = NULL;
4532 i->referenceNextId = 0;
4533 i->lastCollectId = 0;
4534 i->lastCollectTime = time(NULL);
4535 i->freeFramesList = NULL;
4536 i->prngState = NULL;
4537 i->id = 0;
4538 i->sigmask = 0;
4539 i->signal_level = 0;
4540 i->signal_set_result = NULL;
4541 i->localProcs = NULL;
4543 /* Note that we can create objects only after the
4544 * interpreter liveList and freeList pointers are
4545 * initialized to NULL. */
4546 Jim_InitHashTable(&i->commands, &JimCommandsHashTableType, i);
4547 #ifdef JIM_REFERENCES
4548 Jim_InitHashTable(&i->references, &JimReferencesHashTableType, i);
4549 #endif
4550 Jim_InitHashTable(&i->sharedStrings, &JimSharedStringsHashTableType, NULL);
4551 Jim_InitHashTable(&i->assocData, &JimAssocDataHashTableType, i);
4552 Jim_InitHashTable(&i->packages, &JimStringKeyValCopyHashTableType, NULL);
4553 i->framePtr = i->topFramePtr = JimCreateCallFrame(i);
4554 i->emptyObj = Jim_NewEmptyStringObj(i);
4555 i->trueObj = Jim_NewIntObj(i, 1);
4556 i->falseObj = Jim_NewIntObj(i, 0);
4557 i->result = i->emptyObj;
4558 i->stackTrace = Jim_NewListObj(i, NULL, 0);
4559 i->unknown = Jim_NewStringObj(i, "unknown", -1);
4560 i->unknown_called = 0;
4561 i->errorProc = i->emptyObj;
4562 i->currentScriptObj = Jim_NewEmptyStringObj(i);
4563 Jim_IncrRefCount(i->emptyObj);
4564 Jim_IncrRefCount(i->result);
4565 Jim_IncrRefCount(i->stackTrace);
4566 Jim_IncrRefCount(i->unknown);
4567 Jim_IncrRefCount(i->currentScriptObj);
4568 Jim_IncrRefCount(i->errorProc);
4569 Jim_IncrRefCount(i->trueObj);
4570 Jim_IncrRefCount(i->falseObj);
4572 /* Initialize key variables every interpreter should contain */
4573 Jim_SetVariableStrWithStr(i, JIM_LIBPATH, TCL_LIBRARY);
4574 Jim_SetVariableStrWithStr(i, JIM_INTERACTIVE, "0");
4576 Jim_SetVariableStrWithStr(i, "tcl_platform(os)", TCL_PLATFORM_OS);
4577 Jim_SetVariableStrWithStr(i, "tcl_platform(platform)", TCL_PLATFORM_PLATFORM);
4579 return i;
4582 void Jim_FreeInterp(Jim_Interp *i)
4584 Jim_CallFrame *cf = i->framePtr, *prevcf, *nextcf;
4585 Jim_Obj *objPtr, *nextObjPtr;
4587 Jim_DecrRefCount(i, i->emptyObj);
4588 Jim_DecrRefCount(i, i->trueObj);
4589 Jim_DecrRefCount(i, i->falseObj);
4590 Jim_DecrRefCount(i, i->result);
4591 Jim_DecrRefCount(i, i->stackTrace);
4592 Jim_DecrRefCount(i, i->errorProc);
4593 Jim_DecrRefCount(i, i->unknown);
4594 Jim_Free((void *)i->errorFileName);
4595 Jim_DecrRefCount(i, i->currentScriptObj);
4596 Jim_FreeHashTable(&i->commands);
4597 #ifdef JIM_REFERENCES
4598 Jim_FreeHashTable(&i->references);
4599 #endif
4600 Jim_FreeHashTable(&i->assocData);
4601 Jim_FreeHashTable(&i->packages);
4602 Jim_Free(i->prngState);
4603 JimDeleteLocalProcs(i);
4605 /* Free the call frames list */
4606 while (cf) {
4607 prevcf = cf->parentCallFrame;
4608 JimFreeCallFrame(i, cf, JIM_FCF_NONE);
4609 cf = prevcf;
4611 /* Check that the live object list is empty, otherwise
4612 * there is a memory leak. */
4613 if (i->liveList != NULL) {
4614 objPtr = i->liveList;
4616 printf(JIM_NL "-------------------------------------" JIM_NL);
4617 printf("Objects still in the free list:" JIM_NL);
4618 while (objPtr) {
4619 const char *type = objPtr->typePtr ? objPtr->typePtr->name : "string";
4621 printf("%p (%d) %-10s: '%.20s'" JIM_NL,
4622 (void *)objPtr, objPtr->refCount, type, objPtr->bytes ? objPtr->bytes : "(null)");
4623 if (objPtr->typePtr == &sourceObjType) {
4624 printf("FILE %s LINE %d" JIM_NL,
4625 objPtr->internalRep.sourceValue.fileName,
4626 objPtr->internalRep.sourceValue.lineNumber);
4628 objPtr = objPtr->nextObjPtr;
4630 printf("-------------------------------------" JIM_NL JIM_NL);
4631 Jim_Panic(i, "Live list non empty freeing the interpreter! Leak?");
4633 /* Free all the freed objects. */
4634 objPtr = i->freeList;
4635 while (objPtr) {
4636 nextObjPtr = objPtr->nextObjPtr;
4637 Jim_Free(objPtr);
4638 objPtr = nextObjPtr;
4640 /* Free cached CallFrame structures */
4641 cf = i->freeFramesList;
4642 while (cf) {
4643 nextcf = cf->nextFramePtr;
4644 if (cf->vars.table != NULL)
4645 Jim_Free(cf->vars.table);
4646 Jim_Free(cf);
4647 cf = nextcf;
4649 /* Free the sharedString hash table. Make sure to free it
4650 * after every other Jim_Object was freed. */
4651 Jim_FreeHashTable(&i->sharedStrings);
4652 /* Free the interpreter structure. */
4653 Jim_Free(i);
4656 /* Store the call frame relative to the level represented by
4657 * levelObjPtr into *framePtrPtr. If levelObjPtr == NULL, the
4658 * level is assumed to be '1'.
4660 * If a newLevelptr int pointer is specified, the function stores
4661 * the absolute level integer value of the new target callframe into
4662 * *newLevelPtr. (this is used to adjust interp->numLevels
4663 * in the implementation of [uplevel], so that [info level] will
4664 * return a correct information).
4666 * This function accepts the 'level' argument in the form
4667 * of the commands [uplevel] and [upvar].
4669 * For a function accepting a relative integer as level suitable
4670 * for implementation of [info level ?level?] check the
4671 * GetCallFrameByInteger() function. */
4672 int Jim_GetCallFrameByLevel(Jim_Interp *interp, Jim_Obj *levelObjPtr,
4673 Jim_CallFrame **framePtrPtr, int *newLevelPtr)
4675 long level;
4676 const char *str;
4677 Jim_CallFrame *framePtr;
4679 if (newLevelPtr)
4680 *newLevelPtr = interp->numLevels;
4681 if (levelObjPtr) {
4682 str = Jim_GetString(levelObjPtr, NULL);
4683 if (str[0] == '#') {
4684 char *endptr;
4686 /* speedup for the toplevel (level #0) */
4687 if (str[1] == '0' && str[2] == '\0') {
4688 if (newLevelPtr)
4689 *newLevelPtr = 0;
4690 *framePtrPtr = interp->topFramePtr;
4691 return JIM_OK;
4694 level = strtol(str + 1, &endptr, 0);
4695 if (str[1] == '\0' || endptr[0] != '\0' || level < 0)
4696 goto badlevel;
4697 /* An 'absolute' level is converted into the
4698 * 'number of levels to go back' format. */
4699 level = interp->numLevels - level;
4700 if (level < 0)
4701 goto badlevel;
4703 else {
4704 if (Jim_GetLong(interp, levelObjPtr, &level) != JIM_OK || level < 0)
4705 goto badlevel;
4708 else {
4709 str = "1"; /* Needed to format the error message. */
4710 level = 1;
4712 /* Lookup */
4713 framePtr = interp->framePtr;
4714 if (newLevelPtr)
4715 *newLevelPtr = (*newLevelPtr) - level;
4716 while (level--) {
4717 framePtr = framePtr->parentCallFrame;
4718 if (framePtr == NULL)
4719 goto badlevel;
4721 *framePtrPtr = framePtr;
4722 return JIM_OK;
4723 badlevel:
4724 Jim_SetResultFormatted(interp, "bad level \"%s\"", str);
4725 return JIM_ERR;
4728 /* Similar to Jim_GetCallFrameByLevel() but the level is specified
4729 * as a relative integer like in the [info level ?level?] command. */
4730 static int JimGetCallFrameByInteger(Jim_Interp *interp, Jim_Obj *levelObjPtr,
4731 Jim_CallFrame **framePtrPtr)
4733 jim_wide level;
4734 jim_wide relLevel; /* level relative to the current one. */
4735 Jim_CallFrame *framePtr;
4737 if (Jim_GetWide(interp, levelObjPtr, &level) != JIM_OK)
4738 goto badlevel;
4739 if (level > 0) {
4740 /* An 'absolute' level is converted into the
4741 * 'number of levels to go back' format. */
4742 relLevel = interp->numLevels - level;
4744 else {
4745 relLevel = -level;
4747 /* Lookup */
4748 framePtr = interp->framePtr;
4749 while (relLevel--) {
4750 framePtr = framePtr->parentCallFrame;
4751 if (framePtr == NULL)
4752 goto badlevel;
4754 *framePtrPtr = framePtr;
4755 return JIM_OK;
4756 badlevel:
4757 Jim_SetResultFormatted(interp, "bad level \"%#s\"", levelObjPtr);
4758 return JIM_ERR;
4761 static void JimSetErrorFileName(Jim_Interp *interp, const char *filename)
4763 Jim_Free((void *)interp->errorFileName);
4764 interp->errorFileName = Jim_StrDup(filename);
4767 static void JimSetErrorLineNumber(Jim_Interp *interp, int linenr)
4769 interp->errorLine = linenr;
4772 static void JimResetStackTrace(Jim_Interp *interp)
4774 Jim_DecrRefCount(interp, interp->stackTrace);
4775 interp->stackTrace = Jim_NewListObj(interp, NULL, 0);
4776 Jim_IncrRefCount(interp->stackTrace);
4779 static void JimSetStackTrace(Jim_Interp *interp, Jim_Obj *stackTraceObj)
4781 int len;
4783 /* Increment reference first in case these are the same object */
4784 Jim_IncrRefCount(stackTraceObj);
4785 Jim_DecrRefCount(interp, interp->stackTrace);
4786 interp->stackTrace = stackTraceObj;
4787 interp->errorFlag = 1;
4789 /* This is a bit ugly.
4790 * If the filename of the last entry of the stack trace is empty,
4791 * the next stack level should be added.
4793 len = Jim_ListLength(interp, interp->stackTrace);
4794 if (len >= 3) {
4795 Jim_Obj *filenameObj;
4797 Jim_ListIndex(interp, interp->stackTrace, len - 2, &filenameObj, JIM_NONE);
4799 Jim_GetString(filenameObj, &len);
4801 if (len == 0) {
4802 interp->addStackTrace = 1;
4807 /* Returns 1 if the stack trace information was used or 0 if not */
4808 static void JimAppendStackTrace(Jim_Interp *interp, const char *procname,
4809 const char *filename, int linenr)
4811 if (strcmp(procname, "unknown") == 0) {
4812 procname = "";
4814 if (!*procname && !*filename) {
4815 /* No useful info here */
4816 return;
4819 if (Jim_IsShared(interp->stackTrace)) {
4820 Jim_DecrRefCount(interp, interp->stackTrace);
4821 interp->stackTrace = Jim_DuplicateObj(interp, interp->stackTrace);
4822 Jim_IncrRefCount(interp->stackTrace);
4825 /* If we have no procname but the previous element did, merge with that frame */
4826 if (!*procname && *filename) {
4827 /* Just a filename. Check the previous entry */
4828 int len = Jim_ListLength(interp, interp->stackTrace);
4830 if (len >= 3) {
4831 Jim_Obj *procnameObj;
4832 Jim_Obj *filenameObj;
4834 if (Jim_ListIndex(interp, interp->stackTrace, len - 3, &procnameObj, JIM_NONE) == JIM_OK
4835 && Jim_ListIndex(interp, interp->stackTrace, len - 2, &filenameObj,
4836 JIM_NONE) == JIM_OK) {
4838 const char *prev_procname = Jim_GetString(procnameObj, NULL);
4839 const char *prev_filename = Jim_GetString(filenameObj, NULL);
4841 if (*prev_procname && !*prev_filename) {
4842 ListSetIndex(interp, interp->stackTrace, len - 2, Jim_NewStringObj(interp,
4843 filename, -1), 0);
4844 ListSetIndex(interp, interp->stackTrace, len - 1, Jim_NewIntObj(interp, linenr),
4846 return;
4852 Jim_ListAppendElement(interp, interp->stackTrace, Jim_NewStringObj(interp, procname, -1));
4853 Jim_ListAppendElement(interp, interp->stackTrace, Jim_NewStringObj(interp, filename, -1));
4854 Jim_ListAppendElement(interp, interp->stackTrace, Jim_NewIntObj(interp, linenr));
4857 int Jim_SetAssocData(Jim_Interp *interp, const char *key, Jim_InterpDeleteProc * delProc,
4858 void *data)
4860 AssocDataValue *assocEntryPtr = (AssocDataValue *) Jim_Alloc(sizeof(AssocDataValue));
4862 assocEntryPtr->delProc = delProc;
4863 assocEntryPtr->data = data;
4864 return Jim_AddHashEntry(&interp->assocData, key, assocEntryPtr);
4867 void *Jim_GetAssocData(Jim_Interp *interp, const char *key)
4869 Jim_HashEntry *entryPtr = Jim_FindHashEntry(&interp->assocData, key);
4871 if (entryPtr != NULL) {
4872 AssocDataValue *assocEntryPtr = (AssocDataValue *) entryPtr->val;
4874 return assocEntryPtr->data;
4876 return NULL;
4879 int Jim_DeleteAssocData(Jim_Interp *interp, const char *key)
4881 return Jim_DeleteHashEntry(&interp->assocData, key);
4884 int Jim_GetExitCode(Jim_Interp *interp)
4886 return interp->exitCode;
4889 /* -----------------------------------------------------------------------------
4890 * Shared strings.
4891 * Every interpreter has an hash table where to put shared dynamically
4892 * allocate strings that are likely to be used a lot of times.
4893 * For example, in the 'source' object type, there is a pointer to
4894 * the filename associated with that object. Every script has a lot
4895 * of this objects with the identical file name, so it is wise to share
4896 * this info.
4898 * The API is trivial: Jim_GetSharedString(interp, "foobar")
4899 * returns the pointer to the shared string. Every time a reference
4900 * to the string is no longer used, the user should call
4901 * Jim_ReleaseSharedString(interp, stringPointer). Once no one is using
4902 * a given string, it is removed from the hash table.
4903 * ---------------------------------------------------------------------------*/
4904 const char *Jim_GetSharedString(Jim_Interp *interp, const char *str)
4906 Jim_HashEntry *he = Jim_FindHashEntry(&interp->sharedStrings, str);
4908 if (he == NULL) {
4909 char *strCopy = Jim_StrDup(str);
4911 Jim_AddHashEntry(&interp->sharedStrings, strCopy, (void *)1);
4912 return strCopy;
4914 else {
4915 long refCount = (long)he->val;
4917 refCount++;
4918 he->val = (void *)refCount;
4919 return he->key;
4923 void Jim_ReleaseSharedString(Jim_Interp *interp, const char *str)
4925 long refCount;
4926 Jim_HashEntry *he = Jim_FindHashEntry(&interp->sharedStrings, str);
4928 if (he == NULL) {
4929 Jim_Panic(interp, "Jim_ReleaseSharedString called with " "unknown shared string '%s'", str);
4931 else {
4932 refCount = (long)he->val;
4933 refCount--;
4934 if (refCount == 0) {
4935 Jim_DeleteHashEntry(&interp->sharedStrings, str);
4937 else {
4938 he->val = (void *)refCount;
4943 /* -----------------------------------------------------------------------------
4944 * Integer object
4945 * ---------------------------------------------------------------------------*/
4946 #define JIM_INTEGER_SPACE 24
4948 static void UpdateStringOfInt(struct Jim_Obj *objPtr);
4949 static int SetIntFromAny(Jim_Interp *interp, Jim_Obj *objPtr, int flags);
4951 static const Jim_ObjType intObjType = {
4952 "int",
4953 NULL,
4954 NULL,
4955 UpdateStringOfInt,
4956 JIM_TYPE_NONE,
4959 /* A coerced double is closer to an int than a double.
4960 * It is an int value temporarily masquerading as a double value.
4961 * i.e. it has the same string value as an int and Jim_GetWide()
4962 * succeeds, but also Jim_GetDouble() returns the value directly.
4964 static const Jim_ObjType coercedDoubleObjType = {
4965 "coerced-double",
4966 NULL,
4967 NULL,
4968 UpdateStringOfInt,
4969 JIM_TYPE_NONE,
4973 void UpdateStringOfInt(struct Jim_Obj *objPtr)
4975 int len;
4976 char buf[JIM_INTEGER_SPACE + 1];
4978 len = Jim_WideToString(buf, objPtr->internalRep.wideValue);
4979 objPtr->bytes = Jim_Alloc(len + 1);
4980 memcpy(objPtr->bytes, buf, len + 1);
4981 objPtr->length = len;
4984 int SetIntFromAny(Jim_Interp *interp, Jim_Obj *objPtr, int flags)
4986 jim_wide wideValue;
4987 const char *str;
4989 if (objPtr->typePtr == &coercedDoubleObjType) {
4990 /* Simple switcheroo */
4991 objPtr->typePtr = &intObjType;
4992 return JIM_OK;
4995 /* Get the string representation */
4996 str = Jim_GetString(objPtr, NULL);
4997 /* Try to convert into a jim_wide */
4998 if (Jim_StringToWide(str, &wideValue, 0) != JIM_OK) {
4999 if (flags & JIM_ERRMSG) {
5000 Jim_SetResultFormatted(interp, "expected integer but got \"%#s\"", objPtr);
5002 return JIM_ERR;
5004 if ((wideValue == JIM_WIDE_MIN || wideValue == JIM_WIDE_MAX) && errno == ERANGE) {
5005 Jim_SetResultString(interp, "Integer value too big to be represented", -1);
5006 return JIM_ERR;
5008 /* Free the old internal repr and set the new one. */
5009 Jim_FreeIntRep(interp, objPtr);
5010 objPtr->typePtr = &intObjType;
5011 objPtr->internalRep.wideValue = wideValue;
5012 return JIM_OK;
5015 #ifdef JIM_OPTIMIZATION
5016 static int Jim_IsWide(Jim_Obj *objPtr)
5018 return objPtr->typePtr == &intObjType;
5020 #endif
5022 int Jim_GetWide(Jim_Interp *interp, Jim_Obj *objPtr, jim_wide * widePtr)
5024 if (objPtr->typePtr != &intObjType && SetIntFromAny(interp, objPtr, JIM_ERRMSG) == JIM_ERR)
5025 return JIM_ERR;
5026 *widePtr = JimWideValue(objPtr);
5027 return JIM_OK;
5030 /* Get a wide but does not set an error if the format is bad. */
5031 static int JimGetWideNoErr(Jim_Interp *interp, Jim_Obj *objPtr, jim_wide * widePtr)
5033 if (objPtr->typePtr != &intObjType && SetIntFromAny(interp, objPtr, JIM_NONE) == JIM_ERR)
5034 return JIM_ERR;
5035 *widePtr = JimWideValue(objPtr);
5036 return JIM_OK;
5039 int Jim_GetLong(Jim_Interp *interp, Jim_Obj *objPtr, long *longPtr)
5041 jim_wide wideValue;
5042 int retval;
5044 retval = Jim_GetWide(interp, objPtr, &wideValue);
5045 if (retval == JIM_OK) {
5046 *longPtr = (long)wideValue;
5047 return JIM_OK;
5049 return JIM_ERR;
5052 void Jim_SetWide(Jim_Interp *interp, Jim_Obj *objPtr, jim_wide wideValue)
5054 if (Jim_IsShared(objPtr))
5055 Jim_Panic(interp, "Jim_SetWide called with shared object");
5056 if (objPtr->typePtr != &intObjType) {
5057 Jim_FreeIntRep(interp, objPtr);
5058 objPtr->typePtr = &intObjType;
5060 Jim_InvalidateStringRep(objPtr);
5061 objPtr->internalRep.wideValue = wideValue;
5064 Jim_Obj *Jim_NewIntObj(Jim_Interp *interp, jim_wide wideValue)
5066 Jim_Obj *objPtr;
5068 objPtr = Jim_NewObj(interp);
5069 objPtr->typePtr = &intObjType;
5070 objPtr->bytes = NULL;
5071 objPtr->internalRep.wideValue = wideValue;
5072 return objPtr;
5075 /* -----------------------------------------------------------------------------
5076 * Double object
5077 * ---------------------------------------------------------------------------*/
5078 #define JIM_DOUBLE_SPACE 30
5080 static void UpdateStringOfDouble(struct Jim_Obj *objPtr);
5081 static int SetDoubleFromAny(Jim_Interp *interp, Jim_Obj *objPtr);
5083 static const Jim_ObjType doubleObjType = {
5084 "double",
5085 NULL,
5086 NULL,
5087 UpdateStringOfDouble,
5088 JIM_TYPE_NONE,
5091 void UpdateStringOfDouble(struct Jim_Obj *objPtr)
5093 int len;
5094 char buf[JIM_DOUBLE_SPACE + 1];
5096 len = Jim_DoubleToString(buf, objPtr->internalRep.doubleValue);
5097 objPtr->bytes = Jim_Alloc(len + 1);
5098 memcpy(objPtr->bytes, buf, len + 1);
5099 objPtr->length = len;
5102 int SetDoubleFromAny(Jim_Interp *interp, Jim_Obj *objPtr)
5104 double doubleValue;
5105 jim_wide wideValue;
5106 const char *str;
5108 /* Preserve the string representation.
5109 * Needed so we can convert back to int without loss
5111 str = Jim_GetString(objPtr, NULL);
5113 #ifdef HAVE_LONG_LONG
5114 /* Assume a 53 bit mantissa */
5115 #define MIN_INT_IN_DOUBLE -(1LL << 53)
5116 #define MAX_INT_IN_DOUBLE -(MIN_INT_IN_DOUBLE + 1)
5118 if (objPtr->typePtr == &intObjType
5119 && JimWideValue(objPtr) >= MIN_INT_IN_DOUBLE
5120 && JimWideValue(objPtr) <= MAX_INT_IN_DOUBLE) {
5122 /* Direct conversion to coerced double */
5123 objPtr->typePtr = &coercedDoubleObjType;
5124 return JIM_OK;
5126 else
5127 #endif
5128 if (Jim_StringToWide(str, &wideValue, 10) == JIM_OK) {
5129 /* Managed to convert to an int, so we can use this as a cooerced double */
5130 Jim_FreeIntRep(interp, objPtr);
5131 objPtr->typePtr = &coercedDoubleObjType;
5132 objPtr->internalRep.wideValue = wideValue;
5133 return JIM_OK;
5135 else {
5136 /* Try to convert into a double */
5137 if (Jim_StringToDouble(str, &doubleValue) != JIM_OK) {
5138 Jim_SetResultFormatted(interp, "expected number but got \"%#s\"", objPtr);
5139 return JIM_ERR;
5141 /* Free the old internal repr and set the new one. */
5142 Jim_FreeIntRep(interp, objPtr);
5144 objPtr->typePtr = &doubleObjType;
5145 objPtr->internalRep.doubleValue = doubleValue;
5146 return JIM_OK;
5149 int Jim_GetDouble(Jim_Interp *interp, Jim_Obj *objPtr, double *doublePtr)
5151 if (objPtr->typePtr == &coercedDoubleObjType) {
5152 *doublePtr = JimWideValue(objPtr);
5153 return JIM_OK;
5155 if (objPtr->typePtr != &doubleObjType && SetDoubleFromAny(interp, objPtr) == JIM_ERR)
5156 return JIM_ERR;
5158 if (objPtr->typePtr == &coercedDoubleObjType) {
5159 *doublePtr = JimWideValue(objPtr);
5161 else {
5162 *doublePtr = objPtr->internalRep.doubleValue;
5164 return JIM_OK;
5167 Jim_Obj *Jim_NewDoubleObj(Jim_Interp *interp, double doubleValue)
5169 Jim_Obj *objPtr;
5171 objPtr = Jim_NewObj(interp);
5172 objPtr->typePtr = &doubleObjType;
5173 objPtr->bytes = NULL;
5174 objPtr->internalRep.doubleValue = doubleValue;
5175 return objPtr;
5178 /* -----------------------------------------------------------------------------
5179 * List object
5180 * ---------------------------------------------------------------------------*/
5181 static void ListAppendElement(Jim_Obj *listPtr, Jim_Obj *objPtr);
5182 static void FreeListInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
5183 static void DupListInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
5184 static void UpdateStringOfList(struct Jim_Obj *objPtr);
5185 static int SetListFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
5187 /* Note that while the elements of the list may contain references,
5188 * the list object itself can't. This basically means that the
5189 * list object string representation as a whole can't contain references
5190 * that are not presents in the single elements. */
5191 static const Jim_ObjType listObjType = {
5192 "list",
5193 FreeListInternalRep,
5194 DupListInternalRep,
5195 UpdateStringOfList,
5196 JIM_TYPE_NONE,
5199 void FreeListInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
5201 int i;
5203 for (i = 0; i < objPtr->internalRep.listValue.len; i++) {
5204 Jim_DecrRefCount(interp, objPtr->internalRep.listValue.ele[i]);
5206 Jim_Free(objPtr->internalRep.listValue.ele);
5209 void DupListInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
5211 int i;
5213 JIM_NOTUSED(interp);
5215 dupPtr->internalRep.listValue.len = srcPtr->internalRep.listValue.len;
5216 dupPtr->internalRep.listValue.maxLen = srcPtr->internalRep.listValue.maxLen;
5217 dupPtr->internalRep.listValue.ele =
5218 Jim_Alloc(sizeof(Jim_Obj *) * srcPtr->internalRep.listValue.maxLen);
5219 memcpy(dupPtr->internalRep.listValue.ele, srcPtr->internalRep.listValue.ele,
5220 sizeof(Jim_Obj *) * srcPtr->internalRep.listValue.len);
5221 for (i = 0; i < dupPtr->internalRep.listValue.len; i++) {
5222 Jim_IncrRefCount(dupPtr->internalRep.listValue.ele[i]);
5224 dupPtr->typePtr = &listObjType;
5227 /* The following function checks if a given string can be encoded
5228 * into a list element without any kind of quoting, surrounded by braces,
5229 * or using escapes to quote. */
5230 #define JIM_ELESTR_SIMPLE 0
5231 #define JIM_ELESTR_BRACE 1
5232 #define JIM_ELESTR_QUOTE 2
5233 static int ListElementQuotingType(const char *s, int len)
5235 int i, level, trySimple = 1;
5237 /* Try with the SIMPLE case */
5238 if (len == 0)
5239 return JIM_ELESTR_BRACE;
5240 if (s[0] == '#')
5241 return JIM_ELESTR_BRACE;
5242 if (s[0] == '"' || s[0] == '{') {
5243 trySimple = 0;
5244 goto testbrace;
5246 for (i = 0; i < len; i++) {
5247 switch (s[i]) {
5248 case ' ':
5249 case '$':
5250 case '"':
5251 case '[':
5252 case ']':
5253 case ';':
5254 case '\\':
5255 case '\r':
5256 case '\n':
5257 case '\t':
5258 case '\f':
5259 case '\v':
5260 trySimple = 0;
5261 case '{':
5262 case '}':
5263 goto testbrace;
5266 return JIM_ELESTR_SIMPLE;
5268 testbrace:
5269 /* Test if it's possible to do with braces */
5270 if (s[len - 1] == '\\' || s[len - 1] == ']')
5271 return JIM_ELESTR_QUOTE;
5272 level = 0;
5273 for (i = 0; i < len; i++) {
5274 switch (s[i]) {
5275 case '{':
5276 level++;
5277 break;
5278 case '}':
5279 level--;
5280 if (level < 0)
5281 return JIM_ELESTR_QUOTE;
5282 break;
5283 case '\\':
5284 if (s[i + 1] == '\n')
5285 return JIM_ELESTR_QUOTE;
5286 else if (s[i + 1] != '\0')
5287 i++;
5288 break;
5291 if (level == 0) {
5292 if (!trySimple)
5293 return JIM_ELESTR_BRACE;
5294 for (i = 0; i < len; i++) {
5295 switch (s[i]) {
5296 case ' ':
5297 case '$':
5298 case '"':
5299 case '[':
5300 case ']':
5301 case ';':
5302 case '\\':
5303 case '\r':
5304 case '\n':
5305 case '\t':
5306 case '\f':
5307 case '\v':
5308 return JIM_ELESTR_BRACE;
5309 break;
5312 return JIM_ELESTR_SIMPLE;
5314 return JIM_ELESTR_QUOTE;
5317 /* Returns the malloc-ed representation of a string
5318 * using backslash to quote special chars. */
5319 static char *BackslashQuoteString(const char *s, int len, int *qlenPtr)
5321 char *q = Jim_Alloc(len * 2 + 1), *p;
5323 p = q;
5324 while (*s) {
5325 switch (*s) {
5326 case ' ':
5327 case '$':
5328 case '"':
5329 case '[':
5330 case ']':
5331 case '{':
5332 case '}':
5333 case ';':
5334 case '\\':
5335 *p++ = '\\';
5336 *p++ = *s++;
5337 break;
5338 case '\n':
5339 *p++ = '\\';
5340 *p++ = 'n';
5341 s++;
5342 break;
5343 case '\r':
5344 *p++ = '\\';
5345 *p++ = 'r';
5346 s++;
5347 break;
5348 case '\t':
5349 *p++ = '\\';
5350 *p++ = 't';
5351 s++;
5352 break;
5353 case '\f':
5354 *p++ = '\\';
5355 *p++ = 'f';
5356 s++;
5357 break;
5358 case '\v':
5359 *p++ = '\\';
5360 *p++ = 'v';
5361 s++;
5362 break;
5363 default:
5364 *p++ = *s++;
5365 break;
5368 *p = '\0';
5369 *qlenPtr = p - q;
5370 return q;
5373 void UpdateStringOfList(struct Jim_Obj *objPtr)
5375 int i, bufLen, realLength;
5376 const char *strRep;
5377 char *p;
5378 int *quotingType;
5379 Jim_Obj **ele = objPtr->internalRep.listValue.ele;
5381 /* (Over) Estimate the space needed. */
5382 quotingType = Jim_Alloc(sizeof(int) * objPtr->internalRep.listValue.len + 1);
5383 bufLen = 0;
5384 for (i = 0; i < objPtr->internalRep.listValue.len; i++) {
5385 int len;
5387 strRep = Jim_GetString(ele[i], &len);
5388 quotingType[i] = ListElementQuotingType(strRep, len);
5389 switch (quotingType[i]) {
5390 case JIM_ELESTR_SIMPLE:
5391 bufLen += len;
5392 break;
5393 case JIM_ELESTR_BRACE:
5394 bufLen += len + 2;
5395 break;
5396 case JIM_ELESTR_QUOTE:
5397 bufLen += len * 2;
5398 break;
5400 bufLen++; /* elements separator. */
5402 bufLen++;
5404 /* Generate the string rep. */
5405 p = objPtr->bytes = Jim_Alloc(bufLen + 1);
5406 realLength = 0;
5407 for (i = 0; i < objPtr->internalRep.listValue.len; i++) {
5408 int len, qlen;
5409 char *q;
5411 strRep = Jim_GetString(ele[i], &len);
5413 switch (quotingType[i]) {
5414 case JIM_ELESTR_SIMPLE:
5415 memcpy(p, strRep, len);
5416 p += len;
5417 realLength += len;
5418 break;
5419 case JIM_ELESTR_BRACE:
5420 *p++ = '{';
5421 memcpy(p, strRep, len);
5422 p += len;
5423 *p++ = '}';
5424 realLength += len + 2;
5425 break;
5426 case JIM_ELESTR_QUOTE:
5427 q = BackslashQuoteString(strRep, len, &qlen);
5428 memcpy(p, q, qlen);
5429 Jim_Free(q);
5430 p += qlen;
5431 realLength += qlen;
5432 break;
5434 /* Add a separating space */
5435 if (i + 1 != objPtr->internalRep.listValue.len) {
5436 *p++ = ' ';
5437 realLength++;
5440 *p = '\0'; /* nul term. */
5441 objPtr->length = realLength;
5442 Jim_Free(quotingType);
5445 int SetListFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
5447 struct JimParserCtx parser;
5448 const char *str;
5449 int strLen;
5450 const char *filename = NULL;
5451 int linenr = 1;
5453 /* Try to preserve information about filename / line number */
5454 if (objPtr->typePtr == &sourceObjType) {
5455 filename = Jim_GetSharedString(interp, objPtr->internalRep.sourceValue.fileName);
5456 linenr = objPtr->internalRep.sourceValue.lineNumber;
5459 /* Get the string representation */
5460 str = Jim_GetString(objPtr, &strLen);
5462 /* Free the old internal repr just now and initialize the
5463 * new one just now. The string->list conversion can't fail. */
5464 Jim_FreeIntRep(interp, objPtr);
5465 objPtr->typePtr = &listObjType;
5466 objPtr->internalRep.listValue.len = 0;
5467 objPtr->internalRep.listValue.maxLen = 0;
5468 objPtr->internalRep.listValue.ele = NULL;
5470 /* Convert into a list */
5471 JimParserInit(&parser, str, strLen, linenr);
5472 while (!JimParserEof(&parser)) {
5473 Jim_Obj *elementPtr;
5475 JimParseList(&parser);
5476 if (JimParserTtype(&parser) != JIM_TT_STR && JimParserTtype(&parser) != JIM_TT_ESC)
5477 continue;
5478 elementPtr = JimParserGetTokenObj(interp, &parser);
5479 if (filename) {
5480 JimSetSourceInfo(interp, elementPtr, filename, JimParserTline(&parser));
5482 ListAppendElement(objPtr, elementPtr);
5484 if (filename) {
5485 Jim_ReleaseSharedString(interp, filename);
5487 return JIM_OK;
5490 Jim_Obj *Jim_NewListObj(Jim_Interp *interp, Jim_Obj *const *elements, int len)
5492 Jim_Obj *objPtr;
5493 int i;
5495 objPtr = Jim_NewObj(interp);
5496 objPtr->typePtr = &listObjType;
5497 objPtr->bytes = NULL;
5498 objPtr->internalRep.listValue.ele = NULL;
5499 objPtr->internalRep.listValue.len = 0;
5500 objPtr->internalRep.listValue.maxLen = 0;
5501 for (i = 0; i < len; i++) {
5502 ListAppendElement(objPtr, elements[i]);
5504 return objPtr;
5507 /* Return a vector of Jim_Obj with the elements of a Jim list, and the
5508 * length of the vector. Note that the user of this function should make
5509 * sure that the list object can't shimmer while the vector returned
5510 * is in use, this vector is the one stored inside the internal representation
5511 * of the list object. This function is not exported, extensions should
5512 * always access to the List object elements using Jim_ListIndex(). */
5513 static void JimListGetElements(Jim_Interp *interp, Jim_Obj *listObj, int *listLen,
5514 Jim_Obj ***listVec)
5516 *listLen = Jim_ListLength(interp, listObj);
5517 *listVec = listObj->internalRep.listValue.ele;
5520 /* Sorting uses ints, but commands may return wide */
5521 static int JimSign(jim_wide w)
5523 if (w == 0) {
5524 return 0;
5526 else if (w < 0) {
5527 return -1;
5529 return 1;
5532 /* ListSortElements type values */
5533 struct lsort_info {
5534 jmp_buf jmpbuf;
5535 Jim_Obj *command;
5536 Jim_Interp *interp;
5537 enum {
5538 JIM_LSORT_ASCII,
5539 JIM_LSORT_NOCASE,
5540 JIM_LSORT_INTEGER,
5541 JIM_LSORT_COMMAND
5542 } type;
5543 int order;
5544 int index;
5545 int indexed;
5546 int (*subfn)(Jim_Obj **, Jim_Obj **);
5549 static struct lsort_info *sort_info;
5551 static int ListSortIndexHelper(Jim_Obj **lhsObj, Jim_Obj **rhsObj)
5553 Jim_Obj *lObj, *rObj;
5555 if (Jim_ListIndex(sort_info->interp, *lhsObj, sort_info->index, &lObj, JIM_ERRMSG) != JIM_OK ||
5556 Jim_ListIndex(sort_info->interp, *rhsObj, sort_info->index, &rObj, JIM_ERRMSG) != JIM_OK) {
5557 longjmp(sort_info->jmpbuf, JIM_ERR);
5559 return sort_info->subfn(&lObj, &rObj);
5562 /* Sort the internal rep of a list. */
5563 static int ListSortString(Jim_Obj **lhsObj, Jim_Obj **rhsObj)
5565 return Jim_StringCompareObj(sort_info->interp, *lhsObj, *rhsObj, 0) * sort_info->order;
5568 static int ListSortStringNoCase(Jim_Obj **lhsObj, Jim_Obj **rhsObj)
5570 return Jim_StringCompareObj(sort_info->interp, *lhsObj, *rhsObj, 1) * sort_info->order;
5573 static int ListSortInteger(Jim_Obj **lhsObj, Jim_Obj **rhsObj)
5575 jim_wide lhs = 0, rhs = 0;
5577 if (Jim_GetWide(sort_info->interp, *lhsObj, &lhs) != JIM_OK ||
5578 Jim_GetWide(sort_info->interp, *rhsObj, &rhs) != JIM_OK) {
5579 longjmp(sort_info->jmpbuf, JIM_ERR);
5582 return JimSign(lhs - rhs) * sort_info->order;
5585 static int ListSortCommand(Jim_Obj **lhsObj, Jim_Obj **rhsObj)
5587 Jim_Obj *compare_script;
5588 int rc;
5590 jim_wide ret = 0;
5592 /* This must be a valid list */
5593 compare_script = Jim_DuplicateObj(sort_info->interp, sort_info->command);
5594 Jim_ListAppendElement(sort_info->interp, compare_script, *lhsObj);
5595 Jim_ListAppendElement(sort_info->interp, compare_script, *rhsObj);
5597 rc = Jim_EvalObj(sort_info->interp, compare_script);
5599 if (rc != JIM_OK || Jim_GetWide(sort_info->interp, Jim_GetResult(sort_info->interp), &ret) != JIM_OK) {
5600 longjmp(sort_info->jmpbuf, rc);
5603 return JimSign(ret) * sort_info->order;
5606 /* Sort a list *in place*. MUST be called with non-shared objects. */
5607 static int ListSortElements(Jim_Interp *interp, Jim_Obj *listObjPtr, struct lsort_info *info)
5609 struct lsort_info *prev_info;
5611 typedef int (qsort_comparator) (const void *, const void *);
5612 int (*fn) (Jim_Obj **, Jim_Obj **);
5613 Jim_Obj **vector;
5614 int len;
5615 int rc;
5617 if (Jim_IsShared(listObjPtr))
5618 Jim_Panic(interp, "Jim_ListSortElements called with shared object");
5619 if (!Jim_IsList(listObjPtr))
5620 SetListFromAny(interp, listObjPtr);
5622 /* Allow lsort to be called reentrantly */
5623 prev_info = sort_info;
5624 sort_info = info;
5626 vector = listObjPtr->internalRep.listValue.ele;
5627 len = listObjPtr->internalRep.listValue.len;
5628 switch (info->type) {
5629 case JIM_LSORT_ASCII:
5630 fn = ListSortString;
5631 break;
5632 case JIM_LSORT_NOCASE:
5633 fn = ListSortStringNoCase;
5634 break;
5635 case JIM_LSORT_INTEGER:
5636 fn = ListSortInteger;
5637 break;
5638 case JIM_LSORT_COMMAND:
5639 fn = ListSortCommand;
5640 break;
5641 default:
5642 fn = NULL; /* avoid warning */
5643 Jim_Panic(interp, "ListSort called with invalid sort type");
5646 if (info->indexed) {
5647 /* Need to interpose a "list index" function */
5648 info->subfn = fn;
5649 fn = ListSortIndexHelper;
5652 if ((rc = setjmp(info->jmpbuf)) == 0) {
5653 qsort(vector, len, sizeof(Jim_Obj *), (qsort_comparator *) fn);
5655 Jim_InvalidateStringRep(listObjPtr);
5656 sort_info = prev_info;
5658 return rc;
5661 /* This is the low-level function to append an element to a list.
5662 * The higher-level Jim_ListAppendElement() performs shared object
5663 * check and invalidate the string repr. This version is used
5664 * in the internals of the List Object and is not exported.
5666 * NOTE: this function can be called only against objects
5667 * with internal type of List. */
5668 void ListAppendElement(Jim_Obj *listPtr, Jim_Obj *objPtr)
5670 int requiredLen = listPtr->internalRep.listValue.len + 1;
5672 if (requiredLen > listPtr->internalRep.listValue.maxLen) {
5673 int maxLen = requiredLen * 2;
5675 listPtr->internalRep.listValue.ele =
5676 Jim_Realloc(listPtr->internalRep.listValue.ele, sizeof(Jim_Obj *) * maxLen);
5677 listPtr->internalRep.listValue.maxLen = maxLen;
5679 listPtr->internalRep.listValue.ele[listPtr->internalRep.listValue.len] = objPtr;
5680 listPtr->internalRep.listValue.len++;
5681 Jim_IncrRefCount(objPtr);
5684 /* This is the low-level function to insert elements into a list.
5685 * The higher-level Jim_ListInsertElements() performs shared object
5686 * check and invalidate the string repr. This version is used
5687 * in the internals of the List Object and is not exported.
5689 * NOTE: this function can be called only against objects
5690 * with internal type of List. */
5691 static void ListInsertElements(Jim_Obj *listPtr, int idx, int elemc, Jim_Obj *const *elemVec)
5693 int currentLen = listPtr->internalRep.listValue.len;
5694 int requiredLen = currentLen + elemc;
5695 int i;
5696 Jim_Obj **point;
5698 if (requiredLen > listPtr->internalRep.listValue.maxLen) {
5699 int maxLen = requiredLen * 2;
5701 listPtr->internalRep.listValue.ele =
5702 Jim_Realloc(listPtr->internalRep.listValue.ele, sizeof(Jim_Obj *) * maxLen);
5703 listPtr->internalRep.listValue.maxLen = maxLen;
5705 point = listPtr->internalRep.listValue.ele + idx;
5706 memmove(point + elemc, point, (currentLen - idx) * sizeof(Jim_Obj *));
5707 for (i = 0; i < elemc; ++i) {
5708 point[i] = elemVec[i];
5709 Jim_IncrRefCount(point[i]);
5711 listPtr->internalRep.listValue.len += elemc;
5714 /* Appends every element of appendListPtr into listPtr.
5715 * Both have to be of the list type. */
5716 static void ListAppendList(Jim_Obj *listPtr, Jim_Obj *appendListPtr)
5718 int i, oldLen = listPtr->internalRep.listValue.len;
5719 int appendLen = appendListPtr->internalRep.listValue.len;
5720 int requiredLen = oldLen + appendLen;
5722 if (requiredLen > listPtr->internalRep.listValue.maxLen) {
5723 int maxLen = requiredLen * 2;
5725 listPtr->internalRep.listValue.ele =
5726 Jim_Realloc(listPtr->internalRep.listValue.ele, sizeof(Jim_Obj *) * maxLen);
5727 listPtr->internalRep.listValue.maxLen = maxLen;
5729 for (i = 0; i < appendLen; i++) {
5730 Jim_Obj *objPtr = appendListPtr->internalRep.listValue.ele[i];
5732 listPtr->internalRep.listValue.ele[oldLen + i] = objPtr;
5733 Jim_IncrRefCount(objPtr);
5735 listPtr->internalRep.listValue.len += appendLen;
5738 void Jim_ListAppendElement(Jim_Interp *interp, Jim_Obj *listPtr, Jim_Obj *objPtr)
5740 if (Jim_IsShared(listPtr))
5741 Jim_Panic(interp, "Jim_ListAppendElement called with shared object");
5742 if (!Jim_IsList(listPtr))
5743 SetListFromAny(interp, listPtr);
5744 Jim_InvalidateStringRep(listPtr);
5745 ListAppendElement(listPtr, objPtr);
5748 void Jim_ListAppendList(Jim_Interp *interp, Jim_Obj *listPtr, Jim_Obj *appendListPtr)
5750 if (Jim_IsShared(listPtr))
5751 Jim_Panic(interp, "Jim_ListAppendList called with shared object");
5752 if (!Jim_IsList(listPtr))
5753 SetListFromAny(interp, listPtr);
5754 Jim_InvalidateStringRep(listPtr);
5755 ListAppendList(listPtr, appendListPtr);
5758 int Jim_ListLength(Jim_Interp *interp, Jim_Obj *objPtr)
5760 if (!Jim_IsList(objPtr))
5761 SetListFromAny(interp, objPtr);
5762 return objPtr->internalRep.listValue.len;
5765 void Jim_ListInsertElements(Jim_Interp *interp, Jim_Obj *listPtr, int idx,
5766 int objc, Jim_Obj *const *objVec)
5768 if (Jim_IsShared(listPtr))
5769 Jim_Panic(interp, "Jim_ListInsertElement called with shared object");
5770 if (!Jim_IsList(listPtr))
5771 SetListFromAny(interp, listPtr);
5772 if (idx >= 0 && idx > listPtr->internalRep.listValue.len)
5773 idx = listPtr->internalRep.listValue.len;
5774 else if (idx < 0)
5775 idx = 0;
5776 Jim_InvalidateStringRep(listPtr);
5777 ListInsertElements(listPtr, idx, objc, objVec);
5780 int Jim_ListIndex(Jim_Interp *interp, Jim_Obj *listPtr, int idx, Jim_Obj **objPtrPtr, int flags)
5782 if (!Jim_IsList(listPtr))
5783 SetListFromAny(interp, listPtr);
5784 if ((idx >= 0 && idx >= listPtr->internalRep.listValue.len) ||
5785 (idx < 0 && (-idx - 1) >= listPtr->internalRep.listValue.len)) {
5786 if (flags & JIM_ERRMSG) {
5787 Jim_SetResultString(interp, "list index out of range", -1);
5789 *objPtrPtr = NULL;
5790 return JIM_ERR;
5792 if (idx < 0)
5793 idx = listPtr->internalRep.listValue.len + idx;
5794 *objPtrPtr = listPtr->internalRep.listValue.ele[idx];
5795 return JIM_OK;
5798 static int ListSetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int idx,
5799 Jim_Obj *newObjPtr, int flags)
5801 if (!Jim_IsList(listPtr))
5802 SetListFromAny(interp, listPtr);
5803 if ((idx >= 0 && idx >= listPtr->internalRep.listValue.len) ||
5804 (idx < 0 && (-idx - 1) >= listPtr->internalRep.listValue.len)) {
5805 if (flags & JIM_ERRMSG) {
5806 Jim_SetResultString(interp, "list index out of range", -1);
5808 return JIM_ERR;
5810 if (idx < 0)
5811 idx = listPtr->internalRep.listValue.len + idx;
5812 Jim_DecrRefCount(interp, listPtr->internalRep.listValue.ele[idx]);
5813 listPtr->internalRep.listValue.ele[idx] = newObjPtr;
5814 Jim_IncrRefCount(newObjPtr);
5815 return JIM_OK;
5818 /* Modify the list stored into the variable named 'varNamePtr'
5819 * setting the element specified by the 'indexc' indexes objects in 'indexv',
5820 * with the new element 'newObjptr'. */
5821 int Jim_SetListIndex(Jim_Interp *interp, Jim_Obj *varNamePtr,
5822 Jim_Obj *const *indexv, int indexc, Jim_Obj *newObjPtr)
5824 Jim_Obj *varObjPtr, *objPtr, *listObjPtr;
5825 int shared, i, idx;
5827 varObjPtr = objPtr = Jim_GetVariable(interp, varNamePtr, JIM_ERRMSG | JIM_UNSHARED);
5828 if (objPtr == NULL)
5829 return JIM_ERR;
5830 if ((shared = Jim_IsShared(objPtr)))
5831 varObjPtr = objPtr = Jim_DuplicateObj(interp, objPtr);
5832 for (i = 0; i < indexc - 1; i++) {
5833 listObjPtr = objPtr;
5834 if (Jim_GetIndex(interp, indexv[i], &idx) != JIM_OK)
5835 goto err;
5836 if (Jim_ListIndex(interp, listObjPtr, idx, &objPtr, JIM_ERRMSG) != JIM_OK) {
5837 goto err;
5839 if (Jim_IsShared(objPtr)) {
5840 objPtr = Jim_DuplicateObj(interp, objPtr);
5841 ListSetIndex(interp, listObjPtr, idx, objPtr, JIM_NONE);
5843 Jim_InvalidateStringRep(listObjPtr);
5845 if (Jim_GetIndex(interp, indexv[indexc - 1], &idx) != JIM_OK)
5846 goto err;
5847 if (ListSetIndex(interp, objPtr, idx, newObjPtr, JIM_ERRMSG) == JIM_ERR)
5848 goto err;
5849 Jim_InvalidateStringRep(objPtr);
5850 Jim_InvalidateStringRep(varObjPtr);
5851 if (Jim_SetVariable(interp, varNamePtr, varObjPtr) != JIM_OK)
5852 goto err;
5853 Jim_SetResult(interp, varObjPtr);
5854 return JIM_OK;
5855 err:
5856 if (shared) {
5857 Jim_FreeNewObj(interp, varObjPtr);
5859 return JIM_ERR;
5862 Jim_Obj *Jim_ConcatObj(Jim_Interp *interp, int objc, Jim_Obj *const *objv)
5864 int i;
5866 /* If all the objects in objv are lists,
5867 * it's possible to return a list as result, that's the
5868 * concatenation of all the lists. */
5869 for (i = 0; i < objc; i++) {
5870 if (!Jim_IsList(objv[i]))
5871 break;
5873 if (i == objc) {
5874 Jim_Obj *objPtr = Jim_NewListObj(interp, NULL, 0);
5876 for (i = 0; i < objc; i++)
5877 Jim_ListAppendList(interp, objPtr, objv[i]);
5878 return objPtr;
5880 else {
5881 /* Else... we have to glue strings together */
5882 int len = 0, objLen;
5883 char *bytes, *p;
5885 /* Compute the length */
5886 for (i = 0; i < objc; i++) {
5887 Jim_GetString(objv[i], &objLen);
5888 len += objLen;
5890 if (objc)
5891 len += objc - 1;
5892 /* Create the string rep, and a string object holding it. */
5893 p = bytes = Jim_Alloc(len + 1);
5894 for (i = 0; i < objc; i++) {
5895 const char *s = Jim_GetString(objv[i], &objLen);
5897 /* Remove leading space */
5898 while (objLen && (*s == ' ' || *s == '\t' || *s == '\n')) {
5899 s++;
5900 objLen--;
5901 len--;
5903 /* And trailing space */
5904 while (objLen && (s[objLen - 1] == ' ' ||
5905 s[objLen - 1] == '\n' || s[objLen - 1] == '\t')) {
5906 /* Handle trailing backslash-space case */
5907 if (objLen > 1 && s[objLen - 2] == '\\') {
5908 break;
5910 objLen--;
5911 len--;
5913 memcpy(p, s, objLen);
5914 p += objLen;
5915 if (objLen && i + 1 != objc) {
5916 *p++ = ' ';
5918 else if (i + 1 != objc) {
5919 /* Drop the space calcuated for this
5920 * element that is instead null. */
5921 len--;
5924 *p = '\0';
5925 return Jim_NewStringObjNoAlloc(interp, bytes, len);
5929 /* Returns a list composed of the elements in the specified range.
5930 * first and start are directly accepted as Jim_Objects and
5931 * processed for the end?-index? case. */
5932 Jim_Obj *Jim_ListRange(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_Obj *firstObjPtr,
5933 Jim_Obj *lastObjPtr)
5935 int first, last;
5936 int len, rangeLen;
5938 if (Jim_GetIndex(interp, firstObjPtr, &first) != JIM_OK ||
5939 Jim_GetIndex(interp, lastObjPtr, &last) != JIM_OK)
5940 return NULL;
5941 len = Jim_ListLength(interp, listObjPtr); /* will convert into list */
5942 first = JimRelToAbsIndex(len, first);
5943 last = JimRelToAbsIndex(len, last);
5944 JimRelToAbsRange(len, first, last, &first, &last, &rangeLen);
5945 return Jim_NewListObj(interp, listObjPtr->internalRep.listValue.ele + first, rangeLen);
5948 /* -----------------------------------------------------------------------------
5949 * Dict object
5950 * ---------------------------------------------------------------------------*/
5951 static void FreeDictInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
5952 static void DupDictInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
5953 static void UpdateStringOfDict(struct Jim_Obj *objPtr);
5954 static int SetDictFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
5956 /* Dict HashTable Type.
5958 * Keys and Values are Jim objects. */
5960 static unsigned int JimObjectHTHashFunction(const void *key)
5962 const char *str;
5963 Jim_Obj *objPtr = (Jim_Obj *)key;
5964 int len, h;
5966 str = Jim_GetString(objPtr, &len);
5967 h = Jim_GenHashFunction((unsigned char *)str, len);
5968 return h;
5971 static int JimObjectHTKeyCompare(void *privdata, const void *key1, const void *key2)
5973 JIM_NOTUSED(privdata);
5975 return Jim_StringEqObj((Jim_Obj *)key1, (Jim_Obj *)key2);
5978 static void JimObjectHTKeyValDestructor(void *interp, void *val)
5980 Jim_Obj *objPtr = val;
5982 Jim_DecrRefCount(interp, objPtr);
5985 static const Jim_HashTableType JimDictHashTableType = {
5986 JimObjectHTHashFunction, /* hash function */
5987 NULL, /* key dup */
5988 NULL, /* val dup */
5989 JimObjectHTKeyCompare, /* key compare */
5990 (void (*)(void *, const void *)) /* ATTENTION: const cast */
5991 JimObjectHTKeyValDestructor, /* key destructor */
5992 JimObjectHTKeyValDestructor /* val destructor */
5995 /* Note that while the elements of the dict may contain references,
5996 * the list object itself can't. This basically means that the
5997 * dict object string representation as a whole can't contain references
5998 * that are not presents in the single elements. */
5999 static const Jim_ObjType dictObjType = {
6000 "dict",
6001 FreeDictInternalRep,
6002 DupDictInternalRep,
6003 UpdateStringOfDict,
6004 JIM_TYPE_NONE,
6007 void FreeDictInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
6009 JIM_NOTUSED(interp);
6011 Jim_FreeHashTable(objPtr->internalRep.ptr);
6012 Jim_Free(objPtr->internalRep.ptr);
6015 void DupDictInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
6017 Jim_HashTable *ht, *dupHt;
6018 Jim_HashTableIterator *htiter;
6019 Jim_HashEntry *he;
6021 /* Create a new hash table */
6022 ht = srcPtr->internalRep.ptr;
6023 dupHt = Jim_Alloc(sizeof(*dupHt));
6024 Jim_InitHashTable(dupHt, &JimDictHashTableType, interp);
6025 if (ht->size != 0)
6026 Jim_ExpandHashTable(dupHt, ht->size);
6027 /* Copy every element from the source to the dup hash table */
6028 htiter = Jim_GetHashTableIterator(ht);
6029 while ((he = Jim_NextHashEntry(htiter)) != NULL) {
6030 const Jim_Obj *keyObjPtr = he->key;
6031 Jim_Obj *valObjPtr = he->val;
6033 Jim_IncrRefCount((Jim_Obj *)keyObjPtr); /* ATTENTION: const cast */
6034 Jim_IncrRefCount(valObjPtr);
6035 Jim_AddHashEntry(dupHt, keyObjPtr, valObjPtr);
6037 Jim_FreeHashTableIterator(htiter);
6039 dupPtr->internalRep.ptr = dupHt;
6040 dupPtr->typePtr = &dictObjType;
6043 void UpdateStringOfDict(struct Jim_Obj *objPtr)
6045 int i, bufLen, realLength;
6046 const char *strRep;
6047 char *p;
6048 int *quotingType, objc;
6049 Jim_HashTable *ht;
6050 Jim_HashTableIterator *htiter;
6051 Jim_HashEntry *he;
6052 Jim_Obj **objv;
6054 /* Trun the hash table into a flat vector of Jim_Objects. */
6055 ht = objPtr->internalRep.ptr;
6056 objc = ht->used * 2;
6057 objv = Jim_Alloc(objc * sizeof(Jim_Obj *));
6058 htiter = Jim_GetHashTableIterator(ht);
6059 i = 0;
6060 while ((he = Jim_NextHashEntry(htiter)) != NULL) {
6061 objv[i++] = (Jim_Obj *)he->key; /* ATTENTION: const cast */
6062 objv[i++] = he->val;
6064 Jim_FreeHashTableIterator(htiter);
6065 /* (Over) Estimate the space needed. */
6066 quotingType = Jim_Alloc(sizeof(int) * objc);
6067 bufLen = 0;
6068 for (i = 0; i < objc; i++) {
6069 int len;
6071 strRep = Jim_GetString(objv[i], &len);
6072 quotingType[i] = ListElementQuotingType(strRep, len);
6073 switch (quotingType[i]) {
6074 case JIM_ELESTR_SIMPLE:
6075 bufLen += len;
6076 break;
6077 case JIM_ELESTR_BRACE:
6078 bufLen += len + 2;
6079 break;
6080 case JIM_ELESTR_QUOTE:
6081 bufLen += len * 2;
6082 break;
6084 bufLen++; /* elements separator. */
6086 bufLen++;
6088 /* Generate the string rep. */
6089 p = objPtr->bytes = Jim_Alloc(bufLen + 1);
6090 realLength = 0;
6091 for (i = 0; i < objc; i++) {
6092 int len, qlen;
6093 char *q;
6095 strRep = Jim_GetString(objv[i], &len);
6097 switch (quotingType[i]) {
6098 case JIM_ELESTR_SIMPLE:
6099 memcpy(p, strRep, len);
6100 p += len;
6101 realLength += len;
6102 break;
6103 case JIM_ELESTR_BRACE:
6104 *p++ = '{';
6105 memcpy(p, strRep, len);
6106 p += len;
6107 *p++ = '}';
6108 realLength += len + 2;
6109 break;
6110 case JIM_ELESTR_QUOTE:
6111 q = BackslashQuoteString(strRep, len, &qlen);
6112 memcpy(p, q, qlen);
6113 Jim_Free(q);
6114 p += qlen;
6115 realLength += qlen;
6116 break;
6118 /* Add a separating space */
6119 if (i + 1 != objc) {
6120 *p++ = ' ';
6121 realLength++;
6124 *p = '\0'; /* nul term. */
6125 objPtr->length = realLength;
6126 Jim_Free(quotingType);
6127 Jim_Free(objv);
6130 static int SetDictFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
6132 int listlen;
6134 /* Get the string representation. Do this first so we don't
6135 * change order in case of fast conversion to dict.
6137 Jim_GetString(objPtr, NULL);
6139 /* For simplicity, convert a non-list object to a list and then to a dict */
6140 listlen = Jim_ListLength(interp, objPtr);
6141 if (listlen % 2) {
6142 Jim_SetResultString(interp,
6143 "invalid dictionary value: must be a list with an even number of elements", -1);
6144 return JIM_ERR;
6146 else {
6147 /* Now it is easy to convert to a dict from a list, and it can't fail */
6148 Jim_HashTable *ht;
6149 int i;
6151 ht = Jim_Alloc(sizeof(*ht));
6152 Jim_InitHashTable(ht, &JimDictHashTableType, interp);
6154 for (i = 0; i < listlen; i += 2) {
6155 Jim_Obj *keyObjPtr;
6156 Jim_Obj *valObjPtr;
6158 Jim_ListIndex(interp, objPtr, i, &keyObjPtr, JIM_NONE);
6159 Jim_ListIndex(interp, objPtr, i + 1, &valObjPtr, JIM_NONE);
6161 Jim_IncrRefCount(keyObjPtr);
6162 Jim_IncrRefCount(valObjPtr);
6164 if (Jim_AddHashEntry(ht, keyObjPtr, valObjPtr) != JIM_OK) {
6165 Jim_HashEntry *he;
6167 he = Jim_FindHashEntry(ht, keyObjPtr);
6168 Jim_DecrRefCount(interp, keyObjPtr);
6169 /* ATTENTION: const cast */
6170 Jim_DecrRefCount(interp, (Jim_Obj *)he->val);
6171 he->val = valObjPtr;
6175 Jim_FreeIntRep(interp, objPtr);
6176 objPtr->typePtr = &dictObjType;
6177 objPtr->internalRep.ptr = ht;
6179 return JIM_OK;
6183 /* Dict object API */
6185 /* Add an element to a dict. objPtr must be of the "dict" type.
6186 * The higer-level exported function is Jim_DictAddElement().
6187 * If an element with the specified key already exists, the value
6188 * associated is replaced with the new one.
6190 * if valueObjPtr == NULL, the key is instead removed if it exists. */
6191 static int DictAddElement(Jim_Interp *interp, Jim_Obj *objPtr,
6192 Jim_Obj *keyObjPtr, Jim_Obj *valueObjPtr)
6194 Jim_HashTable *ht = objPtr->internalRep.ptr;
6196 if (valueObjPtr == NULL) { /* unset */
6197 return Jim_DeleteHashEntry(ht, keyObjPtr);
6199 Jim_IncrRefCount(keyObjPtr);
6200 Jim_IncrRefCount(valueObjPtr);
6201 if (Jim_AddHashEntry(ht, keyObjPtr, valueObjPtr) != JIM_OK) {
6202 Jim_HashEntry *he = Jim_FindHashEntry(ht, keyObjPtr);
6204 Jim_DecrRefCount(interp, keyObjPtr);
6205 /* ATTENTION: const cast */
6206 Jim_DecrRefCount(interp, (Jim_Obj *)he->val);
6207 he->val = valueObjPtr;
6209 return JIM_OK;
6212 /* Add an element, higher-level interface for DictAddElement().
6213 * If valueObjPtr == NULL, the key is removed if it exists. */
6214 int Jim_DictAddElement(Jim_Interp *interp, Jim_Obj *objPtr,
6215 Jim_Obj *keyObjPtr, Jim_Obj *valueObjPtr)
6217 int retcode;
6219 if (Jim_IsShared(objPtr))
6220 Jim_Panic(interp, "Jim_DictAddElement called with shared object");
6221 if (objPtr->typePtr != &dictObjType) {
6222 if (SetDictFromAny(interp, objPtr) != JIM_OK)
6223 return JIM_ERR;
6225 retcode = DictAddElement(interp, objPtr, keyObjPtr, valueObjPtr);
6226 Jim_InvalidateStringRep(objPtr);
6227 return retcode;
6230 Jim_Obj *Jim_NewDictObj(Jim_Interp *interp, Jim_Obj *const *elements, int len)
6232 Jim_Obj *objPtr;
6233 int i;
6235 if (len % 2)
6236 Jim_Panic(interp, "Jim_NewDictObj() 'len' argument must be even");
6238 objPtr = Jim_NewObj(interp);
6239 objPtr->typePtr = &dictObjType;
6240 objPtr->bytes = NULL;
6241 objPtr->internalRep.ptr = Jim_Alloc(sizeof(Jim_HashTable));
6242 Jim_InitHashTable(objPtr->internalRep.ptr, &JimDictHashTableType, interp);
6243 for (i = 0; i < len; i += 2)
6244 DictAddElement(interp, objPtr, elements[i], elements[i + 1]);
6245 return objPtr;
6248 /* Return the value associated to the specified dict key
6249 * Note: Returns JIM_OK if OK, JIM_ERR if entry not found or -1 if can't create dict value
6251 int Jim_DictKey(Jim_Interp *interp, Jim_Obj *dictPtr, Jim_Obj *keyPtr,
6252 Jim_Obj **objPtrPtr, int flags)
6254 Jim_HashEntry *he;
6255 Jim_HashTable *ht;
6257 if (dictPtr->typePtr != &dictObjType) {
6258 if (SetDictFromAny(interp, dictPtr) != JIM_OK)
6259 return -1;
6261 ht = dictPtr->internalRep.ptr;
6262 if ((he = Jim_FindHashEntry(ht, keyPtr)) == NULL) {
6263 if (flags & JIM_ERRMSG) {
6264 Jim_SetResultFormatted(interp, "key \"%#s\" not found in dictionary", keyPtr);
6266 return JIM_ERR;
6268 *objPtrPtr = he->val;
6269 return JIM_OK;
6272 /* Return an allocated array of key/value pairs for the dictionary. Stores the length in *len */
6273 int Jim_DictPairs(Jim_Interp *interp, Jim_Obj *dictPtr, Jim_Obj ***objPtrPtr, int *len)
6275 Jim_HashTable *ht;
6276 Jim_HashTableIterator *htiter;
6277 Jim_HashEntry *he;
6278 Jim_Obj **objv;
6279 int i;
6281 if (dictPtr->typePtr != &dictObjType) {
6282 if (SetDictFromAny(interp, dictPtr) != JIM_OK)
6283 return JIM_ERR;
6285 ht = dictPtr->internalRep.ptr;
6287 /* Turn the hash table into a flat vector of Jim_Objects. */
6288 objv = Jim_Alloc((ht->used * 2) * sizeof(Jim_Obj *));
6289 htiter = Jim_GetHashTableIterator(ht);
6290 i = 0;
6291 while ((he = Jim_NextHashEntry(htiter)) != NULL) {
6292 objv[i++] = (Jim_Obj *)he->key; /* ATTENTION: const cast */
6293 objv[i++] = he->val;
6295 *len = i;
6296 Jim_FreeHashTableIterator(htiter);
6297 *objPtrPtr = objv;
6298 return JIM_OK;
6302 /* Return the value associated to the specified dict keys */
6303 int Jim_DictKeysVector(Jim_Interp *interp, Jim_Obj *dictPtr,
6304 Jim_Obj *const *keyv, int keyc, Jim_Obj **objPtrPtr, int flags)
6306 int i;
6308 if (keyc == 0) {
6309 *objPtrPtr = dictPtr;
6310 return JIM_OK;
6313 for (i = 0; i < keyc; i++) {
6314 Jim_Obj *objPtr;
6316 if (Jim_DictKey(interp, dictPtr, keyv[i], &objPtr, flags)
6317 != JIM_OK)
6318 return JIM_ERR;
6319 dictPtr = objPtr;
6321 *objPtrPtr = dictPtr;
6322 return JIM_OK;
6325 /* Modify the dict stored into the variable named 'varNamePtr'
6326 * setting the element specified by the 'keyc' keys objects in 'keyv',
6327 * with the new value of the element 'newObjPtr'.
6329 * If newObjPtr == NULL the operation is to remove the given key
6330 * from the dictionary. */
6331 int Jim_SetDictKeysVector(Jim_Interp *interp, Jim_Obj *varNamePtr,
6332 Jim_Obj *const *keyv, int keyc, Jim_Obj *newObjPtr)
6334 Jim_Obj *varObjPtr, *objPtr, *dictObjPtr;
6335 int shared, i;
6337 varObjPtr = objPtr =
6338 Jim_GetVariable(interp, varNamePtr, newObjPtr == NULL ? JIM_ERRMSG : JIM_NONE);
6339 if (objPtr == NULL) {
6340 if (newObjPtr == NULL) /* Cannot remove a key from non existing var */
6341 return JIM_ERR;
6342 varObjPtr = objPtr = Jim_NewDictObj(interp, NULL, 0);
6343 if (Jim_SetVariable(interp, varNamePtr, objPtr) != JIM_OK) {
6344 Jim_FreeNewObj(interp, varObjPtr);
6345 return JIM_ERR;
6348 if ((shared = Jim_IsShared(objPtr)))
6349 varObjPtr = objPtr = Jim_DuplicateObj(interp, objPtr);
6350 for (i = 0; i < keyc - 1; i++) {
6351 dictObjPtr = objPtr;
6353 /* Check if it's a valid dictionary */
6354 if (dictObjPtr->typePtr != &dictObjType) {
6355 if (SetDictFromAny(interp, dictObjPtr) != JIM_OK)
6356 goto err;
6358 /* Check if the given key exists. */
6359 Jim_InvalidateStringRep(dictObjPtr);
6360 if (Jim_DictKey(interp, dictObjPtr, keyv[i], &objPtr,
6361 newObjPtr ? JIM_NONE : JIM_ERRMSG) == JIM_OK) {
6362 /* This key exists at the current level.
6363 * Make sure it's not shared!. */
6364 if (Jim_IsShared(objPtr)) {
6365 objPtr = Jim_DuplicateObj(interp, objPtr);
6366 DictAddElement(interp, dictObjPtr, keyv[i], objPtr);
6369 else {
6370 /* Key not found. If it's an [unset] operation
6371 * this is an error. Only the last key may not
6372 * exist. */
6373 if (newObjPtr == NULL)
6374 goto err;
6375 /* Otherwise set an empty dictionary
6376 * as key's value. */
6377 objPtr = Jim_NewDictObj(interp, NULL, 0);
6378 DictAddElement(interp, dictObjPtr, keyv[i], objPtr);
6381 if (Jim_DictAddElement(interp, objPtr, keyv[keyc - 1], newObjPtr) != JIM_OK) {
6382 goto err;
6384 Jim_InvalidateStringRep(objPtr);
6385 Jim_InvalidateStringRep(varObjPtr);
6386 if (Jim_SetVariable(interp, varNamePtr, varObjPtr) != JIM_OK)
6387 goto err;
6388 Jim_SetResult(interp, varObjPtr);
6389 return JIM_OK;
6390 err:
6391 if (shared) {
6392 Jim_FreeNewObj(interp, varObjPtr);
6394 return JIM_ERR;
6397 /* -----------------------------------------------------------------------------
6398 * Index object
6399 * ---------------------------------------------------------------------------*/
6400 static void UpdateStringOfIndex(struct Jim_Obj *objPtr);
6401 static int SetIndexFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
6403 static const Jim_ObjType indexObjType = {
6404 "index",
6405 NULL,
6406 NULL,
6407 UpdateStringOfIndex,
6408 JIM_TYPE_NONE,
6411 void UpdateStringOfIndex(struct Jim_Obj *objPtr)
6413 int len;
6414 char buf[JIM_INTEGER_SPACE + 1];
6416 if (objPtr->internalRep.indexValue >= 0)
6417 len = sprintf(buf, "%d", objPtr->internalRep.indexValue);
6418 else if (objPtr->internalRep.indexValue == -1)
6419 len = sprintf(buf, "end");
6420 else {
6421 len = sprintf(buf, "end%d", objPtr->internalRep.indexValue + 1);
6423 objPtr->bytes = Jim_Alloc(len + 1);
6424 memcpy(objPtr->bytes, buf, len + 1);
6425 objPtr->length = len;
6428 int SetIndexFromAny(Jim_Interp *interp, Jim_Obj *objPtr)
6430 int idx, end = 0;
6431 const char *str;
6432 char *endptr;
6434 /* Get the string representation */
6435 str = Jim_GetString(objPtr, NULL);
6437 /* Try to convert into an index */
6438 if (strncmp(str, "end", 3) == 0) {
6439 end = 1;
6440 str += 3;
6441 idx = 0;
6443 else {
6444 idx = strtol(str, &endptr, 10);
6446 if (endptr == str) {
6447 goto badindex;
6449 str = endptr;
6452 /* Now str may include or +<num> or -<num> */
6453 if (*str == '+' || *str == '-') {
6454 int sign = (*str == '+' ? 1 : -1);
6456 idx += sign * strtol(++str, &endptr, 10);
6457 if (str == endptr || *endptr) {
6458 goto badindex;
6460 str = endptr;
6462 /* The only thing left should be spaces */
6463 while (isspace(UCHAR(*str))) {
6464 str++;
6466 if (*str) {
6467 goto badindex;
6469 if (end) {
6470 if (idx > 0) {
6471 idx = INT_MAX;
6473 else {
6474 /* end-1 is repesented as -2 */
6475 idx--;
6478 else if (idx < 0) {
6479 idx = -INT_MAX;
6482 /* Free the old internal repr and set the new one. */
6483 Jim_FreeIntRep(interp, objPtr);
6484 objPtr->typePtr = &indexObjType;
6485 objPtr->internalRep.indexValue = idx;
6486 return JIM_OK;
6488 badindex:
6489 Jim_SetResultFormatted(interp,
6490 "bad index \"%#s\": must be integer?[+-]integer? or end?[+-]integer?", objPtr);
6491 return JIM_ERR;
6494 int Jim_GetIndex(Jim_Interp *interp, Jim_Obj *objPtr, int *indexPtr)
6496 /* Avoid shimmering if the object is an integer. */
6497 if (objPtr->typePtr == &intObjType) {
6498 jim_wide val = JimWideValue(objPtr);
6500 if (!(val < LONG_MIN) && !(val > LONG_MAX)) {
6501 *indexPtr = (val < 0) ? -INT_MAX : (long)val;;
6502 return JIM_OK;
6505 if (objPtr->typePtr != &indexObjType && SetIndexFromAny(interp, objPtr) == JIM_ERR)
6506 return JIM_ERR;
6507 *indexPtr = objPtr->internalRep.indexValue;
6508 return JIM_OK;
6511 /* -----------------------------------------------------------------------------
6512 * Return Code Object.
6513 * ---------------------------------------------------------------------------*/
6515 /* NOTE: These must be kept in the same order as JIM_OK, JIM_ERR, ... */
6516 static const char * const jimReturnCodes[] = {
6517 [JIM_OK] = "ok",
6518 [JIM_ERR] = "error",
6519 [JIM_RETURN] = "return",
6520 [JIM_BREAK] = "break",
6521 [JIM_CONTINUE] = "continue",
6522 [JIM_SIGNAL] = "signal",
6523 [JIM_EXIT] = "exit",
6524 [JIM_EVAL] = "eval",
6525 NULL
6528 #define jimReturnCodesSize (sizeof(jimReturnCodes)/sizeof(*jimReturnCodes))
6530 static int SetReturnCodeFromAny(Jim_Interp *interp, Jim_Obj *objPtr);
6532 static const Jim_ObjType returnCodeObjType = {
6533 "return-code",
6534 NULL,
6535 NULL,
6536 NULL,
6537 JIM_TYPE_NONE,
6540 /* Converts a (standard) return code to a string. Returns "?" for
6541 * non-standard return codes.
6543 const char *Jim_ReturnCode(int code)
6545 if (code < 0 || code >= (int)jimReturnCodesSize) {
6546 return "?";
6548 else {
6549 return jimReturnCodes[code];
6553 int SetReturnCodeFromAny(Jim_Interp *interp, Jim_Obj *objPtr)
6555 int returnCode;
6556 jim_wide wideValue;
6558 /* Try to convert into an integer */
6559 if (JimGetWideNoErr(interp, objPtr, &wideValue) != JIM_ERR)
6560 returnCode = (int)wideValue;
6561 else if (Jim_GetEnum(interp, objPtr, jimReturnCodes, &returnCode, NULL, JIM_NONE) != JIM_OK) {
6562 Jim_SetResultFormatted(interp, "expected return code but got \"%#s\"", objPtr);
6563 return JIM_ERR;
6565 /* Free the old internal repr and set the new one. */
6566 Jim_FreeIntRep(interp, objPtr);
6567 objPtr->typePtr = &returnCodeObjType;
6568 objPtr->internalRep.returnCode = returnCode;
6569 return JIM_OK;
6572 int Jim_GetReturnCode(Jim_Interp *interp, Jim_Obj *objPtr, int *intPtr)
6574 if (objPtr->typePtr != &returnCodeObjType && SetReturnCodeFromAny(interp, objPtr) == JIM_ERR)
6575 return JIM_ERR;
6576 *intPtr = objPtr->internalRep.returnCode;
6577 return JIM_OK;
6580 /* -----------------------------------------------------------------------------
6581 * Expression Parsing
6582 * ---------------------------------------------------------------------------*/
6583 static int JimParseExprOperator(struct JimParserCtx *pc);
6584 static int JimParseExprNumber(struct JimParserCtx *pc);
6585 static int JimParseExprIrrational(struct JimParserCtx *pc);
6587 /* Exrp's Stack machine operators opcodes. */
6589 /* Binary operators (numbers) */
6590 enum
6592 /* Continues on from the JIM_TT_ space */
6593 /* Operations */
6594 JIM_EXPROP_MUL = JIM_TT_EXPR_OP, /* 15 */
6595 JIM_EXPROP_DIV,
6596 JIM_EXPROP_MOD,
6597 JIM_EXPROP_SUB,
6598 JIM_EXPROP_ADD,
6599 JIM_EXPROP_LSHIFT,
6600 JIM_EXPROP_RSHIFT,
6601 JIM_EXPROP_ROTL,
6602 JIM_EXPROP_ROTR,
6603 JIM_EXPROP_LT,
6604 JIM_EXPROP_GT,
6605 JIM_EXPROP_LTE,
6606 JIM_EXPROP_GTE,
6607 JIM_EXPROP_NUMEQ,
6608 JIM_EXPROP_NUMNE,
6609 JIM_EXPROP_BITAND, /* 30 */
6610 JIM_EXPROP_BITXOR,
6611 JIM_EXPROP_BITOR,
6613 /* Note must keep these together */
6614 JIM_EXPROP_LOGICAND, /* 33 */
6615 JIM_EXPROP_LOGICAND_LEFT,
6616 JIM_EXPROP_LOGICAND_RIGHT,
6618 /* and these */
6619 JIM_EXPROP_LOGICOR, /* 36 */
6620 JIM_EXPROP_LOGICOR_LEFT,
6621 JIM_EXPROP_LOGICOR_RIGHT,
6623 /* and these */
6624 /* Ternary operators */
6625 JIM_EXPROP_TERNARY, /* 39 */
6626 JIM_EXPROP_TERNARY_LEFT,
6627 JIM_EXPROP_TERNARY_RIGHT,
6629 /* and these */
6630 JIM_EXPROP_COLON, /* 42 */
6631 JIM_EXPROP_COLON_LEFT,
6632 JIM_EXPROP_COLON_RIGHT,
6634 JIM_EXPROP_POW, /* 45 */
6636 /* Binary operators (strings) */
6637 JIM_EXPROP_STREQ,
6638 JIM_EXPROP_STRNE,
6639 JIM_EXPROP_STRIN,
6640 JIM_EXPROP_STRNI,
6642 /* Unary operators (numbers) */
6643 JIM_EXPROP_NOT,
6644 JIM_EXPROP_BITNOT,
6645 JIM_EXPROP_UNARYMINUS,
6646 JIM_EXPROP_UNARYPLUS,
6648 /* Functions */
6649 JIM_EXPROP_FUNC_FIRST,
6650 JIM_EXPROP_FUNC_INT = JIM_EXPROP_FUNC_FIRST,
6651 JIM_EXPROP_FUNC_ABS,
6652 JIM_EXPROP_FUNC_DOUBLE,
6653 JIM_EXPROP_FUNC_ROUND,
6655 #ifdef JIM_MATH_FUNCTIONS
6656 /* math functions from libm */
6657 JIM_EXPROP_FUNC_SIN,
6658 JIM_EXPROP_FUNC_COS,
6659 JIM_EXPROP_FUNC_TAN,
6660 JIM_EXPROP_FUNC_ASIN,
6661 JIM_EXPROP_FUNC_ACOS,
6662 JIM_EXPROP_FUNC_ATAN,
6663 JIM_EXPROP_FUNC_SINH,
6664 JIM_EXPROP_FUNC_COSH,
6665 JIM_EXPROP_FUNC_TANH,
6666 JIM_EXPROP_FUNC_CEIL,
6667 JIM_EXPROP_FUNC_FLOOR,
6668 JIM_EXPROP_FUNC_EXP,
6669 JIM_EXPROP_FUNC_LOG,
6670 JIM_EXPROP_FUNC_LOG10,
6671 JIM_EXPROP_FUNC_SQRT,
6672 #endif
6675 struct JimExprState
6677 Jim_Obj **stack;
6678 int stacklen;
6679 int opcode;
6680 int skip;
6683 /* Operators table */
6684 typedef struct Jim_ExprOperator
6686 const char *name;
6687 int precedence;
6688 int arity;
6689 int (*funcop) (Jim_Interp *interp, struct JimExprState * e);
6690 int lazy;
6691 } Jim_ExprOperator;
6693 static void ExprPush(struct JimExprState *e, Jim_Obj *obj)
6695 Jim_IncrRefCount(obj);
6696 e->stack[e->stacklen++] = obj;
6699 static Jim_Obj *ExprPop(struct JimExprState *e)
6701 return e->stack[--e->stacklen];
6704 static int JimExprOpNumUnary(Jim_Interp *interp, struct JimExprState *e)
6706 int intresult = 0;
6707 int rc = JIM_OK;
6708 Jim_Obj *A = ExprPop(e);
6709 double dA, dC = 0;
6710 jim_wide wA, wC = 0;
6712 if ((A->typePtr != &doubleObjType || A->bytes) && JimGetWideNoErr(interp, A, &wA) == JIM_OK) {
6713 intresult = 1;
6715 switch (e->opcode) {
6716 case JIM_EXPROP_FUNC_INT:
6717 wC = wA;
6718 break;
6719 case JIM_EXPROP_FUNC_ROUND:
6720 wC = wA;
6721 break;
6722 case JIM_EXPROP_FUNC_DOUBLE:
6723 dC = wA;
6724 intresult = 0;
6725 break;
6726 case JIM_EXPROP_FUNC_ABS:
6727 wC = wA >= 0 ? wA : -wA;
6728 break;
6729 case JIM_EXPROP_UNARYMINUS:
6730 wC = -wA;
6731 break;
6732 case JIM_EXPROP_UNARYPLUS:
6733 wC = wA;
6734 break;
6735 case JIM_EXPROP_NOT:
6736 wC = !wA;
6737 break;
6738 default:
6739 abort();
6742 else if ((rc = Jim_GetDouble(interp, A, &dA)) == JIM_OK) {
6743 switch (e->opcode) {
6744 case JIM_EXPROP_FUNC_INT:
6745 wC = dA;
6746 intresult = 1;
6747 break;
6748 case JIM_EXPROP_FUNC_ROUND:
6749 wC = dA < 0 ? (dA - 0.5) : (dA + 0.5);
6750 intresult = 1;
6751 break;
6752 case JIM_EXPROP_FUNC_DOUBLE:
6753 dC = dA;
6754 break;
6755 case JIM_EXPROP_FUNC_ABS:
6756 dC = dA >= 0 ? dA : -dA;
6757 break;
6758 case JIM_EXPROP_UNARYMINUS:
6759 dC = -dA;
6760 break;
6761 case JIM_EXPROP_UNARYPLUS:
6762 dC = dA;
6763 break;
6764 case JIM_EXPROP_NOT:
6765 wC = !dA;
6766 intresult = 1;
6767 break;
6768 default:
6769 abort();
6773 if (rc == JIM_OK) {
6774 if (intresult) {
6775 ExprPush(e, Jim_NewIntObj(interp, wC));
6777 else {
6778 ExprPush(e, Jim_NewDoubleObj(interp, dC));
6782 Jim_DecrRefCount(interp, A);
6784 return rc;
6787 static int JimExprOpIntUnary(Jim_Interp *interp, struct JimExprState *e)
6789 Jim_Obj *A = ExprPop(e);
6790 jim_wide wA;
6791 int rc = JIM_ERR;
6794 if (Jim_GetWide(interp, A, &wA) == JIM_OK) {
6795 jim_wide wC;
6797 switch (e->opcode) {
6798 case JIM_EXPROP_BITNOT:
6799 wC = ~wA;
6800 break;
6801 default:
6802 abort();
6804 ExprPush(e, Jim_NewIntObj(interp, wC));
6805 rc = JIM_OK;
6808 Jim_DecrRefCount(interp, A);
6810 return rc;
6813 #ifdef JIM_MATH_FUNCTIONS
6814 static int JimExprOpDoubleUnary(Jim_Interp *interp, struct JimExprState *e)
6816 int rc;
6817 Jim_Obj *A = ExprPop(e);
6818 double dA, dC;
6820 rc = Jim_GetDouble(interp, A, &dA);
6821 if (rc == JIM_OK) {
6822 switch (e->opcode) {
6823 case JIM_EXPROP_FUNC_SIN:
6824 dC = sin(dA);
6825 break;
6826 case JIM_EXPROP_FUNC_COS:
6827 dC = cos(dA);
6828 break;
6829 case JIM_EXPROP_FUNC_TAN:
6830 dC = tan(dA);
6831 break;
6832 case JIM_EXPROP_FUNC_ASIN:
6833 dC = asin(dA);
6834 break;
6835 case JIM_EXPROP_FUNC_ACOS:
6836 dC = acos(dA);
6837 break;
6838 case JIM_EXPROP_FUNC_ATAN:
6839 dC = atan(dA);
6840 break;
6841 case JIM_EXPROP_FUNC_SINH:
6842 dC = sinh(dA);
6843 break;
6844 case JIM_EXPROP_FUNC_COSH:
6845 dC = cosh(dA);
6846 break;
6847 case JIM_EXPROP_FUNC_TANH:
6848 dC = tanh(dA);
6849 break;
6850 case JIM_EXPROP_FUNC_CEIL:
6851 dC = ceil(dA);
6852 break;
6853 case JIM_EXPROP_FUNC_FLOOR:
6854 dC = floor(dA);
6855 break;
6856 case JIM_EXPROP_FUNC_EXP:
6857 dC = exp(dA);
6858 break;
6859 case JIM_EXPROP_FUNC_LOG:
6860 dC = log(dA);
6861 break;
6862 case JIM_EXPROP_FUNC_LOG10:
6863 dC = log10(dA);
6864 break;
6865 case JIM_EXPROP_FUNC_SQRT:
6866 dC = sqrt(dA);
6867 break;
6868 default:
6869 abort();
6871 ExprPush(e, Jim_NewDoubleObj(interp, dC));
6874 Jim_DecrRefCount(interp, A);
6876 return rc;
6878 #endif
6880 /* A binary operation on two ints */
6881 static int JimExprOpIntBin(Jim_Interp *interp, struct JimExprState *e)
6883 Jim_Obj *B = ExprPop(e);
6884 Jim_Obj *A = ExprPop(e);
6885 jim_wide wA, wB;
6886 int rc = JIM_ERR;
6888 if (Jim_GetWide(interp, A, &wA) == JIM_OK && Jim_GetWide(interp, B, &wB) == JIM_OK) {
6889 jim_wide wC;
6891 rc = JIM_OK;
6893 switch (e->opcode) {
6894 case JIM_EXPROP_LSHIFT:
6895 wC = wA << wB;
6896 break;
6897 case JIM_EXPROP_RSHIFT:
6898 wC = wA >> wB;
6899 break;
6900 case JIM_EXPROP_BITAND:
6901 wC = wA & wB;
6902 break;
6903 case JIM_EXPROP_BITXOR:
6904 wC = wA ^ wB;
6905 break;
6906 case JIM_EXPROP_BITOR:
6907 wC = wA | wB;
6908 break;
6909 case JIM_EXPROP_POW:
6910 wC = JimPowWide(wA, wB);
6911 break;
6912 case JIM_EXPROP_MOD:
6913 if (wB == 0) {
6914 wC = 0;
6915 Jim_SetResultString(interp, "Division by zero", -1);
6916 rc = JIM_ERR;
6918 else {
6920 * From Tcl 8.x
6922 * This code is tricky: C doesn't guarantee much
6923 * about the quotient or remainder, but Tcl does.
6924 * The remainder always has the same sign as the
6925 * divisor and a smaller absolute value.
6927 int negative = 0;
6929 if (wB < 0) {
6930 wB = -wB;
6931 wA = -wA;
6932 negative = 1;
6934 wC = wA % wB;
6935 if (wC < 0) {
6936 wC += wB;
6938 if (negative) {
6939 wC = -wC;
6942 break;
6943 case JIM_EXPROP_ROTL:{
6944 /* uint32_t would be better. But not everyone has inttypes.h? */
6945 unsigned long uA = (unsigned long)wA;
6946 const unsigned int S = sizeof(unsigned long) * 8;
6948 wC = (unsigned long)((uA << wB) | (uA >> (S - wB)));
6949 break;
6951 case JIM_EXPROP_ROTR:{
6952 unsigned long uA = (unsigned long)wA;
6953 const unsigned int S = sizeof(unsigned long) * 8;
6955 wC = (unsigned long)((uA >> wB) | (uA << (S - wB)));
6956 break;
6958 default:
6959 abort();
6961 ExprPush(e, Jim_NewIntObj(interp, wC));
6965 Jim_DecrRefCount(interp, A);
6966 Jim_DecrRefCount(interp, B);
6968 return rc;
6972 /* A binary operation on two ints or two doubles (or two strings for some ops) */
6973 static int JimExprOpBin(Jim_Interp *interp, struct JimExprState *e)
6975 int intresult = 0;
6976 int rc = JIM_OK;
6977 double dA, dB, dC = 0;
6978 jim_wide wA, wB, wC = 0;
6980 Jim_Obj *B = ExprPop(e);
6981 Jim_Obj *A = ExprPop(e);
6983 if ((A->typePtr != &doubleObjType || A->bytes) &&
6984 (B->typePtr != &doubleObjType || B->bytes) &&
6985 JimGetWideNoErr(interp, A, &wA) == JIM_OK && JimGetWideNoErr(interp, B, &wB) == JIM_OK) {
6987 /* Both are ints */
6989 intresult = 1;
6991 switch (e->opcode) {
6992 case JIM_EXPROP_POW:
6993 wC = JimPowWide(wA, wB);
6994 break;
6995 case JIM_EXPROP_ADD:
6996 wC = wA + wB;
6997 break;
6998 case JIM_EXPROP_SUB:
6999 wC = wA - wB;
7000 break;
7001 case JIM_EXPROP_MUL:
7002 wC = wA * wB;
7003 break;
7004 case JIM_EXPROP_DIV:
7005 if (wB == 0) {
7006 Jim_SetResultString(interp, "Division by zero", -1);
7007 rc = JIM_ERR;
7009 else {
7011 * From Tcl 8.x
7013 * This code is tricky: C doesn't guarantee much
7014 * about the quotient or remainder, but Tcl does.
7015 * The remainder always has the same sign as the
7016 * divisor and a smaller absolute value.
7018 if (wB < 0) {
7019 wB = -wB;
7020 wA = -wA;
7022 wC = wA / wB;
7023 if (wA % wB < 0) {
7024 wC--;
7027 break;
7028 case JIM_EXPROP_LT:
7029 wC = wA < wB;
7030 break;
7031 case JIM_EXPROP_GT:
7032 wC = wA > wB;
7033 break;
7034 case JIM_EXPROP_LTE:
7035 wC = wA <= wB;
7036 break;
7037 case JIM_EXPROP_GTE:
7038 wC = wA >= wB;
7039 break;
7040 case JIM_EXPROP_NUMEQ:
7041 wC = wA == wB;
7042 break;
7043 case JIM_EXPROP_NUMNE:
7044 wC = wA != wB;
7045 break;
7046 default:
7047 abort();
7050 else if (Jim_GetDouble(interp, A, &dA) == JIM_OK && Jim_GetDouble(interp, B, &dB) == JIM_OK) {
7051 switch (e->opcode) {
7052 case JIM_EXPROP_POW:
7053 #ifdef JIM_MATH_FUNCTIONS
7054 dC = pow(dA, dB);
7055 #else
7056 rc = JIM_ERR;
7057 #endif
7058 break;
7059 case JIM_EXPROP_ADD:
7060 dC = dA + dB;
7061 break;
7062 case JIM_EXPROP_SUB:
7063 dC = dA - dB;
7064 break;
7065 case JIM_EXPROP_MUL:
7066 dC = dA * dB;
7067 break;
7068 case JIM_EXPROP_DIV:
7069 if (dB == 0) {
7070 #ifdef INFINITY
7071 dC = dA < 0 ? -INFINITY : INFINITY;
7072 #else
7073 dC = (dA < 0 ? -1.0 : 1.0) * strtod("Inf", NULL);
7074 #endif
7076 else {
7077 dC = dA / dB;
7079 break;
7080 case JIM_EXPROP_LT:
7081 wC = dA < dB;
7082 intresult = 1;
7083 break;
7084 case JIM_EXPROP_GT:
7085 wC = dA > dB;
7086 intresult = 1;
7087 break;
7088 case JIM_EXPROP_LTE:
7089 wC = dA <= dB;
7090 intresult = 1;
7091 break;
7092 case JIM_EXPROP_GTE:
7093 wC = dA >= dB;
7094 intresult = 1;
7095 break;
7096 case JIM_EXPROP_NUMEQ:
7097 wC = dA == dB;
7098 intresult = 1;
7099 break;
7100 case JIM_EXPROP_NUMNE:
7101 wC = dA != dB;
7102 intresult = 1;
7103 break;
7104 default:
7105 abort();
7108 else {
7109 /* Handle the string case */
7111 /* REVISIT: Could optimise the eq/ne case by checking lengths */
7112 int i = Jim_StringCompareObj(interp, A, B, 0);
7114 intresult = 1;
7116 switch (e->opcode) {
7117 case JIM_EXPROP_LT:
7118 wC = i < 0;
7119 break;
7120 case JIM_EXPROP_GT:
7121 wC = i > 0;
7122 break;
7123 case JIM_EXPROP_LTE:
7124 wC = i <= 0;
7125 break;
7126 case JIM_EXPROP_GTE:
7127 wC = i >= 0;
7128 break;
7129 case JIM_EXPROP_NUMEQ:
7130 wC = i == 0;
7131 break;
7132 case JIM_EXPROP_NUMNE:
7133 wC = i != 0;
7134 break;
7135 default:
7136 rc = JIM_ERR;
7137 break;
7141 if (rc == JIM_OK) {
7142 if (intresult) {
7143 ExprPush(e, Jim_NewIntObj(interp, wC));
7145 else {
7146 ExprPush(e, Jim_NewDoubleObj(interp, dC));
7150 Jim_DecrRefCount(interp, A);
7151 Jim_DecrRefCount(interp, B);
7153 return rc;
7156 static int JimSearchList(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_Obj *valObj)
7158 int listlen;
7159 int i;
7161 listlen = Jim_ListLength(interp, listObjPtr);
7162 for (i = 0; i < listlen; i++) {
7163 Jim_Obj *objPtr;
7165 Jim_ListIndex(interp, listObjPtr, i, &objPtr, JIM_NONE);
7167 if (Jim_StringEqObj(objPtr, valObj)) {
7168 return 1;
7171 return 0;
7174 static int JimExprOpStrBin(Jim_Interp *interp, struct JimExprState *e)
7176 Jim_Obj *B = ExprPop(e);
7177 Jim_Obj *A = ExprPop(e);
7179 jim_wide wC;
7181 switch (e->opcode) {
7182 case JIM_EXPROP_STREQ:
7183 case JIM_EXPROP_STRNE: {
7184 int Alen, Blen;
7185 const char *sA = Jim_GetString(A, &Alen);
7186 const char *sB = Jim_GetString(B, &Blen);
7188 if (e->opcode == JIM_EXPROP_STREQ) {
7189 wC = (Alen == Blen && memcmp(sA, sB, Alen) == 0);
7191 else {
7192 wC = (Alen != Blen || memcmp(sA, sB, Alen) != 0);
7194 break;
7196 case JIM_EXPROP_STRIN:
7197 wC = JimSearchList(interp, B, A);
7198 break;
7199 case JIM_EXPROP_STRNI:
7200 wC = !JimSearchList(interp, B, A);
7201 break;
7202 default:
7203 abort();
7205 ExprPush(e, Jim_NewIntObj(interp, wC));
7207 Jim_DecrRefCount(interp, A);
7208 Jim_DecrRefCount(interp, B);
7210 return JIM_OK;
7213 static int ExprBool(Jim_Interp *interp, Jim_Obj *obj)
7215 long l;
7216 double d;
7218 if (Jim_GetLong(interp, obj, &l) == JIM_OK) {
7219 return l != 0;
7221 if (Jim_GetDouble(interp, obj, &d) == JIM_OK) {
7222 return d != 0;
7224 return -1;
7227 static int JimExprOpAndLeft(Jim_Interp *interp, struct JimExprState *e)
7229 Jim_Obj *skip = ExprPop(e);
7230 Jim_Obj *A = ExprPop(e);
7231 int rc = JIM_OK;
7233 switch (ExprBool(interp, A)) {
7234 case 0:
7235 /* false, so skip RHS opcodes with a 0 result */
7236 e->skip = JimWideValue(skip);
7237 ExprPush(e, Jim_NewIntObj(interp, 0));
7238 break;
7240 case 1:
7241 /* true so continue */
7242 break;
7244 case -1:
7245 /* Invalid */
7246 rc = JIM_ERR;
7248 Jim_DecrRefCount(interp, A);
7249 Jim_DecrRefCount(interp, skip);
7251 return rc;
7254 static int JimExprOpOrLeft(Jim_Interp *interp, struct JimExprState *e)
7256 Jim_Obj *skip = ExprPop(e);
7257 Jim_Obj *A = ExprPop(e);
7258 int rc = JIM_OK;
7260 switch (ExprBool(interp, A)) {
7261 case 0:
7262 /* false, so do nothing */
7263 break;
7265 case 1:
7266 /* true so skip RHS opcodes with a 1 result */
7267 e->skip = JimWideValue(skip);
7268 ExprPush(e, Jim_NewIntObj(interp, 1));
7269 break;
7271 case -1:
7272 /* Invalid */
7273 rc = JIM_ERR;
7274 break;
7276 Jim_DecrRefCount(interp, A);
7277 Jim_DecrRefCount(interp, skip);
7279 return rc;
7282 static int JimExprOpAndOrRight(Jim_Interp *interp, struct JimExprState *e)
7284 Jim_Obj *A = ExprPop(e);
7285 int rc = JIM_OK;
7287 switch (ExprBool(interp, A)) {
7288 case 0:
7289 ExprPush(e, Jim_NewIntObj(interp, 0));
7290 break;
7292 case 1:
7293 ExprPush(e, Jim_NewIntObj(interp, 1));
7294 break;
7296 case -1:
7297 /* Invalid */
7298 rc = JIM_ERR;
7299 break;
7301 Jim_DecrRefCount(interp, A);
7303 return rc;
7306 static int JimExprOpTernaryLeft(Jim_Interp *interp, struct JimExprState *e)
7308 Jim_Obj *skip = ExprPop(e);
7309 Jim_Obj *A = ExprPop(e);
7310 int rc = JIM_OK;
7312 /* Repush A */
7313 ExprPush(e, A);
7315 switch (ExprBool(interp, A)) {
7316 case 0:
7317 /* false, skip RHS opcodes */
7318 e->skip = JimWideValue(skip);
7319 /* Push a dummy value */
7320 ExprPush(e, Jim_NewIntObj(interp, 0));
7321 break;
7323 case 1:
7324 /* true so do nothing */
7325 break;
7327 case -1:
7328 /* Invalid */
7329 rc = JIM_ERR;
7330 break;
7332 Jim_DecrRefCount(interp, A);
7333 Jim_DecrRefCount(interp, skip);
7335 return rc;
7338 static int JimExprOpColonLeft(Jim_Interp *interp, struct JimExprState *e)
7340 Jim_Obj *skip = ExprPop(e);
7341 Jim_Obj *B = ExprPop(e);
7342 Jim_Obj *A = ExprPop(e);
7344 /* No need to check for A as non-boolean */
7345 if (ExprBool(interp, A)) {
7346 /* true, so skip RHS opcodes */
7347 e->skip = JimWideValue(skip);
7348 /* Repush B as the answer */
7349 ExprPush(e, B);
7352 Jim_DecrRefCount(interp, skip);
7353 Jim_DecrRefCount(interp, A);
7354 Jim_DecrRefCount(interp, B);
7355 return JIM_OK;
7358 static int JimExprOpNull(Jim_Interp *interp, struct JimExprState *e)
7360 return JIM_OK;
7363 enum
7365 LAZY_NONE,
7366 LAZY_OP,
7367 LAZY_LEFT,
7368 LAZY_RIGHT
7371 /* name - precedence - arity - opcode */
7372 static const struct Jim_ExprOperator Jim_ExprOperators[] = {
7373 [JIM_EXPROP_FUNC_INT] = {"int", 400, 1, JimExprOpNumUnary, LAZY_NONE},
7374 [JIM_EXPROP_FUNC_DOUBLE] = {"double", 400, 1, JimExprOpNumUnary, LAZY_NONE},
7375 [JIM_EXPROP_FUNC_ABS] = {"abs", 400, 1, JimExprOpNumUnary, LAZY_NONE},
7376 [JIM_EXPROP_FUNC_ROUND] = {"round", 400, 1, JimExprOpNumUnary, LAZY_NONE},
7378 #ifdef JIM_MATH_FUNCTIONS
7379 [JIM_EXPROP_FUNC_SIN] = {"sin", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7380 [JIM_EXPROP_FUNC_COS] = {"cos", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7381 [JIM_EXPROP_FUNC_TAN] = {"tan", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7382 [JIM_EXPROP_FUNC_ASIN] = {"asin", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7383 [JIM_EXPROP_FUNC_ACOS] = {"acos", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7384 [JIM_EXPROP_FUNC_ATAN] = {"atan", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7385 [JIM_EXPROP_FUNC_SINH] = {"sinh", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7386 [JIM_EXPROP_FUNC_COSH] = {"cosh", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7387 [JIM_EXPROP_FUNC_TANH] = {"tanh", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7388 [JIM_EXPROP_FUNC_CEIL] = {"ceil", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7389 [JIM_EXPROP_FUNC_FLOOR] = {"floor", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7390 [JIM_EXPROP_FUNC_EXP] = {"exp", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7391 [JIM_EXPROP_FUNC_LOG] = {"log", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7392 [JIM_EXPROP_FUNC_LOG10] = {"log10", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7393 [JIM_EXPROP_FUNC_SQRT] = {"sqrt", 400, 1, JimExprOpDoubleUnary, LAZY_NONE},
7394 #endif
7396 [JIM_EXPROP_NOT] = {"!", 300, 1, JimExprOpNumUnary, LAZY_NONE},
7397 [JIM_EXPROP_BITNOT] = {"~", 300, 1, JimExprOpIntUnary, LAZY_NONE},
7398 [JIM_EXPROP_UNARYMINUS] = {NULL, 300, 1, JimExprOpNumUnary, LAZY_NONE},
7399 [JIM_EXPROP_UNARYPLUS] = {NULL, 300, 1, JimExprOpNumUnary, LAZY_NONE},
7401 [JIM_EXPROP_POW] = {"**", 250, 2, JimExprOpBin, LAZY_NONE},
7403 [JIM_EXPROP_MUL] = {"*", 200, 2, JimExprOpBin, LAZY_NONE},
7404 [JIM_EXPROP_DIV] = {"/", 200, 2, JimExprOpBin, LAZY_NONE},
7405 [JIM_EXPROP_MOD] = {"%", 200, 2, JimExprOpIntBin, LAZY_NONE},
7407 [JIM_EXPROP_SUB] = {"-", 100, 2, JimExprOpBin, LAZY_NONE},
7408 [JIM_EXPROP_ADD] = {"+", 100, 2, JimExprOpBin, LAZY_NONE},
7410 [JIM_EXPROP_ROTL] = {"<<<", 90, 2, JimExprOpIntBin, LAZY_NONE},
7411 [JIM_EXPROP_ROTR] = {">>>", 90, 2, JimExprOpIntBin, LAZY_NONE},
7412 [JIM_EXPROP_LSHIFT] = {"<<", 90, 2, JimExprOpIntBin, LAZY_NONE},
7413 [JIM_EXPROP_RSHIFT] = {">>", 90, 2, JimExprOpIntBin, LAZY_NONE},
7415 [JIM_EXPROP_LT] = {"<", 80, 2, JimExprOpBin, LAZY_NONE},
7416 [JIM_EXPROP_GT] = {">", 80, 2, JimExprOpBin, LAZY_NONE},
7417 [JIM_EXPROP_LTE] = {"<=", 80, 2, JimExprOpBin, LAZY_NONE},
7418 [JIM_EXPROP_GTE] = {">=", 80, 2, JimExprOpBin, LAZY_NONE},
7420 [JIM_EXPROP_NUMEQ] = {"==", 70, 2, JimExprOpBin, LAZY_NONE},
7421 [JIM_EXPROP_NUMNE] = {"!=", 70, 2, JimExprOpBin, LAZY_NONE},
7423 [JIM_EXPROP_STREQ] = {"eq", 60, 2, JimExprOpStrBin, LAZY_NONE},
7424 [JIM_EXPROP_STRNE] = {"ne", 60, 2, JimExprOpStrBin, LAZY_NONE},
7426 [JIM_EXPROP_STRIN] = {"in", 55, 2, JimExprOpStrBin, LAZY_NONE},
7427 [JIM_EXPROP_STRNI] = {"ni", 55, 2, JimExprOpStrBin, LAZY_NONE},
7429 [JIM_EXPROP_BITAND] = {"&", 50, 2, JimExprOpIntBin, LAZY_NONE},
7430 [JIM_EXPROP_BITXOR] = {"^", 49, 2, JimExprOpIntBin, LAZY_NONE},
7431 [JIM_EXPROP_BITOR] = {"|", 48, 2, JimExprOpIntBin, LAZY_NONE},
7433 [JIM_EXPROP_LOGICAND] = {"&&", 10, 2, NULL, LAZY_OP},
7434 [JIM_EXPROP_LOGICOR] = {"||", 9, 2, NULL, LAZY_OP},
7436 [JIM_EXPROP_TERNARY] = {"?", 5, 2, JimExprOpNull, LAZY_OP},
7437 [JIM_EXPROP_COLON] = {":", 5, 2, JimExprOpNull, LAZY_OP},
7439 /* private operators */
7440 [JIM_EXPROP_TERNARY_LEFT] = {NULL, 5, 2, JimExprOpTernaryLeft, LAZY_LEFT},
7441 [JIM_EXPROP_TERNARY_RIGHT] = {NULL, 5, 2, JimExprOpNull, LAZY_RIGHT},
7442 [JIM_EXPROP_COLON_LEFT] = {NULL, 5, 2, JimExprOpColonLeft, LAZY_LEFT},
7443 [JIM_EXPROP_COLON_RIGHT] = {NULL, 5, 2, JimExprOpNull, LAZY_RIGHT},
7444 [JIM_EXPROP_LOGICAND_LEFT] = {NULL, 10, 2, JimExprOpAndLeft, LAZY_LEFT},
7445 [JIM_EXPROP_LOGICAND_RIGHT] = {NULL, 10, 2, JimExprOpAndOrRight, LAZY_RIGHT},
7446 [JIM_EXPROP_LOGICOR_LEFT] = {NULL, 9, 2, JimExprOpOrLeft, LAZY_LEFT},
7447 [JIM_EXPROP_LOGICOR_RIGHT] = {NULL, 9, 2, JimExprOpAndOrRight, LAZY_RIGHT},
7450 #define JIM_EXPR_OPERATORS_NUM \
7451 (sizeof(Jim_ExprOperators)/sizeof(struct Jim_ExprOperator))
7453 static int JimParseExpression(struct JimParserCtx *pc)
7455 /* Discard spaces and quoted newline */
7456 while (isspace(UCHAR(*pc->p)) || (*(pc->p) == '\\' && *(pc->p + 1) == '\n')) {
7457 pc->p++;
7458 pc->len--;
7461 if (pc->len == 0) {
7462 pc->tstart = pc->tend = pc->p;
7463 pc->tline = pc->linenr;
7464 pc->tt = JIM_TT_EOL;
7465 pc->eof = 1;
7466 return JIM_OK;
7468 switch (*(pc->p)) {
7469 case '(':
7470 pc->tstart = pc->tend = pc->p;
7471 pc->tline = pc->linenr;
7472 pc->tt = JIM_TT_SUBEXPR_START;
7473 pc->p++;
7474 pc->len--;
7475 break;
7476 case ')':
7477 pc->tstart = pc->tend = pc->p;
7478 pc->tline = pc->linenr;
7479 pc->tt = JIM_TT_SUBEXPR_END;
7480 pc->p++;
7481 pc->len--;
7482 break;
7483 case '[':
7484 return JimParseCmd(pc);
7485 break;
7486 case '$':
7487 if (JimParseVar(pc) == JIM_ERR)
7488 return JimParseExprOperator(pc);
7489 else
7490 return JIM_OK;
7491 break;
7492 case '0':
7493 case '1':
7494 case '2':
7495 case '3':
7496 case '4':
7497 case '5':
7498 case '6':
7499 case '7':
7500 case '8':
7501 case '9':
7502 case '.':
7503 return JimParseExprNumber(pc);
7504 break;
7505 case '"':
7506 case '{':
7507 /* Here it's possible to reuse the List String parsing. */
7508 pc->tt = JIM_TT_NONE; /* Make sure it's sensed as a new word. */
7509 return JimParseListStr(pc);
7510 break;
7511 case 'N':
7512 case 'I':
7513 case 'n':
7514 case 'i':
7515 if (JimParseExprIrrational(pc) == JIM_ERR)
7516 return JimParseExprOperator(pc);
7517 break;
7518 default:
7519 return JimParseExprOperator(pc);
7520 break;
7522 return JIM_OK;
7525 int JimParseExprNumber(struct JimParserCtx *pc)
7527 int allowdot = 1;
7528 int allowhex = 0;
7530 /* Assume an integer for now */
7531 pc->tt = JIM_TT_EXPR_INT;
7532 pc->tstart = pc->p;
7533 pc->tline = pc->linenr;
7534 while (isdigit(UCHAR(*pc->p))
7535 || (allowhex && isxdigit(UCHAR(*pc->p)))
7536 || (allowdot && *pc->p == '.')
7537 || (pc->p - pc->tstart == 1 && *pc->tstart == '0' && (*pc->p == 'x' || *pc->p == 'X'))
7539 if ((*pc->p == 'x') || (*pc->p == 'X')) {
7540 allowhex = 1;
7541 allowdot = 0;
7543 if (*pc->p == '.') {
7544 allowdot = 0;
7545 pc->tt = JIM_TT_EXPR_DOUBLE;
7547 pc->p++;
7548 pc->len--;
7549 if (!allowhex && (*pc->p == 'e' || *pc->p == 'E') && (pc->p[1] == '-' || pc->p[1] == '+'
7550 || isdigit(UCHAR(pc->p[1])))) {
7551 pc->p += 2;
7552 pc->len -= 2;
7553 pc->tt = JIM_TT_EXPR_DOUBLE;
7556 pc->tend = pc->p - 1;
7557 return JIM_OK;
7560 int JimParseExprIrrational(struct JimParserCtx *pc)
7562 const char *Tokens[] = { "NaN", "nan", "NAN", "Inf", "inf", "INF", NULL };
7563 const char **token;
7565 for (token = Tokens; *token != NULL; token++) {
7566 int len = strlen(*token);
7568 if (strncmp(*token, pc->p, len) == 0) {
7569 pc->tstart = pc->p;
7570 pc->tend = pc->p + len - 1;
7571 pc->p += len;
7572 pc->len -= len;
7573 pc->tline = pc->linenr;
7574 pc->tt = JIM_TT_EXPR_DOUBLE;
7575 return JIM_OK;
7578 return JIM_ERR;
7581 int JimParseExprOperator(struct JimParserCtx *pc)
7583 int i;
7584 int bestIdx = -1, bestLen = 0;
7586 /* Try to get the longest match. */
7587 for (i = JIM_TT_EXPR_OP; i < (signed)JIM_EXPR_OPERATORS_NUM; i++) {
7588 const char *opname;
7589 int oplen;
7591 opname = Jim_ExprOperators[i].name;
7592 if (opname == NULL) {
7593 continue;
7595 oplen = strlen(opname);
7597 if (strncmp(opname, pc->p, oplen) == 0 && oplen > bestLen) {
7598 bestIdx = i;
7599 bestLen = oplen;
7602 if (bestIdx == -1) {
7603 return JIM_ERR;
7606 /* Validate paretheses around function arguments */
7607 if (bestIdx >= JIM_EXPROP_FUNC_FIRST) {
7608 const char *p = pc->p + bestLen;
7609 int len = pc->len - bestLen;
7611 while (len && isspace(UCHAR(*p))) {
7612 len--;
7613 p++;
7615 if (*p != '(') {
7616 return JIM_ERR;
7619 pc->tstart = pc->p;
7620 pc->tend = pc->p + bestLen - 1;
7621 pc->p += bestLen;
7622 pc->len -= bestLen;
7623 pc->tline = pc->linenr;
7625 pc->tt = bestIdx;
7626 return JIM_OK;
7629 static const struct Jim_ExprOperator *JimExprOperatorInfoByOpcode(int opcode)
7631 return &Jim_ExprOperators[opcode];
7634 #if defined(DEBUG_SHOW_SCRIPT) || defined(DEBUG_SHOW_SCRIPT_TOKENS) || defined(DEBUG_SHOW_EXPR) || defined(DEBUG_SHOW_SUBST)
7635 static const char *tt_name(int type)
7637 static const char * const tt_names[JIM_TT_EXPR_OP] =
7638 { "NIL", "STR", "ESC", "VAR", "ARY", "CMD", "SEP", "EOL", "EOF", "LIN", "WRD", "(((", ")))", "INT",
7639 "DBL" };
7640 if (type < JIM_TT_EXPR_OP) {
7641 return tt_names[type];
7643 else {
7644 const struct Jim_ExprOperator *op = JimExprOperatorInfoByOpcode(type);
7645 static char buf[20];
7647 if (op && op->name) {
7648 return op->name;
7650 sprintf(buf, "(%d)", type);
7651 return buf;
7654 #endif
7656 /* -----------------------------------------------------------------------------
7657 * Expression Object
7658 * ---------------------------------------------------------------------------*/
7659 static void FreeExprInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
7660 static void DupExprInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
7661 static int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
7663 static const Jim_ObjType exprObjType = {
7664 "expression",
7665 FreeExprInternalRep,
7666 DupExprInternalRep,
7667 NULL,
7668 JIM_TYPE_REFERENCES,
7671 /* Expr bytecode structure */
7672 typedef struct ExprByteCode
7674 int len; /* Length as number of tokens. */
7675 ScriptToken *token; /* Tokens array. */
7676 int inUse; /* Used for sharing. */
7677 } ExprByteCode;
7679 static void ExprFreeByteCode(Jim_Interp *interp, ExprByteCode * expr)
7681 int i;
7683 for (i = 0; i < expr->len; i++) {
7684 Jim_DecrRefCount(interp, expr->token[i].objPtr);
7686 Jim_Free(expr->token);
7687 Jim_Free(expr);
7690 static void FreeExprInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
7692 ExprByteCode *expr = (void *)objPtr->internalRep.ptr;
7694 if (expr) {
7695 if (--expr->inUse != 0) {
7696 return;
7699 ExprFreeByteCode(interp, expr);
7703 static void DupExprInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
7705 JIM_NOTUSED(interp);
7706 JIM_NOTUSED(srcPtr);
7708 /* Just returns an simple string. */
7709 dupPtr->typePtr = NULL;
7712 /* Check if an expr program looks correct. */
7713 static int ExprCheckCorrectness(ExprByteCode * expr)
7715 int i;
7716 int stacklen = 0;
7717 int ternary = 0;
7719 /* Try to check if there are stack underflows,
7720 * and make sure at the end of the program there is
7721 * a single result on the stack. */
7722 for (i = 0; i < expr->len; i++) {
7723 ScriptToken *t = &expr->token[i];
7724 const struct Jim_ExprOperator *op = JimExprOperatorInfoByOpcode(t->type);
7726 if (op) {
7727 stacklen -= op->arity;
7728 if (stacklen < 0) {
7729 break;
7731 if (t->type == JIM_EXPROP_TERNARY || t->type == JIM_EXPROP_TERNARY_LEFT) {
7732 ternary++;
7734 else if (t->type == JIM_EXPROP_COLON || t->type == JIM_EXPROP_COLON_LEFT) {
7735 ternary--;
7739 /* All operations and operands add one to the stack */
7740 stacklen++;
7742 if (stacklen != 1 || ternary != 0) {
7743 return JIM_ERR;
7745 return JIM_OK;
7748 /* This procedure converts every occurrence of || and && opereators
7749 * in lazy unary versions.
7751 * a b || is converted into:
7753 * a <offset> |L b |R
7755 * a b && is converted into:
7757 * a <offset> &L b &R
7759 * "|L" checks if 'a' is true:
7760 * 1) if it is true pushes 1 and skips <offset> instructions to reach
7761 * the opcode just after |R.
7762 * 2) if it is false does nothing.
7763 * "|R" checks if 'b' is true:
7764 * 1) if it is true pushes 1, otherwise pushes 0.
7766 * "&L" checks if 'a' is true:
7767 * 1) if it is true does nothing.
7768 * 2) If it is false pushes 0 and skips <offset> instructions to reach
7769 * the opcode just after &R
7770 * "&R" checks if 'a' is true:
7771 * if it is true pushes 1, otherwise pushes 0.
7773 static void ExprAddLazyOperator(Jim_Interp *interp, ExprByteCode * expr, ParseToken *t)
7775 int i;
7777 int leftindex, arity, offset;
7779 /* Search for the end of the first operator */
7780 leftindex = expr->len - 1;
7781 arity = 1;
7782 while (arity) {
7783 ScriptToken *tt = &expr->token[leftindex];
7785 if (tt->type >= JIM_TT_EXPR_OP) {
7786 arity += JimExprOperatorInfoByOpcode(tt->type)->arity;
7788 arity--;
7789 leftindex--;
7791 leftindex++;
7793 /* Move them up */
7794 memmove(&expr->token[leftindex + 2], &expr->token[leftindex],
7795 sizeof(*expr->token) * (expr->len - leftindex));
7796 expr->len += 2;
7797 offset = (expr->len - leftindex) - 1;
7799 /* Now we rely on the fact the the left and right version have opcodes
7800 * 1 and 2 after the main opcode respectively
7802 expr->token[leftindex + 1].type = t->type + 1;
7803 expr->token[leftindex + 1].objPtr = interp->emptyObj;
7805 expr->token[leftindex].type = JIM_TT_EXPR_INT;
7806 expr->token[leftindex].objPtr = Jim_NewIntObj(interp, offset);
7808 /* Now add the 'R' operator */
7809 expr->token[expr->len].objPtr = interp->emptyObj;
7810 expr->token[expr->len].type = t->type + 2;
7811 expr->len++;
7813 /* Do we need to adjust the skip count for any &L, |L, ?L or :L in the left operand? */
7814 for (i = leftindex - 1; i > 0; i--) {
7815 if (JimExprOperatorInfoByOpcode(expr->token[i].type)->lazy == LAZY_LEFT) {
7816 if (JimWideValue(expr->token[i - 1].objPtr) + i - 1 >= leftindex) {
7817 expr->token[i - 1].objPtr->internalRep.wideValue += 2;
7823 static void ExprAddOperator(Jim_Interp *interp, ExprByteCode * expr, ParseToken *t)
7825 struct ScriptToken *token = &expr->token[expr->len];
7827 if (JimExprOperatorInfoByOpcode(t->type)->lazy == LAZY_OP) {
7828 ExprAddLazyOperator(interp, expr, t);
7830 else {
7831 token->objPtr = interp->emptyObj;
7832 token->type = t->type;
7833 expr->len++;
7838 * Returns the index of the COLON_LEFT to the left of 'right_index'
7839 * taking into account nesting.
7841 * The expression *must* be well formed, thus a COLON_LEFT will always be found.
7843 static int ExprTernaryGetColonLeftIndex(ExprByteCode *expr, int right_index)
7845 int ternary_count = 1;
7847 right_index--;
7849 while (right_index > 1) {
7850 if (expr->token[right_index].type == JIM_EXPROP_TERNARY_LEFT) {
7851 ternary_count--;
7853 else if (expr->token[right_index].type == JIM_EXPROP_COLON_RIGHT) {
7854 ternary_count++;
7856 else if (expr->token[right_index].type == JIM_EXPROP_COLON_LEFT && ternary_count == 1) {
7857 return right_index;
7859 right_index--;
7862 /*notreached*/
7863 return -1;
7867 * Find the left/right indices for the ternary expression to the left of 'right_index'.
7869 * Returns 1 if found, and fills in *prev_right_index and *prev_left_index.
7870 * Otherwise returns 0.
7872 static int ExprTernaryGetMoveIndices(ExprByteCode *expr, int right_index, int *prev_right_index, int *prev_left_index)
7874 int i = right_index - 1;
7875 int ternary_count = 1;
7877 while (i > 1) {
7878 if (expr->token[i].type == JIM_EXPROP_TERNARY_LEFT) {
7879 if (--ternary_count == 0 && expr->token[i - 2].type == JIM_EXPROP_COLON_RIGHT) {
7880 *prev_right_index = i - 2;
7881 *prev_left_index = ExprTernaryGetColonLeftIndex(expr, *prev_right_index);
7882 return 1;
7885 else if (expr->token[i].type == JIM_EXPROP_COLON_RIGHT) {
7886 if (ternary_count == 0) {
7887 return 0;
7889 ternary_count++;
7891 i--;
7893 return 0;
7897 * ExprTernaryReorderExpression description
7898 * ========================================
7900 * ?: is right-to-left associative which doesn't work with the stack-based
7901 * expression engine. The fix is to reorder the bytecode.
7903 * The expression:
7905 * expr 1?2:0?3:4
7907 * Has initial bytecode:
7909 * '1' '2' (40=TERNARY_LEFT) '2' (41=TERNARY_RIGHT) '2' (43=COLON_LEFT) '0' (44=COLON_RIGHT)
7910 * '2' (40=TERNARY_LEFT) '3' (41=TERNARY_RIGHT) '2' (43=COLON_LEFT) '4' (44=COLON_RIGHT)
7912 * The fix involves simulating this expression instead:
7914 * expr 1?2:(0?3:4)
7916 * With the following bytecode:
7918 * '1' '2' (40=TERNARY_LEFT) '2' (41=TERNARY_RIGHT) '10' (43=COLON_LEFT) '0' '2' (40=TERNARY_LEFT)
7919 * '3' (41=TERNARY_RIGHT) '2' (43=COLON_LEFT) '4' (44=COLON_RIGHT) (44=COLON_RIGHT)
7921 * i.e. The token COLON_RIGHT at index 8 is moved towards the end of the stack, all tokens above 8
7922 * are shifted down and the skip count of the token JIM_EXPROP_COLON_LEFT at index 5 is
7923 * incremented by the amount tokens shifted down. The token JIM_EXPROP_COLON_RIGHT that is moved
7924 * is identified as immediately preceeding a token JIM_EXPROP_TERNARY_LEFT
7926 * ExprTernaryReorderExpression works thus as follows :
7927 * - start from the end of the stack
7928 * - while walking towards the beginning of the stack
7929 * if token=JIM_EXPROP_COLON_RIGHT then
7930 * find the associated token JIM_EXPROP_TERNARY_LEFT, which allows to
7931 * find the associated token previous(JIM_EXPROP_COLON_RIGHT)
7932 * find the associated token previous(JIM_EXPROP_LEFT_RIGHT)
7933 * if all found then
7934 * perform the rotation
7935 * update the skip count of the token previous(JIM_EXPROP_LEFT_RIGHT)
7936 * end if
7937 * end if
7939 * Note: care has to be taken for nested ternary constructs!!!
7941 static void ExprTernaryReorderExpression(Jim_Interp *interp, ExprByteCode *expr)
7943 int i;
7945 for (i = expr->len - 1; i > 1; i--) {
7946 int prev_right_index;
7947 int prev_left_index;
7948 int j;
7949 ScriptToken tmp;
7951 if (expr->token[i].type != JIM_EXPROP_COLON_RIGHT) {
7952 continue;
7955 /* COLON_RIGHT found: get the indexes needed to move the tokens in the stack (if any) */
7956 if (ExprTernaryGetMoveIndices(expr, i, &prev_right_index, &prev_left_index) == 0) {
7957 continue;
7961 ** rotate tokens down
7963 ** +-> [i] : JIM_EXPROP_COLON_RIGHT
7964 ** | | |
7965 ** | V V
7966 ** | [...] : ...
7967 ** | | |
7968 ** | V V
7969 ** | [...] : ...
7970 ** | | |
7971 ** | V V
7972 ** +- [prev_right_index] : JIM_EXPROP_COLON_RIGHT
7974 tmp = expr->token[prev_right_index];
7975 for (j = prev_right_index; j < i; j++) {
7976 expr->token[j] = expr->token[j + 1];
7978 expr->token[i] = tmp;
7980 /* Increment the 'skip' count associated to the previous JIM_EXPROP_COLON_LEFT token
7982 * This is 'colon left increment' = i - prev_right_index
7984 * [prev_left_index] : JIM_EXPROP_LEFT_RIGHT
7985 * [prev_left_index-1] : skip_count
7988 JimWideValue(expr->token[prev_left_index-1].objPtr) += (i - prev_right_index);
7990 /* Adjust for i-- in the loop */
7991 i++;
7995 static ExprByteCode *ExprCreateByteCode(Jim_Interp *interp, const ParseTokenList *tokenlist)
7997 Jim_Stack stack;
7998 ExprByteCode *expr;
7999 int ok = 1;
8000 int i;
8001 int prevtt = JIM_TT_NONE;
8002 int have_ternary = 0;
8004 /* -1 for EOL */
8005 int count = tokenlist->count - 1;
8007 expr = Jim_Alloc(sizeof(*expr));
8008 expr->inUse = 1;
8009 expr->len = 0;
8011 Jim_InitStack(&stack);
8013 /* Need extra bytecodes for lazy operators.
8014 * Also check for the ternary operator
8016 for (i = 0; i < tokenlist->count; i++) {
8017 ParseToken *t = &tokenlist->list[i];
8019 if (JimExprOperatorInfoByOpcode(t->type)->lazy == LAZY_OP) {
8020 count += 2;
8021 /* Ternary is a lazy op but also needs reordering */
8022 if (t->type == JIM_EXPROP_TERNARY) {
8023 have_ternary = 1;
8028 expr->token = Jim_Alloc(sizeof(ScriptToken) * count);
8030 for (i = 0; i < tokenlist->count && ok; i++) {
8031 ParseToken *t = &tokenlist->list[i];
8033 /* Next token will be stored here */
8034 struct ScriptToken *token = &expr->token[expr->len];
8036 if (t->type == JIM_TT_EOL) {
8037 break;
8040 switch (t->type) {
8041 case JIM_TT_STR:
8042 case JIM_TT_ESC:
8043 case JIM_TT_VAR:
8044 case JIM_TT_DICTSUGAR:
8045 case JIM_TT_CMD:
8046 token->objPtr = Jim_NewStringObj(interp, t->token, t->len);
8047 token->type = t->type;
8048 expr->len++;
8049 break;
8051 case JIM_TT_EXPR_INT:
8052 token->objPtr = Jim_NewIntObj(interp, strtoull(t->token, NULL, 0));
8053 token->type = t->type;
8054 expr->len++;
8055 break;
8057 case JIM_TT_EXPR_DOUBLE:
8058 token->objPtr = Jim_NewDoubleObj(interp, strtod(t->token, NULL));
8059 token->type = t->type;
8060 expr->len++;
8061 break;
8063 case JIM_TT_SUBEXPR_START:
8064 Jim_StackPush(&stack, t);
8065 prevtt = JIM_TT_NONE;
8066 continue;
8068 case JIM_TT_SUBEXPR_END:
8069 ok = 0;
8070 while (Jim_StackLen(&stack)) {
8071 ParseToken *tt = Jim_StackPop(&stack);
8073 if (tt->type == JIM_TT_SUBEXPR_START) {
8074 ok = 1;
8075 break;
8078 ExprAddOperator(interp, expr, tt);
8080 if (!ok) {
8081 Jim_SetResultString(interp, "Unexpected close parenthesis", -1);
8082 goto err;
8084 break;
8087 default:{
8088 /* Must be an operator */
8089 const struct Jim_ExprOperator *op;
8090 ParseToken *tt;
8092 /* Convert -/+ to unary minus or unary plus if necessary */
8093 if (prevtt == JIM_TT_NONE || prevtt >= JIM_TT_EXPR_OP) {
8094 if (t->type == JIM_EXPROP_SUB) {
8095 t->type = JIM_EXPROP_UNARYMINUS;
8097 else if (t->type == JIM_EXPROP_ADD) {
8098 t->type = JIM_EXPROP_UNARYPLUS;
8102 op = JimExprOperatorInfoByOpcode(t->type);
8104 /* Now handle precedence */
8105 while ((tt = Jim_StackPeek(&stack)) != NULL) {
8106 const struct Jim_ExprOperator *tt_op =
8107 JimExprOperatorInfoByOpcode(tt->type);
8109 /* Note that right-to-left associativity of ?: operator is handled later */
8111 if (op->arity != 1 && tt_op->precedence >= op->precedence) {
8112 ExprAddOperator(interp, expr, tt);
8113 Jim_StackPop(&stack);
8115 else {
8116 break;
8119 Jim_StackPush(&stack, t);
8120 break;
8123 prevtt = t->type;
8126 /* Reduce any remaining subexpr */
8127 while (Jim_StackLen(&stack)) {
8128 ParseToken *tt = Jim_StackPop(&stack);
8130 if (tt->type == JIM_TT_SUBEXPR_START) {
8131 ok = 0;
8132 Jim_SetResultString(interp, "Missing close parenthesis", -1);
8133 goto err;
8135 ExprAddOperator(interp, expr, tt);
8138 if (have_ternary) {
8139 ExprTernaryReorderExpression(interp, expr);
8142 err:
8143 /* Free the stack used for the compilation. */
8144 Jim_FreeStack(&stack);
8146 for (i = 0; i < expr->len; i++) {
8147 Jim_IncrRefCount(expr->token[i].objPtr);
8150 if (!ok) {
8151 ExprFreeByteCode(interp, expr);
8152 return NULL;
8155 return expr;
8159 /* This method takes the string representation of an expression
8160 * and generates a program for the Expr's stack-based VM. */
8161 int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
8163 int exprTextLen;
8164 const char *exprText;
8165 struct JimParserCtx parser;
8166 struct ExprByteCode *expr;
8167 ParseTokenList tokenlist;
8168 int rc = JIM_ERR;
8170 exprText = Jim_GetString(objPtr, &exprTextLen);
8172 /* Initially tokenise the expression into tokenlist */
8173 ScriptTokenListInit(&tokenlist);
8175 JimParserInit(&parser, exprText, exprTextLen, 0);
8176 while (!JimParserEof(&parser)) {
8177 if (JimParseExpression(&parser) != JIM_OK) {
8178 ScriptTokenListFree(&tokenlist);
8179 invalidexpr:
8180 Jim_SetResultFormatted(interp, "syntax error in expression: \"%#s\"", objPtr);
8181 expr = NULL;
8182 goto err;
8185 ScriptAddToken(&tokenlist, parser.tstart, parser.tend - parser.tstart + 1, parser.tt,
8186 parser.tline);
8189 /* Now create the expression bytecode from the tokenlist */
8190 expr = ExprCreateByteCode(interp, &tokenlist);
8192 /* No longer need the token list */
8193 ScriptTokenListFree(&tokenlist);
8195 if (!expr) {
8196 goto err;
8199 #ifdef DEBUG_SHOW_EXPR
8201 int i;
8203 printf("==== Expr ====\n");
8204 for (i = 0; i < expr->len; i++) {
8205 ScriptToken *t = &expr->token[i];
8207 printf("[%2d] %s '%s'\n", i, tt_name(t->type), Jim_GetString(t->objPtr, NULL));
8210 #endif
8212 /* Check program correctness. */
8213 if (ExprCheckCorrectness(expr) != JIM_OK) {
8214 ExprFreeByteCode(interp, expr);
8215 goto invalidexpr;
8218 rc = JIM_OK;
8220 err:
8221 /* Free the old internal rep and set the new one. */
8222 Jim_FreeIntRep(interp, objPtr);
8223 Jim_SetIntRepPtr(objPtr, expr);
8224 objPtr->typePtr = &exprObjType;
8225 return rc;
8228 static ExprByteCode *Jim_GetExpression(Jim_Interp *interp, Jim_Obj *objPtr)
8230 if (objPtr->typePtr != &exprObjType) {
8231 if (SetExprFromAny(interp, objPtr) != JIM_OK) {
8232 return NULL;
8235 return (ExprByteCode *) Jim_GetIntRepPtr(objPtr);
8238 /* -----------------------------------------------------------------------------
8239 * Expressions evaluation.
8240 * Jim uses a specialized stack-based virtual machine for expressions,
8241 * that takes advantage of the fact that expr's operators
8242 * can't be redefined.
8244 * Jim_EvalExpression() uses the bytecode compiled by
8245 * SetExprFromAny() method of the "expression" object.
8247 * On success a Tcl Object containing the result of the evaluation
8248 * is stored into expResultPtrPtr (having refcount of 1), and JIM_OK is
8249 * returned.
8250 * On error the function returns a retcode != to JIM_OK and set a suitable
8251 * error on the interp.
8252 * ---------------------------------------------------------------------------*/
8253 #define JIM_EE_STATICSTACK_LEN 10
8255 int Jim_EvalExpression(Jim_Interp *interp, Jim_Obj *exprObjPtr, Jim_Obj **exprResultPtrPtr)
8257 ExprByteCode *expr;
8258 Jim_Obj *staticStack[JIM_EE_STATICSTACK_LEN];
8259 int i;
8260 int retcode = JIM_OK;
8261 struct JimExprState e;
8263 expr = Jim_GetExpression(interp, exprObjPtr);
8264 if (!expr) {
8265 return JIM_ERR; /* error in expression. */
8268 #ifdef JIM_OPTIMIZATION
8269 /* Check for one of the following common expressions used by while/for
8271 * CONST
8272 * $a
8273 * !$a
8274 * $a < CONST, $a < $b
8275 * $a <= CONST, $a <= $b
8276 * $a > CONST, $a > $b
8277 * $a >= CONST, $a >= $b
8278 * $a != CONST, $a != $b
8279 * $a == CONST, $a == $b
8282 Jim_Obj *objPtr;
8284 /* STEP 1 -- Check if there are the conditions to run the specialized
8285 * version of while */
8287 switch (expr->len) {
8288 case 1:
8289 if (expr->token[0].type == JIM_TT_EXPR_INT) {
8290 *exprResultPtrPtr = expr->token[0].objPtr;
8291 Jim_IncrRefCount(*exprResultPtrPtr);
8292 return JIM_OK;
8294 if (expr->token[0].type == JIM_TT_VAR) {
8295 objPtr = Jim_GetVariable(interp, expr->token[0].objPtr, JIM_ERRMSG);
8296 if (objPtr) {
8297 *exprResultPtrPtr = objPtr;
8298 Jim_IncrRefCount(*exprResultPtrPtr);
8299 return JIM_OK;
8302 break;
8304 case 2:
8305 if (expr->token[1].type == JIM_EXPROP_NOT && expr->token[0].type == JIM_TT_VAR) {
8306 jim_wide wideValue;
8308 objPtr = Jim_GetVariable(interp, expr->token[0].objPtr, JIM_NONE);
8309 if (objPtr && Jim_IsWide(objPtr)
8310 && Jim_GetWide(interp, objPtr, &wideValue) == JIM_OK) {
8311 *exprResultPtrPtr = wideValue ? interp->falseObj : interp->trueObj;
8312 Jim_IncrRefCount(*exprResultPtrPtr);
8313 return JIM_OK;
8316 break;
8318 case 3:
8319 if (expr->token[0].type == JIM_TT_VAR && (expr->token[1].type == JIM_TT_EXPR_INT
8320 || expr->token[1].type == JIM_TT_VAR)) {
8321 switch (expr->token[2].type) {
8322 case JIM_EXPROP_LT:
8323 case JIM_EXPROP_LTE:
8324 case JIM_EXPROP_GT:
8325 case JIM_EXPROP_GTE:
8326 case JIM_EXPROP_NUMEQ:
8327 case JIM_EXPROP_NUMNE:{
8328 /* optimise ok */
8329 jim_wide wideValueA;
8330 jim_wide wideValueB;
8332 objPtr = Jim_GetVariable(interp, expr->token[0].objPtr, JIM_NONE);
8333 if (objPtr && Jim_IsWide(objPtr)
8334 && Jim_GetWide(interp, objPtr, &wideValueA) == JIM_OK) {
8335 if (expr->token[1].type == JIM_TT_VAR) {
8336 objPtr =
8337 Jim_GetVariable(interp, expr->token[1].objPtr,
8338 JIM_NONE);
8340 else {
8341 objPtr = expr->token[1].objPtr;
8343 if (objPtr && Jim_IsWide(objPtr)
8344 && Jim_GetWide(interp, objPtr, &wideValueB) == JIM_OK) {
8345 int cmpRes;
8347 switch (expr->token[2].type) {
8348 case JIM_EXPROP_LT:
8349 cmpRes = wideValueA < wideValueB;
8350 break;
8351 case JIM_EXPROP_LTE:
8352 cmpRes = wideValueA <= wideValueB;
8353 break;
8354 case JIM_EXPROP_GT:
8355 cmpRes = wideValueA > wideValueB;
8356 break;
8357 case JIM_EXPROP_GTE:
8358 cmpRes = wideValueA >= wideValueB;
8359 break;
8360 case JIM_EXPROP_NUMEQ:
8361 cmpRes = wideValueA == wideValueB;
8362 break;
8363 case JIM_EXPROP_NUMNE:
8364 cmpRes = wideValueA != wideValueB;
8365 break;
8366 default: /*notreached */
8367 cmpRes = 0;
8369 *exprResultPtrPtr =
8370 cmpRes ? interp->trueObj : interp->falseObj;
8371 Jim_IncrRefCount(*exprResultPtrPtr);
8372 return JIM_OK;
8378 break;
8381 #endif
8383 /* In order to avoid that the internal repr gets freed due to
8384 * shimmering of the exprObjPtr's object, we make the internal rep
8385 * shared. */
8386 expr->inUse++;
8388 /* The stack-based expr VM itself */
8390 /* Stack allocation. Expr programs have the feature that
8391 * a program of length N can't require a stack longer than
8392 * N. */
8393 if (expr->len > JIM_EE_STATICSTACK_LEN)
8394 e.stack = Jim_Alloc(sizeof(Jim_Obj *) * expr->len);
8395 else
8396 e.stack = staticStack;
8398 e.stacklen = 0;
8400 /* Execute every instruction */
8401 for (i = 0; i < expr->len && retcode == JIM_OK; i++) {
8402 Jim_Obj *objPtr;
8404 switch (expr->token[i].type) {
8405 case JIM_TT_EXPR_INT:
8406 case JIM_TT_EXPR_DOUBLE:
8407 case JIM_TT_STR:
8408 ExprPush(&e, expr->token[i].objPtr);
8409 break;
8411 case JIM_TT_VAR:
8412 objPtr = Jim_GetVariable(interp, expr->token[i].objPtr, JIM_ERRMSG);
8413 if (objPtr) {
8414 ExprPush(&e, objPtr);
8416 else {
8417 retcode = JIM_ERR;
8419 break;
8421 case JIM_TT_DICTSUGAR:
8422 objPtr = Jim_ExpandDictSugar(interp, expr->token[i].objPtr);
8423 if (objPtr) {
8424 ExprPush(&e, objPtr);
8426 else {
8427 retcode = JIM_ERR;
8429 break;
8431 case JIM_TT_ESC:
8432 retcode = Jim_SubstObj(interp, expr->token[i].objPtr, &objPtr, JIM_NONE);
8433 if (retcode == JIM_OK) {
8434 ExprPush(&e, objPtr);
8436 break;
8438 case JIM_TT_CMD:
8439 retcode = Jim_EvalObj(interp, expr->token[i].objPtr);
8440 if (retcode == JIM_OK) {
8441 ExprPush(&e, Jim_GetResult(interp));
8443 break;
8445 default:{
8446 /* Find and execute the operation */
8447 e.skip = 0;
8448 e.opcode = expr->token[i].type;
8450 retcode = JimExprOperatorInfoByOpcode(e.opcode)->funcop(interp, &e);
8451 /* Skip some opcodes if necessary */
8452 i += e.skip;
8453 continue;
8458 expr->inUse--;
8460 if (retcode == JIM_OK) {
8461 *exprResultPtrPtr = ExprPop(&e);
8463 else {
8464 for (i = 0; i < e.stacklen; i++) {
8465 Jim_DecrRefCount(interp, e.stack[i]);
8468 if (e.stack != staticStack) {
8469 Jim_Free(e.stack);
8471 return retcode;
8474 int Jim_GetBoolFromExpr(Jim_Interp *interp, Jim_Obj *exprObjPtr, int *boolPtr)
8476 int retcode;
8477 jim_wide wideValue;
8478 double doubleValue;
8479 Jim_Obj *exprResultPtr;
8481 retcode = Jim_EvalExpression(interp, exprObjPtr, &exprResultPtr);
8482 if (retcode != JIM_OK)
8483 return retcode;
8485 if (JimGetWideNoErr(interp, exprResultPtr, &wideValue) != JIM_OK) {
8486 if (Jim_GetDouble(interp, exprResultPtr, &doubleValue) != JIM_OK) {
8487 Jim_DecrRefCount(interp, exprResultPtr);
8488 return JIM_ERR;
8490 else {
8491 Jim_DecrRefCount(interp, exprResultPtr);
8492 *boolPtr = doubleValue != 0;
8493 return JIM_OK;
8496 *boolPtr = wideValue != 0;
8498 Jim_DecrRefCount(interp, exprResultPtr);
8499 return JIM_OK;
8502 /* -----------------------------------------------------------------------------
8503 * ScanFormat String Object
8504 * ---------------------------------------------------------------------------*/
8506 /* This Jim_Obj will held a parsed representation of a format string passed to
8507 * the Jim_ScanString command. For error diagnostics, the scanformat string has
8508 * to be parsed in its entirely first and then, if correct, can be used for
8509 * scanning. To avoid endless re-parsing, the parsed representation will be
8510 * stored in an internal representation and re-used for performance reason. */
8512 /* A ScanFmtPartDescr will held the information of /one/ part of the whole
8513 * scanformat string. This part will later be used to extract information
8514 * out from the string to be parsed by Jim_ScanString */
8516 typedef struct ScanFmtPartDescr
8518 char type; /* Type of conversion (e.g. c, d, f) */
8519 char modifier; /* Modify type (e.g. l - long, h - short */
8520 size_t width; /* Maximal width of input to be converted */
8521 int pos; /* -1 - no assign, 0 - natural pos, >0 - XPG3 pos */
8522 char *arg; /* Specification of a CHARSET conversion */
8523 char *prefix; /* Prefix to be scanned literally before conversion */
8524 } ScanFmtPartDescr;
8526 /* The ScanFmtStringObj will hold the internal representation of a scanformat
8527 * string parsed and separated in part descriptions. Furthermore it contains
8528 * the original string representation of the scanformat string to allow for
8529 * fast update of the Jim_Obj's string representation part.
8531 * As an add-on the internal object representation adds some scratch pad area
8532 * for usage by Jim_ScanString to avoid endless allocating and freeing of
8533 * memory for purpose of string scanning.
8535 * The error member points to a static allocated string in case of a mal-
8536 * formed scanformat string or it contains '0' (NULL) in case of a valid
8537 * parse representation.
8539 * The whole memory of the internal representation is allocated as a single
8540 * area of memory that will be internally separated. So freeing and duplicating
8541 * of such an object is cheap */
8543 typedef struct ScanFmtStringObj
8545 jim_wide size; /* Size of internal repr in bytes */
8546 char *stringRep; /* Original string representation */
8547 size_t count; /* Number of ScanFmtPartDescr contained */
8548 size_t convCount; /* Number of conversions that will assign */
8549 size_t maxPos; /* Max position index if XPG3 is used */
8550 const char *error; /* Ptr to error text (NULL if no error */
8551 char *scratch; /* Some scratch pad used by Jim_ScanString */
8552 ScanFmtPartDescr descr[1]; /* The vector of partial descriptions */
8553 } ScanFmtStringObj;
8556 static void FreeScanFmtInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
8557 static void DupScanFmtInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
8558 static void UpdateStringOfScanFmt(Jim_Obj *objPtr);
8560 static const Jim_ObjType scanFmtStringObjType = {
8561 "scanformatstring",
8562 FreeScanFmtInternalRep,
8563 DupScanFmtInternalRep,
8564 UpdateStringOfScanFmt,
8565 JIM_TYPE_NONE,
8568 void FreeScanFmtInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
8570 JIM_NOTUSED(interp);
8571 Jim_Free((char *)objPtr->internalRep.ptr);
8572 objPtr->internalRep.ptr = 0;
8575 void DupScanFmtInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
8577 size_t size = (size_t) ((ScanFmtStringObj *) srcPtr->internalRep.ptr)->size;
8578 ScanFmtStringObj *newVec = (ScanFmtStringObj *) Jim_Alloc(size);
8580 JIM_NOTUSED(interp);
8581 memcpy(newVec, srcPtr->internalRep.ptr, size);
8582 dupPtr->internalRep.ptr = newVec;
8583 dupPtr->typePtr = &scanFmtStringObjType;
8586 void UpdateStringOfScanFmt(Jim_Obj *objPtr)
8588 char *bytes = ((ScanFmtStringObj *) objPtr->internalRep.ptr)->stringRep;
8590 objPtr->bytes = Jim_StrDup(bytes);
8591 objPtr->length = strlen(bytes);
8594 /* SetScanFmtFromAny will parse a given string and create the internal
8595 * representation of the format specification. In case of an error
8596 * the error data member of the internal representation will be set
8597 * to an descriptive error text and the function will be left with
8598 * JIM_ERR to indicate unsucessful parsing (aka. malformed scanformat
8599 * specification */
8601 static int SetScanFmtFromAny(Jim_Interp *interp, Jim_Obj *objPtr)
8603 ScanFmtStringObj *fmtObj;
8604 char *buffer;
8605 int maxCount, i, approxSize, lastPos = -1;
8606 const char *fmt = objPtr->bytes;
8607 int maxFmtLen = objPtr->length;
8608 const char *fmtEnd = fmt + maxFmtLen;
8609 int curr;
8611 Jim_FreeIntRep(interp, objPtr);
8612 /* Count how many conversions could take place maximally */
8613 for (i = 0, maxCount = 0; i < maxFmtLen; ++i)
8614 if (fmt[i] == '%')
8615 ++maxCount;
8616 /* Calculate an approximation of the memory necessary */
8617 approxSize = sizeof(ScanFmtStringObj) /* Size of the container */
8618 +(maxCount + 1) * sizeof(ScanFmtPartDescr) /* Size of all partials */
8619 +maxFmtLen * sizeof(char) + 3 + 1 /* Scratch + "%n" + '\0' */
8620 + maxFmtLen * sizeof(char) + 1 /* Original stringrep */
8621 + maxFmtLen * sizeof(char) /* Arg for CHARSETs */
8622 +(maxCount + 1) * sizeof(char) /* '\0' for every partial */
8623 +1; /* safety byte */
8624 fmtObj = (ScanFmtStringObj *) Jim_Alloc(approxSize);
8625 memset(fmtObj, 0, approxSize);
8626 fmtObj->size = approxSize;
8627 fmtObj->maxPos = 0;
8628 fmtObj->scratch = (char *)&fmtObj->descr[maxCount + 1];
8629 fmtObj->stringRep = fmtObj->scratch + maxFmtLen + 3 + 1;
8630 memcpy(fmtObj->stringRep, fmt, maxFmtLen);
8631 buffer = fmtObj->stringRep + maxFmtLen + 1;
8632 objPtr->internalRep.ptr = fmtObj;
8633 objPtr->typePtr = &scanFmtStringObjType;
8634 for (i = 0, curr = 0; fmt < fmtEnd; ++fmt) {
8635 int width = 0, skip;
8636 ScanFmtPartDescr *descr = &fmtObj->descr[curr];
8638 fmtObj->count++;
8639 descr->width = 0; /* Assume width unspecified */
8640 /* Overread and store any "literal" prefix */
8641 if (*fmt != '%' || fmt[1] == '%') {
8642 descr->type = 0;
8643 descr->prefix = &buffer[i];
8644 for (; fmt < fmtEnd; ++fmt) {
8645 if (*fmt == '%') {
8646 if (fmt[1] != '%')
8647 break;
8648 ++fmt;
8650 buffer[i++] = *fmt;
8652 buffer[i++] = 0;
8654 /* Skip the conversion introducing '%' sign */
8655 ++fmt;
8656 /* End reached due to non-conversion literal only? */
8657 if (fmt >= fmtEnd)
8658 goto done;
8659 descr->pos = 0; /* Assume "natural" positioning */
8660 if (*fmt == '*') {
8661 descr->pos = -1; /* Okay, conversion will not be assigned */
8662 ++fmt;
8664 else
8665 fmtObj->convCount++; /* Otherwise count as assign-conversion */
8666 /* Check if next token is a number (could be width or pos */
8667 if (sscanf(fmt, "%d%n", &width, &skip) == 1) {
8668 fmt += skip;
8669 /* Was the number a XPG3 position specifier? */
8670 if (descr->pos != -1 && *fmt == '$') {
8671 int prev;
8673 ++fmt;
8674 descr->pos = width;
8675 width = 0;
8676 /* Look if "natural" postioning and XPG3 one was mixed */
8677 if ((lastPos == 0 && descr->pos > 0)
8678 || (lastPos > 0 && descr->pos == 0)) {
8679 fmtObj->error = "cannot mix \"%\" and \"%n$\" conversion specifiers";
8680 return JIM_ERR;
8682 /* Look if this position was already used */
8683 for (prev = 0; prev < curr; ++prev) {
8684 if (fmtObj->descr[prev].pos == -1)
8685 continue;
8686 if (fmtObj->descr[prev].pos == descr->pos) {
8687 fmtObj->error =
8688 "variable is assigned by multiple \"%n$\" conversion specifiers";
8689 return JIM_ERR;
8692 /* Try to find a width after the XPG3 specifier */
8693 if (sscanf(fmt, "%d%n", &width, &skip) == 1) {
8694 descr->width = width;
8695 fmt += skip;
8697 if (descr->pos > 0 && (size_t) descr->pos > fmtObj->maxPos)
8698 fmtObj->maxPos = descr->pos;
8700 else {
8701 /* Number was not a XPG3, so it has to be a width */
8702 descr->width = width;
8705 /* If positioning mode was undetermined yet, fix this */
8706 if (lastPos == -1)
8707 lastPos = descr->pos;
8708 /* Handle CHARSET conversion type ... */
8709 if (*fmt == '[') {
8710 int swapped = 1, beg = i, end, j;
8712 descr->type = '[';
8713 descr->arg = &buffer[i];
8714 ++fmt;
8715 if (*fmt == '^')
8716 buffer[i++] = *fmt++;
8717 if (*fmt == ']')
8718 buffer[i++] = *fmt++;
8719 while (*fmt && *fmt != ']')
8720 buffer[i++] = *fmt++;
8721 if (*fmt != ']') {
8722 fmtObj->error = "unmatched [ in format string";
8723 return JIM_ERR;
8725 end = i;
8726 buffer[i++] = 0;
8727 /* In case a range fence was given "backwards", swap it */
8728 while (swapped) {
8729 swapped = 0;
8730 for (j = beg + 1; j < end - 1; ++j) {
8731 if (buffer[j] == '-' && buffer[j - 1] > buffer[j + 1]) {
8732 char tmp = buffer[j - 1];
8734 buffer[j - 1] = buffer[j + 1];
8735 buffer[j + 1] = tmp;
8736 swapped = 1;
8741 else {
8742 /* Remember any valid modifier if given */
8743 if (strchr("hlL", *fmt) != 0)
8744 descr->modifier = tolower((int)*fmt++);
8746 descr->type = *fmt;
8747 if (strchr("efgcsndoxui", *fmt) == 0) {
8748 fmtObj->error = "bad scan conversion character";
8749 return JIM_ERR;
8751 else if (*fmt == 'c' && descr->width != 0) {
8752 fmtObj->error = "field width may not be specified in %c " "conversion";
8753 return JIM_ERR;
8755 else if (*fmt == 'u' && descr->modifier == 'l') {
8756 fmtObj->error = "unsigned wide not supported";
8757 return JIM_ERR;
8760 curr++;
8762 done:
8763 return JIM_OK;
8766 /* Some accessor macros to allow lowlevel access to fields of internal repr */
8768 #define FormatGetCnvCount(_fo_) \
8769 ((ScanFmtStringObj*)((_fo_)->internalRep.ptr))->convCount
8770 #define FormatGetMaxPos(_fo_) \
8771 ((ScanFmtStringObj*)((_fo_)->internalRep.ptr))->maxPos
8772 #define FormatGetError(_fo_) \
8773 ((ScanFmtStringObj*)((_fo_)->internalRep.ptr))->error
8775 /* JimScanAString is used to scan an unspecified string that ends with
8776 * next WS, or a string that is specified via a charset.
8779 static Jim_Obj *JimScanAString(Jim_Interp *interp, const char *sdescr, const char *str)
8781 char *buffer = Jim_StrDup(str);
8782 char *p = buffer;
8784 while (*str) {
8785 int c;
8786 int n;
8788 if (!sdescr && isspace(UCHAR(*str)))
8789 break; /* EOS via WS if unspecified */
8791 n = utf8_tounicode(str, &c);
8792 if (sdescr && !JimCharsetMatch(sdescr, c, JIM_CHARSET_SCAN))
8793 break;
8794 while (n--)
8795 *p++ = *str++;
8797 *p = 0;
8798 return Jim_NewStringObjNoAlloc(interp, buffer, p - buffer);
8801 /* ScanOneEntry will scan one entry out of the string passed as argument.
8802 * It use the sscanf() function for this task. After extracting and
8803 * converting of the value, the count of scanned characters will be
8804 * returned of -1 in case of no conversion tool place and string was
8805 * already scanned thru */
8807 static int ScanOneEntry(Jim_Interp *interp, const char *str, int pos, int strLen,
8808 ScanFmtStringObj * fmtObj, long idx, Jim_Obj **valObjPtr)
8810 const char *tok;
8811 const ScanFmtPartDescr *descr = &fmtObj->descr[idx];
8812 size_t scanned = 0;
8813 size_t anchor = pos;
8814 int i;
8815 Jim_Obj *tmpObj = NULL;
8817 /* First pessimistically assume, we will not scan anything :-) */
8818 *valObjPtr = 0;
8819 if (descr->prefix) {
8820 /* There was a prefix given before the conversion, skip it and adjust
8821 * the string-to-be-parsed accordingly */
8822 /* XXX: Should be checking strLen, not str[pos] */
8823 for (i = 0; pos < strLen && descr->prefix[i]; ++i) {
8824 /* If prefix require, skip WS */
8825 if (isspace(UCHAR(descr->prefix[i])))
8826 while (pos < strLen && isspace(UCHAR(str[pos])))
8827 ++pos;
8828 else if (descr->prefix[i] != str[pos])
8829 break; /* Prefix do not match here, leave the loop */
8830 else
8831 ++pos; /* Prefix matched so far, next round */
8833 if (pos >= strLen) {
8834 return -1; /* All of str consumed: EOF condition */
8836 else if (descr->prefix[i] != 0)
8837 return 0; /* Not whole prefix consumed, no conversion possible */
8839 /* For all but following conversion, skip leading WS */
8840 if (descr->type != 'c' && descr->type != '[' && descr->type != 'n')
8841 while (isspace(UCHAR(str[pos])))
8842 ++pos;
8843 /* Determine how much skipped/scanned so far */
8844 scanned = pos - anchor;
8846 /* %c is a special, simple case. no width */
8847 if (descr->type == 'n') {
8848 /* Return pseudo conversion means: how much scanned so far? */
8849 *valObjPtr = Jim_NewIntObj(interp, anchor + scanned);
8851 else if (pos >= strLen) {
8852 /* Cannot scan anything, as str is totally consumed */
8853 return -1;
8855 else if (descr->type == 'c') {
8856 int c;
8857 scanned += utf8_tounicode(&str[pos], &c);
8858 *valObjPtr = Jim_NewIntObj(interp, c);
8859 return scanned;
8861 else {
8862 /* Processing of conversions follows ... */
8863 if (descr->width > 0) {
8864 /* Do not try to scan as fas as possible but only the given width.
8865 * To ensure this, we copy the part that should be scanned. */
8866 size_t sLen = utf8_strlen(&str[pos], strLen - pos);
8867 size_t tLen = descr->width > sLen ? sLen : descr->width;
8869 tmpObj = Jim_NewStringObjUtf8(interp, str + pos, tLen);
8870 tok = tmpObj->bytes;
8872 else {
8873 /* As no width was given, simply refer to the original string */
8874 tok = &str[pos];
8876 switch (descr->type) {
8877 case 'd':
8878 case 'o':
8879 case 'x':
8880 case 'u':
8881 case 'i':{
8882 char *endp; /* Position where the number finished */
8883 jim_wide w;
8885 int base = descr->type == 'o' ? 8
8886 : descr->type == 'x' ? 16 : descr->type == 'i' ? 0 : 10;
8888 /* Try to scan a number with the given base */
8889 w = strtoull(tok, &endp, base);
8890 if (endp == tok && base == 0) {
8891 /* If scanning failed, and base was undetermined, simply
8892 * put it to 10 and try once more. This should catch the
8893 * case where %i begin to parse a number prefix (e.g.
8894 * '0x' but no further digits follows. This will be
8895 * handled as a ZERO followed by a char 'x' by Tcl */
8896 w = strtoull(tok, &endp, 10);
8899 if (endp != tok) {
8900 /* There was some number sucessfully scanned! */
8901 *valObjPtr = Jim_NewIntObj(interp, w);
8903 /* Adjust the number-of-chars scanned so far */
8904 scanned += endp - tok;
8906 else {
8907 /* Nothing was scanned. We have to determine if this
8908 * happened due to e.g. prefix mismatch or input str
8909 * exhausted */
8910 scanned = *tok ? 0 : -1;
8912 break;
8914 case 's':
8915 case '[':{
8916 *valObjPtr = JimScanAString(interp, descr->arg, tok);
8917 scanned += Jim_Length(*valObjPtr);
8918 break;
8920 case 'e':
8921 case 'f':
8922 case 'g':{
8923 char *endp;
8924 double value = strtod(tok, &endp);
8926 if (endp != tok) {
8927 /* There was some number sucessfully scanned! */
8928 *valObjPtr = Jim_NewDoubleObj(interp, value);
8929 /* Adjust the number-of-chars scanned so far */
8930 scanned += endp - tok;
8932 else {
8933 /* Nothing was scanned. We have to determine if this
8934 * happened due to e.g. prefix mismatch or input str
8935 * exhausted */
8936 scanned = *tok ? 0 : -1;
8938 break;
8941 /* If a substring was allocated (due to pre-defined width) do not
8942 * forget to free it */
8943 if (tmpObj) {
8944 Jim_FreeNewObj(interp, tmpObj);
8947 return scanned;
8950 /* Jim_ScanString is the workhorse of string scanning. It will scan a given
8951 * string and returns all converted (and not ignored) values in a list back
8952 * to the caller. If an error occured, a NULL pointer will be returned */
8954 Jim_Obj *Jim_ScanString(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *fmtObjPtr, int flags)
8956 size_t i, pos;
8957 int scanned = 1;
8958 const char *str = Jim_GetString(strObjPtr, NULL);
8959 int strLen = Jim_Utf8Length(interp, strObjPtr);
8960 Jim_Obj *resultList = 0;
8961 Jim_Obj **resultVec = 0;
8962 int resultc;
8963 Jim_Obj *emptyStr = 0;
8964 ScanFmtStringObj *fmtObj;
8966 /* This should never happen. The format object should already be of the correct type */
8967 if (fmtObjPtr->typePtr != &scanFmtStringObjType) {
8968 Jim_Panic(interp, "Jim_ScanString() for non-scan format");
8969 exit(1);
8971 fmtObj = (ScanFmtStringObj *) fmtObjPtr->internalRep.ptr;
8972 /* Check if format specification was valid */
8973 if (fmtObj->error != 0) {
8974 if (flags & JIM_ERRMSG)
8975 Jim_SetResultString(interp, fmtObj->error, -1);
8976 return 0;
8978 /* Allocate a new "shared" empty string for all unassigned conversions */
8979 emptyStr = Jim_NewEmptyStringObj(interp);
8980 Jim_IncrRefCount(emptyStr);
8981 /* Create a list and fill it with empty strings up to max specified XPG3 */
8982 resultList = Jim_NewListObj(interp, 0, 0);
8983 if (fmtObj->maxPos > 0) {
8984 for (i = 0; i < fmtObj->maxPos; ++i)
8985 Jim_ListAppendElement(interp, resultList, emptyStr);
8986 JimListGetElements(interp, resultList, &resultc, &resultVec);
8988 /* Now handle every partial format description */
8989 for (i = 0, pos = 0; i < fmtObj->count; ++i) {
8990 ScanFmtPartDescr *descr = &(fmtObj->descr[i]);
8991 Jim_Obj *value = 0;
8993 /* Only last type may be "literal" w/o conversion - skip it! */
8994 if (descr->type == 0)
8995 continue;
8996 /* As long as any conversion could be done, we will proceed */
8997 if (scanned > 0)
8998 scanned = ScanOneEntry(interp, str, pos, strLen, fmtObj, i, &value);
8999 /* In case our first try results in EOF, we will leave */
9000 if (scanned == -1 && i == 0)
9001 goto eof;
9002 /* Advance next pos-to-be-scanned for the amount scanned already */
9003 pos += scanned;
9005 /* value == 0 means no conversion took place so take empty string */
9006 if (value == 0)
9007 value = Jim_NewEmptyStringObj(interp);
9008 /* If value is a non-assignable one, skip it */
9009 if (descr->pos == -1) {
9010 Jim_FreeNewObj(interp, value);
9012 else if (descr->pos == 0)
9013 /* Otherwise append it to the result list if no XPG3 was given */
9014 Jim_ListAppendElement(interp, resultList, value);
9015 else if (resultVec[descr->pos - 1] == emptyStr) {
9016 /* But due to given XPG3, put the value into the corr. slot */
9017 Jim_DecrRefCount(interp, resultVec[descr->pos - 1]);
9018 Jim_IncrRefCount(value);
9019 resultVec[descr->pos - 1] = value;
9021 else {
9022 /* Otherwise, the slot was already used - free obj and ERROR */
9023 Jim_FreeNewObj(interp, value);
9024 goto err;
9027 Jim_DecrRefCount(interp, emptyStr);
9028 return resultList;
9029 eof:
9030 Jim_DecrRefCount(interp, emptyStr);
9031 Jim_FreeNewObj(interp, resultList);
9032 return (Jim_Obj *)EOF;
9033 err:
9034 Jim_DecrRefCount(interp, emptyStr);
9035 Jim_FreeNewObj(interp, resultList);
9036 return 0;
9039 /* -----------------------------------------------------------------------------
9040 * Pseudo Random Number Generation
9041 * ---------------------------------------------------------------------------*/
9042 static void JimPrngSeed(Jim_Interp *interp, const unsigned char *seed, int seedLen);
9044 /* Initialize the sbox with the numbers from 0 to 255 */
9045 static void JimPrngInit(Jim_Interp *interp)
9047 int i;
9048 /* XXX: Move off stack */
9049 unsigned int seed[256];
9050 unsigned rseed; /* uninitialized! */
9052 interp->prngState = Jim_Alloc(sizeof(Jim_PrngState));
9053 for (i = 0; i < 256; i++)
9054 seed[i] = (rand_r(&rseed) ^ time(NULL) ^ clock());
9055 JimPrngSeed(interp, (unsigned char *)seed, sizeof(int) * 256);
9058 /* Generates N bytes of random data */
9059 static void JimRandomBytes(Jim_Interp *interp, void *dest, unsigned int len)
9061 Jim_PrngState *prng;
9062 unsigned char *destByte = (unsigned char *)dest;
9063 unsigned int si, sj, x;
9065 /* initialization, only needed the first time */
9066 if (interp->prngState == NULL)
9067 JimPrngInit(interp);
9068 prng = interp->prngState;
9069 /* generates 'len' bytes of pseudo-random numbers */
9070 for (x = 0; x < len; x++) {
9071 prng->i = (prng->i + 1) & 0xff;
9072 si = prng->sbox[prng->i];
9073 prng->j = (prng->j + si) & 0xff;
9074 sj = prng->sbox[prng->j];
9075 prng->sbox[prng->i] = sj;
9076 prng->sbox[prng->j] = si;
9077 *destByte++ = prng->sbox[(si + sj) & 0xff];
9081 /* Re-seed the generator with user-provided bytes */
9082 static void JimPrngSeed(Jim_Interp *interp, const unsigned char *seed, int seedLen)
9084 int i;
9085 /* XXX: Move off stack */
9086 unsigned char buf[256];
9087 Jim_PrngState *prng;
9089 /* initialization, only needed the first time */
9090 if (interp->prngState == NULL)
9091 JimPrngInit(interp);
9092 prng = interp->prngState;
9094 /* Set the sbox[i] with i */
9095 for (i = 0; i < 256; i++)
9096 prng->sbox[i] = i;
9097 /* Now use the seed to perform a random permutation of the sbox */
9098 for (i = 0; i < seedLen; i++) {
9099 unsigned char t;
9101 t = prng->sbox[i & 0xFF];
9102 prng->sbox[i & 0xFF] = prng->sbox[seed[i]];
9103 prng->sbox[seed[i]] = t;
9105 prng->i = prng->j = 0;
9106 /* discard the first 256 bytes of stream. */
9107 JimRandomBytes(interp, buf, 256);
9110 /* [incr] */
9111 static int Jim_IncrCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
9113 jim_wide wideValue, increment = 1;
9114 Jim_Obj *intObjPtr;
9116 if (argc != 2 && argc != 3) {
9117 Jim_WrongNumArgs(interp, 1, argv, "varName ?increment?");
9118 return JIM_ERR;
9120 if (argc == 3) {
9121 if (Jim_GetWide(interp, argv[2], &increment) != JIM_OK)
9122 return JIM_ERR;
9124 intObjPtr = Jim_GetVariable(interp, argv[1], JIM_UNSHARED);
9125 if (!intObjPtr) {
9126 /* Set missing variable to 0 */
9127 wideValue = 0;
9129 else if (Jim_GetWide(interp, intObjPtr, &wideValue) != JIM_OK) {
9130 return JIM_ERR;
9132 if (!intObjPtr || Jim_IsShared(intObjPtr)) {
9133 intObjPtr = Jim_NewIntObj(interp, wideValue + increment);
9134 if (Jim_SetVariable(interp, argv[1], intObjPtr) != JIM_OK) {
9135 Jim_FreeNewObj(interp, intObjPtr);
9136 return JIM_ERR;
9139 else {
9140 Jim_SetWide(interp, intObjPtr, wideValue + increment);
9141 /* The following step is required in order to invalidate the
9142 * string repr of "FOO" if the var name is on the form of "FOO(IDX)" */
9143 if (argv[1]->typePtr != &variableObjType) {
9144 if (Jim_SetVariable(interp, argv[1], intObjPtr) != JIM_OK) {
9145 return JIM_ERR;
9149 Jim_SetResult(interp, intObjPtr);
9150 return JIM_OK;
9154 /* -----------------------------------------------------------------------------
9155 * Eval
9156 * ---------------------------------------------------------------------------*/
9157 #define JIM_EVAL_SARGV_LEN 8 /* static arguments vector length */
9158 #define JIM_EVAL_SINTV_LEN 8 /* static interpolation vector length */
9160 /* Handle calls to the [unknown] command */
9161 static int JimUnknown(Jim_Interp *interp, int argc, Jim_Obj *const *argv, const char *filename,
9162 int linenr)
9164 Jim_Obj **v, *sv[JIM_EVAL_SARGV_LEN];
9165 int retCode;
9167 /* If JimUnknown() is recursively called too many times...
9168 * done here
9170 if (interp->unknown_called > 50) {
9171 return JIM_ERR;
9174 /* If the [unknown] command does not exists returns
9175 * just now */
9176 if (Jim_GetCommand(interp, interp->unknown, JIM_NONE) == NULL)
9177 return JIM_ERR;
9179 /* The object interp->unknown just contains
9180 * the "unknown" string, it is used in order to
9181 * avoid to lookup the unknown command every time
9182 * but instread to cache the result. */
9183 if (argc + 1 <= JIM_EVAL_SARGV_LEN)
9184 v = sv;
9185 else
9186 v = Jim_Alloc(sizeof(Jim_Obj *) * (argc + 1));
9187 /* Make a copy of the arguments vector, but shifted on
9188 * the right of one position. The command name of the
9189 * command will be instead the first argument of the
9190 * [unknown] call. */
9191 memcpy(v + 1, argv, sizeof(Jim_Obj *) * argc);
9192 v[0] = interp->unknown;
9193 /* Call it */
9194 interp->unknown_called++;
9195 retCode = JimEvalObjVector(interp, argc + 1, v, filename, linenr);
9196 interp->unknown_called--;
9198 /* Clean up */
9199 if (v != sv)
9200 Jim_Free(v);
9201 return retCode;
9204 /* Eval the object vector 'objv' composed of 'objc' elements.
9205 * Every element is used as single argument.
9206 * Jim_EvalObj() will call this function every time its object
9207 * argument is of "list" type, with no string representation.
9209 * This is possible because the string representation of a
9210 * list object generated by the UpdateStringOfList is made
9211 * in a way that ensures that every list element is a different
9212 * command argument. */
9213 static int JimEvalObjVector(Jim_Interp *interp, int objc, Jim_Obj *const *objv,
9214 const char *filename, int linenr)
9216 int i, retcode;
9217 Jim_Cmd *cmdPtr;
9219 /* Incr refcount of arguments. */
9220 for (i = 0; i < objc; i++)
9221 Jim_IncrRefCount(objv[i]);
9222 /* Command lookup */
9223 cmdPtr = Jim_GetCommand(interp, objv[0], JIM_ERRMSG);
9224 if (cmdPtr == NULL) {
9225 retcode = JimUnknown(interp, objc, objv, filename, linenr);
9227 else {
9228 /* Call it -- Make sure result is an empty object. */
9229 JimIncrCmdRefCount(cmdPtr);
9230 Jim_SetEmptyResult(interp);
9231 if (cmdPtr->cmdProc) {
9232 interp->cmdPrivData = cmdPtr->privData;
9233 retcode = cmdPtr->cmdProc(interp, objc, objv);
9235 else {
9236 retcode = JimCallProcedure(interp, cmdPtr, filename, linenr, objc, objv);
9238 JimDecrCmdRefCount(interp, cmdPtr);
9240 /* Decr refcount of arguments and return the retcode */
9241 for (i = 0; i < objc; i++)
9242 Jim_DecrRefCount(interp, objv[i]);
9244 return retcode;
9247 int Jim_EvalObjVector(Jim_Interp *interp, int objc, Jim_Obj *const *objv)
9249 return JimEvalObjVector(interp, objc, objv, NULL, 0);
9253 * Invokes 'prefix' as a command with the objv array as arguments.
9255 int Jim_EvalObjPrefix(Jim_Interp *interp, const char *prefix, int objc, Jim_Obj *const *objv)
9257 int i;
9258 int ret;
9259 Jim_Obj **nargv = Jim_Alloc((objc + 1) * sizeof(*nargv));
9261 nargv[0] = Jim_NewStringObj(interp, prefix, -1);
9262 for (i = 0; i < objc; i++) {
9263 nargv[i + 1] = objv[i];
9265 ret = Jim_EvalObjVector(interp, objc + 1, nargv);
9266 Jim_Free(nargv);
9267 return ret;
9270 /* Interpolate the given tokens into a unique Jim_Obj returned by reference
9271 * via *objPtrPtr. This function is only called by Jim_EvalObj().
9272 * The returned object has refcount = 0. */
9273 int Jim_InterpolateTokens(Jim_Interp *interp, ScriptToken * token, int tokens, Jim_Obj **objPtrPtr)
9275 int totlen = 0, i, retcode;
9276 Jim_Obj **intv;
9277 Jim_Obj *sintv[JIM_EVAL_SINTV_LEN];
9278 Jim_Obj *objPtr;
9279 char *s;
9281 if (tokens <= JIM_EVAL_SINTV_LEN)
9282 intv = sintv;
9283 else
9284 intv = Jim_Alloc(sizeof(Jim_Obj *) * tokens);
9285 /* Compute every token forming the argument
9286 * in the intv objects vector. */
9287 for (i = 0; i < tokens; i++) {
9288 switch (token[i].type) {
9289 case JIM_TT_ESC:
9290 case JIM_TT_STR:
9291 intv[i] = token[i].objPtr;
9292 break;
9293 case JIM_TT_VAR:
9294 intv[i] = Jim_GetVariable(interp, token[i].objPtr, JIM_ERRMSG);
9295 if (!intv[i]) {
9296 retcode = JIM_ERR;
9297 goto err;
9299 break;
9300 case JIM_TT_DICTSUGAR:
9301 intv[i] = Jim_ExpandDictSugar(interp, token[i].objPtr);
9302 if (!intv[i]) {
9303 retcode = JIM_ERR;
9304 goto err;
9306 break;
9307 case JIM_TT_CMD:
9308 retcode = Jim_EvalObj(interp, token[i].objPtr);
9309 if (retcode != JIM_OK)
9310 goto err;
9311 intv[i] = Jim_GetResult(interp);
9312 break;
9313 default:
9314 Jim_Panic(interp, "default token type reached " "in Jim_InterpolateTokens().");
9315 exit(1);
9317 Jim_IncrRefCount(intv[i]);
9318 /* Make sure there is a valid
9319 * string rep, and add the string
9320 * length to the total legnth. */
9321 Jim_GetString(intv[i], NULL);
9322 totlen += intv[i]->length;
9324 /* Concatenate every token in an unique
9325 * object. */
9326 objPtr = Jim_NewStringObjNoAlloc(interp, NULL, 0);
9328 if (tokens == 4 && token[0].type == JIM_TT_ESC && token[1].type == JIM_TT_ESC
9329 && token[2].type == JIM_TT_VAR) {
9330 /* May be able to do fast interpolated object -> dictSubst */
9331 objPtr->typePtr = &interpolatedObjType;
9332 objPtr->internalRep.twoPtrValue.ptr1 = token;
9333 objPtr->internalRep.twoPtrValue.ptr2 = intv[2];
9334 Jim_IncrRefCount(intv[2]);
9337 s = objPtr->bytes = Jim_Alloc(totlen + 1);
9338 objPtr->length = totlen;
9339 for (i = 0; i < tokens; i++) {
9340 memcpy(s, intv[i]->bytes, intv[i]->length);
9341 s += intv[i]->length;
9342 Jim_DecrRefCount(interp, intv[i]);
9344 objPtr->bytes[totlen] = '\0';
9345 /* Free the intv vector if not static. */
9346 if (tokens > JIM_EVAL_SINTV_LEN)
9347 Jim_Free(intv);
9349 *objPtrPtr = objPtr;
9350 return JIM_OK;
9351 err:
9352 i--;
9353 for (; i >= 0; i--)
9354 Jim_DecrRefCount(interp, intv[i]);
9355 if (tokens > JIM_EVAL_SINTV_LEN)
9356 Jim_Free(intv);
9357 return retcode;
9360 static void JimAddErrorToStack(Jim_Interp *interp, int retcode, const char *filename, int line)
9362 int rc = retcode;
9364 if (rc == JIM_ERR && !interp->errorFlag) {
9365 /* This is the first error, so save the file/line information and reset the stack */
9366 interp->errorFlag = 1;
9367 JimSetErrorFileName(interp, filename);
9368 JimSetErrorLineNumber(interp, line);
9370 JimResetStackTrace(interp);
9371 /* Always add a level where the error first occurs */
9372 interp->addStackTrace++;
9375 /* Now if this is an "interesting" level, add it to the stack trace */
9376 if (rc == JIM_ERR && interp->addStackTrace > 0) {
9377 /* Add the stack info for the current level */
9379 JimAppendStackTrace(interp, Jim_GetString(interp->errorProc, NULL), filename, line);
9381 /* Note: if we didn't have a filename for this level,
9382 * don't clear the addStackTrace flag
9383 * so we can pick it up at the next level
9385 if (*filename) {
9386 interp->addStackTrace = 0;
9389 Jim_DecrRefCount(interp, interp->errorProc);
9390 interp->errorProc = interp->emptyObj;
9391 Jim_IncrRefCount(interp->errorProc);
9393 else if (rc == JIM_RETURN && interp->returnCode == JIM_ERR) {
9394 /* Propagate the addStackTrace value through 'return -code error' */
9396 else {
9397 interp->addStackTrace = 0;
9401 /* And delete any local procs */
9402 static void JimDeleteLocalProcs(Jim_Interp *interp)
9404 if (interp->localProcs) {
9405 char *procname;
9407 while ((procname = Jim_StackPop(interp->localProcs)) != NULL) {
9408 Jim_DeleteCommand(interp, procname);
9409 Jim_Free(procname);
9411 Jim_FreeStack(interp->localProcs);
9412 Jim_Free(interp->localProcs);
9413 interp->localProcs = NULL;
9417 /* If listPtr is a list, call JimEvalObjVector() with the given source info.
9418 * Otherwise eval with Jim_EvalObj()
9420 int Jim_EvalObjList(Jim_Interp *interp, Jim_Obj *listPtr, const char *filename, int linenr)
9422 if (!Jim_IsList(listPtr)) {
9423 return Jim_EvalObj(interp, listPtr);
9425 else {
9426 int retcode = JIM_OK;
9428 if (listPtr->internalRep.listValue.len) {
9429 Jim_IncrRefCount(listPtr);
9430 retcode = JimEvalObjVector(interp,
9431 listPtr->internalRep.listValue.len,
9432 listPtr->internalRep.listValue.ele, filename, linenr);
9433 Jim_DecrRefCount(interp, listPtr);
9435 return retcode;
9439 int Jim_EvalObj(Jim_Interp *interp, Jim_Obj *scriptObjPtr)
9441 int i;
9442 ScriptObj *script;
9443 ScriptToken *token;
9444 int retcode = JIM_OK;
9445 Jim_Obj *sargv[JIM_EVAL_SARGV_LEN], **argv = NULL;
9446 int linenr = 0;
9448 interp->errorFlag = 0;
9450 /* If the object is of type "list", we can call
9451 * a specialized version of Jim_EvalObj() */
9452 if (Jim_IsList(scriptObjPtr)) {
9453 return Jim_EvalObjList(interp, scriptObjPtr, NULL, 0);
9456 Jim_IncrRefCount(scriptObjPtr); /* Make sure it's shared. */
9457 script = Jim_GetScript(interp, scriptObjPtr);
9459 /* Reset the interpreter result. This is useful to
9460 * return the empty result in the case of empty program. */
9461 Jim_SetEmptyResult(interp);
9463 #ifdef JIM_OPTIMIZATION
9464 /* Check for one of the following common scripts used by for, while
9466 * {}
9467 * incr a
9469 if (script->len == 0) {
9470 Jim_DecrRefCount(interp, scriptObjPtr);
9471 return JIM_OK;
9473 if (script->len == 3
9474 && script->token[1].objPtr->typePtr == &commandObjType
9475 && script->token[1].objPtr->internalRep.cmdValue.cmdPtr->cmdProc == Jim_IncrCoreCommand
9476 && script->token[2].objPtr->typePtr == &variableObjType) {
9478 Jim_Obj *objPtr = Jim_GetVariable(interp, script->token[2].objPtr, JIM_NONE);
9480 if (objPtr && !Jim_IsShared(objPtr) && objPtr->typePtr == &intObjType) {
9481 objPtr->internalRep.wideValue++;
9482 Jim_InvalidateStringRep(objPtr);
9483 Jim_DecrRefCount(interp, scriptObjPtr);
9484 Jim_SetResult(interp, objPtr);
9485 return JIM_OK;
9488 #endif
9490 /* Now we have to make sure the internal repr will not be
9491 * freed on shimmering.
9493 * Think for example to this:
9495 * set x {llength $x; ... some more code ...}; eval $x
9497 * In order to preserve the internal rep, we increment the
9498 * inUse field of the script internal rep structure. */
9499 script->inUse++;
9501 token = script->token;
9502 argv = sargv;
9504 /* Execute every command sequentially until the end of the script
9505 * or an error occurs.
9507 for (i = 0; i < script->len && retcode == JIM_OK; ) {
9508 int argc;
9509 int j;
9510 Jim_Cmd *cmd;
9512 /* First token of the line is always JIM_TT_LINE */
9513 argc = token[i].objPtr->internalRep.scriptLineValue.argc;
9514 linenr = token[i].objPtr->internalRep.scriptLineValue.line;
9516 /* Allocate the arguments vector if required */
9517 if (argc > JIM_EVAL_SARGV_LEN)
9518 argv = Jim_Alloc(sizeof(Jim_Obj *) * argc);
9520 /* Skip the JIM_TT_LINE token */
9521 i++;
9523 /* Populate the arguments objects.
9524 * If an error occurs, retcode will be set and
9525 * 'j' will be set to the number of args expanded
9527 for (j = 0; j < argc; j++) {
9528 long wordtokens = 1;
9529 int expand = 0;
9530 Jim_Obj *wordObjPtr = NULL;
9532 if (token[i].type == JIM_TT_WORD) {
9533 wordtokens = JimWideValue(token[i++].objPtr);
9534 if (wordtokens < 0) {
9535 expand = 1;
9536 wordtokens = -wordtokens;
9540 if (wordtokens == 1) {
9541 /* Fast path if the token does not
9542 * need interpolation */
9544 switch (token[i].type) {
9545 case JIM_TT_ESC:
9546 case JIM_TT_STR:
9547 wordObjPtr = token[i].objPtr;
9548 break;
9549 case JIM_TT_VAR:
9550 wordObjPtr = Jim_GetVariable(interp, token[i].objPtr, JIM_ERRMSG);
9551 break;
9552 case JIM_TT_DICTSUGAR:
9553 wordObjPtr = Jim_ExpandDictSugar(interp, token[i].objPtr);
9554 break;
9555 case JIM_TT_CMD:
9556 retcode = Jim_EvalObj(interp, token[i].objPtr);
9557 if (retcode == JIM_OK) {
9558 wordObjPtr = Jim_GetResult(interp);
9560 break;
9561 default:
9562 Jim_Panic(interp, "default token type reached " "in Jim_EvalObj().");
9563 exit(1);
9566 else {
9567 /* For interpolation we call a helper
9568 * function to do the work for us. */
9569 retcode = Jim_InterpolateTokens(interp, token + i, wordtokens, &wordObjPtr);
9572 if (!wordObjPtr) {
9573 if (retcode == JIM_OK) {
9574 retcode = JIM_ERR;
9576 break;
9579 Jim_IncrRefCount(wordObjPtr);
9580 i += wordtokens;
9582 if (!expand) {
9583 argv[j] = wordObjPtr;
9585 else {
9586 /* Need to expand wordObjPtr into multiple args from argv[j] ... */
9587 int len = Jim_ListLength(interp, wordObjPtr);
9588 int newargc = argc + len - 1;
9589 int k;
9591 if (len > 1) {
9592 if (argv == sargv) {
9593 if (newargc > JIM_EVAL_SARGV_LEN) {
9594 argv = Jim_Alloc(sizeof(*argv) * newargc);
9595 memcpy(argv, sargv, sizeof(*argv) * j);
9598 else {
9599 /* Need to realloc to make room for (len - 1) more entries */
9600 argv = Jim_Realloc(argv, sizeof(*argv) * newargc);
9604 /* Now copy in the expanded version */
9605 for (k = 0; k < len; k++) {
9606 argv[j++] = wordObjPtr->internalRep.listValue.ele[k];
9607 Jim_IncrRefCount(wordObjPtr->internalRep.listValue.ele[k]);
9610 /* The original object reference is no longer needed,
9611 * after the expansion it is no longer present on
9612 * the argument vector, but the single elements are
9613 * in its place. */
9614 Jim_DecrRefCount(interp, wordObjPtr);
9616 /* And update the indexes */
9617 j--;
9618 argc += len - 1;
9622 if (retcode == JIM_OK && argc) {
9623 /* Lookup the command to call */
9624 cmd = Jim_GetCommand(interp, argv[0], JIM_ERRMSG);
9625 if (cmd != NULL) {
9626 /* Call it -- Make sure result is an empty object. */
9627 JimIncrCmdRefCount(cmd);
9628 Jim_SetEmptyResult(interp);
9629 if (cmd->cmdProc) {
9630 interp->cmdPrivData = cmd->privData;
9631 retcode = cmd->cmdProc(interp, argc, argv);
9633 else {
9634 retcode =
9635 JimCallProcedure(interp, cmd, script->fileName, linenr, argc, argv);
9637 JimDecrCmdRefCount(interp, cmd);
9639 else {
9640 /* Call [unknown] */
9641 retcode = JimUnknown(interp, argc, argv, script->fileName, linenr);
9643 if (interp->signal_level && interp->sigmask) {
9644 /* Check for a signal after each command */
9645 retcode = JIM_SIGNAL;
9649 /* Finished with the command, so decrement ref counts of each argument */
9650 while (j-- > 0) {
9651 Jim_DecrRefCount(interp, argv[j]);
9654 if (argv != sargv) {
9655 Jim_Free(argv);
9656 argv = sargv;
9660 /* Possibly add to the error stack trace */
9661 JimAddErrorToStack(interp, retcode, script->fileName, linenr);
9663 /* Note that we don't have to decrement inUse, because the
9664 * following code transfers our use of the reference again to
9665 * the script object. */
9666 Jim_FreeIntRep(interp, scriptObjPtr);
9667 scriptObjPtr->typePtr = &scriptObjType;
9668 Jim_SetIntRepPtr(scriptObjPtr, script);
9669 Jim_DecrRefCount(interp, scriptObjPtr);
9671 return retcode;
9674 /* Call a procedure implemented in Tcl.
9675 * It's possible to speed-up a lot this function, currently
9676 * the callframes are not cached, but allocated and
9677 * destroied every time. What is expecially costly is
9678 * to create/destroy the local vars hash table every time.
9680 * This can be fixed just implementing callframes caching
9681 * in JimCreateCallFrame() and JimFreeCallFrame(). */
9682 int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, const char *filename, int linenr, int argc,
9683 Jim_Obj *const *argv)
9685 int i, d, retcode;
9686 Jim_CallFrame *callFramePtr;
9687 Jim_Obj *argObjPtr;
9688 Jim_Obj *procname = argv[0];
9689 Jim_Stack *prevLocalProcs;
9691 /* Check arity */
9692 if (argc - 1 < cmd->leftArity + cmd->rightArity ||
9693 (!cmd->args && argc - 1 > cmd->leftArity + cmd->rightArity + cmd->optionalArgs)) {
9694 /* Create a nice error message, consistent with Tcl 8.5 */
9695 Jim_Obj *argmsg = Jim_NewStringObj(interp, "", 0);
9696 int arglen = Jim_ListLength(interp, cmd->argListObjPtr);
9698 for (i = 0; i < arglen; i++) {
9699 Jim_ListIndex(interp, cmd->argListObjPtr, i, &argObjPtr, JIM_NONE);
9701 Jim_AppendString(interp, argmsg, " ", 1);
9703 if (i < cmd->leftArity || i >= arglen - cmd->rightArity) {
9704 Jim_AppendObj(interp, argmsg, argObjPtr);
9706 else if (i == arglen - cmd->rightArity - cmd->args) {
9707 Jim_AppendString(interp, argmsg, "?argument ...?", -1);
9709 else {
9710 Jim_Obj *objPtr;
9712 Jim_AppendString(interp, argmsg, "?", 1);
9713 Jim_ListIndex(interp, argObjPtr, 0, &objPtr, JIM_NONE);
9714 Jim_AppendObj(interp, argmsg, objPtr);
9715 Jim_AppendString(interp, argmsg, "?", 1);
9718 Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s%#s\"", procname, argmsg);
9719 Jim_FreeNewObj(interp, argmsg);
9720 return JIM_ERR;
9723 /* Check if there are too nested calls */
9724 if (interp->numLevels == interp->maxNestingDepth) {
9725 Jim_SetResultString(interp, "Too many nested calls. Infinite recursion?", -1);
9726 return JIM_ERR;
9729 /* Create a new callframe */
9730 callFramePtr = JimCreateCallFrame(interp);
9731 callFramePtr->parentCallFrame = interp->framePtr;
9732 callFramePtr->argv = argv;
9733 callFramePtr->argc = argc;
9734 callFramePtr->procArgsObjPtr = cmd->argListObjPtr;
9735 callFramePtr->procBodyObjPtr = cmd->bodyObjPtr;
9736 callFramePtr->staticVars = cmd->staticVars;
9737 callFramePtr->filename = filename;
9738 callFramePtr->line = linenr;
9739 Jim_IncrRefCount(cmd->argListObjPtr);
9740 Jim_IncrRefCount(cmd->bodyObjPtr);
9741 interp->framePtr = callFramePtr;
9742 interp->numLevels++;
9744 /* Simplify arg counting */
9745 argv++;
9746 argc--;
9748 /* Set arguments */
9750 /* Assign in this order:
9751 * leftArity required args.
9752 * rightArity required args (but actually do it last for simplicity)
9753 * optionalArgs optional args
9754 * remaining args into 'args' if 'args'
9757 /* Note that 'd' steps along the arg list, whilst argc/argv follow the supplied args */
9759 /* leftArity required args */
9760 for (d = 0; d < cmd->leftArity; d++) {
9761 Jim_ListIndex(interp, cmd->argListObjPtr, d, &argObjPtr, JIM_NONE);
9762 Jim_SetVariable(interp, argObjPtr, *argv++);
9763 argc--;
9766 /* Shorten our idea of the number of supplied args */
9767 argc -= cmd->rightArity;
9769 /* optionalArgs optional args */
9770 for (i = 0; i < cmd->optionalArgs; i++) {
9771 Jim_Obj *nameObjPtr;
9772 Jim_Obj *valueObjPtr;
9774 Jim_ListIndex(interp, cmd->argListObjPtr, d++, &argObjPtr, JIM_NONE);
9776 /* The name is the first element of the list */
9777 Jim_ListIndex(interp, argObjPtr, 0, &nameObjPtr, JIM_NONE);
9778 if (argc) {
9779 valueObjPtr = *argv++;
9780 argc--;
9782 else {
9783 /* No more values, so use default */
9784 /* The value is the second element of the list */
9785 Jim_ListIndex(interp, argObjPtr, 1, &valueObjPtr, JIM_NONE);
9787 Jim_SetVariable(interp, nameObjPtr, valueObjPtr);
9790 /* Any remaining args go to 'args' */
9791 if (cmd->args) {
9792 Jim_Obj *listObjPtr = Jim_NewListObj(interp, argv, argc);
9794 /* Use the 'args' name from the procedure args */
9795 Jim_ListIndex(interp, cmd->argListObjPtr, d, &argObjPtr, JIM_NONE);
9796 Jim_SetVariable(interp, argObjPtr, listObjPtr);
9797 argv += argc;
9798 d++;
9801 /* rightArity required args */
9802 for (i = 0; i < cmd->rightArity; i++) {
9803 Jim_ListIndex(interp, cmd->argListObjPtr, d++, &argObjPtr, JIM_NONE);
9804 Jim_SetVariable(interp, argObjPtr, *argv++);
9807 /* Install a new stack for local procs */
9808 prevLocalProcs = interp->localProcs;
9809 interp->localProcs = NULL;
9811 /* Eval the body */
9812 retcode = Jim_EvalObj(interp, cmd->bodyObjPtr);
9814 /* Delete any local procs */
9815 JimDeleteLocalProcs(interp);
9816 interp->localProcs = prevLocalProcs;
9818 /* Destroy the callframe */
9819 interp->numLevels--;
9820 interp->framePtr = interp->framePtr->parentCallFrame;
9821 if (callFramePtr->vars.size != JIM_HT_INITIAL_SIZE) {
9822 JimFreeCallFrame(interp, callFramePtr, JIM_FCF_NONE);
9824 else {
9825 JimFreeCallFrame(interp, callFramePtr, JIM_FCF_NOHT);
9827 /* Handle the JIM_EVAL return code */
9828 while (retcode == JIM_EVAL) {
9829 Jim_Obj *resultScriptObjPtr = Jim_GetResult(interp);
9831 Jim_IncrRefCount(resultScriptObjPtr);
9832 /* Should be a list! */
9833 retcode = Jim_EvalObjList(interp, resultScriptObjPtr, filename, linenr);
9834 Jim_DecrRefCount(interp, resultScriptObjPtr);
9836 /* Handle the JIM_RETURN return code */
9837 if (retcode == JIM_RETURN) {
9838 if (--interp->returnLevel <= 0) {
9839 retcode = interp->returnCode;
9840 interp->returnCode = JIM_OK;
9841 interp->returnLevel = 0;
9844 else if (retcode == JIM_ERR) {
9845 interp->addStackTrace++;
9846 Jim_DecrRefCount(interp, interp->errorProc);
9847 interp->errorProc = procname;
9848 Jim_IncrRefCount(interp->errorProc);
9850 return retcode;
9853 int Jim_Eval_Named(Jim_Interp *interp, const char *script, const char *filename, int lineno)
9855 int retval;
9856 Jim_Obj *scriptObjPtr;
9858 scriptObjPtr = Jim_NewStringObj(interp, script, -1);
9859 Jim_IncrRefCount(scriptObjPtr);
9862 if (filename) {
9863 Jim_Obj *prevScriptObj;
9865 JimSetSourceInfo(interp, scriptObjPtr, filename, lineno);
9867 prevScriptObj = interp->currentScriptObj;
9868 interp->currentScriptObj = scriptObjPtr;
9870 retval = Jim_EvalObj(interp, scriptObjPtr);
9872 interp->currentScriptObj = prevScriptObj;
9874 else {
9875 retval = Jim_EvalObj(interp, scriptObjPtr);
9877 Jim_DecrRefCount(interp, scriptObjPtr);
9878 return retval;
9881 int Jim_Eval(Jim_Interp *interp, const char *script)
9883 return Jim_Eval_Named(interp, script, NULL, 0);
9886 /* Execute script in the scope of the global level */
9887 int Jim_EvalGlobal(Jim_Interp *interp, const char *script)
9889 Jim_CallFrame *savedFramePtr;
9890 int retval;
9892 savedFramePtr = interp->framePtr;
9893 interp->framePtr = interp->topFramePtr;
9894 retval = Jim_Eval(interp, script);
9895 interp->framePtr = savedFramePtr;
9896 return retval;
9899 #include <sys/stat.h>
9901 int Jim_EvalFile(Jim_Interp *interp, const char *filename)
9903 FILE *fp;
9904 char *buf;
9905 Jim_Obj *scriptObjPtr;
9906 Jim_Obj *prevScriptObj;
9907 Jim_Stack *prevLocalProcs;
9908 struct stat sb;
9909 int retcode;
9910 int readlen;
9911 char missing;
9913 if (stat(filename, &sb) != 0 || (fp = fopen(filename, "rt")) == NULL) {
9914 Jim_SetResultFormatted(interp, "couldn't read file \"%s\": %s", filename, strerror(errno));
9915 return JIM_ERR;
9917 if (sb.st_size == 0) {
9918 fclose(fp);
9919 return JIM_OK;
9922 buf = Jim_Alloc(sb.st_size + 1);
9923 readlen = fread(buf, 1, sb.st_size, fp);
9924 if (ferror(fp)) {
9925 fclose(fp);
9926 Jim_Free(buf);
9927 Jim_SetResultFormatted(interp, "failed to load file \"%s\": %s", filename, strerror(errno));
9928 return JIM_ERR;
9930 fclose(fp);
9931 buf[readlen] = 0;
9933 if (!Jim_ScriptIsComplete(buf, sb.st_size, &missing)) {
9934 Jim_SetResultFormatted(interp, "missing %s in \"%s\"",
9935 missing == '{' ? "close-brace" : "\"", filename);
9936 Jim_Free(buf);
9937 return JIM_ERR;
9940 scriptObjPtr = Jim_NewStringObjNoAlloc(interp, buf, readlen);
9941 JimSetSourceInfo(interp, scriptObjPtr, filename, 1);
9942 Jim_IncrRefCount(scriptObjPtr);
9944 prevScriptObj = interp->currentScriptObj;
9945 interp->currentScriptObj = scriptObjPtr;
9947 /* Install a new stack for local procs */
9948 prevLocalProcs = interp->localProcs;
9949 interp->localProcs = NULL;
9951 retcode = Jim_EvalObj(interp, scriptObjPtr);
9953 /* Delete any local procs */
9954 JimDeleteLocalProcs(interp);
9955 interp->localProcs = prevLocalProcs;
9957 /* Handle the JIM_RETURN return code */
9958 if (retcode == JIM_RETURN) {
9959 if (--interp->returnLevel <= 0) {
9960 retcode = interp->returnCode;
9961 interp->returnCode = JIM_OK;
9962 interp->returnLevel = 0;
9965 if (retcode == JIM_ERR) {
9966 /* EvalFile changes context, so add a stack frame here */
9967 interp->addStackTrace++;
9970 interp->currentScriptObj = prevScriptObj;
9972 Jim_DecrRefCount(interp, scriptObjPtr);
9974 return retcode;
9977 /* -----------------------------------------------------------------------------
9978 * Subst
9979 * ---------------------------------------------------------------------------*/
9980 static int JimParseSubstStr(struct JimParserCtx *pc)
9982 pc->tstart = pc->p;
9983 pc->tline = pc->linenr;
9984 while (*pc->p && *pc->p != '$' && *pc->p != '[') {
9985 if (*pc->p == '\\' && pc->len > 1) {
9986 pc->p++;
9987 pc->len--;
9989 pc->p++;
9990 pc->len--;
9992 pc->tend = pc->p - 1;
9993 pc->tt = JIM_TT_ESC;
9994 return JIM_OK;
9997 static int JimParseSubst(struct JimParserCtx *pc, int flags)
9999 int retval;
10001 if (pc->len == 0) {
10002 pc->tstart = pc->tend = pc->p;
10003 pc->tline = pc->linenr;
10004 pc->tt = JIM_TT_EOL;
10005 pc->eof = 1;
10006 return JIM_OK;
10008 switch (*pc->p) {
10009 case '[':
10010 retval = JimParseCmd(pc);
10011 if (flags & JIM_SUBST_NOCMD) {
10012 pc->tstart--;
10013 pc->tend++;
10014 pc->tt = (flags & JIM_SUBST_NOESC) ? JIM_TT_STR : JIM_TT_ESC;
10016 return retval;
10017 break;
10018 case '$':
10019 if (JimParseVar(pc) == JIM_ERR) {
10020 pc->tstart = pc->tend = pc->p++;
10021 pc->len--;
10022 pc->tline = pc->linenr;
10023 pc->tt = JIM_TT_STR;
10025 else {
10026 if (flags & JIM_SUBST_NOVAR) {
10027 pc->tstart--;
10028 if (flags & JIM_SUBST_NOESC)
10029 pc->tt = JIM_TT_STR;
10030 else
10031 pc->tt = JIM_TT_ESC;
10032 if (*pc->tstart == '{') {
10033 pc->tstart--;
10034 if (*(pc->tend + 1))
10035 pc->tend++;
10039 break;
10040 default:
10041 retval = JimParseSubstStr(pc);
10042 if (flags & JIM_SUBST_NOESC)
10043 pc->tt = JIM_TT_STR;
10044 return retval;
10045 break;
10047 return JIM_OK;
10050 /* The subst object type reuses most of the data structures and functions
10051 * of the script object. Script's data structures are a bit more complex
10052 * for what is needed for [subst]itution tasks, but the reuse helps to
10053 * deal with a single data structure at the cost of some more memory
10054 * usage for substitutions. */
10055 static const Jim_ObjType substObjType = {
10056 "subst",
10057 FreeScriptInternalRep,
10058 DupScriptInternalRep,
10059 NULL,
10060 JIM_TYPE_REFERENCES,
10063 /* This method takes the string representation of an object
10064 * as a Tcl string where to perform [subst]itution, and generates
10065 * the pre-parsed internal representation. */
10066 static int SetSubstFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr, int flags)
10068 int scriptTextLen;
10069 const char *scriptText = Jim_GetString(objPtr, &scriptTextLen);
10070 struct JimParserCtx parser;
10071 struct ScriptObj *script = Jim_Alloc(sizeof(*script));
10072 ParseTokenList tokenlist;
10074 /* Initially parse the subst into tokens (in tokenlist) */
10075 ScriptTokenListInit(&tokenlist);
10077 JimParserInit(&parser, scriptText, scriptTextLen, 1);
10078 while (1) {
10079 JimParseSubst(&parser, flags);
10080 if (JimParserEof(&parser)) {
10081 /* Note that subst doesn't need the EOL token */
10082 break;
10084 ScriptAddToken(&tokenlist, parser.tstart, parser.tend - parser.tstart + 1, parser.tt,
10085 parser.tline);
10088 /* Create the "real" subst/script tokens from the initial token list */
10089 script->inUse = 1;
10090 script->substFlags = flags;
10091 script->fileName = NULL;
10092 SubstObjAddTokens(interp, script, &tokenlist);
10094 /* No longer need the token list */
10095 ScriptTokenListFree(&tokenlist);
10097 #ifdef DEBUG_SHOW_SUBST
10099 int i;
10101 printf("==== Subst ====\n");
10102 for (i = 0; i < script->len; i++) {
10103 printf("[%2d] %s '%s'\n", i, tt_name(script->token[i].type),
10104 Jim_GetString(script->token[i].objPtr, NULL));
10107 #endif
10109 /* Free the old internal rep and set the new one. */
10110 Jim_FreeIntRep(interp, objPtr);
10111 Jim_SetIntRepPtr(objPtr, script);
10112 objPtr->typePtr = &scriptObjType;
10113 return JIM_OK;
10116 ScriptObj *Jim_GetSubst(Jim_Interp *interp, Jim_Obj *objPtr, int flags)
10118 struct ScriptObj *script = Jim_GetIntRepPtr(objPtr);
10120 if (objPtr->typePtr != &substObjType || script->substFlags != flags)
10121 SetSubstFromAny(interp, objPtr, flags);
10122 return (ScriptObj *) Jim_GetIntRepPtr(objPtr);
10125 /* Performs commands,variables,blackslashes substitution,
10126 * storing the result object (with refcount 0) into
10127 * resObjPtrPtr. */
10128 int Jim_SubstObj(Jim_Interp *interp, Jim_Obj *substObjPtr, Jim_Obj **resObjPtrPtr, int flags)
10130 ScriptObj *script;
10131 ScriptToken *token;
10132 int i, len, retcode = JIM_OK;
10133 int rc;
10134 Jim_Obj *resObjPtr, *savedResultObjPtr;
10136 script = Jim_GetSubst(interp, substObjPtr, flags);
10137 #ifdef JIM_OPTIMIZATION
10138 /* Fast path for a very common case with array-alike syntax,
10139 * that's: $foo($bar) */
10140 if (script->len == 1 && script->token[0].type == JIM_TT_VAR) {
10141 Jim_Obj *varObjPtr = script->token[0].objPtr;
10143 Jim_IncrRefCount(varObjPtr);
10144 resObjPtr = Jim_GetVariable(interp, varObjPtr, JIM_ERRMSG);
10145 if (resObjPtr == NULL) {
10146 Jim_DecrRefCount(interp, varObjPtr);
10147 return JIM_ERR;
10149 Jim_DecrRefCount(interp, varObjPtr);
10150 *resObjPtrPtr = resObjPtr;
10151 return JIM_OK;
10153 #endif
10155 Jim_IncrRefCount(substObjPtr); /* Make sure it's shared. */
10156 /* In order to preserve the internal rep, we increment the
10157 * inUse field of the script internal rep structure. */
10158 script->inUse++;
10160 token = script->token;
10161 len = script->len;
10163 /* Save the interp old result, to set it again before
10164 * to return. */
10165 savedResultObjPtr = interp->result;
10166 Jim_IncrRefCount(savedResultObjPtr);
10168 /* Perform the substitution. Starts with an empty object
10169 * and adds every token (performing the appropriate
10170 * var/command/escape substitution). */
10171 resObjPtr = Jim_NewStringObj(interp, "", 0);
10172 for (i = 0; i < len; i++) {
10173 Jim_Obj *objPtr;
10175 switch (token[i].type) {
10176 case JIM_TT_STR:
10177 case JIM_TT_ESC:
10178 Jim_AppendObj(interp, resObjPtr, token[i].objPtr);
10179 break;
10180 case JIM_TT_VAR:
10181 case JIM_TT_DICTSUGAR:
10182 if (token[i].type == JIM_TT_VAR) {
10183 objPtr = Jim_GetVariable(interp, token[i].objPtr, JIM_ERRMSG);
10185 else {
10186 objPtr = Jim_ExpandDictSugar(interp, token[i].objPtr);
10188 if (objPtr == NULL)
10189 goto err;
10190 Jim_IncrRefCount(objPtr);
10191 Jim_AppendObj(interp, resObjPtr, objPtr);
10192 Jim_DecrRefCount(interp, objPtr);
10193 break;
10194 case JIM_TT_CMD:
10195 rc = Jim_EvalObj(interp, token[i].objPtr);
10196 if (rc == JIM_BREAK) {
10197 /* Stop substituting */
10198 goto ok;
10200 else if (rc == JIM_CONTINUE) {
10201 /* just skip this one */
10203 else if (rc == JIM_OK || rc == JIM_RETURN) {
10204 Jim_AppendObj(interp, resObjPtr, interp->result);
10206 else {
10207 goto err;
10209 break;
10210 default:
10211 Jim_Panic(interp,
10212 "default token type (%d) reached " "in Jim_SubstObj().", token[i].type);
10213 break;
10217 if (retcode == JIM_OK)
10218 Jim_SetResult(interp, savedResultObjPtr);
10219 Jim_DecrRefCount(interp, savedResultObjPtr);
10220 /* Note that we don't have to decrement inUse, because the
10221 * following code transfers our use of the reference again to
10222 * the script object. */
10223 Jim_FreeIntRep(interp, substObjPtr);
10224 substObjPtr->typePtr = &scriptObjType;
10225 Jim_SetIntRepPtr(substObjPtr, script);
10226 Jim_DecrRefCount(interp, substObjPtr);
10227 *resObjPtrPtr = resObjPtr;
10228 return retcode;
10229 err:
10230 Jim_FreeNewObj(interp, resObjPtr);
10231 retcode = JIM_ERR;
10232 goto ok;
10235 /* -----------------------------------------------------------------------------
10236 * Core commands utility functions
10237 * ---------------------------------------------------------------------------*/
10238 void Jim_WrongNumArgs(Jim_Interp *interp, int argc, Jim_Obj *const *argv, const char *msg)
10240 int i;
10241 Jim_Obj *objPtr = Jim_NewEmptyStringObj(interp);
10243 Jim_AppendString(interp, objPtr, "wrong # args: should be \"", -1);
10244 for (i = 0; i < argc; i++) {
10245 Jim_AppendObj(interp, objPtr, argv[i]);
10246 if (!(i + 1 == argc && msg[0] == '\0'))
10247 Jim_AppendString(interp, objPtr, " ", 1);
10249 Jim_AppendString(interp, objPtr, msg, -1);
10250 Jim_AppendString(interp, objPtr, "\"", 1);
10251 Jim_SetResult(interp, objPtr);
10254 #define JimTrivialMatch(pattern) (strpbrk((pattern), "*[?\\") == NULL)
10256 /* type is: 0=commands, 1=procs, 2=channels */
10257 static Jim_Obj *JimCommandsList(Jim_Interp *interp, Jim_Obj *patternObjPtr, int type)
10259 Jim_HashTableIterator *htiter;
10260 Jim_HashEntry *he;
10261 Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0);
10263 /* Check for the non-pattern case. We can do this much more efficiently. */
10264 if (patternObjPtr && JimTrivialMatch(Jim_GetString(patternObjPtr, NULL))) {
10265 Jim_Cmd *cmdPtr = Jim_GetCommand(interp, patternObjPtr, JIM_NONE);
10266 if (cmdPtr) {
10267 if (type == 1 && cmdPtr->cmdProc) {
10268 /* not a proc */
10270 else if (type == 2 && !Jim_AioFilehandle(interp, patternObjPtr)) {
10271 /* not a channel */
10273 else {
10274 Jim_ListAppendElement(interp, listObjPtr, patternObjPtr);
10277 return listObjPtr;
10280 htiter = Jim_GetHashTableIterator(&interp->commands);
10281 while ((he = Jim_NextHashEntry(htiter)) != NULL) {
10282 Jim_Cmd *cmdPtr = he->val;
10283 Jim_Obj *cmdNameObj;
10285 if (type == 1 && cmdPtr->cmdProc) {
10286 /* not a proc */
10287 continue;
10289 if (patternObjPtr && !JimStringMatch(interp, patternObjPtr, he->key, 0))
10290 continue;
10292 cmdNameObj = Jim_NewStringObj(interp, he->key, -1);
10294 /* Is it a channel? */
10295 if (type == 2 && !Jim_AioFilehandle(interp, cmdNameObj)) {
10296 Jim_FreeNewObj(interp, cmdNameObj);
10297 continue;
10300 Jim_ListAppendElement(interp, listObjPtr, cmdNameObj);
10302 Jim_FreeHashTableIterator(htiter);
10303 return listObjPtr;
10306 /* Keep this in order */
10307 #define JIM_VARLIST_GLOBALS 0
10308 #define JIM_VARLIST_LOCALS 1
10309 #define JIM_VARLIST_VARS 2
10311 static Jim_Obj *JimVariablesList(Jim_Interp *interp, Jim_Obj *patternObjPtr, int mode)
10313 Jim_HashTableIterator *htiter;
10314 Jim_HashEntry *he;
10315 Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0);
10317 if (mode == JIM_VARLIST_GLOBALS) {
10318 htiter = Jim_GetHashTableIterator(&interp->topFramePtr->vars);
10320 else {
10321 /* For [info locals], if we are at top level an emtpy list
10322 * is returned. I don't agree, but we aim at compatibility (SS) */
10323 if (mode == JIM_VARLIST_LOCALS && interp->framePtr == interp->topFramePtr)
10324 return listObjPtr;
10325 htiter = Jim_GetHashTableIterator(&interp->framePtr->vars);
10327 while ((he = Jim_NextHashEntry(htiter)) != NULL) {
10328 Jim_Var *varPtr = (Jim_Var *)he->val;
10330 if (mode == JIM_VARLIST_LOCALS) {
10331 if (varPtr->linkFramePtr != NULL)
10332 continue;
10334 if (patternObjPtr && !JimStringMatch(interp, patternObjPtr, he->key, 0))
10335 continue;
10336 Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, he->key, -1));
10338 Jim_FreeHashTableIterator(htiter);
10339 return listObjPtr;
10342 static int JimInfoLevel(Jim_Interp *interp, Jim_Obj *levelObjPtr,
10343 Jim_Obj **objPtrPtr, int info_level_cmd)
10345 Jim_CallFrame *targetCallFrame;
10347 if (JimGetCallFrameByInteger(interp, levelObjPtr, &targetCallFrame)
10348 != JIM_OK)
10349 return JIM_ERR;
10350 /* No proc call at toplevel callframe */
10351 if (targetCallFrame == interp->topFramePtr) {
10352 Jim_SetResultFormatted(interp, "bad level \"%#s\"", levelObjPtr);
10353 return JIM_ERR;
10355 if (info_level_cmd) {
10356 *objPtrPtr = Jim_NewListObj(interp, targetCallFrame->argv, targetCallFrame->argc);
10358 else {
10359 Jim_Obj *listObj = Jim_NewListObj(interp, NULL, 0);
10361 Jim_ListAppendElement(interp, listObj, targetCallFrame->argv[0]);
10362 Jim_ListAppendElement(interp, listObj, Jim_NewStringObj(interp,
10363 targetCallFrame->filename ? targetCallFrame->filename : "", -1));
10364 Jim_ListAppendElement(interp, listObj, Jim_NewIntObj(interp, targetCallFrame->line));
10365 *objPtrPtr = listObj;
10367 return JIM_OK;
10370 /* -----------------------------------------------------------------------------
10371 * Core commands
10372 * ---------------------------------------------------------------------------*/
10374 /* fake [puts] -- not the real puts, just for debugging. */
10375 static int Jim_PutsCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10377 if (argc != 2 && argc != 3) {
10378 Jim_WrongNumArgs(interp, 1, argv, "?-nonewline? string");
10379 return JIM_ERR;
10381 if (argc == 3) {
10382 if (!Jim_CompareStringImmediate(interp, argv[1], "-nonewline")) {
10383 Jim_SetResultString(interp, "The second argument must " "be -nonewline", -1);
10384 return JIM_ERR;
10386 else {
10387 fputs(Jim_GetString(argv[2], NULL), stdout);
10390 else {
10391 puts(Jim_GetString(argv[1], NULL));
10393 return JIM_OK;
10396 /* Helper for [+] and [*] */
10397 static int Jim_AddMulHelper(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int op)
10399 jim_wide wideValue, res;
10400 double doubleValue, doubleRes;
10401 int i;
10403 res = (op == JIM_EXPROP_ADD) ? 0 : 1;
10405 for (i = 1; i < argc; i++) {
10406 if (Jim_GetWide(interp, argv[i], &wideValue) != JIM_OK)
10407 goto trydouble;
10408 if (op == JIM_EXPROP_ADD)
10409 res += wideValue;
10410 else
10411 res *= wideValue;
10413 Jim_SetResultInt(interp, res);
10414 return JIM_OK;
10415 trydouble:
10416 doubleRes = (double)res;
10417 for (; i < argc; i++) {
10418 if (Jim_GetDouble(interp, argv[i], &doubleValue) != JIM_OK)
10419 return JIM_ERR;
10420 if (op == JIM_EXPROP_ADD)
10421 doubleRes += doubleValue;
10422 else
10423 doubleRes *= doubleValue;
10425 Jim_SetResult(interp, Jim_NewDoubleObj(interp, doubleRes));
10426 return JIM_OK;
10429 /* Helper for [-] and [/] */
10430 static int Jim_SubDivHelper(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int op)
10432 jim_wide wideValue, res = 0;
10433 double doubleValue, doubleRes = 0;
10434 int i = 2;
10436 if (argc < 2) {
10437 Jim_WrongNumArgs(interp, 1, argv, "number ?number ... number?");
10438 return JIM_ERR;
10440 else if (argc == 2) {
10441 /* The arity = 2 case is different. For [- x] returns -x,
10442 * while [/ x] returns 1/x. */
10443 if (Jim_GetWide(interp, argv[1], &wideValue) != JIM_OK) {
10444 if (Jim_GetDouble(interp, argv[1], &doubleValue) != JIM_OK) {
10445 return JIM_ERR;
10447 else {
10448 if (op == JIM_EXPROP_SUB)
10449 doubleRes = -doubleValue;
10450 else
10451 doubleRes = 1.0 / doubleValue;
10452 Jim_SetResult(interp, Jim_NewDoubleObj(interp, doubleRes));
10453 return JIM_OK;
10456 if (op == JIM_EXPROP_SUB) {
10457 res = -wideValue;
10458 Jim_SetResultInt(interp, res);
10460 else {
10461 doubleRes = 1.0 / wideValue;
10462 Jim_SetResult(interp, Jim_NewDoubleObj(interp, doubleRes));
10464 return JIM_OK;
10466 else {
10467 if (Jim_GetWide(interp, argv[1], &res) != JIM_OK) {
10468 if (Jim_GetDouble(interp, argv[1], &doubleRes)
10469 != JIM_OK) {
10470 return JIM_ERR;
10472 else {
10473 goto trydouble;
10477 for (i = 2; i < argc; i++) {
10478 if (Jim_GetWide(interp, argv[i], &wideValue) != JIM_OK) {
10479 doubleRes = (double)res;
10480 goto trydouble;
10482 if (op == JIM_EXPROP_SUB)
10483 res -= wideValue;
10484 else
10485 res /= wideValue;
10487 Jim_SetResultInt(interp, res);
10488 return JIM_OK;
10489 trydouble:
10490 for (; i < argc; i++) {
10491 if (Jim_GetDouble(interp, argv[i], &doubleValue) != JIM_OK)
10492 return JIM_ERR;
10493 if (op == JIM_EXPROP_SUB)
10494 doubleRes -= doubleValue;
10495 else
10496 doubleRes /= doubleValue;
10498 Jim_SetResult(interp, Jim_NewDoubleObj(interp, doubleRes));
10499 return JIM_OK;
10503 /* [+] */
10504 static int Jim_AddCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10506 return Jim_AddMulHelper(interp, argc, argv, JIM_EXPROP_ADD);
10509 /* [*] */
10510 static int Jim_MulCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10512 return Jim_AddMulHelper(interp, argc, argv, JIM_EXPROP_MUL);
10515 /* [-] */
10516 static int Jim_SubCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10518 return Jim_SubDivHelper(interp, argc, argv, JIM_EXPROP_SUB);
10521 /* [/] */
10522 static int Jim_DivCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10524 return Jim_SubDivHelper(interp, argc, argv, JIM_EXPROP_DIV);
10527 /* [set] */
10528 static int Jim_SetCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10530 if (argc != 2 && argc != 3) {
10531 Jim_WrongNumArgs(interp, 1, argv, "varName ?newValue?");
10532 return JIM_ERR;
10534 if (argc == 2) {
10535 Jim_Obj *objPtr;
10537 objPtr = Jim_GetVariable(interp, argv[1], JIM_ERRMSG);
10538 if (!objPtr)
10539 return JIM_ERR;
10540 Jim_SetResult(interp, objPtr);
10541 return JIM_OK;
10543 /* argc == 3 case. */
10544 if (Jim_SetVariable(interp, argv[1], argv[2]) != JIM_OK)
10545 return JIM_ERR;
10546 Jim_SetResult(interp, argv[2]);
10547 return JIM_OK;
10550 /* [unset]
10552 * unset ?-nocomplain? ?--? ?varName ...?
10554 static int Jim_UnsetCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10556 int i = 1;
10557 int complain = 1;
10559 while (i < argc) {
10560 if (Jim_CompareStringImmediate(interp, argv[i], "--")) {
10561 i++;
10562 break;
10564 if (Jim_CompareStringImmediate(interp, argv[i], "-nocomplain")) {
10565 complain = 0;
10566 i++;
10567 continue;
10569 break;
10572 while (i < argc) {
10573 if (Jim_UnsetVariable(interp, argv[i], complain ? JIM_ERRMSG : JIM_NONE) != JIM_OK
10574 && complain) {
10575 return JIM_ERR;
10577 i++;
10579 return JIM_OK;
10582 /* [while] */
10583 static int Jim_WhileCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10585 if (argc != 3) {
10586 Jim_WrongNumArgs(interp, 1, argv, "condition body");
10587 return JIM_ERR;
10590 /* The general purpose implementation of while starts here */
10591 while (1) {
10592 int boolean, retval;
10594 if ((retval = Jim_GetBoolFromExpr(interp, argv[1], &boolean)) != JIM_OK)
10595 return retval;
10596 if (!boolean)
10597 break;
10599 if ((retval = Jim_EvalObj(interp, argv[2])) != JIM_OK) {
10600 switch (retval) {
10601 case JIM_BREAK:
10602 goto out;
10603 break;
10604 case JIM_CONTINUE:
10605 continue;
10606 break;
10607 default:
10608 return retval;
10612 out:
10613 Jim_SetEmptyResult(interp);
10614 return JIM_OK;
10617 /* [for] */
10618 static int Jim_ForCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10620 int retval;
10621 int boolean = 1;
10622 Jim_Obj *varNamePtr = NULL;
10623 Jim_Obj *stopVarNamePtr = NULL;
10625 if (argc != 5) {
10626 Jim_WrongNumArgs(interp, 1, argv, "start test next body");
10627 return JIM_ERR;
10630 /* Do the initialisation */
10631 if ((retval = Jim_EvalObj(interp, argv[1])) != JIM_OK) {
10632 return retval;
10635 /* And do the first test now. Better for optimisation
10636 * if we can do next/test at the bottom of the loop
10638 retval = Jim_GetBoolFromExpr(interp, argv[2], &boolean);
10640 /* Ready to do the body as follows:
10641 * while (1) {
10642 * body // check retcode
10643 * next // check retcode
10644 * test // check retcode/test bool
10648 #ifdef JIM_OPTIMIZATION
10649 /* Check if the for is on the form:
10650 * for ... {$i < CONST} {incr i}
10651 * for ... {$i < $j} {incr i}
10653 if (retval == JIM_OK && boolean) {
10654 ScriptObj *incrScript;
10655 ExprByteCode *expr;
10656 jim_wide stop, currentVal;
10657 unsigned jim_wide procEpoch;
10658 Jim_Obj *objPtr;
10659 int cmpOffset;
10661 /* Do it only if there aren't shared arguments */
10662 expr = Jim_GetExpression(interp, argv[2]);
10663 incrScript = Jim_GetScript(interp, argv[3]);
10665 /* Ensure proper lengths to start */
10666 if (incrScript->len != 3 || !expr || expr->len != 3) {
10667 goto evalstart;
10669 /* Ensure proper token types. */
10670 if (incrScript->token[1].type != JIM_TT_ESC ||
10671 expr->token[0].type != JIM_TT_VAR ||
10672 (expr->token[1].type != JIM_TT_EXPR_INT && expr->token[1].type != JIM_TT_VAR)) {
10673 goto evalstart;
10676 if (expr->token[2].type == JIM_EXPROP_LT) {
10677 cmpOffset = 0;
10679 else if (expr->token[2].type == JIM_EXPROP_LTE) {
10680 cmpOffset = 1;
10682 else {
10683 goto evalstart;
10686 /* Update command must be incr */
10687 if (!Jim_CompareStringImmediate(interp, incrScript->token[1].objPtr, "incr")) {
10688 goto evalstart;
10691 /* incr, expression must be about the same variable */
10692 if (!Jim_StringEqObj(incrScript->token[2].objPtr, expr->token[0].objPtr)) {
10693 goto evalstart;
10696 /* Get the stop condition (must be a variable or integer) */
10697 if (expr->token[1].type == JIM_TT_EXPR_INT) {
10698 if (Jim_GetWide(interp, expr->token[1].objPtr, &stop) == JIM_ERR) {
10699 goto evalstart;
10702 else {
10703 stopVarNamePtr = expr->token[1].objPtr;
10704 Jim_IncrRefCount(stopVarNamePtr);
10705 /* Keep the compiler happy */
10706 stop = 0;
10709 /* Initialization */
10710 procEpoch = interp->procEpoch;
10711 varNamePtr = expr->token[0].objPtr;
10712 Jim_IncrRefCount(varNamePtr);
10714 objPtr = Jim_GetVariable(interp, varNamePtr, JIM_NONE);
10715 if (objPtr == NULL || Jim_GetWide(interp, objPtr, &currentVal) != JIM_OK) {
10716 goto testcond;
10719 /* --- OPTIMIZED FOR --- */
10720 while (retval == JIM_OK) {
10721 /* === Check condition === */
10722 /* Note that currentVal is already set here */
10724 /* Immediate or Variable? get the 'stop' value if the latter. */
10725 if (stopVarNamePtr) {
10726 objPtr = Jim_GetVariable(interp, stopVarNamePtr, JIM_NONE);
10727 if (objPtr == NULL || Jim_GetWide(interp, objPtr, &stop) != JIM_OK) {
10728 goto testcond;
10732 if (currentVal >= stop + cmpOffset) {
10733 break;
10736 /* Eval body */
10737 retval = Jim_EvalObj(interp, argv[4]);
10738 if (retval == JIM_OK || retval == JIM_CONTINUE) {
10739 retval = JIM_OK;
10740 /* If there was a change in procedures/command continue
10741 * with the usual [for] command implementation */
10742 if (procEpoch != interp->procEpoch) {
10743 goto evalnext;
10746 objPtr = Jim_GetVariable(interp, varNamePtr, JIM_NONE);
10748 /* Increment */
10749 if (!Jim_IsShared(objPtr) && objPtr->typePtr == &intObjType) {
10750 currentVal = ++objPtr->internalRep.wideValue;
10751 Jim_InvalidateStringRep(objPtr);
10753 else {
10754 if (Jim_GetWide(interp, objPtr, &currentVal) != JIM_OK ||
10755 Jim_SetVariable(interp, varNamePtr, Jim_NewIntObj(interp,
10756 ++currentVal)) != JIM_OK) {
10757 goto evalnext;
10762 goto out;
10764 evalstart:
10765 #endif
10767 while (boolean && (retval == JIM_OK || retval == JIM_CONTINUE)) {
10768 /* Body */
10769 retval = Jim_EvalObj(interp, argv[4]);
10771 if (retval == JIM_OK || retval == JIM_CONTINUE) {
10772 /* increment */
10773 evalnext:
10774 retval = Jim_EvalObj(interp, argv[3]);
10775 if (retval == JIM_OK || retval == JIM_CONTINUE) {
10776 /* test */
10777 testcond:
10778 retval = Jim_GetBoolFromExpr(interp, argv[2], &boolean);
10782 out:
10783 if (stopVarNamePtr) {
10784 Jim_DecrRefCount(interp, stopVarNamePtr);
10786 if (varNamePtr) {
10787 Jim_DecrRefCount(interp, varNamePtr);
10790 if (retval == JIM_CONTINUE || retval == JIM_BREAK || retval == JIM_OK) {
10791 Jim_SetEmptyResult(interp);
10792 return JIM_OK;
10795 return retval;
10798 /* foreach + lmap implementation. */
10799 static int JimForeachMapHelper(Jim_Interp *interp, int argc, Jim_Obj *const *argv, int doMap)
10801 int result = JIM_ERR, i, nbrOfLists, *listsIdx, *listsEnd;
10802 int nbrOfLoops = 0;
10803 Jim_Obj *emptyStr, *script, *mapRes = NULL;
10805 if (argc < 4 || argc % 2 != 0) {
10806 Jim_WrongNumArgs(interp, 1, argv, "varList list ?varList list ...? script");
10807 return JIM_ERR;
10809 if (doMap) {
10810 mapRes = Jim_NewListObj(interp, NULL, 0);
10811 Jim_IncrRefCount(mapRes);
10813 emptyStr = Jim_NewEmptyStringObj(interp);
10814 Jim_IncrRefCount(emptyStr);
10815 script = argv[argc - 1]; /* Last argument is a script */
10816 nbrOfLists = (argc - 1 - 1) / 2; /* argc - 'foreach' - script */
10817 listsIdx = (int *)Jim_Alloc(nbrOfLists * sizeof(int));
10818 listsEnd = (int *)Jim_Alloc(nbrOfLists * 2 * sizeof(int));
10819 /* Initialize iterators and remember max nbr elements each list */
10820 memset(listsIdx, 0, nbrOfLists * sizeof(int));
10821 /* Remember lengths of all lists and calculate how much rounds to loop */
10822 for (i = 0; i < nbrOfLists * 2; i += 2) {
10823 div_t cnt;
10824 int count;
10826 listsEnd[i] = Jim_ListLength(interp, argv[i + 1]);
10827 listsEnd[i + 1] = Jim_ListLength(interp, argv[i + 2]);
10828 if (listsEnd[i] == 0) {
10829 Jim_SetResultString(interp, "foreach varlist is empty", -1);
10830 goto err;
10832 cnt = div(listsEnd[i + 1], listsEnd[i]);
10833 count = cnt.quot + (cnt.rem ? 1 : 0);
10834 if (count > nbrOfLoops)
10835 nbrOfLoops = count;
10837 for (; nbrOfLoops-- > 0;) {
10838 for (i = 0; i < nbrOfLists; ++i) {
10839 int varIdx = 0, var = i * 2;
10841 while (varIdx < listsEnd[var]) {
10842 Jim_Obj *varName, *ele;
10843 int lst = i * 2 + 1;
10845 if (Jim_ListIndex(interp, argv[var + 1], varIdx, &varName, JIM_ERRMSG)
10846 != JIM_OK)
10847 goto err;
10848 if (listsIdx[i] < listsEnd[lst]) {
10849 if (Jim_ListIndex(interp, argv[lst + 1], listsIdx[i], &ele, JIM_ERRMSG)
10850 != JIM_OK)
10851 goto err;
10852 /* Avoid shimmering */
10853 Jim_IncrRefCount(ele);
10854 result = Jim_SetVariable(interp, varName, ele);
10855 Jim_DecrRefCount(interp, ele);
10856 if (result == JIM_OK) {
10857 ++listsIdx[i]; /* Remember next iterator of current list */
10858 ++varIdx; /* Next variable */
10859 continue;
10862 else if (Jim_SetVariable(interp, varName, emptyStr) == JIM_OK) {
10863 ++varIdx; /* Next variable */
10864 continue;
10866 goto err;
10869 switch (result = Jim_EvalObj(interp, script)) {
10870 case JIM_OK:
10871 if (doMap)
10872 Jim_ListAppendElement(interp, mapRes, interp->result);
10873 break;
10874 case JIM_CONTINUE:
10875 break;
10876 case JIM_BREAK:
10877 goto out;
10878 break;
10879 default:
10880 goto err;
10883 out:
10884 result = JIM_OK;
10885 if (doMap)
10886 Jim_SetResult(interp, mapRes);
10887 else
10888 Jim_SetEmptyResult(interp);
10889 err:
10890 if (doMap)
10891 Jim_DecrRefCount(interp, mapRes);
10892 Jim_DecrRefCount(interp, emptyStr);
10893 Jim_Free(listsIdx);
10894 Jim_Free(listsEnd);
10895 return result;
10898 /* [foreach] */
10899 static int Jim_ForeachCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10901 return JimForeachMapHelper(interp, argc, argv, 0);
10904 /* [lmap] */
10905 static int Jim_LmapCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10907 return JimForeachMapHelper(interp, argc, argv, 1);
10910 /* [if] */
10911 static int Jim_IfCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10913 int boolean, retval, current = 1, falsebody = 0;
10915 if (argc >= 3) {
10916 while (1) {
10917 /* Far not enough arguments given! */
10918 if (current >= argc)
10919 goto err;
10920 if ((retval = Jim_GetBoolFromExpr(interp, argv[current++], &boolean))
10921 != JIM_OK)
10922 return retval;
10923 /* There lacks something, isn't it? */
10924 if (current >= argc)
10925 goto err;
10926 if (Jim_CompareStringImmediate(interp, argv[current], "then"))
10927 current++;
10928 /* Tsk tsk, no then-clause? */
10929 if (current >= argc)
10930 goto err;
10931 if (boolean)
10932 return Jim_EvalObj(interp, argv[current]);
10933 /* Ok: no else-clause follows */
10934 if (++current >= argc) {
10935 Jim_SetResult(interp, Jim_NewEmptyStringObj(interp));
10936 return JIM_OK;
10938 falsebody = current++;
10939 if (Jim_CompareStringImmediate(interp, argv[falsebody], "else")) {
10940 /* IIICKS - else-clause isn't last cmd? */
10941 if (current != argc - 1)
10942 goto err;
10943 return Jim_EvalObj(interp, argv[current]);
10945 else if (Jim_CompareStringImmediate(interp, argv[falsebody], "elseif"))
10946 /* Ok: elseif follows meaning all the stuff
10947 * again (how boring...) */
10948 continue;
10949 /* OOPS - else-clause is not last cmd? */
10950 else if (falsebody != argc - 1)
10951 goto err;
10952 return Jim_EvalObj(interp, argv[falsebody]);
10954 return JIM_OK;
10956 err:
10957 Jim_WrongNumArgs(interp, 1, argv, "condition ?then? trueBody ?elseif ...? ?else? falseBody");
10958 return JIM_ERR;
10962 /* Returns 1 if match, 0 if no match or -<error> on error (e.g. -JIM_ERR, -JIM_BREAK)*/
10963 int Jim_CommandMatchObj(Jim_Interp *interp, Jim_Obj *commandObj, Jim_Obj *patternObj,
10964 Jim_Obj *stringObj, int nocase)
10966 Jim_Obj *parms[4];
10967 int argc = 0;
10968 long eq;
10969 int rc;
10971 parms[argc++] = commandObj;
10972 if (nocase) {
10973 parms[argc++] = Jim_NewStringObj(interp, "-nocase", -1);
10975 parms[argc++] = patternObj;
10976 parms[argc++] = stringObj;
10978 rc = Jim_EvalObjVector(interp, argc, parms);
10980 if (rc != JIM_OK || Jim_GetLong(interp, Jim_GetResult(interp), &eq) != JIM_OK) {
10981 eq = -rc;
10984 return eq;
10987 enum
10988 { SWITCH_EXACT, SWITCH_GLOB, SWITCH_RE, SWITCH_CMD };
10990 /* [switch] */
10991 static int Jim_SwitchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
10993 int matchOpt = SWITCH_EXACT, opt = 1, patCount, i;
10994 Jim_Obj *command = 0, *const *caseList = 0, *strObj;
10995 Jim_Obj *script = 0;
10997 if (argc < 3) {
10998 wrongnumargs:
10999 Jim_WrongNumArgs(interp, 1, argv, "?options? string "
11000 "pattern body ... ?default body? or " "{pattern body ?pattern body ...?}");
11001 return JIM_ERR;
11003 for (opt = 1; opt < argc; ++opt) {
11004 const char *option = Jim_GetString(argv[opt], 0);
11006 if (*option != '-')
11007 break;
11008 else if (strncmp(option, "--", 2) == 0) {
11009 ++opt;
11010 break;
11012 else if (strncmp(option, "-exact", 2) == 0)
11013 matchOpt = SWITCH_EXACT;
11014 else if (strncmp(option, "-glob", 2) == 0)
11015 matchOpt = SWITCH_GLOB;
11016 else if (strncmp(option, "-regexp", 2) == 0)
11017 matchOpt = SWITCH_RE;
11018 else if (strncmp(option, "-command", 2) == 0) {
11019 matchOpt = SWITCH_CMD;
11020 if ((argc - opt) < 2)
11021 goto wrongnumargs;
11022 command = argv[++opt];
11024 else {
11025 Jim_SetResultFormatted(interp,
11026 "bad option \"%#s\": must be -exact, -glob, -regexp, -command procname or --",
11027 argv[opt]);
11028 return JIM_ERR;
11030 if ((argc - opt) < 2)
11031 goto wrongnumargs;
11033 strObj = argv[opt++];
11034 patCount = argc - opt;
11035 if (patCount == 1) {
11036 Jim_Obj **vector;
11038 JimListGetElements(interp, argv[opt], &patCount, &vector);
11039 caseList = vector;
11041 else
11042 caseList = &argv[opt];
11043 if (patCount == 0 || patCount % 2 != 0)
11044 goto wrongnumargs;
11045 for (i = 0; script == 0 && i < patCount; i += 2) {
11046 Jim_Obj *patObj = caseList[i];
11048 if (!Jim_CompareStringImmediate(interp, patObj, "default")
11049 || i < (patCount - 2)) {
11050 switch (matchOpt) {
11051 case SWITCH_EXACT:
11052 if (Jim_StringEqObj(strObj, patObj))
11053 script = caseList[i + 1];
11054 break;
11055 case SWITCH_GLOB:
11056 if (Jim_StringMatchObj(interp, patObj, strObj, 0))
11057 script = caseList[i + 1];
11058 break;
11059 case SWITCH_RE:
11060 command = Jim_NewStringObj(interp, "regexp", -1);
11061 /* Fall thru intentionally */
11062 case SWITCH_CMD:{
11063 int rc = Jim_CommandMatchObj(interp, command, patObj, strObj, 0);
11065 /* After the execution of a command we need to
11066 * make sure to reconvert the object into a list
11067 * again. Only for the single-list style [switch]. */
11068 if (argc - opt == 1) {
11069 Jim_Obj **vector;
11071 JimListGetElements(interp, argv[opt], &patCount, &vector);
11072 caseList = vector;
11074 /* command is here already decref'd */
11075 if (rc < 0) {
11076 return -rc;
11078 if (rc)
11079 script = caseList[i + 1];
11080 break;
11084 else {
11085 script = caseList[i + 1];
11088 for (; i < patCount && Jim_CompareStringImmediate(interp, script, "-"); i += 2)
11089 script = caseList[i + 1];
11090 if (script && Jim_CompareStringImmediate(interp, script, "-")) {
11091 Jim_SetResultFormatted(interp, "no body specified for pattern \"%#s\"", caseList[i - 2]);
11092 return JIM_ERR;
11094 Jim_SetEmptyResult(interp);
11095 if (script) {
11096 return Jim_EvalObj(interp, script);
11098 return JIM_OK;
11101 /* [list] */
11102 static int Jim_ListCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11104 Jim_Obj *listObjPtr;
11106 listObjPtr = Jim_NewListObj(interp, argv + 1, argc - 1);
11107 Jim_SetResult(interp, listObjPtr);
11108 return JIM_OK;
11111 /* [lindex] */
11112 static int Jim_LindexCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11114 Jim_Obj *objPtr, *listObjPtr;
11115 int i;
11116 int idx;
11118 if (argc < 3) {
11119 Jim_WrongNumArgs(interp, 1, argv, "list index ?...?");
11120 return JIM_ERR;
11122 objPtr = argv[1];
11123 Jim_IncrRefCount(objPtr);
11124 for (i = 2; i < argc; i++) {
11125 listObjPtr = objPtr;
11126 if (Jim_GetIndex(interp, argv[i], &idx) != JIM_OK) {
11127 Jim_DecrRefCount(interp, listObjPtr);
11128 return JIM_ERR;
11130 if (Jim_ListIndex(interp, listObjPtr, idx, &objPtr, JIM_NONE) != JIM_OK) {
11131 /* Returns an empty object if the index
11132 * is out of range. */
11133 Jim_DecrRefCount(interp, listObjPtr);
11134 Jim_SetEmptyResult(interp);
11135 return JIM_OK;
11137 Jim_IncrRefCount(objPtr);
11138 Jim_DecrRefCount(interp, listObjPtr);
11140 Jim_SetResult(interp, objPtr);
11141 Jim_DecrRefCount(interp, objPtr);
11142 return JIM_OK;
11145 /* [llength] */
11146 static int Jim_LlengthCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11148 if (argc != 2) {
11149 Jim_WrongNumArgs(interp, 1, argv, "list");
11150 return JIM_ERR;
11152 Jim_SetResultInt(interp, Jim_ListLength(interp, argv[1]));
11153 return JIM_OK;
11156 /* [lsearch] */
11157 static int Jim_LsearchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11159 static const char * const options[] = {
11160 "-bool", "-not", "-nocase", "-exact", "-glob", "-regexp", "-all", "-inline", "-command",
11161 NULL
11163 enum
11164 { OPT_BOOL, OPT_NOT, OPT_NOCASE, OPT_EXACT, OPT_GLOB, OPT_REGEXP, OPT_ALL, OPT_INLINE,
11165 OPT_COMMAND };
11166 int i;
11167 int opt_bool = 0;
11168 int opt_not = 0;
11169 int opt_nocase = 0;
11170 int opt_all = 0;
11171 int opt_inline = 0;
11172 int opt_match = OPT_EXACT;
11173 int listlen;
11174 int rc = JIM_OK;
11175 Jim_Obj *listObjPtr = NULL;
11176 Jim_Obj *commandObj = NULL;
11178 if (argc < 3) {
11179 wrongargs:
11180 Jim_WrongNumArgs(interp, 1, argv,
11181 "?-exact|-glob|-regexp|-command 'command'? ?-bool|-inline? ?-not? ?-nocase? ?-all? list value");
11182 return JIM_ERR;
11185 for (i = 1; i < argc - 2; i++) {
11186 int option;
11188 if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ERRMSG) != JIM_OK) {
11189 return JIM_ERR;
11191 switch (option) {
11192 case OPT_BOOL:
11193 opt_bool = 1;
11194 opt_inline = 0;
11195 break;
11196 case OPT_NOT:
11197 opt_not = 1;
11198 break;
11199 case OPT_NOCASE:
11200 opt_nocase = 1;
11201 break;
11202 case OPT_INLINE:
11203 opt_inline = 1;
11204 opt_bool = 0;
11205 break;
11206 case OPT_ALL:
11207 opt_all = 1;
11208 break;
11209 case OPT_COMMAND:
11210 if (i >= argc - 2) {
11211 goto wrongargs;
11213 commandObj = argv[++i];
11214 /* fallthru */
11215 case OPT_EXACT:
11216 case OPT_GLOB:
11217 case OPT_REGEXP:
11218 opt_match = option;
11219 break;
11223 argv += i;
11225 if (opt_all) {
11226 listObjPtr = Jim_NewListObj(interp, NULL, 0);
11228 if (opt_match == OPT_REGEXP) {
11229 commandObj = Jim_NewStringObj(interp, "regexp", -1);
11231 if (commandObj) {
11232 Jim_IncrRefCount(commandObj);
11235 listlen = Jim_ListLength(interp, argv[0]);
11236 for (i = 0; i < listlen; i++) {
11237 Jim_Obj *objPtr;
11238 int eq = 0;
11240 Jim_ListIndex(interp, argv[0], i, &objPtr, JIM_NONE);
11241 switch (opt_match) {
11242 case OPT_EXACT:
11243 eq = Jim_StringCompareObj(interp, objPtr, argv[1], opt_nocase) == 0;
11244 break;
11246 case OPT_GLOB:
11247 eq = Jim_StringMatchObj(interp, argv[1], objPtr, opt_nocase);
11248 break;
11250 case OPT_REGEXP:
11251 case OPT_COMMAND:
11252 eq = Jim_CommandMatchObj(interp, commandObj, argv[1], objPtr, opt_nocase);
11253 if (eq < 0) {
11254 if (listObjPtr) {
11255 Jim_FreeNewObj(interp, listObjPtr);
11257 rc = JIM_ERR;
11258 goto done;
11260 break;
11263 /* If we have a non-match with opt_bool, opt_not, !opt_all, can't exit early */
11264 if (!eq && opt_bool && opt_not && !opt_all) {
11265 continue;
11268 if ((!opt_bool && eq == !opt_not) || (opt_bool && (eq || opt_all))) {
11269 /* Got a match (or non-match for opt_not), or (opt_bool && opt_all) */
11270 Jim_Obj *resultObj;
11272 if (opt_bool) {
11273 resultObj = Jim_NewIntObj(interp, eq ^ opt_not);
11275 else if (!opt_inline) {
11276 resultObj = Jim_NewIntObj(interp, i);
11278 else {
11279 resultObj = objPtr;
11282 if (opt_all) {
11283 Jim_ListAppendElement(interp, listObjPtr, resultObj);
11285 else {
11286 Jim_SetResult(interp, resultObj);
11287 goto done;
11292 if (opt_all) {
11293 Jim_SetResult(interp, listObjPtr);
11295 else {
11296 /* No match */
11297 if (opt_bool) {
11298 Jim_SetResultBool(interp, opt_not);
11300 else if (!opt_inline) {
11301 Jim_SetResultInt(interp, -1);
11305 done:
11306 if (commandObj) {
11307 Jim_DecrRefCount(interp, commandObj);
11309 return rc;
11312 /* [lappend] */
11313 static int Jim_LappendCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11315 Jim_Obj *listObjPtr;
11316 int shared, i;
11318 if (argc < 2) {
11319 Jim_WrongNumArgs(interp, 1, argv, "varName ?value value ...?");
11320 return JIM_ERR;
11322 listObjPtr = Jim_GetVariable(interp, argv[1], JIM_UNSHARED);
11323 if (!listObjPtr) {
11324 /* Create the list if it does not exists */
11325 listObjPtr = Jim_NewListObj(interp, NULL, 0);
11326 if (Jim_SetVariable(interp, argv[1], listObjPtr) != JIM_OK) {
11327 Jim_FreeNewObj(interp, listObjPtr);
11328 return JIM_ERR;
11331 shared = Jim_IsShared(listObjPtr);
11332 if (shared)
11333 listObjPtr = Jim_DuplicateObj(interp, listObjPtr);
11334 for (i = 2; i < argc; i++)
11335 Jim_ListAppendElement(interp, listObjPtr, argv[i]);
11336 if (Jim_SetVariable(interp, argv[1], listObjPtr) != JIM_OK) {
11337 if (shared)
11338 Jim_FreeNewObj(interp, listObjPtr);
11339 return JIM_ERR;
11341 Jim_SetResult(interp, listObjPtr);
11342 return JIM_OK;
11345 /* [linsert] */
11346 static int Jim_LinsertCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11348 int idx, len;
11349 Jim_Obj *listPtr;
11351 if (argc < 4) {
11352 Jim_WrongNumArgs(interp, 1, argv, "list index element " "?element ...?");
11353 return JIM_ERR;
11355 listPtr = argv[1];
11356 if (Jim_IsShared(listPtr))
11357 listPtr = Jim_DuplicateObj(interp, listPtr);
11358 if (Jim_GetIndex(interp, argv[2], &idx) != JIM_OK)
11359 goto err;
11360 len = Jim_ListLength(interp, listPtr);
11361 if (idx >= len)
11362 idx = len;
11363 else if (idx < 0)
11364 idx = len + idx + 1;
11365 Jim_ListInsertElements(interp, listPtr, idx, argc - 3, &argv[3]);
11366 Jim_SetResult(interp, listPtr);
11367 return JIM_OK;
11368 err:
11369 if (listPtr != argv[1]) {
11370 Jim_FreeNewObj(interp, listPtr);
11372 return JIM_ERR;
11375 /* [lreplace] */
11376 static int Jim_LreplaceCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11378 int first, last, len, rangeLen;
11379 Jim_Obj *listObj;
11380 Jim_Obj *newListObj;
11381 int i;
11382 int shared;
11384 if (argc < 4) {
11385 Jim_WrongNumArgs(interp, 1, argv, "list first last ?element element ...?");
11386 return JIM_ERR;
11388 if (Jim_GetIndex(interp, argv[2], &first) != JIM_OK ||
11389 Jim_GetIndex(interp, argv[3], &last) != JIM_OK) {
11390 return JIM_ERR;
11393 listObj = argv[1];
11394 len = Jim_ListLength(interp, listObj);
11396 first = JimRelToAbsIndex(len, first);
11397 last = JimRelToAbsIndex(len, last);
11398 JimRelToAbsRange(len, first, last, &first, &last, &rangeLen);
11400 /* Now construct a new list which consists of:
11401 * <elements before first> <supplied elements> <elements after last>
11404 /* Check to see if trying to replace past the end of the list */
11405 if (first < len) {
11406 /* OK. Not past the end */
11408 else if (len == 0) {
11409 /* Special for empty list, adjust first to 0 */
11410 first = 0;
11412 else {
11413 Jim_SetResultString(interp, "list doesn't contain element ", -1);
11414 Jim_AppendObj(interp, Jim_GetResult(interp), argv[2]);
11415 return JIM_ERR;
11418 newListObj = Jim_NewListObj(interp, NULL, 0);
11420 shared = Jim_IsShared(listObj);
11421 if (shared) {
11422 listObj = Jim_DuplicateObj(interp, listObj);
11425 /* Add the first set of elements */
11426 for (i = 0; i < first; i++) {
11427 Jim_ListAppendElement(interp, newListObj, listObj->internalRep.listValue.ele[i]);
11430 /* Add supplied elements */
11431 for (i = 4; i < argc; i++) {
11432 Jim_ListAppendElement(interp, newListObj, argv[i]);
11435 /* Add the remaining elements */
11436 for (i = first + rangeLen; i < len; i++) {
11437 Jim_ListAppendElement(interp, newListObj, listObj->internalRep.listValue.ele[i]);
11439 Jim_SetResult(interp, newListObj);
11440 if (shared) {
11441 Jim_FreeNewObj(interp, listObj);
11443 return JIM_OK;
11446 /* [lset] */
11447 static int Jim_LsetCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11449 if (argc < 3) {
11450 Jim_WrongNumArgs(interp, 1, argv, "listVar ?index...? newVal");
11451 return JIM_ERR;
11453 else if (argc == 3) {
11454 if (Jim_SetVariable(interp, argv[1], argv[2]) != JIM_OK)
11455 return JIM_ERR;
11456 Jim_SetResult(interp, argv[2]);
11457 return JIM_OK;
11459 if (Jim_SetListIndex(interp, argv[1], argv + 2, argc - 3, argv[argc - 1])
11460 == JIM_ERR)
11461 return JIM_ERR;
11462 return JIM_OK;
11465 /* [lsort] */
11466 static int Jim_LsortCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const argv[])
11468 const char *options[] = {
11469 "-ascii", "-nocase", "-increasing", "-decreasing", "-command", "-integer", "-index", NULL
11471 enum
11472 { OPT_ASCII, OPT_NOCASE, OPT_INCREASING, OPT_DECREASING, OPT_COMMAND, OPT_INTEGER, OPT_INDEX };
11473 Jim_Obj *resObj;
11474 int i;
11475 int retCode;
11477 struct lsort_info info;
11479 if (argc < 2) {
11480 Jim_WrongNumArgs(interp, 1, argv, "?options? list");
11481 return JIM_ERR;
11484 info.type = JIM_LSORT_ASCII;
11485 info.order = 1;
11486 info.indexed = 0;
11487 info.command = NULL;
11488 info.interp = interp;
11490 for (i = 1; i < (argc - 1); i++) {
11491 int option;
11493 if (Jim_GetEnum(interp, argv[i], options, &option, NULL, JIM_ERRMSG)
11494 != JIM_OK)
11495 return JIM_ERR;
11496 switch (option) {
11497 case OPT_ASCII:
11498 info.type = JIM_LSORT_ASCII;
11499 break;
11500 case OPT_NOCASE:
11501 info.type = JIM_LSORT_NOCASE;
11502 break;
11503 case OPT_INTEGER:
11504 info.type = JIM_LSORT_INTEGER;
11505 break;
11506 case OPT_INCREASING:
11507 info.order = 1;
11508 break;
11509 case OPT_DECREASING:
11510 info.order = -1;
11511 break;
11512 case OPT_COMMAND:
11513 if (i >= (argc - 2)) {
11514 Jim_SetResultString(interp, "\"-command\" option must be followed by comparison command", -1);
11515 return JIM_ERR;
11517 info.type = JIM_LSORT_COMMAND;
11518 info.command = argv[i + 1];
11519 i++;
11520 break;
11521 case OPT_INDEX:
11522 if (i >= (argc - 2)) {
11523 Jim_SetResultString(interp, "\"-index\" option must be followed by list index", -1);
11524 return JIM_ERR;
11526 if (Jim_GetIndex(interp, argv[i + 1], &info.index) != JIM_OK) {
11527 return JIM_ERR;
11529 info.indexed = 1;
11530 i++;
11531 break;
11534 resObj = Jim_DuplicateObj(interp, argv[argc - 1]);
11535 retCode = ListSortElements(interp, resObj, &info);
11536 if (retCode == JIM_OK) {
11537 Jim_SetResult(interp, resObj);
11539 else {
11540 Jim_FreeNewObj(interp, resObj);
11542 return retCode;
11545 /* [append] */
11546 static int Jim_AppendCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11548 Jim_Obj *stringObjPtr;
11549 int shared, i;
11551 if (argc < 2) {
11552 Jim_WrongNumArgs(interp, 1, argv, "varName ?value value ...?");
11553 return JIM_ERR;
11555 if (argc == 2) {
11556 stringObjPtr = Jim_GetVariable(interp, argv[1], JIM_ERRMSG);
11557 if (!stringObjPtr)
11558 return JIM_ERR;
11560 else {
11561 stringObjPtr = Jim_GetVariable(interp, argv[1], JIM_UNSHARED);
11562 if (!stringObjPtr) {
11563 /* Create the string if it does not exists */
11564 stringObjPtr = Jim_NewEmptyStringObj(interp);
11565 if (Jim_SetVariable(interp, argv[1], stringObjPtr)
11566 != JIM_OK) {
11567 Jim_FreeNewObj(interp, stringObjPtr);
11568 return JIM_ERR;
11572 shared = Jim_IsShared(stringObjPtr);
11573 if (shared)
11574 stringObjPtr = Jim_DuplicateObj(interp, stringObjPtr);
11575 for (i = 2; i < argc; i++)
11576 Jim_AppendObj(interp, stringObjPtr, argv[i]);
11577 if (Jim_SetVariable(interp, argv[1], stringObjPtr) != JIM_OK) {
11578 if (shared)
11579 Jim_FreeNewObj(interp, stringObjPtr);
11580 return JIM_ERR;
11582 Jim_SetResult(interp, stringObjPtr);
11583 return JIM_OK;
11586 /* [debug] */
11587 static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11589 #ifdef JIM_DEBUG_COMMAND
11590 const char *options[] = {
11591 "refcount", "objcount", "objects", "invstr", "scriptlen", "exprlen",
11592 "exprbc", "show",
11593 NULL
11595 enum
11597 OPT_REFCOUNT, OPT_OBJCOUNT, OPT_OBJECTS, OPT_INVSTR, OPT_SCRIPTLEN,
11598 OPT_EXPRLEN, OPT_EXPRBC, OPT_SHOW,
11600 int option;
11602 if (argc < 2) {
11603 Jim_WrongNumArgs(interp, 1, argv, "subcommand ?...?");
11604 return JIM_ERR;
11606 if (Jim_GetEnum(interp, argv[1], options, &option, "subcommand", JIM_ERRMSG) != JIM_OK)
11607 return JIM_ERR;
11608 if (option == OPT_REFCOUNT) {
11609 if (argc != 3) {
11610 Jim_WrongNumArgs(interp, 2, argv, "object");
11611 return JIM_ERR;
11613 Jim_SetResultInt(interp, argv[2]->refCount);
11614 return JIM_OK;
11616 else if (option == OPT_OBJCOUNT) {
11617 int freeobj = 0, liveobj = 0;
11618 char buf[256];
11619 Jim_Obj *objPtr;
11621 if (argc != 2) {
11622 Jim_WrongNumArgs(interp, 2, argv, "");
11623 return JIM_ERR;
11625 /* Count the number of free objects. */
11626 objPtr = interp->freeList;
11627 while (objPtr) {
11628 freeobj++;
11629 objPtr = objPtr->nextObjPtr;
11631 /* Count the number of live objects. */
11632 objPtr = interp->liveList;
11633 while (objPtr) {
11634 liveobj++;
11635 objPtr = objPtr->nextObjPtr;
11637 /* Set the result string and return. */
11638 sprintf(buf, "free %d used %d", freeobj, liveobj);
11639 Jim_SetResultString(interp, buf, -1);
11640 return JIM_OK;
11642 else if (option == OPT_OBJECTS) {
11643 Jim_Obj *objPtr, *listObjPtr, *subListObjPtr;
11645 /* Count the number of live objects. */
11646 objPtr = interp->liveList;
11647 listObjPtr = Jim_NewListObj(interp, NULL, 0);
11648 while (objPtr) {
11649 char buf[128];
11650 const char *type = objPtr->typePtr ? objPtr->typePtr->name : "";
11652 subListObjPtr = Jim_NewListObj(interp, NULL, 0);
11653 sprintf(buf, "%p", objPtr);
11654 Jim_ListAppendElement(interp, subListObjPtr, Jim_NewStringObj(interp, buf, -1));
11655 Jim_ListAppendElement(interp, subListObjPtr, Jim_NewStringObj(interp, type, -1));
11656 Jim_ListAppendElement(interp, subListObjPtr, Jim_NewIntObj(interp, objPtr->refCount));
11657 Jim_ListAppendElement(interp, subListObjPtr, objPtr);
11658 Jim_ListAppendElement(interp, listObjPtr, subListObjPtr);
11659 objPtr = objPtr->nextObjPtr;
11661 Jim_SetResult(interp, listObjPtr);
11662 return JIM_OK;
11664 else if (option == OPT_INVSTR) {
11665 Jim_Obj *objPtr;
11667 if (argc != 3) {
11668 Jim_WrongNumArgs(interp, 2, argv, "object");
11669 return JIM_ERR;
11671 objPtr = argv[2];
11672 if (objPtr->typePtr != NULL)
11673 Jim_InvalidateStringRep(objPtr);
11674 Jim_SetEmptyResult(interp);
11675 return JIM_OK;
11677 else if (option == OPT_SHOW) {
11678 const char *s;
11679 int len, charlen;
11681 if (argc != 3) {
11682 Jim_WrongNumArgs(interp, 2, argv, "object");
11683 return JIM_ERR;
11685 s = Jim_GetString(argv[2], &len);
11686 Jim_GetStringUtf8(interp, argv[2], &charlen);
11687 printf("chars (%d): <<%s>>\n", charlen, s);
11688 printf("bytes (%d):", len);
11689 while (len--) {
11690 printf(" %02x", (unsigned char)*s++);
11692 printf("\n");
11693 return JIM_OK;
11695 else if (option == OPT_SCRIPTLEN) {
11696 ScriptObj *script;
11698 if (argc != 3) {
11699 Jim_WrongNumArgs(interp, 2, argv, "script");
11700 return JIM_ERR;
11702 script = Jim_GetScript(interp, argv[2]);
11703 Jim_SetResultInt(interp, script->len);
11704 return JIM_OK;
11706 else if (option == OPT_EXPRLEN) {
11707 ExprByteCode *expr;
11709 if (argc != 3) {
11710 Jim_WrongNumArgs(interp, 2, argv, "expression");
11711 return JIM_ERR;
11713 expr = Jim_GetExpression(interp, argv[2]);
11714 if (expr == NULL)
11715 return JIM_ERR;
11716 Jim_SetResultInt(interp, expr->len);
11717 return JIM_OK;
11719 else if (option == OPT_EXPRBC) {
11720 Jim_Obj *objPtr;
11721 ExprByteCode *expr;
11722 int i;
11724 if (argc != 3) {
11725 Jim_WrongNumArgs(interp, 2, argv, "expression");
11726 return JIM_ERR;
11728 expr = Jim_GetExpression(interp, argv[2]);
11729 if (expr == NULL)
11730 return JIM_ERR;
11731 objPtr = Jim_NewListObj(interp, NULL, 0);
11732 for (i = 0; i < expr->len; i++) {
11733 const char *type;
11734 const Jim_ExprOperator *op;
11735 Jim_Obj *obj = expr->token[i].objPtr;
11737 switch (expr->token[i].type) {
11738 case JIM_TT_EXPR_INT:
11739 type = "int";
11740 break;
11741 case JIM_TT_EXPR_DOUBLE:
11742 type = "double";
11743 break;
11744 case JIM_TT_CMD:
11745 type = "command";
11746 break;
11747 case JIM_TT_VAR:
11748 type = "variable";
11749 break;
11750 case JIM_TT_DICTSUGAR:
11751 type = "dictsugar";
11752 break;
11753 case JIM_TT_ESC:
11754 type = "subst";
11755 break;
11756 case JIM_TT_STR:
11757 type = "string";
11758 break;
11759 default:
11760 op = JimExprOperatorInfoByOpcode(expr->token[i].type);
11761 if (op == NULL) {
11762 type = "private";
11764 else {
11765 type = "operator";
11767 obj = Jim_NewStringObj(interp, op ? op->name : "", -1);
11768 break;
11770 Jim_ListAppendElement(interp, objPtr, Jim_NewStringObj(interp, type, -1));
11771 Jim_ListAppendElement(interp, objPtr, obj);
11773 Jim_SetResult(interp, objPtr);
11774 return JIM_OK;
11776 else {
11777 Jim_SetResultString(interp,
11778 "bad option. Valid options are refcount, " "objcount, objects, invstr", -1);
11779 return JIM_ERR;
11781 /* unreached */
11782 #else
11783 Jim_SetResultString(interp, "unsupported", -1);
11784 return JIM_ERR;
11785 #endif
11788 /* [eval] */
11789 static int Jim_EvalCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11791 int rc;
11792 Jim_Stack *prevLocalProcs;
11794 if (argc < 2) {
11795 Jim_WrongNumArgs(interp, 1, argv, "script ?...?");
11796 return JIM_ERR;
11799 /* Install a new stack for local procs */
11800 prevLocalProcs = interp->localProcs;
11801 interp->localProcs = NULL;
11803 if (argc == 2) {
11804 rc = Jim_EvalObj(interp, argv[1]);
11806 else {
11807 rc = Jim_EvalObj(interp, Jim_ConcatObj(interp, argc - 1, argv + 1));
11810 /* Delete any local procs */
11811 JimDeleteLocalProcs(interp);
11812 interp->localProcs = prevLocalProcs;
11814 if (rc == JIM_ERR) {
11815 /* eval is "interesting", so add a stack frame here */
11816 interp->addStackTrace++;
11818 return rc;
11821 /* [uplevel] */
11822 static int Jim_UplevelCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11824 if (argc >= 2) {
11825 int retcode, newLevel, oldLevel;
11826 Jim_CallFrame *savedCallFrame, *targetCallFrame;
11827 Jim_Obj *objPtr;
11828 const char *str;
11830 /* Save the old callframe pointer */
11831 savedCallFrame = interp->framePtr;
11833 /* Lookup the target frame pointer */
11834 str = Jim_GetString(argv[1], NULL);
11835 if ((str[0] >= '0' && str[0] <= '9') || str[0] == '#') {
11836 if (Jim_GetCallFrameByLevel(interp, argv[1], &targetCallFrame, &newLevel) != JIM_OK)
11837 return JIM_ERR;
11838 argc--;
11839 argv++;
11841 else {
11842 if (Jim_GetCallFrameByLevel(interp, NULL, &targetCallFrame, &newLevel) != JIM_OK)
11843 return JIM_ERR;
11845 if (argc < 2) {
11846 argv--;
11847 Jim_WrongNumArgs(interp, 1, argv, "?level? command ?arg ...?");
11848 return JIM_ERR;
11850 /* Eval the code in the target callframe. */
11851 interp->framePtr = targetCallFrame;
11852 oldLevel = interp->numLevels;
11853 interp->numLevels = newLevel;
11854 if (argc == 2) {
11855 retcode = Jim_EvalObj(interp, argv[1]);
11857 else {
11858 objPtr = Jim_ConcatObj(interp, argc - 1, argv + 1);
11859 Jim_IncrRefCount(objPtr);
11860 retcode = Jim_EvalObj(interp, objPtr);
11861 Jim_DecrRefCount(interp, objPtr);
11863 interp->numLevels = oldLevel;
11864 interp->framePtr = savedCallFrame;
11865 return retcode;
11867 else {
11868 Jim_WrongNumArgs(interp, 1, argv, "?level? command ?arg ...?");
11869 return JIM_ERR;
11873 /* [expr] */
11874 static int Jim_ExprCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11876 Jim_Obj *exprResultPtr;
11877 int retcode;
11879 if (argc == 2) {
11880 retcode = Jim_EvalExpression(interp, argv[1], &exprResultPtr);
11882 else if (argc > 2) {
11883 Jim_Obj *objPtr;
11885 objPtr = Jim_ConcatObj(interp, argc - 1, argv + 1);
11886 Jim_IncrRefCount(objPtr);
11887 retcode = Jim_EvalExpression(interp, objPtr, &exprResultPtr);
11888 Jim_DecrRefCount(interp, objPtr);
11890 else {
11891 Jim_WrongNumArgs(interp, 1, argv, "expression ?...?");
11892 return JIM_ERR;
11894 if (retcode != JIM_OK)
11895 return retcode;
11896 Jim_SetResult(interp, exprResultPtr);
11897 Jim_DecrRefCount(interp, exprResultPtr);
11898 return JIM_OK;
11901 /* [break] */
11902 static int Jim_BreakCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11904 if (argc != 1) {
11905 Jim_WrongNumArgs(interp, 1, argv, "");
11906 return JIM_ERR;
11908 return JIM_BREAK;
11911 /* [continue] */
11912 static int Jim_ContinueCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11914 if (argc != 1) {
11915 Jim_WrongNumArgs(interp, 1, argv, "");
11916 return JIM_ERR;
11918 return JIM_CONTINUE;
11921 /* [return] */
11922 static int Jim_ReturnCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11924 int i;
11925 Jim_Obj *stackTraceObj = NULL;
11926 Jim_Obj *errorCodeObj = NULL;
11927 int returnCode = JIM_OK;
11928 long level = 1;
11930 for (i = 1; i < argc - 1; i += 2) {
11931 if (Jim_CompareStringImmediate(interp, argv[i], "-code")) {
11932 if (Jim_GetReturnCode(interp, argv[i + 1], &returnCode) == JIM_ERR) {
11933 return JIM_ERR;
11936 else if (Jim_CompareStringImmediate(interp, argv[i], "-errorinfo")) {
11937 stackTraceObj = argv[i + 1];
11939 else if (Jim_CompareStringImmediate(interp, argv[i], "-errorcode")) {
11940 errorCodeObj = argv[i + 1];
11942 else if (Jim_CompareStringImmediate(interp, argv[i], "-level")) {
11943 if (Jim_GetLong(interp, argv[i + 1], &level) != JIM_OK || level < 0) {
11944 Jim_SetResultFormatted(interp, "bad level \"%#s\"", argv[i + 1]);
11945 return JIM_ERR;
11948 else {
11949 break;
11953 if (i != argc - 1 && i != argc) {
11954 Jim_WrongNumArgs(interp, 1, argv,
11955 "?-code code? ?-errorinfo stacktrace? ?-level level? ?result?");
11958 /* If a stack trace is supplied and code is error, set the stack trace */
11959 if (stackTraceObj && returnCode == JIM_ERR) {
11960 JimSetStackTrace(interp, stackTraceObj);
11962 /* If an error code list is supplied, set the global $errorCode */
11963 if (errorCodeObj && returnCode == JIM_ERR) {
11964 Jim_SetGlobalVariableStr(interp, "errorCode", errorCodeObj);
11966 interp->returnCode = returnCode;
11967 interp->returnLevel = level;
11969 if (i == argc - 1) {
11970 Jim_SetResult(interp, argv[i]);
11972 return JIM_RETURN;
11975 /* [tailcall] */
11976 static int Jim_TailcallCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11978 Jim_Obj *objPtr;
11980 objPtr = Jim_NewListObj(interp, argv + 1, argc - 1);
11981 Jim_SetResult(interp, objPtr);
11982 return JIM_EVAL;
11985 /* [proc] */
11986 static int Jim_ProcCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
11988 int argListLen;
11989 int leftArity, rightArity;
11990 int i;
11991 int optionalArgs = 0;
11992 int args = 0;
11994 if (argc != 4 && argc != 5) {
11995 Jim_WrongNumArgs(interp, 1, argv, "name arglist ?statics? body");
11996 return JIM_ERR;
11999 if (Jim_ValidName(interp, "procedure", argv[1]) != JIM_OK) {
12000 return JIM_ERR;
12003 argListLen = Jim_ListLength(interp, argv[2]);
12004 leftArity = 0;
12005 rightArity = 0;
12007 /* Examine the argument list for default parameters and 'args' */
12008 for (i = 0; i < argListLen; i++) {
12009 Jim_Obj *argPtr;
12010 int len;
12012 Jim_ListIndex(interp, argv[2], i, &argPtr, JIM_NONE);
12013 if (Jim_CompareStringImmediate(interp, argPtr, "args")) {
12014 if (args) {
12015 Jim_SetResultString(interp, "procedure has 'args' specified more than once", -1);
12016 return JIM_ERR;
12018 if (rightArity) {
12019 Jim_SetResultString(interp, "procedure has 'args' in invalid position", -1);
12020 return JIM_ERR;
12022 args = 1;
12023 continue;
12026 /* Does this parameter have a default? */
12027 Jim_GetString(argPtr, NULL);
12028 len = Jim_ListLength(interp, argPtr);
12029 if (len == 0) {
12030 Jim_SetResultString(interp, "procedure has argument with no name", -1);
12031 return JIM_ERR;
12033 if (len > 2) {
12034 Jim_SetResultString(interp, "procedure has argument with too many fields", -1);
12035 return JIM_ERR;
12037 if (len == 1) {
12038 /* A required arg. Is it part of leftArity or rightArity? */
12039 if (optionalArgs || args) {
12040 rightArity++;
12042 else {
12043 leftArity++;
12046 else {
12047 /* Optional arg. Can't be after rightArity */
12048 if (rightArity || args) {
12049 Jim_SetResultString(interp, "procedure has optional arg in invalid position", -1);
12050 return JIM_ERR;
12052 optionalArgs++;
12056 if (argc == 4) {
12057 return Jim_CreateProcedure(interp, Jim_GetString(argv[1], NULL),
12058 argv[2], NULL, argv[3], leftArity, optionalArgs, args, rightArity);
12060 else {
12061 return Jim_CreateProcedure(interp, Jim_GetString(argv[1], NULL),
12062 argv[2], argv[3], argv[4], leftArity, optionalArgs, args, rightArity);
12066 /* [local] */
12067 static int Jim_LocalCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12069 /* Evaluate the arguments */
12070 int retcode = Jim_EvalObjVector(interp, argc - 1, argv + 1);
12072 /* If OK, and the result is a proc, add it to the list of local procs */
12073 if (retcode == 0) {
12074 const char *procname = Jim_GetString(Jim_GetResult(interp), NULL);
12076 if (Jim_FindHashEntry(&interp->commands, procname) == NULL) {
12077 Jim_SetResultFormatted(interp, "not a proc: \"%s\"", procname);
12078 return JIM_ERR;
12080 if (interp->localProcs == NULL) {
12081 interp->localProcs = Jim_Alloc(sizeof(*interp->localProcs));
12082 Jim_InitStack(interp->localProcs);
12084 Jim_StackPush(interp->localProcs, Jim_StrDup(procname));
12087 return retcode;
12091 /* [concat] */
12092 static int Jim_ConcatCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12094 Jim_SetResult(interp, Jim_ConcatObj(interp, argc - 1, argv + 1));
12095 return JIM_OK;
12098 /* [upvar] */
12099 static int Jim_UpvarCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12101 int i;
12102 Jim_CallFrame *targetCallFrame;
12104 /* Lookup the target frame pointer */
12105 if (argc > 3 && (argc % 2 == 0)) {
12106 if (Jim_GetCallFrameByLevel(interp, argv[1], &targetCallFrame, NULL) != JIM_OK) {
12107 return JIM_ERR;
12109 argc--;
12110 argv++;
12112 else if (Jim_GetCallFrameByLevel(interp, NULL, &targetCallFrame, NULL) != JIM_OK) {
12113 return JIM_ERR;
12116 /* Check for arity */
12117 if (argc < 3) {
12118 Jim_WrongNumArgs(interp, 1, argv, "?level? otherVar localVar ?otherVar localVar ...?");
12119 return JIM_ERR;
12122 /* Now... for every other/local couple: */
12123 for (i = 1; i < argc; i += 2) {
12124 if (Jim_SetVariableLink(interp, argv[i + 1], argv[i], targetCallFrame) != JIM_OK)
12125 return JIM_ERR;
12127 return JIM_OK;
12130 /* [global] */
12131 static int Jim_GlobalCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12133 int i;
12135 if (argc < 2) {
12136 Jim_WrongNumArgs(interp, 1, argv, "varName ?varName ...?");
12137 return JIM_ERR;
12139 /* Link every var to the toplevel having the same name */
12140 if (interp->numLevels == 0)
12141 return JIM_OK; /* global at toplevel... */
12142 for (i = 1; i < argc; i++) {
12143 if (Jim_SetVariableLink(interp, argv[i], argv[i], interp->topFramePtr) != JIM_OK)
12144 return JIM_ERR;
12146 return JIM_OK;
12149 /* does the [string map] operation. On error NULL is returned,
12150 * otherwise a new string object with the result, having refcount = 0,
12151 * is returned. */
12152 static Jim_Obj *JimStringMap(Jim_Interp *interp, Jim_Obj *mapListObjPtr,
12153 Jim_Obj *objPtr, int nocase)
12155 int numMaps;
12156 const char *str, *noMatchStart = NULL;
12157 int strLen, i;
12158 Jim_Obj *resultObjPtr;
12160 numMaps = Jim_ListLength(interp, mapListObjPtr);
12161 if (numMaps % 2) {
12162 Jim_SetResultString(interp, "list must contain an even number of elements", -1);
12163 return NULL;
12166 str = Jim_GetString(objPtr, NULL);
12167 strLen = Jim_Utf8Length(interp, objPtr);
12169 /* Map it */
12170 resultObjPtr = Jim_NewStringObj(interp, "", 0);
12171 while (strLen) {
12172 for (i = 0; i < numMaps; i += 2) {
12173 Jim_Obj *objPtr;
12174 const char *k;
12175 int kl;
12177 Jim_ListIndex(interp, mapListObjPtr, i, &objPtr, JIM_NONE);
12178 k = Jim_GetString(objPtr, NULL);
12179 kl = Jim_Utf8Length(interp, objPtr);
12181 if (strLen >= kl && kl) {
12182 int rc;
12183 if (nocase) {
12184 rc = JimStringCompareNoCase(str, k, kl);
12186 else {
12187 rc = JimStringCompare(str, kl, k, kl);
12189 if (rc == 0) {
12190 if (noMatchStart) {
12191 Jim_AppendString(interp, resultObjPtr, noMatchStart, str - noMatchStart);
12192 noMatchStart = NULL;
12194 Jim_ListIndex(interp, mapListObjPtr, i + 1, &objPtr, JIM_NONE);
12195 Jim_AppendObj(interp, resultObjPtr, objPtr);
12196 str += utf8_index(str, kl);
12197 strLen -= kl;
12198 break;
12202 if (i == numMaps) { /* no match */
12203 int c;
12204 if (noMatchStart == NULL)
12205 noMatchStart = str;
12206 str += utf8_tounicode(str, &c);
12207 strLen--;
12210 if (noMatchStart) {
12211 Jim_AppendString(interp, resultObjPtr, noMatchStart, str - noMatchStart);
12213 return resultObjPtr;
12216 /* [string] */
12217 static int Jim_StringCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12219 int len;
12220 int opt_case = 1;
12221 int option;
12222 static const char * const options[] = {
12223 "bytelength", "length", "compare", "match", "equal", "is", "range", "map",
12224 "repeat", "reverse", "index", "first", "last",
12225 "trim", "trimleft", "trimright", "tolower", "toupper", NULL
12227 enum
12229 OPT_BYTELENGTH, OPT_LENGTH, OPT_COMPARE, OPT_MATCH, OPT_EQUAL, OPT_IS, OPT_RANGE, OPT_MAP,
12230 OPT_REPEAT, OPT_REVERSE, OPT_INDEX, OPT_FIRST, OPT_LAST,
12231 OPT_TRIM, OPT_TRIMLEFT, OPT_TRIMRIGHT, OPT_TOLOWER, OPT_TOUPPER
12233 static const char * const nocase_options[] = {
12234 "-nocase", NULL
12237 if (argc < 2) {
12238 Jim_WrongNumArgs(interp, 1, argv, "option ?arguments ...?");
12239 return JIM_ERR;
12241 if (Jim_GetEnum(interp, argv[1], options, &option, NULL,
12242 JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK)
12243 return JIM_ERR;
12245 switch (option) {
12246 case OPT_LENGTH:
12247 case OPT_BYTELENGTH:
12248 if (argc != 3) {
12249 Jim_WrongNumArgs(interp, 2, argv, "string");
12250 return JIM_ERR;
12252 if (option == OPT_LENGTH) {
12253 len = Jim_Utf8Length(interp, argv[2]);
12255 else {
12256 len = Jim_Length(argv[2]);
12258 Jim_SetResultInt(interp, len);
12259 return JIM_OK;
12261 case OPT_COMPARE:
12262 case OPT_EQUAL:
12263 if (argc != 4 &&
12264 (argc != 5 ||
12265 Jim_GetEnum(interp, argv[2], nocase_options, &opt_case, NULL,
12266 JIM_ENUM_ABBREV) != JIM_OK)) {
12267 Jim_WrongNumArgs(interp, 2, argv, "?-nocase? string1 string2");
12268 return JIM_ERR;
12270 if (opt_case == 0) {
12271 argv++;
12273 if (option == OPT_COMPARE) {
12274 Jim_SetResultInt(interp, Jim_StringCompareObj(interp, argv[2], argv[3], !opt_case));
12276 else if (opt_case) {
12277 Jim_SetResultBool(interp, Jim_StringEqObj(argv[2], argv[3]));
12279 else {
12280 Jim_SetResultBool(interp, Jim_StringCompareObj(interp, argv[2], argv[3], 1) == 0);
12282 return JIM_OK;
12284 case OPT_MATCH:
12285 if (argc != 4 &&
12286 (argc != 5 ||
12287 Jim_GetEnum(interp, argv[2], nocase_options, &opt_case, NULL,
12288 JIM_ENUM_ABBREV) != JIM_OK)) {
12289 Jim_WrongNumArgs(interp, 2, argv, "?-nocase? pattern string");
12290 return JIM_ERR;
12292 if (opt_case == 0) {
12293 argv++;
12295 Jim_SetResultBool(interp, Jim_StringMatchObj(interp, argv[2], argv[3], !opt_case));
12296 return JIM_OK;
12298 case OPT_MAP:{
12299 Jim_Obj *objPtr;
12301 if (argc != 4 &&
12302 (argc != 5 ||
12303 Jim_GetEnum(interp, argv[2], nocase_options, &opt_case, NULL,
12304 JIM_ENUM_ABBREV) != JIM_OK)) {
12305 Jim_WrongNumArgs(interp, 2, argv, "?-nocase? mapList string");
12306 return JIM_ERR;
12309 if (opt_case == 0) {
12310 argv++;
12312 objPtr = JimStringMap(interp, argv[2], argv[3], !opt_case);
12313 if (objPtr == NULL) {
12314 return JIM_ERR;
12316 Jim_SetResult(interp, objPtr);
12317 return JIM_OK;
12320 case OPT_RANGE:{
12321 Jim_Obj *objPtr;
12323 if (argc != 5) {
12324 Jim_WrongNumArgs(interp, 2, argv, "string first last");
12325 return JIM_ERR;
12327 objPtr = Jim_StringRangeObj(interp, argv[2], argv[3], argv[4]);
12328 if (objPtr == NULL) {
12329 return JIM_ERR;
12331 Jim_SetResult(interp, objPtr);
12332 return JIM_OK;
12335 case OPT_REPEAT:{
12336 Jim_Obj *objPtr;
12337 jim_wide count;
12339 if (argc != 4) {
12340 Jim_WrongNumArgs(interp, 2, argv, "string count");
12341 return JIM_ERR;
12343 if (Jim_GetWide(interp, argv[3], &count) != JIM_OK) {
12344 return JIM_ERR;
12346 objPtr = Jim_NewStringObj(interp, "", 0);
12347 if (count > 0) {
12348 while (count--) {
12349 Jim_AppendObj(interp, objPtr, argv[2]);
12352 Jim_SetResult(interp, objPtr);
12353 return JIM_OK;
12356 case OPT_REVERSE:{
12357 char *buf, *p;
12358 const char *str;
12359 int len;
12360 int i;
12362 if (argc != 3) {
12363 Jim_WrongNumArgs(interp, 2, argv, "string");
12364 return JIM_ERR;
12367 str = Jim_GetString(argv[2], &len);
12368 if (!str) {
12369 return JIM_ERR;
12372 buf = Jim_Alloc(len + 1);
12373 p = buf + len;
12374 *p = 0;
12375 for (i = 0; i < len; ) {
12376 int c;
12377 int l = utf8_tounicode(str, &c);
12378 memcpy(p - l, str, l);
12379 p -= l;
12380 i += l;
12381 str += l;
12383 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, len));
12384 return JIM_OK;
12387 case OPT_INDEX:{
12388 int idx;
12389 const char *str;
12391 if (argc != 4) {
12392 Jim_WrongNumArgs(interp, 2, argv, "string index");
12393 return JIM_ERR;
12395 if (Jim_GetIndex(interp, argv[3], &idx) != JIM_OK) {
12396 return JIM_ERR;
12398 str = Jim_GetString(argv[2], NULL);
12399 len = Jim_Utf8Length(interp, argv[2]);
12400 if (idx != INT_MIN && idx != INT_MAX) {
12401 idx = JimRelToAbsIndex(len, idx);
12403 if (idx < 0 || idx >= len || str == NULL) {
12404 Jim_SetResultString(interp, "", 0);
12406 else if (len == Jim_Length(argv[2])) {
12407 /* ASCII optimisation */
12408 Jim_SetResultString(interp, str + idx, 1);
12410 else {
12411 int c;
12412 int i = utf8_index(str, idx);
12413 Jim_SetResultString(interp, str + i, utf8_tounicode(str + i, &c));
12415 return JIM_OK;
12418 case OPT_FIRST:
12419 case OPT_LAST:{
12420 int idx = 0, l1, l2;
12421 const char *s1, *s2;
12423 if (argc != 4 && argc != 5) {
12424 Jim_WrongNumArgs(interp, 2, argv, "subString string ?index?");
12425 return JIM_ERR;
12427 s1 = Jim_GetString(argv[2], NULL);
12428 s2 = Jim_GetString(argv[3], NULL);
12429 l1 = Jim_Utf8Length(interp, argv[2]);
12430 l2 = Jim_Utf8Length(interp, argv[3]);
12431 if (argc == 5) {
12432 if (Jim_GetIndex(interp, argv[4], &idx) != JIM_OK) {
12433 return JIM_ERR;
12435 idx = JimRelToAbsIndex(l2, idx);
12437 else if (option == OPT_LAST) {
12438 idx = l2;
12440 if (option == OPT_FIRST) {
12441 Jim_SetResultInt(interp, JimStringFirst(s1, l1, s2, l2, idx));
12443 else {
12444 Jim_SetResultInt(interp, JimStringLast(s1, l1, s2, idx));
12446 return JIM_OK;
12449 case OPT_TRIM:
12450 case OPT_TRIMLEFT:
12451 case OPT_TRIMRIGHT:{
12452 Jim_Obj *trimchars;
12454 if (argc != 3 && argc != 4) {
12455 Jim_WrongNumArgs(interp, 2, argv, "string ?trimchars?");
12456 return JIM_ERR;
12458 trimchars = (argc == 4 ? argv[3] : NULL);
12459 if (option == OPT_TRIM) {
12460 Jim_SetResult(interp, JimStringTrim(interp, argv[2], trimchars));
12462 else if (option == OPT_TRIMLEFT) {
12463 Jim_SetResult(interp, JimStringTrimLeft(interp, argv[2], trimchars));
12465 else if (option == OPT_TRIMRIGHT) {
12466 Jim_SetResult(interp, JimStringTrimRight(interp, argv[2], trimchars));
12468 return JIM_OK;
12471 case OPT_TOLOWER:
12472 case OPT_TOUPPER:
12473 if (argc != 3) {
12474 Jim_WrongNumArgs(interp, 2, argv, "string");
12475 return JIM_ERR;
12477 if (option == OPT_TOLOWER) {
12478 Jim_SetResult(interp, JimStringToLower(interp, argv[2]));
12480 else {
12481 Jim_SetResult(interp, JimStringToUpper(interp, argv[2]));
12483 return JIM_OK;
12485 case OPT_IS:
12486 if (argc == 4 || (argc == 5 && Jim_CompareStringImmediate(interp, argv[3], "-strict"))) {
12487 return JimStringIs(interp, argv[argc - 1], argv[2], argc == 5);
12489 Jim_WrongNumArgs(interp, 2, argv, "class ?-strict? str");
12490 return JIM_ERR;
12492 return JIM_OK;
12495 /* [time] */
12496 static int Jim_TimeCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12498 long i, count = 1;
12499 jim_wide start, elapsed;
12500 char buf[60];
12501 const char *fmt = "%" JIM_WIDE_MODIFIER " microseconds per iteration";
12503 if (argc < 2) {
12504 Jim_WrongNumArgs(interp, 1, argv, "script ?count?");
12505 return JIM_ERR;
12507 if (argc == 3) {
12508 if (Jim_GetLong(interp, argv[2], &count) != JIM_OK)
12509 return JIM_ERR;
12511 if (count < 0)
12512 return JIM_OK;
12513 i = count;
12514 start = JimClock();
12515 while (i-- > 0) {
12516 int retval;
12518 retval = Jim_EvalObj(interp, argv[1]);
12519 if (retval != JIM_OK) {
12520 return retval;
12523 elapsed = JimClock() - start;
12524 sprintf(buf, fmt, count == 0 ? 0 : elapsed / count);
12525 Jim_SetResultString(interp, buf, -1);
12526 return JIM_OK;
12529 /* [exit] */
12530 static int Jim_ExitCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12532 long exitCode = 0;
12534 if (argc > 2) {
12535 Jim_WrongNumArgs(interp, 1, argv, "?exitCode?");
12536 return JIM_ERR;
12538 if (argc == 2) {
12539 if (Jim_GetLong(interp, argv[1], &exitCode) != JIM_OK)
12540 return JIM_ERR;
12542 interp->exitCode = exitCode;
12543 return JIM_EXIT;
12546 /* [catch] */
12547 static int Jim_CatchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12549 int exitCode = 0;
12550 int i;
12551 int sig = 0;
12553 /* Which return codes are caught? These are the defaults */
12554 jim_wide mask =
12555 (1 << JIM_OK | 1 << JIM_ERR | 1 << JIM_BREAK | 1 << JIM_CONTINUE | 1 << JIM_RETURN);
12557 /* Reset the error code before catch.
12558 * Note that this is not strictly correct.
12560 Jim_SetGlobalVariableStr(interp, "errorCode", Jim_NewStringObj(interp, "NONE", -1));
12562 for (i = 1; i < argc - 1; i++) {
12563 const char *arg = Jim_GetString(argv[i], NULL);
12564 jim_wide option;
12565 int add;
12567 /* It's a pity we can't use Jim_GetEnum here :-( */
12568 if (strcmp(arg, "--") == 0) {
12569 i++;
12570 break;
12572 if (*arg != '-') {
12573 break;
12576 if (strncmp(arg, "-no", 3) == 0) {
12577 arg += 3;
12578 add = 0;
12580 else {
12581 arg++;
12582 add = 1;
12585 if (Jim_StringToWide(arg, &option, 10) != JIM_OK) {
12586 option = -1;
12588 if (option < 0) {
12589 option = Jim_FindByName(arg, jimReturnCodes, jimReturnCodesSize);
12591 if (option < 0) {
12592 goto wrongargs;
12595 if (add) {
12596 mask |= (1 << option);
12598 else {
12599 mask &= ~(1 << option);
12603 argc -= i;
12604 if (argc < 1 || argc > 3) {
12605 wrongargs:
12606 Jim_WrongNumArgs(interp, 1, argv,
12607 "?-?no?code ... --? script ?resultVarName? ?optionVarName?");
12608 return JIM_ERR;
12610 argv += i;
12612 if (mask & (1 << JIM_SIGNAL)) {
12613 sig++;
12616 interp->signal_level += sig;
12617 if (interp->signal_level && interp->sigmask) {
12618 /* If a signal is set, don't even try to execute the body */
12619 exitCode = JIM_SIGNAL;
12621 else {
12622 exitCode = Jim_EvalObj(interp, argv[0]);
12624 interp->signal_level -= sig;
12626 /* Catch or pass through? Only the first 64 codes can be passed through */
12627 if (exitCode >= 0 && exitCode < (int)sizeof(mask) && ((1 << exitCode) & mask) == 0) {
12628 /* Not caught, pass it up */
12629 return exitCode;
12632 if (sig && exitCode == JIM_SIGNAL) {
12633 /* Catch the signal at this level */
12634 if (interp->signal_set_result) {
12635 interp->signal_set_result(interp, interp->sigmask);
12637 else {
12638 Jim_SetResultInt(interp, interp->sigmask);
12640 interp->sigmask = 0;
12643 if (argc >= 2) {
12644 if (Jim_SetVariable(interp, argv[1], Jim_GetResult(interp)) != JIM_OK) {
12645 return JIM_ERR;
12647 if (argc == 3) {
12648 Jim_Obj *optListObj = Jim_NewListObj(interp, NULL, 0);
12650 Jim_ListAppendElement(interp, optListObj, Jim_NewStringObj(interp, "-code", -1));
12651 Jim_ListAppendElement(interp, optListObj,
12652 Jim_NewIntObj(interp, exitCode == JIM_RETURN ? interp->returnCode : exitCode));
12653 Jim_ListAppendElement(interp, optListObj, Jim_NewStringObj(interp, "-level", -1));
12654 Jim_ListAppendElement(interp, optListObj, Jim_NewIntObj(interp, interp->returnLevel));
12655 if (exitCode == JIM_ERR) {
12656 Jim_Obj *errorCode;
12657 Jim_ListAppendElement(interp, optListObj, Jim_NewStringObj(interp, "-errorinfo",
12658 -1));
12659 Jim_ListAppendElement(interp, optListObj, interp->stackTrace);
12661 errorCode = Jim_GetGlobalVariableStr(interp, "errorCode", JIM_NONE);
12662 if (errorCode) {
12663 Jim_ListAppendElement(interp, optListObj, Jim_NewStringObj(interp, "-errorcode", -1));
12664 Jim_ListAppendElement(interp, optListObj, errorCode);
12667 if (Jim_SetVariable(interp, argv[2], optListObj) != JIM_OK) {
12668 return JIM_ERR;
12672 Jim_SetResultInt(interp, exitCode);
12673 return JIM_OK;
12676 #ifdef JIM_REFERENCES
12678 /* [ref] */
12679 static int Jim_RefCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12681 if (argc != 3 && argc != 4) {
12682 Jim_WrongNumArgs(interp, 1, argv, "string tag ?finalizer?");
12683 return JIM_ERR;
12685 if (argc == 3) {
12686 Jim_SetResult(interp, Jim_NewReference(interp, argv[1], argv[2], NULL));
12688 else {
12689 Jim_SetResult(interp, Jim_NewReference(interp, argv[1], argv[2], argv[3]));
12691 return JIM_OK;
12694 /* [getref] */
12695 static int Jim_GetrefCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12697 Jim_Reference *refPtr;
12699 if (argc != 2) {
12700 Jim_WrongNumArgs(interp, 1, argv, "reference");
12701 return JIM_ERR;
12703 if ((refPtr = Jim_GetReference(interp, argv[1])) == NULL)
12704 return JIM_ERR;
12705 Jim_SetResult(interp, refPtr->objPtr);
12706 return JIM_OK;
12709 /* [setref] */
12710 static int Jim_SetrefCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12712 Jim_Reference *refPtr;
12714 if (argc != 3) {
12715 Jim_WrongNumArgs(interp, 1, argv, "reference newValue");
12716 return JIM_ERR;
12718 if ((refPtr = Jim_GetReference(interp, argv[1])) == NULL)
12719 return JIM_ERR;
12720 Jim_IncrRefCount(argv[2]);
12721 Jim_DecrRefCount(interp, refPtr->objPtr);
12722 refPtr->objPtr = argv[2];
12723 Jim_SetResult(interp, argv[2]);
12724 return JIM_OK;
12727 /* [collect] */
12728 static int Jim_CollectCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12730 if (argc != 1) {
12731 Jim_WrongNumArgs(interp, 1, argv, "");
12732 return JIM_ERR;
12734 Jim_SetResultInt(interp, Jim_Collect(interp));
12735 return JIM_OK;
12738 /* [finalize] reference ?newValue? */
12739 static int Jim_FinalizeCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12741 if (argc != 2 && argc != 3) {
12742 Jim_WrongNumArgs(interp, 1, argv, "reference ?finalizerProc?");
12743 return JIM_ERR;
12745 if (argc == 2) {
12746 Jim_Obj *cmdNamePtr;
12748 if (Jim_GetFinalizer(interp, argv[1], &cmdNamePtr) != JIM_OK)
12749 return JIM_ERR;
12750 if (cmdNamePtr != NULL) /* otherwise the null string is returned. */
12751 Jim_SetResult(interp, cmdNamePtr);
12753 else {
12754 if (Jim_SetFinalizer(interp, argv[1], argv[2]) != JIM_OK)
12755 return JIM_ERR;
12756 Jim_SetResult(interp, argv[2]);
12758 return JIM_OK;
12761 /* [info references] */
12762 static int JimInfoReferences(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12764 Jim_Obj *listObjPtr;
12765 Jim_HashTableIterator *htiter;
12766 Jim_HashEntry *he;
12768 listObjPtr = Jim_NewListObj(interp, NULL, 0);
12770 htiter = Jim_GetHashTableIterator(&interp->references);
12771 while ((he = Jim_NextHashEntry(htiter)) != NULL) {
12772 char buf[JIM_REFERENCE_SPACE];
12773 Jim_Reference *refPtr = he->val;
12774 const jim_wide *refId = he->key;
12776 JimFormatReference(buf, refPtr, *refId);
12777 Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, buf, -1));
12779 Jim_FreeHashTableIterator(htiter);
12780 Jim_SetResult(interp, listObjPtr);
12781 return JIM_OK;
12783 #endif
12785 /* [rename] */
12786 static int Jim_RenameCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12788 const char *oldName, *newName;
12790 if (argc != 3) {
12791 Jim_WrongNumArgs(interp, 1, argv, "oldName newName");
12792 return JIM_ERR;
12795 if (Jim_ValidName(interp, "new procedure", argv[2])) {
12796 return JIM_ERR;
12799 oldName = Jim_GetString(argv[1], NULL);
12800 newName = Jim_GetString(argv[2], NULL);
12801 return Jim_RenameCommand(interp, oldName, newName);
12804 int Jim_DictKeys(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *patternObj)
12806 int i;
12807 int len;
12808 Jim_Obj *resultObj;
12809 Jim_Obj *dictObj;
12810 Jim_Obj **dictValuesObj;
12812 if (Jim_DictKeysVector(interp, objPtr, NULL, 0, &dictObj, JIM_ERRMSG) != JIM_OK) {
12813 return JIM_ERR;
12816 /* XXX: Could make the exact-match case much more efficient here.
12817 * See JimCommandsList()
12819 if (Jim_DictPairs(interp, dictObj, &dictValuesObj, &len) != JIM_OK) {
12820 return JIM_ERR;
12823 /* Only return the matching values */
12824 resultObj = Jim_NewListObj(interp, NULL, 0);
12826 for (i = 0; i < len; i += 2) {
12827 if (patternObj == NULL || Jim_StringMatchObj(interp, patternObj, dictValuesObj[i], 0)) {
12828 Jim_ListAppendElement(interp, resultObj, dictValuesObj[i]);
12831 Jim_Free(dictValuesObj);
12833 Jim_SetResult(interp, resultObj);
12834 return JIM_OK;
12837 int Jim_DictSize(Jim_Interp *interp, Jim_Obj *objPtr)
12839 if (SetDictFromAny(interp, objPtr) != JIM_OK) {
12840 return -1;
12842 return ((Jim_HashTable *)objPtr->internalRep.ptr)->used;
12845 /* [dict] */
12846 static int Jim_DictCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12848 Jim_Obj *objPtr;
12849 int option;
12850 const char *options[] = {
12851 "create", "get", "set", "unset", "exists", "keys", "merge", "size", "with", NULL
12853 enum
12855 OPT_CREATE, OPT_GET, OPT_SET, OPT_UNSET, OPT_EXIST, OPT_KEYS, OPT_MERGE, OPT_SIZE, OPT_WITH,
12858 if (argc < 2) {
12859 Jim_WrongNumArgs(interp, 1, argv, "subcommand ?arguments ...?");
12860 return JIM_ERR;
12863 if (Jim_GetEnum(interp, argv[1], options, &option, "subcommand", JIM_ERRMSG) != JIM_OK) {
12864 return JIM_ERR;
12867 switch (option) {
12868 case OPT_GET:
12869 if (argc < 3) {
12870 Jim_WrongNumArgs(interp, 2, argv, "varName ?key ...?");
12871 return JIM_ERR;
12873 if (Jim_DictKeysVector(interp, argv[2], argv + 3, argc - 3, &objPtr,
12874 JIM_ERRMSG) != JIM_OK) {
12875 return JIM_ERR;
12877 Jim_SetResult(interp, objPtr);
12878 return JIM_OK;
12880 case OPT_SET:
12881 if (argc < 5) {
12882 Jim_WrongNumArgs(interp, 2, argv, "varName key ?key ...? value");
12883 return JIM_ERR;
12885 return Jim_SetDictKeysVector(interp, argv[2], argv + 3, argc - 4, argv[argc - 1]);
12887 case OPT_EXIST:
12888 if (argc < 3) {
12889 Jim_WrongNumArgs(interp, 2, argv, "varName ?key ...?");
12890 return JIM_ERR;
12892 Jim_SetResultBool(interp, Jim_DictKeysVector(interp, argv[2], argv + 3, argc - 3,
12893 &objPtr, JIM_ERRMSG) == JIM_OK);
12894 return JIM_OK;
12896 case OPT_UNSET:
12897 if (argc < 4) {
12898 Jim_WrongNumArgs(interp, 2, argv, "varName key ?key ...?");
12899 return JIM_ERR;
12901 return Jim_SetDictKeysVector(interp, argv[2], argv + 3, argc - 3, NULL);
12903 case OPT_KEYS:
12904 if (argc != 3 && argc != 4) {
12905 Jim_WrongNumArgs(interp, 2, argv, "dictVar ?pattern?");
12906 return JIM_ERR;
12908 return Jim_DictKeys(interp, argv[2], argc == 4 ? argv[3] : NULL);
12910 case OPT_SIZE: {
12911 int size;
12913 if (argc != 3) {
12914 Jim_WrongNumArgs(interp, 2, argv, "dictVar");
12915 return JIM_ERR;
12918 size = Jim_DictSize(interp, argv[2]);
12919 if (size < 0) {
12920 return JIM_ERR;
12922 Jim_SetResultInt(interp, size);
12923 return JIM_OK;
12926 case OPT_MERGE:
12927 if (argc == 2) {
12928 return JIM_OK;
12930 else if (argv[2]->typePtr != &dictObjType && SetDictFromAny(interp, argv[2]) != JIM_OK) {
12931 return JIM_ERR;
12933 else {
12934 return Jim_EvalObjPrefix(interp, "dict merge", argc - 2, argv + 2);
12937 case OPT_WITH:
12938 if (argc < 4) {
12939 Jim_WrongNumArgs(interp, 2, argv, "dictVar ?key ...? script");
12940 return JIM_ERR;
12942 else if (Jim_GetVariable(interp, argv[2], JIM_ERRMSG) == NULL) {
12943 return JIM_ERR;
12945 else {
12946 return Jim_EvalObjPrefix(interp, "dict with", argc - 2, argv + 2);
12949 case OPT_CREATE:
12950 if (argc % 2) {
12951 Jim_WrongNumArgs(interp, 2, argv, "?key value ...?");
12952 return JIM_ERR;
12954 objPtr = Jim_NewDictObj(interp, argv + 2, argc - 2);
12955 Jim_SetResult(interp, objPtr);
12956 return JIM_OK;
12958 default:
12959 abort();
12963 /* [subst] */
12964 static int Jim_SubstCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
12966 const char *options[] = {
12967 "-nobackslashes", "-nocommands", "-novariables", NULL
12969 enum
12970 { OPT_NOBACKSLASHES, OPT_NOCOMMANDS, OPT_NOVARIABLES };
12971 int i;
12972 int flags = JIM_SUBST_FLAG;
12973 Jim_Obj *objPtr;
12975 if (argc < 2) {
12976 Jim_WrongNumArgs(interp, 1, argv, "?options? string");
12977 return JIM_ERR;
12979 for (i = 1; i < (argc - 1); i++) {
12980 int option;
12982 if (Jim_GetEnum(interp, argv[i], options, &option, NULL,
12983 JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) {
12984 return JIM_ERR;
12986 switch (option) {
12987 case OPT_NOBACKSLASHES:
12988 flags |= JIM_SUBST_NOESC;
12989 break;
12990 case OPT_NOCOMMANDS:
12991 flags |= JIM_SUBST_NOCMD;
12992 break;
12993 case OPT_NOVARIABLES:
12994 flags |= JIM_SUBST_NOVAR;
12995 break;
12998 if (Jim_SubstObj(interp, argv[argc - 1], &objPtr, flags) != JIM_OK) {
12999 return JIM_ERR;
13001 Jim_SetResult(interp, objPtr);
13002 return JIM_OK;
13005 /* [info] */
13006 static int Jim_InfoCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13008 int cmd;
13009 Jim_Obj *objPtr;
13010 int mode = 0;
13012 static const char * const commands[] = {
13013 "body", "commands", "procs", "channels", "exists", "globals", "level", "frame", "locals",
13014 "vars", "version", "patchlevel", "complete", "args", "hostname",
13015 "script", "source", "stacktrace", "nameofexecutable", "returncodes",
13016 "references", NULL
13018 enum
13019 { INFO_BODY, INFO_COMMANDS, INFO_PROCS, INFO_CHANNELS, INFO_EXISTS, INFO_GLOBALS, INFO_LEVEL,
13020 INFO_FRAME, INFO_LOCALS, INFO_VARS, INFO_VERSION, INFO_PATCHLEVEL, INFO_COMPLETE, INFO_ARGS,
13021 INFO_HOSTNAME, INFO_SCRIPT, INFO_SOURCE, INFO_STACKTRACE, INFO_NAMEOFEXECUTABLE,
13022 INFO_RETURNCODES, INFO_REFERENCES,
13025 if (argc < 2) {
13026 Jim_WrongNumArgs(interp, 1, argv, "subcommand ?args ...?");
13027 return JIM_ERR;
13029 if (Jim_GetEnum(interp, argv[1], commands, &cmd, "subcommand", JIM_ERRMSG | JIM_ENUM_ABBREV)
13030 != JIM_OK) {
13031 return JIM_ERR;
13034 /* Test for the the most common commands first, just in case it makes a difference */
13035 switch (cmd) {
13036 case INFO_EXISTS:{
13037 if (argc != 3) {
13038 Jim_WrongNumArgs(interp, 2, argv, "varName");
13039 return JIM_ERR;
13041 Jim_SetResultBool(interp, Jim_GetVariable(interp, argv[2], 0) != NULL);
13042 break;
13045 case INFO_CHANNELS:
13046 #ifndef jim_ext_aio
13047 Jim_SetResultString(interp, "aio not enabled", -1);
13048 return JIM_ERR;
13049 #endif
13050 case INFO_COMMANDS:
13051 case INFO_PROCS:
13052 if (argc != 2 && argc != 3) {
13053 Jim_WrongNumArgs(interp, 2, argv, "?pattern?");
13054 return JIM_ERR;
13056 Jim_SetResult(interp, JimCommandsList(interp, (argc == 3) ? argv[2] : NULL,
13057 (cmd - INFO_COMMANDS)));
13058 break;
13060 case INFO_VARS:
13061 mode++; /* JIM_VARLIST_VARS */
13062 case INFO_LOCALS:
13063 mode++; /* JIM_VARLIST_LOCALS */
13064 case INFO_GLOBALS:
13065 /* mode 0 => JIM_VARLIST_GLOBALS */
13066 if (argc != 2 && argc != 3) {
13067 Jim_WrongNumArgs(interp, 2, argv, "?pattern?");
13068 return JIM_ERR;
13070 Jim_SetResult(interp, JimVariablesList(interp, argc == 3 ? argv[2] : NULL, mode));
13071 break;
13073 case INFO_SCRIPT:
13074 if (argc != 2) {
13075 Jim_WrongNumArgs(interp, 2, argv, "");
13076 return JIM_ERR;
13078 Jim_SetResultString(interp, Jim_GetScript(interp, interp->currentScriptObj)->fileName,
13079 -1);
13080 break;
13082 case INFO_SOURCE:{
13083 const char *filename = "";
13084 int line = 0;
13085 Jim_Obj *resObjPtr;
13087 if (argc != 3) {
13088 Jim_WrongNumArgs(interp, 2, argv, "source");
13089 return JIM_ERR;
13091 if (argv[2]->typePtr == &sourceObjType) {
13092 filename = argv[2]->internalRep.sourceValue.fileName;
13093 line = argv[2]->internalRep.sourceValue.lineNumber;
13095 else if (argv[2]->typePtr == &scriptObjType) {
13096 ScriptObj *script = Jim_GetScript(interp, argv[2]);
13097 filename = script->fileName;
13098 line = script->line;
13100 resObjPtr = Jim_NewListObj(interp, NULL, 0);
13101 Jim_ListAppendElement(interp, resObjPtr, Jim_NewStringObj(interp, filename, -1));
13102 Jim_ListAppendElement(interp, resObjPtr, Jim_NewIntObj(interp, line));
13103 Jim_SetResult(interp, resObjPtr);
13104 break;
13107 case INFO_STACKTRACE:
13108 Jim_SetResult(interp, interp->stackTrace);
13109 break;
13111 case INFO_LEVEL:
13112 case INFO_FRAME:
13113 switch (argc) {
13114 case 2:
13115 Jim_SetResultInt(interp, interp->numLevels);
13116 break;
13118 case 3:
13119 if (JimInfoLevel(interp, argv[2], &objPtr, cmd == INFO_LEVEL) != JIM_OK) {
13120 return JIM_ERR;
13122 Jim_SetResult(interp, objPtr);
13123 break;
13125 default:
13126 Jim_WrongNumArgs(interp, 2, argv, "?levelNum?");
13127 return JIM_ERR;
13129 break;
13131 case INFO_BODY:
13132 case INFO_ARGS:{
13133 Jim_Cmd *cmdPtr;
13135 if (argc != 3) {
13136 Jim_WrongNumArgs(interp, 2, argv, "procname");
13137 return JIM_ERR;
13139 if ((cmdPtr = Jim_GetCommand(interp, argv[2], JIM_ERRMSG)) == NULL) {
13140 return JIM_ERR;
13142 if (cmdPtr->cmdProc != NULL) {
13143 Jim_SetResultFormatted(interp, "command \"%#s\" is not a procedure", argv[2]);
13144 return JIM_ERR;
13146 Jim_SetResult(interp,
13147 cmd == INFO_BODY ? cmdPtr->bodyObjPtr : cmdPtr->argListObjPtr);
13148 break;
13151 case INFO_VERSION:
13152 case INFO_PATCHLEVEL:{
13153 char buf[(JIM_INTEGER_SPACE * 2) + 1];
13155 sprintf(buf, "%d.%d", JIM_VERSION / 100, JIM_VERSION % 100);
13156 Jim_SetResultString(interp, buf, -1);
13157 break;
13160 case INFO_COMPLETE:
13161 if (argc != 3) {
13162 Jim_WrongNumArgs(interp, 2, argv, "script");
13163 return JIM_ERR;
13165 else {
13166 int len;
13167 const char *s = Jim_GetString(argv[2], &len);
13169 Jim_SetResultBool(interp, Jim_ScriptIsComplete(s, len, NULL));
13171 break;
13173 case INFO_HOSTNAME:
13174 /* Redirect to os.gethostname if it exists */
13175 return Jim_Eval(interp, "os.gethostname");
13177 case INFO_NAMEOFEXECUTABLE:
13178 /* Redirect to Tcl proc */
13179 return Jim_Eval(interp, "{info nameofexecutable}");
13181 case INFO_RETURNCODES:
13182 if (argc == 2) {
13183 int i;
13184 Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0);
13186 for (i = 0; jimReturnCodes[i]; i++) {
13187 Jim_ListAppendElement(interp, listObjPtr, Jim_NewIntObj(interp, i));
13188 Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp,
13189 jimReturnCodes[i], -1));
13192 Jim_SetResult(interp, listObjPtr);
13194 else if (argc == 3) {
13195 long code;
13196 const char *name;
13198 if (Jim_GetLong(interp, argv[2], &code) != JIM_OK) {
13199 return JIM_ERR;
13201 name = Jim_ReturnCode(code);
13202 if (*name == '?') {
13203 Jim_SetResultInt(interp, code);
13205 else {
13206 Jim_SetResultString(interp, name, -1);
13209 else {
13210 Jim_WrongNumArgs(interp, 2, argv, "?code?");
13211 return JIM_ERR;
13213 break;
13214 case INFO_REFERENCES:
13215 #ifdef JIM_REFERENCES
13216 return JimInfoReferences(interp, argc, argv);
13217 #else
13218 Jim_SetResultString(interp, "not supported", -1);
13219 return JIM_ERR;
13220 #endif
13222 return JIM_OK;
13225 /* [exists] */
13226 static int Jim_ExistsCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13228 Jim_Obj *objPtr;
13230 static const char * const options[] = {
13231 "-command", "-proc", "-var", NULL
13233 enum
13235 OPT_COMMAND, OPT_PROC, OPT_VAR
13237 int option;
13239 if (argc == 2) {
13240 option = OPT_VAR;
13241 objPtr = argv[1];
13243 else if (argc == 3) {
13244 if (Jim_GetEnum(interp, argv[1], options, &option, NULL, JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) {
13245 return JIM_ERR;
13247 objPtr = argv[2];
13249 else {
13250 Jim_WrongNumArgs(interp, 1, argv, "?option? name");
13251 return JIM_ERR;
13254 /* Test for the the most common commands first, just in case it makes a difference */
13255 switch (option) {
13256 case OPT_VAR:
13257 Jim_SetResultBool(interp, Jim_GetVariable(interp, objPtr, 0) != NULL);
13258 break;
13260 case OPT_COMMAND:
13261 case OPT_PROC: {
13262 Jim_Cmd *cmd = Jim_GetCommand(interp, objPtr, JIM_NONE);
13263 Jim_SetResultBool(interp, cmd != NULL && (option == OPT_COMMAND || !cmd->cmdProc));
13264 break;
13267 return JIM_OK;
13270 /* [split] */
13271 static int Jim_SplitCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13273 const char *str, *splitChars, *noMatchStart;
13274 int splitLen, strLen;
13275 Jim_Obj *resObjPtr;
13276 int c;
13277 int len;
13279 if (argc != 2 && argc != 3) {
13280 Jim_WrongNumArgs(interp, 1, argv, "string ?splitChars?");
13281 return JIM_ERR;
13284 str = Jim_GetString(argv[1], &len);
13285 if (len == 0) {
13286 return JIM_OK;
13288 strLen = Jim_Utf8Length(interp, argv[1]);
13290 /* Init */
13291 if (argc == 2) {
13292 splitChars = " \n\t\r";
13293 splitLen = 4;
13295 else {
13296 splitChars = Jim_GetString(argv[2], NULL);
13297 splitLen = Jim_Utf8Length(interp, argv[2]);
13300 noMatchStart = str;
13301 resObjPtr = Jim_NewListObj(interp, NULL, 0);
13303 /* Split */
13304 if (splitLen) {
13305 Jim_Obj *objPtr;
13306 while (strLen--) {
13307 const char *sc = splitChars;
13308 int scLen = splitLen;
13309 int sl = utf8_tounicode(str, &c);
13310 while (scLen--) {
13311 int pc;
13312 sc += utf8_tounicode(sc, &pc);
13313 if (c == pc) {
13314 objPtr = Jim_NewStringObj(interp, noMatchStart, (str - noMatchStart));
13315 Jim_ListAppendElement(interp, resObjPtr, objPtr);
13316 noMatchStart = str + sl;
13317 break;
13320 str += sl;
13322 objPtr = Jim_NewStringObj(interp, noMatchStart, (str - noMatchStart));
13323 Jim_ListAppendElement(interp, resObjPtr, objPtr);
13325 else {
13326 /* This handles the special case of splitchars eq {}
13327 * Optimise by sharing common (ASCII) characters
13329 Jim_Obj **commonObj = NULL;
13330 #define NUM_COMMON (128 - 32)
13331 while (strLen--) {
13332 int n = utf8_tounicode(str, &c);
13333 #ifdef JIM_OPTIMIZATION
13334 if (c >= 32 && c < 128) {
13335 /* Common ASCII char */
13336 c -= 32;
13337 if (!commonObj) {
13338 commonObj = Jim_Alloc(sizeof(*commonObj) * NUM_COMMON);
13339 memset(commonObj, 0, sizeof(*commonObj) * NUM_COMMON);
13341 if (!commonObj[c]) {
13342 commonObj[c] = Jim_NewStringObj(interp, str, 1);
13344 Jim_ListAppendElement(interp, resObjPtr, commonObj[c]);
13345 str++;
13346 continue;
13348 #endif
13349 Jim_ListAppendElement(interp, resObjPtr, Jim_NewStringObjUtf8(interp, str, 1));
13350 str += n;
13352 Jim_Free(commonObj);
13355 Jim_SetResult(interp, resObjPtr);
13356 return JIM_OK;
13359 /* [join] */
13360 static int Jim_JoinCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13362 const char *joinStr;
13363 int joinStrLen, i, listLen;
13364 Jim_Obj *resObjPtr;
13366 if (argc != 2 && argc != 3) {
13367 Jim_WrongNumArgs(interp, 1, argv, "list ?joinString?");
13368 return JIM_ERR;
13370 /* Init */
13371 if (argc == 2) {
13372 joinStr = " ";
13373 joinStrLen = 1;
13375 else {
13376 joinStr = Jim_GetString(argv[2], &joinStrLen);
13378 listLen = Jim_ListLength(interp, argv[1]);
13379 resObjPtr = Jim_NewStringObj(interp, NULL, 0);
13380 /* Split */
13381 for (i = 0; i < listLen; i++) {
13382 Jim_Obj *objPtr = 0;
13384 Jim_ListIndex(interp, argv[1], i, &objPtr, JIM_NONE);
13385 Jim_AppendObj(interp, resObjPtr, objPtr);
13386 if (i + 1 != listLen) {
13387 Jim_AppendString(interp, resObjPtr, joinStr, joinStrLen);
13390 Jim_SetResult(interp, resObjPtr);
13391 return JIM_OK;
13394 /* [format] */
13395 static int Jim_FormatCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13397 Jim_Obj *objPtr;
13399 if (argc < 2) {
13400 Jim_WrongNumArgs(interp, 1, argv, "formatString ?arg arg ...?");
13401 return JIM_ERR;
13403 objPtr = Jim_FormatString(interp, argv[1], argc - 2, argv + 2);
13404 if (objPtr == NULL)
13405 return JIM_ERR;
13406 Jim_SetResult(interp, objPtr);
13407 return JIM_OK;
13410 /* [scan] */
13411 static int Jim_ScanCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13413 Jim_Obj *listPtr, **outVec;
13414 int outc, i;
13416 if (argc < 3) {
13417 Jim_WrongNumArgs(interp, 1, argv, "string format ?varName varName ...?");
13418 return JIM_ERR;
13420 if (argv[2]->typePtr != &scanFmtStringObjType)
13421 SetScanFmtFromAny(interp, argv[2]);
13422 if (FormatGetError(argv[2]) != 0) {
13423 Jim_SetResultString(interp, FormatGetError(argv[2]), -1);
13424 return JIM_ERR;
13426 if (argc > 3) {
13427 int maxPos = FormatGetMaxPos(argv[2]);
13428 int count = FormatGetCnvCount(argv[2]);
13430 if (maxPos > argc - 3) {
13431 Jim_SetResultString(interp, "\"%n$\" argument index out of range", -1);
13432 return JIM_ERR;
13434 else if (count > argc - 3) {
13435 Jim_SetResultString(interp, "different numbers of variable names and "
13436 "field specifiers", -1);
13437 return JIM_ERR;
13439 else if (count < argc - 3) {
13440 Jim_SetResultString(interp, "variable is not assigned by any "
13441 "conversion specifiers", -1);
13442 return JIM_ERR;
13445 listPtr = Jim_ScanString(interp, argv[1], argv[2], JIM_ERRMSG);
13446 if (listPtr == 0)
13447 return JIM_ERR;
13448 if (argc > 3) {
13449 int rc = JIM_OK;
13450 int count = 0;
13452 if (listPtr != 0 && listPtr != (Jim_Obj *)EOF) {
13453 int len = Jim_ListLength(interp, listPtr);
13455 if (len != 0) {
13456 JimListGetElements(interp, listPtr, &outc, &outVec);
13457 for (i = 0; i < outc; ++i) {
13458 if (Jim_Length(outVec[i]) > 0) {
13459 ++count;
13460 if (Jim_SetVariable(interp, argv[3 + i], outVec[i]) != JIM_OK) {
13461 rc = JIM_ERR;
13466 Jim_FreeNewObj(interp, listPtr);
13468 else {
13469 count = -1;
13471 if (rc == JIM_OK) {
13472 Jim_SetResultInt(interp, count);
13474 return rc;
13476 else {
13477 if (listPtr == (Jim_Obj *)EOF) {
13478 Jim_SetResult(interp, Jim_NewListObj(interp, 0, 0));
13479 return JIM_OK;
13481 Jim_SetResult(interp, listPtr);
13483 return JIM_OK;
13486 /* [error] */
13487 static int Jim_ErrorCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13489 if (argc != 2 && argc != 3) {
13490 Jim_WrongNumArgs(interp, 1, argv, "message ?stacktrace?");
13491 return JIM_ERR;
13493 Jim_SetResult(interp, argv[1]);
13494 if (argc == 3) {
13495 JimSetStackTrace(interp, argv[2]);
13496 return JIM_ERR;
13498 interp->addStackTrace++;
13499 return JIM_ERR;
13502 /* [lrange] */
13503 static int Jim_LrangeCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13505 Jim_Obj *objPtr;
13507 if (argc != 4) {
13508 Jim_WrongNumArgs(interp, 1, argv, "list first last");
13509 return JIM_ERR;
13511 if ((objPtr = Jim_ListRange(interp, argv[1], argv[2], argv[3])) == NULL)
13512 return JIM_ERR;
13513 Jim_SetResult(interp, objPtr);
13514 return JIM_OK;
13517 /* [lrepeat] */
13518 static int Jim_LrepeatCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13520 Jim_Obj *objPtr;
13521 long count;
13523 if (argc < 2 || Jim_GetLong(interp, argv[1], &count) != JIM_OK || count < 0) {
13524 Jim_WrongNumArgs(interp, 1, argv, "count ?value ...?");
13525 return JIM_ERR;
13528 if (count == 0 || argc == 2) {
13529 return JIM_OK;
13532 argc -= 2;
13533 argv += 2;
13535 objPtr = Jim_NewListObj(interp, argv, argc);
13536 while (--count) {
13537 int i;
13539 for (i = 0; i < argc; i++) {
13540 ListAppendElement(objPtr, argv[i]);
13544 Jim_SetResult(interp, objPtr);
13545 return JIM_OK;
13548 /* [env] */
13549 static int Jim_EnvCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13551 const char *key;
13552 const char *val;
13554 if (argc == 1) {
13555 #ifndef NO_ENVIRON_EXTERN
13556 extern char **environ;
13557 #endif
13559 int i;
13560 Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0);
13562 for (i = 0; environ[i]; i++) {
13563 const char *equals = strchr(environ[i], '=');
13565 if (equals) {
13566 Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, environ[i],
13567 equals - environ[i]));
13568 Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, equals + 1, -1));
13572 Jim_SetResult(interp, listObjPtr);
13573 return JIM_OK;
13576 if (argc < 2) {
13577 Jim_WrongNumArgs(interp, 1, argv, "varName ?default?");
13578 return JIM_ERR;
13580 key = Jim_GetString(argv[1], NULL);
13581 val = getenv(key);
13582 if (val == NULL) {
13583 if (argc < 3) {
13584 Jim_SetResultFormatted(interp, "environment variable \"%#s\" does not exist", argv[1]);
13585 return JIM_ERR;
13587 val = Jim_GetString(argv[2], NULL);
13589 Jim_SetResult(interp, Jim_NewStringObj(interp, val, -1));
13590 return JIM_OK;
13593 /* [source] */
13594 static int Jim_SourceCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13596 int retval;
13598 if (argc != 2) {
13599 Jim_WrongNumArgs(interp, 1, argv, "fileName");
13600 return JIM_ERR;
13602 retval = Jim_EvalFile(interp, Jim_GetString(argv[1], NULL));
13603 if (retval == JIM_RETURN)
13604 return JIM_OK;
13605 return retval;
13608 /* [lreverse] */
13609 static int Jim_LreverseCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13611 Jim_Obj *revObjPtr, **ele;
13612 int len;
13614 if (argc != 2) {
13615 Jim_WrongNumArgs(interp, 1, argv, "list");
13616 return JIM_ERR;
13618 JimListGetElements(interp, argv[1], &len, &ele);
13619 len--;
13620 revObjPtr = Jim_NewListObj(interp, NULL, 0);
13621 while (len >= 0)
13622 ListAppendElement(revObjPtr, ele[len--]);
13623 Jim_SetResult(interp, revObjPtr);
13624 return JIM_OK;
13627 static int JimRangeLen(jim_wide start, jim_wide end, jim_wide step)
13629 jim_wide len;
13631 if (step == 0)
13632 return -1;
13633 if (start == end)
13634 return 0;
13635 else if (step > 0 && start > end)
13636 return -1;
13637 else if (step < 0 && end > start)
13638 return -1;
13639 len = end - start;
13640 if (len < 0)
13641 len = -len; /* abs(len) */
13642 if (step < 0)
13643 step = -step; /* abs(step) */
13644 len = 1 + ((len - 1) / step);
13645 /* We can truncate safely to INT_MAX, the range command
13646 * will always return an error for a such long range
13647 * because Tcl lists can't be so long. */
13648 if (len > INT_MAX)
13649 len = INT_MAX;
13650 return (int)((len < 0) ? -1 : len);
13653 /* [range] */
13654 static int Jim_RangeCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13656 jim_wide start = 0, end, step = 1;
13657 int len, i;
13658 Jim_Obj *objPtr;
13660 if (argc < 2 || argc > 4) {
13661 Jim_WrongNumArgs(interp, 1, argv, "?start? end ?step?");
13662 return JIM_ERR;
13664 if (argc == 2) {
13665 if (Jim_GetWide(interp, argv[1], &end) != JIM_OK)
13666 return JIM_ERR;
13668 else {
13669 if (Jim_GetWide(interp, argv[1], &start) != JIM_OK ||
13670 Jim_GetWide(interp, argv[2], &end) != JIM_OK)
13671 return JIM_ERR;
13672 if (argc == 4 && Jim_GetWide(interp, argv[3], &step) != JIM_OK)
13673 return JIM_ERR;
13675 if ((len = JimRangeLen(start, end, step)) == -1) {
13676 Jim_SetResultString(interp, "Invalid (infinite?) range specified", -1);
13677 return JIM_ERR;
13679 objPtr = Jim_NewListObj(interp, NULL, 0);
13680 for (i = 0; i < len; i++)
13681 ListAppendElement(objPtr, Jim_NewIntObj(interp, start + i * step));
13682 Jim_SetResult(interp, objPtr);
13683 return JIM_OK;
13686 /* [rand] */
13687 static int Jim_RandCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
13689 jim_wide min = 0, max = 0, len, maxMul;
13691 if (argc < 1 || argc > 3) {
13692 Jim_WrongNumArgs(interp, 1, argv, "?min? max");
13693 return JIM_ERR;
13695 if (argc == 1) {
13696 max = JIM_WIDE_MAX;
13697 } else if (argc == 2) {
13698 if (Jim_GetWide(interp, argv[1], &max) != JIM_OK)
13699 return JIM_ERR;
13700 } else if (argc == 3) {
13701 if (Jim_GetWide(interp, argv[1], &min) != JIM_OK ||
13702 Jim_GetWide(interp, argv[2], &max) != JIM_OK)
13703 return JIM_ERR;
13705 len = max-min;
13706 if (len < 0) {
13707 Jim_SetResultString(interp, "Invalid arguments (max < min)", -1);
13708 return JIM_ERR;
13710 maxMul = JIM_WIDE_MAX - (len ? (JIM_WIDE_MAX%len) : 0);
13711 while (1) {
13712 jim_wide r;
13714 JimRandomBytes(interp, &r, sizeof(jim_wide));
13715 if (r < 0 || r >= maxMul) continue;
13716 r = (len == 0) ? 0 : r%len;
13717 Jim_SetResultInt(interp, min+r);
13718 return JIM_OK;
13722 static const struct {
13723 const char *name;
13724 Jim_CmdProc cmdProc;
13725 } Jim_CoreCommandsTable[] = {
13726 {"set", Jim_SetCoreCommand},
13727 {"unset", Jim_UnsetCoreCommand},
13728 {"puts", Jim_PutsCoreCommand},
13729 {"+", Jim_AddCoreCommand},
13730 {"*", Jim_MulCoreCommand},
13731 {"-", Jim_SubCoreCommand},
13732 {"/", Jim_DivCoreCommand},
13733 {"incr", Jim_IncrCoreCommand},
13734 {"while", Jim_WhileCoreCommand},
13735 {"for", Jim_ForCoreCommand},
13736 {"foreach", Jim_ForeachCoreCommand},
13737 {"lmap", Jim_LmapCoreCommand},
13738 {"if", Jim_IfCoreCommand},
13739 {"switch", Jim_SwitchCoreCommand},
13740 {"list", Jim_ListCoreCommand},
13741 {"lindex", Jim_LindexCoreCommand},
13742 {"lset", Jim_LsetCoreCommand},
13743 {"lsearch", Jim_LsearchCoreCommand},
13744 {"llength", Jim_LlengthCoreCommand},
13745 {"lappend", Jim_LappendCoreCommand},
13746 {"linsert", Jim_LinsertCoreCommand},
13747 {"lreplace", Jim_LreplaceCoreCommand},
13748 {"lsort", Jim_LsortCoreCommand},
13749 {"append", Jim_AppendCoreCommand},
13750 {"debug", Jim_DebugCoreCommand},
13751 {"eval", Jim_EvalCoreCommand},
13752 {"uplevel", Jim_UplevelCoreCommand},
13753 {"expr", Jim_ExprCoreCommand},
13754 {"break", Jim_BreakCoreCommand},
13755 {"continue", Jim_ContinueCoreCommand},
13756 {"proc", Jim_ProcCoreCommand},
13757 {"concat", Jim_ConcatCoreCommand},
13758 {"return", Jim_ReturnCoreCommand},
13759 {"upvar", Jim_UpvarCoreCommand},
13760 {"global", Jim_GlobalCoreCommand},
13761 {"string", Jim_StringCoreCommand},
13762 {"time", Jim_TimeCoreCommand},
13763 {"exit", Jim_ExitCoreCommand},
13764 {"catch", Jim_CatchCoreCommand},
13765 #ifdef JIM_REFERENCES
13766 {"ref", Jim_RefCoreCommand},
13767 {"getref", Jim_GetrefCoreCommand},
13768 {"setref", Jim_SetrefCoreCommand},
13769 {"finalize", Jim_FinalizeCoreCommand},
13770 {"collect", Jim_CollectCoreCommand},
13771 #endif
13772 {"rename", Jim_RenameCoreCommand},
13773 {"dict", Jim_DictCoreCommand},
13774 {"subst", Jim_SubstCoreCommand},
13775 {"info", Jim_InfoCoreCommand},
13776 {"exists", Jim_ExistsCoreCommand},
13777 {"split", Jim_SplitCoreCommand},
13778 {"join", Jim_JoinCoreCommand},
13779 {"format", Jim_FormatCoreCommand},
13780 {"scan", Jim_ScanCoreCommand},
13781 {"error", Jim_ErrorCoreCommand},
13782 {"lrange", Jim_LrangeCoreCommand},
13783 {"lrepeat", Jim_LrepeatCoreCommand},
13784 {"env", Jim_EnvCoreCommand},
13785 {"source", Jim_SourceCoreCommand},
13786 {"lreverse", Jim_LreverseCoreCommand},
13787 {"range", Jim_RangeCoreCommand},
13788 {"rand", Jim_RandCoreCommand},
13789 {"tailcall", Jim_TailcallCoreCommand},
13790 {"local", Jim_LocalCoreCommand},
13791 {NULL, NULL},
13794 void Jim_RegisterCoreCommands(Jim_Interp *interp)
13796 int i = 0;
13798 while (Jim_CoreCommandsTable[i].name != NULL) {
13799 Jim_CreateCommand(interp,
13800 Jim_CoreCommandsTable[i].name, Jim_CoreCommandsTable[i].cmdProc, NULL, NULL);
13801 i++;
13805 /* -----------------------------------------------------------------------------
13806 * Interactive prompt
13807 * ---------------------------------------------------------------------------*/
13808 void Jim_MakeErrorMessage(Jim_Interp *interp)
13810 Jim_Obj *argv[2];
13812 argv[0] = Jim_NewStringObj(interp, "errorInfo", -1);
13813 argv[1] = interp->result;
13815 Jim_EvalObjVector(interp, 2, argv);
13818 static void JimSetFailedEnumResult(Jim_Interp *interp, const char *arg, const char *badtype,
13819 const char *prefix, const char *const *tablePtr, const char *name)
13821 int count;
13822 char **tablePtrSorted;
13823 int i;
13825 for (count = 0; tablePtr[count]; count++) {
13828 if (name == NULL) {
13829 name = "option";
13832 Jim_SetResultFormatted(interp, "%s%s \"%s\": must be ", badtype, name, arg);
13833 tablePtrSorted = Jim_Alloc(sizeof(char *) * count);
13834 memcpy(tablePtrSorted, tablePtr, sizeof(char *) * count);
13835 qsort(tablePtrSorted, count, sizeof(char *), qsortCompareStringPointers);
13836 for (i = 0; i < count; i++) {
13837 if (i + 1 == count && count > 1) {
13838 Jim_AppendString(interp, Jim_GetResult(interp), "or ", -1);
13840 Jim_AppendStrings(interp, Jim_GetResult(interp), prefix, tablePtrSorted[i], NULL);
13841 if (i + 1 != count) {
13842 Jim_AppendString(interp, Jim_GetResult(interp), ", ", -1);
13845 Jim_Free(tablePtrSorted);
13848 int Jim_GetEnum(Jim_Interp *interp, Jim_Obj *objPtr,
13849 const char *const *tablePtr, int *indexPtr, const char *name, int flags)
13851 const char *bad = "bad ";
13852 const char *const *entryPtr = NULL;
13853 int i;
13854 int match = -1;
13855 int arglen;
13856 const char *arg = Jim_GetString(objPtr, &arglen);
13858 *indexPtr = -1;
13860 for (entryPtr = tablePtr, i = 0; *entryPtr != NULL; entryPtr++, i++) {
13861 if (Jim_CompareStringImmediate(interp, objPtr, *entryPtr)) {
13862 /* Found an exact match */
13863 *indexPtr = i;
13864 return JIM_OK;
13866 if (flags & JIM_ENUM_ABBREV) {
13867 /* Accept an unambiguous abbreviation.
13868 * Note that '-' doesnt' consitute a valid abbreviation
13870 if (strncmp(arg, *entryPtr, arglen) == 0) {
13871 if (*arg == '-' && arglen == 1) {
13872 break;
13874 if (match >= 0) {
13875 bad = "ambiguous ";
13876 goto ambiguous;
13878 match = i;
13883 /* If we had an unambiguous partial match */
13884 if (match >= 0) {
13885 *indexPtr = match;
13886 return JIM_OK;
13889 ambiguous:
13890 if (flags & JIM_ERRMSG) {
13891 JimSetFailedEnumResult(interp, arg, bad, "", tablePtr, name);
13893 return JIM_ERR;
13896 int Jim_FindByName(const char *name, const char * const array[], size_t len)
13898 int i;
13900 for (i = 0; i < (int)len; i++) {
13901 if (array[i] && strcmp(array[i], name) == 0) {
13902 return i;
13905 return -1;
13908 int Jim_IsDict(Jim_Obj *objPtr)
13910 return objPtr->typePtr == &dictObjType;
13913 int Jim_IsList(Jim_Obj *objPtr)
13915 return objPtr->typePtr == &listObjType;
13919 * Very simple printf-like formatting, designed for error messages.
13921 * The format may contain up to 5 '%s' or '%#s', corresponding to variable arguments.
13922 * The resulting string is created and set as the result.
13924 * Each '%s' should correspond to a regular string parameter.
13925 * Each '%#s' should correspond to a (Jim_Obj *) parameter.
13926 * Any other printf specifier is not allowed (but %% is allowed for the % character).
13928 * e.g. Jim_SetResultFormatted(interp, "Bad option \"%#s\" in proc \"%#s\"", optionObjPtr, procNamePtr);
13930 * Note: We take advantage of the fact that printf has the same behaviour for both %s and %#s
13932 void Jim_SetResultFormatted(Jim_Interp *interp, const char *format, ...)
13934 /* Initial space needed */
13935 int len = strlen(format);
13936 int extra = 0;
13937 int n = 0;
13938 const char *params[5];
13939 char *buf;
13940 va_list args;
13941 int i;
13943 va_start(args, format);
13945 for (i = 0; i < len && n < 5; i++) {
13946 int l;
13948 if (strncmp(format + i, "%s", 2) == 0) {
13949 params[n] = va_arg(args, char *);
13951 l = strlen(params[n]);
13953 else if (strncmp(format + i, "%#s", 3) == 0) {
13954 Jim_Obj *objPtr = va_arg(args, Jim_Obj *);
13956 params[n] = Jim_GetString(objPtr, &l);
13958 else {
13959 if (format[i] == '%') {
13960 i++;
13962 continue;
13964 n++;
13965 extra += l;
13968 len += extra;
13969 buf = Jim_Alloc(len + 1);
13970 len = snprintf(buf, len + 1, format, params[0], params[1], params[2], params[3], params[4]);
13972 Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, len));
13975 /* stubs */
13976 #ifndef jim_ext_package
13977 int Jim_PackageProvide(Jim_Interp *interp, const char *name, const char *ver, int flags)
13979 return JIM_OK;
13981 #endif
13982 #ifndef jim_ext_aio
13983 FILE *Jim_AioFilehandle(Jim_Interp *interp, Jim_Obj *fhObj)
13985 Jim_SetResultString(interp, "aio not enabled", -1);
13986 return NULL;
13988 #endif
13992 * Local Variables: ***
13993 * c-basic-offset: 4 ***
13994 * tab-width: 4 ***
13995 * End: ***