diff options
author | Didier Raboud <odyx@debian.org> | 2019-11-20 20:25:37 +0100 |
---|---|---|
committer | Didier Raboud <odyx@debian.org> | 2019-11-20 20:25:37 +0100 |
commit | 052cee686ea886c16b59dcabb5a04b2e6d390ade (patch) | |
tree | 0f0dc73676b4d94e58c5b0d77e5c5df5192aaf75 /jim.c | |
parent | 352d61e6661d45100eeea2c960b027be01b7fc37 (diff) | |
parent | 0aa0fb4e3a38d38a49de9eb585d93d63a370dcf6 (diff) |
Merge tag 'upstream/0.79' into upstream/latest-repack
Diffstat (limited to 'jim.c')
-rw-r--r-- | jim.c | 2384 |
1 files changed, 1174 insertions, 1210 deletions
@@ -41,7 +41,9 @@ * 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> @@ -222,7 +224,7 @@ first: pattern += utf8_tounicode_case(pattern, &start, nocase); if (pattern[0] == '-' && pattern[1]) { /* skip '-' */ - pattern += utf8_tounicode(pattern, &pchar); + pattern++; pattern += utf8_tounicode_case(pattern, &end, nocase); /* Handle reversed range too */ @@ -366,9 +368,12 @@ static int JimStringCompareLen(const char *s1, const char *s2, int maxchars, int return 0; } -/* Search 's1' inside 's2', starting to search from char 'index' of 's2'. +/* 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. */ + * 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; @@ -393,7 +398,10 @@ static int JimStringFirst(const char *s1, int l1, const char *s2, int l2, int id 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) @@ -414,7 +422,7 @@ static int JimStringLast(const char *s1, int l1, const char *s2, int l2) #ifdef JIM_UTF8 /** - * Note: Lengths and return value are in chars. + * 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) { @@ -450,7 +458,7 @@ static int JimCheckConversion(const char *str, const char *endptr) return JIM_OK; } -/* Parses the front of a number to determine it's sign and base +/* 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) @@ -719,7 +727,13 @@ unsigned int Jim_GenHashFunction(const unsigned char *buf, int len) /* ----------------------------- API implementation ------------------------- */ -/* reset a hashtable already initialized */ +/* + * Reset a hashtable already initialized. + * The table data should already have been freed. + * + * Note that type and privdata are not initialised + * to allow the now-empty hashtable to be reused + */ static void JimResetHashTable(Jim_HashTable *ht) { ht->table = NULL; @@ -754,17 +768,6 @@ int Jim_InitHashTable(Jim_HashTable *ht, const Jim_HashTableType *type, void *pr 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) { @@ -902,7 +905,9 @@ int Jim_DeleteHashEntry(Jim_HashTable *ht, const void *key) return JIM_ERR; /* not found */ } -/* Destroy an entire hash table and leave it ready for reuse */ +/* Remove all entries from the hash table + * and leave it empty for reuse + */ int Jim_FreeHashTable(Jim_HashTable *ht) { unsigned int i; @@ -981,7 +986,7 @@ Jim_HashEntry *Jim_NextHashEntry(Jim_HashTableIterator *iter) static void JimExpandHashTableIfNeeded(Jim_HashTable *ht) { /* If the hash table is empty expand it to the intial size, - * if the table is "full" dobule its size. */ + * if the table is "full" double its size. */ if (ht->size == 0) Jim_ExpandHashTable(ht, JIM_HT_INITIAL_SIZE); if (ht->size == ht->used) @@ -1186,12 +1191,12 @@ void Jim_FreeStackElements(Jim_Stack *stack, void (*freeFunc) (void *ptr)) * Results of missing quotes, braces, etc. from parsing. */ struct JimParseMissing { - int ch; /* At end of parse, ' ' if complete or '{', '[', '"', '\\' , '{' if incomplete */ + 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 both to parse - * Tcl scripts and lists. */ +/* 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 */ @@ -1337,17 +1342,17 @@ static int JimParseEol(struct JimParserCtx *pc) ** Here are the rules for parsing: ** {braced expression} ** - Count open and closing braces -** - Backslash escapes meaning of braces +** - Backslash escapes meaning of braces but doesn't remove the backslash ** ** "quoted expression" -** - First double quote at start of word terminates the expression -** - Backslash escapes quote and bracket +** - 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. quotes have their own rules) +** - command rules apply within [brackets], not quoting rules (i.e. brackets have their own rules) ** ** [command expression] ** - Count open and closing brackets -** - Backslash escapes quote, bracket and brace +** - 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 @@ -1810,8 +1815,8 @@ static int odigitval(int c) /* Perform Tcl escape substitution of 's', storing the result * string into 'dest'. The escaped string is guaranteed to - * be the same length or shorted than the source string. - * Slen is the length of the string at 's'. + * 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) @@ -2002,23 +2007,19 @@ static Jim_Obj *JimParserGetTokenObj(Jim_Interp *interp, struct JimParserCtx *pc start = pc->tstart; end = pc->tend; - if (start > end) { + len = (end - start) + 1; + if (len < 0) { len = 0; - token = Jim_Alloc(1); - token[0] = '\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 { - len = (end - start) + 1; - 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); - } + /* Else convert the escape chars. */ + len = JimEscape(token, start, len); } return Jim_NewStringObjNoAlloc(interp, token, len); @@ -2158,10 +2159,10 @@ Jim_Obj *Jim_NewObj(Jim_Interp *interp) } /* Object is returned with refCount of 0. Every - * kind of GC implemented should take care to don't try - * to scan objects with refCount == 0. */ + * kind of GC implemented should take care to avoid + * scanning objects with refCount == 0. */ objPtr->refCount = 0; - /* All the other fields are left not initialized to save time. + /* All the other fields are left uninitialized to save time. * The caller will probably want to set them to the right * value anyway. */ @@ -2232,7 +2233,9 @@ Jim_Obj *Jim_DuplicateObj(Jim_Interp *interp, Jim_Obj *objPtr) 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 */ + /* 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; @@ -2275,13 +2278,12 @@ const char *Jim_GetString(Jim_Obj *objPtr, int *lenPtr) return objPtr->bytes; } -/* Just returns the length of the object's string rep */ +/* 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); + Jim_GetString(objPtr, NULL); } return objPtr->length; } @@ -2291,9 +2293,7 @@ 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); + Jim_GetString(objPtr, NULL); } return objPtr->bytes; } @@ -2315,19 +2315,30 @@ static const Jim_ObjType dictSubstObjType = { JIM_TYPE_NONE, }; -static void FreeInterpolatedInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) -{ - Jim_DecrRefCount(interp, objPtr->internalRep.dictSubstValue.indexObjPtr); -} +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, - NULL, + 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 * ---------------------------------------------------------------------------*/ @@ -2407,9 +2418,7 @@ Jim_Obj *Jim_NewStringObj(Jim_Interp *interp, const char *s, int len) objPtr->bytes = JimEmptyStringRep; } else { - objPtr->bytes = Jim_Alloc(len + 1); - memcpy(objPtr->bytes, s, len); - objPtr->bytes[len] = '\0'; + objPtr->bytes = Jim_StrDupLen(s, len); } objPtr->length = len; @@ -2732,8 +2741,6 @@ static Jim_Obj *JimStringToLower(Jim_Interp *interp, Jim_Obj *strObjPtr) int len; const char *str; - SetStringFromAny(interp, strObjPtr); - str = Jim_GetString(strObjPtr, &len); #ifdef JIM_UTF8 @@ -2756,10 +2763,6 @@ static Jim_Obj *JimStringToUpper(Jim_Interp *interp, Jim_Obj *strObjPtr) const char *str; int len; - if (strObjPtr->typePtr != &stringObjType) { - SetStringFromAny(interp, strObjPtr); - } - str = Jim_GetString(strObjPtr, &len); #ifdef JIM_UTF8 @@ -2784,9 +2787,7 @@ static Jim_Obj *JimStringToTitle(Jim_Interp *interp, Jim_Obj *strObjPtr) const char *str; str = Jim_GetString(strObjPtr, &len); - if (len == 0) { - return strObjPtr; - } + #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 @@ -3032,7 +3033,7 @@ static int JimStringIs(Jim_Interp *interp, Jim_Obj *strObjPtr, Jim_Obj *strClass } for (i = 0; i < len; i++) { - if (!isclassfunc(str[i])) { + if (!isclassfunc(UCHAR(str[i]))) { Jim_SetResultBool(interp, 0); return JIM_OK; } @@ -3075,9 +3076,7 @@ int Jim_CompareStringImmediate(Jim_Interp *interp, Jim_Obj *objPtr, const char * return 1; } else { - const char *objStr = Jim_String(objPtr); - - if (strcmp(str, objStr) != 0) + if (strcmp(str, Jim_String(objPtr)) != 0) return 0; if (objPtr->typePtr != &comparedStringObjType) { @@ -3399,7 +3398,7 @@ static void ScriptAddToken(ParseTokenList *tokenlist, const char *token, int len * operator (in which case the count doesn't include * that token). */ -static int JimCountWordTokens(ParseToken *t) +static int JimCountWordTokens(struct ScriptObj *script, ParseToken *t) { int expand = 1; int count = 0; @@ -3411,6 +3410,13 @@ static int JimCountWordTokens(ParseToken *t) 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 */ @@ -3497,7 +3503,7 @@ static void ScriptObjAddTokens(Jim_Interp *interp, struct ScriptObj *script, i++; } - wordtokens = JimCountWordTokens(tokenlist->list + i); + wordtokens = JimCountWordTokens(script, tokenlist->list + i); if (wordtokens == 0) { /* None, so at end of line */ @@ -3578,6 +3584,10 @@ static void ScriptObjAddTokens(Jim_Interp *interp, struct ScriptObj *script, * '\\' 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) { @@ -3585,7 +3595,7 @@ int Jim_ScriptIsComplete(Jim_Interp *interp, Jim_Obj *scriptObj, char *stateChar if (stateCharPtr) { *stateCharPtr = script->missing; } - return (script->missing == ' '); + return script->missing == ' ' || script->missing == '}'; } /** @@ -3610,6 +3620,9 @@ static int JimParseCheckMissing(Jim_Interp *interp, int ch) case '{': msg = "missing close-brace"; break; + case '}': + msg = "extra characters after close-brace"; + break; case '"': default: msg = "missing quote"; @@ -3777,10 +3790,6 @@ static void JimDecrCmdRefCount(Jim_Interp *interp, Jim_Cmd *cmdPtr) * * Keys are dynamically allocated strings, Values are Jim_Var structures. */ - -/* Variables HashTable Type. - * - * Keys are dynamic allocated strings, Values are Jim_Var structures. */ static void JimVariablesHTValDestructor(void *interp, void *val) { Jim_DecrRefCount(interp, ((Jim_Var *)val)->objPtr); @@ -3839,6 +3848,11 @@ static Jim_Obj *JimQualifyNameObj(Jim_Interp *interp, Jim_Obj *nsObj) 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; @@ -4010,6 +4024,10 @@ static int JimCreateProcedureStatics(Jim_Interp *interp, Jim_Cmd *cmdPtr, Jim_Ob 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 @@ -4022,7 +4040,7 @@ static void JimUpdateProcNamespace(Jim_Interp *interp, Jim_Cmd *cmdPtr, const ch Jim_IncrRefCount(cmdPtr->u.proc.nsObj); if (Jim_FindHashEntry(&interp->commands, pt + 1)) { - /* This commands shadows a global command, so a proc epoch update is required */ + /* This command shadows a global command, so a proc epoch update is required */ Jim_InterpIncrProcEpoch(interp); } } @@ -4207,12 +4225,12 @@ static const Jim_ObjType commandObjType = { }; /* This function returns the command structure for the command name - * stored in objPtr. It tries to specialize the objPtr to contain - * a cached info instead to perform the lookup into the hash table - * every time. The information cached may not be uptodate, in such - * a case the lookup is performed and the cache updated. + * stored in objPtr. It specializes the objPtr to contain + * cached info instead of performing the lookup into the hash table + * every time. The information cached may not be up-to-date, in this + * case the lookup is performed and the cache updated. * - * Respects the 'upcall' setting + * Respects the 'upcall' setting. */ Jim_Cmd *Jim_GetCommand(Jim_Interp *interp, Jim_Obj *objPtr, int flags) { @@ -4263,7 +4281,7 @@ found: #endif cmd = Jim_GetHashEntryVal(he); - /* Free the old internal repr and set the new one. */ + /* Free the old internal rep and set the new one. */ Jim_FreeIntRep(interp, objPtr); objPtr->typePtr = &commandObjType; objPtr->internalRep.cmdValue.procEpoch = interp->procEpoch; @@ -4437,6 +4455,9 @@ static Jim_Var *JimCreateVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, Jim_O * All the caching should also have an 'epoch' mechanism similar * to the one used by Tcl for procedures lookup caching. */ +/** + * Set the variable nameObjPtr to value valObjptr. + */ int Jim_SetVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, Jim_Obj *valObjPtr) { int err; @@ -4500,15 +4521,12 @@ int Jim_SetGlobalVariableStr(Jim_Interp *interp, const char *name, Jim_Obj *objP int Jim_SetVariableStrWithStr(Jim_Interp *interp, const char *name, const char *val) { - Jim_Obj *nameObjPtr, *valObjPtr; + Jim_Obj *valObjPtr; int result; - nameObjPtr = Jim_NewStringObj(interp, name, -1); valObjPtr = Jim_NewStringObj(interp, val, -1); - Jim_IncrRefCount(nameObjPtr); Jim_IncrRefCount(valObjPtr); - result = Jim_SetVariable(interp, nameObjPtr, valObjPtr); - Jim_DecrRefCount(interp, nameObjPtr); + result = Jim_SetVariableStr(interp, name, valObjPtr); Jim_DecrRefCount(interp, valObjPtr); return result; } @@ -4684,8 +4702,9 @@ Jim_Obj *Jim_GetGlobalVariableStr(Jim_Interp *interp, const char *name, int flag } /* Unset a variable. - * Note: On success unset invalidates all the variable objects created - * in the current call frame incrementing. */ + * Note: On success unset invalidates all the (cached) variable objects + * by incrementing callFrameEpoch + */ int Jim_UnsetVariable(Jim_Interp *interp, Jim_Obj *nameObjPtr, int flags) { Jim_Var *varPtr; @@ -4849,14 +4868,13 @@ void FreeDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) Jim_DecrRefCount(interp, objPtr->internalRep.dictSubstValue.indexObjPtr); } -void DupDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) +static void DupDictSubstInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dupPtr) { - JIM_NOTUSED(interp); - - dupPtr->internalRep.dictSubstValue.varNameObjPtr = - srcPtr->internalRep.dictSubstValue.varNameObjPtr; - dupPtr->internalRep.dictSubstValue.indexObjPtr = srcPtr->internalRep.dictSubstValue.indexObjPtr; - dupPtr->typePtr = &dictSubstObjType; + /* Copy the internal rep */ + dupPtr->internalRep = srcPtr->internalRep; + /* Need to increment the ref counts */ + Jim_IncrRefCount(dupPtr->internalRep.dictSubstValue.varNameObjPtr); + Jim_IncrRefCount(dupPtr->internalRep.dictSubstValue.indexObjPtr); } /* Note: The object *must* be in dict-sugar format */ @@ -4912,18 +4930,6 @@ static Jim_Obj *JimExpandDictSugar(Jim_Interp *interp, Jim_Obj *objPtr) return resObjPtr; } -static Jim_Obj *JimExpandExprSugar(Jim_Interp *interp, Jim_Obj *objPtr) -{ - Jim_Obj *resultObjPtr; - - if (Jim_EvalExpression(interp, objPtr, &resultObjPtr) == JIM_OK) { - /* Note that the result has a ref count of 1, but we need a ref count of 0 */ - resultObjPtr->refCount--; - return resultObjPtr; - } - return NULL; -} - /* ----------------------------------------------------------------------------- * CallFrame * ---------------------------------------------------------------------------*/ @@ -4991,8 +4997,8 @@ static int JimDeleteLocalProcs(Jim_Interp *interp, Jim_Stack *localCommands) } else { Jim_DeleteHashEntry(ht, fqname); - Jim_InterpIncrProcEpoch(interp); } + Jim_InterpIncrProcEpoch(interp); } Jim_DecrRefCount(interp, cmdNameObj); JimFreeQualifiedName(interp, fqObjName); @@ -5003,6 +5009,62 @@ static int JimDeleteLocalProcs(Jim_Interp *interp, Jim_Stack *localCommands) return JIM_OK; } +/** + * Run any $jim::defer scripts for the current call frame. + * + * retcode is the return code from the current proc. + * + * Returns the new return code. + */ +static int JimInvokeDefer(Jim_Interp *interp, int retcode) +{ + Jim_Obj *objPtr; + + /* Fast check for the likely case that the variable doesn't exist */ + if (Jim_FindHashEntry(&interp->framePtr->vars, "jim::defer") == NULL) { + return retcode; + } + + objPtr = Jim_GetVariableStr(interp, "jim::defer", JIM_NONE); + + if (objPtr) { + int ret = JIM_OK; + int i; + int listLen = Jim_ListLength(interp, objPtr); + Jim_Obj *resultObjPtr; + + Jim_IncrRefCount(objPtr); + + /* Need to save away the current interp result and + * restore it if appropriate + */ + resultObjPtr = Jim_GetResult(interp); + Jim_IncrRefCount(resultObjPtr); + Jim_SetEmptyResult(interp); + + /* Invoke in reverse order */ + for (i = listLen; i > 0; i--) { + /* If a defer script returns an error, don't evaluate remaining scripts */ + Jim_Obj *scriptObjPtr = Jim_ListGetIndex(interp, objPtr, i - 1); + ret = Jim_EvalObj(interp, scriptObjPtr); + if (ret != JIM_OK) { + break; + } + } + + if (ret == JIM_OK || retcode == JIM_ERR) { + /* defer script had no error, or proc had an error so restore proc result */ + Jim_SetResult(interp, resultObjPtr); + } + else { + retcode = ret; + } + + Jim_DecrRefCount(interp, resultObjPtr); + Jim_DecrRefCount(interp, objPtr); + } + return retcode; +} #define JIM_FCF_FULL 0 /* Always free the vars hash table */ #define JIM_FCF_REUSE 1 /* Reuse the vars hash table if possible */ @@ -5308,11 +5370,11 @@ int Jim_Collect(Jim_Interp *interp) Jim_Obj *objPtr; /* Avoid recursive calls */ - if (interp->lastCollectId == -1) { + if (interp->lastCollectId == (unsigned long)~0) { /* Jim_Collect() already running. Return just now. */ return 0; } - interp->lastCollectId = -1; + interp->lastCollectId = ~0; /* Mark all the references found into the 'mark' hash table. * The references are searched in every live object that @@ -5523,6 +5585,8 @@ void Jim_FreeInterp(Jim_Interp *i) /* Free the active call frames list - must be done before i->commands is destroyed */ for (cf = i->framePtr; cf; cf = cfx) { + /* Note that we ignore any errors */ + JimInvokeDefer(i, JIM_OK); cfx = cf->parent; JimFreeCallFrame(i, cf, JIM_FCF_FULL); } @@ -5555,6 +5619,7 @@ void Jim_FreeInterp(Jim_Interp *i) printf("Objects still in the free list:\n"); while (objPtr) { const char *type = objPtr->typePtr ? objPtr->typePtr->name : "string"; + Jim_String(objPtr); if (objPtr->bytes && strlen(objPtr->bytes) > 20) { printf("%p (%d) %-10s: '%.20s...'\n", @@ -6011,11 +6076,6 @@ static int SetDoubleFromAny(Jim_Interp *interp, Jim_Obj *objPtr) jim_wide wideValue; const char *str; - /* Preserve the string representation. - * Needed so we can convert back to int without loss - */ - str = Jim_String(objPtr); - #ifdef HAVE_LONG_LONG /* Assume a 53 bit mantissa */ #define MIN_INT_IN_DOUBLE -(1LL << 53) @@ -6029,8 +6089,12 @@ static int SetDoubleFromAny(Jim_Interp *interp, Jim_Obj *objPtr) objPtr->typePtr = &coercedDoubleObjType; return JIM_OK; } - else #endif + /* Preserve the string representation. + * Needed so we can convert back to int without loss + */ + str = Jim_String(objPtr); + if (Jim_StringToWide(str, &wideValue, 10) == JIM_OK) { /* Managed to convert to an int, so we can use this as a cooerced double */ Jim_FreeIntRep(interp, objPtr); @@ -6657,8 +6721,12 @@ static void ListRemoveDuplicates(Jim_Obj *listObjPtr, int (*comp)(Jim_Obj **lhs, } ele[dst] = ele[src]; } - /* At end of list, keep the final element */ - ele[++dst] = ele[src]; + + /* At end of list, keep the final element unless all elements were kept */ + dst++; + if (dst < listObjPtr->internalRep.listValue.len) { + ele[dst] = ele[src]; + } /* Set the new length */ listObjPtr->internalRep.listValue.len = dst; @@ -7247,8 +7315,10 @@ int Jim_DictKey(Jim_Interp *interp, Jim_Obj *dictPtr, Jim_Obj *keyPtr, } return JIM_ERR; } - *objPtrPtr = he->u.val; - return JIM_OK; + else { + *objPtrPtr = Jim_GetHashEntryVal(he); + return JIM_OK; + } } /* Return an allocated array of key/value pairs for the dictionary. Stores the length in *len */ @@ -7509,7 +7579,7 @@ static const char * const jimReturnCodes[] = { NULL }; -#define jimReturnCodesSize (sizeof(jimReturnCodes)/sizeof(*jimReturnCodes)) +#define jimReturnCodesSize (sizeof(jimReturnCodes)/sizeof(*jimReturnCodes) - 1) static const Jim_ObjType returnCodeObjType = { "return-code", @@ -7567,13 +7637,12 @@ static int JimParseExprNumber(struct JimParserCtx *pc); static int JimParseExprIrrational(struct JimParserCtx *pc); static int JimParseExprBoolean(struct JimParserCtx *pc); -/* Exrp's Stack machine operators opcodes. */ - -/* Binary operators (numbers) */ +/* expr operator opcodes. */ enum { /* Continues on from the JIM_TT_ space */ - /* Operations */ + + /* Binary operators (numbers) */ JIM_EXPROP_MUL = JIM_TT_EXPR_OP, /* 20 */ JIM_EXPROP_DIV, JIM_EXPROP_MOD, @@ -7592,45 +7661,26 @@ enum JIM_EXPROP_BITAND, /* 35 */ JIM_EXPROP_BITXOR, JIM_EXPROP_BITOR, - - /* Note must keep these together */ JIM_EXPROP_LOGICAND, /* 38 */ - JIM_EXPROP_LOGICAND_LEFT, - JIM_EXPROP_LOGICAND_RIGHT, - - /* and these */ - JIM_EXPROP_LOGICOR, /* 41 */ - JIM_EXPROP_LOGICOR_LEFT, - JIM_EXPROP_LOGICOR_RIGHT, - - /* and these */ - /* Ternary operators */ - JIM_EXPROP_TERNARY, /* 44 */ - JIM_EXPROP_TERNARY_LEFT, - JIM_EXPROP_TERNARY_RIGHT, + JIM_EXPROP_LOGICOR, /* 39 */ + JIM_EXPROP_TERNARY, /* 40 */ + JIM_EXPROP_COLON, /* 41 */ + JIM_EXPROP_POW, /* 42 */ - /* and these */ - JIM_EXPROP_COLON, /* 47 */ - JIM_EXPROP_COLON_LEFT, - JIM_EXPROP_COLON_RIGHT, - - JIM_EXPROP_POW, /* 50 */ - -/* Binary operators (strings) */ - JIM_EXPROP_STREQ, /* 51 */ + /* Binary operators (strings) */ + JIM_EXPROP_STREQ, /* 43 */ JIM_EXPROP_STRNE, JIM_EXPROP_STRIN, JIM_EXPROP_STRNI, -/* Unary operators (numbers) */ - JIM_EXPROP_NOT, /* 55 */ + /* Unary operators (numbers) */ + JIM_EXPROP_NOT, /* 47 */ JIM_EXPROP_BITNOT, JIM_EXPROP_UNARYMINUS, JIM_EXPROP_UNARYPLUS, /* Functions */ - JIM_EXPROP_FUNC_FIRST, /* 59 */ - JIM_EXPROP_FUNC_INT = JIM_EXPROP_FUNC_FIRST, + JIM_EXPROP_FUNC_INT, /* 51 */ JIM_EXPROP_FUNC_WIDE, JIM_EXPROP_FUNC_ABS, JIM_EXPROP_FUNC_DOUBLE, @@ -7660,46 +7710,47 @@ enum JIM_EXPROP_FUNC_FMOD, }; -struct JimExprState -{ - Jim_Obj **stack; - int stacklen; - int opcode; - int skip; +/* A expression node is either a term or an operator + * If a node is an operator, 'op' points to the details of the operator and it's terms. + */ +struct JimExprNode { + int type; /* JIM_TT_xxx */ + struct Jim_Obj *objPtr; /* The object for a term, or NULL for an operator */ + + struct JimExprNode *left; /* For all operators */ + struct JimExprNode *right; /* For binary operators */ + struct JimExprNode *ternary; /* For ternary operator only */ }; /* Operators table */ typedef struct Jim_ExprOperator { const char *name; - int (*funcop) (Jim_Interp *interp, struct JimExprState * e); + int (*funcop) (Jim_Interp *interp, struct JimExprNode *opnode); unsigned char precedence; unsigned char arity; - unsigned char lazy; + unsigned char attr; unsigned char namelen; } Jim_ExprOperator; -static void ExprPush(struct JimExprState *e, Jim_Obj *obj) -{ - Jim_IncrRefCount(obj); - e->stack[e->stacklen++] = obj; -} - -static Jim_Obj *ExprPop(struct JimExprState *e) -{ - return e->stack[--e->stacklen]; -} +static int JimExprGetTerm(Jim_Interp *interp, struct JimExprNode *node, Jim_Obj **objPtrPtr); +static int JimExprGetTermBoolean(Jim_Interp *interp, struct JimExprNode *node); +static int JimExprEvalTermNode(Jim_Interp *interp, struct JimExprNode *node); -static int JimExprOpNumUnary(Jim_Interp *interp, struct JimExprState *e) +static int JimExprOpNumUnary(Jim_Interp *interp, struct JimExprNode *node) { int intresult = 1; - int rc = JIM_OK; - Jim_Obj *A = ExprPop(e); + int rc; double dA, dC = 0; jim_wide wA, wC = 0; + Jim_Obj *A; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } if ((A->typePtr != &doubleObjType || A->bytes) && JimGetWideNoErr(interp, A, &wA) == JIM_OK) { - switch (e->opcode) { + switch (node->type) { case JIM_EXPROP_FUNC_INT: case JIM_EXPROP_FUNC_WIDE: case JIM_EXPROP_FUNC_ROUND: @@ -7724,7 +7775,7 @@ static int JimExprOpNumUnary(Jim_Interp *interp, struct JimExprState *e) } } else if ((rc = Jim_GetDouble(interp, A, &dA)) == JIM_OK) { - switch (e->opcode) { + switch (node->type) { case JIM_EXPROP_FUNC_INT: case JIM_EXPROP_FUNC_WIDE: wC = dA; @@ -7759,10 +7810,10 @@ static int JimExprOpNumUnary(Jim_Interp *interp, struct JimExprState *e) if (rc == JIM_OK) { if (intresult) { - ExprPush(e, Jim_NewIntObj(interp, wC)); + Jim_SetResultInt(interp, wC); } else { - ExprPush(e, Jim_NewDoubleObj(interp, dC)); + Jim_SetResult(interp, Jim_NewDoubleObj(interp, dC)); } } @@ -7779,20 +7830,25 @@ static double JimRandDouble(Jim_Interp *interp) return (double)x / (unsigned long)~0; } -static int JimExprOpIntUnary(Jim_Interp *interp, struct JimExprState *e) +static int JimExprOpIntUnary(Jim_Interp *interp, struct JimExprNode *node) { - Jim_Obj *A = ExprPop(e); jim_wide wA; + Jim_Obj *A; + int rc; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } - int rc = Jim_GetWide(interp, A, &wA); + rc = Jim_GetWide(interp, A, &wA); if (rc == JIM_OK) { - switch (e->opcode) { + switch (node->type) { case JIM_EXPROP_BITNOT: - ExprPush(e, Jim_NewIntObj(interp, ~wA)); + Jim_SetResultInt(interp, ~wA); break; case JIM_EXPROP_FUNC_SRAND: JimPrngSeed(interp, (unsigned char *)&wA, sizeof(wA)); - ExprPush(e, Jim_NewDoubleObj(interp, JimRandDouble(interp))); + Jim_SetResult(interp, Jim_NewDoubleObj(interp, JimRandDouble(interp))); break; default: abort(); @@ -7804,25 +7860,29 @@ static int JimExprOpIntUnary(Jim_Interp *interp, struct JimExprState *e) return rc; } -static int JimExprOpNone(Jim_Interp *interp, struct JimExprState *e) +static int JimExprOpNone(Jim_Interp *interp, struct JimExprNode *node) { - JimPanic((e->opcode != JIM_EXPROP_FUNC_RAND, "JimExprOpNone only support rand()")); + JimPanic((node->type != JIM_EXPROP_FUNC_RAND, "JimExprOpNone only support rand()")); - ExprPush(e, Jim_NewDoubleObj(interp, JimRandDouble(interp))); + Jim_SetResult(interp, Jim_NewDoubleObj(interp, JimRandDouble(interp))); return JIM_OK; } #ifdef JIM_MATH_FUNCTIONS -static int JimExprOpDoubleUnary(Jim_Interp *interp, struct JimExprState *e) +static int JimExprOpDoubleUnary(Jim_Interp *interp, struct JimExprNode *node) { int rc; - Jim_Obj *A = ExprPop(e); double dA, dC; + Jim_Obj *A; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } rc = Jim_GetDouble(interp, A, &dA); if (rc == JIM_OK) { - switch (e->opcode) { + switch (node->type) { case JIM_EXPROP_FUNC_SIN: dC = sin(dA); break; @@ -7871,7 +7931,7 @@ static int JimExprOpDoubleUnary(Jim_Interp *interp, struct JimExprState *e) default: abort(); } - ExprPush(e, Jim_NewDoubleObj(interp, dC)); + Jim_SetResult(interp, Jim_NewDoubleObj(interp, dC)); } Jim_DecrRefCount(interp, A); @@ -7881,19 +7941,28 @@ static int JimExprOpDoubleUnary(Jim_Interp *interp, struct JimExprState *e) #endif /* A binary operation on two ints */ -static int JimExprOpIntBin(Jim_Interp *interp, struct JimExprState *e) +static int JimExprOpIntBin(Jim_Interp *interp, struct JimExprNode *node) { - Jim_Obj *B = ExprPop(e); - Jim_Obj *A = ExprPop(e); jim_wide wA, wB; - int rc = JIM_ERR; + int rc; + Jim_Obj *A, *B; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } + if ((rc = JimExprGetTerm(interp, node->right, &B)) != JIM_OK) { + Jim_DecrRefCount(interp, A); + return rc; + } + + rc = JIM_ERR; if (Jim_GetWide(interp, A, &wA) == JIM_OK && Jim_GetWide(interp, B, &wB) == JIM_OK) { jim_wide wC; rc = JIM_OK; - switch (e->opcode) { + switch (node->type) { case JIM_EXPROP_LSHIFT: wC = wA << wB; break; @@ -7950,7 +8019,7 @@ static int JimExprOpIntBin(Jim_Interp *interp, struct JimExprState *e) /* Shift left by the word size or more is undefined. */ uB %= S; - if (e->opcode == JIM_EXPROP_ROTR) { + if (node->type == JIM_EXPROP_ROTR) { uB = S - uB; } wC = (unsigned long)(uA << uB) | (uA >> (S - uB)); @@ -7959,8 +8028,7 @@ static int JimExprOpIntBin(Jim_Interp *interp, struct JimExprState *e) default: abort(); } - ExprPush(e, Jim_NewIntObj(interp, wC)); - + Jim_SetResultInt(interp, wC); } Jim_DecrRefCount(interp, A); @@ -7971,14 +8039,20 @@ static int JimExprOpIntBin(Jim_Interp *interp, struct JimExprState *e) /* A binary operation on two ints or two doubles (or two strings for some ops) */ -static int JimExprOpBin(Jim_Interp *interp, struct JimExprState *e) +static int JimExprOpBin(Jim_Interp *interp, struct JimExprNode *node) { int rc = JIM_OK; double dA, dB, dC = 0; jim_wide wA, wB, wC = 0; + Jim_Obj *A, *B; - Jim_Obj *B = ExprPop(e); - Jim_Obj *A = ExprPop(e); + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } + if ((rc = JimExprGetTerm(interp, node->right, &B)) != JIM_OK) { + Jim_DecrRefCount(interp, A); + return rc; + } if ((A->typePtr != &doubleObjType || A->bytes) && (B->typePtr != &doubleObjType || B->bytes) && @@ -7986,7 +8060,7 @@ static int JimExprOpBin(Jim_Interp *interp, struct JimExprState *e) /* Both are ints */ - switch (e->opcode) { + switch (node->type) { case JIM_EXPROP_POW: case JIM_EXPROP_FUNC_POW: if (wA == 0 && wB < 0) { @@ -8051,7 +8125,7 @@ static int JimExprOpBin(Jim_Interp *interp, struct JimExprState *e) } } if (Jim_GetDouble(interp, A, &dA) == JIM_OK && Jim_GetDouble(interp, B, &dB) == JIM_OK) { - switch (e->opcode) { + switch (node->type) { #ifndef JIM_MATH_FUNCTIONS case JIM_EXPROP_POW: case JIM_EXPROP_FUNC_POW: @@ -8123,7 +8197,7 @@ static int JimExprOpBin(Jim_Interp *interp, struct JimExprState *e) /* XXX: Could optimise the eq/ne case by checking lengths */ int i = Jim_StringCompareObj(interp, A, B, 0); - switch (e->opcode) { + switch (node->type) { case JIM_EXPROP_LT: wC = i < 0; goto intresult; @@ -8151,10 +8225,10 @@ done: Jim_DecrRefCount(interp, B); return rc; intresult: - ExprPush(e, Jim_NewIntObj(interp, wC)); + Jim_SetResultInt(interp, wC); goto done; doubleresult: - ExprPush(e, Jim_NewDoubleObj(interp, dC)); + Jim_SetResult(interp, Jim_NewDoubleObj(interp, dC)); goto done; } @@ -8172,18 +8246,27 @@ static int JimSearchList(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_Obj *valOb return 0; } -static int JimExprOpStrBin(Jim_Interp *interp, struct JimExprState *e) -{ - Jim_Obj *B = ExprPop(e); - Jim_Obj *A = ExprPop(e); + +static int JimExprOpStrBin(Jim_Interp *interp, struct JimExprNode *node) +{ + Jim_Obj *A, *B; jim_wide wC; + int rc; + + if ((rc = JimExprGetTerm(interp, node->left, &A)) != JIM_OK) { + return rc; + } + if ((rc = JimExprGetTerm(interp, node->right, &B)) != JIM_OK) { + Jim_DecrRefCount(interp, A); + return rc; + } - switch (e->opcode) { + switch (node->type) { case JIM_EXPROP_STREQ: case JIM_EXPROP_STRNE: wC = Jim_StringEqObj(A, B); - if (e->opcode == JIM_EXPROP_STRNE) { + if (node->type == JIM_EXPROP_STRNE) { wC = !wC; } break; @@ -8196,12 +8279,12 @@ static int JimExprOpStrBin(Jim_Interp *interp, struct JimExprState *e) default: abort(); } - ExprPush(e, Jim_NewIntObj(interp, wC)); + Jim_SetResultInt(interp, wC); Jim_DecrRefCount(interp, A); Jim_DecrRefCount(interp, B); - return JIM_OK; + return rc; } static int ExprBool(Jim_Interp *interp, Jim_Obj *obj) @@ -8209,162 +8292,78 @@ static int ExprBool(Jim_Interp *interp, Jim_Obj *obj) long l; double d; int b; + int ret = -1; + + /* In case the object is interp->result with refcount 1*/ + Jim_IncrRefCount(obj); if (Jim_GetLong(interp, obj, &l) == JIM_OK) { - return l != 0; - } - if (Jim_GetDouble(interp, obj, &d) == JIM_OK) { - return d != 0; + ret = (l != 0); } - if (Jim_GetBoolean(interp, obj, &b) == JIM_OK) { - return b != 0; + else if (Jim_GetDouble(interp, obj, &d) == JIM_OK) { + ret = (d != 0); } - return -1; -} - -static int JimExprOpAndLeft(Jim_Interp *interp, struct JimExprState *e) -{ - Jim_Obj *skip = ExprPop(e); - Jim_Obj *A = ExprPop(e); - int rc = JIM_OK; - - switch (ExprBool(interp, A)) { - case 0: - /* false, so skip RHS opcodes with a 0 result */ - e->skip = JimWideValue(skip); - ExprPush(e, Jim_NewIntObj(interp, 0)); - break; - - case 1: - /* true so continue */ - break; - - case -1: - /* Invalid */ - rc = JIM_ERR; + else if (Jim_GetBoolean(interp, obj, &b) == JIM_OK) { + ret = (b != 0); } - Jim_DecrRefCount(interp, A); - Jim_DecrRefCount(interp, skip); - return rc; + Jim_DecrRefCount(interp, obj); + return ret; } -static int JimExprOpOrLeft(Jim_Interp *interp, struct JimExprState *e) +static int JimExprOpAnd(Jim_Interp *interp, struct JimExprNode *node) { - Jim_Obj *skip = ExprPop(e); - Jim_Obj *A = ExprPop(e); - int rc = JIM_OK; - - switch (ExprBool(interp, A)) { - case 0: - /* false, so do nothing */ - break; + /* evaluate left */ + int result = JimExprGetTermBoolean(interp, node->left); - case 1: - /* true so skip RHS opcodes with a 1 result */ - e->skip = JimWideValue(skip); - ExprPush(e, Jim_NewIntObj(interp, 1)); - break; - - case -1: - /* Invalid */ - rc = JIM_ERR; - break; + if (result == 1) { + /* true so evaluate right */ + result = JimExprGetTermBoolean(interp, node->right); } - Jim_DecrRefCount(interp, A); - Jim_DecrRefCount(interp, skip); - - return rc; -} - -static int JimExprOpAndOrRight(Jim_Interp *interp, struct JimExprState *e) -{ - Jim_Obj *A = ExprPop(e); - int rc = JIM_OK; - - switch (ExprBool(interp, A)) { - case 0: - ExprPush(e, Jim_NewIntObj(interp, 0)); - break; - - case 1: - ExprPush(e, Jim_NewIntObj(interp, 1)); - break; - - case -1: - /* Invalid */ - rc = JIM_ERR; - break; + if (result == -1) { + return JIM_ERR; } - Jim_DecrRefCount(interp, A); - - return rc; + Jim_SetResultInt(interp, result); + return JIM_OK; } -static int JimExprOpTernaryLeft(Jim_Interp *interp, struct JimExprState *e) +static int JimExprOpOr(Jim_Interp *interp, struct JimExprNode *node) { - Jim_Obj *skip = ExprPop(e); - Jim_Obj *A = ExprPop(e); - int rc = JIM_OK; - - /* Repush A */ - ExprPush(e, A); - - switch (ExprBool(interp, A)) { - case 0: - /* false, skip RHS opcodes */ - e->skip = JimWideValue(skip); - /* Push a dummy value */ - ExprPush(e, Jim_NewIntObj(interp, 0)); - break; + /* evaluate left */ + int result = JimExprGetTermBoolean(interp, node->left); - case 1: - /* true so do nothing */ - break; - - case -1: - /* Invalid */ - rc = JIM_ERR; - break; + if (result == 0) { + /* false so evaluate right */ + result = JimExprGetTermBoolean(interp, node->right); } - Jim_DecrRefCount(interp, A); - Jim_DecrRefCount(interp, skip); - - return rc; -} - -static int JimExprOpColonLeft(Jim_Interp *interp, struct JimExprState *e) -{ - Jim_Obj *skip = ExprPop(e); - Jim_Obj *B = ExprPop(e); - Jim_Obj *A = ExprPop(e); - - /* No need to check for A as non-boolean */ - if (ExprBool(interp, A)) { - /* true, so skip RHS opcodes */ - e->skip = JimWideValue(skip); - /* Repush B as the answer */ - ExprPush(e, B); + if (result == -1) { + return JIM_ERR; } - - Jim_DecrRefCount(interp, skip); - Jim_DecrRefCount(interp, A); - Jim_DecrRefCount(interp, B); + Jim_SetResultInt(interp, result); return JIM_OK; } -static int JimExprOpNull(Jim_Interp *interp, struct JimExprState *e) +static int JimExprOpTernary(Jim_Interp *interp, struct JimExprNode *node) { - return JIM_OK; + /* evaluate left */ + int result = JimExprGetTermBoolean(interp, node->left); + + if (result == 1) { + /* true so select right */ + return JimExprEvalTermNode(interp, node->right); + } + else if (result == 0) { + /* false so select ternary */ + return JimExprEvalTermNode(interp, node->ternary); + } + /* error */ + return JIM_ERR; } enum { - LAZY_NONE, - LAZY_OP, - LAZY_LEFT, - LAZY_RIGHT, - RIGHT_ASSOC, /* reuse this field for right associativity too */ + OP_FUNC = 0x0001, /* function syntax */ + OP_RIGHT_ASSOC = 0x0002, /* right associative */ }; /* name - precedence - arity - opcode @@ -8374,7 +8373,7 @@ enum * The following macros pre-compute the string length at compile time. */ #define OPRINIT_ATTR(N, P, ARITY, F, ATTR) {N, F, P, ARITY, ATTR, sizeof(N) - 1} -#define OPRINIT(N, P, ARITY, F) OPRINIT_ATTR(N, P, ARITY, F, LAZY_NONE) +#define OPRINIT(N, P, ARITY, F) OPRINIT_ATTR(N, P, ARITY, F, 0) static const struct Jim_ExprOperator Jim_ExprOperators[] = { OPRINIT("*", 110, 2, JimExprOpBin), @@ -8402,24 +8401,13 @@ static const struct Jim_ExprOperator Jim_ExprOperators[] = { OPRINIT("^", 49, 2, JimExprOpIntBin), OPRINIT("|", 48, 2, JimExprOpIntBin), - OPRINIT_ATTR("&&", 10, 2, NULL, LAZY_OP), - OPRINIT_ATTR(NULL, 10, 2, JimExprOpAndLeft, LAZY_LEFT), - OPRINIT_ATTR(NULL, 10, 2, JimExprOpAndOrRight, LAZY_RIGHT), - - OPRINIT_ATTR("||", 9, 2, NULL, LAZY_OP), - OPRINIT_ATTR(NULL, 9, 2, JimExprOpOrLeft, LAZY_LEFT), - OPRINIT_ATTR(NULL, 9, 2, JimExprOpAndOrRight, LAZY_RIGHT), - - OPRINIT_ATTR("?", 5, 2, JimExprOpNull, LAZY_OP), - OPRINIT_ATTR(NULL, 5, 2, JimExprOpTernaryLeft, LAZY_LEFT), - OPRINIT_ATTR(NULL, 5, 2, JimExprOpNull, LAZY_RIGHT), - - OPRINIT_ATTR(":", 5, 2, JimExprOpNull, LAZY_OP), - OPRINIT_ATTR(NULL, 5, 2, JimExprOpColonLeft, LAZY_LEFT), - OPRINIT_ATTR(NULL, 5, 2, JimExprOpNull, LAZY_RIGHT), + OPRINIT("&&", 10, 2, JimExprOpAnd), + OPRINIT("||", 9, 2, JimExprOpOr), + OPRINIT_ATTR("?", 5, 3, JimExprOpTernary, OP_RIGHT_ASSOC), + OPRINIT_ATTR(":", 5, 3, NULL, OP_RIGHT_ASSOC), /* Precedence is higher than * and / but lower than ! and ~, and right-associative */ - OPRINIT_ATTR("**", 120, 2, JimExprOpBin, RIGHT_ASSOC), + OPRINIT_ATTR("**", 120, 2, JimExprOpBin, OP_RIGHT_ASSOC), OPRINIT("eq", 60, 2, JimExprOpStrBin), OPRINIT("ne", 60, 2, JimExprOpStrBin), @@ -8427,45 +8415,45 @@ static const struct Jim_ExprOperator Jim_ExprOperators[] = { OPRINIT("in", 55, 2, JimExprOpStrBin), OPRINIT("ni", 55, 2, JimExprOpStrBin), - OPRINIT("!", 150, 1, JimExprOpNumUnary), - OPRINIT("~", 150, 1, JimExprOpIntUnary), - OPRINIT(NULL, 150, 1, JimExprOpNumUnary), - OPRINIT(NULL, 150, 1, JimExprOpNumUnary), + OPRINIT_ATTR("!", 150, 1, JimExprOpNumUnary, OP_RIGHT_ASSOC), + OPRINIT_ATTR("~", 150, 1, JimExprOpIntUnary, OP_RIGHT_ASSOC), + OPRINIT_ATTR(" -", 150, 1, JimExprOpNumUnary, OP_RIGHT_ASSOC), + OPRINIT_ATTR(" +", 150, 1, JimExprOpNumUnary, OP_RIGHT_ASSOC), - OPRINIT("int", 200, 1, JimExprOpNumUnary), - OPRINIT("wide", 200, 1, JimExprOpNumUnary), - OPRINIT("abs", 200, 1, JimExprOpNumUnary), - OPRINIT("double", 200, 1, JimExprOpNumUnary), - OPRINIT("round", 200, 1, JimExprOpNumUnary), - OPRINIT("rand", 200, 0, JimExprOpNone), - OPRINIT("srand", 200, 1, JimExprOpIntUnary), + OPRINIT_ATTR("int", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("wide", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("abs", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("double", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("round", 200, 1, JimExprOpNumUnary, OP_FUNC), + OPRINIT_ATTR("rand", 200, 0, JimExprOpNone, OP_FUNC), + OPRINIT_ATTR("srand", 200, 1, JimExprOpIntUnary, OP_FUNC), #ifdef JIM_MATH_FUNCTIONS - OPRINIT("sin", 200, 1, JimExprOpDoubleUnary), - OPRINIT("cos", 200, 1, JimExprOpDoubleUnary), - OPRINIT("tan", 200, 1, JimExprOpDoubleUnary), - OPRINIT("asin", 200, 1, JimExprOpDoubleUnary), - OPRINIT("acos", 200, 1, JimExprOpDoubleUnary), - OPRINIT("atan", 200, 1, JimExprOpDoubleUnary), - OPRINIT("atan2", 200, 2, JimExprOpBin), - OPRINIT("sinh", 200, 1, JimExprOpDoubleUnary), - OPRINIT("cosh", 200, 1, JimExprOpDoubleUnary), - OPRINIT("tanh", 200, 1, JimExprOpDoubleUnary), - OPRINIT("ceil", 200, 1, JimExprOpDoubleUnary), - OPRINIT("floor", 200, 1, JimExprOpDoubleUnary), - OPRINIT("exp", 200, 1, JimExprOpDoubleUnary), - OPRINIT("log", 200, 1, JimExprOpDoubleUnary), - OPRINIT("log10", 200, 1, JimExprOpDoubleUnary), - OPRINIT("sqrt", 200, 1, JimExprOpDoubleUnary), - OPRINIT("pow", 200, 2, JimExprOpBin), - OPRINIT("hypot", 200, 2, JimExprOpBin), - OPRINIT("fmod", 200, 2, JimExprOpBin), + OPRINIT_ATTR("sin", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("cos", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("tan", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("asin", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("acos", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("atan", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("atan2", 200, 2, JimExprOpBin, OP_FUNC), + OPRINIT_ATTR("sinh", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("cosh", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("tanh", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("ceil", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("floor", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("exp", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("log", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("log10", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("sqrt", 200, 1, JimExprOpDoubleUnary, OP_FUNC), + OPRINIT_ATTR("pow", 200, 2, JimExprOpBin, OP_FUNC), + OPRINIT_ATTR("hypot", 200, 2, JimExprOpBin, OP_FUNC), + OPRINIT_ATTR("fmod", 200, 2, JimExprOpBin, OP_FUNC), #endif }; #undef OPRINIT -#undef OPRINIT_LAZY +#undef OPRINIT_ATTR #define JIM_EXPR_OPERATORS_NUM \ (sizeof(Jim_ExprOperators)/sizeof(struct Jim_ExprOperator)) @@ -8624,31 +8612,40 @@ static int JimParseExprBoolean(struct JimParserCtx *pc) return JIM_ERR; } +static const struct Jim_ExprOperator *JimExprOperatorInfoByOpcode(int opcode) +{ + static Jim_ExprOperator dummy_op; + if (opcode < JIM_TT_EXPR_OP) { + return &dummy_op; + } + return &Jim_ExprOperators[opcode - JIM_TT_EXPR_OP]; +} + static int JimParseExprOperator(struct JimParserCtx *pc) { int i; - int bestIdx = -1, bestLen = 0; + const struct Jim_ExprOperator *bestOp = NULL; + int bestLen = 0; /* Try to get the longest match. */ for (i = 0; i < (signed)JIM_EXPR_OPERATORS_NUM; i++) { - const char * const opname = Jim_ExprOperators[i].name; - const int oplen = Jim_ExprOperators[i].namelen; + const struct Jim_ExprOperator *op = &Jim_ExprOperators[i]; - if (opname == NULL || opname[0] != pc->p[0]) { + if (op->name[0] != pc->p[0]) { continue; } - if (oplen > bestLen && strncmp(opname, pc->p, oplen) == 0) { - bestIdx = i + JIM_TT_EXPR_OP; - bestLen = oplen; + if (op->namelen > bestLen && strncmp(op->name, pc->p, op->namelen) == 0) { + bestOp = op; + bestLen = op->namelen; } } - if (bestIdx == -1) { + if (bestOp == NULL) { return JIM_ERR; } /* Validate paretheses around function arguments */ - if (bestIdx >= JIM_EXPROP_FUNC_FIRST) { + if (bestOp->attr & OP_FUNC) { const char *p = pc->p + bestLen; int len = pc->len - bestLen; @@ -8664,19 +8661,10 @@ static int JimParseExprOperator(struct JimParserCtx *pc) pc->p += bestLen; pc->len -= bestLen; - pc->tt = bestIdx; + pc->tt = (bestOp - Jim_ExprOperators) + JIM_TT_EXPR_OP; return JIM_OK; } -static const struct Jim_ExprOperator *JimExprOperatorInfoByOpcode(int opcode) -{ - static Jim_ExprOperator dummy_op; - if (opcode < JIM_TT_EXPR_OP) { - return &dummy_op; - } - return &Jim_ExprOperators[opcode - JIM_TT_EXPR_OP]; -} - const char *jim_tt_name(int type) { static const char * const tt_names[JIM_TT_EXPR_OP] = @@ -8718,35 +8706,42 @@ static const Jim_ObjType exprObjType = { JIM_TYPE_REFERENCES, }; -/* Expr bytecode structure */ -typedef struct ExprByteCode +/* expr tree structure */ +struct ExprTree { - ScriptToken *token; /* Tokens array. */ - int len; /* Length as number of tokens. */ + struct JimExprNode *expr; /* The first operator or term */ + struct JimExprNode *nodes; /* Storage of all nodes in the tree */ + int len; /* Number of nodes in use */ int inUse; /* Used for sharing. */ -} ExprByteCode; +}; -static void ExprFreeByteCode(Jim_Interp *interp, ExprByteCode * expr) +static void ExprTreeFreeNodes(Jim_Interp *interp, struct JimExprNode *nodes, int num) { int i; - - for (i = 0; i < expr->len; i++) { - Jim_DecrRefCount(interp, expr->token[i].objPtr); + for (i = 0; i < num; i++) { + if (nodes[i].objPtr) { + Jim_DecrRefCount(interp, nodes[i].objPtr); + } } - Jim_Free(expr->token); + Jim_Free(nodes); +} + +static void ExprTreeFree(Jim_Interp *interp, struct ExprTree *expr) +{ + ExprTreeFreeNodes(interp, expr->nodes, expr->len); Jim_Free(expr); } static void FreeExprInternalRep(Jim_Interp *interp, Jim_Obj *objPtr) { - ExprByteCode *expr = (void *)objPtr->internalRep.ptr; + struct ExprTree *expr = (void *)objPtr->internalRep.ptr; if (expr) { if (--expr->inUse != 0) { return; } - ExprFreeByteCode(interp, expr); + ExprTreeFree(interp, expr); } } @@ -8759,376 +8754,156 @@ static void DupExprInternalRep(Jim_Interp *interp, Jim_Obj *srcPtr, Jim_Obj *dup dupPtr->typePtr = NULL; } -/* Check if an expr program looks correct - * Sets an error result on invalid - */ -static int ExprCheckCorrectness(Jim_Interp *interp, Jim_Obj *exprObjPtr, ExprByteCode * expr) +struct ExprBuilder { + int parencount; /* count of outstanding parentheses */ + int level; /* recursion depth */ + ParseToken *token; /* The current token */ + ParseToken *first_token; /* The first token */ + Jim_Stack stack; /* stack of pending terms */ + Jim_Obj *exprObjPtr; /* the original expression */ + Jim_Obj *fileNameObj; /* filename of the original expression */ + struct JimExprNode *nodes; /* storage for all nodes */ + struct JimExprNode *next; /* storage for the next node */ +}; + +#ifdef DEBUG_SHOW_EXPR +static void JimShowExprNode(struct JimExprNode *node, int level) { int i; - int stacklen = 0; - int ternary = 0; - int lasttt = JIM_TT_NONE; - const char *errmsg; - - /* Try to check if there are stack underflows, - * and make sure at the end of the program there is - * a single result on the stack. */ - for (i = 0; i < expr->len; i++) { - ScriptToken *t = &expr->token[i]; - const struct Jim_ExprOperator *op = JimExprOperatorInfoByOpcode(t->type); - lasttt = t->type; - - stacklen -= op->arity; - - if (stacklen < 0) { - break; - } - if (t->type == JIM_EXPROP_TERNARY || t->type == JIM_EXPROP_TERNARY_LEFT) { - ternary++; - } - else if (t->type == JIM_EXPROP_COLON || t->type == JIM_EXPROP_COLON_LEFT) { - ternary--; - } - - /* All operations and operands add one to the stack */ - stacklen++; - } - if (stacklen == 1 && ternary == 0) { - return JIM_OK; + for (i = 0; i < level; i++) { + printf(" "); } - - if (stacklen <= 0) { - /* Too few args */ - if (lasttt >= JIM_EXPROP_FUNC_FIRST) { - errmsg = "too few arguments for math function"; - Jim_SetResultString(interp, "too few arguments for math function", -1); - } else { - errmsg = "premature end of expression"; + if (TOKEN_IS_EXPR_OP(node->type)) { + printf("%s\n", jim_tt_name(node->type)); + if (node->left) { + JimShowExprNode(node->left, level + 1); } - } - else if (stacklen > 1) { - if (lasttt >= JIM_EXPROP_FUNC_FIRST) { - errmsg = "too many arguments for math function"; - } else { - errmsg = "extra tokens at end of expression"; + if (node->right) { + JimShowExprNode(node->right, level + 1); + } + if (node->ternary) { + JimShowExprNode(node->ternary, level + 1); } } else { - errmsg = "invalid ternary expression"; + printf("[%s] %s\n", jim_tt_name(node->type), Jim_String(node->objPtr)); } - Jim_SetResultFormatted(interp, "syntax error in expression \"%#s\": %s", exprObjPtr, errmsg); - return JIM_ERR; } +#endif -/* This procedure converts every occurrence of || and && opereators - * in lazy unary versions. - * - * a b || is converted into: - * - * a <offset> |L b |R - * - * a b && is converted into: +#define EXPR_UNTIL_CLOSE 0x0001 +#define EXPR_FUNC_ARGS 0x0002 +#define EXPR_TERNARY 0x0004 + +/** + * Parse the subexpression at builder->token and return with the node on the stack. + * builder->token is advanced to the next unconsumed token. + * Returns JIM_OK if OK or JIM_ERR on error and leaves a message in the interpreter result. * - * a <offset> &L b &R + * 'precedence' is the precedence of the current operator. Tokens are consumed until an operator + * with an equal or lower precedence is reached (or strictly lower if right associative). * - * "|L" checks if 'a' is true: - * 1) if it is true pushes 1 and skips <offset> instructions to reach - * the opcode just after |R. - * 2) if it is false does nothing. - * "|R" checks if 'b' is true: - * 1) if it is true pushes 1, otherwise pushes 0. + * If EXPR_UNTIL_CLOSE is set, the subexpression extends up to and including the next close parenthesis. + * If EXPR_FUNC_ARGS is set, multiple subexpressions (terms) are expected separated by comma + * If EXPR_TERNARY is set, two subexpressions (terms) are expected separated by colon * - * "&L" checks if 'a' is true: - * 1) if it is true does nothing. - * 2) If it is false pushes 0 and skips <offset> instructions to reach - * the opcode just after &R - * "&R" checks if 'a' is true: - * if it is true pushes 1, otherwise pushes 0. + * 'exp_numterms' indicates how many terms are expected. Normally this is 1, but may be more for EXPR_FUNC_ARGS and EXPR_TERNARY. */ -static int ExprAddLazyOperator(Jim_Interp *interp, ExprByteCode * expr, ParseToken *t) -{ - int i; - - int leftindex, arity, offset; - - /* Search for the end of the first operator */ - leftindex = expr->len - 1; - - arity = 1; - while (arity) { - ScriptToken *tt = &expr->token[leftindex]; - - if (tt->type >= JIM_TT_EXPR_OP) { - arity += JimExprOperatorInfoByOpcode(tt->type)->arity; - } - arity--; - if (--leftindex < 0) { - return JIM_ERR; - } - } - leftindex++; - - /* Move them up */ - memmove(&expr->token[leftindex + 2], &expr->token[leftindex], - sizeof(*expr->token) * (expr->len - leftindex)); - expr->len += 2; - offset = (expr->len - leftindex) - 1; - - /* Now we rely on the fact that the left and right version have opcodes - * 1 and 2 after the main opcode respectively - */ - expr->token[leftindex + 1].type = t->type + 1; - expr->token[leftindex + 1].objPtr = interp->emptyObj; - - expr->token[leftindex].type = JIM_TT_EXPR_INT; - expr->token[leftindex].objPtr = Jim_NewIntObj(interp, offset); - - /* Now add the 'R' operator */ - expr->token[expr->len].objPtr = interp->emptyObj; - expr->token[expr->len].type = t->type + 2; - expr->len++; - - /* Do we need to adjust the skip count for any &L, |L, ?L or :L in the left operand? */ - for (i = leftindex - 1; i > 0; i--) { - const struct Jim_ExprOperator *op = JimExprOperatorInfoByOpcode(expr->token[i].type); - if (op->lazy == LAZY_LEFT) { - if (JimWideValue(expr->token[i - 1].objPtr) + i - 1 >= leftindex) { - JimWideValue(expr->token[i - 1].objPtr) += 2; - } - } - } - return JIM_OK; -} - -static int ExprAddOperator(Jim_Interp *interp, ExprByteCode * expr, ParseToken *t) +static int ExprTreeBuildTree(Jim_Interp *interp, struct ExprBuilder *builder, int precedence, int flags, int exp_numterms) { - struct ScriptToken *token = &expr->token[expr->len]; - const struct Jim_ExprOperator *op = JimExprOperatorInfoByOpcode(t->type); + int rc; + struct JimExprNode *node; + /* Calculate the stack length expected after pushing the number of expected terms */ + int exp_stacklen = builder->stack.len + exp_numterms; - if (op->lazy == LAZY_OP) { - if (ExprAddLazyOperator(interp, expr, t) != JIM_OK) { - Jim_SetResultFormatted(interp, "Expression has bad operands to %s", op->name); - return JIM_ERR; - } - } - else { - token->objPtr = interp->emptyObj; - token->type = t->type; - expr->len++; + if (builder->level++ > 200) { + Jim_SetResultString(interp, "Expression too complex", -1); + return JIM_ERR; } - return JIM_OK; -} -/** - * Returns the index of the COLON_LEFT to the left of 'right_index' - * taking into account nesting. - * - * The expression *must* be well formed, thus a COLON_LEFT will always be found. - */ -static int ExprTernaryGetColonLeftIndex(ExprByteCode *expr, int right_index) -{ - int ternary_count = 1; + while (builder->token->type != JIM_TT_EOL) { + ParseToken *t = builder->token++; + int prevtt; - right_index--; - - while (right_index > 1) { - if (expr->token[right_index].type == JIM_EXPROP_TERNARY_LEFT) { - ternary_count--; - } - else if (expr->token[right_index].type == JIM_EXPROP_COLON_RIGHT) { - ternary_count++; + if (t == builder->first_token) { + prevtt = JIM_TT_NONE; } - else if (expr->token[right_index].type == JIM_EXPROP_COLON_LEFT && ternary_count == 1) { - return right_index; + else { + prevtt = t[-1].type; } - right_index--; - } - - /*notreached*/ - return -1; -} - -/** - * Find the left/right indices for the ternary expression to the left of 'right_index'. - * - * Returns 1 if found, and fills in *prev_right_index and *prev_left_index. - * Otherwise returns 0. - */ -static int ExprTernaryGetMoveIndices(ExprByteCode *expr, int right_index, int *prev_right_index, int *prev_left_index) -{ - int i = right_index - 1; - int ternary_count = 1; - while (i > 1) { - if (expr->token[i].type == JIM_EXPROP_TERNARY_LEFT) { - if (--ternary_count == 0 && expr->token[i - 2].type == JIM_EXPROP_COLON_RIGHT) { - *prev_right_index = i - 2; - *prev_left_index = ExprTernaryGetColonLeftIndex(expr, *prev_right_index); - return 1; + if (t->type == JIM_TT_SUBEXPR_START) { + if (builder->stack.len == exp_stacklen) { + Jim_SetResultFormatted(interp, "unexpected open parenthesis in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; } - } - else if (expr->token[i].type == JIM_EXPROP_COLON_RIGHT) { - if (ternary_count == 0) { - return 0; + builder->parencount++; + rc = ExprTreeBuildTree(interp, builder, 0, EXPR_UNTIL_CLOSE, 1); + if (rc != JIM_OK) { + return rc; } - ternary_count++; - } - i--; - } - return 0; -} - -/* -* ExprTernaryReorderExpression description -* ======================================== -* -* ?: is right-to-left associative which doesn't work with the stack-based -* expression engine. The fix is to reorder the bytecode. -* -* The expression: -* -* expr 1?2:0?3:4 -* -* Has initial bytecode: -* -* '1' '2' (40=TERNARY_LEFT) '2' (41=TERNARY_RIGHT) '2' (43=COLON_LEFT) '0' (44=COLON_RIGHT) -* '2' (40=TERNARY_LEFT) '3' (41=TERNARY_RIGHT) '2' (43=COLON_LEFT) '4' (44=COLON_RIGHT) -* -* The fix involves simulating this expression instead: -* -* expr 1?2:(0?3:4) -* -* With the following bytecode: -* -* '1' '2' (40=TERNARY_LEFT) '2' (41=TERNARY_RIGHT) '10' (43=COLON_LEFT) '0' '2' (40=TERNARY_LEFT) -* '3' (41=TERNARY_RIGHT) '2' (43=COLON_LEFT) '4' (44=COLON_RIGHT) (44=COLON_RIGHT) -* -* i.e. The token COLON_RIGHT at index 8 is moved towards the end of the stack, all tokens above 8 -* are shifted down and the skip count of the token JIM_EXPROP_COLON_LEFT at index 5 is -* incremented by the amount tokens shifted down. The token JIM_EXPROP_COLON_RIGHT that is moved -* is identified as immediately preceeding a token JIM_EXPROP_TERNARY_LEFT -* -* ExprTernaryReorderExpression works thus as follows : -* - start from the end of the stack -* - while walking towards the beginning of the stack -* if token=JIM_EXPROP_COLON_RIGHT then -* find the associated token JIM_EXPROP_TERNARY_LEFT, which allows to -* find the associated token previous(JIM_EXPROP_COLON_RIGHT) -* find the associated token previous(JIM_EXPROP_LEFT_RIGHT) -* if all found then -* perform the rotation -* update the skip count of the token previous(JIM_EXPROP_LEFT_RIGHT) -* end if -* end if -* -* Note: care has to be taken for nested ternary constructs!!! -*/ -static void ExprTernaryReorderExpression(Jim_Interp *interp, ExprByteCode *expr) -{ - int i; - - for (i = expr->len - 1; i > 1; i--) { - int prev_right_index; - int prev_left_index; - int j; - ScriptToken tmp; - - if (expr->token[i].type != JIM_EXPROP_COLON_RIGHT) { - continue; + /* A complete subexpression is on the stack */ } - - /* COLON_RIGHT found: get the indexes needed to move the tokens in the stack (if any) */ - if (ExprTernaryGetMoveIndices(expr, i, &prev_right_index, &prev_left_index) == 0) { - continue; + else if (t->type == JIM_TT_SUBEXPR_END) { + if (!(flags & EXPR_UNTIL_CLOSE)) { + if (builder->stack.len == exp_stacklen && builder->level > 1) { + builder->token--; + builder->level--; + return JIM_OK; + } + Jim_SetResultFormatted(interp, "unexpected closing parenthesis in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; + } + builder->parencount--; + if (builder->stack.len == exp_stacklen) { + /* Return with the expected number of subexpressions on the stack */ + break; + } } - - /* - ** rotate tokens down - ** - ** +-> [i] : JIM_EXPROP_COLON_RIGHT - ** | | | - ** | V V - ** | [...] : ... - ** | | | - ** | V V - ** | [...] : ... - ** | | | - ** | V V - ** +- [prev_right_index] : JIM_EXPROP_COLON_RIGHT - */ - tmp = expr->token[prev_right_index]; - for (j = prev_right_index; j < i; j++) { - expr->token[j] = expr->token[j + 1]; - } - expr->token[i] = tmp; - - /* Increment the 'skip' count associated to the previous JIM_EXPROP_COLON_LEFT token - * - * This is 'colon left increment' = i - prev_right_index - * - * [prev_left_index] : JIM_EXPROP_LEFT_RIGHT - * [prev_left_index-1] : skip_count - * - */ - JimWideValue(expr->token[prev_left_index-1].objPtr) += (i - prev_right_index); - - /* Adjust for i-- in the loop */ - i++; - } -} - -static ExprByteCode *ExprCreateByteCode(Jim_Interp *interp, const ParseTokenList *tokenlist, Jim_Obj *exprObjPtr, Jim_Obj *fileNameObj) -{ - Jim_Stack stack; - ExprByteCode *expr; - int ok = 1; - int i; - int prevtt = JIM_TT_NONE; - int have_ternary = 0; - - /* -1 for EOL */ - int count = tokenlist->count - 1; - - expr = Jim_Alloc(sizeof(*expr)); - expr->inUse = 1; - expr->len = 0; - - Jim_InitStack(&stack); - - /* Need extra bytecodes for lazy operators. - * Also check for the ternary operator - */ - for (i = 0; i < tokenlist->count; i++) { - ParseToken *t = &tokenlist->list[i]; - const struct Jim_ExprOperator *op = JimExprOperatorInfoByOpcode(t->type); - - if (op->lazy == LAZY_OP) { - count += 2; - /* Ternary is a lazy op but also needs reordering */ - if (t->type == JIM_EXPROP_TERNARY) { - have_ternary = 1; + else if (t->type == JIM_TT_SUBEXPR_COMMA) { + if (!(flags & EXPR_FUNC_ARGS)) { + if (builder->stack.len == exp_stacklen) { + /* handle the comma back at the parent level */ + builder->token--; + builder->level--; + return JIM_OK; + } + Jim_SetResultFormatted(interp, "unexpected comma in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; + } + else { + /* If we see more terms than expected, it is an error */ + if (builder->stack.len > exp_stacklen) { + Jim_SetResultFormatted(interp, "too many arguments to math function"); + return JIM_ERR; + } } + /* just go onto the next arg */ } - } - - expr->token = Jim_Alloc(sizeof(ScriptToken) * count); - - for (i = 0; i < tokenlist->count && ok; i++) { - ParseToken *t = &tokenlist->list[i]; - - /* Next token will be stored here */ - struct ScriptToken *token = &expr->token[expr->len]; - - if (t->type == JIM_TT_EOL) { - break; + else if (t->type == JIM_EXPROP_COLON) { + if (!(flags & EXPR_TERNARY)) { + if (builder->level != 1) { + /* handle the comma back at the parent level */ + builder->token--; + builder->level--; + return JIM_OK; + } + Jim_SetResultFormatted(interp, ": without ? in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; + } + if (builder->stack.len == exp_stacklen) { + /* handle the comma back at the parent level */ + builder->token--; + builder->level--; + return JIM_OK; + } + /* just go onto the next term */ } - - if (TOKEN_IS_EXPR_OP(t->type)) { + else if (TOKEN_IS_EXPR_OP(t->type)) { const struct Jim_ExprOperator *op; - ParseToken *tt; /* Convert -/+ to unary minus or unary plus if necessary */ - if (prevtt == JIM_TT_NONE || prevtt == JIM_TT_SUBEXPR_START || prevtt == JIM_TT_SUBEXPR_COMMA || prevtt >= JIM_TT_EXPR_OP) { + if (TOKEN_IS_EXPR_OP(prevtt) || TOKEN_IS_EXPR_START(prevtt)) { if (t->type == JIM_EXPROP_SUB) { t->type = JIM_EXPROP_UNARYMINUS; } @@ -9139,68 +8914,85 @@ static ExprByteCode *ExprCreateByteCode(Jim_Interp *interp, const ParseTokenList op = JimExprOperatorInfoByOpcode(t->type); - /* Handle precedence */ - while ((tt = Jim_StackPeek(&stack)) != NULL) { - const struct Jim_ExprOperator *tt_op = - JimExprOperatorInfoByOpcode(tt->type); - - /* Note that right-to-left associativity of ?: operator is handled later. - */ + if (op->precedence < precedence || (!(op->attr & OP_RIGHT_ASSOC) && op->precedence == precedence)) { + /* next op is lower precedence, or equal and left associative, so done here */ + builder->token--; + break; + } - if (op->arity != 1 && tt_op->precedence >= op->precedence) { - /* Don't reduce if right associative with equal precedence? */ - if (tt_op->precedence == op->precedence && tt_op->lazy == RIGHT_ASSOC) { - break; - } - if (ExprAddOperator(interp, expr, tt) != JIM_OK) { - ok = 0; - goto err; - } - Jim_StackPop(&stack); + if (op->attr & OP_FUNC) { + if (builder->token->type != JIM_TT_SUBEXPR_START) { + Jim_SetResultString(interp, "missing arguments for math function", -1); + return JIM_ERR; } - else { - break; + builder->token++; + if (op->arity == 0) { + if (builder->token->type != JIM_TT_SUBEXPR_END) { + Jim_SetResultString(interp, "too many arguments for math function", -1); + return JIM_ERR; + } + builder->token++; + goto noargs; } + builder->parencount++; + + /* This will push left and return right */ + rc = ExprTreeBuildTree(interp, builder, 0, EXPR_FUNC_ARGS | EXPR_UNTIL_CLOSE, op->arity); + } + else if (t->type == JIM_EXPROP_TERNARY) { + /* Collect the two arguments to the ternary operator */ + rc = ExprTreeBuildTree(interp, builder, op->precedence, EXPR_TERNARY, 2); + } + else { + /* Recursively handle everything on the right until we see a precendence <= op->precedence or == and right associative + * and push that on the term stack + */ + rc = ExprTreeBuildTree(interp, builder, op->precedence, 0, 1); } - Jim_StackPush(&stack, t); - } - else if (t->type == JIM_TT_SUBEXPR_START) { - Jim_StackPush(&stack, t); - } - else if (t->type == JIM_TT_SUBEXPR_END || t->type == JIM_TT_SUBEXPR_COMMA) { - /* Reduce the expression back to the previous ( or , */ - ok = 0; - while (Jim_StackLen(&stack)) { - ParseToken *tt = Jim_StackPop(&stack); - if (tt->type == JIM_TT_SUBEXPR_START || tt->type == JIM_TT_SUBEXPR_COMMA) { - if (t->type == JIM_TT_SUBEXPR_COMMA) { - /* Need to push back the previous START or COMMA in the case of comma */ - Jim_StackPush(&stack, tt); - } - ok = 1; - break; + if (rc != JIM_OK) { + return rc; + } + +noargs: + node = builder->next++; + node->type = t->type; + + if (op->arity >= 3) { + node->ternary = Jim_StackPop(&builder->stack); + if (node->ternary == NULL) { + goto missingoperand; } - if (ExprAddOperator(interp, expr, tt) != JIM_OK) { - goto err; + } + if (op->arity >= 2) { + node->right = Jim_StackPop(&builder->stack); + if (node->right == NULL) { + goto missingoperand; } } - if (!ok) { - Jim_SetResultFormatted(interp, "Unexpected close parenthesis in expression: \"%#s\"", exprObjPtr); - goto err; + if (op->arity >= 1) { + node->left = Jim_StackPop(&builder->stack); + if (node->left == NULL) { +missingoperand: + Jim_SetResultFormatted(interp, "missing operand to %s in expression: \"%#s\"", op->name, builder->exprObjPtr); + builder->next--; + return JIM_ERR; + + } } + + /* Now push the node */ + Jim_StackPush(&builder->stack, node); } else { Jim_Obj *objPtr = NULL; /* This is a simple non-operator term, so create and push the appropriate object */ - token->type = t->type; /* Two consecutive terms without an operator is invalid */ if (!TOKEN_IS_EXPR_START(prevtt) && !TOKEN_IS_EXPR_OP(prevtt)) { - Jim_SetResultFormatted(interp, "missing operator in expression: \"%#s\"", exprObjPtr); - ok = 0; - goto err; + Jim_SetResultFormatted(interp, "missing operator in expression: \"%#s\"", builder->exprObjPtr); + return JIM_ERR; } /* Immediately create a double or int object? */ @@ -9219,66 +9011,105 @@ static ExprByteCode *ExprCreateByteCode(Jim_Interp *interp, const ParseTokenList } } - if (objPtr) { - token->objPtr = objPtr; - } - else { + if (!objPtr) { /* Everything else is stored a simple string term */ - token->objPtr = Jim_NewStringObj(interp, t->token, t->len); + objPtr = Jim_NewStringObj(interp, t->token, t->len); if (t->type == JIM_TT_CMD) { /* Only commands need source info */ - JimSetSourceInfo(interp, token->objPtr, fileNameObj, t->line); + JimSetSourceInfo(interp, objPtr, builder->fileNameObj, t->line); } } - expr->len++; + + /* Now push a term node */ + node = builder->next++; + node->objPtr = objPtr; + Jim_IncrRefCount(node->objPtr); + node->type = t->type; + Jim_StackPush(&builder->stack, node); } - prevtt = t->type; } - /* Reduce any remaining subexpr */ - while (Jim_StackLen(&stack)) { - ParseToken *tt = Jim_StackPop(&stack); + if (builder->stack.len == exp_stacklen) { + builder->level--; + return JIM_OK; + } - if (tt->type == JIM_TT_SUBEXPR_START) { - ok = 0; - Jim_SetResultString(interp, "Missing close parenthesis", -1); - goto err; + if ((flags & EXPR_FUNC_ARGS)) { + Jim_SetResultFormatted(interp, "too %s arguments for math function", (builder->stack.len < exp_stacklen) ? "few" : "many"); + } + else { + if (builder->stack.len < exp_stacklen) { + if (builder->level == 0) { + Jim_SetResultFormatted(interp, "empty expression"); + } + else { + Jim_SetResultFormatted(interp, "syntax error in expression \"%#s\": premature end of expression", builder->exprObjPtr); + } } - if (ExprAddOperator(interp, expr, tt) != JIM_OK) { - ok = 0; - goto err; + else { + Jim_SetResultFormatted(interp, "extra terms after expression"); } } - if (have_ternary) { - ExprTernaryReorderExpression(interp, expr); - } + return JIM_ERR; +} - err: - /* Free the stack used for the compilation. */ - Jim_FreeStack(&stack); +static struct ExprTree *ExprTreeCreateTree(Jim_Interp *interp, const ParseTokenList *tokenlist, Jim_Obj *exprObjPtr, Jim_Obj *fileNameObj) +{ + struct ExprTree *expr; + struct ExprBuilder builder; + int rc; + struct JimExprNode *top = NULL; + + builder.parencount = 0; + builder.level = 0; + builder.token = builder.first_token = tokenlist->list; + builder.exprObjPtr = exprObjPtr; + builder.fileNameObj = fileNameObj; + /* The bytecode will never produce more nodes than there are tokens - 1 (for EOL)*/ + builder.nodes = malloc(sizeof(struct JimExprNode) * (tokenlist->count - 1)); + memset(builder.nodes, 0, sizeof(struct JimExprNode) * (tokenlist->count - 1)); + builder.next = builder.nodes; + Jim_InitStack(&builder.stack); - for (i = 0; i < expr->len; i++) { - Jim_IncrRefCount(expr->token[i].objPtr); + rc = ExprTreeBuildTree(interp, &builder, 0, 0, 1); + + if (rc == JIM_OK) { + top = Jim_StackPop(&builder.stack); + + if (builder.parencount) { + Jim_SetResultString(interp, "missing close parenthesis", -1); + rc = JIM_ERR; + } } - if (!ok) { - ExprFreeByteCode(interp, expr); + /* Free the stack used for the compilation. */ + Jim_FreeStack(&builder.stack); + + if (rc != JIM_OK) { + ExprTreeFreeNodes(interp, builder.nodes, builder.next - builder.nodes); return NULL; } + expr = Jim_Alloc(sizeof(*expr)); + expr->inUse = 1; + expr->expr = top; + expr->nodes = builder.nodes; + expr->len = builder.next - builder.nodes; + + assert(expr->len <= tokenlist->count - 1); + return expr; } - /* This method takes the string representation of an expression - * and generates a program for the Expr's stack-based VM. */ + * and generates a program for the expr engine */ static int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) { int exprTextLen; const char *exprText; struct JimParserCtx parser; - struct ExprByteCode *expr; + struct ExprTree *expr; ParseTokenList tokenlist; int line; Jim_Obj *fileNameObj; @@ -9331,7 +9162,7 @@ static int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) } /* Now create the expression bytecode from the tokenlist */ - expr = ExprCreateByteCode(interp, &tokenlist, objPtr, fileNameObj); + expr = ExprTreeCreateTree(interp, &tokenlist, objPtr, fileNameObj); /* No longer need the token list */ ScriptTokenListFree(&tokenlist); @@ -9341,26 +9172,10 @@ static int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) } #ifdef DEBUG_SHOW_EXPR - { - int i; - - printf("==== Expr ====\n"); - for (i = 0; i < expr->len; i++) { - ScriptToken *t = &expr->token[i]; - - printf("[%2d] %s '%s'\n", i, jim_tt_name(t->type), Jim_String(t->objPtr)); - } - } + printf("==== Expr ====\n"); + JimShowExprNode(expr->expr, 0); #endif - /* Check program correctness. */ - if (ExprCheckCorrectness(interp, objPtr, expr) != JIM_OK) { - /* ExprCheckCorrectness set an error in this case */ - ExprFreeByteCode(interp, expr); - expr = NULL; - goto err; - } - rc = JIM_OK; err: @@ -9372,25 +9187,25 @@ static int SetExprFromAny(Jim_Interp *interp, struct Jim_Obj *objPtr) return rc; } -static ExprByteCode *JimGetExpression(Jim_Interp *interp, Jim_Obj *objPtr) +static struct ExprTree *JimGetExpression(Jim_Interp *interp, Jim_Obj *objPtr) { if (objPtr->typePtr != &exprObjType) { if (SetExprFromAny(interp, objPtr) != JIM_OK) { return NULL; } } - return (ExprByteCode *) Jim_GetIntRepPtr(objPtr); + return (struct ExprTree *) Jim_GetIntRepPtr(objPtr); } #ifdef JIM_OPTIMIZATION -static Jim_Obj *JimExprIntValOrVar(Jim_Interp *interp, const ScriptToken *token) -{ - if (token->type == JIM_TT_EXPR_INT) - return token->objPtr; - else if (token->type == JIM_TT_VAR) - return Jim_GetVariable(interp, token->objPtr, JIM_NONE); - else if (token->type == JIM_TT_DICTSUGAR) - return JimExpandDictSugar(interp, token->objPtr); +static Jim_Obj *JimExprIntValOrVar(Jim_Interp *interp, struct JimExprNode *node) +{ + if (node->type == JIM_TT_EXPR_INT) + return node->objPtr; + else if (node->type == JIM_TT_VAR) + return Jim_GetVariable(interp, node->objPtr, JIM_NONE); + else if (node->type == JIM_TT_DICTSUGAR) + return JimExpandDictSugar(interp, node->objPtr); else return NULL; } @@ -9398,11 +9213,11 @@ static Jim_Obj *JimExprIntValOrVar(Jim_Interp *interp, const ScriptToken *token) /* ----------------------------------------------------------------------------- * Expressions evaluation. - * Jim uses a specialized stack-based virtual machine for expressions, + * Jim uses a recursive evaluation engine for expressions, * that takes advantage of the fact that expr's operators * can't be redefined. * - * Jim_EvalExpression() uses the bytecode compiled by + * Jim_EvalExpression() uses the expression tree compiled by * SetExprFromAny() method of the "expression" object. * * On success a Tcl Object containing the result of the evaluation @@ -9411,15 +9226,80 @@ static Jim_Obj *JimExprIntValOrVar(Jim_Interp *interp, const ScriptToken *token) * On error the function returns a retcode != to JIM_OK and set a suitable * error on the interp. * ---------------------------------------------------------------------------*/ -#define JIM_EE_STATICSTACK_LEN 10 -int Jim_EvalExpression(Jim_Interp *interp, Jim_Obj *exprObjPtr, Jim_Obj **exprResultPtrPtr) +static int JimExprEvalTermNode(Jim_Interp *interp, struct JimExprNode *node) { - ExprByteCode *expr; - Jim_Obj *staticStack[JIM_EE_STATICSTACK_LEN]; - int i; + if (TOKEN_IS_EXPR_OP(node->type)) { + const struct Jim_ExprOperator *op = JimExprOperatorInfoByOpcode(node->type); + return op->funcop(interp, node); + } + else { + Jim_Obj *objPtr; + + /* A term */ + switch (node->type) { + case JIM_TT_EXPR_INT: + case JIM_TT_EXPR_DOUBLE: + case JIM_TT_EXPR_BOOLEAN: + case JIM_TT_STR: + Jim_SetResult(interp, node->objPtr); + return JIM_OK; + + case JIM_TT_VAR: + objPtr = Jim_GetVariable(interp, node->objPtr, JIM_ERRMSG); + if (objPtr) { + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + return JIM_ERR; + + case JIM_TT_DICTSUGAR: + objPtr = JimExpandDictSugar(interp, node->objPtr); + if (objPtr) { + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + return JIM_ERR; + + case JIM_TT_ESC: + if (Jim_SubstObj(interp, node->objPtr, &objPtr, JIM_NONE) == JIM_OK) { + Jim_SetResult(interp, objPtr); + return JIM_OK; + } + return JIM_ERR; + + case JIM_TT_CMD: + return Jim_EvalObj(interp, node->objPtr); + + default: + /* Should never get here */ + return JIM_ERR; + } + } +} + +static int JimExprGetTerm(Jim_Interp *interp, struct JimExprNode *node, Jim_Obj **objPtrPtr) +{ + int rc = JimExprEvalTermNode(interp, node); + if (rc == JIM_OK) { + *objPtrPtr = Jim_GetResult(interp); + Jim_IncrRefCount(*objPtrPtr); + } + return rc; +} + +static int JimExprGetTermBoolean(Jim_Interp *interp, struct JimExprNode *node) +{ + if (JimExprEvalTermNode(interp, node) == JIM_OK) { + return ExprBool(interp, Jim_GetResult(interp)); + } + return -1; +} + +int Jim_EvalExpression(Jim_Interp *interp, Jim_Obj *exprObjPtr) +{ + struct ExprTree *expr; int retcode = JIM_OK; - struct JimExprState e; expr = JimGetExpression(interp, exprObjPtr); if (!expr) { @@ -9447,35 +9327,33 @@ int Jim_EvalExpression(Jim_Interp *interp, Jim_Obj *exprObjPtr, Jim_Obj **exprRe switch (expr->len) { case 1: - objPtr = JimExprIntValOrVar(interp, &expr->token[0]); + objPtr = JimExprIntValOrVar(interp, expr->expr); if (objPtr) { - Jim_IncrRefCount(objPtr); - *exprResultPtrPtr = objPtr; + Jim_SetResult(interp, objPtr); return JIM_OK; } break; case 2: - if (expr->token[1].type == JIM_EXPROP_NOT) { - objPtr = JimExprIntValOrVar(interp, &expr->token[0]); + if (expr->expr->type == JIM_EXPROP_NOT) { + objPtr = JimExprIntValOrVar(interp, expr->expr->left); if (objPtr && JimIsWide(objPtr)) { - *exprResultPtrPtr = JimWideValue(objPtr) ? interp->falseObj : interp->trueObj; - Jim_IncrRefCount(*exprResultPtrPtr); + Jim_SetResult(interp, JimWideValue(objPtr) ? interp->falseObj : interp->trueObj); return JIM_OK; } } break; case 3: - objPtr = JimExprIntValOrVar(interp, &expr->token[0]); + objPtr = JimExprIntValOrVar(interp, expr->expr->left); if (objPtr && JimIsWide(objPtr)) { - Jim_Obj *objPtr2 = JimExprIntValOrVar(interp, &expr->token[1]); + Jim_Obj *objPtr2 = JimExprIntValOrVar(interp, expr->expr->right); if (objPtr2 && JimIsWide(objPtr2)) { jim_wide wideValueA = JimWideValue(objPtr); jim_wide wideValueB = JimWideValue(objPtr2); int cmpRes; - switch (expr->token[2].type) { + switch (expr->expr->type) { case JIM_EXPROP_LT: cmpRes = wideValueA < wideValueB; break; @@ -9497,8 +9375,7 @@ int Jim_EvalExpression(Jim_Interp *interp, Jim_Obj *exprObjPtr, Jim_Obj **exprRe default: goto noopt; } - *exprResultPtrPtr = cmpRes ? interp->trueObj : interp->falseObj; - Jim_IncrRefCount(*exprResultPtrPtr); + Jim_SetResult(interp, cmpRes ? interp->trueObj : interp->falseObj); return JIM_OK; } } @@ -9508,131 +9385,39 @@ int Jim_EvalExpression(Jim_Interp *interp, Jim_Obj *exprObjPtr, Jim_Obj **exprRe noopt: #endif - /* In order to avoid that the internal repr gets freed due to + /* In order to avoid the internal repr being freed due to * shimmering of the exprObjPtr's object, we make the internal rep * shared. */ expr->inUse++; - /* The stack-based expr VM itself */ - - /* Stack allocation. Expr programs have the feature that - * a program of length N can't require a stack longer than - * N. */ - if (expr->len > JIM_EE_STATICSTACK_LEN) - e.stack = Jim_Alloc(sizeof(Jim_Obj *) * expr->len); - else - e.stack = staticStack; - - e.stacklen = 0; - - /* Execute every instruction */ - for (i = 0; i < expr->len && retcode == JIM_OK; i++) { - Jim_Obj *objPtr; - - switch (expr->token[i].type) { - case JIM_TT_EXPR_INT: - case JIM_TT_EXPR_DOUBLE: - case JIM_TT_EXPR_BOOLEAN: - case JIM_TT_STR: - ExprPush(&e, expr->token[i].objPtr); - break; - - case JIM_TT_VAR: - objPtr = Jim_GetVariable(interp, expr->token[i].objPtr, JIM_ERRMSG); - if (objPtr) { - ExprPush(&e, objPtr); - } - else { - retcode = JIM_ERR; - } - break; - - case JIM_TT_DICTSUGAR: - objPtr = JimExpandDictSugar(interp, expr->token[i].objPtr); - if (objPtr) { - ExprPush(&e, objPtr); - } - else { - retcode = JIM_ERR; - } - break; - - case JIM_TT_ESC: - retcode = Jim_SubstObj(interp, expr->token[i].objPtr, &objPtr, JIM_NONE); - if (retcode == JIM_OK) { - ExprPush(&e, objPtr); - } - break; - - case JIM_TT_CMD: - retcode = Jim_EvalObj(interp, expr->token[i].objPtr); - if (retcode == JIM_OK) { - ExprPush(&e, Jim_GetResult(interp)); - } - break; - - default:{ - /* Find and execute the operation */ - e.skip = 0; - e.opcode = expr->token[i].type; - - retcode = JimExprOperatorInfoByOpcode(e.opcode)->funcop(interp, &e); - /* Skip some opcodes if necessary */ - i += e.skip; - continue; - } - } - } + /* Evaluate with the recursive expr engine */ + retcode = JimExprEvalTermNode(interp, expr->expr); expr->inUse--; - if (retcode == JIM_OK) { - *exprResultPtrPtr = ExprPop(&e); - } - else { - for (i = 0; i < e.stacklen; i++) { - Jim_DecrRefCount(interp, e.stack[i]); - } - } - if (e.stack != staticStack) { - Jim_Free(e.stack); - } return retcode; } int Jim_GetBoolFromExpr(Jim_Interp *interp, Jim_Obj *exprObjPtr, int *boolPtr) { - int retcode; - jim_wide wideValue; - double doubleValue; - int booleanValue; - Jim_Obj *exprResultPtr; + int retcode = Jim_EvalExpression(interp, exprObjPtr); - retcode = Jim_EvalExpression(interp, exprObjPtr, &exprResultPtr); - if (retcode != JIM_OK) - return retcode; + if (retcode == JIM_OK) { + switch (ExprBool(interp, Jim_GetResult(interp))) { + case 0: + *boolPtr = 0; + break; - if (JimGetWideNoErr(interp, exprResultPtr, &wideValue) != JIM_OK) { - if (Jim_GetDouble(interp, exprResultPtr, &doubleValue) != JIM_OK) { - if (Jim_GetBoolean(interp, exprResultPtr, &booleanValue) != JIM_OK) { - Jim_DecrRefCount(interp, exprResultPtr); - return JIM_ERR; - } else { - Jim_DecrRefCount(interp, exprResultPtr); - *boolPtr = booleanValue; - return JIM_OK; - } - } - else { - Jim_DecrRefCount(interp, exprResultPtr); - *boolPtr = doubleValue != 0; - return JIM_OK; + case 1: + *boolPtr = 1; + break; + + case -1: + retcode = JIM_ERR; + break; } } - *boolPtr = wideValue != 0; - - Jim_DecrRefCount(interp, exprResultPtr); - return JIM_OK; + return retcode; } /* ----------------------------------------------------------------------------- @@ -9651,8 +9436,8 @@ int Jim_GetBoolFromExpr(Jim_Interp *interp, Jim_Obj *exprObjPtr, int *boolPtr) typedef struct ScanFmtPartDescr { - char *arg; /* Specification of a CHARSET conversion */ - char *prefix; /* Prefix to be scanned literally before conversion */ + const char *arg; /* Specification of a CHARSET conversion */ + const char *prefix; /* Prefix to be scanned literally before conversion */ size_t width; /* Maximal width of input to be converted */ int pos; /* -1 - no assign, 0 - natural pos, >0 - XPG3 pos */ char type; /* Type of conversion (e.g. c, d, f) */ @@ -9736,8 +9521,8 @@ static int SetScanFmtFromAny(Jim_Interp *interp, Jim_Obj *objPtr) ScanFmtStringObj *fmtObj; char *buffer; int maxCount, i, approxSize, lastPos = -1; - const char *fmt = objPtr->bytes; - int maxFmtLen = objPtr->length; + const char *fmt = Jim_String(objPtr); + int maxFmtLen = Jim_Length(objPtr); const char *fmtEnd = fmt + maxFmtLen; int curr; @@ -9822,6 +9607,11 @@ static int SetScanFmtFromAny(Jim_Interp *interp, Jim_Obj *objPtr) return JIM_ERR; } } + if (descr->pos < 0) { + fmtObj->error = + "\"%n$\" conversion specifier is negative"; + return JIM_ERR; + } /* Try to find a width after the XPG3 specifier */ if (sscanf(fmt, "%d%n", &width, &skip) == 1) { descr->width = width; @@ -9873,9 +9663,14 @@ static int SetScanFmtFromAny(Jim_Interp *interp, Jim_Obj *objPtr) } else { /* Remember any valid modifier if given */ - if (strchr("hlL", *fmt) != 0) + if (fmt < fmtEnd && strchr("hlL", *fmt)) descr->modifier = tolower((int)*fmt++); + if (fmt >= fmtEnd) { + fmtObj->error = "missing scan conversion character"; + return JIM_ERR; + } + descr->type = *fmt; if (strchr("efgcsndoxui", *fmt) == 0) { fmtObj->error = "bad scan conversion character"; @@ -10324,6 +10119,7 @@ static int JimInvokeCommand(Jim_Interp *interp, int objc, Jim_Obj *const *objv) { int retcode; Jim_Cmd *cmdPtr; + void *prevPrivData; #if 0 printf("invoke"); @@ -10353,6 +10149,7 @@ static int JimInvokeCommand(Jim_Interp *interp, int objc, Jim_Obj *const *objv) goto out; } interp->evalDepth++; + prevPrivData = interp->cmdPrivData; /* Call it -- Make sure result is an empty object. */ Jim_SetEmptyResult(interp); @@ -10363,6 +10160,7 @@ static int JimInvokeCommand(Jim_Interp *interp, int objc, Jim_Obj *const *objv) interp->cmdPrivData = cmdPtr->u.native.privData; retcode = cmdPtr->u.native.cmdProc(interp, objc, objv); } + interp->cmdPrivData = prevPrivData; interp->evalDepth--; out: @@ -10450,6 +10248,7 @@ static void JimAddErrorToStack(Jim_Interp *interp, ScriptObj *script) static int JimSubstOneToken(Jim_Interp *interp, const ScriptToken *token, Jim_Obj **objPtrPtr) { Jim_Obj *objPtr; + int ret = JIM_ERR; switch (token->type) { case JIM_TT_STR: @@ -10463,22 +10262,21 @@ static int JimSubstOneToken(Jim_Interp *interp, const ScriptToken *token, Jim_Ob objPtr = JimExpandDictSugar(interp, token->objPtr); break; case JIM_TT_EXPRSUGAR: - objPtr = JimExpandExprSugar(interp, token->objPtr); + ret = Jim_EvalExpression(interp, token->objPtr); + if (ret == JIM_OK) { + objPtr = Jim_GetResult(interp); + } + else { + objPtr = NULL; + } break; case JIM_TT_CMD: - switch (Jim_EvalObj(interp, token->objPtr)) { - case JIM_OK: - case JIM_RETURN: - objPtr = interp->result; - break; - case JIM_BREAK: - /* Stop substituting */ - return JIM_BREAK; - case JIM_CONTINUE: - /* just skip this one */ - return JIM_CONTINUE; - default: - return JIM_ERR; + ret = Jim_EvalObj(interp, token->objPtr); + if (ret == JIM_OK || ret == JIM_RETURN) { + objPtr = interp->result; + } else { + /* includes JIM_BREAK, JIM_CONTINUE */ + objPtr = NULL; } break; default: @@ -10491,7 +10289,7 @@ static int JimSubstOneToken(Jim_Interp *interp, const ScriptToken *token, Jim_Ob *objPtrPtr = objPtr; return JIM_OK; } - return JIM_ERR; + return ret; } /* Interpolate the given tokens into a unique Jim_Obj returned by reference @@ -10549,7 +10347,8 @@ static Jim_Obj *JimInterpolateTokens(Jim_Interp *interp, const ScriptToken * tok /* Fast path return for a single token */ if (tokens == 1 && intv[0] && intv == sintv) { - Jim_DecrRefCount(interp, intv[0]); + /* Reverse the Jim_IncrRefCount() above, but don't free the object */ + intv[0]->refCount--; return intv[0]; } @@ -10738,7 +10537,13 @@ int Jim_EvalObj(Jim_Interp *interp, Jim_Obj *scriptObjPtr) wordObjPtr = Jim_GetVariable(interp, token[i].objPtr, JIM_ERRMSG); break; case JIM_TT_EXPRSUGAR: - wordObjPtr = JimExpandExprSugar(interp, token[i].objPtr); + retcode = Jim_EvalExpression(interp, token[i].objPtr); + if (retcode == JIM_OK) { + wordObjPtr = Jim_GetResult(interp); + } + else { + wordObjPtr = NULL; + } break; case JIM_TT_DICTSUGAR: wordObjPtr = JimExpandDictSugar(interp, token[i].objPtr); @@ -10922,7 +10727,6 @@ static void JimSetProcWrongArgs(Jim_Interp *interp, Jim_Obj *procNameObj, Jim_Cm } } Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s%#s\"", procNameObj, argmsg); - Jim_FreeNewObj(interp, argmsg); } #ifdef jim_ext_namespace @@ -11060,7 +10864,8 @@ static int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, int argc, Jim_Obj badargset: - /* Free the callframe */ + /* Invoke $jim::defer then destroy the callframe */ + retcode = JimInvokeDefer(interp, retcode); interp->framePtr = interp->framePtr->parent; JimFreeCallFrame(interp, callFramePtr, JIM_FCF_REUSE); @@ -11342,7 +11147,11 @@ static ScriptObj *Jim_GetSubst(Jim_Interp *interp, Jim_Obj *objPtr, int flags) * resObjPtrPtr. */ int Jim_SubstObj(Jim_Interp *interp, Jim_Obj *substObjPtr, Jim_Obj **resObjPtrPtr, int flags) { - ScriptObj *script = Jim_GetSubst(interp, substObjPtr, flags); + ScriptObj *script; + + JimPanic((substObjPtr->refCount == 0, "Jim_SubstObj() called with zero refcount object")); + + script = Jim_GetSubst(interp, substObjPtr, flags); Jim_IncrRefCount(substObjPtr); /* Make sure it's shared. */ /* In order to preserve the internal rep, we increment the @@ -11371,16 +11180,14 @@ void Jim_WrongNumArgs(Jim_Interp *interp, int argc, Jim_Obj *const *argv, const listObjPtr = Jim_NewListObj(interp, argv, argc); - if (*msg) { + if (msg && *msg) { Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, msg, -1)); } Jim_IncrRefCount(listObjPtr); objPtr = Jim_ListJoin(interp, listObjPtr, " ", 1); Jim_DecrRefCount(interp, listObjPtr); - Jim_IncrRefCount(objPtr); Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s\"", objPtr); - Jim_DecrRefCount(interp, objPtr); } /** @@ -11634,8 +11441,13 @@ static int JimSubDivHelper(Jim_Interp *interp, int argc, Jim_Obj *const *argv, i } if (op == JIM_EXPROP_SUB) res -= wideValue; - else + else { + if (wideValue == 0) { + Jim_SetResultString(interp, "Division by zero", -1); + return JIM_ERR; + } res /= wideValue; + } } Jim_SetResultInt(interp, res); return JIM_OK; @@ -11805,7 +11617,7 @@ static int Jim_ForCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv */ if (retval == JIM_OK && boolean) { ScriptObj *incrScript; - ExprByteCode *expr; + struct ExprTree *expr; jim_wide stop, currentVal; Jim_Obj *objPtr; int cmpOffset; @@ -11819,47 +11631,53 @@ static int Jim_ForCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv goto evalstart; } /* Ensure proper token types. */ - if (incrScript->token[1].type != JIM_TT_ESC || - expr->token[0].type != JIM_TT_VAR || - (expr->token[1].type != JIM_TT_EXPR_INT && expr->token[1].type != JIM_TT_VAR)) { + if (incrScript->token[1].type != JIM_TT_ESC) { goto evalstart; } - if (expr->token[2].type == JIM_EXPROP_LT) { + if (expr->expr->type == JIM_EXPROP_LT) { cmpOffset = 0; } - else if (expr->token[2].type == JIM_EXPROP_LTE) { + else if (expr->expr->type == JIM_EXPROP_LTE) { cmpOffset = 1; } else { goto evalstart; } + if (expr->expr->left->type != JIM_TT_VAR) { + goto evalstart; + } + + if (expr->expr->right->type != JIM_TT_VAR && expr->expr->right->type != JIM_TT_EXPR_INT) { + goto evalstart; + } + /* Update command must be incr */ if (!Jim_CompareStringImmediate(interp, incrScript->token[1].objPtr, "incr")) { goto evalstart; } /* incr, expression must be about the same variable */ - if (!Jim_StringEqObj(incrScript->token[2].objPtr, expr->token[0].objPtr)) { + if (!Jim_StringEqObj(incrScript->token[2].objPtr, expr->expr->left->objPtr)) { goto evalstart; } /* Get the stop condition (must be a variable or integer) */ - if (expr->token[1].type == JIM_TT_EXPR_INT) { - if (Jim_GetWide(interp, expr->token[1].objPtr, &stop) == JIM_ERR) { + if (expr->expr->right->type == JIM_TT_EXPR_INT) { + if (Jim_GetWide(interp, expr->expr->right->objPtr, &stop) == JIM_ERR) { goto evalstart; } } else { - stopVarNamePtr = expr->token[1].objPtr; + stopVarNamePtr = expr->expr->right->objPtr; Jim_IncrRefCount(stopVarNamePtr); /* Keep the compiler happy */ stop = 0; } /* Initialization */ - varNamePtr = expr->token[0].objPtr; + varNamePtr = expr->expr->left->objPtr; Jim_IncrRefCount(varNamePtr); objPtr = Jim_GetVariable(interp, varNamePtr, JIM_NONE); @@ -12080,7 +11898,7 @@ static int JimForeachMapHelper(Jim_Interp *interp, int argc, Jim_Obj *const *arg } if (result != JIM_OK) { Jim_SetResultString(interp, "foreach varlist is empty", -1); - return result; + goto empty_varlist; } if (doMap) { @@ -12143,6 +11961,7 @@ static int JimForeachMapHelper(Jim_Interp *interp, int argc, Jim_Obj *const *arg Jim_SetResult(interp, resultObj); err: Jim_DecrRefCount(interp, resultObj); + empty_varlist: if (numargs > 2) { Jim_Free(iters); } @@ -12271,15 +12090,13 @@ int Jim_CommandMatchObj(Jim_Interp *interp, Jim_Obj *commandObj, Jim_Obj *patter return eq; } -enum -{ SWITCH_EXACT, SWITCH_GLOB, SWITCH_RE, SWITCH_CMD }; - /* [switch] */ static int Jim_SwitchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { + enum { SWITCH_EXACT, SWITCH_GLOB, SWITCH_RE, SWITCH_CMD }; int matchOpt = SWITCH_EXACT, opt = 1, patCount, i; - Jim_Obj *command = 0, *const *caseList = 0, *strObj; - Jim_Obj *script = 0; + Jim_Obj *command = NULL, *scriptObj = NULL, *strObj; + Jim_Obj **caseList; if (argc < 3) { wrongnumargs: @@ -12320,16 +12137,13 @@ static int Jim_SwitchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *a strObj = argv[opt++]; patCount = argc - opt; if (patCount == 1) { - Jim_Obj **vector; - - JimListGetElements(interp, argv[opt], &patCount, &vector); - caseList = vector; + JimListGetElements(interp, argv[opt], &patCount, &caseList); } else - caseList = &argv[opt]; + caseList = (Jim_Obj **)&argv[opt]; if (patCount == 0 || patCount % 2 != 0) goto wrongnumargs; - for (i = 0; script == 0 && i < patCount; i += 2) { + for (i = 0; scriptObj == NULL && i < patCount; i += 2) { Jim_Obj *patObj = caseList[i]; if (!Jim_CompareStringImmediate(interp, patObj, "default") @@ -12337,11 +12151,11 @@ static int Jim_SwitchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *a switch (matchOpt) { case SWITCH_EXACT: if (Jim_StringEqObj(strObj, patObj)) - script = caseList[i + 1]; + scriptObj = caseList[i + 1]; break; case SWITCH_GLOB: if (Jim_StringMatchObj(interp, patObj, strObj, 0)) - script = caseList[i + 1]; + scriptObj = caseList[i + 1]; break; case SWITCH_RE: command = Jim_NewStringObj(interp, "regexp", -1); @@ -12353,34 +12167,31 @@ static int Jim_SwitchCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *a * make sure to reconvert the object into a list * again. Only for the single-list style [switch]. */ if (argc - opt == 1) { - Jim_Obj **vector; - - JimListGetElements(interp, argv[opt], &patCount, &vector); - caseList = vector; + JimListGetElements(interp, argv[opt], &patCount, &caseList); } /* command is here already decref'd */ if (rc < 0) { return -rc; } if (rc) - script = caseList[i + 1]; + scriptObj = caseList[i + 1]; break; } } } else { - script = caseList[i + 1]; + scriptObj = caseList[i + 1]; } } - for (; i < patCount && Jim_CompareStringImmediate(interp, script, "-"); i += 2) - script = caseList[i + 1]; - if (script && Jim_CompareStringImmediate(interp, script, "-")) { + for (; i < patCount && Jim_CompareStringImmediate(interp, scriptObj, "-"); i += 2) + scriptObj = caseList[i + 1]; + if (scriptObj && Jim_CompareStringImmediate(interp, scriptObj, "-")) { Jim_SetResultFormatted(interp, "no body specified for pattern \"%#s\"", caseList[i - 2]); return JIM_ERR; } Jim_SetEmptyResult(interp); - if (script) { - return Jim_EvalObj(interp, script); + if (scriptObj) { + return Jim_EvalObj(interp, scriptObj); } return JIM_OK; } @@ -12684,18 +12495,11 @@ static int Jim_LreplaceCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const * <elements before first> <supplied elements> <elements after last> */ - /* Check to see if trying to replace past the end of the list */ - if (first < len) { - /* OK. Not past the end */ - } - else if (len == 0) { - /* Special for empty list, adjust first to 0 */ - first = 0; - } - else { - Jim_SetResultString(interp, "list doesn't contain element ", -1); - Jim_AppendObj(interp, Jim_GetResult(interp), argv[2]); - return JIM_ERR; + /* Trying to replace past the end of the list means end of list + * See TIP #505 + */ + if (first > len) { + first = len; } /* Add the first set of elements */ @@ -12739,6 +12543,7 @@ static int Jim_LsortCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const arg Jim_Obj *resObj; int i; int retCode; + int shared; struct lsort_info info; @@ -12804,12 +12609,14 @@ static int Jim_LsortCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const arg break; } } - resObj = Jim_DuplicateObj(interp, argv[argc - 1]); + resObj = argv[argc - 1]; + if ((shared = Jim_IsShared(resObj))) + resObj = Jim_DuplicateObj(interp, resObj); retCode = ListSortElements(interp, resObj, &info); if (retCode == JIM_OK) { Jim_SetResult(interp, resObj); } - else { + else if (shared) { Jim_FreeNewObj(interp, resObj); } return retCode; @@ -12856,6 +12663,33 @@ static int Jim_AppendCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *a return JIM_OK; } +#if defined(JIM_DEBUG_COMMAND) && !defined(JIM_BOOTSTRAP) +/** + * Returns a zero-refcount list describing the expression at 'node' + */ +static Jim_Obj *JimGetExprAsList(Jim_Interp *interp, struct JimExprNode *node) +{ + Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0); + + Jim_ListAppendElement(interp, listObjPtr, Jim_NewStringObj(interp, jim_tt_name(node->type), -1)); + if (TOKEN_IS_EXPR_OP(node->type)) { + if (node->left) { + Jim_ListAppendElement(interp, listObjPtr, JimGetExprAsList(interp, node->left)); + } + if (node->right) { + Jim_ListAppendElement(interp, listObjPtr, JimGetExprAsList(interp, node->right)); + } + if (node->ternary) { + Jim_ListAppendElement(interp, listObjPtr, JimGetExprAsList(interp, node->ternary)); + } + } + else { + Jim_ListAppendElement(interp, listObjPtr, node->objPtr); + } + return listObjPtr; +} +#endif /* JIM_DEBUG_COMMAND && !JIM_BOOTSTRAP */ + /* [debug] */ static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { @@ -12877,7 +12711,7 @@ static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar return JIM_ERR; } if (Jim_GetEnum(interp, argv[1], options, &option, "subcommand", JIM_ERRMSG) != JIM_OK) - return JIM_ERR; + return Jim_CheckShowCommands(interp, argv[1], options); if (option == OPT_REFCOUNT) { if (argc != 3) { Jim_WrongNumArgs(interp, 2, argv, "object"); @@ -12984,7 +12818,7 @@ static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar return JIM_OK; } else if (option == OPT_EXPRLEN) { - ExprByteCode *expr; + struct ExprTree *expr; if (argc != 3) { Jim_WrongNumArgs(interp, 2, argv, "expression"); @@ -12997,9 +12831,7 @@ static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar return JIM_OK; } else if (option == OPT_EXPRBC) { - Jim_Obj *objPtr; - ExprByteCode *expr; - int i; + struct ExprTree *expr; if (argc != 3) { Jim_WrongNumArgs(interp, 2, argv, "expression"); @@ -13008,55 +12840,7 @@ static int Jim_DebugCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar expr = JimGetExpression(interp, argv[2]); if (expr == NULL) return JIM_ERR; - objPtr = Jim_NewListObj(interp, NULL, 0); - for (i = 0; i < expr->len; i++) { - const char *type; - const Jim_ExprOperator *op; - Jim_Obj *obj = expr->token[i].objPtr; - - switch (expr->token[i].type) { - case JIM_TT_EXPR_INT: - type = "int"; - break; - case JIM_TT_EXPR_DOUBLE: - type = "double"; - break; - case JIM_TT_EXPR_BOOLEAN: - type = "boolean"; - break; - case JIM_TT_CMD: - type = "command"; - break; - case JIM_TT_VAR: - type = "variable"; - break; - case JIM_TT_DICTSUGAR: - type = "dictsugar"; - break; - case JIM_TT_EXPRSUGAR: - type = "exprsugar"; - break; - case JIM_TT_ESC: - type = "subst"; - break; - case JIM_TT_STR: - type = "string"; - break; - default: - op = JimExprOperatorInfoByOpcode(expr->token[i].type); - if (op == NULL) { - type = "private"; - } - else { - type = "operator"; - } - obj = Jim_NewStringObj(interp, op ? op->name : "", -1); - break; - } - Jim_ListAppendElement(interp, objPtr, Jim_NewStringObj(interp, type, -1)); - Jim_ListAppendElement(interp, objPtr, obj); - } - Jim_SetResult(interp, objPtr); + Jim_SetResult(interp, JimGetExprAsList(interp, expr->expr)); return JIM_OK; } else { @@ -13144,18 +12928,17 @@ static int Jim_UplevelCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const * /* [expr] */ static int Jim_ExprCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - Jim_Obj *exprResultPtr; int retcode; if (argc == 2) { - retcode = Jim_EvalExpression(interp, argv[1], &exprResultPtr); + retcode = Jim_EvalExpression(interp, argv[1]); } else if (argc > 2) { Jim_Obj *objPtr; objPtr = Jim_ConcatObj(interp, argc - 1, argv + 1); Jim_IncrRefCount(objPtr); - retcode = Jim_EvalExpression(interp, objPtr, &exprResultPtr); + retcode = Jim_EvalExpression(interp, objPtr); Jim_DecrRefCount(interp, objPtr); } else { @@ -13164,8 +12947,6 @@ static int Jim_ExprCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *arg } if (retcode != JIM_OK) return retcode; - Jim_SetResult(interp, exprResultPtr); - Jim_DecrRefCount(interp, exprResultPtr); return JIM_OK; } @@ -13629,7 +13410,7 @@ static int Jim_StringCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *a } if (Jim_GetEnum(interp, argv[1], options, &option, NULL, JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) - return JIM_ERR; + return Jim_CheckShowCommands(interp, argv[1], options); switch (option) { case OPT_LENGTH: @@ -14269,55 +14050,43 @@ static int Jim_RenameCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *a return Jim_RenameCommand(interp, Jim_String(argv[1]), Jim_String(argv[2])); } -#define JIM_DICTMATCH_VALUES 0x0001 - -typedef void JimDictMatchCallbackType(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_HashEntry *he, int type); - -static void JimDictMatchKeys(Jim_Interp *interp, Jim_Obj *listObjPtr, Jim_HashEntry *he, int type) -{ - Jim_ListAppendElement(interp, listObjPtr, (Jim_Obj *)he->key); - if (type & JIM_DICTMATCH_VALUES) { - Jim_ListAppendElement(interp, listObjPtr, Jim_GetHashEntryVal(he)); - } -} +#define JIM_DICTMATCH_KEYS 0x0001 +#define JIM_DICTMATCH_VALUES 0x002 /** - * Like JimHashtablePatternMatch, but for dictionaries. + * match_type must be one of JIM_DICTMATCH_KEYS or JIM_DICTMATCH_VALUES + * return_types should be either or both */ -static Jim_Obj *JimDictPatternMatch(Jim_Interp *interp, Jim_HashTable *ht, Jim_Obj *patternObjPtr, - JimDictMatchCallbackType *callback, int type) +int Jim_DictMatchTypes(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *patternObj, int match_type, int return_types) { Jim_HashEntry *he; - Jim_Obj *listObjPtr = Jim_NewListObj(interp, NULL, 0); - - /* Check for the non-pattern case. We can do this much more efficiently. */ + Jim_Obj *listObjPtr; Jim_HashTableIterator htiter; - JimInitHashTableIterator(ht, &htiter); - while ((he = Jim_NextHashEntry(&htiter)) != NULL) { - if (patternObjPtr == NULL || JimGlobMatch(Jim_String(patternObjPtr), Jim_String((Jim_Obj *)he->key), 0)) { - callback(interp, listObjPtr, he, type); - } - } - - return listObjPtr; -} - -int Jim_DictKeys(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *patternObjPtr) -{ if (SetDictFromAny(interp, objPtr) != JIM_OK) { return JIM_ERR; } - Jim_SetResult(interp, JimDictPatternMatch(interp, objPtr->internalRep.ptr, patternObjPtr, JimDictMatchKeys, 0)); - return JIM_OK; -} -int Jim_DictValues(Jim_Interp *interp, Jim_Obj *objPtr, Jim_Obj *patternObjPtr) -{ - if (SetDictFromAny(interp, objPtr) != JIM_OK) { - return JIM_ERR; + listObjPtr = Jim_NewListObj(interp, NULL, 0); + + JimInitHashTableIterator(objPtr->internalRep.ptr, &htiter); + while ((he = Jim_NextHashEntry(&htiter)) != NULL) { + if (patternObj) { + Jim_Obj *matchObj = (match_type == JIM_DICTMATCH_KEYS) ? (Jim_Obj *)he->key : Jim_GetHashEntryVal(he); + if (!JimGlobMatch(Jim_String(patternObj), Jim_String(matchObj), 0)) { + /* no match */ + continue; + } + } + if (return_types & JIM_DICTMATCH_KEYS) { + Jim_ListAppendElement(interp, listObjPtr, (Jim_Obj *)he->key); + } + if (return_types & JIM_DICTMATCH_VALUES) { + Jim_ListAppendElement(interp, listObjPtr, Jim_GetHashEntryVal(he)); + } } - Jim_SetResult(interp, JimDictPatternMatch(interp, objPtr->internalRep.ptr, patternObjPtr, JimDictMatchKeys, JIM_DICTMATCH_VALUES)); + + Jim_SetResult(interp, listObjPtr); return JIM_OK; } @@ -14329,10 +14098,46 @@ int Jim_DictSize(Jim_Interp *interp, Jim_Obj *objPtr) return ((Jim_HashTable *)objPtr->internalRep.ptr)->used; } +/** + * Must be called with at least one object. + * Returns the new dictionary, or NULL on error. + */ +Jim_Obj *Jim_DictMerge(Jim_Interp *interp, int objc, Jim_Obj *const *objv) +{ + Jim_Obj *objPtr = Jim_NewDictObj(interp, NULL, 0); + int i; + + JimPanic((objc == 0, "Jim_DictMerge called with objc=0")); + + /* Note that we don't optimise the trivial case of a single argument */ + + for (i = 0; i < objc; i++) { + Jim_HashTable *ht; + Jim_HashTableIterator htiter; + Jim_HashEntry *he; + + if (SetDictFromAny(interp, objv[i]) != JIM_OK) { + Jim_FreeNewObj(interp, objPtr); + return NULL; + } + ht = objv[i]->internalRep.ptr; + JimInitHashTableIterator(ht, &htiter); + while ((he = Jim_NextHashEntry(&htiter)) != NULL) { + Jim_ReplaceHashEntry(objPtr->internalRep.ptr, Jim_GetHashEntryKey(he), Jim_GetHashEntryVal(he)); + } + } + return objPtr; +} + int Jim_DictInfo(Jim_Interp *interp, Jim_Obj *objPtr) { Jim_HashTable *ht; unsigned int i; + char buffer[100]; + int sum = 0; + int nonzero_count = 0; + Jim_Obj *output; + int bucket_counts[11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; if (SetDictFromAny(interp, objPtr) != JIM_OK) { return JIM_ERR; @@ -14341,21 +14146,36 @@ int Jim_DictInfo(Jim_Interp *interp, Jim_Obj *objPtr) ht = (Jim_HashTable *)objPtr->internalRep.ptr; /* Note that this uses internal knowledge of the hash table */ - printf("%d entries in table, %d buckets\n", ht->used, ht->size); + snprintf(buffer, sizeof(buffer), "%d entries in table, %d buckets\n", ht->used, ht->size); + output = Jim_NewStringObj(interp, buffer, -1); for (i = 0; i < ht->size; i++) { Jim_HashEntry *he = ht->table[i]; - - if (he) { - printf("%d: ", i); - - while (he) { - printf(" %s", Jim_String(he->key)); - he = he->next; - } - printf("\n"); + int entries = 0; + while (he) { + entries++; + he = he->next; + } + if (entries > 9) { + bucket_counts[10]++; + } + else { + bucket_counts[entries]++; + } + if (entries) { + sum += entries; + nonzero_count++; } } + for (i = 0; i < 10; i++) { + snprintf(buffer, sizeof(buffer), "number of buckets with %d entries: %d\n", i, bucket_counts[i]); + Jim_AppendString(interp, output, buffer, -1); + } + snprintf(buffer, sizeof(buffer), "number of buckets with 10 or more entries: %d\n", bucket_counts[10]); + Jim_AppendString(interp, output, buffer, -1); + snprintf(buffer, sizeof(buffer), "average search distance for entry: %.1f", nonzero_count ? (double)sum / nonzero_count : 0.0); + Jim_AppendString(interp, output, buffer, -1); + Jim_SetResult(interp, output); return JIM_OK; } @@ -14369,10 +14189,66 @@ static int Jim_EvalEnsemble(Jim_Interp *interp, const char *basecmd, const char return Jim_EvalObjPrefix(interp, prefixObj, argc, argv); } +/** + * Implements the [dict with] command + */ +static int JimDictWith(Jim_Interp *interp, Jim_Obj *dictVarName, Jim_Obj *const *keyv, int keyc, Jim_Obj *scriptObj) +{ + int i; + Jim_Obj *objPtr; + Jim_Obj *dictObj; + Jim_Obj **dictValues; + int len; + int ret = JIM_OK; + + /* Open up the appropriate level of the dictionary */ + dictObj = Jim_GetVariable(interp, dictVarName, JIM_ERRMSG); + if (dictObj == NULL || Jim_DictKeysVector(interp, dictObj, keyv, keyc, &objPtr, JIM_ERRMSG) != JIM_OK) { + return JIM_ERR; + } + /* Set the local variables */ + if (Jim_DictPairs(interp, objPtr, &dictValues, &len) == JIM_ERR) { + return JIM_ERR; + } + for (i = 0; i < len; i += 2) { + if (Jim_SetVariable(interp, dictValues[i], dictValues[i + 1]) == JIM_ERR) { + Jim_Free(dictValues); + return JIM_ERR; + } + } + + /* As an optimisation, if the script is empty, no need to evaluate it or update the dict */ + if (Jim_Length(scriptObj)) { + ret = Jim_EvalObj(interp, scriptObj); + + /* Now if the dictionary still exists, update it based on the local variables */ + if (ret == JIM_OK && Jim_GetVariable(interp, dictVarName, 0) != NULL) { + /* We need a copy of keyv with one extra element at the end for Jim_SetDictKeysVector() */ + Jim_Obj **newkeyv = Jim_Alloc(sizeof(*newkeyv) * (keyc + 1)); + for (i = 0; i < keyc; i++) { + newkeyv[i] = keyv[i]; + } + + for (i = 0; i < len; i += 2) { + /* This will be NULL if the variable no longer exists, thus deleting the variable */ + objPtr = Jim_GetVariable(interp, dictValues[i], 0); + newkeyv[keyc] = dictValues[i]; + Jim_SetDictKeysVector(interp, dictVarName, newkeyv, keyc + 1, objPtr, 0); + } + Jim_Free(newkeyv); + } + } + + Jim_Free(dictValues); + + return ret; +} + /* [dict] */ static int Jim_DictCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { Jim_Obj *objPtr; + int types = JIM_DICTMATCH_KEYS; int option; static const char * const options[] = { "create", "get", "set", "unset", "exists", "keys", "size", "info", @@ -14392,7 +14268,7 @@ static int Jim_DictCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *arg } if (Jim_GetEnum(interp, argv[1], options, &option, "subcommand", JIM_ERRMSG) != JIM_OK) { - return JIM_ERR; + return Jim_CheckShowCommands(interp, argv[1], options); } switch (option) { @@ -14439,12 +14315,15 @@ static int Jim_DictCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *arg } return JIM_OK; + case OPT_VALUES: + types = JIM_DICTMATCH_VALUES; + /* fallthru */ case OPT_KEYS: if (argc != 3 && argc != 4) { Jim_WrongNumArgs(interp, 2, argv, "dictionary ?pattern?"); return JIM_ERR; } - return Jim_DictKeys(interp, argv[2], argc == 4 ? argv[3] : NULL); + return Jim_DictMatchTypes(interp, argv[2], argc == 4 ? argv[3] : NULL, types, types); case OPT_SIZE: if (argc != 3) { @@ -14461,11 +14340,12 @@ static int Jim_DictCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *arg if (argc == 2) { return JIM_OK; } - if (Jim_DictSize(interp, argv[2]) < 0) { + objPtr = Jim_DictMerge(interp, argc - 2, argv + 2); + if (objPtr == NULL) { return JIM_ERR; } - /* Handle as ensemble */ - break; + Jim_SetResult(interp, objPtr); + return JIM_OK; case OPT_UPDATE: if (argc < 6 || argc % 2) { @@ -14489,6 +14369,13 @@ static int Jim_DictCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *arg return JIM_ERR; } return Jim_DictInfo(interp, argv[2]); + + case OPT_WITH: + if (argc < 4) { + Jim_WrongNumArgs(interp, 2, argv, "dictVar ?key ...? script"); + return JIM_ERR; + } + return JimDictWith(interp, argv[2], argv + 3, argc - 4, argv[argc - 1]); } /* Handle command as an ensemble */ return Jim_EvalEnsemble(interp, "dict", options[option], argc - 2, argv + 2); @@ -14571,9 +14458,8 @@ static int Jim_InfoCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *arg Jim_WrongNumArgs(interp, 1, argv, "subcommand ?args ...?"); return JIM_ERR; } - if (Jim_GetEnum(interp, argv[1], commands, &cmd, "subcommand", JIM_ERRMSG | JIM_ENUM_ABBREV) - != JIM_OK) { - return JIM_ERR; + if (Jim_GetEnum(interp, argv[1], commands, &cmd, "subcommand", JIM_ERRMSG | JIM_ENUM_ABBREV) != JIM_OK) { + return Jim_CheckShowCommands(interp, argv[1], commands); } /* Test for the most common commands first, just in case it makes a difference */ @@ -15448,36 +15334,87 @@ void Jim_MakeErrorMessage(Jim_Interp *interp) Jim_EvalObjVector(interp, 2, argv); } -static void JimSetFailedEnumResult(Jim_Interp *interp, const char *arg, const char *badtype, - const char *prefix, const char *const *tablePtr, const char *name) +/* + * Given a null terminated array of strings, returns an allocated, sorted + * copy of the array. + */ +static char **JimSortStringTable(const char *const *tablePtr) { int count; char **tablePtrSorted; - int i; + /* Find the size of the table */ for (count = 0; tablePtr[count]; count++) { } + /* Allocate one extra for the terminating NULL pointer */ + tablePtrSorted = Jim_Alloc(sizeof(char *) * (count + 1)); + memcpy(tablePtrSorted, tablePtr, sizeof(char *) * count); + qsort(tablePtrSorted, count, sizeof(char *), qsortCompareStringPointers); + tablePtrSorted[count] = NULL; + + return tablePtrSorted; +} + +static void JimSetFailedEnumResult(Jim_Interp *interp, const char *arg, const char *badtype, + const char *prefix, const char *const *tablePtr, const char *name) +{ + char **tablePtrSorted; + int i; + if (name == NULL) { name = "option"; } Jim_SetResultFormatted(interp, "%s%s \"%s\": must be ", badtype, name, arg); - tablePtrSorted = Jim_Alloc(sizeof(char *) * count); - memcpy(tablePtrSorted, tablePtr, sizeof(char *) * count); - qsort(tablePtrSorted, count, sizeof(char *), qsortCompareStringPointers); - for (i = 0; i < count; i++) { - if (i + 1 == count && count > 1) { + tablePtrSorted = JimSortStringTable(tablePtr); + for (i = 0; tablePtrSorted[i]; i++) { + if (tablePtrSorted[i + 1] == NULL && i > 0) { Jim_AppendString(interp, Jim_GetResult(interp), "or ", -1); } Jim_AppendStrings(interp, Jim_GetResult(interp), prefix, tablePtrSorted[i], NULL); - if (i + 1 != count) { + if (tablePtrSorted[i + 1]) { Jim_AppendString(interp, Jim_GetResult(interp), ", ", -1); } } Jim_Free(tablePtrSorted); } + +/* + * If objPtr is "-commands" sets the Jim result as a sorted list of options in the table + * and returns JIM_OK. + * + * Otherwise returns JIM_ERR. + */ +int Jim_CheckShowCommands(Jim_Interp *interp, Jim_Obj *objPtr, const char *const *tablePtr) +{ + if (Jim_CompareStringImmediate(interp, objPtr, "-commands")) { + int i; + char **tablePtrSorted = JimSortStringTable(tablePtr); + Jim_SetResult(interp, Jim_NewListObj(interp, NULL, 0)); + for (i = 0; tablePtrSorted[i]; i++) { + Jim_ListAppendElement(interp, Jim_GetResult(interp), Jim_NewStringObj(interp, tablePtrSorted[i], -1)); + } + Jim_Free(tablePtrSorted); + return JIM_OK; + } + return JIM_ERR; +} + +/* internal rep is stored in ptrIntvalue + * ptr = tablePtr + * int1 = flags + * int2 = index + */ +static const Jim_ObjType getEnumObjType = { + "get-enum", + NULL, + NULL, + NULL, + JIM_TYPE_REFERENCES +}; + int Jim_GetEnum(Jim_Interp *interp, Jim_Obj *objPtr, const char *const *tablePtr, int *indexPtr, const char *name, int flags) { @@ -15486,15 +15423,24 @@ int Jim_GetEnum(Jim_Interp *interp, Jim_Obj *objPtr, int i; int match = -1; int arglen; - const char *arg = Jim_GetString(objPtr, &arglen); + const char *arg; + + if (objPtr->typePtr == &getEnumObjType) { + if (objPtr->internalRep.ptrIntValue.ptr == tablePtr && objPtr->internalRep.ptrIntValue.int1 == flags) { + *indexPtr = objPtr->internalRep.ptrIntValue.int2; + return JIM_OK; + } + } + + arg = Jim_GetString(objPtr, &arglen); *indexPtr = -1; for (entryPtr = tablePtr, i = 0; *entryPtr != NULL; entryPtr++, i++) { if (Jim_CompareStringImmediate(interp, objPtr, *entryPtr)) { /* Found an exact match */ - *indexPtr = i; - return JIM_OK; + match = i; + goto found; } if (flags & JIM_ENUM_ABBREV) { /* Accept an unambiguous abbreviation. @@ -15515,6 +15461,14 @@ int Jim_GetEnum(Jim_Interp *interp, Jim_Obj *objPtr, /* If we had an unambiguous partial match */ if (match >= 0) { + found: + /* Record the match in the object */ + Jim_FreeIntRep(interp, objPtr); + objPtr->typePtr = &getEnumObjType; + objPtr->internalRep.ptrIntValue.ptr = (void *)tablePtr; + objPtr->internalRep.ptrIntValue.int1 = flags; + objPtr->internalRep.ptrIntValue.int2 = match; + /* Return the result */ *indexPtr = match; return JIM_OK; } @@ -15561,6 +15515,8 @@ int Jim_IsList(Jim_Obj *objPtr) * e.g. Jim_SetResultFormatted(interp, "Bad option \"%#s\" in proc \"%#s\"", optionObjPtr, procNamePtr); * * Note: We take advantage of the fact that printf has the same behaviour for both %s and %#s + * + * Note that any Jim_Obj parameters with zero ref count will be freed as a result of this call. */ void Jim_SetResultFormatted(Jim_Interp *interp, const char *format, ...) { @@ -15569,6 +15525,8 @@ void Jim_SetResultFormatted(Jim_Interp *interp, const char *format, ...) int extra = 0; int n = 0; const char *params[5]; + int nobjparam = 0; + Jim_Obj *objparam[5]; char *buf; va_list args; int i; @@ -15587,6 +15545,8 @@ void Jim_SetResultFormatted(Jim_Interp *interp, const char *format, ...) Jim_Obj *objPtr = va_arg(args, Jim_Obj *); params[n] = Jim_GetString(objPtr, &l); + objparam[nobjparam++] = objPtr; + Jim_IncrRefCount(objPtr); } else { if (format[i] == '%') { @@ -15605,6 +15565,10 @@ void Jim_SetResultFormatted(Jim_Interp *interp, const char *format, ...) va_end(args); Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, len)); + + for (i = 0; i < nobjparam; i++) { + Jim_DecrRefCount(interp, objparam[i]); + } } /* stubs */ |