Permalink
Fetching contributors…
Cannot retrieve contributors at this time
15603 lines (13800 sloc) 474 KB
/* 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
*
* 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 */
#ifndef _GNU_SOURCE
#define _GNU_SOURCE /* Mostly just for environ */
#endif
#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 "jim.h"
#include "jimautoconf.h"
#include "utf8.h"
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_BACKTRACE
#include <execinfo.h>
#endif
#ifdef HAVE_CRT_EXTERNS_H
#include <crt_externs.h>
#endif
/* For INFINITY, even if math functions are not enabled */
#include <math.h>
/* We may decide to switch to using $[...] after all, so leave it as an option */
/*#define EXPRSUGAR_BRACKET*/
/* For the no-autoconf case */
#ifndef TCL_LIBRARY
#define TCL_LIBRARY "."
#endif
#ifndef TCL_PLATFORM_OS
#define TCL_PLATFORM_OS "unknown"
#endif
#ifndef TCL_PLATFORM_PLATFORM
#define TCL_PLATFORM_PLATFORM "unknown"
#endif
#ifndef TCL_PLATFORM_PATH_SEPARATOR
#define TCL_PLATFORM_PATH_SEPARATOR ":"
#endif
/*#define DEBUG_SHOW_SCRIPT*/
/*#define DEBUG_SHOW_SCRIPT_TOKENS*/
/*#define DEBUG_SHOW_SUBST*/
/*#define DEBUG_SHOW_EXPR*/
/*#define DEBUG_SHOW_EXPR_TOKENS*/
/*#define JIM_DEBUG_GC*/
#ifdef JIM_MAINTAINER
#define JIM_DEBUG_COMMAND
#define JIM_DEBUG_PANIC
#endif
/* Enable this (in conjunction with valgrind) to help debug
* reference counting issues
*/
/*#define JIM_DISABLE_OBJECT_POOL*/
/* Maximum size of an integer */
#define JIM_INTEGER_SPACE 24
const char *jim_tt_name(int type);
#ifdef JIM_DEBUG_PANIC
static void JimPanicDump(int fail_condition, const char *fmt, ...);
#define JimPanic(X) JimPanicDump X
#else
#define JimPanic(X)
#endif
#ifdef JIM_OPTIMIZATION
#define JIM_IF_OPTIM(X) X
#else
#define JIM_IF_OPTIM(X)
#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 JimFreeCallFrame(Jim_Interp *interp, Jim_CallFrame *cf, int action);
static int ListSetIndex(Jim_Interp *interp, Jim_Obj *listPtr, int listindex, Jim_Obj *newObjPtr,
int flags);
static int JimDeleteLocalProcs(Jim_Interp *interp, Jim_Stack *localCommands);
static Jim_Obj *JimExpandDictSugar(Jim_Interp *interp, Jim_Obj *objPtr);
static void SetDictSubstFromAny(Jim_Interp *interp, Jim_Obj *objPtr);
static Jim_Obj **JimDictPairs(Jim_Obj *dictPtr, int *len);
static void JimSetFailedEnumResult(Jim_Interp *interp, const char *arg, const char *badtype,
const char *prefix, const char *const *tablePtr, const char *name);
static int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, int argc, Jim_Obj *const *argv);
static int JimGetWideNoErr(Jim_Interp *interp, Jim_Obj *objPtr, jim_wide * widePtr);
static int JimSign(jim_wide w);
static int JimValidName(Jim_Interp *interp, const char *type, Jim_Obj *nameObjPtr);
static void JimPrngSeed(Jim_Interp *interp, unsigned char *seed, int seedLen);
static void JimRandomBytes(Jim_Interp *interp, void *dest, unsigned int len);
/* Fast access to the int (wide) value of an object which is known to be of int type */
#define JimWideValue(objPtr) (objPtr)->internalRep.wideValue
#define JimObjTypeName(O) ((O)->typePtr ? (O)->typePtr->name : "none")
static int utf8_tounicode_case(const char *s, int *uc, int upper)
{
int l = utf8_tounicode(s, uc);
if (upper) {
*uc = utf8_upper(*uc);
}
return l;
}
/* These can be used in addition to JIM_CASESENS/JIM_NOCASE */
#define JIM_CHARSET_SCAN 2
#define JIM_CHARSET_GLOB 0
/**
* pattern points to a string like "[^a-z\ub5]"
*
* The pattern may contain trailing chars, which are ignored.
*
* The pattern is matched against unicode char 'c'.
*
* If (flags & JIM_NOCASE), case is ignored when matching.
* If (flags & JIM_CHARSET_SCAN), the considers ^ and ] special at the start
* of the charset, per scan, rather than glob/string match.
*
* If the unicode char 'c' matches that set, returns a pointer to the ']' character,
* or the null character if the ']' is missing.
*
* Returns NULL on no match.
*/
static const char *JimCharsetMatch(const char *pattern, int c, int flags)
{
int not = 0;
int pchar;
int match = 0;
int nocase = 0;
if (flags & JIM_NOCASE) {
nocase++;
c = utf8_upper(c);
}
if (flags & JIM_CHARSET_SCAN) {
if (*pattern == '^') {
not++;
pattern++;
}
/* Special case. If the first char is ']', it is part of the set */
if (*pattern == ']') {
goto first;
}
}
while (*pattern && *pattern != ']') {
/* Exact match */
if (pattern[0] == '\\') {
first:
pattern += utf8_tounicode_case(pattern, &pchar, nocase);
}
else {
/* Is this a range? a-z */
int start;
int end;
pattern += utf8_tounicode_case(pattern, &start, nocase);
if (pattern[0] == '-' && pattern[1]) {
/* skip '-' */
pattern++;
pattern += utf8_tounicode_case(pattern, &end, nocase);
/* Handle reversed range too */
if ((c >= start && c <= end) || (c >= end && c <= start)) {
match = 1;
}
continue;
}
pchar = start;
}
if (pchar == c) {
match = 1;
}
}
if (not) {
match = !match;
}
return match ? pattern : NULL;
}
/* Glob-style pattern matching. */
/* Note: string *must* be valid UTF-8 sequences
*/
static int JimGlobMatch(const char *pattern, const char *string, int nocase)
{
int c;
int pchar;
while (*pattern) {
switch (pattern[0]) {
case '*':
while (pattern[1] == '*') {
pattern++;
}
pattern++;
if (!pattern[0]) {
return 1; /* match */
}
while (*string) {
/* Recursive call - Does the remaining pattern match anywhere? */
if (JimGlobMatch(pattern, string, nocase))
return 1; /* match */
string += utf8_tounicode(string, &c);
}
return 0; /* no match */
case '?':
string += utf8_tounicode(string, &c);
break;
case '[': {
string += utf8_tounicode(string, &c);
pattern = JimCharsetMatch(pattern + 1, c, nocase ? JIM_NOCASE : 0);
if (!pattern) {
return 0;
}
if (!*pattern) {
/* Ran out of pattern (no ']') */
continue;
}
break;
}
case '\\':
if (pattern[1]) {
pattern++;
}
/* fall through */
default:
string += utf8_tounicode_case(string, &c, nocase);
utf8_tounicode_case(pattern, &pchar, nocase);
if (pchar != c) {
return 0;
}
break;
}
pattern += utf8_tounicode_case(pattern, &pchar, nocase);
if (!*string) {
while (*pattern == '*') {
pattern++;
}
break;
}
}
if (!*pattern && !*string) {
return 1;
}
return 0;
}
/**
* string comparison. Works on binary data.
*
* Returns -1, 0 or 1
*
* Note that the lengths are byte lengths, not char lengths.
*/
static int JimStringCompare(const char *s1, int l1, const char *s2, int l2)
{
if (l1 < l2) {
return memcmp(s1, s2, l1) <= 0 ? -1 : 1;
}
else if (l2 < l1) {
return memcmp(s1, s2, l2) >= 0 ? 1 : -1;
}
else {
return JimSign(memcmp(s1, s2, l1));
}
}
/**
* Compare null terminated strings, up to a maximum of 'maxchars' characters,
* (or end of string if 'maxchars' is -1).
*
* Returns -1, 0, 1 for s1 < s2, s1 == s2, s1 > s2 respectively.
*
* Note: does not support embedded nulls.
*/
static int JimStringCompareLen(const char *s1, const char *s2, int maxchars, int nocase)
{
while (*s1 && *s2 && maxchars) {
int c1, c2;
s1 += utf8_tounicode_case(s1, &c1, nocase);
s2 += utf8_tounicode_case(s2, &c2, nocase);
if (c1 != c2) {
return JimSign(c1 - c2);
}
maxchars--;
}
if (!maxchars) {
return 0;
}
/* One string or both terminated */
if (*s1) {
return 1;
}
if (*s2) {
return -1;
}
return 0;
}
/* Search for '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.
*
* Note: Lengths and return value are in bytes, not chars.
*/
static int JimStringFirst(const char *s1, int l1, const char *s2, int l2, int idx)
{
int i;
int l1bytelen;
if (!l1 || !l2 || l1 > l2) {
return -1;
}
if (idx < 0)
idx = 0;
s2 += utf8_index(s2, idx);
l1bytelen = utf8_index(s1, l1);
for (i = idx; i <= l2 - l1; i++) {
int c;
if (memcmp(s2, s1, l1bytelen) == 0) {
return i;
}
s2 += utf8_tounicode(s2, &c);
}
return -1;
}
/* Search for the last occurrence 's1' inside 's2', starting to search from char 'index' of 's2'.
* The index of the last occurrence of s1 in s2 is returned.
* If s1 is not found inside s2, -1 is returned.
*
* Note: Lengths and return value are in bytes, not chars.
*/
static 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;
}
#ifdef JIM_UTF8
/**
* Per JimStringLast but lengths and return value are in chars, not bytes.
*/
static int JimStringLastUtf8(const char *s1, int l1, const char *s2, int l2)
{
int n = JimStringLast(s1, utf8_index(s1, l1), s2, utf8_index(s2, l2));
if (n > 0) {
n = utf8_strlen(s2, n);
}
return n;
}
#endif
/**
* 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(UCHAR(*endptr))) {
return JIM_ERR;
}
endptr++;
}
}
return JIM_OK;
}
/* Parses the front of a number to determine its sign and base.
* Returns the index to start parsing according to the given base
*/
static int JimNumberBase(const char *str, int *base, int *sign)
{
int i = 0;
*base = 10;
while (isspace(UCHAR(str[i]))) {
i++;
}
if (str[i] == '-') {
*sign = -1;
i++;
}
else {
if (str[i] == '+') {
i++;
}
*sign = 1;
}
if (str[i] != '0') {
/* base 10 */
return 0;
}
/* We have 0<x>, so see if we can convert it */
switch (str[i + 1]) {
case 'x': case 'X': *base = 16; break;
case 'o': case 'O': *base = 8; break;
case 'b': case 'B': *base = 2; break;
default: return 0;
}
i += 2;
/* Ensure that (e.g.) 0x-5 fails to parse */
if (str[i] != '-' && str[i] != '+' && !isspace(UCHAR(str[i]))) {
/* Parse according to this base */
return i;
}
/* Parse as base 10 */
*base = 10;
return 0;
}
/* Converts a number as per strtol(..., 0) except leading zeros do *not*
* imply octal. Instead, decimal is assumed unless the number begins with 0x, 0o or 0b
*/
static long jim_strtol(const char *str, char **endptr)
{
int sign;
int base;
int i = JimNumberBase(str, &base, &sign);
if (base != 10) {
long value = strtol(str + i, endptr, base);
if (endptr == NULL || *endptr != str + i) {
return value * sign;
}
}
/* Can just do a regular base-10 conversion */
return strtol(str, endptr, 10);
}
/* Converts a number as per strtoull(..., 0) except leading zeros do *not*
* imply octal. Instead, decimal is assumed unless the number begins with 0x, 0o or 0b
*/
static jim_wide jim_strtoull(const char *str, char **endptr)
{
#ifdef HAVE_LONG_LONG
int sign;
int base;
int i = JimNumberBase(str, &base, &sign);
if (base != 10) {
jim_wide value = strtoull(str + i, endptr, base);
if (endptr == NULL || *endptr != str + i) {
return value * sign;
}
}
/* Can just do a regular base-10 conversion */
return strtoull(str, endptr, 10);
#else
return (unsigned long)jim_strtol(str, endptr);
#endif
}
int Jim_StringToWide(const char *str, jim_wide * widePtr, int base)
{
char *endptr;
if (base) {
*widePtr = strtoull(str, &endptr, base);
}
else {
*widePtr = jim_strtoull(str, &endptr);
}
return JimCheckConversion(str, endptr);
}
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 res = 1;
/* Special cases */
if (b == 1) {
/* 1 ^ any = 1 */
return 1;
}
if (e < 0) {
if (b != -1) {
return 0;
}
/* Only special case is -1 ^ -n
* -1^-1 = -1
* -1^-2 = 1
* i.e. same as +ve n
*/
e = -e;
}
while (e)
{
if (e & 1) {
res *= b;
}
e >>= 1;
b *= b;
}
return res;
}
/* -----------------------------------------------------------------------------
* Special functions
* ---------------------------------------------------------------------------*/
#ifdef JIM_DEBUG_PANIC
static void JimPanicDump(int condition, const char *fmt, ...)
{
va_list ap;
if (!condition) {
return;
}
va_start(ap, fmt);
fprintf(stderr, "\nJIM INTERPRETER PANIC: ");
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n\n");
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\n", strings[i]);
fprintf(stderr, "[backtrace] Include the above lines and the output\n");
fprintf(stderr, "[backtrace] of 'nm <executable>' in the bug report.\n");
}
#endif
exit(1);
}
#endif
/* -----------------------------------------------------------------------------
* Memory allocation
* ---------------------------------------------------------------------------*/
void *Jim_Alloc(int size)
{
return size ? malloc(size) : NULL;
}
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 current time in microseconds */
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 void JimExpandHashTableIfNeeded(Jim_HashTable *ht);
static unsigned int JimHashTableNextPower(unsigned int size);
static Jim_HashEntry *JimInsertHashEntry(Jim_HashTable *ht, const void *key, int replace);
/* -------------------------- 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 */
static void JimResetHashTable(Jim_HashTable *ht)
{
ht->table = NULL;
ht->size = 0;
ht->sizemask = 0;
ht->used = 0;
ht->collisions = 0;
#ifdef JIM_RANDOMISE_HASH
/* This is initialised to a random value to avoid a hash collision attack.
* See: n.runs-SA-2011.004
*/
ht->uniq = (rand() ^ time(NULL) ^ clock());
#else
ht->uniq = 0;
#endif
}
static void JimInitHashTableIterator(Jim_HashTable *ht, Jim_HashTableIterator *iter)
{
iter->ht = ht;
iter->index = -1;
iter->entry = NULL;
iter->nextEntry = NULL;
}
/* 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 */
void Jim_ResizeHashTable(Jim_HashTable *ht)
{
int minimal = ht->used;
if (minimal < JIM_HT_INITIAL_SIZE)
minimal = JIM_HT_INITIAL_SIZE;
Jim_ExpandHashTable(ht, minimal);
}
/* Expand or create the hashtable */
void 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 (size <= ht->used)
return;
Jim_InitHashTable(&n, ht->type, ht->privdata);
n.size = realsize;
n.sizemask = realsize - 1;
n.table = Jim_Alloc(realsize * sizeof(Jim_HashEntry *));
/* Keep the same 'uniq' as the original */
n.uniq = ht->uniq;
/* 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->used is zero,
* so Jim_ExpandHashTable just creates an empty hash table. */
n.used = ht->used;
for (i = 0; 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;
}
/* Add an element to the target hash table */
int Jim_AddHashEntry(Jim_HashTable *ht, const void *key, void *val)
{
Jim_HashEntry *entry;
/* Get the index of the new element, or -1 if
* the element already exists. */
entry = JimInsertHashEntry(ht, key, 0);
if (entry == NULL)
return JIM_ERR;
/* Set the hash entry fields. */
Jim_SetHashKey(ht, entry, key);
Jim_SetHashVal(ht, entry, val);
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)
{
int existed;
Jim_HashEntry *entry;
/* Get the index of the new element, or -1 if
* the element already exists. */
entry = JimInsertHashEntry(ht, key, 1);
if (entry->key) {
/* It already exists, so only replace the value.
* Note if both a destructor and a duplicate function exist,
* need to dup before destroy. perhaps they are the same
* reference counted object
*/
if (ht->type->valDestructor && ht->type->valDup) {
void *newval = ht->type->valDup(ht->privdata, val);
ht->type->valDestructor(ht->privdata, entry->u.val);
entry->u.val = newval;
}
else {
Jim_FreeEntryVal(ht, entry);
Jim_SetHashVal(ht, entry, val);
}
existed = 1;
}
else {
/* Doesn't exist, so set the key */
Jim_SetHashKey(ht, entry, key);
Jim_SetHashVal(ht, entry, val);
existed = 0;
}
return existed;
}
/* Search and remove an element */
int Jim_DeleteHashEntry(Jim_HashTable *ht, const void *key)
{
unsigned int h;
Jim_HashEntry *he, *prevHe;
if (ht->used == 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 and leave it ready for reuse */
int Jim_FreeHashTable(Jim_HashTable *ht)
{
unsigned int i;
/* Free all the elements */
for (i = 0; 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->used == 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));
JimInitHashTableIterator(ht, iter);
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 void JimExpandHashTableIfNeeded(Jim_HashTable *ht)
{
/* If the hash table is empty expand it to the intial size,
* if the table is "full" double its size. */
if (ht->size == 0)
Jim_ExpandHashTable(ht, JIM_HT_INITIAL_SIZE);
if (ht->size == ht->used)
Jim_ExpandHashTable(ht, ht->size * 2);
}
/* 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
* a hash entry for the given 'key'.
* If the key already exists, -1 is returned. */
static Jim_HashEntry *JimInsertHashEntry(Jim_HashTable *ht, const void *key, int replace)
{
unsigned int h;
Jim_HashEntry *he;
/* Expand the hashtable if needed */
JimExpandHashTableIfNeeded(ht);
/* 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 replace ? he : NULL;
he = he->next;
}
/* Allocates the memory and stores key */
he = Jim_Alloc(sizeof(*he));
he->next = ht->table[h];
ht->table[h] = he;
ht->used++;
he->key = NULL;
return he;
}
/* ----------------------- StringCopy Hash Table Type ------------------------*/
static unsigned int JimStringCopyHTHashFunction(const void *key)
{
return Jim_GenHashFunction(key, strlen(key));
}
static void *JimStringCopyHTDup(void *privdata, const void *key)
{
return Jim_StrDup(key);
}
static int JimStringCopyHTKeyCompare(void *privdata, const void *key1, const void *key2)
{
return strcmp(key1, key2) == 0;
}
static void JimStringCopyHTKeyDestructor(void *privdata, void *key)
{
Jim_Free(key);
}
static const Jim_HashTableType JimPackageHashTableType = {
JimStringCopyHTHashFunction, /* hash function */
JimStringCopyHTDup, /* key dup */
NULL, /* val dup */
JimStringCopyHTKeyCompare, /* key compare */
JimStringCopyHTKeyDestructor, /* key destructor */
NULL /* 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 */
JimStringCopyHTDup, /* 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]);
}
/* -----------------------------------------------------------------------------
* Tcl 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 (white space) */
#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_SUBEXPR_COMMA 13
#define JIM_TT_EXPR_INT 14
#define JIM_TT_EXPR_DOUBLE 15
#define JIM_TT_EXPR_BOOLEAN 16
#define JIM_TT_EXPRSUGAR 17 /* $(expression) */
/* Operator token types start here */
#define JIM_TT_EXPR_OP 20
#define TOKEN_IS_SEP(type) (type >= JIM_TT_SEP && type <= JIM_TT_EOF)
/* Can this token start an expression? */
#define TOKEN_IS_EXPR_START(type) (type == JIM_TT_NONE || type == JIM_TT_SUBEXPR_START || type == JIM_TT_SUBEXPR_COMMA)
/* Is this token an expression operator? */
#define TOKEN_IS_EXPR_OP(type) (type >= JIM_TT_EXPR_OP)
/**
* Results of missing quotes, braces, etc. from parsing.
*/
struct JimParseMissing {
int ch; /* At end of parse, ' ' if complete or '{', '[', '"', '\\', '}' if incomplete */
int line; /* Line number starting the missing token */
};
/* Parser context structure. The same context is used to parse
* Tcl scripts, expressions and lists. */
struct JimParserCtx
{
const char *p; /* Pointer to the point of the program we are parsing */
int len; /* Remaining length */
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 inquote; /* Parsing a quoted string */
int comment; /* Non zero if the next chars may be a comment. */
struct JimParseMissing missing; /* Details of any missing quotes, etc. */
};
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 JimParseQuote(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 void JimParseSubCmd(struct JimParserCtx *pc);
static int JimParseSubQuote(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->p = prg;
pc->len = len;
pc->tstart = NULL;
pc->tend = NULL;
pc->tline = 0;
pc->tt = JIM_TT_NONE;
pc->eof = 0;
pc->inquote = 0;
pc->linenr = linenr;
pc->comment = 1;
pc->missing.ch = ' ';
pc->missing.line = linenr;
}
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' && !pc->inquote) {
return JimParseSep(pc);
}
pc->comment = 0;
return JimParseStr(pc);
case ' ':
case '\t':
case '\r':
case '\f':
if (!pc->inquote)
return JimParseSep(pc);
pc->comment = 0;
return JimParseStr(pc);
case '\n':
case ';':
pc->comment = 1;
if (!pc->inquote)
return JimParseEol(pc);
return JimParseStr(pc);
case '[':
pc->comment = 0;
return JimParseCmd(pc);
case '$':
pc->comment = 0;
if (JimParseVar(pc) == JIM_ERR) {
/* An orphan $. Create as a separate token */
pc->tstart = pc->tend = pc->p++;
pc->len--;
pc->tt = JIM_TT_ESC;
}
return JIM_OK;
case '#':
if (pc->comment) {
JimParseComment(pc);
continue;
}
return JimParseStr(pc);
default:
pc->comment = 0;
return JimParseStr(pc);
}
return JIM_OK;
}
}
static int JimParseSep(struct JimParserCtx *pc)
{
pc->tstart = pc->p;
pc->tline = pc->linenr;
while (isspace(UCHAR(*pc->p)) || (*pc->p == '\\' && *(pc->p + 1) == '\n')) {
if (*pc->p == '\n') {
break;
}
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 (isspace(UCHAR(*pc->p)) || *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;
}
/*
** Here are the rules for parsing:
** {braced expression}
** - Count open and closing braces
** - Backslash escapes meaning of braces but doesn't remove the backslash
**
** "quoted expression"
** - Unescaped double quote terminates the expression
** - Backslash escapes next char
** - [commands brackets] are counted/nested
** - command rules apply within [brackets], not quoting rules (i.e. brackets have their own rules)
**
** [command expression]
** - Count open and closing brackets
** - Backslash escapes next char
** - [commands brackets] are counted/nested
** - "quoted expressions" are parsed according to quoting rules
** - {braced expressions} are parsed according to brace rules
**
** For everything, backslash escapes the next char, newline increments current line
*/
/**
* Parses a braced expression starting at pc->p.
*
* Positions the parser at the end of the braced expression,
* sets pc->tend and possibly pc->missing.
*/
static void JimParseSubBrace(struct JimParserCtx *pc)
{
int level = 1;
/* Skip the brace */
pc->p++;
pc->len--;
while (pc->len) {
switch (*pc->p) {
case '\\':
if (pc->len > 1) {
if (*++pc->p == '\n') {
pc->linenr++;
}
pc->len--;
}
break;
case '{':
level++;
break;
case '}':
if (--level == 0) {
pc->tend = pc->p - 1;
pc->p++;
pc->len--;
return;
}
break;
case '\n':
pc->linenr++;
break;
}
pc->p++;
pc->len--;
}
pc->missing.ch = '{';
pc->missing.line = pc->tline;
pc->tend = pc->p - 1;
}
/**
* Parses a quoted expression starting at pc->p.
*
* Positions the parser at the end of the quoted expression,
* sets pc->tend and possibly pc->missing.
*
* Returns the type of the token of the string,
* either JIM_TT_ESC (if it contains values which need to be [subst]ed)
* or JIM_TT_STR.
*/
static int JimParseSubQuote(struct JimParserCtx *pc)
{
int tt = JIM_TT_STR;
int line = pc->tline;
/* Skip the quote */
pc->p++;
pc->len--;
while (pc->len) {
switch (*pc->p) {
case '\\':
if (pc->len > 1) {
if (*++pc->p == '\n') {
pc->linenr++;
}
pc->len--;
tt = JIM_TT_ESC;
}
break;
case '"':
pc->tend = pc->p - 1;
pc->p++;
pc->len--;
return tt;
case '[':
JimParseSubCmd(pc);
tt = JIM_TT_ESC;
continue;
case '\n':
pc->linenr++;
break;
case '$':
tt = JIM_TT_ESC;
break;
}
pc->p++;
pc->len--;
}
pc->missing.ch = '"';
pc->missing.line = line;
pc->tend = pc->p - 1;
return tt;
}
/**
* Parses a [command] expression starting at pc->p.
*
* Positions the parser at the end of the command expression,
* sets pc->tend and possibly pc->missing.
*/
static void JimParseSubCmd(struct JimParserCtx *pc)
{
int level = 1;
int startofword = 1;
int line = pc->tline;
/* Skip the bracket */
pc->p++;
pc->len--;
while (pc->len) {
switch (*pc->p) {
case '\\':
if (pc->len > 1) {
if (*++pc->p == '\n') {
pc->linenr++;
}
pc->len--;
}
break;
case '[':
level++;
break;
case ']':
if (--level == 0) {
pc->tend = pc->p - 1;
pc->p++;
pc->len--;
return;
}
break;
case '"':
if (startofword) {
JimParseSubQuote(pc);
continue;
}
break;
case '{':
JimParseSubBrace(pc);
startofword = 0;
continue;
case '\n':
pc->linenr++;
break;
}
startofword = isspace(UCHAR(*pc->p));
pc->p++;
pc->len--;
}
pc->missing.ch = '[';
pc->missing.line = line;
pc->tend = pc->p - 1;
}
static int JimParseBrace(struct JimParserCtx *pc)
{
pc->tstart = pc->p + 1;
pc->tline = pc->linenr;
pc->tt = JIM_TT_STR;
JimParseSubBrace(pc);
return JIM_OK;
}
static int JimParseCmd(struct JimParserCtx *pc)
{
pc->tstart = pc->p + 1;
pc->tline = pc->linenr;
pc->tt = JIM_TT_CMD;
JimParseSubCmd(pc);
return JIM_OK;
}
static int JimParseQuote(struct JimParserCtx *pc)
{
pc->tstart = pc->p + 1;
pc->tline = pc->linenr;
pc->tt = JimParseSubQuote(pc);
return JIM_OK;
}
static int JimParseVar(struct JimParserCtx *pc)
{
/* skip the $ */
pc->p++;
pc->len--;
#ifdef EXPRSUGAR_BRACKET
if (*pc->p == '[') {
/* Parse $[...] expr shorthand syntax */
JimParseCmd(pc);
pc->tt = JIM_TT_EXPRSUGAR;
return JIM_OK;
}
#endif
pc->tstart = pc->p;
pc->tt = JIM_TT_VAR;
pc->tline = pc->linenr;
if (*pc->p == '{') {
pc->tstart = ++pc->p;
pc->len--;
while (pc->len && *pc->p != '}') {
if (*pc->p == '\n') {
pc->linenr++;
}
pc->p++;
pc->len--;
}
pc->tend = pc->p - 1;
if (pc->len) {
pc->p++;
pc->len--;
}
}
else {
while (1) {
/* Skip double colon, but not single colon! */
if (pc->p[0] == ':' && pc->p[1] == ':') {
while (*pc->p == ':') {
pc->p++;
pc->len--;
}
continue;
}
/* Note that any char >= 0x80 must be part of a utf-8 char.
* We consider all unicode points outside of ASCII as letters
*/
if (isalnum(UCHAR(*pc->p)) || *pc->p == '_' || UCHAR(*pc->p) >= 0x80) {
pc->p++;
pc->len--;
continue;
}
break;
}
/* Parse [dict get] syntax sugar. */
if (*pc->p == '(') {
int count = 1;
const char *paren = NULL;
pc->tt = JIM_TT_DICTSUGAR;
while (count && pc->len) {
pc->p++;
pc->len--;
if (*pc->p == '\\' && pc->len >= 1) {
pc->p++;
pc->len--;
}
else if (*pc->p == '(') {
count++;
}
else if (*pc->p == ')') {
paren = pc->p;
count--;
}
}
if (count == 0) {
pc->p++;
pc->len--;
}
else if (paren) {
/* Did not find a matching paren. Back up */
paren++;
pc->len += (pc->p - paren);
pc->p = paren;
}
#ifndef EXPRSUGAR_BRACKET
if (*pc->tstart == '(') {
pc->tt = JIM_TT_EXPRSUGAR;
}
#endif
}
pc->tend = pc->p - 1;
}
/* Check if we parsed just the '$' character.
* That's not a variable so an error is returned
* to tell the state machine to consider this '$' just
* a string. */
if (pc->tstart == pc->p) {
pc->p--;
pc->len++;
return JIM_ERR;
}
return JIM_OK;
}
static int JimParseStr(struct JimParserCtx *pc)
{
if (pc->tt == JIM_TT_SEP || pc->tt == JIM_TT_EOL ||
pc->tt == JIM_TT_NONE || pc->tt == JIM_TT_STR) {
/* Starting a new word */
if (*pc->p == '{') {
return JimParseBrace(pc);
}
if (*pc->p == '"') {
pc->inquote = 1;
pc->p++;
pc->len--;
/* In case the end quote is missing */
pc->missing.line = pc->tline;
}
}
pc->tstart = pc->p;
pc->tline = pc->linenr;
while (1) {
if (pc->len == 0) {
if (pc->inquote) {
pc->missing.ch = '"';
}
pc->tend = pc->p - 1;
pc->tt = JIM_TT_ESC;
return JIM_OK;
}
switch (*pc->p) {
case '\\':
if (!pc->inquote && *(pc->p + 1) == '\n') {
pc->tend = pc->p - 1;
pc->tt = JIM_TT_ESC;
return JIM_OK;
}
if (pc->len >= 2) {
if (*(pc->p + 1) == '\n') {
pc->linenr++;
}
pc->p++;
pc->len--;
}
else if (pc->len == 1) {
/* End of script with trailing backslash */
pc->missing.ch = '\\';
}
break;
case '(':
/* If the following token is not '$' just keep going */
if (pc->len > 1 && pc->p[1] != '$') {
break;
}
/* fall through */
case ')':
/* Only need a separate ')' token if the previous was a var */
if (*pc->p == '(' || pc->tt == JIM_TT_VAR) {
if (pc->p == pc->tstart) {
/* At the start of the token, so just return this char */
pc->p++;
pc->len--;
}
pc->tend = pc->p - 1;
pc->tt = JIM_TT_ESC;
return JIM_OK;
}
break;
case '$':
case '[':
pc->tend = pc->p - 1;
pc->tt = JIM_TT_ESC;
return JIM_OK;
case ' ':
case '\t':
case '\n':
case '\r':
case '\f':
case ';':
if (!pc->inquote) {
pc->tend = pc->p - 1;
pc->tt = JIM_TT_ESC;
return JIM_OK;
}
else if (*pc->p == '\n') {
pc->linenr++;
}
break;
case '"':
if (pc->inquote) {
pc->tend = pc->p - 1;
pc->tt = JIM_TT_ESC;
pc->p++;
pc->len--;
pc->inquote = 0;
return JIM_OK;
}
break;
}
pc->p++;
pc->len--;
}
return JIM_OK; /* unreached */
}
static int JimParseComment(struct JimParserCtx *pc)
{
while (*pc->p) {
if (*pc->p == '\\') {
pc->p++;
pc->len--;
if (pc->len == 0) {
pc->missing.ch = '\\';
return JIM_OK;
}
if (*pc->p == '\n') {
pc->linenr++;
}
}
else if (*pc->p == '\n') {
pc->p++;
pc->len--;
pc->linenr++;
break;
}
pc->p++;
pc->len--;
}
return JIM_OK;
}
/* xdigitval and odigitval are helper functions for JimEscape() */
static int xdigitval(int c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
static int odigitval(int c)
{
if (c >= '0' && c <= '7')
return c - '0';
return -1;
}
/* Perform Tcl escape substitution of 's', storing the result
* string into 'dest'. The escaped string is guaranteed to
* be the same length or shorter than the source string.
* slen is the length of the string at 's'.
*
* The function returns the length of the resulting string. */
static int JimEscape(char *dest, const char *s, int slen)
{
char *p = dest;
int i, len;
for (i = 0; i < slen; i++) {
switch (s[i]) {
case '\\':
switch (s[i + 1]) {
case 'a':
*p++ = 0x7;
i++;
break;
case 'b':
*p++ = 0x8;
i++;
break;
case 'f':
*p++ = 0xc;
i++;
break;
case 'n':
*p++ = 0xa;
i++;
break;
case 'r':
*p++ = 0xd;
i++;
break;
case 't':
*p++ = 0x9;
i++;
break;
case 'u':
case 'U':
case 'x':
/* A unicode or hex sequence.
* \x Expect 1-2 hex chars and convert to hex.
* \u Expect 1-4 hex chars and convert to utf-8.
* \U Expect 1-8 hex chars and convert to utf-8.
* \u{NNN} supports 1-6 hex chars and convert to utf-8.
* An invalid sequence means simply the escaped char.
*/
{
unsigned val = 0;
int k;
int maxchars = 2;
i++;
if (s[i] == 'U') {
maxchars = 8;
}
else if (s[i] == 'u') {
if (s[i + 1] == '{') {
maxchars = 6;
i++;
}
else {
maxchars = 4;
}
}
for (k = 0; k < maxchars; k++) {
int c = xdigitval(s[i + k + 1]);
if (c == -1) {
break;
}
val = (val << 4) | c;
}
/* The \u{nnn} syntax supports up to 21 bit codepoints. */
if (s[i] == '{') {
if (k == 0 || val > 0x1fffff || s[i + k + 1] != '}') {
/* Back up */
i--;
k = 0;
}
else {
/* Skip the closing brace */
k++;
}
}
if (k) {
/* Got a valid sequence, so convert */
if (s[i] == 'x') {
*p++ = val;
}
else {
p += utf8_fromunicode(p, val);
}
i += k;
break;
}
/* Not a valid codepoint, just an escaped char */
*p++ = s[i];
}
break;
case 'v':
*p++ = 0xb;
i++;
break;
case '\0':
*p++ = '\\';
i++;
break;
case '\n':
/* Replace all spaces and tabs after backslash newline with a single space*/
*p++ = ' ';
do {
i++;
} while (s[i + 1] == ' ' || s[i + 1] == '\t');
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
/* octal escape */
{
int val = 0;
int c = odigitval(s[i + 1]);
val = c;
c = odigitval(s[i + 2]);
if (c == -1) {
*p++ = val;
i++;
break;
}
val = (val * 8) + c;
c = odigitval(s[i + 3]);
if (c == -1) {
*p++ = val;
i += 2;
break;
}
val = (val * 8) + c;
*p++ = val;
i += 3;
}
break;
default:
*p++ = s[i + 1];
i++;
break;
}
break;
default:
*p++ = s[i];
break;
}
}
len = p - dest;
*p = '\0';
return len;
}
/* Returns a dynamically allocated copy of the current token in the
* parser context. The function performs conversion of escapes if
* the token is of type JIM_TT_ESC.
*
* Note that after the conversion, tokens that are grouped with
* braces in the source code, are always recognizable from the
* identical string obtained in a different way from the type.
*
* For example the string:
*
* {*}$a
*
* will return as first token "*", of type JIM_TT_STR
*
* While the string:
*
* *$a
*
* will return as first token "*", of type JIM_TT_ESC
*/
static Jim_Obj *JimParserGetTokenObj(Jim_Interp *interp, struct JimParserCtx *pc)
{
const char *start, *end;
char *token;
int len;
start = pc->tstart;
end = pc->tend;
len = (end - start) + 1;
if (len < 0) {
len = 0;
}
token = Jim_Alloc(len + 1);
if (pc->tt != JIM_TT_ESC) {
/* No escape conversion needed? Just copy it. */
memcpy(token, start, len);
token[len] = '\0';
}
else {
/* Else convert the escape chars. */
len = JimEscape(token, start, len);
}
return Jim_NewStringObjNoAlloc(interp, token, len);
}
/* -----------------------------------------------------------------------------
* Tcl Lists parsing
* ---------------------------------------------------------------------------*/
static int JimParseListSep(struct JimParserCtx *pc);
static int JimParseListStr(struct JimParserCtx *pc);
static int JimParseListQuote(struct JimParserCtx *pc);
static int JimParseList(struct JimParserCtx *pc)
{
if (isspace(UCHAR(*pc->p))) {
return JimParseListSep(pc);
}
switch (*pc->p) {
case '"':
return JimParseListQuote(pc);
case '{':
return JimParseBrace(pc);
default:
if (pc->len) {
return JimParseListStr(pc);
}
break;
}
pc->tstart = pc->tend = pc->p;
pc->tline = pc->linenr;
pc->tt = JIM_TT_EOL;
pc->eof = 1;
return JIM_OK;
}
static int JimParseListSep(struct JimParserCtx *pc)
{
pc->tstart = pc->p;
pc->tline = pc->linenr;
while (isspace(UCHAR(*pc->p))) {
if (*pc->p == '\n') {
pc->linenr++;
}
pc->p++;
pc->len--;
}
pc->tend = pc->p - 1;
pc->tt = JIM_TT_SEP;
return JIM_OK;
}
static int JimParseListQuote(struct JimParserCtx *pc)
{
pc->p++;
pc->len--;
pc->tstart = pc->p;
pc->tline = pc->linenr;
pc->tt = JIM_TT_STR;
while (pc->len) {
switch (*pc->p) {
case '\\':
pc->tt = JIM_TT_ESC;
if (--pc->len == 0) {
/* Trailing backslash */
pc->tend = pc->p;
return JIM_OK;
}
pc->p++;
break;
case '\n':
pc->linenr++;
break;
case '"':
pc->tend = pc->p - 1;
pc->p++;
pc->len--;
return JIM_OK;
}
pc->p++;
pc->len--;
}
pc->tend = pc->p - 1;
return JIM_OK;
}
static int JimParseListStr(struct JimParserCtx *pc)
{
pc->tstart = pc->p;
pc->tline = pc->linenr;
pc->tt = JIM_TT_STR;
while (pc->len) {
if (isspace(UCHAR(*pc->p))) {
pc->tend = pc->p - 1;
return JIM_OK;
}
if (*pc->p == '\\') {
if (--pc->len == 0) {
/* Trailing backslash */
pc->tend = pc->p;
return JIM_OK;
}
pc->tt = JIM_TT_ESC;
pc->p++;
}
pc->p++;
pc->len--;
}
pc->tend = pc->p - 1;
return JIM_OK;
}
/* -----------------------------------------------------------------------------
* Jim_Obj related functions
* ---------------------------------------------------------------------------*/
/* Return a new initialized object. */
Jim_Obj *Jim_NewObj(Jim_Interp *interp)
{
Jim_Obj *objPtr;
/* -- Check if there are objects in the free list -- */
if (interp->freeList != NULL) {
/* -- Unlink the object from the free list -- */
objPtr = interp->freeList;
interp->freeList = objPtr->nextObjPtr;
}
else {
/* -- No ready to use objects: allocate a new one -- */
objPtr = Jim_Alloc(sizeof(*objPtr));
}
/* Object is returned with refCount of 0. Every
* kind of GC implemented should take care to avoid
* scanning objects with refCount == 0. */
objPtr->refCount = 0;
/* All the other fields are left uninitialized to save time.
* The caller will probably want to set them to the right
* value anyway. */
/* -- Put the object into the live list -- */
objPtr->prevObjPtr = NULL;
objPtr->nextObjPtr = interp->liveList;
if (interp->liveList)
interp->liveList->prevObjPtr = objPtr;
interp->liveList = objPtr;
return objPtr;
}
/* Free an object. Actually objects are never freed, but
* just moved to the free objects list, where they will be
* reused by Jim_NewObj(). */
void Jim_FreeObj(Jim_Interp *interp, Jim_Obj *objPtr)
{
/* Check if the object was already freed, panic. */
JimPanic((objPtr->refCount != 0, "!!!Object %p freed with bad refcount %d, type=%s", objPtr,
objPtr->refCount, objPtr->typePtr ? objPtr->typePtr->name : "<none>"));
/* Free the internal representation */
Jim_FreeIntRep(interp, objPtr);
/* Free the string representation */
if (objPtr->bytes != NULL) {
if (objPtr->bytes != JimEmptyStringRep)
Jim_Free(objPtr->bytes);
}
/* Unlink the object from the live objects list */
if (objPtr->prevObjPtr)
objPtr->prevObjPtr->nextObjPtr = objPtr->nextObjPtr;
if (objPtr->nextObjPtr)
objPtr->nextObjPtr->prevObjPtr = objPtr->prevObjPtr;
if (interp->liveList == objPtr)
interp->liveList = objPtr->nextObjPtr;
#ifdef JIM_DISABLE_OBJECT_POOL
Jim_Free(objPtr);
#else
/* Link the object into the free objects list */
objPtr->prevObjPtr = NULL;
objPtr->nextObjPtr = interp->freeList;
if (interp->freeList)
interp->freeList->prevObjPtr = objPtr;
interp->freeList = objPtr;
objPtr->refCount = -1;
#endif
}
/* Invalidate the string representation of an object. */
void Jim_InvalidateStringRep(Jim_Obj *objPtr)
{
if (objPtr->bytes != NULL) {
if (objPtr->bytes != JimEmptyStringRep)
Jim_Free(objPtr->bytes);
}
objPtr->bytes = NULL;
}
/* Duplicate an object. The returned object has refcount = 0. */
Jim_Obj *Jim_DuplicateObj(Jim_Interp *interp, Jim_Obj *objPtr)
{
Jim_Obj *dupPtr;
dupPtr = Jim_NewObj(interp);
if (objPtr->bytes == NULL) {
/* Object does not have a valid string representation. */
dupPtr->bytes = NULL;
}
else if (objPtr->length == 0) {
/* Zero length, so don't even bother with the type-specific dup,
* since all zero length objects look the same
*/
dupPtr->bytes = JimEmptyStringRep;
dupPtr->length = 0;
dupPtr->typePtr = NULL;
return dupPtr;
}
else {
dupPtr->bytes = Jim_Alloc(objPtr->length + 1);
dupPtr->length = objPtr->length;
/* Copy the null byte too */
memcpy(dupPtr->bytes, objPtr->bytes, objPtr->length + 1);
}
/* By default, the new object has the same type as the old object */
dupPtr->typePtr = objPtr->typePtr;
if (objPtr->typePtr != NULL) {
if (objPtr->typePtr->dupIntRepProc == NULL) {
dupPtr->internalRep = objPtr->internalRep;
}
else {
/* The dup proc may set a different type, e.g. NULL */
objPtr->typePtr->dupIntRepProc(interp, objPtr, dupPtr);
}
}
return dupPtr;
}
/* Return the string representation for objPtr. If the object's
* string representation is invalid, calls the updateStringProc method to create
* a new one from the internal representation of the object.
*/
const char *Jim_GetString(Jim_Obj *objPtr, int *lenPtr)
{
if (objPtr->bytes == NULL) {
/* Invalid string repr. Generate it. */
JimPanic((objPtr->typePtr->updateStringProc == NULL, "UpdateStringProc called against '%s' type.", objPtr->typePtr->name));
objPtr->typePtr->updateStringProc(objPtr);
}
if (lenPtr)
*lenPtr = objPtr->length;
return objPtr->bytes;
}
/* Just returns the length (in bytes) of the object's string rep */
int Jim_Length(Jim_Obj *objPtr)
{
if (objPtr->bytes == NULL) {
/* Invalid string repr. Generate it. */
JimPanic((objPtr->typePtr->updateStringProc == NULL, "UpdateStringProc called against '%s' type.", objPtr->typePtr->name));
objPtr->typePtr->updateStringProc(objPtr);
}
return objPtr->length;
}
/* Just returns object's string rep */
const char *Jim_String(Jim_Obj *objPtr)
{
if (objPtr->bytes == NULL) {
/* Invalid string repr. Generate it. */
JimPanic((objPtr->typePtr == NULL, "UpdateStringProc called against typeless value."));
JimPanic((objPtr->typePtr->updateStringProc == NULL, "UpdateStringProc called against '%s' type.", objPtr->typePtr->name));
objPtr->typePtr->updateStringProc(objPtr);
}
return objPtr->bytes;
}
static void JimSetStringBytes(Jim_Obj *objPtr, const char *str)
{
objPtr->bytes = Jim_StrDup(str);
objPtr->length = strlen(str);
}
static void FreeDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
static void DupDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
static const Jim_ObjType dictSubstObjType = {
"dict-substitution",
FreeDictSubstInternalRep,
DupDictSubstInternalRep,
NULL,
JIM_TYPE_NONE,
};
static void FreeInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
static void DupInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
static const Jim_ObjType interpolatedObjType = {
"interpolated",
FreeInterpolatedInternalRep,
DupInterpolatedInternalRep,
NULL,
JIM_TYPE_NONE,
};
static void FreeInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
{
Jim_DecrRefCount(interp, objPtr->internalRep.dictSubstValue.indexObjPtr);
}
static void DupInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
{
/* Copy the interal rep */
dupPtr->internalRep = srcPtr->internalRep;
/* Need to increment the key ref count */
Jim_IncrRefCount(dupPtr->internalRep.dictSubstValue.indexObjPtr);
}
/* -----------------------------------------------------------------------------
* String Object
* ---------------------------------------------------------------------------*/
static void DupStringInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
static int SetStringFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
static const Jim_ObjType stringObjType = {
"string",
NULL,
DupStringInternalRep,
NULL,
JIM_TYPE_REFERENCES,
};
static void DupStringInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
{
JIM_NOTUSED(interp);
/* This is a bit subtle: the only caller of this function
* should be Jim_DuplicateObj(), that will copy the
* string representaion. After the copy, the duplicated
* object will not have more room in the buffer than
* srcPtr->length bytes. So we just set it to length. */
dupPtr->internalRep.strValue.maxLength = srcPtr->length;
dupPtr->internalRep.strValue.charLength = srcPtr->internalRep.strValue.charLength;
}
static int SetStringFromAny(Jim_Interp *interp, Jim_Obj *objPtr)
{
if (objPtr->typePtr != &stringObjType) {
/* Get a fresh string representation. */
if (objPtr->bytes == NULL) {
/* Invalid string repr. Generate it. */
JimPanic((objPtr->typePtr->updateStringProc == NULL, "UpdateStringProc called against '%s' type.", objPtr->typePtr->name));
objPtr->typePtr->updateStringProc(objPtr);
}
/* Free any other internal representation. */
Jim_FreeIntRep(interp, objPtr);
/* Set it as string, i.e. just set the maxLength field. */
objPtr->typePtr = &stringObjType;
objPtr->internalRep.strValue.maxLength = objPtr->length;
/* Don't know the utf-8 length yet */
objPtr->internalRep.strValue.charLength = -1;
}
return JIM_OK;
}
/**
* Returns the length of the object string in chars, not bytes.
*
* These may be different for a utf-8 string.
*/
int Jim_Utf8Length(Jim_Interp *interp, Jim_Obj *objPtr)
{
#ifdef JIM_UTF8
SetStringFromAny(interp, objPtr);
if (objPtr->internalRep.strValue.charLength < 0) {
objPtr->internalRep.strValue.charLength = utf8_strlen(objPtr->bytes, objPtr->length);
}
return objPtr->internalRep.strValue.charLength;
#else
return Jim_Length(objPtr);
#endif
}
/* len is in bytes -- see also Jim_NewStringObjUtf8() */
Jim_Obj *Jim_NewStringObj(Jim_Interp *interp, const char *s, int len)
{
Jim_Obj *objPtr = Jim_NewObj(interp);
/* Need to find out how many bytes the string requires */
if (len == -1)
len = strlen(s);
/* Alloc/Set the string rep. */
if (len == 0) {
objPtr->bytes = JimEmptyStringRep;
}
else {
objPtr->bytes = Jim_StrDupLen(s, len);
}
objPtr->length = len;
/* No typePtr field for the vanilla string object. */
objPtr->typePtr = NULL;
return objPtr;
}
/* charlen is in characters -- see also Jim_NewStringObj() */
Jim_Obj *Jim_NewStringObjUtf8(Jim_Interp *interp, const char *s, int charlen)
{
#ifdef JIM_UTF8
/* Need to find out how many bytes the string requires */
int bytelen = utf8_index(s, charlen);
Jim_Obj *objPtr = Jim_NewStringObj(interp, s, bytelen);
/* Remember the utf8 length, so set the type */
objPtr->typePtr = &stringObjType;
objPtr->internalRep.strValue.maxLength = bytelen;
objPtr->internalRep.strValue.charLength = charlen;
return objPtr;
#else
return Jim_NewStringObj(interp, s, charlen);
#endif
}
/* This version does not try to duplicate the 's' pointer, but
* use it directly. */
Jim_Obj *Jim_NewStringObjNoAlloc(Jim_Interp *interp, char *s, int len)
{
Jim_Obj *objPtr = Jim_NewObj(interp);
objPtr->bytes = s;
objPtr->length = (len == -1) ? strlen(s) : len;
objPtr->typePtr = NULL;
return objPtr;
}
/* Low-level string append. Use it only against unshared objects
* of type "string". */
static void StringAppendString(Jim_Obj *objPtr, const char *str, int len)
{
int needlen;
if (len == -1)
len = strlen(str);
needlen = objPtr->length + len;
if (objPtr->internalRep.strValue.maxLength < needlen ||
objPtr->internalRep.strValue.maxLength == 0) {
needlen *= 2;
/* Inefficient to malloc() for less than 8 bytes */
if (needlen < 7) {
needlen = 7;
}
if (objPtr->bytes == JimEmptyStringRep) {
objPtr->bytes = Jim_Alloc(needlen + 1);
}
else {
objPtr->bytes = Jim_Realloc(objPtr->bytes, needlen + 1);
}
objPtr->internalRep.strValue.maxLength = needlen;
}
memcpy(objPtr->bytes + objPtr->length, str, len);
objPtr->bytes[objPtr->length + len] = '\0';
if (objPtr->internalRep.strValue.charLength >= 0) {
/* Update the utf-8 char length */
objPtr->internalRep.strValue.charLength += utf8_strlen(objPtr->bytes + objPtr->length, len);
}
objPtr->length += len;
}
/* Higher level API to append strings to objects.
* Object must not be unshared for each of these.
*/
void Jim_AppendString(Jim_Interp *interp, Jim_Obj *objPtr, const char *str, int len)
{
JimPanic((Jim_IsShared(objPtr), "Jim_AppendString called with shared object"));
SetStringFromAny(interp, objPtr);
StringAppendString(objPtr, str, len);
}
void Jim_AppendObj(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *appendObjPtr)
{
int len;
const char *str = Jim_GetString(appendObjPtr, &len);
Jim_AppendString(interp, objPtr, str, len);
}
void Jim_AppendStrings(Jim_Interp *interp, Jim_Obj *objPtr, ...)
{
va_list ap;
SetStringFromAny(interp, objPtr);
va_start(ap, objPtr);
while (1) {
const char *s = va_arg(ap, const char *);
if (s == NULL)
break;
Jim_AppendString(interp, objPtr, s, -1);
}
va_end(ap);
}
int Jim_StringEqObj(Jim_Obj *aObjPtr, Jim_Obj *bObjPtr)
{
if (aObjPtr == bObjPtr) {
return 1;
}
else {
int Alen, Blen;
const char *sA = Jim_GetString(aObjPtr, &Alen);
const char *sB = Jim_GetString(bObjPtr, &Blen);
return Alen == Blen && memcmp(sA, sB, Alen) == 0;
}
}
/**
* Note. Does not support embedded nulls in either the pattern or the object.
*/
int Jim_StringMatchObj(Jim_Interp *interp, Jim_Obj *patternObjPtr, Jim_Obj *objPtr, int nocase)
{
return JimGlobMatch(Jim_String(patternObjPtr), Jim_String(objPtr), nocase);
}
/*
* Note: does not support embedded nulls for the nocase option.
*/
int Jim_StringCompareObj(Jim_Interp *interp, Jim_Obj *firstObjPtr, Jim_Obj *secondObjPtr, int nocase)
{
int l1, l2;
const char *s1 = Jim_GetString(firstObjPtr, &l1);
const char *s2 = Jim_GetString(secondObjPtr, &l2);
if (nocase) {
/* Do a character compare for nocase */
return JimStringCompareLen(s1, s2, -1, nocase);
}
return JimStringCompare(s1, l1, s2, l2);
}
/**
* Like Jim_StringCompareObj() except compares to a maximum of the length of firstObjPtr.
*
* Note: does not support embedded nulls
*/
int Jim_StringCompareLenObj(Jim_Interp *interp, Jim_Obj *firstObjPtr, Jim_Obj *secondObjPtr, int nocase)
{
const char *s1 = Jim_String(firstObjPtr);
const char *s2 = Jim_String(secondObjPtr);
return JimStringCompareLen(s1, s2, Jim_Utf8Length(interp, firstObjPtr), nocase);
}
/* Convert a range, as returned by Jim_GetRange(), into
* an absolute index into an object of the specified length.
* This function may return negative values, or values
* greater than or equal to the length of the list if the index
* is out of range. */
static int JimRelToAbsIndex(int len, int idx)
{
if (idx < 0)
return len + idx;
return idx;
}
/* Convert a pair of indexes (*firstPtr, *lastPtr) as normalized by JimRelToAbsIndex(),
* into a form suitable for implementation of commands like [string range] and [lrange].
*
* The resulting range is guaranteed to address valid elements of
* the structure.
*/
static void JimRelToAbsRange(int len, int *firstPtr, int *lastPtr, int *rangeLenPtr)
{
int rangeLen;
if (*firstPtr > *lastPtr) {
rangeLen = 0;
}
else {
rangeLen = *lastPtr - *firstPtr + 1;
if (rangeLen) {
if (*firstPtr < 0) {
rangeLen += *firstPtr;
*firstPtr = 0;
}
if (*lastPtr >= len) {
rangeLen -= (*lastPtr - (len - 1));
*lastPtr = len - 1;
}
}
}
if (rangeLen < 0)
rangeLen = 0;
*rangeLenPtr = rangeLen;
}
static int JimStringGetRange(Jim_Interp *interp, Jim_Obj *firstObjPtr, Jim_Obj *lastObjPtr,
int len, int *first, int *last, int *range)
{
if (Jim_GetIndex(interp, firstObjPtr, first) != JIM_OK) {
return JIM_ERR;
}
if (Jim_GetIndex(interp, lastObjPtr, last) != JIM_OK) {
return JIM_ERR;
}
*first = JimRelToAbsIndex(len, *first);
*last = JimRelToAbsIndex(len, *last);
JimRelToAbsRange(len, first, last, range);
return JIM_OK;
}
Jim_Obj *Jim_StringByteRangeObj(Jim_Interp *interp,
Jim_Obj *strObjPtr, Jim_Obj *firstObjPtr, Jim_Obj *lastObjPtr)
{
int first, last;
const char *str;
int rangeLen;
int bytelen;
str = Jim_GetString(strObjPtr, &bytelen);
if (JimStringGetRange(interp, firstObjPtr, lastObjPtr, bytelen, &first, &last, &rangeLen) != JIM_OK) {
return NULL;
}
if (first == 0 && rangeLen == bytelen) {
return strObjPtr;
}
return Jim_NewStringObj(interp, str + first, rangeLen);
}
Jim_Obj *Jim_StringRangeObj(Jim_Interp *interp,
Jim_Obj *strObjPtr, Jim_Obj *firstObjPtr, Jim_Obj *lastObjPtr)
{
#ifdef JIM_UTF8
int first, last;
const char *str;
int len, rangeLen;
int bytelen;
str = Jim_GetString(strObjPtr, &bytelen);
len = Jim_Utf8Length(interp, strObjPtr);
if (JimStringGetRange(interp, firstObjPtr, lastObjPtr, len, &first, &last, &rangeLen) != JIM_OK) {
return NULL;
}
if (first == 0 && rangeLen == len) {
return strObjPtr;
}
if (len == bytelen) {
/* ASCII optimisation */
return Jim_NewStringObj(interp, str + first, rangeLen);
}
return Jim_NewStringObjUtf8(interp, str + utf8_index(str, first), rangeLen);
#else
return Jim_StringByteRangeObj(interp, strObjPtr, firstObjPtr, lastObjPtr);
#endif
}
Jim_Obj *JimStringReplaceObj(Jim_Interp *interp,
Jim_Obj *strObjPtr, Jim_Obj *firstObjPtr, Jim_Obj *lastObjPtr, Jim_Obj *newStrObj)
{
int first, last;
const char *str;
int len, rangeLen;
Jim_Obj *objPtr;
len = Jim_Utf8Length(interp, strObjPtr);
if (JimStringGetRange(interp, firstObjPtr, lastObjPtr, len, &first, &last, &rangeLen) != JIM_OK) {
return NULL;
}
if (last < first) {
return strObjPtr;
}
str = Jim_String(strObjPtr);
/* Before part */
objPtr = Jim_NewStringObjUtf8(interp, str, first);
/* Replacement */
if (newStrObj) {
Jim_AppendObj(interp, objPtr, newStrObj);
}
/* After part */
Jim_AppendString(interp, objPtr, str + utf8_index(str, last + 1), len - last - 1);
return objPtr;
}
/**
* Note: does not support embedded nulls.
*/
static void JimStrCopyUpperLower(char *dest, const char *str, int uc)
{
while (*str) {
int c;
str += utf8_tounicode(str, &c);
dest += utf8_getchars(dest, uc ? utf8_upper(c) : utf8_lower(c));
}
*dest = 0;
}
/**
* Note: does not support embedded nulls.
*/
static Jim_Obj *JimStringToLower(Jim_Interp *interp, Jim_Obj *strObjPtr)
{
char *buf;
int len;
const char *str;
str = Jim_GetString(strObjPtr, &len);
#ifdef JIM_UTF8
/* Case mapping can change the utf-8 length of the string.
* But at worst it will be by one extra byte per char
*/
len *= 2;
#endif
buf = Jim_Alloc(len + 1);
JimStrCopyUpperLower(buf, str, 0);
return Jim_NewStringObjNoAlloc(interp, buf, -1);
}
/**
* Note: does not support embedded nulls.
*/
static Jim_Obj *JimStringToUpper(Jim_Interp *interp, Jim_Obj *strObjPtr)
{
char *buf;
const char *str;
int len;
str = Jim_GetString(strObjPtr, &len);
#ifdef JIM_UTF8
/* Case mapping can change the utf-8 length of the string.
* But at worst it will be by one extra byte per char
*/
len *= 2;
#endif
buf = Jim_Alloc(len + 1);
JimStrCopyUpperLower(buf, str, 1);
return Jim_NewStringObjNoAlloc(interp, buf, -1);
}
/**
* Note: does not support embedded nulls.
*/
static Jim_Obj *JimStringToTitle(Jim_Interp *interp, Jim_Obj *strObjPtr)
{
char *buf, *p;
int len;
int c;
const char *str;
str = Jim_GetString(strObjPtr, &len);
#ifdef JIM_UTF8
/* Case mapping can change the utf-8 length of the string.
* But at worst it will be by one extra byte per char
*/
len *= 2;
#endif
buf = p = Jim_Alloc(len + 1);
str += utf8_tounicode(str, &c);
p += utf8_getchars(p, utf8_title(c));
JimStrCopyUpperLower(p, str, 0);
return Jim_NewStringObjNoAlloc(interp, buf, -1);
}
/* Similar to memchr() except searches a UTF-8 string 'str' of byte length 'len'
* for unicode character 'c'.
* Returns the position if found or NULL if not
*/
static const char *utf8_memchr(const char *str, int len, int c)
{
#ifdef JIM_UTF8
while (len) {
int sc;
int n = utf8_tounicode(str, &sc);
if (sc == c) {
return str;
}
str += n;
len -= n;
}
return NULL;
#else
return memchr(str, c, len);
#endif
}
/**
* Searches for the first non-trim char in string (str, len)
*
* If none is found, returns just past the last char.
*
* Lengths are in bytes.
*/
static const char *JimFindTrimLeft(const char *str, int len, const char *trimchars, int trimlen)
{
while (len) {
int c;
int n = utf8_tounicode(str, &c);
if (utf8_memchr(trimchars, trimlen, c) == NULL) {
/* Not a trim char, so stop */
break;
}
str += n;
len -= n;
}
return str;
}
/**
* Searches backwards for a non-trim char in string (str, len).
*
* Returns a pointer to just after the non-trim char, or NULL if not found.
*
* Lengths are in bytes.
*/
static const char *JimFindTrimRight(const char *str, int len, const char *trimchars, int trimlen)
{
str += len;
while (len) {
int c;
int n = utf8_prev_len(str, len);
len -= n;
str -= n;
n = utf8_tounicode(str, &c);
if (utf8_memchr(trimchars, trimlen, c) == NULL) {
return str + n;
}
}
return NULL;
}
static const char default_trim_chars[] = " \t\n\r";
/* sizeof() here includes the null byte */
static int default_trim_chars_len = sizeof(default_trim_chars);
static Jim_Obj *JimStringTrimLeft(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *trimcharsObjPtr)
{
int len;
const char *str = Jim_GetString(strObjPtr, &len);
const char *trimchars = default_trim_chars;
int trimcharslen = default_trim_chars_len;
const char *newstr;
if (trimcharsObjPtr) {
trimchars = Jim_GetString(trimcharsObjPtr, &trimcharslen);
}
newstr = JimFindTrimLeft(str, len, trimchars, trimcharslen);
if (newstr == str) {
return strObjPtr;
}
return Jim_NewStringObj(interp, newstr, len - (newstr - str));
}
static Jim_Obj *JimStringTrimRight(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *trimcharsObjPtr)
{
int len;
const char *trimchars = default_trim_chars;
int trimcharslen = default_trim_chars_len;
const char *nontrim;
if (trimcharsObjPtr) {
trimchars = Jim_GetString(trimcharsObjPtr, &trimcharslen);
}
SetStringFromAny(interp, strObjPtr);
len = Jim_Length(strObjPtr);
nontrim = JimFindTrimRight(strObjPtr->bytes, len, trimchars, trimcharslen);
if (nontrim == NULL) {
/* All trim, so return a zero-length string */
return Jim_NewEmptyStringObj(interp);
}
if (nontrim == strObjPtr->bytes + len) {
/* All non-trim, so return the original object */
return strObjPtr;
}
if (Jim_IsShared(strObjPtr)) {
strObjPtr = Jim_NewStringObj(interp, strObjPtr->bytes, (nontrim - strObjPtr->bytes));
}
else {
/* Can modify this string in place */
strObjPtr->bytes[nontrim - strObjPtr->bytes] = 0;
strObjPtr->length = (nontrim - strObjPtr->bytes);
}
return strObjPtr;
}
static Jim_Obj *JimStringTrim(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *trimcharsObjPtr)
{
/* First trim left. */
Jim_Obj *objPtr = JimStringTrimLeft(interp, strObjPtr, trimcharsObjPtr);
/* Now trim right */
strObjPtr = JimStringTrimRight(interp, objPtr, trimcharsObjPtr);
/* Note: refCount check is needed since objPtr may be emptyObj */
if (objPtr != strObjPtr && objPtr->refCount == 0) {
/* We don't want this object to be leaked */
Jim_FreeNewObj(interp, objPtr);
}
return strObjPtr;
}
/* Some platforms don't have isascii - need a non-macro version */
#ifdef HAVE_ISASCII
#define jim_isascii isascii
#else
static int jim_isascii(int c)
{
return !(c & ~0x7f);
}
#endif
static int JimStringIs(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *strClass, int strict)
{
static const char * const strclassnames[] = {
"integer", "alpha", "alnum", "ascii", "digit",
"double", "lower", "upper", "space", "xdigit",
"control", "print", "graph", "punct", "boolean",
NULL
};
enum {
STR_IS_INTEGER, STR_IS_ALPHA, STR_IS_ALNUM, STR_IS_ASCII, STR_IS_DIGIT,
STR_IS_DOUBLE, STR_IS_LOWER, STR_IS_UPPER, STR_IS_SPACE, STR_IS_XDIGIT,
STR_IS_CONTROL, STR_IS_PRINT, STR_IS_GRAPH, STR_IS_PUNCT, STR_IS_BOOLEAN,
};
int strclass;
int len;
int i;
const char *str;
int (*isclassfunc)(int c) = NULL;
if (Jim_GetEnum(interp, strClass, strclassnames, &strclass, "class", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) {
return JIM_ERR;
}
str = Jim_GetString(strObjPtr, &len);
if (len == 0) {
Jim_SetResultBool(interp, !strict);
return JIM_OK;
}
switch (strclass) {
case STR_IS_INTEGER:
{
jim_wide w;
Jim_SetResultBool(interp, JimGetWideNoErr(interp, strObjPtr, &w) == JIM_OK);
return JIM_OK;
}
case STR_IS_DOUBLE:
{
double d;
Jim_SetResultBool(interp, Jim_GetDouble(interp, strObjPtr, &d) == JIM_OK && errno != ERANGE);
return JIM_OK;
}
case STR_IS_BOOLEAN:
{
int b;
Jim_SetResultBool(interp, Jim_GetBoolean(interp, strObjPtr, &b) == JIM_OK);
return JIM_OK;
}
case STR_IS_ALPHA: isclassfunc = isalpha; break;
case STR_IS_ALNUM: isclassfunc = isalnum; break;
case STR_IS_ASCII: isclassfunc = jim_isascii; break;
case STR_IS_DIGIT: isclassfunc = isdigit; break;
case STR_IS_LOWER: isclassfunc = islower; break;
case STR_IS_UPPER: isclassfunc = isupper; break;
case STR_IS_SPACE: isclassfunc = isspace; break;
case STR_IS_XDIGIT: isclassfunc = isxdigit; break;
case STR_IS_CONTROL: isclassfunc = iscntrl; break;
case STR_IS_PRINT: isclassfunc = isprint; break;
case STR_IS_GRAPH: isclassfunc = isgraph; break;
case STR_IS_PUNCT: isclassfunc = ispunct; break;
default:
return JIM_ERR;
}
for (i = 0; i < len; i++) {
if (!isclassfunc(UCHAR(str[i]))) {
Jim_SetResultBool(interp, 0);
return JIM_OK;
}
}
Jim_SetResultBool(interp, 1);
return JIM_OK;
}
/* -----------------------------------------------------------------------------
* Compared String Object
* ---------------------------------------------------------------------------*/
/* This is strange object that allows comparison of a C literal string
* with a Jim object in a very short time if the same comparison is done
* multiple times. For example every time the [if] command is executed,
* Jim has to check if a given argument is "else".
* If the code has no errors, this comparison is true most of the time,
* so we can cache the pointer of the string of the last matching
* comparison inside the object. Because most C compilers perform literal sharing,
* so that: char *x = "foo", char *y = "foo", will lead to x == y,
* this works pretty well even if comparisons are at different places
* inside the C code. */
static const Jim_ObjType comparedStringObjType = {
"compared-string",
NULL,
NULL,
NULL,
JIM_TYPE_REFERENCES,
};
/* The only way this object is exposed to the API is via the following
* function. Returns true if the string and the object string repr.
* are the same, otherwise zero is returned.
*
* Note: this isn't binary safe, but it hardly needs to be.*/
int Jim_CompareStringImmediate(Jim_Interp *interp, Jim_Obj *objPtr, const char *str)
{
if (objPtr->typePtr == &comparedStringObjType && objPtr->internalRep.ptr == str) {
return 1;
}
else {
if (strcmp(str, Jim_String(objPtr)) != 0)
return 0;
if (objPtr->typePtr != &comparedStringObjType) {
Jim_FreeIntRep(interp, objPtr);
objPtr->typePtr = &comparedStringObjType;
}
objPtr->internalRep.ptr = (char *)str; /*ATTENTION: const cast */
return 1;
}
}
static int qsortCompareStringPointers(const void *a, const void *b)
{
char *const *sa = (char *const *)a;
char *const *sb = (char *const *)b;
return strcmp(*sa, *sb);
}
/* -----------------------------------------------------------------------------
* Source Object
*
* This object is just a string from the language point of view, but
* the internal representation contains the filename and line number
* where this token was read. This information is used by
* Jim_EvalObj() if the object passed happens to be of type "source".
*
* This allows propagation of the information about line numbers and file
* names and gives error messages with absolute line numbers.
*
* Note that this object uses the internal representation of the Jim_Object,
* so there is almost no memory overhead. (One Jim_Obj for each filename).
*
* Also the object will be converted to something else if the given
* token it represents in the source file is not something to be
* evaluated (not a script), and will be specialized in some other way,
* so the time overhead is also almost zero.
* ---------------------------------------------------------------------------*/
static void FreeSourceInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
static void DupSourceInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
static const Jim_ObjType sourceObjType = {
"source",
FreeSourceInternalRep,
DupSourceInternalRep,
NULL,
JIM_TYPE_REFERENCES,
};
void FreeSourceInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
{
Jim_DecrRefCount(interp, objPtr->internalRep.sourceValue.fileNameObj);
}
void DupSourceInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
{
dupPtr->internalRep.sourceValue = srcPtr->internalRep.sourceValue;
Jim_IncrRefCount(dupPtr->internalRep.sourceValue.fileNameObj);
}
static void JimSetSourceInfo(Jim_Interp *interp, Jim_Obj *objPtr,
Jim_Obj *fileNameObj, int lineNumber)
{
JimPanic((Jim_IsShared(objPtr), "JimSetSourceInfo called with shared object"));
JimPanic((objPtr->typePtr != NULL, "JimSetSourceInfo called with typed object"));
Jim_IncrRefCount(fileNameObj);
objPtr->internalRep.sourceValue.fileNameObj = fileNameObj;
objPtr->internalRep.sourceValue.lineNumber = lineNumber;
objPtr->typePtr = &sourceObjType;
}
/* -----------------------------------------------------------------------------
* ScriptLine Object
*
* This object is used only in the Script internal represenation.
* For each line of the script, it holds the number of tokens on the line
* and the source line number.
*/
static const Jim_ObjType scriptLineObjType = {
"scriptline",
NULL,
NULL,
NULL,
JIM_NONE,
};
static Jim_Obj *JimNewScriptLineObj(Jim_Interp *interp, int argc, int line)
{
Jim_Obj *objPtr;
#ifdef DEBUG_SHOW_SCRIPT
char buf[100];
snprintf(buf, sizeof(buf), "line=%d, argc=%d", line, argc);
objPtr = Jim_NewStringObj(interp, buf, -1);
#else
objPtr = Jim_NewEmptyStringObj(interp);
#endif
objPtr->typePtr = &scriptLineObjType;
objPtr->internalRep.scriptLineValue.argc = argc;
objPtr->internalRep.scriptLineValue.line = line;
return objPtr;
}
/* -----------------------------------------------------------------------------
* Script Object
*
* This object holds the parsed internal representation of a script.
* This representation is help within an allocated ScriptObj (see below)
*/
static void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr);
static void DupScriptInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr);
static const Jim_ObjType scriptObjType = {
"script",
FreeScriptInternalRep,
DupScriptInternalRep,
NULL,
JIM_TYPE_REFERENCES,
};
/* Each token of a script is represented by a ScriptToken.
* The ScriptToken contains a type and a Jim_Obj. The Jim_Obj
* can be specialized by commands operating on it.
*/
typedef struct ScriptToken
{
Jim_Obj *objPtr;
int type;
} ScriptToken;
/* This is the script object internal representation. An array of
* ScriptToken structures, including a pre-computed representation of the
* command length and arguments.
*
* For example the script:
*
* puts hello
* set $i $x$y [foo]BAR
*
* will produce a ScriptObj with the following ScriptToken's:
*
* LIN 2
* ESC puts
* ESC hello
* LIN 4
* ESC set
* VAR i
* WRD 2
* VAR x
* VAR y
* WRD 2
* CMD foo
* ESC BAR
*
* "puts hello" has two args (LIN 2), composed of single tokens.
* (Note that the WRD token is omitted for the common case of a single token.)
*
* "set $i $x$y [foo]BAR" has four (LIN 4) args, the first word
* has 1 token (ESC SET), and the last has two tokens (WRD 2 CMD foo ESC BAR)
*
* The precomputation of the command structure makes Jim_Eval() faster,
* and simpler because there aren't dynamic lengths / allocations.
*
* -- {expand}/{*} handling --
*
* Expand is handled in a special way.
*
* If a "word" begins with {*}, the word token count is -ve.
*
* For example the command:
*
* list {*}{a b}
*
* Will produce the following cmdstruct array:
*
* LIN 2
* ESC list
* WRD -1
* STR a b
*
* Note that the 'LIN' token also contains the source information for the
* first word of the line for error reporting purposes
*
* -- the substFlags field of the structure --
*
* The scriptObj structure is used to represent both "script" objects
* and "subst" objects. In the second case, there are no LIN and WRD
* tokens. Instead SEP and EOL tokens are added as-is.
* In addition, the field 'substFlags' is used to represent the flags used to turn
* the string into the internal representation.
* If these flags do not match what the application requires,
* the scriptObj is created again. For example the script:
*
* subst -nocommands $string
* subst -novariables $string
*
* Will (re)create the internal representation of the $string object
* two times.
*/
typedef struct ScriptObj
{
ScriptToken *token; /* Tokens array. */
Jim_Obj *fileNameObj; /* Filename */
int len; /* Length of token[] */
int substFlags; /* flags used for the compilation of "subst" objects */
int inUse; /* Used to share a ScriptObj. Currently
only used by Jim_EvalObj() as protection against
shimmering of the currently evaluated object. */
int firstline; /* Line number of the first line */
int linenr; /* Error line number, if any */
int missing; /* Missing char if script failed to parse, (or space or backslash if OK) */
} ScriptObj;
static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr);
static int JimParseCheckMissing(Jim_Interp *interp, int ch);
static ScriptObj *JimGetScript(Jim_Interp *interp, Jim_Obj *objPtr);
void FreeScriptInternalRep(Jim_Interp *interp, Jim_Obj *objPtr)
{
int i;
struct ScriptObj *script = (void *)objPtr->internalRep.ptr;
if (--script->inUse != 0)
return;
for (i = 0; i < script->len; i++) {
Jim_DecrRefCount(interp, script->token[i].objPtr);
}
Jim_Free(script->token);
Jim_DecrRefCount(interp, script->fileNameObj);
Jim_Free(script);
}
void DupScriptInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr)
{
JIM_NOTUSED(interp);
JIM_NOTUSED(srcPtr);
/* Just return a simple string. We don't try to preserve the source info
* since in practice scripts are never duplicated
*/
dupPtr->typePtr = NULL;
}
/* A simple parse token.
* As the script is parsed, the created tokens point into the script string rep.
*/
typedef struct
{
const char *token; /* Pointer to the start of the token */
int len; /* Length of this token */
int type; /* Token type */
int line; /* Line number */
} ParseToken;
/* A list of parsed tokens representing a script.
* Tokens are added to this list as the script is parsed.
* It grows as needed.
*/
typedef struct
{
/* Start with a statically allocated list of tokens which will be expanded with realloc if needed */
ParseToken *list; /* Array of tokens */
int size; /* Current size of the list */
int count; /* Number of entries used */
ParseToken static_list[20]; /* Small initial token space to avoid allocation */
} ParseTokenList;
static void ScriptTokenListInit(ParseTokenList *tokenlist)
{
tokenlist->list = tokenlist->static_list;
tokenlist->size = sizeof(tokenlist->static_list) / sizeof(ParseToken);
tokenlist->count = 0;
}
static void ScriptTokenListFree(ParseTokenList *tokenlist)
{
if (tokenlist->list != tokenlist->static_list) {
Jim_Free(tokenlist->list);
}
}
/**
* Adds the new token to the tokenlist.
* The token has the given length, type and line number.
* The token list is resized as necessary.
*/
static void ScriptAddToken(ParseTokenList *tokenlist, const char *token, int len, int type,
int line)
{
ParseToken *t;
if (tokenlist->count == tokenlist->size) {
/* Resize the list */
tokenlist->size *= 2;
if (tokenlist->list != tokenlist->static_list) {
tokenlist->list =
Jim_Realloc(tokenlist->list, tokenlist->size * sizeof(*tokenlist->list));
}
else {
/* The list needs to become allocated */
tokenlist->list = Jim_Alloc(tokenlist->size * sizeof(*tokenlist->list));
memcpy(tokenlist->list, tokenlist->static_list,
tokenlist->count * sizeof(*tokenlist->list));
}
}
t = &tokenlist->list[tokenlist->count++];
t->token = token;
t->len = len;
t->type = type;
t->line = line;
}
/* Counts the number of adjoining non-separator tokens.
*
* Returns -ve if the first token is the expansion
* operator (in which case the count doesn't include
* that token).
*/
static int JimCountWordTokens(struct ScriptObj *script, ParseToken *t)
{
int expand = 1;
int count = 0;
/* Is the first word {*} or {expand}? */
if (t->type == JIM_TT_STR && !TOKEN_IS_SEP(t[1].type)) {
if ((t->len == 1 && *t->token == '*') || (t->len == 6 && strncmp(t->token, "expand", 6) == 0)) {
/* Create an expand token */
expand = -1;
t++;
}
else {
if (script->missing == ' ') {
/* This is a "extra characters after close-brace" error. Report the first error */
script->missing = '}';
script->linenr = t[1].line;
}
}
}
/* Now count non-separator words */
while (!TOKEN_IS_SEP(t->type)) {
t++;
count++;
}
return count * expand;
}
/**
* Create a script/subst object from the given token.
*/
static Jim_Obj *JimMakeScriptObj(Jim_Interp *interp, const ParseToken *t)
{
Jim_Obj *objPtr;
if (t->type == JIM_TT_ESC && memchr(t->token, '\\', t->len) != NULL) {
/* Convert backlash escapes. The result will never be longer than the original */
int len = t->len;
char *str = Jim_Alloc(len + 1);
len = JimEscape(str, t->token, len);
objPtr = Jim_NewStringObjNoAlloc(interp, str, len);
}
else {
/* XXX: For strict Tcl compatibility, JIM_TT_STR should replace <backslash><newline><whitespace>
* with a single space.
*/
objPtr = Jim_NewStringObj(interp, t->token, t->len);
}
return objPtr;
}
/**
* Takes a tokenlist and creates the allocated list of script tokens
* in script->token, of length script->len.
*
* Unnecessary tokens are discarded, and LINE and WORD tokens are inserted
* as required.
*
* Also sets script->line to the line number of the first token
*/
static void ScriptObjAddTokens(Jim_Interp *interp, struct ScriptObj *script,
ParseTokenList *tokenlist)
{
int i;
struct ScriptToken *token;
/* Number of tokens so far for the current command */
int lineargs = 0;
/* This is the first token for the current command */
ScriptToken *linefirst;
int count;
int linenr;
#ifdef DEBUG_SHOW_SCRIPT_TOKENS
printf("==== Tokens ====\n");
for (i = 0; i < tokenlist->count; i++) {
printf("[%2d]@%d %s '%.*s'\n", i, tokenlist->list[i].line, jim_tt_name(tokenlist->list[i].type),
tokenlist->list[i].len, tokenlist->list[i].token);
}
#endif
/* May need up to one extra script token for each EOL in the worst case */
count = tokenlist->count;
for (i = 0; i < tokenlist->count; i++) {
if (tokenlist->list[i].type == JIM_TT_EOL) {
count++;
}
}
linenr = script->firstline = tokenlist->list[0].line;
token = script->token = Jim_Alloc(sizeof(ScriptToken) * count);
/* This is the first token for the current command */
linefirst = token++;
for (i = 0; i < tokenlist->count; ) {
/* Look ahead to find out how many tokens make up the next word */
int wordtokens;
/* Skip any leading separators */
while (tokenlist->list[i].type == JIM_TT_SEP) {
i++;
}
wordtokens = JimCountWordTokens(script, tokenlist->list + i);
if (wordtokens == 0) {
/* None, so at end of line */
if (lineargs) {
linefirst->type = JIM_TT_LINE;
linefirst->objPtr = JimNewScriptLineObj(interp, lineargs, linenr);
Jim_IncrRefCount(linefirst->objPtr);
/* Reset for new line */
lineargs = 0;
linefirst = token++;
}
i++;
continue;
}
else if (wordtokens != 1) {
/* More than 1, or {*}, so insert a WORD token */
token->type = JIM_TT_WORD;
token->objPtr = Jim_NewIntObj(interp, wordtokens);
Jim_IncrRefCount(token->objPtr);
token++;
if (wordtokens < 0) {
/* Skip the expand token */
i++;
wordtokens = -wordtokens - 1;
lineargs--;
}
}
if (lineargs == 0) {
/* First real token on the line, so record the line number */
linenr = tokenlist->list[i].line;
}
lineargs++;
/* Add each non-separator word token to the line */
while (wordtokens--) {
const ParseToken *t = &tokenlist->list[i++];
token->type = t->type;
token->objPtr = JimMakeScriptObj(interp, t);
Jim_IncrRefCount(token->objPtr);
/* Every object is initially a string of type 'source', but the
* internal type may be specialized during execution of the
* script. */
JimSetSourceInfo(interp, token->objPtr, script->fileNameObj, t->line);
token++;
}
}
if (lineargs == 0) {
token--;
}
script->len = token - script->token;
JimPanic((script->len >= count, "allocated script array is too short"));
#ifdef DEBUG_SHOW_SCRIPT
printf("==== Script (%s) ====\n", Jim_String(script->fileNameObj));
for (i = 0; i < script->len; i++) {
const ScriptToken *t = &script->token[i];
printf("[%2d] %s %s\n", i, jim_tt_name(t->type), Jim_String(t->objPtr));
}
#endif
}
/* Parses the given string object to determine if it represents a complete script.
*
* This is useful for interactive shells implementation, for [info complete].
*
* If 'stateCharPtr' != NULL, the function stores ' ' on complete script,
* '{' on scripts incomplete missing one or more '}' to be balanced.
* '[' on scripts incomplete missing one or more ']' to be balanced.
* '"' on scripts incomplete missing a '"' char.
* '\\' on scripts with a trailing backslash.
*
* If the script is complete, 1 is returned, otherwise 0.
*
* If the script has extra characters after a close brace, this still returns 1,
* but sets *stateCharPtr to '}'
* Evaluating the script will give the error "extra characters after close-brace".
*/
int Jim_ScriptIsComplete(Jim_Interp *interp, Jim_Obj *scriptObj, char *stateCharPtr)
{
ScriptObj *script = JimGetScript(interp, scriptObj);
if (stateCharPtr) {
*stateCharPtr = script->missing;
}
return script->missing == ' ' || script->missing == '}';
}
/**
* Sets an appropriate error message for a missing script/expression terminator.
*
* Returns JIM_ERR if 'ch' represents an unmatched/missing character.
*
* Note that a trailing backslash is not considered to be an error.
*/
static int JimParseCheckMissing(Jim_Interp *interp, int ch)
{
const char *msg;
switch (ch) {
case '\\':
case ' ':
return JIM_OK;
case '[':
msg = "unmatched \"[\"";
break;
case '{':
msg = "missing close-brace";
break;
case '}':
msg = "extra characters after close-brace";
break;
case '"':
default:
msg = "missing quote";
break;
}
Jim_SetResultString(interp, msg, -1);
return JIM_ERR;
}
/**
* Similar to ScriptObjAddTokens(), but for subst objects.
*/
static void SubstObjAddTokens(Jim_Interp *interp, struct ScriptObj *script,
ParseTokenList *tokenlist)
{
int i;
struct ScriptToken *token;
token = script->token = Jim_Alloc(sizeof(ScriptToken) * tokenlist->count);
for (i = 0; i < tokenlist->count; i++) {
const ParseToken *t = &tokenlist->list[i];
/* Create a token for 't' */
token->type = t->type;
token->objPtr = JimMakeScriptObj(interp, t);
Jim_IncrRefCount(token->objPtr);
token++;
}
script->len = i;
}
/* This method takes the string representation of an object
* as a Tcl script, and generates the pre-parsed internal representation
* of the script.
*
* On parse error, sets an error message and returns JIM_ERR
* (Note: the object is still converted to a script, even if an error occurs)
*/
static void JimSetScriptFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr)
{
int scriptTextLen;
const char *scriptText = Jim_GetString(objPtr, &scriptTextLen);
struct JimParserCtx parser;
struct ScriptObj *script;
ParseTokenList tokenlist;
int line = 1;
/* Try to get information about filename / line number */
if (objPtr->typePtr == &sourceObjType) {
line = objPtr->internalRep.sourceValue.lineNumber;
}
/* Initially parse the script into tokens (in tokenlist) */
ScriptTokenListInit(&tokenlist);
JimParserInit(&parser, scriptText, scriptTextLen, line);
while (!parser.eof) {
JimParseScript(&parser);
ScriptAddToken(&tokenlist, parser.tstart, parser.tend - parser.tstart + 1, parser.tt,
parser.tline);
}
/* Add a final EOF token */
ScriptAddToken(&tokenlist, scriptText + scriptTextLen, 0, JIM_TT_EOF, 0);
/* Create the "real" script tokens from the parsed tokens */
script = Jim_Alloc(sizeof(*script));
memset(script, 0, sizeof(*script));
script->inUse = 1;
if (objPtr->typePtr == &sourceObjType) {
script->fileNameObj = objPtr->internalRep.sourceValue.fileNameObj;
}
else {
script->fileNameObj = interp->emptyObj;
}
Jim_IncrRefCount(script->fileNameObj);
script->missing = parser.missing.ch;
script->linenr = parser.missing.line;
ScriptObjAddTokens(interp, script, &tokenlist);
/* No longer need the token list */
ScriptTokenListFree(&tokenlist);
/* Free the old internal rep and set the new one. */
Jim_FreeIntRep(interp, objPtr);
Jim_SetIntRepPtr(objPtr, script);
objPtr->typePtr = &scriptObjType;
}
static void JimAddErrorToStack(Jim_Interp *interp, ScriptObj *script);
/**
* Returns the parsed script.
* Note that if there is any possibility that the script is not valid,
* call JimScriptValid() to check
*/
static ScriptObj *JimGetScript(Jim_Interp *interp, Jim_Obj *objPtr)
{
if (objPtr == interp->emptyObj) {
/* Avoid converting emptyObj to a script. use nullScriptObj instead. */
objPtr = interp->nullScriptObj;
}
if (objPtr->typePtr != &scriptObjType || ((struct ScriptObj *)Jim_GetIntRepPtr(objPtr))->substFlags) {
JimSetScriptFromAny(interp, objPtr);
}
return (ScriptObj *)Jim_GetIntRepPtr(objPtr);
}
/**
* Returns 1 if the script is valid (parsed ok), otherwise returns 0
* and leaves an error message in the interp result.
*
*/
static int JimScriptValid(Jim_Interp *interp, ScriptObj *script)
{
if (JimParseCheckMissing(interp, script->missing) == JIM_ERR) {
JimAddErrorToStack(interp, script);
return 0;
}
return 1;
}
/* -----------------------------------------------------------------------------
* Commands
* ---------------------------------------------------------------------------*/
static void JimIncrCmdRefCount(Jim_Cmd *cmdPtr)
{
cmdPtr->inUse++;
}
static void JimDecrCmdRefCount(Jim_Interp *interp, Jim_Cmd *cmdPtr)
{
if (--cmdPtr->inUse == 0) {
if (cmdPtr->isproc) {
Jim_DecrRefCount(interp, cmdPtr->u.proc.argListObjPtr);
Jim_DecrRefCount(interp, cmdPtr->u.proc.bodyObjPtr);
Jim_DecrRefCount(interp, cmdPtr->u.proc.nsObj);
if (cmdPtr->u.proc.staticVars) {
Jim_FreeHashTable(cmdPtr->u.proc.staticVars);
Jim_Free(cmdPtr->u.proc.staticVars);
}
}
else {
/* native (C) */
if (cmdPtr->u.native.delProc) {
cmdPtr->u.native.delProc(interp, cmdPtr->u.native.privData);
}
}
if (cmdPtr->prevCmd) {
/* Delete any pushed command too */
JimDecrCmdRefCount(interp, cmdPtr->prevCmd);
}
Jim_Free(cmdPtr);
}
}
/* Variables HashTable Type.
*
* Keys are dynamically allocated strings, Values are Jim_Var structures.
*/
static void JimVariablesHTValDestructor(void *interp, void *val)
{
Jim_DecrRefCount(interp, ((Jim_Var *)val)->objPtr);
Jim_Free(val);
}
static const Jim_HashTableType JimVariablesHashTableType = {
JimStringCopyHTHashFunction, /* hash function */
JimStringCopyHTDup, /* key dup */
NULL, /* val dup */
JimStringCopyHTKeyCompare, /* key compare */
JimStringCopyHTKeyDestructor, /* key destructor */
JimVariablesHTValDestructor /* val destructor */
};
/* Commands HashTable Type.
*
* Keys are dynamic allocated strings, Values are Jim_Cmd structures.
*/
static void JimCommandsHT_ValDestructor(void *interp, void *val)
{
JimDecrCmdRefCount(interp, val);
}
static const Jim_HashTableType JimCommandsHashTableType = {
JimStringCopyHTHashFunction, /* hash function */
JimStringCopyHTDup, /* key dup */
NULL, /* val dup */
JimStringCopyHTKeyCompare, /* key compare */
JimStringCopyHTKeyDestructor, /* key destructor */
JimCommandsHT_ValDestructor /* val destructor */
};
/* ------------------------- Commands related functions --------------------- */
#ifdef jim_ext_namespace
/**
* Returns the "unscoped" version of the given namespace.
* That is, the fully qualified name without the leading ::
* The returned value is either nsObj, or an object with a zero ref count.
*/
static Jim_Obj *JimQualifyNameObj(Jim_Interp *interp, Jim_Obj *nsObj)
{
const char *name = Jim_String(nsObj);
if (name[0] == ':' && name[1] == ':') {
/* This command is being defined in the global namespace */
while (*++name == ':') {
}
nsObj = Jim_NewStringObj(interp, name, -1);
}
else if (Jim_Length(interp->framePtr->nsObj)) {
/* This command is being defined in a non-global namespace */
nsObj = Jim_DuplicateObj(interp, interp->framePtr->nsObj);
Jim_AppendStrings(interp, nsObj, "::", name, NULL);
}
return nsObj;
}
/**
* If nameObjPtr starts with "::", returns it.
* Otherwise returns a new object with nameObjPtr prefixed with "::".
* In this case, decrements the ref count of nameObjPtr.
*/
Jim_Obj *Jim_MakeGlobalNamespaceName(Jim_Interp *interp, Jim_Obj *nameObjPtr)
{
Jim_Obj *resultObj;
const char *name = Jim_String(nameObjPtr);
if (name[0] == ':' && name[1] == ':') {
return nameObjPtr;
}
Jim_IncrRefCount(nameObjPtr);
resultObj = Jim_NewStringObj(interp, "::", -1);
Jim_AppendObj(interp, resultObj, nameObjPtr);
Jim_DecrRefCount(interp, nameObjPtr);
return resultObj;
}
/**
* An efficient version of JimQualifyNameObj() where the name is
* available (and needed) as a 'const char *'.
* Avoids creating an object if not necessary.
* The object stored in *objPtrPtr should be disposed of with JimFreeQualifiedName() after use.
*/
static const char *JimQualifyName(Jim_Interp *interp, const char *name, Jim_Obj **objPtrPtr)
{
Jim_Obj *objPtr = interp->emptyObj;
if (name[0] == ':' && name[1] == ':') {
/* This command is being defined in the global namespace */
while (*++name == ':') {
}
}
else if (Jim_Length(interp->framePtr->nsObj)) {
/* This command is being defined in a non-global namespace */
objPtr = Jim_DuplicateObj(interp, interp->framePtr->nsObj);
Jim_AppendStrings(interp, objPtr, "::", name, NULL);
name = Jim_String(objPtr);
}
Jim_IncrRefCount(objPtr);
*objPtrPtr = objPtr;
return name;
}
#define JimFreeQualifiedName(INTERP, OBJ) Jim_DecrRefCount((INTERP), (OBJ))
#else
/* We can be more efficient in the no-namespace case */
#define JimQualifyName(INTERP, NAME, DUMMY) (((NAME)[0] == ':' && (NAME)[1] == ':') ? (NAME) + 2 : (NAME))
#define JimFreeQualifiedName(INTERP, DUMMY) (void)(DUMMY)
Jim_Obj *Jim_MakeGlobalNamespaceName(Jim_Interp *interp, Jim_Obj *nameObjPtr)
{
return nameObjPtr;
}
#endif
static int JimCreateCommand(Jim_Interp *interp, const char *name, Jim_Cmd *cmd)
{
/* It may already exist, so we try to delete the old one.
* Note that reference count means that it won't be deleted yet if
* it exists in the call stack.
*
* BUT, if 'local' is in force, instead of deleting the existing
* proc, we stash a reference to the old proc here.
*/
Jim_HashEntry *he = Jim_FindHashEntry(&interp->commands, name);
if (he) {
/* There was an old cmd with the same name,
* so this requires a 'proc epoch' update. */
/* If a procedure with the same name didn't exist there is no need
* to increment the 'proc epoch' because creation of a new procedure
* can never affect existing cached commands. We don't do
* negative caching. */
Jim_InterpIncrProcEpoch(interp);
}
if (he && interp->local) {
/* Push this command over the top of the previous one */
cmd->prevCmd = Jim_GetHashEntryVal(he);
Jim_SetHashVal(&interp->commands, he, cmd);
}
else {
if (he) {
/* Replace the existing command */
Jim_DeleteHashEntry(&interp->commands, name);
}
Jim_AddHashEntry(&interp->commands, name, cmd);
}
return JIM_OK;
}
int Jim_CreateCommand(Jim_Interp *interp, const char *cmdNameStr,
Jim_CmdProc *cmdProc, void *privData, Jim_DelCmdProc *delProc)
{
Jim_Cmd *cmdPtr = Jim_Alloc(sizeof(*cmdPtr));
/* Store the new details for this command */
memset(cmdPtr, 0, sizeof(*cmdPtr));
cmdPtr->inUse = 1;
cmdPtr->u.native.delProc = delProc;
cmdPtr->u.native.cmdProc = cmdProc;
cmdPtr->u.native.privData = privData;
JimCreateCommand(interp, cmdNameStr, cmdPtr);
return JIM_OK;
}
static int JimCreateProcedureStatics(Jim_Interp *interp, Jim_Cmd *cmdPtr, Jim_Obj *staticsListObjPtr)
{
int len, i;
len = Jim_ListLength(interp, staticsListObjPtr);
if (len == 0) {
return JIM_OK;
}
cmdPtr->u.proc.staticVars = Jim_Alloc(sizeof(Jim_HashTable));
Jim_InitHashTable(cmdPtr->u.proc.staticVars, &JimVariablesHashTableType, interp);
for (i = 0; i < len; i++) {
Jim_Obj *objPtr, *initObjPtr, *nameObjPtr;
Jim_Var *varPtr;
int subLen;
objPtr = Jim_ListGetIndex(interp, staticsListObjPtr, i);
/* Check if it's composed of two elements. */
subLen = Jim_ListLength(interp, objPtr);
if (subLen == 1 || subLen == 2) {
/* Try to get the variable value from the current
* environment. */
nameObjPtr = Jim_ListGetIndex(interp, objPtr, 0);
if (subLen == 1) {
initObjPtr = Jim_GetVariable(interp, nameObjPtr, JIM_NONE);
if (initObjPtr == NULL) {
Jim_SetResultFormatted(interp,
"variable for initialization of static \"%#s\" not found in the local context",
nameObjPtr);
return JIM_ERR;
}
}
else {
initObjPtr = Jim_ListGetIndex(interp, objPtr, 1);
}
if (JimValidName(interp, "static variable", nameObjPtr) != JIM_OK) {
return JIM_ERR;
}
varPtr = Jim_Alloc(sizeof(*varPtr));
varPtr->objPtr = initObjPtr;
Jim_IncrRefCount(initObjPtr);
varPtr->linkFramePtr = NULL;
if (Jim_AddHashEntry(cmdPtr->u.proc.staticVars,
Jim_String(nameObjPtr), varPtr) != JIM_OK) {
Jim_SetResultFormatted(interp,
"static variable name \"%#s\" duplicated in statics list", nameObjPtr);
Jim_DecrRefCount(interp, initObjPtr);
Jim_Free(varPtr);
return JIM_ERR;
}
}
else {
Jim_SetResultFormatted(interp, "too many fields in static specifier \"%#s\"",
objPtr);
return JIM_ERR;
}
}
return JIM_OK;
}
/**
* If the command is a proc, sets/updates the cached namespace (nsObj)
* based on the command name.
*/
static void JimUpdateProcNamespace(Jim_Interp *interp, Jim_Cmd *cmdPtr, const char *cmdname)
{
#ifdef jim_ext_namespace
if (cmdPtr->isproc) {
/* XXX: Really need JimNamespaceSplit() */
const char *pt = strrchr(cmdname, ':');
if (pt && pt != cmdname && pt[-1] == ':') {
Jim_DecrRefCount(interp, cmdPtr->u.proc.nsObj);
cmdPtr->u.proc.