/* Jim - A small embeddable Tcl interpreter
*
* Copyright 2005 Salvatore Sanfilippo <antirez@invece.org>
* Copyright 2005 Clemens Hintze <c.hintze@gmx.net>
* Copyright 2005 patthoyts - Pat Thoyts <patthoyts@users.sf.net>
* Copyright 2008,2009 oharboe - Øyvind Harboe - oyvind.harboe@zylin.com
* Copyright 2008 Andrew Lunn <andrew@lunn.ch>
* Copyright 2008 Duane Ellis <openocd@duaneellis.com>
* Copyright 2008 Uwe Klein <uklein@klein-messgeraete.de>
* Copyright 2008 Steve Bennett <steveb@workware.net.au>
* Copyright 2009 Nico Coesel <ncoesel@dealogic.nl>
* Copyright 2009 Zachary T Welch zw@superlucidity.net
* Copyright 2009 David Brownell
*
* The FreeBSD license
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following
* disclaimer in the documentation and/or other materials
* provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE JIM TCL PROJECT ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* JIM TCL PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* The views and conclusions contained in the software and documentation
* are those of the authors and should not be interpreted as representing
* official policies, either expressed or implied, of the Jim Tcl Project.
**/
#define JIM_OPTIMIZATION /* comment to avoid optimizations and reduce size */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <ctype.h>
#include <limits.h>
#include <assert.h>
#include <errno.h>
#include <time.h>
#include <setjmp.h>
#include <unistd.h>
#include <sys/time.h>
#include "jim.h"
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
#endif
/* For INFINITY, even if math functions are not enabled */
#include <math.h>
/*#define DEBUG_SHOW_SCRIPT*/
/*#define DEBUG_SHOW_SCRIPT_TOKENS*/
/*#define DEBUG_SHOW_SUBST*/
/*#define DEBUG_SHOW_EXPR*/
/*#define JIM_DEBUG_GC*/
/*#define JIM_DEBUG_COMMAND*/
#if defined(DEBUG_SHOW_SCRIPT) || defined(DEBUG_SHOW_SCRIPT_TOKENS) || defined(DEBUG_SHOW_EXPR) || defined(DEBUG_SHOW_SUBST)
static const char *tt_name(int type);
#endif
/* -----------------------------------------------------------------------------
* Global variables
* ---------------------------------------------------------------------------*/
/* A shared empty string for the objects string representation.
* Jim_InvalidateStringRep knows about it and doesn't try to free it. */
static char JimEmptyStringRep[] = "";
/* -----------------------------------------------------------------------------
* Required prototypes of not exported functions
* ---------------------------------------------------------------------------*/
static void JimChangeCallFrameId(Jim_Interp *interp, Jim_CallFrame *cf);
static void JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int flags);
static int ListSetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int index, Jim_Obj *newObjPtr,
int flags);
static Jim_Obj *Jim_ExpandDictSugar(Jim_Interp *interp, Jim_Obj *objPtr);
static void SetDictSubstFromAny(Jim_Interp *interp, Jim_Obj *objPtr);
static void JimSetFailedEnumResult(Jim_Interp *interp, const char *arg, const char *badtype,
const char *prefix, const char *const *tablePtr, const char *name);
static void JimDeleteLocalProcs(Jim_Interp *interp);
static int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, const char *filename, int linenr,
int argc, Jim_Obj *const *argv);
static int JimEvalObjVector(Jim_Interp *interp, int objc, Jim_Obj *const *objv,
const char *filename, int linenr);
static int JimGetWideNoErr(Jim_Interp *interp, Jim_Obj *objPtr, jim_wide * widePtr);
static const Jim_HashTableType JimVariablesHashTableType;
/* Fast access to the int (wide) value of an object which is known to be of int type */
#define JimWideValue(objPtr) (objPtr)->internalRep.wideValue
/* -----------------------------------------------------------------------------
* Utility functions
* ---------------------------------------------------------------------------*/
/* Glob-style pattern matching. */
static int JimStringMatch(const char *pattern, int patternLen,
const char *string, int stringLen, int nocase)
{
while (patternLen) {
switch (pattern[0]) {
case '*':
while (pattern[1] == '*') {
pattern++;
patternLen--;
}
if (patternLen == 1)
return 1; /* match */
while (stringLen) {
if (JimStringMatch(pattern + 1, patternLen - 1, string, stringLen, nocase))
return 1; /* match */
string++;
stringLen--;
}
return 0; /* no match */
break;
case '?':
if (stringLen == 0)
return 0; /* no match */
string++;
stringLen--;
break;
case '[':
{
int not, match;
pattern++;
patternLen--;
not = pattern[0] == '^';
if (not) {
pattern++;
patternLen--;
}
match = 0;
while (1) {
if (pattern[0] == '\\') {
pattern++;
patternLen--;
if (pattern[0] == string[0])
match = 1;
}
else if (pattern[0] == ']') {
break;
}
else if (patternLen == 0) {
pattern--;
patternLen++;
break;
}
else if (pattern[1] == '-' && patternLen >= 3) {
int start = pattern[0];
int end = pattern[2];
int c = string[0];
if (start > end) {
int t = start;
start = end;
end = t;
}
if (nocase) {
start = tolower(start);
end = tolower(end);
c = tolower(c);
}
pattern += 2;
patternLen -= 2;
if (c >= start && c <= end)
match = 1;
}
else {
if (!nocase) {
if (pattern[0] == string[0])
match = 1;
}
else {
if (tolower((int)pattern[0]) == tolower((int)string[0]))
match = 1;
}
}
pattern++;
patternLen--;
}
if (not)
match = !match;
if (!match)
return 0; /* no match */
string++;
stringLen--;
break;
}
case '\\':
if (patternLen >= 2) {
pattern++;
patternLen--;
}
/* fall through */
default:
if (!nocase) {
if (pattern[0] != string[0])
return 0; /* no match */
}
else {
if (tolower((int)pattern[0]) != tolower((int)string[0]))
return 0; /* no match */
}
string++;
stringLen--;
break;
}
pattern++;
patternLen--;
if (stringLen == 0) {
while (*pattern == '*') {
pattern++;
patternLen--;
}
break;
}
}
if (patternLen == 0 && stringLen == 0)
return 1;
return 0;
}
int JimStringCompare(const char *s1, int l1, const char *s2, int l2, int nocase)
{
unsigned char *u1 = (unsigned char *)s1, *u2 = (unsigned char *)s2;
int diff;
if (nocase == 0) {
while (l1 && l2) {
diff = (int)*u1 - *u2;
if (diff) {
goto done;
}
u1++;
u2++;
l1--;
l2--;
}
diff = l1 - l2;
}
else {
while (l1 && l2) {
diff = tolower((int)*u1) - tolower((int)*u2);
if (diff) {
goto done;
}
u1++;
u2++;
l1--;
l2--;
}
diff = l1 - l2;
}
if (diff == 0) {
return 0;
}
done:
return diff < 0 ? -1 : 1;
}
/* Search 's1' inside 's2', starting to search from char 'index' of 's2'.
* The index of the first occurrence of s1 in s2 is returned.
* If s1 is not found inside s2, -1 is returned. */
int JimStringFirst(const char *s1, int l1, const char *s2, int l2, int index)
{
int i;
if (!l1 || !l2 || l1 > l2)
return -1;
if (index < 0)
index = 0;
s2 += index;
for (i = index; i <= l2 - l1; i++) {
if (memcmp(s2, s1, l1) == 0)
return i;
s2++;
}
return -1;
}
int JimStringLast(const char *s1, int l1, const char *s2, int l2)
{
const char *p;
if (!l1 || !l2 || l1 > l2)
return -1;
/* Now search for the needle */
for (p = s2 + l2 - 1; p != s2 - 1; p--) {
if (*p == *s1 && memcmp(s1, p, l1) == 0) {
return p - s2;
}
}
return -1;
}
int Jim_WideToString(char *buf, jim_wide wideValue)
{
const char *fmt = "%" JIM_WIDE_MODIFIER;
return sprintf(buf, fmt, wideValue);
}
/**
* After an strtol()/strtod()-like conversion,
* check whether something was converted and that
* the only thing left is white space.
*
* Returns JIM_OK or JIM_ERR.
*/
static int JimCheckConversion(const char *str, const char *endptr)
{
if (str[0] == '\0' || str == endptr) {
return JIM_ERR;
}
if (endptr[0] != '\0') {
while (*endptr) {
if (!isspace(*endptr)) {
return JIM_ERR;
}
endptr++;
}
}
return JIM_OK;
}
int Jim_StringToWide(const char *str, jim_wide * widePtr, int base)
{
char *endptr;
*widePtr = strtoull(str, &endptr, base);
return JimCheckConversion(str, endptr);
}
int Jim_DoubleToString(char *buf, double doubleValue)
{
int len;
len = sprintf(buf, "%.12g", doubleValue);
/* Add a final ".0" if it's a number. But not
* for NaN or InF */
while (*buf) {
if (*buf == '.' || isalpha(*buf)) {
/* inf -> Inf, nan -> Nan */
if (*buf == 'i' || *buf == 'n') {
*buf = toupper(*buf);
}
return len;
}
buf++;
}
*buf++ = '.';
*buf++ = '0';
*buf = '\0';
return len + 2;
}
int Jim_StringToDouble(const char *str, double *doublePtr)
{
char *endptr;
/* Callers can check for underflow via ERANGE */
errno = 0;
*doublePtr = strtod(str, &endptr);
return JimCheckConversion(str, endptr);
}
static jim_wide JimPowWide(jim_wide b, jim_wide e)
{
jim_wide i, res = 1;
if ((b == 0 && e != 0) || (e < 0))
return 0;
for (i = 0; i < e; i++) {
res *= b;
}
return res;
}
/* -----------------------------------------------------------------------------
* Special functions
* ---------------------------------------------------------------------------*/
/* Note that 'interp' may be NULL if not available in the
* context of the panic. It's only useful to get the error
* file descriptor, it will default to stderr otherwise. */
void Jim_Panic(Jim_Interp *interp, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
/*
* Send it here first.. Assuming STDIO still works
*/
fprintf(stderr, JIM_NL "JIM INTERPRETER PANIC: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, JIM_NL JIM_NL);
va_end(ap);
#ifdef HAVE_BACKTRACE
{
void *array[40];
int size, i;
char **strings;
size = backtrace(array, 40);
strings = backtrace_symbols(array, size);
for (i = 0; i < size; i++)
fprintf(stderr, "[backtrace] %s" JIM_NL, strings[i]);
fprintf(stderr, "[backtrace] Include the above lines and the output" JIM_NL);
fprintf(stderr, "[backtrace] of 'nm <executable>' in the bug report." JIM_NL);
}
#endif
abort();
}
/* -----------------------------------------------------------------------------
* Memory allocation
* ---------------------------------------------------------------------------*/
void *Jim_Alloc(int size)
{
return malloc(size);
}
void Jim_Free(void *ptr)
{
free(ptr);
}
void *Jim_Realloc(void *ptr, int size)
{
return realloc(ptr, size);
}
char *Jim_StrDup(const char *s)
{
return strdup(s);
}
char *Jim_StrDupLen(const char *s, int l)
{
char *copy = Jim_Alloc(l + 1);
memcpy(copy, s, l + 1);
copy[l] = 0; /* Just to be sure, original could be substring */
return copy;
}
/* -----------------------------------------------------------------------------
* Time related functions
* ---------------------------------------------------------------------------*/
/* Returns microseconds of CPU used since start. */
static jim_wide JimClock(void)
{
struct timeval tv;
gettimeofday(&tv, NULL);
return (jim_wide) tv.tv_sec * 1000000 + tv.tv_usec;
}
/* -----------------------------------------------------------------------------
* Hash Tables
* ---------------------------------------------------------------------------*/
/* -------------------------- private prototypes ---------------------------- */
static int JimExpandHashTableIfNeeded(Jim_HashTable *ht);
static unsigned int JimHashTableNextPower(unsigned int size);
static int JimInsertHashEntry(Jim_HashTable *ht, const void *key);
/* -------------------------- hash functions -------------------------------- */
/* Thomas Wang's 32 bit Mix Function */
unsigned int Jim_IntHashFunction(unsigned int key)
{
key += ~(key << 15);
key ^= (key >> 10);
key += (key << 3);
key ^= (key >> 6);
key += ~(key << 11);
key ^= (key >> 16);
return key;
}
/* Generic hash function (we are using to multiply by 9 and add the byte
* as Tcl) */
unsigned int Jim_GenHashFunction(const unsigned char *buf, int len)
{
unsigned int h = 0;
while (len--)
h += (h << 3) + *buf++;
return h;
}
/* ----------------------------- API implementation ------------------------- */
/* reset a hashtable already initialized with ht_init().
* NOTE: This function should only called by ht_destroy(). */
static void JimResetHashTable(Jim_HashTable *ht)
{
ht->table = NULL;
ht->size = 0;
ht->sizemask = 0;
ht->used = 0;
ht->collisions = 0;
}
/* Initialize the hash table */
int Jim_InitHashTable(Jim_HashTable *ht, const Jim_HashTableType *type, void *privDataPtr)
{
JimResetHashTable(ht);
ht->type = type;
ht->privdata = privDataPtr;
return JIM_OK;
}
/* Resize the table to the minimal size that contains all the elements,
* but with the invariant of a USER/BUCKETS ration near to <= 1 */
int Jim_ResizeHashTable(Jim_HashTable *ht)
{
int minimal = ht->used;
if (minimal < JIM_HT_INITIAL_SIZE)
minimal = JIM_HT_INITIAL_SIZE;
return Jim_ExpandHashTable(ht, minimal);
}
/* Expand or create the hashtable */
int Jim_ExpandHashTable(Jim_HashTable *ht, unsigned int size)
{
Jim_HashTable n; /* the new hashtable */
unsigned int realsize = JimHashTableNextPower(size), i;
/* the size is invalid if it is smaller than the number of
* elements already inside the hashtable */
if (ht->used >= size)
return JIM_ERR;
Jim_InitHashTable(&n, ht->type, ht->privdata);
n.size = realsize;
n.sizemask = realsize - 1;
n.table = Jim_Alloc(realsize * sizeof(Jim_HashEntry *));
/* Initialize all the pointers to NULL */
memset(n.table, 0, realsize * sizeof(Jim_HashEntry *));
/* Copy all the elements from the old to the new table:
* note that if the old hash table is empty ht->size is zero,
* so Jim_ExpandHashTable just creates an hash table. */
n.used = ht->used;
for (i = 0; i < ht->size && ht->used > 0; i++) {
Jim_HashEntry *he, *nextHe;
if (ht->table[i] == NULL)
continue;
/* For each hash entry on this slot... */
he = ht->table[i];
while (he) {
unsigned int h;
nextHe = he->next;
/* Get the new element index */
h = Jim_HashKey(ht, he->key) & n.sizemask;
he->next = n.table[h];
n.table[h] = he;
ht->used--;
/* Pass to the next element */
he = nextHe;
}
}
assert(ht->used == 0);
Jim_Free(ht->table);
/* Remap the new hashtable in the old */
*ht = n;
return JIM_OK;
}
/* Add an element to the target hash table */
int Jim_AddHashEntry(Jim_HashTable *ht, const void *key, void *val)
{
int index;
Jim_HashEntry *entry;
/* Get the index of the new element, or -1 if
* the element already exists. */
if ((index = JimInsertHashEntry(ht, key)) == -1)
return JIM_ERR;
/* Allocates the memory and stores key */
entry = Jim_Alloc(sizeof(*entry));
entry->next = ht->table[index];
ht->table[index] = entry;
/* Set the hash entry fields. */
Jim_SetHashKey(ht, entry, key);
Jim_SetHashVal(ht, entry, val);
ht->used++;
return JIM_OK;
}
/* Add an element, discarding the old if the key already exists */
int Jim_ReplaceHashEntry(Jim_HashTable *ht, const void *key, void *val)
{
Jim_HashEntry *entry;
/* Try to add the element. If the key
* does not exists Jim_AddHashEntry will suceed. */
if (Jim_AddHashEntry(ht, key, val) == JIM_OK)
return JIM_OK;
/* It already exists, get the entry */
entry = Jim_FindHashEntry(ht, key);
/* Free the old value and set the new one */
Jim_FreeEntryVal(ht, entry);
Jim_SetHashVal(ht, entry, val);
return JIM_OK;
}
/* Search and remove an element */
int Jim_DeleteHashEntry(Jim_HashTable *ht, const void *key)
{
unsigned int h;
Jim_HashEntry *he, *prevHe;
if (ht->size == 0)
return JIM_ERR;
h = Jim_HashKey(ht, key) & ht->sizemask;
he = ht->table[h];
prevHe = NULL;
while (he) {
if (Jim_CompareHashKeys(ht, key, he->key)) {
/* Unlink the element from the list */
if (prevHe)
prevHe->next = he->next;
else
ht->table[h] = he->next;
Jim_FreeEntryKey(ht, he);
Jim_FreeEntryVal(ht, he);
Jim_Free(he);
ht->used--;
return JIM_OK;
}
prevHe = he;
he = he->next;
}
return JIM_ERR; /* not found */
}
/* Destroy an entire hash table */
int Jim_FreeHashTable(Jim_HashTable *ht)
{
unsigned int i;
/* Free all the elements */
for (i = 0; i < ht->size && ht->used > 0; i++) {
Jim_HashEntry *he, *nextHe;
if ((he = ht->table[i]) == NULL)
continue;
while (he) {
nextHe = he->next;
Jim_FreeEntryKey(ht, he);
Jim_FreeEntryVal(ht, he);
Jim_Free(he);
ht->used--;
he = nextHe;
}
}
/* Free the table and the allocated cache structure */
Jim_Free(ht->table);
/* Re-initialize the table */
JimResetHashTable(ht);
return JIM_OK; /* never fails */
}
Jim_HashEntry *Jim_FindHashEntry(Jim_HashTable *ht, const void *key)
{
Jim_HashEntry *he;
unsigned int h;
if (ht->size == 0)
return NULL;
h = Jim_HashKey(ht, key) & ht->sizemask;
he = ht->table[h];
while (he) {
if (Jim_CompareHashKeys(ht, key, he->key))
return he;
he = he->next;
}
return NULL;
}
Jim_HashTableIterator *Jim_GetHashTableIterator(Jim_HashTable *ht)
{
Jim_HashTableIterator *iter = Jim_Alloc(sizeof(*iter));
iter->ht = ht;
iter->index = -1;
iter->entry = NULL;
iter->nextEntry = NULL;
return iter;
}
Jim_HashEntry *Jim_NextHashEntry(Jim_HashTableIterator *iter)
{
while (1) {
if (iter->entry == NULL) {
iter->index++;
if (iter->index >= (signed)iter->ht->size)
break;
iter->entry = iter->ht->table[iter->index];
}
else {
iter->entry = iter->nextEntry;
}
if (iter->entry) {
/* We need to save the 'next' here, the iterator user
* may delete the entry we are returning. */
iter->nextEntry = iter->entry->next;
return iter->entry;
}
}
return NULL;
}
/* ------------------------- private functions ------------------------------ */
/* Expand the hash table if needed */
static int JimExpandHashTableIfNeeded(Jim_HashTable *ht)
{
/* If the hash table is empty expand it to the intial size,
* if the table is "full" dobule its size. */
if (ht->size == 0)
return Jim_ExpandHashTable(ht, JIM_HT_INITIAL_SIZE);
if (ht->size == ht->used)
return Jim_ExpandHashTable(ht, ht->size * 2);
return JIM_OK;
}
/* Our hash table capability is a power of two */
static unsigned int JimHashTableNextPower(unsigned int size)
{
unsigned int i = JIM_HT_INITIAL_SIZE;
if (size >= 2147483648U)
return 2147483648U;
while (1) {
if (i >= size)
return i;
i *= 2;
}
}
/* Returns the index of a free slot that can be populated with
* an hash entry for the given 'key'.
* If the key already exists, -1 is returned. */
static int JimInsertHashEntry(Jim_HashTable *ht, const void *key)
{
unsigned int h;
Jim_HashEntry *he;
/* Expand the hashtable if needed */
if (JimExpandHashTableIfNeeded(ht) == JIM_ERR)
return -1;
/* Compute the key hash value */
h = Jim_HashKey(ht, key) & ht->sizemask;
/* Search if this slot does not already contain the given key */
he = ht->table[h];
while (he) {
if (Jim_CompareHashKeys(ht, key, he->key))
return -1;
he = he->next;
}
return h;
}
/* ----------------------- StringCopy Hash Table Type ------------------------*/
static unsigned int JimStringCopyHTHashFunction(const void *key)
{
return Jim_GenHashFunction(key, strlen(key));
}
static const void *JimStringCopyHTKeyDup(void *privdata, const void *key)
{
int len = strlen(key);
char *copy = Jim_Alloc(len + 1);
JIM_NOTUSED(privdata);
memcpy(copy, key, len);
copy[len] = '\0';
return copy;
}
static void *JimStringKeyValCopyHTValDup(void *privdata, const void *val)
{
int len = strlen(val);
char *copy = Jim_Alloc(len + 1);
JIM_NOTUSED(privdata);
memcpy(copy, val, len);
copy[len] = '\0';
return copy;
}
static int JimStringCopyHTKeyCompare(void *privdata, const void *key1, const void *key2)
{
JIM_NOTUSED(privdata);
return strcmp(key1, key2) == 0;
}
static void JimStringCopyHTKeyDestructor(void *privdata, const void *key)
{
JIM_NOTUSED(privdata);
Jim_Free((void *)key); /* ATTENTION: const cast */
}
static void JimStringKeyValCopyHTValDestructor(void *privdata, void *val)
{
JIM_NOTUSED(privdata);
Jim_Free((void *)val); /* ATTENTION: const cast */
}
#if 0
static Jim_HashTableType JimStringCopyHashTableType = {
JimStringCopyHTHashFunction, /* hash function */
JimStringCopyHTKeyDup, /* key dup */
NULL, /* val dup */
JimStringCopyHTKeyCompare, /* key compare */
JimStringCopyHTKeyDestructor, /* key destructor */
NULL /* val destructor */
};
#endif
/* This is like StringCopy but does not auto-duplicate the key.
* It's used for intepreter's shared strings. */
static const Jim_HashTableType JimSharedStringsHashTableType = {
JimStringCopyHTHashFunction, /* hash function */
NULL, /* key dup */
NULL, /* val dup */
JimStringCopyHTKeyCompare, /* key compare */
JimStringCopyHTKeyDestructor, /* key destructor */
NULL /* val destructor */
};
/* This is like StringCopy but also automatically handle dynamic
* allocated C strings as values. */
static const Jim_HashTableType JimStringKeyValCopyHashTableType = {
JimStringCopyHTHashFunction, /* hash function */
JimStringCopyHTKeyDup, /* key dup */
JimStringKeyValCopyHTValDup, /* val dup */
JimStringCopyHTKeyCompare, /* key compare */
JimStringCopyHTKeyDestructor, /* key destructor */
JimStringKeyValCopyHTValDestructor, /* val destructor */
};
typedef struct AssocDataValue
{
Jim_InterpDeleteProc *delProc;
void *data;
} AssocDataValue;
static void JimAssocDataHashTableValueDestructor(void *privdata, void *data)
{
AssocDataValue *assocPtr = (AssocDataValue *) data;
if (assocPtr->delProc != NULL)
assocPtr->delProc((Jim_Interp *)privdata, assocPtr->data);
Jim_Free(data);
}
static const Jim_HashTableType JimAssocDataHashTableType = {
JimStringCopyHTHashFunction, /* hash function */
JimStringCopyHTKeyDup, /* key dup */
NULL, /* val dup */
JimStringCopyHTKeyCompare, /* key compare */
JimStringCopyHTKeyDestructor, /* key destructor */
JimAssocDataHashTableValueDestructor /* val destructor */
};
/* -----------------------------------------------------------------------------
* Stack - This is a simple generic stack implementation. It is used for
* example in the 'expr' expression compiler.
* ---------------------------------------------------------------------------*/
void Jim_InitStack(Jim_Stack *stack)
{
stack->len = 0;
stack->maxlen = 0;
stack->vector = NULL;
}
void Jim_FreeStack(Jim_Stack *stack)
{
Jim_Free(stack->vector);
}
int Jim_StackLen(Jim_Stack *stack)
{
return stack->len;
}
void Jim_StackPush(Jim_Stack *stack, void *element)
{
int neededLen = stack->len + 1;
if (neededLen > stack->maxlen) {
stack->maxlen = neededLen < 20 ? 20 : neededLen * 2;
stack->vector = Jim_Realloc(stack->vector, sizeof(void *) * stack->maxlen);
}
stack->vector[stack->len] = element;
stack->len++;
}
void *Jim_StackPop(Jim_Stack *stack)
{
if (stack->len == 0)
return NULL;
stack->len--;
return stack->vector[stack->len];
}
void *Jim_StackPeek(Jim_Stack *stack)
{
if (stack->len == 0)
return NULL;
return stack->vector[stack->len - 1];
}
void Jim_FreeStackElements(Jim_Stack *stack, void (*freeFunc) (void *ptr))
{
int i;
for (i = 0; i < stack->len; i++)
freeFunc(stack->vector[i]);
}
/* -----------------------------------------------------------------------------
* Parser
* ---------------------------------------------------------------------------*/
/* Token types */
#define JIM_TT_NONE 0 /* No token returned */
#define JIM_TT_STR 1 /* simple string */
#define JIM_TT_ESC 2 /* string that needs escape chars conversion */
#define JIM_TT_VAR 3 /* var substitution */
#define JIM_TT_DICTSUGAR 4 /* Syntax sugar for [dict get], $foo(bar) */
#define JIM_TT_CMD 5 /* command substitution */
/* Note: Keep these three together for TOKEN_IS_SEP() */
#define JIM_TT_SEP 6 /* word separator. arg is # of tokens. -ve if {*} */
#define JIM_TT_EOL 7 /* line separator */
#define JIM_TT_EOF 8 /* end of script */
#define JIM_TT_LINE 9 /* special 'start-of-line' token. arg is # of arguments to the command. -ve if {*} */
#define JIM_TT_WORD 10 /* special 'start-of-word' token. arg is # of tokens to combine. -ve if {*} */
/* Additional token types needed for expressions */
#define JIM_TT_SUBEXPR_START 11
#define JIM_TT_SUBEXPR_END 12
#define JIM_TT_EXPR_INT 13
#define JIM_TT_EXPR_DOUBLE 14
/* Operator token types start here */
#define JIM_TT_EXPR_OP 15
#define TOKEN_IS_SEP(type) (type >= JIM_TT_SEP && type <= JIM_TT_EOF)
/* Parser states */
#define JIM_PS_DEF 0 /* Default state */
#define JIM_PS_QUOTE 1 /* Inside "" */
#define JIM_PS_DICTSUGAR 2 /* Tokenising abc(def) into 4 separate tokens */
/* Parser context structure. The same context is used both to parse
* Tcl scripts and lists. */
struct JimParserCtx
{
const char *prg; /* Program text */
const char *p; /* Pointer to the point of the program we are parsing */
int len; /* Left length of 'prg' */
int linenr; /* Current line number */
const char *tstart;
const char *tend; /* Returned token is at tstart-tend in 'prg'. */
int tline; /* Line number of the returned token */
int tt; /* Token type */
int eof; /* Non zero if EOF condition is true. */
int state; /* Parser state */
int comment; /* Non zero if the next chars may be a comment. */
char missing; /* At end of parse, ' ' if complete, '{' if braces incomplete, '"' if quotes incomplete */
};
#define JimParserEof(c) ((c)->eof)
#define JimParserTstart(c) ((c)->tstart)
#define JimParserTend(c) ((c)->tend)
#define JimParserTtype(c) ((c)->tt)
#define JimParserTline(c) ((c)->tline)
static int JimParseScript(struct JimParserCtx *pc);
static int JimParseSep(struct JimParserCtx *pc);
static int JimParseEol(struct JimParserCtx *pc);
static int JimParseCmd(struct JimParserCtx *pc);
static int JimParseVar(struct JimParserCtx *pc);
static int JimParseBrace(struct JimParserCtx *pc);
static int JimParseStr(struct JimParserCtx *pc);
static int JimParseComment(struct JimParserCtx *pc);
static Jim_Obj *JimParserGetTokenObj(Jim_Interp *interp, struct JimParserCtx *pc);
/* Initialize a parser context.
* 'prg' is a pointer to the program text, linenr is the line
* number of the first line contained in the program. */
static void JimParserInit(struct JimParserCtx *pc, const char *prg, int len, int linenr)
{
pc->prg = prg;
pc->p = prg;
pc->len = len;
pc->tstart = NULL;
pc->tend = NULL;
pc->tline = 0;
pc->tt = JIM_TT_NONE;
pc->eof = 0;
pc->state = JIM_PS_DEF;
pc->linenr = linenr;
pc->comment = 1;
pc->missing = ' ';
}
static int JimParseScript(struct JimParserCtx *pc)
{
while (1) { /* the while is used to reiterate with continue if needed */
if (!pc->len) {
pc->tstart = pc->p;
pc->tend = pc->p - 1;
pc->tline = pc->linenr;
pc->tt = JIM_TT_EOL;
pc->eof = 1;
return JIM_OK;
}
switch (*(pc->p)) {
case '\\':
if (*(pc->p + 1) == '\n')
return JimParseSep(pc);
else {
pc->comment = 0;
return JimParseStr(pc);
}
break;
case ' ':
case '\t':
case '\r':
if (pc->state == JIM_PS_DEF)
return JimParseSep(pc);
else {
pc->comment = 0;
return JimParseStr(pc);
}
break;
case '\n':
case ';':
pc->comment = 1;
if (pc->state == JIM_PS_DEF)
return JimParseEol(pc);
else
return JimParseStr(pc);
break;
case '[':
pc->comment = 0;
return JimParseCmd(pc);
break;
case '$':
pc->comment = 0;
if (JimParseVar(pc) == JIM_ERR) {
pc->tstart = pc->tend = pc->p++;
pc->len--;
pc->tline = pc->linenr;
pc->tt = JIM_TT_STR;
return JIM_OK;
}
else
return JIM_OK;
break;
case '#':
if (pc->comment) {
JimParseComment(pc);
continue;
}
else {
return JimParseStr(pc);
}
default:
pc->comment = 0;
return JimParseStr(pc);
break;
}
return JIM_OK;
}
}
static int JimParseSep(struct JimParserCtx *pc)
{
pc->tstart = pc->p;
pc->tline = pc->linenr;
while (*pc->p == ' ' || *pc->p == '\t' || *pc->p == '\r' ||
(*pc->p == '\\' && *(pc->p + 1) == '\n')) {
if (*pc->p == '\\') {
pc->p++;
pc->len--;
pc->linenr++;
}
pc->p++;
pc->len--;
}
pc->tend = pc->p - 1;
pc->tt = JIM_TT_SEP;
return JIM_OK;
}
static int JimParseEol(struct JimParserCtx *pc)
{
pc->tstart = pc->p;
pc->tline = pc->linenr;
while (*pc->p == ' ' || *pc->p == '\n' || *pc->p == '\t' || *pc->p == '\r' || *pc->p == ';') {
if (*pc->p == '\n')
pc->linenr++;
pc->p++;
pc->len--;
}
pc->tend = pc->p - 1;
pc->tt = JIM_TT_EOL;
return JIM_OK;
}
/* Todo. Don't stop if ']' appears inside {} or quoted.
* Also should handle the case of puts [string length "]"] */
static int JimParseCmd(struct JimParserCtx *pc)
{
int level = 1;
int blevel = 0;
pc->tstart = ++pc->p;
pc->len--;
pc->tline = pc->linenr;
while (1) {
if (pc->len == 0) {
break;
}
else if (*pc->p == '[' && blevel == 0) {
level++;
}
else if (*pc->p == ']' && blevel == 0) {
level--;
if (!level)
break;
}
else if (*pc->p == '\\') {
pc->p++;
pc->len--;
if (*pc->p == '\n')
pc->linenr++;
}
else if (*pc->p == '{') {
blevel++;
}
else if (*pc->p == '}') {
if (blevel != 0)
blevel--;
}
else if (*pc->p == '\n')
pc->linenr++;
pc->p++;
pc->len--;
}
pc->tend = pc->p - 1;
pc->tt = JIM_TT_CMD;
if (*pc->p == ']') {
pc->p++;
pc->len--;
}
return JIM_OK;
}
static int JimParseVar(struct JimParserCtx *pc)
{
int brace = 0, stop = 0;
int ttype = JIM_TT_VAR;
pc->tstart = ++pc->p;
pc->len--; /* skip the $ */
pc->tline = pc->linenr;
if (*pc->p == '{') {
pc->tstart = ++pc->p;
pc->len--;
brace = 1;
}
if (brace) {
while (!stop) {
if (*pc->p == '}' || pc->len == 0) {
pc->tend = pc->p - 1;
stop = 1;
if (pc->len == 0)
break;
}
else if (*pc->p == '\n')
pc->linenr++;
pc->p++;
pc->len--;
}
}
else {
/* Include leading colons */
|