diff options
Diffstat (limited to 'generic/tclCompCmds.c')
-rw-r--r-- | generic/tclCompCmds.c | 4979 |
1 files changed, 2403 insertions, 2576 deletions
diff --git a/generic/tclCompCmds.c b/generic/tclCompCmds.c index 92381a9..d1d7a80 100644 --- a/generic/tclCompCmds.c +++ b/generic/tclCompCmds.c @@ -1,47 +1,69 @@ -/* +/* * tclCompCmds.c -- * - * This file contains compilation procedures that compile various - * Tcl commands into a sequence of instructions ("bytecodes"). + * This file contains compilation procedures that compile various Tcl + * commands into a sequence of instructions ("bytecodes"). * * Copyright (c) 1997-1998 Sun Microsystems, Inc. * Copyright (c) 2001 by Kevin B. Kenny. All rights reserved. * Copyright (c) 2002 ActiveState Corporation. + * Copyright (c) 2004-2013 by Donal K. Fellows. * - * See the file "license.terms" for information on usage and redistribution - * of this file, and for a DISCLAIMER OF ALL WARRANTIES. - * - * RCS: @(#) $Id: tclCompCmds.c,v 1.59 2004/10/18 21:15:37 dgp Exp $ + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #include "tclCompile.h" +#include <assert.h> /* * Prototypes for procedures defined later in this file: */ -static ClientData DupForeachInfo _ANSI_ARGS_((ClientData clientData)); -static void FreeForeachInfo _ANSI_ARGS_((ClientData clientData)); -static int PushVarName _ANSI_ARGS_((Tcl_Interp *interp, - Tcl_Token *varTokenPtr, CompileEnv *envPtr, int flags, - int *localIndexPtr, int *simpleVarNamePtr, int *isScalarPtr)); +static ClientData DupDictUpdateInfo(ClientData clientData); +static void FreeDictUpdateInfo(ClientData clientData); +static void PrintDictUpdateInfo(ClientData clientData, + Tcl_Obj *appendObj, ByteCode *codePtr, + unsigned int pcOffset); +static ClientData DupForeachInfo(ClientData clientData); +static void FreeForeachInfo(ClientData clientData); +static void PrintForeachInfo(ClientData clientData, + Tcl_Obj *appendObj, ByteCode *codePtr, + unsigned int pcOffset); +static void PrintNewForeachInfo(ClientData clientData, + Tcl_Obj *appendObj, ByteCode *codePtr, + unsigned int pcOffset); +static int CompileEachloopCmd(Tcl_Interp *interp, + Tcl_Parse *parsePtr, Command *cmdPtr, + CompileEnv *envPtr, int collect); +static int CompileDictEachCmd(Tcl_Interp *interp, + Tcl_Parse *parsePtr, Command *cmdPtr, + struct CompileEnv *envPtr, int collect); /* - * Flags bits used by PushVarName. + * The structures below define the AuxData types defined in this file. */ -#define TCL_CREATE_VAR 1 /* Create a compiled local if none is found */ -#define TCL_NO_LARGE_INDEX 2 /* Do not return localIndex value > 255 */ +const AuxDataType tclForeachInfoType = { + "ForeachInfo", /* name */ + DupForeachInfo, /* dupProc */ + FreeForeachInfo, /* freeProc */ + PrintForeachInfo /* printProc */ +}; -/* - * The structures below define the AuxData types defined in this file. - */ +const AuxDataType tclNewForeachInfoType = { + "NewForeachInfo", /* name */ + DupForeachInfo, /* dupProc */ + FreeForeachInfo, /* freeProc */ + PrintNewForeachInfo /* printProc */ +}; -AuxDataType tclForeachInfoType = { - "ForeachInfo", /* name */ - DupForeachInfo, /* dupProc */ - FreeForeachInfo /* freeProc */ +const AuxDataType tclDictUpdateInfoType = { + "DictUpdateInfo", /* name */ + DupDictUpdateInfo, /* dupProc */ + FreeDictUpdateInfo, /* freeProc */ + PrintDictUpdateInfo /* printProc */ }; /* @@ -52,102 +74,372 @@ AuxDataType tclForeachInfoType = { * Procedure called to compile the "append" command. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "append" command - * at runtime. + * Instructions are added to envPtr to execute the "append" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileAppendCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileAppendCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { Tcl_Token *varTokenPtr, *valueTokenPtr; - int simpleVarName, isScalar, localIndex, numWords; + int isScalar, localIndex, numWords, i; + DefineLineInformation; /* TIP #280 */ + /* TODO: Consider support for compiling expanded args. */ numWords = parsePtr->numWords; if (numWords == 1) { - return TCL_OUT_LINE_COMPILE; + return TCL_ERROR; } else if (numWords == 2) { /* * append varName == set varName */ - return TclCompileSetCmd(interp, parsePtr, envPtr); + + return TclCompileSetCmd(interp, parsePtr, cmdPtr, envPtr); } else if (numWords > 3) { /* - * APPEND instructions currently only handle one value + * APPEND instructions currently only handle one value, but we can + * handle some multi-value cases by stringing them together. */ - return TCL_OUT_LINE_COMPILE; + + goto appendMultiple; } /* - * Decide if we can use a frame slot for the var/array name or if we - * need to emit code to compute and push the name at runtime. We use a - * frame slot (entry in the array of local vars) if we are compiling a - * procedure body and if the name is simple text that does not include - * namespace qualifiers. + * Decide if we can use a frame slot for the var/array name or if we need + * to emit code to compute and push the name at runtime. We use a frame + * slot (entry in the array of local vars) if we are compiling a procedure + * body and if the name is simple text that does not include namespace + * qualifiers. */ - varTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); + varTokenPtr = TokenAfter(parsePtr->tokenPtr); - PushVarName(interp, varTokenPtr, envPtr, TCL_CREATE_VAR, - &localIndex, &simpleVarName, &isScalar); + PushVarNameWord(interp, varTokenPtr, envPtr, 0, + &localIndex, &isScalar, 1); /* - * We are doing an assignment, otherwise TclCompileSetCmd was called, - * so push the new value. This will need to be extended to push a - * value for each argument. + * We are doing an assignment, otherwise TclCompileSetCmd was called, so + * push the new value. This will need to be extended to push a value for + * each argument. */ - if (numWords > 2) { - valueTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - if (valueTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, - valueTokenPtr[1].start, valueTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, valueTokenPtr+1, - valueTokenPtr->numComponents, envPtr); - } - } + valueTokenPtr = TokenAfter(varTokenPtr); + CompileWord(envPtr, valueTokenPtr, interp, 2); /* * Emit instructions to set/get the variable. */ - if (simpleVarName) { if (isScalar) { - if (localIndex >= 0) { - if (localIndex <= 255) { - TclEmitInstInt1(INST_APPEND_SCALAR1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_APPEND_SCALAR4, localIndex, envPtr); - } - } else { + if (localIndex < 0) { TclEmitOpcode(INST_APPEND_STK, envPtr); + } else { + Emit14Inst(INST_APPEND_SCALAR, localIndex, envPtr); } } else { - if (localIndex >= 0) { - if (localIndex <= 255) { - TclEmitInstInt1(INST_APPEND_ARRAY1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_APPEND_ARRAY4, localIndex, envPtr); - } - } else { + if (localIndex < 0) { TclEmitOpcode(INST_APPEND_ARRAY_STK, envPtr); + } else { + Emit14Inst(INST_APPEND_ARRAY, localIndex, envPtr); } } + + return TCL_OK; + + appendMultiple: + /* + * Can only handle the case where we are appending to a local scalar when + * there are multiple values to append. Fortunately, this is common. + */ + + varTokenPtr = TokenAfter(parsePtr->tokenPtr); + PushVarNameWord(interp, varTokenPtr, envPtr, TCL_NO_ELEMENT, + &localIndex, &isScalar, 1); + if (!isScalar || localIndex < 0) { + return TCL_ERROR; + } + + /* + * Definitely appending to a local scalar; generate the words and append + * them. + */ + + valueTokenPtr = TokenAfter(varTokenPtr); + for (i = 2 ; i < numWords ; i++) { + CompileWord(envPtr, valueTokenPtr, interp, i); + valueTokenPtr = TokenAfter(valueTokenPtr); + } + TclEmitInstInt4( INST_REVERSE, numWords-2, envPtr); + for (i = 2 ; i < numWords ;) { + Emit14Inst( INST_APPEND_SCALAR, localIndex, envPtr); + if (++i < numWords) { + TclEmitOpcode(INST_POP, envPtr); + } + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileArray*Cmd -- + * + * Functions called to compile "array" sucommands. + * + * Results: + * All return TCL_OK for a successful compile, and TCL_ERROR to defer + * evaluation to runtime. + * + * Side effects: + * Instructions are added to envPtr to execute the "array" subcommand at + * runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileArrayExistsCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + DefineLineInformation; /* TIP #280 */ + Tcl_Token *tokenPtr; + int isScalar, localIndex; + + if (parsePtr->numWords != 2) { + return TCL_ERROR; + } + + tokenPtr = TokenAfter(parsePtr->tokenPtr); + PushVarNameWord(interp, tokenPtr, envPtr, TCL_NO_ELEMENT, + &localIndex, &isScalar, 1); + if (!isScalar) { + return TCL_ERROR; + } + + if (localIndex >= 0) { + TclEmitInstInt4(INST_ARRAY_EXISTS_IMM, localIndex, envPtr); } else { - TclEmitOpcode(INST_APPEND_STK, envPtr); + TclEmitOpcode( INST_ARRAY_EXISTS_STK, envPtr); + } + return TCL_OK; +} + +int +TclCompileArraySetCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + DefineLineInformation; /* TIP #280 */ + Tcl_Token *varTokenPtr, *dataTokenPtr; + int isScalar, localIndex, code = TCL_OK; + int isDataLiteral, isDataValid, isDataEven, len; + int keyVar, valVar, infoIndex; + int fwd, offsetBack, offsetFwd; + Tcl_Obj *literalObj; + ForeachInfo *infoPtr; + + if (parsePtr->numWords != 3) { + return TCL_ERROR; } + varTokenPtr = TokenAfter(parsePtr->tokenPtr); + dataTokenPtr = TokenAfter(varTokenPtr); + literalObj = Tcl_NewObj(); + isDataLiteral = TclWordKnownAtCompileTime(dataTokenPtr, literalObj); + isDataValid = (isDataLiteral + && Tcl_ListObjLength(NULL, literalObj, &len) == TCL_OK); + isDataEven = (isDataValid && (len & 1) == 0); + + /* + * Special case: literal odd-length argument is always an error. + */ + + if (isDataValid && !isDataEven) { + PushStringLiteral(envPtr, "list must have an even number of elements"); + PushStringLiteral(envPtr, "-errorcode {TCL ARGUMENT FORMAT}"); + TclEmitInstInt4(INST_RETURN_IMM, TCL_ERROR, envPtr); + TclEmitInt4( 0, envPtr); + goto done; + } + + /* + * Except for the special "ensure array" case below, when we're not in + * a proc, we cannot do a better compile than generic. + */ + + if (envPtr->procPtr == NULL && !(isDataEven && len == 0)) { + code = TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr); + goto done; + } + + PushVarNameWord(interp, varTokenPtr, envPtr, TCL_NO_ELEMENT, + &localIndex, &isScalar, 1); + if (!isScalar) { + code = TCL_ERROR; + goto done; + } + + /* + * Special case: literal empty value argument is just an "ensure array" + * operation. + */ + + if (isDataEven && len == 0) { + if (localIndex >= 0) { + TclEmitInstInt4(INST_ARRAY_EXISTS_IMM, localIndex, envPtr); + TclEmitInstInt1(INST_JUMP_TRUE1, 7, envPtr); + TclEmitInstInt4(INST_ARRAY_MAKE_IMM, localIndex, envPtr); + } else { + TclEmitOpcode( INST_DUP, envPtr); + TclEmitOpcode( INST_ARRAY_EXISTS_STK, envPtr); + TclEmitInstInt1(INST_JUMP_TRUE1, 5, envPtr); + TclEmitOpcode( INST_ARRAY_MAKE_STK, envPtr); + TclEmitInstInt1(INST_JUMP1, 3, envPtr); + /* Each branch decrements stack depth, but we only take one. */ + TclAdjustStackDepth(1, envPtr); + TclEmitOpcode( INST_POP, envPtr); + } + PushStringLiteral(envPtr, ""); + goto done; + } + + if (localIndex < 0) { + /* + * a non-local variable: upvar from a local one! This consumes the + * variable name that was left at stacktop. + */ + + localIndex = AnonymousLocal(envPtr); + PushStringLiteral(envPtr, "0"); + TclEmitInstInt4(INST_REVERSE, 2, envPtr); + TclEmitInstInt4(INST_UPVAR, localIndex, envPtr); + TclEmitOpcode(INST_POP, envPtr); + } + + /* + * Prepare for the internal foreach. + */ + + keyVar = AnonymousLocal(envPtr); + valVar = AnonymousLocal(envPtr); + + infoPtr = ckalloc(sizeof(ForeachInfo)); + infoPtr->numLists = 1; + infoPtr->varLists[0] = ckalloc(sizeof(ForeachVarList) + sizeof(int)); + infoPtr->varLists[0]->numVars = 2; + infoPtr->varLists[0]->varIndexes[0] = keyVar; + infoPtr->varLists[0]->varIndexes[1] = valVar; + infoIndex = TclCreateAuxData(infoPtr, &tclForeachInfoType, envPtr); + + /* + * Start issuing instructions to write to the array. + */ + + CompileWord(envPtr, dataTokenPtr, interp, 2); + if (!isDataLiteral || !isDataValid) { + /* + * Only need this safety check if we're handling a non-literal or list + * containing an invalid literal; with valid list literals, we've + * already checked (worth it because literals are a very common + * use-case with [array set]). + */ + + TclEmitOpcode( INST_DUP, envPtr); + TclEmitOpcode( INST_LIST_LENGTH, envPtr); + PushStringLiteral(envPtr, "1"); + TclEmitOpcode( INST_BITAND, envPtr); + offsetFwd = CurrentOffset(envPtr); + TclEmitInstInt1(INST_JUMP_FALSE1, 0, envPtr); + PushStringLiteral(envPtr, "list must have an even number of elements"); + PushStringLiteral(envPtr, "-errorcode {TCL ARGUMENT FORMAT}"); + TclEmitInstInt4(INST_RETURN_IMM, TCL_ERROR, envPtr); + TclEmitInt4( 0, envPtr); + TclAdjustStackDepth(-1, envPtr); + fwd = CurrentOffset(envPtr) - offsetFwd; + TclStoreInt1AtPtr(fwd, envPtr->codeStart+offsetFwd+1); + } + + TclEmitInstInt4(INST_ARRAY_EXISTS_IMM, localIndex, envPtr); + TclEmitInstInt1(INST_JUMP_TRUE1, 7, envPtr); + TclEmitInstInt4(INST_ARRAY_MAKE_IMM, localIndex, envPtr); + TclEmitInstInt4(INST_FOREACH_START, infoIndex, envPtr); + offsetBack = CurrentOffset(envPtr); + Emit14Inst( INST_LOAD_SCALAR, keyVar, envPtr); + Emit14Inst( INST_LOAD_SCALAR, valVar, envPtr); + Emit14Inst( INST_STORE_ARRAY, localIndex, envPtr); + TclEmitOpcode( INST_POP, envPtr); + infoPtr->loopCtTemp = offsetBack - CurrentOffset(envPtr); /*misuse */ + TclEmitOpcode( INST_FOREACH_STEP, envPtr); + TclEmitOpcode( INST_FOREACH_END, envPtr); + TclAdjustStackDepth(-3, envPtr); + PushStringLiteral(envPtr, ""); + + done: + Tcl_DecrRefCount(literalObj); + return code; +} + +int +TclCompileArrayUnsetCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + DefineLineInformation; /* TIP #280 */ + Tcl_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr); + int isScalar, localIndex; + + if (parsePtr->numWords != 2) { + return TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } + + PushVarNameWord(interp, tokenPtr, envPtr, TCL_NO_ELEMENT, + &localIndex, &isScalar, 1); + if (!isScalar) { + return TCL_ERROR; + } + + if (localIndex >= 0) { + TclEmitInstInt4(INST_ARRAY_EXISTS_IMM, localIndex, envPtr); + TclEmitInstInt1(INST_JUMP_FALSE1, 8, envPtr); + TclEmitInstInt1(INST_UNSET_SCALAR, 1, envPtr); + TclEmitInt4( localIndex, envPtr); + } else { + TclEmitOpcode( INST_DUP, envPtr); + TclEmitOpcode( INST_ARRAY_EXISTS_STK, envPtr); + TclEmitInstInt1(INST_JUMP_FALSE1, 6, envPtr); + TclEmitInstInt1(INST_UNSET_STK, 1, envPtr); + TclEmitInstInt1(INST_JUMP1, 3, envPtr); + /* Each branch decrements stack depth, but we only take one. */ + TclAdjustStackDepth(1, envPtr); + TclEmitOpcode( INST_POP, envPtr); + } + PushStringLiteral(envPtr, ""); return TCL_OK; } @@ -159,32 +451,53 @@ TclCompileAppendCmd(interp, parsePtr, envPtr) * Procedure called to compile the "break" command. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "break" command - * at runtime. + * Instructions are added to envPtr to execute the "break" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileBreakCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileBreakCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { + ExceptionRange *rangePtr; + ExceptionAux *auxPtr; + if (parsePtr->numWords != 1) { - return TCL_OUT_LINE_COMPILE; + return TCL_ERROR; } /* - * Emit a break instruction. + * Find the innermost exception range that contains this command. */ - TclEmitOpcode(INST_BREAK, envPtr); + rangePtr = TclGetInnermostExceptionRange(envPtr, TCL_BREAK, &auxPtr); + if (rangePtr && rangePtr->type == LOOP_EXCEPTION_RANGE) { + /* + * Found the target! No need for a nasty INST_BREAK here. + */ + + TclCleanupStackForBreakContinue(envPtr, auxPtr); + TclAddLoopBreakFixup(envPtr, auxPtr); + } else { + /* + * Emit a real break. + */ + + TclEmitOpcode(INST_BREAK, envPtr); + } + TclAdjustStackDepth(1, envPtr); + return TCL_OK; } @@ -196,3156 +509,2662 @@ TclCompileBreakCmd(interp, parsePtr, envPtr) * Procedure called to compile the "catch" command. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "catch" command - * at runtime. + * Instructions are added to envPtr to execute the "catch" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileCatchCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileCatchCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { JumpFixup jumpFixup; - Tcl_Token *cmdTokenPtr, *nameTokenPtr; - CONST char *name; - int localIndex, nameChars, range, startOffset; - int savedStackDepth = envPtr->currStackDepth; - + Tcl_Token *cmdTokenPtr, *resultNameTokenPtr, *optsNameTokenPtr; + int resultIndex, optsIndex, range, dropScript = 0; + DefineLineInformation; /* TIP #280 */ + int depth = TclGetStackDepth(envPtr); + /* - * If syntax does not match what we expect for [catch], do not - * compile. Let runtime checks determine if syntax has changed. + * If syntax does not match what we expect for [catch], do not compile. + * Let runtime checks determine if syntax has changed. */ - if ((parsePtr->numWords != 2) && (parsePtr->numWords != 3)) { - return TCL_OUT_LINE_COMPILE; + + if ((parsePtr->numWords < 2) || (parsePtr->numWords > 4)) { + return TCL_ERROR; } /* - * If a variable was specified and the catch command is at global level - * (not in a procedure), don't compile it inline: the payoff is - * too small. + * If variables were specified and the catch command is at global level + * (not in a procedure), don't compile it inline: the payoff is too small. */ - if ((parsePtr->numWords == 3) && (envPtr->procPtr == NULL)) { - return TCL_OUT_LINE_COMPILE; + if ((parsePtr->numWords >= 3) && !EnvHasLVT(envPtr)) { + return TCL_ERROR; } /* - * Make sure the variable name, if any, has no substitutions and just - * refers to a local scaler. + * Make sure the variable names, if any, have no substitutions and just + * refer to local scalars. */ - localIndex = -1; - cmdTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); - if (parsePtr->numWords == 3) { - nameTokenPtr = cmdTokenPtr + (cmdTokenPtr->numComponents + 1); - if (nameTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - name = nameTokenPtr[1].start; - nameChars = nameTokenPtr[1].size; - if (!TclIsLocalScalar(name, nameChars)) { - return TCL_OUT_LINE_COMPILE; + resultIndex = optsIndex = -1; + cmdTokenPtr = TokenAfter(parsePtr->tokenPtr); + if (parsePtr->numWords >= 3) { + resultNameTokenPtr = TokenAfter(cmdTokenPtr); + /* DGP */ + resultIndex = LocalScalarFromToken(resultNameTokenPtr, envPtr); + if (resultIndex < 0) { + return TCL_ERROR; + } + + /* DKF */ + if (parsePtr->numWords == 4) { + optsNameTokenPtr = TokenAfter(resultNameTokenPtr); + optsIndex = LocalScalarFromToken(optsNameTokenPtr, envPtr); + if (optsIndex < 0) { + return TCL_ERROR; } - localIndex = TclFindCompiledLocal(nameTokenPtr[1].start, - nameTokenPtr[1].size, /*create*/ 1, - /*flags*/ VAR_SCALAR, envPtr->procPtr); - } else { - return TCL_OUT_LINE_COMPILE; } } /* - * We will compile the catch command. Emit a beginCatch instruction at - * the start of the catch body: the subcommand it controls. + * We will compile the catch command. Declare the exception range that it + * uses. + * + * If the body is a simple word, compile a BEGIN_CATCH instruction, + * followed by the instructions to eval the body. + * Otherwise, compile instructions to substitute the body text before + * starting the catch, then BEGIN_CATCH, and then EVAL_STK to evaluate the + * substituted body. + * Care has to be taken to make sure that substitution happens outside the + * catch range so that errors in the substitution are not caught. + * [Bug 219184] + * The reason for duplicating the script is that EVAL_STK would otherwise + * begin by undeflowing the stack below the mark set by BEGIN_CATCH4. */ - envPtr->exceptDepth++; - envPtr->maxExceptDepth = - TclMax(envPtr->exceptDepth, envPtr->maxExceptDepth); range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); - TclEmitInstInt4(INST_BEGIN_CATCH4, range, envPtr); + if (cmdTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { + TclEmitInstInt4( INST_BEGIN_CATCH4, range, envPtr); + ExceptionRangeStarts(envPtr, range); + BODY(cmdTokenPtr, 1); + } else { + SetLineInformation(1); + CompileTokens(envPtr, cmdTokenPtr, interp); + TclEmitInstInt4( INST_BEGIN_CATCH4, range, envPtr); + ExceptionRangeStarts(envPtr, range); + TclEmitOpcode( INST_DUP, envPtr); + TclEmitInvoke(envPtr, INST_EVAL_STK); + /* drop the script */ + dropScript = 1; + TclEmitInstInt4( INST_REVERSE, 2, envPtr); + TclEmitOpcode( INST_POP, envPtr); + } + ExceptionRangeEnds(envPtr, range); + /* - * If the body is a simple word, compile the instructions to - * eval it. Otherwise, compile instructions to substitute its - * text without catching, a catch instruction that resets the - * stack to what it was before substituting the body, and then - * an instruction to eval the body. Care has to be taken to - * register the correct startOffset for the catch range so that - * errors in the substitution are not catched [Bug 219184] + * Emit the "no errors" epilogue: push "0" (TCL_OK) as the catch result, + * and jump around the "error case" code. */ - if (cmdTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - startOffset = (envPtr->codeNext - envPtr->codeStart); - TclCompileCmdWord(interp, cmdTokenPtr+1, 1, envPtr); - } else { - TclCompileTokens(interp, cmdTokenPtr+1, - cmdTokenPtr->numComponents, envPtr); - startOffset = (envPtr->codeNext - envPtr->codeStart); - TclEmitOpcode(INST_EVAL_STK, envPtr); + TclCheckStackDepth(depth+1, envPtr); + PushStringLiteral(envPtr, "0"); + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); + + /* + * Emit the "error case" epilogue. Push the interpreter result and the + * return code. + */ + + ExceptionRangeTarget(envPtr, range, catchOffset); + TclSetStackDepth(depth + dropScript, envPtr); + + if (dropScript) { + TclEmitOpcode( INST_POP, envPtr); + } + + + /* Stack at this point is empty */ + TclEmitOpcode( INST_PUSH_RESULT, envPtr); + TclEmitOpcode( INST_PUSH_RETURN_CODE, envPtr); + + /* Stack at this point on both branches: result returnCode */ + + if (TclFixupForwardJumpToHere(envPtr, &jumpFixup, 127)) { + Tcl_Panic("TclCompileCatchCmd: bad jump distance %d", + (int)(CurrentOffset(envPtr) - jumpFixup.codeOffset)); } - envPtr->exceptArrayPtr[range].codeOffset = startOffset; - envPtr->exceptArrayPtr[range].numCodeBytes = - (envPtr->codeNext - envPtr->codeStart) - startOffset; /* - * The "no errors" epilogue code: store the body's result into the - * variable (if any), push "0" (TCL_OK) as the catch's "no error" - * result, and jump around the "error case" code. + * Push the return options if the caller wants them. This needs to happen + * before INST_END_CATCH */ - if (localIndex != -1) { - if (localIndex <= 255) { - TclEmitInstInt1(INST_STORE_SCALAR1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_STORE_SCALAR4, localIndex, envPtr); - } + if (optsIndex != -1) { + TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); } - TclEmitOpcode(INST_POP, envPtr); - TclEmitPush(TclRegisterNewLiteral(envPtr, "0", 1), envPtr); - TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); /* - * The "error case" code: store the body's result into the variable (if - * any), then push the error result code. The initial PC offset here is - * the catch's error target. + * End the catch */ - envPtr->currStackDepth = savedStackDepth; - envPtr->exceptArrayPtr[range].catchOffset = - (envPtr->codeNext - envPtr->codeStart); - if (localIndex != -1) { - TclEmitOpcode(INST_PUSH_RESULT, envPtr); - if (localIndex <= 255) { - TclEmitInstInt1(INST_STORE_SCALAR1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_STORE_SCALAR4, localIndex, envPtr); - } - TclEmitOpcode(INST_POP, envPtr); - } - TclEmitOpcode(INST_PUSH_RETURN_CODE, envPtr); + TclEmitOpcode( INST_END_CATCH, envPtr); + + /* + * Save the result and return options if the caller wants them. This needs + * to happen after INST_END_CATCH (compile-3.6/7). + */ + if (optsIndex != -1) { + Emit14Inst( INST_STORE_SCALAR, optsIndex, envPtr); + TclEmitOpcode( INST_POP, envPtr); + } /* - * Update the target of the jump after the "no errors" code, then emit - * an endCatch instruction at the end of the catch command. + * At this point, the top of the stack is inconveniently ordered: + * result returnCode + * Reverse the stack to store the result. */ - if (TclFixupForwardJumpToHere(envPtr, &jumpFixup, 127)) { - Tcl_Panic("TclCompileCatchCmd: bad jump distance %d\n", - (envPtr->codeNext - envPtr->codeStart) - jumpFixup.codeOffset); + TclEmitInstInt4( INST_REVERSE, 2, envPtr); + if (resultIndex != -1) { + Emit14Inst( INST_STORE_SCALAR, resultIndex, envPtr); } - TclEmitOpcode(INST_END_CATCH, envPtr); + TclEmitOpcode( INST_POP, envPtr); - envPtr->currStackDepth = savedStackDepth + 1; - envPtr->exceptDepth--; + TclCheckStackDepth(depth+1, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * - * TclCompileContinueCmd -- + * TclCompileConcatCmd -- * - * Procedure called to compile the "continue" command. + * Procedure called to compile the "concat" command. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "continue" command - * at runtime. + * Instructions are added to envPtr to execute the "concat" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileContinueCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileConcatCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { + DefineLineInformation; /* TIP #280 */ + Tcl_Obj *objPtr, *listObj; + Tcl_Token *tokenPtr; + int i; + + /* TODO: Consider compiling expansion case. */ + if (parsePtr->numWords == 1) { + /* + * [concat] without arguments just pushes an empty object. + */ + + PushStringLiteral(envPtr, ""); + return TCL_OK; + } + /* - * There should be no argument after the "continue". + * Test if all arguments are compile-time known. If they are, we can + * implement with a simple push. */ - if (parsePtr->numWords != 1) { - return TCL_OUT_LINE_COMPILE; + listObj = Tcl_NewObj(); + for (i = 1, tokenPtr = parsePtr->tokenPtr; i < parsePtr->numWords; i++) { + tokenPtr = TokenAfter(tokenPtr); + objPtr = Tcl_NewObj(); + if (!TclWordKnownAtCompileTime(tokenPtr, objPtr)) { + Tcl_DecrRefCount(objPtr); + Tcl_DecrRefCount(listObj); + listObj = NULL; + break; + } + (void) Tcl_ListObjAppendElement(NULL, listObj, objPtr); + } + if (listObj != NULL) { + Tcl_Obj **objs; + const char *bytes; + int len; + + Tcl_ListObjGetElements(NULL, listObj, &len, &objs); + objPtr = Tcl_ConcatObj(len, objs); + Tcl_DecrRefCount(listObj); + bytes = Tcl_GetStringFromObj(objPtr, &len); + PushLiteral(envPtr, bytes, len); + Tcl_DecrRefCount(objPtr); + return TCL_OK; } /* - * Emit a continue instruction. + * General case: runtime concat. */ - TclEmitOpcode(INST_CONTINUE, envPtr); + for (i = 1, tokenPtr = parsePtr->tokenPtr; i < parsePtr->numWords; i++) { + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, i); + } + + TclEmitInstInt4( INST_CONCAT_STK, i-1, envPtr); + return TCL_OK; } /* *---------------------------------------------------------------------- * - * TclCompileExprCmd -- + * TclCompileContinueCmd -- * - * Procedure called to compile the "expr" command. + * Procedure called to compile the "continue" command. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "expr" command - * at runtime. + * Instructions are added to envPtr to execute the "continue" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileExprCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileContinueCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - Tcl_Token *firstWordPtr; + ExceptionRange *rangePtr; + ExceptionAux *auxPtr; - if (parsePtr->numWords == 1) { - return TCL_OUT_LINE_COMPILE; + /* + * There should be no argument after the "continue". + */ + + if (parsePtr->numWords != 1) { + return TCL_ERROR; + } + + /* + * See if we can find a valid continueOffset (i.e., not -1) in the + * innermost containing exception range. + */ + + rangePtr = TclGetInnermostExceptionRange(envPtr, TCL_CONTINUE, &auxPtr); + if (rangePtr && rangePtr->type == LOOP_EXCEPTION_RANGE) { + /* + * Found the target! No need for a nasty INST_CONTINUE here. + */ + + TclCleanupStackForBreakContinue(envPtr, auxPtr); + TclAddLoopContinueFixup(envPtr, auxPtr); + } else { + /* + * Emit a real continue. + */ + + TclEmitOpcode(INST_CONTINUE, envPtr); } + TclAdjustStackDepth(1, envPtr); - firstWordPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); - TclCompileExprWords(interp, firstWordPtr, (parsePtr->numWords-1), envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * - * TclCompileForCmd -- + * TclCompileDict*Cmd -- * - * Procedure called to compile the "for" command. + * Functions called to compile "dict" sucommands. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * All return TCL_OK for a successful compile, and TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "for" command - * at runtime. + * Instructions are added to envPtr to execute the "dict" subcommand at + * runtime. * *---------------------------------------------------------------------- */ + int -TclCompileForCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileDictSetCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - Tcl_Token *startTokenPtr, *testTokenPtr, *nextTokenPtr, *bodyTokenPtr; - JumpFixup jumpEvalCondFixup; - int testCodeOffset, bodyCodeOffset, nextCodeOffset, jumpDist; - int bodyRange, nextRange; - int savedStackDepth = envPtr->currStackDepth; - - if (parsePtr->numWords != 5) { - return TCL_OUT_LINE_COMPILE; - } + Tcl_Token *tokenPtr; + int i, dictVarIndex; + DefineLineInformation; /* TIP #280 */ + Tcl_Token *varTokenPtr; /* - * If the test expression requires substitutions, don't compile the for - * command inline. E.g., the expression might cause the loop to never - * execute or execute forever, as in "for {} "$x > 5" {incr x} {}". + * There must be at least one argument after the command. */ - startTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); - testTokenPtr = startTokenPtr + (startTokenPtr->numComponents + 1); - if (testTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { - return TCL_OUT_LINE_COMPILE; + if (parsePtr->numWords < 4) { + return TCL_ERROR; } /* - * Bail out also if the body or the next expression require substitutions - * in order to insure correct behaviour [Bug 219166] + * The dictionary variable must be a local scalar that is knowable at + * compile time; anything else exceeds the complexity of the opcode. So + * discover what the index is. */ - nextTokenPtr = testTokenPtr + (testTokenPtr->numComponents + 1); - bodyTokenPtr = nextTokenPtr + (nextTokenPtr->numComponents + 1); - if ((nextTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) - || (bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD)) { - return TCL_OUT_LINE_COMPILE; + varTokenPtr = TokenAfter(parsePtr->tokenPtr); + dictVarIndex = LocalScalarFromToken(varTokenPtr, envPtr); + if (dictVarIndex < 0) { + return TCL_ERROR; } /* - * Create ExceptionRange records for the body and the "next" command. - * The "next" command's ExceptionRange supports break but not continue - * (and has a -1 continueOffset). - */ - - envPtr->exceptDepth++; - envPtr->maxExceptDepth = - TclMax(envPtr->exceptDepth, envPtr->maxExceptDepth); - bodyRange = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); - nextRange = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); - - /* - * Inline compile the initial command. - */ - - TclCompileCmdWord(interp, startTokenPtr+1, - startTokenPtr->numComponents, envPtr); - TclEmitOpcode(INST_POP, envPtr); - - /* - * Jump to the evaluation of the condition. This code uses the "loop - * rotation" optimisation (which eliminates one branch from the loop). - * "for start cond next body" produces then: - * start - * goto A - * B: body : bodyCodeOffset - * next : nextCodeOffset, continueOffset - * A: cond -> result : testCodeOffset - * if (result) goto B + * Remaining words (key path and value to set) can be handled normally. */ - TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpEvalCondFixup); + tokenPtr = TokenAfter(varTokenPtr); + for (i=2 ; i< parsePtr->numWords ; i++) { + CompileWord(envPtr, tokenPtr, interp, i); + tokenPtr = TokenAfter(tokenPtr); + } /* - * Compile the loop body. + * Now emit the instruction to do the dict manipulation. */ - bodyCodeOffset = (envPtr->codeNext - envPtr->codeStart); - - TclCompileCmdWord(interp, bodyTokenPtr+1, - bodyTokenPtr->numComponents, envPtr); - envPtr->currStackDepth = savedStackDepth + 1; - envPtr->exceptArrayPtr[bodyRange].numCodeBytes = - (envPtr->codeNext - envPtr->codeStart) - bodyCodeOffset; - TclEmitOpcode(INST_POP, envPtr); + TclEmitInstInt4( INST_DICT_SET, parsePtr->numWords-3, envPtr); + TclEmitInt4( dictVarIndex, envPtr); + TclAdjustStackDepth(-1, envPtr); + return TCL_OK; +} +int +TclCompileDictIncrCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + DefineLineInformation; /* TIP #280 */ + Tcl_Token *varTokenPtr, *keyTokenPtr; + int dictVarIndex, incrAmount; /* - * Compile the "next" subcommand. + * There must be at least two arguments after the command. */ - nextCodeOffset = (envPtr->codeNext - envPtr->codeStart); - - envPtr->currStackDepth = savedStackDepth; - TclCompileCmdWord(interp, nextTokenPtr+1, - nextTokenPtr->numComponents, envPtr); - envPtr->currStackDepth = savedStackDepth + 1; - envPtr->exceptArrayPtr[nextRange].numCodeBytes = - (envPtr->codeNext - envPtr->codeStart) - - nextCodeOffset; - TclEmitOpcode(INST_POP, envPtr); - envPtr->currStackDepth = savedStackDepth; + if (parsePtr->numWords < 3 || parsePtr->numWords > 4) { + return TCL_ERROR; + } + varTokenPtr = TokenAfter(parsePtr->tokenPtr); + keyTokenPtr = TokenAfter(varTokenPtr); /* - * Compile the test expression then emit the conditional jump that - * terminates the for. + * Parse the increment amount, if present. */ - testCodeOffset = (envPtr->codeNext - envPtr->codeStart); - - jumpDist = testCodeOffset - jumpEvalCondFixup.codeOffset; - if (TclFixupForwardJump(envPtr, &jumpEvalCondFixup, jumpDist, 127)) { - bodyCodeOffset += 3; - nextCodeOffset += 3; - testCodeOffset += 3; - } + if (parsePtr->numWords == 4) { + const char *word; + int numBytes, code; + Tcl_Token *incrTokenPtr; + Tcl_Obj *intObj; - envPtr->currStackDepth = savedStackDepth; - TclCompileExprWords(interp, testTokenPtr, 1, envPtr); - envPtr->currStackDepth = savedStackDepth + 1; + incrTokenPtr = TokenAfter(keyTokenPtr); + if (incrTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { + return TclCompileBasic2Or3ArgCmd(interp, parsePtr,cmdPtr, envPtr); + } + word = incrTokenPtr[1].start; + numBytes = incrTokenPtr[1].size; - jumpDist = (envPtr->codeNext - envPtr->codeStart) - bodyCodeOffset; - if (jumpDist > 127) { - TclEmitInstInt4(INST_JUMP_TRUE4, -jumpDist, envPtr); + intObj = Tcl_NewStringObj(word, numBytes); + Tcl_IncrRefCount(intObj); + code = TclGetIntFromObj(NULL, intObj, &incrAmount); + TclDecrRefCount(intObj); + if (code != TCL_OK) { + return TclCompileBasic2Or3ArgCmd(interp, parsePtr,cmdPtr, envPtr); + } } else { - TclEmitInstInt1(INST_JUMP_TRUE1, -jumpDist, envPtr); + incrAmount = 1; } /* - * Set the loop's offsets and break target. + * The dictionary variable must be a local scalar that is knowable at + * compile time; anything else exceeds the complexity of the opcode. So + * discover what the index is. */ - envPtr->exceptArrayPtr[bodyRange].codeOffset = bodyCodeOffset; - envPtr->exceptArrayPtr[bodyRange].continueOffset = nextCodeOffset; - - envPtr->exceptArrayPtr[nextRange].codeOffset = nextCodeOffset; - - envPtr->exceptArrayPtr[bodyRange].breakOffset = - envPtr->exceptArrayPtr[nextRange].breakOffset = - (envPtr->codeNext - envPtr->codeStart); + dictVarIndex = LocalScalarFromToken(varTokenPtr, envPtr); + if (dictVarIndex < 0) { + return TclCompileBasic2Or3ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } /* - * The for command's result is an empty string. + * Emit the key and the code to actually do the increment. */ - envPtr->currStackDepth = savedStackDepth; - TclEmitPush(TclRegisterNewLiteral(envPtr, "", 0), envPtr); - - envPtr->exceptDepth--; + CompileWord(envPtr, keyTokenPtr, interp, 2); + TclEmitInstInt4( INST_DICT_INCR_IMM, incrAmount, envPtr); + TclEmitInt4( dictVarIndex, envPtr); return TCL_OK; } - -/* - *---------------------------------------------------------------------- - * - * TclCompileForeachCmd -- - * - * Procedure called to compile the "foreach" command. - * - * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. - * - * Side effects: - * Instructions are added to envPtr to execute the "foreach" command - * at runtime. - * -n*---------------------------------------------------------------------- - */ int -TclCompileForeachCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileDictGetCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - Proc *procPtr = envPtr->procPtr; - ForeachInfo *infoPtr; /* Points to the structure describing this - * foreach command. Stored in a AuxData - * record in the ByteCode. */ - int firstValueTemp; /* Index of the first temp var in the frame - * used to point to a value list. */ - int loopCtTemp; /* Index of temp var holding the loop's - * iteration count. */ - Tcl_Token *tokenPtr, *bodyTokenPtr; - unsigned char *jumpPc; - JumpFixup jumpFalseFixup; - int jumpBackDist, jumpBackOffset, infoIndex, range; - int numWords, numLists, numVars, loopIndex, tempVar, i, j, code; - int savedStackDepth = envPtr->currStackDepth; + Tcl_Token *tokenPtr; + int i; + DefineLineInformation; /* TIP #280 */ /* - * We parse the variable list argument words and create two arrays: - * varcList[i] is number of variables in i-th var list - * varvList[i] points to array of var names in i-th var list + * There must be at least two arguments after the command (the single-arg + * case is legal, but too special and magic for us to deal with here). */ -#define STATIC_VAR_LIST_SIZE 5 - int varcListStaticSpace[STATIC_VAR_LIST_SIZE]; - CONST char **varvListStaticSpace[STATIC_VAR_LIST_SIZE]; - int *varcList = varcListStaticSpace; - CONST char ***varvList = varvListStaticSpace; + /* TODO: Consider support for compiling expanded args. */ + if (parsePtr->numWords < 3) { + return TCL_ERROR; + } + tokenPtr = TokenAfter(parsePtr->tokenPtr); /* - * If the foreach command isn't in a procedure, don't compile it inline: - * the payoff is too small. + * Only compile this because we need INST_DICT_GET anyway. */ - if (procPtr == NULL) { - return TCL_OUT_LINE_COMPILE; + for (i=1 ; i<parsePtr->numWords ; i++) { + CompileWord(envPtr, tokenPtr, interp, i); + tokenPtr = TokenAfter(tokenPtr); } + TclEmitInstInt4(INST_DICT_GET, parsePtr->numWords-2, envPtr); + TclAdjustStackDepth(-1, envPtr); + return TCL_OK; +} - numWords = parsePtr->numWords; - if ((numWords < 4) || (numWords%2 != 0)) { - return TCL_OUT_LINE_COMPILE; - } +int +TclCompileDictExistsCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + Tcl_Token *tokenPtr; + int i; + DefineLineInformation; /* TIP #280 */ /* - * Bail out if the body requires substitutions - * in order to insure correct behaviour [Bug 219166] + * There must be at least two arguments after the command (the single-arg + * case is legal, but too special and magic for us to deal with here). */ - for (i = 0, tokenPtr = parsePtr->tokenPtr; - i < numWords-1; - i++, tokenPtr += (tokenPtr->numComponents + 1)) { - } - bodyTokenPtr = tokenPtr; - if (bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { - return TCL_OUT_LINE_COMPILE; + + /* TODO: Consider support for compiling expanded args. */ + if (parsePtr->numWords < 3) { + return TCL_ERROR; } + tokenPtr = TokenAfter(parsePtr->tokenPtr); /* - * Allocate storage for the varcList and varvList arrays if necessary. + * Now we do the code generation. */ - numLists = (numWords - 2)/2; - if (numLists > STATIC_VAR_LIST_SIZE) { - varcList = (int *) ckalloc(numLists * sizeof(int)); - varvList = (CONST char ***) ckalloc(numLists * sizeof(CONST char **)); + for (i=1 ; i<parsePtr->numWords ; i++) { + CompileWord(envPtr, tokenPtr, interp, i); + tokenPtr = TokenAfter(tokenPtr); } - for (loopIndex = 0; loopIndex < numLists; loopIndex++) { - varcList[loopIndex] = 0; - varvList[loopIndex] = NULL; - } - - /* - * Set the exception stack depth. - */ + TclEmitInstInt4(INST_DICT_EXISTS, parsePtr->numWords-2, envPtr); + TclAdjustStackDepth(-1, envPtr); + return TCL_OK; +} - envPtr->exceptDepth++; - envPtr->maxExceptDepth = - TclMax(envPtr->exceptDepth, envPtr->maxExceptDepth); +int +TclCompileDictUnsetCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + Tcl_Token *tokenPtr; + DefineLineInformation; /* TIP #280 */ + int i, dictVarIndex; /* - * Break up each var list and set the varcList and varvList arrays. - * Don't compile the foreach inline if any var name needs substitutions - * or isn't a scalar, or if any var list needs substitutions. + * There must be at least one argument after the variable name for us to + * compile to bytecode. */ - loopIndex = 0; - for (i = 0, tokenPtr = parsePtr->tokenPtr; - i < numWords-1; - i++, tokenPtr += (tokenPtr->numComponents + 1)) { - if (i%2 == 1) { - if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { - code = TCL_OUT_LINE_COMPILE; - goto done; - } else { - /* Lots of copying going on here. Need a ListObj wizard - * to show a better way. */ - - Tcl_DString varList; - - Tcl_DStringInit(&varList); - Tcl_DStringAppend(&varList, tokenPtr[1].start, - tokenPtr[1].size); - code = Tcl_SplitList(interp, Tcl_DStringValue(&varList), - &varcList[loopIndex], &varvList[loopIndex]); - Tcl_DStringFree(&varList); - if (code != TCL_OK) { - code = TCL_OUT_LINE_COMPILE; - goto done; - } - numVars = varcList[loopIndex]; - for (j = 0; j < numVars; j++) { - CONST char *varName = varvList[loopIndex][j]; - if (!TclIsLocalScalar(varName, (int) strlen(varName))) { - code = TCL_OUT_LINE_COMPILE; - goto done; - } - } - } - loopIndex++; - } + /* TODO: Consider support for compiling expanded args. */ + if (parsePtr->numWords < 3) { + return TCL_ERROR; } /* - * We will compile the foreach command. - * Reserve (numLists + 1) temporary variables: - * - numLists temps to hold each value list - * - 1 temp for the loop counter (index of next element in each list) - * At this time we don't try to reuse temporaries; if there are two - * nonoverlapping foreach loops, they don't share any temps. + * The dictionary variable must be a local scalar that is knowable at + * compile time; anything else exceeds the complexity of the opcode. So + * discover what the index is. */ - code = TCL_OK; - firstValueTemp = -1; - for (loopIndex = 0; loopIndex < numLists; loopIndex++) { - tempVar = TclFindCompiledLocal(NULL, /*nameChars*/ 0, - /*create*/ 1, /*flags*/ VAR_SCALAR, procPtr); - if (loopIndex == 0) { - firstValueTemp = tempVar; - } + tokenPtr = TokenAfter(parsePtr->tokenPtr); + dictVarIndex = LocalScalarFromToken(tokenPtr, envPtr); + if (dictVarIndex < 0) { + return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } - loopCtTemp = TclFindCompiledLocal(NULL, /*nameChars*/ 0, - /*create*/ 1, /*flags*/ VAR_SCALAR, procPtr); /* - * Create and initialize the ForeachInfo and ForeachVarList data - * structures describing this command. Then create a AuxData record - * pointing to the ForeachInfo structure. + * Remaining words (the key path) can be handled normally. */ - infoPtr = (ForeachInfo *) ckalloc((unsigned) - (sizeof(ForeachInfo) + (numLists * sizeof(ForeachVarList *)))); - infoPtr->numLists = numLists; - infoPtr->firstValueTemp = firstValueTemp; - infoPtr->loopCtTemp = loopCtTemp; - for (loopIndex = 0; loopIndex < numLists; loopIndex++) { - ForeachVarList *varListPtr; - numVars = varcList[loopIndex]; - varListPtr = (ForeachVarList *) ckalloc((unsigned) - sizeof(ForeachVarList) + (numVars * sizeof(int))); - varListPtr->numVars = numVars; - for (j = 0; j < numVars; j++) { - CONST char *varName = varvList[loopIndex][j]; - int nameChars = strlen(varName); - varListPtr->varIndexes[j] = TclFindCompiledLocal(varName, - nameChars, /*create*/ 1, /*flags*/ VAR_SCALAR, procPtr); - } - infoPtr->varLists[loopIndex] = varListPtr; + for (i=2 ; i<parsePtr->numWords ; i++) { + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, i); } - infoIndex = TclCreateAuxData((ClientData) infoPtr, &tclForeachInfoType, envPtr); /* - * Evaluate then store each value list in the associated temporary. + * Now emit the instruction to do the dict manipulation. */ - range = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); + TclEmitInstInt4( INST_DICT_UNSET, parsePtr->numWords-2, envPtr); + TclEmitInt4( dictVarIndex, envPtr); + return TCL_OK; +} - loopIndex = 0; - for (i = 0, tokenPtr = parsePtr->tokenPtr; - i < numWords-1; - i++, tokenPtr += (tokenPtr->numComponents + 1)) { - if ((i%2 == 0) && (i > 0)) { - TclCompileTokens(interp, tokenPtr+1, - tokenPtr->numComponents, envPtr); +int +TclCompileDictCreateCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + DefineLineInformation; /* TIP #280 */ + int worker; /* Temp var for building the value in. */ + Tcl_Token *tokenPtr; + Tcl_Obj *keyObj, *valueObj, *dictObj; + const char *bytes; + int i, len; - tempVar = (firstValueTemp + loopIndex); - if (tempVar <= 255) { - TclEmitInstInt1(INST_STORE_SCALAR1, tempVar, envPtr); - } else { - TclEmitInstInt4(INST_STORE_SCALAR4, tempVar, envPtr); - } - TclEmitOpcode(INST_POP, envPtr); - loopIndex++; + if ((parsePtr->numWords & 1) == 0) { + return TCL_ERROR; + } + + /* + * See if we can build the value at compile time... + */ + + tokenPtr = TokenAfter(parsePtr->tokenPtr); + dictObj = Tcl_NewObj(); + Tcl_IncrRefCount(dictObj); + for (i=1 ; i<parsePtr->numWords ; i+=2) { + keyObj = Tcl_NewObj(); + Tcl_IncrRefCount(keyObj); + if (!TclWordKnownAtCompileTime(tokenPtr, keyObj)) { + Tcl_DecrRefCount(keyObj); + Tcl_DecrRefCount(dictObj); + goto nonConstant; } + tokenPtr = TokenAfter(tokenPtr); + valueObj = Tcl_NewObj(); + Tcl_IncrRefCount(valueObj); + if (!TclWordKnownAtCompileTime(tokenPtr, valueObj)) { + Tcl_DecrRefCount(keyObj); + Tcl_DecrRefCount(valueObj); + Tcl_DecrRefCount(dictObj); + goto nonConstant; + } + tokenPtr = TokenAfter(tokenPtr); + Tcl_DictObjPut(NULL, dictObj, keyObj, valueObj); + Tcl_DecrRefCount(keyObj); + Tcl_DecrRefCount(valueObj); } /* - * Initialize the temporary var that holds the count of loop iterations. + * We did! Excellent. The "verifyDict" is to do type forcing. */ - TclEmitInstInt4(INST_FOREACH_START4, infoIndex, envPtr); + bytes = Tcl_GetStringFromObj(dictObj, &len); + PushLiteral(envPtr, bytes, len); + TclEmitOpcode( INST_DUP, envPtr); + TclEmitOpcode( INST_DICT_VERIFY, envPtr); + Tcl_DecrRefCount(dictObj); + return TCL_OK; /* - * Top of loop code: assign each loop variable and check whether - * to terminate the loop. + * Otherwise, we've got to issue runtime code to do the building, which we + * do by [dict set]ting into an unnamed local variable. This requires that + * we are in a context with an LVT. */ - envPtr->exceptArrayPtr[range].continueOffset = - (envPtr->codeNext - envPtr->codeStart); - TclEmitInstInt4(INST_FOREACH_STEP4, infoIndex, envPtr); - TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, &jumpFalseFixup); + nonConstant: + worker = AnonymousLocal(envPtr); + if (worker < 0) { + return TclCompileBasicMin0ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } + + PushStringLiteral(envPtr, ""); + Emit14Inst( INST_STORE_SCALAR, worker, envPtr); + TclEmitOpcode( INST_POP, envPtr); + tokenPtr = TokenAfter(parsePtr->tokenPtr); + for (i=1 ; i<parsePtr->numWords ; i+=2) { + CompileWord(envPtr, tokenPtr, interp, i); + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, i+1); + tokenPtr = TokenAfter(tokenPtr); + TclEmitInstInt4( INST_DICT_SET, 1, envPtr); + TclEmitInt4( worker, envPtr); + TclAdjustStackDepth(-1, envPtr); + TclEmitOpcode( INST_POP, envPtr); + } + Emit14Inst( INST_LOAD_SCALAR, worker, envPtr); + TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); + TclEmitInt4( worker, envPtr); + return TCL_OK; +} + +int +TclCompileDictMergeCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + DefineLineInformation; /* TIP #280 */ + Tcl_Token *tokenPtr; + int i, workerIndex, infoIndex, outLoop; /* - * Inline compile the loop body. + * Deal with some special edge cases. Note that in the case with one + * argument, the only thing to do is to verify the dict-ness. */ - envPtr->exceptArrayPtr[range].codeOffset = - (envPtr->codeNext - envPtr->codeStart); - TclCompileCmdWord(interp, bodyTokenPtr+1, - bodyTokenPtr->numComponents, envPtr); - envPtr->currStackDepth = savedStackDepth + 1; - envPtr->exceptArrayPtr[range].numCodeBytes = - (envPtr->codeNext - envPtr->codeStart) - - envPtr->exceptArrayPtr[range].codeOffset; - TclEmitOpcode(INST_POP, envPtr); + /* TODO: Consider support for compiling expanded args. (less likely) */ + if (parsePtr->numWords < 2) { + PushStringLiteral(envPtr, ""); + return TCL_OK; + } else if (parsePtr->numWords == 2) { + tokenPtr = TokenAfter(parsePtr->tokenPtr); + CompileWord(envPtr, tokenPtr, interp, 1); + TclEmitOpcode( INST_DUP, envPtr); + TclEmitOpcode( INST_DICT_VERIFY, envPtr); + return TCL_OK; + } /* - * Jump back to the test at the top of the loop. Generate a 4 byte jump - * if the distance to the test is > 120 bytes. This is conservative and - * ensures that we won't have to replace this jump if we later need to - * replace the ifFalse jump with a 4 byte jump. + * There's real merging work to do. + * + * Allocate some working space. This means we'll only ever compile this + * command when there's an LVT present. */ - jumpBackOffset = (envPtr->codeNext - envPtr->codeStart); - jumpBackDist = - (jumpBackOffset - envPtr->exceptArrayPtr[range].continueOffset); - if (jumpBackDist > 120) { - TclEmitInstInt4(INST_JUMP4, -jumpBackDist, envPtr); - } else { - TclEmitInstInt1(INST_JUMP1, -jumpBackDist, envPtr); + workerIndex = AnonymousLocal(envPtr); + if (workerIndex < 0) { + return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } + infoIndex = AnonymousLocal(envPtr); /* - * Fix the target of the jump after the foreach_step test. + * Get the first dictionary and verify that it is so. */ - if (TclFixupForwardJumpToHere(envPtr, &jumpFalseFixup, 127)) { - /* - * Update the loop body's starting PC offset since it moved down. - */ + tokenPtr = TokenAfter(parsePtr->tokenPtr); + CompileWord(envPtr, tokenPtr, interp, 1); + TclEmitOpcode( INST_DUP, envPtr); + TclEmitOpcode( INST_DICT_VERIFY, envPtr); + Emit14Inst( INST_STORE_SCALAR, workerIndex, envPtr); + TclEmitOpcode( INST_POP, envPtr); - envPtr->exceptArrayPtr[range].codeOffset += 3; + /* + * For each of the remaining dictionaries... + */ + outLoop = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); + TclEmitInstInt4( INST_BEGIN_CATCH4, outLoop, envPtr); + ExceptionRangeStarts(envPtr, outLoop); + for (i=2 ; i<parsePtr->numWords ; i++) { /* - * Update the jump back to the test at the top of the loop since it - * also moved down 3 bytes. + * Get the dictionary, and merge its pairs into the first dict (using + * a small loop). */ - jumpBackOffset += 3; - jumpPc = (envPtr->codeStart + jumpBackOffset); - jumpBackDist += 3; - if (jumpBackDist > 120) { - TclUpdateInstInt4AtPc(INST_JUMP4, -jumpBackDist, jumpPc); - } else { - TclUpdateInstInt1AtPc(INST_JUMP1, -jumpBackDist, jumpPc); - } + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, i); + TclEmitInstInt4( INST_DICT_FIRST, infoIndex, envPtr); + TclEmitInstInt1( INST_JUMP_TRUE1, 24, envPtr); + TclEmitInstInt4( INST_REVERSE, 2, envPtr); + TclEmitInstInt4( INST_DICT_SET, 1, envPtr); + TclEmitInt4( workerIndex, envPtr); + TclAdjustStackDepth(-1, envPtr); + TclEmitOpcode( INST_POP, envPtr); + TclEmitInstInt4( INST_DICT_NEXT, infoIndex, envPtr); + TclEmitInstInt1( INST_JUMP_FALSE1, -20, envPtr); + TclEmitOpcode( INST_POP, envPtr); + TclEmitOpcode( INST_POP, envPtr); + TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); + TclEmitInt4( infoIndex, envPtr); } + ExceptionRangeEnds(envPtr, outLoop); + TclEmitOpcode( INST_END_CATCH, envPtr); /* - * Set the loop's break target. + * Clean up any state left over. */ - envPtr->exceptArrayPtr[range].breakOffset = - (envPtr->codeNext - envPtr->codeStart); + Emit14Inst( INST_LOAD_SCALAR, workerIndex, envPtr); + TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); + TclEmitInt4( workerIndex, envPtr); + TclEmitInstInt1( INST_JUMP1, 18, envPtr); /* - * The foreach command's result is an empty string. + * If an exception happens when starting to iterate over the second (and + * subsequent) dicts. This is strictly not necessary, but it is nice. */ - envPtr->currStackDepth = savedStackDepth; - TclEmitPush(TclRegisterNewLiteral(envPtr, "", 0), envPtr); - envPtr->currStackDepth = savedStackDepth + 1; + TclAdjustStackDepth(-1, envPtr); + ExceptionRangeTarget(envPtr, outLoop, catchOffset); + TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); + TclEmitOpcode( INST_PUSH_RESULT, envPtr); + TclEmitOpcode( INST_END_CATCH, envPtr); + TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); + TclEmitInt4( workerIndex, envPtr); + TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); + TclEmitInt4( infoIndex, envPtr); + TclEmitOpcode( INST_RETURN_STK, envPtr); - done: - for (loopIndex = 0; loopIndex < numLists; loopIndex++) { - if (varvList[loopIndex] != (CONST char **) NULL) { - ckfree((char *) varvList[loopIndex]); - } - } - if (varcList != varcListStaticSpace) { - ckfree((char *) varcList); - ckfree((char *) varvList); - } - envPtr->exceptDepth--; - return code; + return TCL_OK; } - -/* - *---------------------------------------------------------------------- - * - * DupForeachInfo -- - * - * This procedure duplicates a ForeachInfo structure created as - * auxiliary data during the compilation of a foreach command. - * - * Results: - * A pointer to a newly allocated copy of the existing ForeachInfo - * structure is returned. - * - * Side effects: - * Storage for the copied ForeachInfo record is allocated. If the - * original ForeachInfo structure pointed to any ForeachVarList - * records, these structures are also copied and pointers to them - * are stored in the new ForeachInfo record. - * - *---------------------------------------------------------------------- - */ -static ClientData -DupForeachInfo(clientData) - ClientData clientData; /* The foreach command's compilation - * auxiliary data to duplicate. */ +int +TclCompileDictForCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - register ForeachInfo *srcPtr = (ForeachInfo *) clientData; - ForeachInfo *dupPtr; - register ForeachVarList *srcListPtr, *dupListPtr; - int numLists = srcPtr->numLists; - int numVars, i, j; - - dupPtr = (ForeachInfo *) ckalloc((unsigned) - (sizeof(ForeachInfo) + (numLists * sizeof(ForeachVarList *)))); - dupPtr->numLists = numLists; - dupPtr->firstValueTemp = srcPtr->firstValueTemp; - dupPtr->loopCtTemp = srcPtr->loopCtTemp; - - for (i = 0; i < numLists; i++) { - srcListPtr = srcPtr->varLists[i]; - numVars = srcListPtr->numVars; - dupListPtr = (ForeachVarList *) ckalloc((unsigned) - sizeof(ForeachVarList) + numVars*sizeof(int)); - dupListPtr->numVars = numVars; - for (j = 0; j < numVars; j++) { - dupListPtr->varIndexes[j] = srcListPtr->varIndexes[j]; - } - dupPtr->varLists[i] = dupListPtr; - } - return (ClientData) dupPtr; + return CompileDictEachCmd(interp, parsePtr, cmdPtr, envPtr, + TCL_EACH_KEEP_NONE); } - -/* - *---------------------------------------------------------------------- - * - * FreeForeachInfo -- - * - * Procedure to free a ForeachInfo structure created as auxiliary data - * during the compilation of a foreach command. - * - * Results: - * None. - * - * Side effects: - * Storage for the ForeachInfo structure pointed to by the ClientData - * argument is freed as is any ForeachVarList record pointed to by the - * ForeachInfo structure. - * - *---------------------------------------------------------------------- - */ -static void -FreeForeachInfo(clientData) - ClientData clientData; /* The foreach command's compilation - * auxiliary data to free. */ +int +TclCompileDictMapCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - register ForeachInfo *infoPtr = (ForeachInfo *) clientData; - register ForeachVarList *listPtr; - int numLists = infoPtr->numLists; - register int i; - - for (i = 0; i < numLists; i++) { - listPtr = infoPtr->varLists[i]; - ckfree((char *) listPtr); - } - ckfree((char *) infoPtr); + return CompileDictEachCmd(interp, parsePtr, cmdPtr, envPtr, + TCL_EACH_COLLECT); } - -/* - *---------------------------------------------------------------------- - * - * TclCompileIfCmd -- - * - * Procedure called to compile the "if" command. - * - * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. - * - * Side effects: - * Instructions are added to envPtr to execute the "if" command - * at runtime. - * - *---------------------------------------------------------------------- - */ + int -TclCompileIfCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +CompileDictEachCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr, /* Holds resulting instructions. */ + int collect) /* Flag == TCL_EACH_COLLECT to collect and + * construct a new dictionary with the loop + * body result. */ { - JumpFixupArray jumpFalseFixupArray; - /* Used to fix the ifFalse jump after each - * test when its target PC is determined. */ - JumpFixupArray jumpEndFixupArray; - /* Used to fix the jump after each "then" - * body to the end of the "if" when that PC - * is determined. */ - Tcl_Token *tokenPtr, *testTokenPtr; - int jumpFalseDist; - int jumpIndex = 0; /* avoid compiler warning. */ - int numWords, wordIdx, numBytes, j, code; - CONST char *word; - int savedStackDepth = envPtr->currStackDepth; - /* Saved stack depth at the start of the first - * test; the envPtr current depth is restored - * to this value at the start of each test. */ - int realCond = 1; /* set to 0 for static conditions: "if 0 {..}" */ - int boolVal; /* value of static condition */ - int compileScripts = 1; - - /* - * Only compile the "if" command if all arguments are simple - * words, in order to insure correct substitution [Bug 219166] - */ - - tokenPtr = parsePtr->tokenPtr; - wordIdx = 0; - numWords = parsePtr->numWords; + DefineLineInformation; /* TIP #280 */ + Tcl_Token *varsTokenPtr, *dictTokenPtr, *bodyTokenPtr; + int keyVarIndex, valueVarIndex, nameChars, loopRange, catchRange; + int infoIndex, jumpDisplacement, bodyTargetOffset, emptyTargetOffset; + int numVars, endTargetOffset; + int collectVar = -1; /* Index of temp var holding the result + * dict. */ + const char **argv; + Tcl_DString buffer; - for (wordIdx = 0; wordIdx < numWords; wordIdx++) { - if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { - return TCL_OUT_LINE_COMPILE; + /* + * There must be three arguments after the command. + */ + + if (parsePtr->numWords != 4) { + return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } + + varsTokenPtr = TokenAfter(parsePtr->tokenPtr); + dictTokenPtr = TokenAfter(varsTokenPtr); + bodyTokenPtr = TokenAfter(dictTokenPtr); + if (varsTokenPtr->type != TCL_TOKEN_SIMPLE_WORD || + bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { + return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } + + /* + * Create temporary variable to capture return values from loop body when + * we're collecting results. + */ + + if (collect == TCL_EACH_COLLECT) { + collectVar = AnonymousLocal(envPtr); + if (collectVar < 0) { + return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); } - tokenPtr += 2; } + /* + * Check we've got a pair of variables and that they are local variables. + * Then extract their indices in the LVT. + */ + + Tcl_DStringInit(&buffer); + TclDStringAppendToken(&buffer, &varsTokenPtr[1]); + if (Tcl_SplitList(NULL, Tcl_DStringValue(&buffer), &numVars, + &argv) != TCL_OK) { + Tcl_DStringFree(&buffer); + return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } + Tcl_DStringFree(&buffer); + if (numVars != 2) { + ckfree(argv); + return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } + + nameChars = strlen(argv[0]); + keyVarIndex = LocalScalar(argv[0], nameChars, envPtr); + nameChars = strlen(argv[1]); + valueVarIndex = LocalScalar(argv[1], nameChars, envPtr); + ckfree(argv); - TclInitJumpFixupArray(&jumpFalseFixupArray); - TclInitJumpFixupArray(&jumpEndFixupArray); - code = TCL_OK; + if ((keyVarIndex < 0) || (valueVarIndex < 0)) { + return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } /* - * Each iteration of this loop compiles one "if expr ?then? body" - * or "elseif expr ?then? body" clause. + * Allocate a temporary variable to store the iterator reference. The + * variable will contain a Tcl_DictSearch reference which will be + * allocated by INST_DICT_FIRST and disposed when the variable is unset + * (at which point it should also have been finished with). */ - tokenPtr = parsePtr->tokenPtr; - wordIdx = 0; - while (wordIdx < numWords) { - /* - * Stop looping if the token isn't "if" or "elseif". - */ + infoIndex = AnonymousLocal(envPtr); + if (infoIndex < 0) { + return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } - word = tokenPtr[1].start; - numBytes = tokenPtr[1].size; - if ((tokenPtr == parsePtr->tokenPtr) - || ((numBytes == 6) && (strncmp(word, "elseif", 6) == 0))) { - tokenPtr += (tokenPtr->numComponents + 1); - wordIdx++; - } else { - break; - } - if (wordIdx >= numWords) { - code = TCL_OUT_LINE_COMPILE; - goto done; - } + /* + * Preparation complete; issue instructions. Note that this code issues + * fixed-sized jumps. That simplifies things a lot! + * + * First up, initialize the accumulator dictionary if needed. + */ - /* - * Compile the test expression then emit the conditional jump - * around the "then" part. - */ + if (collect == TCL_EACH_COLLECT) { + PushStringLiteral(envPtr, ""); + Emit14Inst( INST_STORE_SCALAR, collectVar, envPtr); + TclEmitOpcode( INST_POP, envPtr); + } - envPtr->currStackDepth = savedStackDepth; - testTokenPtr = tokenPtr; + /* + * Get the dictionary and start the iteration. No catching of errors at + * this point. + */ + CompileWord(envPtr, dictTokenPtr, interp, 2); - if (realCond) { - /* - * Find out if the condition is a constant. - */ + /* + * Now we catch errors from here on so that we can finalize the search + * started by Tcl_DictObjFirst above. + */ - Tcl_Obj *boolObj = Tcl_NewStringObj(testTokenPtr[1].start, - testTokenPtr[1].size); - Tcl_IncrRefCount(boolObj); - code = Tcl_GetBooleanFromObj(NULL, boolObj, &boolVal); - Tcl_DecrRefCount(boolObj); - if (code == TCL_OK) { - /* - * A static condition - */ - realCond = 0; - if (!boolVal) { - compileScripts = 0; - } - } else { - Tcl_ResetResult(interp); - TclCompileExprWords(interp, testTokenPtr, 1, envPtr); - if (jumpFalseFixupArray.next >= jumpFalseFixupArray.end) { - TclExpandJumpFixupArray(&jumpFalseFixupArray); - } - jumpIndex = jumpFalseFixupArray.next; - jumpFalseFixupArray.next++; - TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, - &(jumpFalseFixupArray.fixup[jumpIndex])); - } - code = TCL_OK; - } + catchRange = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); + TclEmitInstInt4( INST_BEGIN_CATCH4, catchRange, envPtr); + ExceptionRangeStarts(envPtr, catchRange); + TclEmitInstInt4( INST_DICT_FIRST, infoIndex, envPtr); + emptyTargetOffset = CurrentOffset(envPtr); + TclEmitInstInt4( INST_JUMP_TRUE4, 0, envPtr); - /* - * Skip over the optional "then" before the then clause. - */ + /* + * Inside the iteration, write the loop variables. + */ - tokenPtr = testTokenPtr + (testTokenPtr->numComponents + 1); - wordIdx++; - if (wordIdx >= numWords) { - code = TCL_OUT_LINE_COMPILE; - goto done; - } - if (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - word = tokenPtr[1].start; - numBytes = tokenPtr[1].size; - if ((numBytes == 4) && (strncmp(word, "then", 4) == 0)) { - tokenPtr += (tokenPtr->numComponents + 1); - wordIdx++; - if (wordIdx >= numWords) { - code = TCL_OUT_LINE_COMPILE; - goto done; - } - } - } + bodyTargetOffset = CurrentOffset(envPtr); + Emit14Inst( INST_STORE_SCALAR, keyVarIndex, envPtr); + TclEmitOpcode( INST_POP, envPtr); + Emit14Inst( INST_STORE_SCALAR, valueVarIndex, envPtr); + TclEmitOpcode( INST_POP, envPtr); - /* - * Compile the "then" command body. - */ + /* + * Set up the loop exception targets. + */ - if (compileScripts) { - envPtr->currStackDepth = savedStackDepth; - TclCompileCmdWord(interp, tokenPtr+1, - tokenPtr->numComponents, envPtr); - } + loopRange = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); + ExceptionRangeStarts(envPtr, loopRange); - if (realCond) { - /* - * Jump to the end of the "if" command. Both jumpFalseFixupArray and - * jumpEndFixupArray are indexed by "jumpIndex". - */ + /* + * Compile the loop body itself. It should be stack-neutral. + */ - if (jumpEndFixupArray.next >= jumpEndFixupArray.end) { - TclExpandJumpFixupArray(&jumpEndFixupArray); - } - jumpEndFixupArray.next++; - TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, - &(jumpEndFixupArray.fixup[jumpIndex])); + BODY(bodyTokenPtr, 3); + if (collect == TCL_EACH_COLLECT) { + Emit14Inst( INST_LOAD_SCALAR, keyVarIndex, envPtr); + TclEmitInstInt4(INST_OVER, 1, envPtr); + TclEmitInstInt4(INST_DICT_SET, 1, envPtr); + TclEmitInt4( collectVar, envPtr); + TclAdjustStackDepth(-1, envPtr); + TclEmitOpcode( INST_POP, envPtr); + } + TclEmitOpcode( INST_POP, envPtr); - /* - * Fix the target of the jumpFalse after the test. Generate a 4 byte - * jump if the distance is > 120 bytes. This is conservative, and - * ensures that we won't have to replace this jump if we later also - * need to replace the proceeding jump to the end of the "if" with a - * 4 byte jump. - */ + /* + * Both exception target ranges (error and loop) end here. + */ - if (TclFixupForwardJumpToHere(envPtr, - &(jumpFalseFixupArray.fixup[jumpIndex]), 120)) { - /* - * Adjust the code offset for the proceeding jump to the end - * of the "if" command. - */ + ExceptionRangeEnds(envPtr, loopRange); + ExceptionRangeEnds(envPtr, catchRange); - jumpEndFixupArray.fixup[jumpIndex].codeOffset += 3; - } - } else if (boolVal) { - /* - *We were processing an "if 1 {...}"; stop compiling - * scripts - */ + /* + * Continue (or just normally process) by getting the next pair of items + * from the dictionary and jumping back to the code to write them into + * variables if there is another pair. + */ - compileScripts = 0; - } else { - /* - *We were processing an "if 0 {...}"; reset so that - * the rest (elseif, else) is compiled correctly - */ + ExceptionRangeTarget(envPtr, loopRange, continueOffset); + TclEmitInstInt4( INST_DICT_NEXT, infoIndex, envPtr); + jumpDisplacement = bodyTargetOffset - CurrentOffset(envPtr); + TclEmitInstInt4( INST_JUMP_FALSE4, jumpDisplacement, envPtr); + endTargetOffset = CurrentOffset(envPtr); + TclEmitInstInt1( INST_JUMP1, 0, envPtr); - realCond = 1; - compileScripts = 1; - } + /* + * Error handler "finally" clause, which force-terminates the iteration + * and rethrows the error. + */ - tokenPtr += (tokenPtr->numComponents + 1); - wordIdx++; + TclAdjustStackDepth(-1, envPtr); + ExceptionRangeTarget(envPtr, catchRange, catchOffset); + TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); + TclEmitOpcode( INST_PUSH_RESULT, envPtr); + TclEmitOpcode( INST_END_CATCH, envPtr); + TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); + TclEmitInt4( infoIndex, envPtr); + if (collect == TCL_EACH_COLLECT) { + TclEmitInstInt1(INST_UNSET_SCALAR, 0, envPtr); + TclEmitInt4( collectVar, envPtr); } + TclEmitOpcode( INST_RETURN_STK, envPtr); /* - * Restore the current stack depth in the environment; the - * "else" clause (or its default) will add 1 to this. + * Otherwise we're done (the jump after the DICT_FIRST points here) and we + * need to pop the bogus key/value pair (pushed to keep stack calculations + * easy!) Note that we skip the END_CATCH. [Bug 1382528] */ - envPtr->currStackDepth = savedStackDepth; + jumpDisplacement = CurrentOffset(envPtr) - emptyTargetOffset; + TclUpdateInstInt4AtPc(INST_JUMP_TRUE4, jumpDisplacement, + envPtr->codeStart + emptyTargetOffset); + jumpDisplacement = CurrentOffset(envPtr) - endTargetOffset; + TclUpdateInstInt1AtPc(INST_JUMP1, jumpDisplacement, + envPtr->codeStart + endTargetOffset); + TclEmitOpcode( INST_POP, envPtr); + TclEmitOpcode( INST_POP, envPtr); + ExceptionRangeTarget(envPtr, loopRange, breakOffset); + TclFinalizeLoopExceptionRange(envPtr, loopRange); + TclEmitOpcode( INST_END_CATCH, envPtr); /* - * Check for the optional else clause. Do not compile - * anything if this was an "if 1 {...}" case. + * Final stage of the command (normal case) is that we push an empty + * object (or push the accumulator as the result object). This is done + * last to promote peephole optimization when it's dropped immediately. */ - if ((wordIdx < numWords) - && (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD)) { - /* - * There is an else clause. Skip over the optional "else" word. - */ + TclEmitInstInt1( INST_UNSET_SCALAR, 0, envPtr); + TclEmitInt4( infoIndex, envPtr); + if (collect == TCL_EACH_COLLECT) { + Emit14Inst( INST_LOAD_SCALAR, collectVar, envPtr); + TclEmitInstInt1(INST_UNSET_SCALAR, 0, envPtr); + TclEmitInt4( collectVar, envPtr); + } else { + PushStringLiteral(envPtr, ""); + } + return TCL_OK; +} - word = tokenPtr[1].start; - numBytes = tokenPtr[1].size; - if ((numBytes == 4) && (strncmp(word, "else", 4) == 0)) { - tokenPtr += (tokenPtr->numComponents + 1); - wordIdx++; - if (wordIdx >= numWords) { - code = TCL_OUT_LINE_COMPILE; - goto done; - } - } +int +TclCompileDictUpdateCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + DefineLineInformation; /* TIP #280 */ + int i, dictIndex, numVars, range, infoIndex; + Tcl_Token **keyTokenPtrs, *dictVarTokenPtr, *bodyTokenPtr, *tokenPtr; + DictUpdateInfo *duiPtr; + JumpFixup jumpFixup; - if (compileScripts) { - /* - * Compile the else command body. - */ + /* + * There must be at least one argument after the command. + */ - TclCompileCmdWord(interp, tokenPtr+1, - tokenPtr->numComponents, envPtr); - } + if (parsePtr->numWords < 5) { + return TCL_ERROR; + } + + /* + * Parse the command. Expect the following: + * dict update <lit(eral)> <any> <lit> ?<any> <lit> ...? <lit> + */ + + if ((parsePtr->numWords - 1) & 1) { + return TCL_ERROR; + } + numVars = (parsePtr->numWords - 3) / 2; + + /* + * The dictionary variable must be a local scalar that is knowable at + * compile time; anything else exceeds the complexity of the opcode. So + * discover what the index is. + */ + + dictVarTokenPtr = TokenAfter(parsePtr->tokenPtr); + dictIndex = LocalScalarFromToken(dictVarTokenPtr, envPtr); + if (dictIndex < 0) { + goto issueFallback; + } + + /* + * Assemble the instruction metadata. This is complex enough that it is + * represented as auxData; it holds an ordered list of variable indices + * that are to be used. + */ + duiPtr = ckalloc(sizeof(DictUpdateInfo) + sizeof(int) * (numVars - 1)); + duiPtr->length = numVars; + keyTokenPtrs = TclStackAlloc(interp, sizeof(Tcl_Token *) * numVars); + tokenPtr = TokenAfter(dictVarTokenPtr); + + for (i=0 ; i<numVars ; i++) { /* - * Make sure there are no words after the else clause. + * Put keys to one side for later compilation to bytecode. */ - wordIdx++; - if (wordIdx < numWords) { - code = TCL_OUT_LINE_COMPILE; - goto done; - } - } else { + keyTokenPtrs[i] = tokenPtr; + tokenPtr = TokenAfter(tokenPtr); + /* - * No else clause: the "if" command's result is an empty string. + * Stash the index in the auxiliary data (if it is indeed a local + * scalar that is resolvable at compile-time). */ - if (compileScripts) { - TclEmitPush(TclRegisterNewLiteral(envPtr, "", 0), envPtr); + duiPtr->varIndices[i] = LocalScalarFromToken(tokenPtr, envPtr); + if (duiPtr->varIndices[i] < 0) { + goto failedUpdateInfoAssembly; } + tokenPtr = TokenAfter(tokenPtr); } + if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { + goto failedUpdateInfoAssembly; + } + bodyTokenPtr = tokenPtr; /* - * Fix the unconditional jumps to the end of the "if" command. + * The list of variables to bind is stored in auxiliary data so that it + * can't be snagged by literal sharing and forced to shimmer dangerously. */ - for (j = jumpEndFixupArray.next; j > 0; j--) { - jumpIndex = (j - 1); /* i.e. process the closest jump first */ - if (TclFixupForwardJumpToHere(envPtr, - &(jumpEndFixupArray.fixup[jumpIndex]), 127)) { - /* - * Adjust the immediately preceeding "ifFalse" jump. We moved - * it's target (just after this jump) down three bytes. - */ + infoIndex = TclCreateAuxData(duiPtr, &tclDictUpdateInfoType, envPtr); - unsigned char *ifFalsePc = envPtr->codeStart - + jumpFalseFixupArray.fixup[jumpIndex].codeOffset; - unsigned char opCode = *ifFalsePc; - if (opCode == INST_JUMP_FALSE1) { - jumpFalseDist = TclGetInt1AtPtr(ifFalsePc + 1); - jumpFalseDist += 3; - TclStoreInt1AtPtr(jumpFalseDist, (ifFalsePc + 1)); - } else if (opCode == INST_JUMP_FALSE4) { - jumpFalseDist = TclGetInt4AtPtr(ifFalsePc + 1); - jumpFalseDist += 3; - TclStoreInt4AtPtr(jumpFalseDist, (ifFalsePc + 1)); - } else { - Tcl_Panic("TclCompileIfCmd: unexpected opcode updating ifFalse jump"); - } - } + for (i=0 ; i<numVars ; i++) { + CompileWord(envPtr, keyTokenPtrs[i], interp, 2*i+2); } + TclEmitInstInt4( INST_LIST, numVars, envPtr); + TclEmitInstInt4( INST_DICT_UPDATE_START, dictIndex, envPtr); + TclEmitInt4( infoIndex, envPtr); - /* - * Free the jumpFixupArray array if malloc'ed storage was used. - */ + range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); + TclEmitInstInt4( INST_BEGIN_CATCH4, range, envPtr); - done: - envPtr->currStackDepth = savedStackDepth + 1; - TclFreeJumpFixupArray(&jumpFalseFixupArray); - TclFreeJumpFixupArray(&jumpEndFixupArray); - return code; -} - -/* - *---------------------------------------------------------------------- - * - * TclCompileIncrCmd -- - * - * Procedure called to compile the "incr" command. - * - * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. - * - * Side effects: - * Instructions are added to envPtr to execute the "incr" command - * at runtime. - * - *---------------------------------------------------------------------- - */ + ExceptionRangeStarts(envPtr, range); + BODY(bodyTokenPtr, parsePtr->numWords - 1); + ExceptionRangeEnds(envPtr, range); -int -TclCompileIncrCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ -{ - Tcl_Token *varTokenPtr, *incrTokenPtr; - int simpleVarName, isScalar, localIndex, haveImmValue, immValue; + /* + * Normal termination code: the stack has the key list below the result of + * the body evaluation: swap them and finish the update code. + */ - if ((parsePtr->numWords != 2) && (parsePtr->numWords != 3)) { - return TCL_OUT_LINE_COMPILE; - } + TclEmitOpcode( INST_END_CATCH, envPtr); + TclEmitInstInt4( INST_REVERSE, 2, envPtr); + TclEmitInstInt4( INST_DICT_UPDATE_END, dictIndex, envPtr); + TclEmitInt4( infoIndex, envPtr); - varTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); + /* + * Jump around the exceptional termination code. + */ - PushVarName(interp, varTokenPtr, envPtr, - (TCL_NO_LARGE_INDEX | TCL_CREATE_VAR), - &localIndex, &simpleVarName, &isScalar); + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); /* - * If an increment is given, push it, but see first if it's a small - * integer. + * Termination code for non-ok returns: stash the result and return + * options in the stack, bring up the key list, finish the update code, + * and finally return with the catched return data */ - haveImmValue = 0; - immValue = 0; - if (parsePtr->numWords == 3) { - incrTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - if (incrTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - CONST char *word = incrTokenPtr[1].start; - int numBytes = incrTokenPtr[1].size; - int validLength = TclParseInteger(word, numBytes); - long n; + ExceptionRangeTarget(envPtr, range, catchOffset); + TclEmitOpcode( INST_PUSH_RESULT, envPtr); + TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); + TclEmitOpcode( INST_END_CATCH, envPtr); + TclEmitInstInt4( INST_REVERSE, 3, envPtr); - /* - * Note there is a danger that modifying the string could have - * undesirable side effects. In this case, TclLooksLikeInt and - * TclGetLong do not have any dependencies on shared strings so we - * should be safe. - */ + TclEmitInstInt4( INST_DICT_UPDATE_END, dictIndex, envPtr); + TclEmitInt4( infoIndex, envPtr); + TclEmitInvoke(envPtr,INST_RETURN_STK); - if (validLength == numBytes) { - int code; - Tcl_Obj *longObj = Tcl_NewStringObj(word, numBytes); - Tcl_IncrRefCount(longObj); - code = Tcl_GetLongFromObj(NULL, longObj, &n); - Tcl_DecrRefCount(longObj); - if ((code == TCL_OK) && (-127 <= n) && (n <= 127)) { - haveImmValue = 1; - immValue = n; - } - } - if (!haveImmValue) { - TclEmitPush( - TclRegisterNewLiteral(envPtr, word, numBytes), envPtr); - } - } else { - TclCompileTokens(interp, incrTokenPtr+1, - incrTokenPtr->numComponents, envPtr); - } - } else { /* no incr amount given so use 1 */ - haveImmValue = 1; - immValue = 1; + if (TclFixupForwardJumpToHere(envPtr, &jumpFixup, 127)) { + Tcl_Panic("TclCompileDictCmd(update): bad jump distance %d", + (int) (CurrentOffset(envPtr) - jumpFixup.codeOffset)); } + TclStackFree(interp, keyTokenPtrs); + return TCL_OK; /* - * Emit the instruction to increment the variable. + * Clean up after a failure to create the DictUpdateInfo structure. */ - if (simpleVarName) { - if (isScalar) { - if (localIndex >= 0) { - if (haveImmValue) { - TclEmitInstInt1(INST_INCR_SCALAR1_IMM, localIndex, envPtr); - TclEmitInt1(immValue, envPtr); - } else { - TclEmitInstInt1(INST_INCR_SCALAR1, localIndex, envPtr); - } - } else { - if (haveImmValue) { - TclEmitInstInt1(INST_INCR_SCALAR_STK_IMM, immValue, envPtr); - } else { - TclEmitOpcode(INST_INCR_SCALAR_STK, envPtr); - } - } - } else { - if (localIndex >= 0) { - if (haveImmValue) { - TclEmitInstInt1(INST_INCR_ARRAY1_IMM, localIndex, envPtr); - TclEmitInt1(immValue, envPtr); - } else { - TclEmitInstInt1(INST_INCR_ARRAY1, localIndex, envPtr); - } - } else { - if (haveImmValue) { - TclEmitInstInt1(INST_INCR_ARRAY_STK_IMM, immValue, envPtr); - } else { - TclEmitOpcode(INST_INCR_ARRAY_STK, envPtr); - } - } - } - } else { /* non-simple variable name */ - if (haveImmValue) { - TclEmitInstInt1(INST_INCR_STK_IMM, immValue, envPtr); - } else { - TclEmitOpcode(INST_INCR_STK, envPtr); - } - } - - return TCL_OK; + failedUpdateInfoAssembly: + ckfree(duiPtr); + TclStackFree(interp, keyTokenPtrs); + issueFallback: + return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } - -/* - *---------------------------------------------------------------------- - * - * TclCompileLappendCmd -- - * - * Procedure called to compile the "lappend" command. - * - * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. - * - * Side effects: - * Instructions are added to envPtr to execute the "lappend" command - * at runtime. - * - *---------------------------------------------------------------------- - */ int -TclCompileLappendCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileDictAppendCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - Tcl_Token *varTokenPtr, *valueTokenPtr; - int simpleVarName, isScalar, localIndex, numWords; + DefineLineInformation; /* TIP #280 */ + Tcl_Token *tokenPtr; + int i, dictVarIndex; /* - * If we're not in a procedure, don't compile. + * There must be at least two argument after the command. And we impose an + * (arbirary) safe limit; anyone exceeding it should stop worrying about + * speed quite so much. ;-) */ - if (envPtr->procPtr == NULL) { - return TCL_OUT_LINE_COMPILE; + + /* TODO: Consider support for compiling expanded args. */ + if (parsePtr->numWords<4 || parsePtr->numWords>100) { + return TCL_ERROR; } - numWords = parsePtr->numWords; - if (numWords == 1) { - return TCL_OUT_LINE_COMPILE; + /* + * Get the index of the local variable that we will be working with. + */ + + tokenPtr = TokenAfter(parsePtr->tokenPtr); + dictVarIndex = LocalScalarFromToken(tokenPtr, envPtr); + if (dictVarIndex < 0) { + return TclCompileBasicMin2ArgCmd(interp, parsePtr,cmdPtr, envPtr); } - if (numWords != 3) { - /* - * LAPPEND instructions currently only handle one value appends - */ - return TCL_OUT_LINE_COMPILE; + + /* + * Produce the string to concatenate onto the dictionary entry. + */ + + tokenPtr = TokenAfter(tokenPtr); + for (i=2 ; i<parsePtr->numWords ; i++) { + CompileWord(envPtr, tokenPtr, interp, i); + tokenPtr = TokenAfter(tokenPtr); + } + if (parsePtr->numWords > 4) { + TclEmitInstInt1(INST_STR_CONCAT1, parsePtr->numWords-3, envPtr); } /* - * Decide if we can use a frame slot for the var/array name or if we - * need to emit code to compute and push the name at runtime. We use a - * frame slot (entry in the array of local vars) if we are compiling a - * procedure body and if the name is simple text that does not include - * namespace qualifiers. + * Do the concatenation. */ - varTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); + TclEmitInstInt4(INST_DICT_APPEND, dictVarIndex, envPtr); + return TCL_OK; +} - PushVarName(interp, varTokenPtr, envPtr, TCL_CREATE_VAR, - &localIndex, &simpleVarName, &isScalar); +int +TclCompileDictLappendCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + DefineLineInformation; /* TIP #280 */ + Tcl_Token *varTokenPtr, *keyTokenPtr, *valueTokenPtr; + int dictVarIndex; /* - * If we are doing an assignment, push the new value. - * In the no values case, create an empty object. + * There must be three arguments after the command. */ - if (numWords > 2) { - valueTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - if (valueTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, - valueTokenPtr[1].start, valueTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, valueTokenPtr+1, - valueTokenPtr->numComponents, envPtr); - } + /* TODO: Consider support for compiling expanded args. */ + /* Probably not. Why is INST_DICT_LAPPEND limited to one value? */ + if (parsePtr->numWords != 4) { + return TCL_ERROR; } /* - * Emit instructions to set/get the variable. + * Parse the arguments. */ + varTokenPtr = TokenAfter(parsePtr->tokenPtr); + keyTokenPtr = TokenAfter(varTokenPtr); + valueTokenPtr = TokenAfter(keyTokenPtr); + dictVarIndex = LocalScalarFromToken(varTokenPtr, envPtr); + if (dictVarIndex < 0) { + return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr); + } + /* - * The *_STK opcodes should be refactored to make better use of existing - * LOAD/STORE instructions. + * Issue the implementation. */ - if (simpleVarName) { - if (isScalar) { - if (localIndex >= 0) { - if (localIndex <= 255) { - TclEmitInstInt1(INST_LAPPEND_SCALAR1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_LAPPEND_SCALAR4, localIndex, envPtr); - } - } else { - TclEmitOpcode(INST_LAPPEND_STK, envPtr); - } - } else { - if (localIndex >= 0) { - if (localIndex <= 255) { - TclEmitInstInt1(INST_LAPPEND_ARRAY1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_LAPPEND_ARRAY4, localIndex, envPtr); - } - } else { - TclEmitOpcode(INST_LAPPEND_ARRAY_STK, envPtr); - } - } - } else { - TclEmitOpcode(INST_LAPPEND_STK, envPtr); - } + CompileWord(envPtr, keyTokenPtr, interp, 2); + CompileWord(envPtr, valueTokenPtr, interp, 3); + TclEmitInstInt4( INST_DICT_LAPPEND, dictVarIndex, envPtr); return TCL_OK; } - -/* - *---------------------------------------------------------------------- - * - * TclCompileLassignCmd -- - * - * Procedure called to compile the "lassign" command. - * - * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. - * - * Side effects: - * Instructions are added to envPtr to execute the "lassign" command - * at runtime. - * - *---------------------------------------------------------------------- - */ int -TclCompileLassignCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileDictWithCmd( + Tcl_Interp *interp, /* Used for looking up stuff. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - Tcl_Token *tokenPtr; - int simpleVarName, isScalar, localIndex, numWords, idx; + DefineLineInformation; /* TIP #280 */ + int i, range, varNameTmp = -1, pathTmp = -1, keysTmp, gotPath; + int dictVar, bodyIsEmpty = 1; + Tcl_Token *varTokenPtr, *tokenPtr; + JumpFixup jumpFixup; + const char *ptr, *end; - numWords = parsePtr->numWords; /* - * Check for command syntax error, but we'll punt that to runtime + * There must be at least one argument after the command. */ - if (numWords < 3) { - return TCL_OUT_LINE_COMPILE; + + /* TODO: Consider support for compiling expanded args. */ + if (parsePtr->numWords < 3) { + return TCL_ERROR; } /* - * Generate code to push list being taken apart by [lassign]. + * Parse the command (trivially). Expect the following: + * dict with <any (varName)> ?<any> ...? <literal> */ - tokenPtr = parsePtr->tokenPtr + (parsePtr->tokenPtr->numComponents + 1); - if (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, - tokenPtr[1].start, tokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, tokenPtr+1, tokenPtr->numComponents, envPtr); + + varTokenPtr = TokenAfter(parsePtr->tokenPtr); + tokenPtr = TokenAfter(varTokenPtr); + for (i=3 ; i<parsePtr->numWords ; i++) { + tokenPtr = TokenAfter(tokenPtr); + } + if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { + return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, envPtr); } /* - * Generate code to assign values from the list to variables + * Test if the last word is an empty script; if so, we can compile it in + * all cases, but if it is non-empty we need local variable table entries + * to hold the temporary variables (used to keep stack usage simple). */ - for (idx=0 ; idx<numWords-2 ; idx++) { - tokenPtr += tokenPtr->numComponents + 1; - - /* - * Generate the next variable name - */ - PushVarName(interp, tokenPtr, envPtr, TCL_CREATE_VAR, - &localIndex, &simpleVarName, &isScalar); - /* - * Emit instructions to get the idx'th item out of the list - * value on the stack and assign it to the variable. - */ - if (simpleVarName) { - if (isScalar) { - if (localIndex >= 0) { - TclEmitOpcode(INST_DUP, envPtr); - TclEmitInstInt4(INST_LIST_INDEX_IMM, idx, envPtr); - if (localIndex <= 255) { - TclEmitInstInt1(INST_STORE_SCALAR1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_STORE_SCALAR4, localIndex, envPtr); - } - } else { - TclEmitInstInt4(INST_OVER, 1, envPtr); - TclEmitInstInt4(INST_LIST_INDEX_IMM, idx, envPtr); - TclEmitOpcode(INST_STORE_SCALAR_STK, envPtr); - } - } else { - if (localIndex >= 0) { - TclEmitInstInt4(INST_OVER, 1, envPtr); - TclEmitInstInt4(INST_LIST_INDEX_IMM, idx, envPtr); - if (localIndex <= 255) { - TclEmitInstInt1(INST_STORE_ARRAY1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_STORE_ARRAY4, localIndex, envPtr); - } - } else { - TclEmitInstInt4(INST_OVER, 2, envPtr); - TclEmitInstInt4(INST_LIST_INDEX_IMM, idx, envPtr); - TclEmitOpcode(INST_STORE_ARRAY_STK, envPtr); - } + for (ptr=tokenPtr[1].start,end=ptr+tokenPtr[1].size ; ptr!=end ; ptr++) { + if (*ptr!=' ' && *ptr!='\t' && *ptr!='\n' && *ptr!='\r') { + if (envPtr->procPtr == NULL) { + return TclCompileBasicMin2ArgCmd(interp, parsePtr, cmdPtr, + envPtr); } - } else { - TclEmitInstInt4(INST_OVER, 1, envPtr); - TclEmitInstInt4(INST_LIST_INDEX_IMM, idx, envPtr); - TclEmitOpcode(INST_STORE_STK, envPtr); + bodyIsEmpty = 0; + break; } - TclEmitOpcode(INST_POP, envPtr); } /* - * Generate code to leave the rest of the list on the stack. + * Determine if we're manipulating a dict in a simple local variable. */ - TclEmitInstInt4(INST_LIST_RANGE_IMM, idx, envPtr); - TclEmitInt4(-2, envPtr); /* -2 == "end" */ - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * TclCompileLindexCmd -- - * - * Procedure called to compile the "lindex" command. - * - * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. - * - * Side effects: - * Instructions are added to envPtr to execute the "lindex" command - * at runtime. - * - *---------------------------------------------------------------------- - */ - -int -TclCompileLindexCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ -{ - Tcl_Token *varTokenPtr; - int i, numWords; - numWords = parsePtr->numWords; + gotPath = (parsePtr->numWords > 3); + dictVar = LocalScalarFromToken(varTokenPtr, envPtr); /* - * Quit if too few args + * Special case: an empty body means we definitely have no need to issue + * try-finally style code or to allocate local variable table entries for + * storing temporaries. Still need to do both INST_DICT_EXPAND and + * INST_DICT_RECOMBINE_* though, because we can't determine if we're free + * of traces. */ - if (numWords <= 1) { - return TCL_OUT_LINE_COMPILE; - } + if (bodyIsEmpty) { + if (dictVar >= 0) { + if (gotPath) { + /* + * Case: Path into dict in LVT with empty body. + */ + + tokenPtr = TokenAfter(varTokenPtr); + for (i=2 ; i<parsePtr->numWords-1 ; i++) { + CompileWord(envPtr, tokenPtr, interp, i); + tokenPtr = TokenAfter(tokenPtr); + } + TclEmitInstInt4(INST_LIST, parsePtr->numWords-3,envPtr); + Emit14Inst( INST_LOAD_SCALAR, dictVar, envPtr); + TclEmitInstInt4(INST_OVER, 1, envPtr); + TclEmitOpcode( INST_DICT_EXPAND, envPtr); + TclEmitInstInt4(INST_DICT_RECOMBINE_IMM, dictVar, envPtr); + } else { + /* + * Case: Direct dict in LVT with empty body. + */ + + PushStringLiteral(envPtr, ""); + Emit14Inst( INST_LOAD_SCALAR, dictVar, envPtr); + PushStringLiteral(envPtr, ""); + TclEmitOpcode( INST_DICT_EXPAND, envPtr); + TclEmitInstInt4(INST_DICT_RECOMBINE_IMM, dictVar, envPtr); + } + } else { + if (gotPath) { + /* + * Case: Path into dict in non-simple var with empty body. + */ - varTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); + tokenPtr = varTokenPtr; + for (i=1 ; i<parsePtr->numWords-1 ; i++) { + CompileWord(envPtr, tokenPtr, interp, i); + tokenPtr = TokenAfter(tokenPtr); + } + TclEmitInstInt4(INST_LIST, parsePtr->numWords-3,envPtr); + TclEmitInstInt4(INST_OVER, 1, envPtr); + TclEmitOpcode( INST_LOAD_STK, envPtr); + TclEmitInstInt4(INST_OVER, 1, envPtr); + TclEmitOpcode( INST_DICT_EXPAND, envPtr); + TclEmitOpcode( INST_DICT_RECOMBINE_STK, envPtr); + } else { + /* + * Case: Direct dict in non-simple var with empty body. + */ + + CompileWord(envPtr, varTokenPtr, interp, 1); + TclEmitOpcode( INST_DUP, envPtr); + TclEmitOpcode( INST_LOAD_STK, envPtr); + PushStringLiteral(envPtr, ""); + TclEmitOpcode( INST_DICT_EXPAND, envPtr); + PushStringLiteral(envPtr, ""); + TclEmitInstInt4(INST_REVERSE, 2, envPtr); + TclEmitOpcode( INST_DICT_RECOMBINE_STK, envPtr); + } + } + PushStringLiteral(envPtr, ""); + return TCL_OK; + } /* - * Push the operands onto the stack. + * OK, we have a non-trivial body. This means that the focus is on + * generating a try-finally structure where the INST_DICT_RECOMBINE_* goes + * in the 'finally' clause. + * + * Start by allocating local (unnamed, untraced) working variables. */ - for (i=1 ; i<numWords ; i++) { - if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush( - TclRegisterNewLiteral(envPtr, varTokenPtr[1].start, - varTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, varTokenPtr+1, - varTokenPtr->numComponents, envPtr); - } - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); + if (dictVar == -1) { + varNameTmp = AnonymousLocal(envPtr); + } + if (gotPath) { + pathTmp = AnonymousLocal(envPtr); } + keysTmp = AnonymousLocal(envPtr); /* - * Emit INST_LIST_INDEX if objc==3, or INST_LIST_INDEX_MULTI - * if there are multiple index args. + * Issue instructions. First, the part to expand the dictionary. */ - if (numWords == 3) { - TclEmitOpcode(INST_LIST_INDEX, envPtr); + if (dictVar == -1) { + CompileWord(envPtr, varTokenPtr, interp, 1); + Emit14Inst( INST_STORE_SCALAR, varNameTmp, envPtr); + } + tokenPtr = TokenAfter(varTokenPtr); + if (gotPath) { + for (i=2 ; i<parsePtr->numWords-1 ; i++) { + CompileWord(envPtr, tokenPtr, interp, i); + tokenPtr = TokenAfter(tokenPtr); + } + TclEmitInstInt4( INST_LIST, parsePtr->numWords-3,envPtr); + Emit14Inst( INST_STORE_SCALAR, pathTmp, envPtr); + TclEmitOpcode( INST_POP, envPtr); + } + if (dictVar == -1) { + TclEmitOpcode( INST_LOAD_STK, envPtr); } else { - TclEmitInstInt4(INST_LIST_INDEX_MULTI, numWords-1, envPtr); + Emit14Inst( INST_LOAD_SCALAR, dictVar, envPtr); } + if (gotPath) { + Emit14Inst( INST_LOAD_SCALAR, pathTmp, envPtr); + } else { + PushStringLiteral(envPtr, ""); + } + TclEmitOpcode( INST_DICT_EXPAND, envPtr); + Emit14Inst( INST_STORE_SCALAR, keysTmp, envPtr); + TclEmitOpcode( INST_POP, envPtr); - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * TclCompileListCmd -- - * - * Procedure called to compile the "list" command. - * - * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. - * - * Side effects: - * Instructions are added to envPtr to execute the "list" command - * at runtime. - * - *---------------------------------------------------------------------- - */ + /* + * Now the body of the [dict with]. + */ + + range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr); + TclEmitInstInt4( INST_BEGIN_CATCH4, range, envPtr); + + ExceptionRangeStarts(envPtr, range); + BODY(tokenPtr, parsePtr->numWords - 1); + ExceptionRangeEnds(envPtr, range); -int -TclCompileListCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ -{ /* - * If we're not in a procedure, don't compile. + * Now fold the results back into the dictionary in the OK case. */ - if (envPtr->procPtr == NULL) { - return TCL_OUT_LINE_COMPILE; + + TclEmitOpcode( INST_END_CATCH, envPtr); + if (dictVar == -1) { + Emit14Inst( INST_LOAD_SCALAR, varNameTmp, envPtr); + } + if (gotPath) { + Emit14Inst( INST_LOAD_SCALAR, pathTmp, envPtr); + } else { + PushStringLiteral(envPtr, ""); + } + Emit14Inst( INST_LOAD_SCALAR, keysTmp, envPtr); + if (dictVar == -1) { + TclEmitOpcode( INST_DICT_RECOMBINE_STK, envPtr); + } else { + TclEmitInstInt4( INST_DICT_RECOMBINE_IMM, dictVar, envPtr); } + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); - if (parsePtr->numWords == 1) { - /* - * Empty args case - */ + /* + * Now fold the results back into the dictionary in the exception case. + */ - TclEmitPush(TclRegisterNewLiteral(envPtr, "", 0), envPtr); + TclAdjustStackDepth(-1, envPtr); + ExceptionRangeTarget(envPtr, range, catchOffset); + TclEmitOpcode( INST_PUSH_RETURN_OPTIONS, envPtr); + TclEmitOpcode( INST_PUSH_RESULT, envPtr); + TclEmitOpcode( INST_END_CATCH, envPtr); + if (dictVar == -1) { + Emit14Inst( INST_LOAD_SCALAR, varNameTmp, envPtr); + } + if (parsePtr->numWords > 3) { + Emit14Inst( INST_LOAD_SCALAR, pathTmp, envPtr); } else { - /* - * Push the all values onto the stack. - */ - Tcl_Token *valueTokenPtr; - int i, numWords; + PushStringLiteral(envPtr, ""); + } + Emit14Inst( INST_LOAD_SCALAR, keysTmp, envPtr); + if (dictVar == -1) { + TclEmitOpcode( INST_DICT_RECOMBINE_STK, envPtr); + } else { + TclEmitInstInt4( INST_DICT_RECOMBINE_IMM, dictVar, envPtr); + } + TclEmitInvoke(envPtr, INST_RETURN_STK); - numWords = parsePtr->numWords; + /* + * Prepare for the start of the next command. + */ - valueTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); - for (i = 1; i < numWords; i++) { - if (valueTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, - valueTokenPtr[1].start, valueTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, valueTokenPtr+1, - valueTokenPtr->numComponents, envPtr); - } - valueTokenPtr = valueTokenPtr + (valueTokenPtr->numComponents + 1); - } - TclEmitInstInt4(INST_LIST, numWords - 1, envPtr); + if (TclFixupForwardJumpToHere(envPtr, &jumpFixup, 127)) { + Tcl_Panic("TclCompileDictCmd(update): bad jump distance %d", + (int) (CurrentOffset(envPtr) - jumpFixup.codeOffset)); } - return TCL_OK; } /* *---------------------------------------------------------------------- * - * TclCompileLlengthCmd -- + * DupDictUpdateInfo, FreeDictUpdateInfo -- * - * Procedure called to compile the "llength" command. + * Functions to duplicate, release and print the aux data created for use + * with the INST_DICT_UPDATE_START and INST_DICT_UPDATE_END instructions. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * DupDictUpdateInfo: a copy of the auxiliary data + * FreeDictUpdateInfo: none + * PrintDictUpdateInfo: none * * Side effects: - * Instructions are added to envPtr to execute the "llength" command - * at runtime. + * DupDictUpdateInfo: allocates memory + * FreeDictUpdateInfo: releases memory + * PrintDictUpdateInfo: none * *---------------------------------------------------------------------- */ -int -TclCompileLlengthCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +static ClientData +DupDictUpdateInfo( + ClientData clientData) { - Tcl_Token *varTokenPtr; + DictUpdateInfo *dui1Ptr, *dui2Ptr; + unsigned len; + + dui1Ptr = clientData; + len = sizeof(DictUpdateInfo) + sizeof(int) * (dui1Ptr->length - 1); + dui2Ptr = ckalloc(len); + memcpy(dui2Ptr, dui1Ptr, len); + return dui2Ptr; +} - if (parsePtr->numWords != 2) { - return TCL_OUT_LINE_COMPILE; - } - varTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); +static void +FreeDictUpdateInfo( + ClientData clientData) +{ + ckfree(clientData); +} - if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - /* - * We could simply count the number of elements here and push - * that value, but that is too rare a case to waste the code space. - */ - TclEmitPush(TclRegisterNewLiteral(envPtr, varTokenPtr[1].start, - varTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, varTokenPtr+1, - varTokenPtr->numComponents, envPtr); +static void +PrintDictUpdateInfo( + ClientData clientData, + Tcl_Obj *appendObj, + ByteCode *codePtr, + unsigned int pcOffset) +{ + DictUpdateInfo *duiPtr = clientData; + int i; + + for (i=0 ; i<duiPtr->length ; i++) { + if (i) { + Tcl_AppendToObj(appendObj, ", ", -1); + } + Tcl_AppendPrintfToObj(appendObj, "%%v%u", duiPtr->varIndices[i]); } - TclEmitOpcode(INST_LIST_LENGTH, envPtr); - return TCL_OK; } /* *---------------------------------------------------------------------- * - * TclCompileLsetCmd -- + * TclCompileErrorCmd -- * - * Procedure called to compile the "lset" command. + * Procedure called to compile the "error" command. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "lset" command - * at runtime. - * - * The general template for execution of the "lset" command is: - * (1) Instructions to push the variable name, unless the - * variable is local to the stack frame. - * (2) If the variable is an array element, instructions - * to push the array element name. - * (3) Instructions to push each of zero or more "index" arguments - * to the stack, followed with the "newValue" element. - * (4) Instructions to duplicate the variable name and/or array - * element name onto the top of the stack, if either was - * pushed at steps (1) and (2). - * (5) The appropriate INST_LOAD_* instruction to place the - * original value of the list variable at top of stack. - * (6) At this point, the stack contains: - * varName? arrayElementName? index1 index2 ... newValue oldList - * The compiler emits one of INST_LSET_FLAT or INST_LSET_LIST - * according as whether there is exactly one index element (LIST) - * or either zero or else two or more (FLAT). This instruction - * removes everything from the stack except for the two names - * and pushes the new value of the variable. - * (7) Finally, INST_STORE_* stores the new value in the variable - * and cleans up the stack. + * Instructions are added to envPtr to execute the "error" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileLsetCmd(interp, parsePtr, envPtr) - Tcl_Interp* interp; /* Tcl interpreter for error reporting */ - Tcl_Parse* parsePtr; /* Points to a parse structure for - * the command */ - CompileEnv* envPtr; /* Holds the resulting instructions */ +TclCompileErrorCmd( + Tcl_Interp *interp, /* Used for context. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - int tempDepth; /* Depth used for emitting one part - * of the code burst. */ - Tcl_Token* varTokenPtr; /* Pointer to the Tcl_Token representing - * the parse of the variable name */ - int localIndex; /* Index of var in local var table */ - int simpleVarName; /* Flag == 1 if var name is simple */ - int isScalar; /* Flag == 1 if scalar, 0 if array */ - int i; - - /* Check argument count */ - - if (parsePtr->numWords < 3) { - /* Fail at run time, not in compilation */ - return TCL_OUT_LINE_COMPILE; - } - /* - * Decide if we can use a frame slot for the var/array name or if we - * need to emit code to compute and push the name at runtime. We use a - * frame slot (entry in the array of local vars) if we are compiling a - * procedure body and if the name is simple text that does not include - * namespace qualifiers. + * General syntax: [error message ?errorInfo? ?errorCode?] */ - varTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); - PushVarName(interp, varTokenPtr, envPtr, TCL_CREATE_VAR, - &localIndex, &simpleVarName, &isScalar); - - /* Push the "index" args and the new element value. */ - - for (i=2 ; i<parsePtr->numWords ; ++i) { - /* Advance to next arg */ - - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - - /* Push an arg */ + Tcl_Token *tokenPtr; + DefineLineInformation; /* TIP #280 */ - if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, varTokenPtr[1].start, - varTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, varTokenPtr+1, - varTokenPtr->numComponents, envPtr); - } + if (parsePtr->numWords < 2 || parsePtr->numWords > 4) { + return TCL_ERROR; } /* - * Duplicate the variable name if it's been pushed. + * Handle the message. */ - if (!simpleVarName || localIndex < 0) { - if (!simpleVarName || isScalar) { - tempDepth = parsePtr->numWords - 2; - } else { - tempDepth = parsePtr->numWords - 1; - } - TclEmitInstInt4(INST_OVER, tempDepth, envPtr); - } + tokenPtr = TokenAfter(parsePtr->tokenPtr); + CompileWord(envPtr, tokenPtr, interp, 1); /* - * Duplicate an array index if one's been pushed + * Construct the options. Note that -code and -level are not here. */ - if (simpleVarName && !isScalar) { - if (localIndex < 0) { - tempDepth = parsePtr->numWords - 1; + if (parsePtr->numWords == 2) { + PushStringLiteral(envPtr, ""); + } else { + PushStringLiteral(envPtr, "-errorinfo"); + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, 2); + if (parsePtr->numWords == 3) { + TclEmitInstInt4( INST_LIST, 2, envPtr); } else { - tempDepth = parsePtr->numWords - 2; + PushStringLiteral(envPtr, "-errorcode"); + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, 3); + TclEmitInstInt4( INST_LIST, 4, envPtr); } - TclEmitInstInt4(INST_OVER, tempDepth, envPtr); } /* - * Emit code to load the variable's value. + * Issue the error via 'returnImm error 0'. */ - if (!simpleVarName) { - TclEmitOpcode(INST_LOAD_STK, envPtr); - } else if (isScalar) { - if (localIndex < 0) { - TclEmitOpcode(INST_LOAD_SCALAR_STK, envPtr); - } else if (localIndex < 0x100) { - TclEmitInstInt1(INST_LOAD_SCALAR1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_LOAD_SCALAR4, localIndex, envPtr); - } - } else { - if (localIndex < 0) { - TclEmitOpcode(INST_LOAD_ARRAY_STK, envPtr); - } else if (localIndex < 0x100) { - TclEmitInstInt1(INST_LOAD_ARRAY1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_LOAD_ARRAY4, localIndex, envPtr); - } - } + TclEmitInstInt4( INST_RETURN_IMM, TCL_ERROR, envPtr); + TclEmitInt4( 0, envPtr); + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileExprCmd -- + * + * Procedure called to compile the "expr" command. + * + * Results: + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. + * + * Side effects: + * Instructions are added to envPtr to execute the "expr" command at + * runtime. + * + *---------------------------------------------------------------------- + */ - /* - * Emit the correct variety of 'lset' instruction - */ +int +TclCompileExprCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ +{ + Tcl_Token *firstWordPtr; - if (parsePtr->numWords == 4) { - TclEmitOpcode(INST_LSET_LIST, envPtr); - } else { - TclEmitInstInt4(INST_LSET_FLAT, (parsePtr->numWords - 1), envPtr); + if (parsePtr->numWords == 1) { + return TCL_ERROR; } /* - * Emit code to put the value back in the variable + * TIP #280: Use the per-word line information of the current command. */ - if (!simpleVarName) { - TclEmitOpcode(INST_STORE_STK, envPtr); - } else if (isScalar) { - if (localIndex < 0) { - TclEmitOpcode(INST_STORE_SCALAR_STK, envPtr); - } else if (localIndex < 0x100) { - TclEmitInstInt1(INST_STORE_SCALAR1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_STORE_SCALAR4, localIndex, envPtr); - } - } else { - if (localIndex < 0) { - TclEmitOpcode(INST_STORE_ARRAY_STK, envPtr); - } else if (localIndex < 0x100) { - TclEmitInstInt1(INST_STORE_ARRAY1, localIndex, envPtr); - } else { - TclEmitInstInt4(INST_STORE_ARRAY4, localIndex, envPtr); - } - } + envPtr->line = envPtr->extCmdMapPtr->loc[ + envPtr->extCmdMapPtr->nuloc-1].line[1]; + firstWordPtr = TokenAfter(parsePtr->tokenPtr); + TclCompileExprWords(interp, firstWordPtr, parsePtr->numWords-1, envPtr); return TCL_OK; } /* *---------------------------------------------------------------------- * - * TclCompileRegexpCmd -- + * TclCompileForCmd -- * - * Procedure called to compile the "regexp" command. + * Procedure called to compile the "for" command. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "regexp" command - * at runtime. + * Instructions are added to envPtr to execute the "for" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileRegexpCmd(interp, parsePtr, envPtr) - Tcl_Interp* interp; /* Tcl interpreter for error reporting */ - Tcl_Parse* parsePtr; /* Points to a parse structure for - * the command */ - CompileEnv* envPtr; /* Holds the resulting instructions */ +TclCompileForCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - Tcl_Token *varTokenPtr; /* Pointer to the Tcl_Token representing - * the parse of the RE or string */ - int i, len, nocase, anchorLeft, anchorRight, start; - char *str; + Tcl_Token *startTokenPtr, *testTokenPtr, *nextTokenPtr, *bodyTokenPtr; + JumpFixup jumpEvalCondFixup; + int bodyCodeOffset, nextCodeOffset, jumpDist; + int bodyRange, nextRange; + DefineLineInformation; /* TIP #280 */ - /* - * We are only interested in compiling simple regexp cases. - * Currently supported compile cases are: - * regexp ?-nocase? ?--? staticString $var - * regexp ?-nocase? ?--? {^staticString$} $var - */ - if (parsePtr->numWords < 3) { - return TCL_OUT_LINE_COMPILE; + if (parsePtr->numWords != 5) { + return TCL_ERROR; } - nocase = 0; - varTokenPtr = parsePtr->tokenPtr; - /* - * We only look for -nocase and -- as options. Everything else - * gets pushed to runtime execution. This is different than regexp's - * runtime option handling, but satisfies our stricter needs. + * If the test expression requires substitutions, don't compile the for + * command inline. E.g., the expression might cause the loop to never + * execute or execute forever, as in "for {} "$x > 5" {incr x} {}". */ - for (i = 1; i < parsePtr->numWords - 2; i++) { - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - if (varTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { - /* Not a simple string - punt to runtime. */ - return TCL_OUT_LINE_COMPILE; - } - str = (char *) varTokenPtr[1].start; - len = varTokenPtr[1].size; - if ((len == 2) && (str[0] == '-') && (str[1] == '-')) { - i++; - break; - } else if ((len > 1) - && (strncmp(str, "-nocase", (unsigned) len) == 0)) { - nocase = 1; - } else { - /* Not an option we recognize. */ - return TCL_OUT_LINE_COMPILE; - } - } - if ((parsePtr->numWords - i) != 2) { - /* We don't support capturing to variables */ - return TCL_OUT_LINE_COMPILE; + startTokenPtr = TokenAfter(parsePtr->tokenPtr); + testTokenPtr = TokenAfter(startTokenPtr); + if (testTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { + return TCL_ERROR; } /* - * Get the regexp string. If it is not a simple string, punt to runtime. - * If it has a '-', it could be an incorrectly formed regexp command. + * Bail out also if the body or the next expression require substitutions + * in order to insure correct behaviour [Bug 219166] */ - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - str = (char *) varTokenPtr[1].start; - len = varTokenPtr[1].size; - if ((varTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) || (*str == '-')) { - return TCL_OUT_LINE_COMPILE; - } - if (len == 0) { - /* - * The semantics of regexp are always match on re == "". - */ - TclEmitPush(TclRegisterNewLiteral(envPtr, "1", 1), envPtr); - return TCL_OK; + nextTokenPtr = TokenAfter(testTokenPtr); + bodyTokenPtr = TokenAfter(nextTokenPtr); + if ((nextTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) + || (bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD)) { + return TCL_ERROR; } /* - * Make a copy of the string that is null-terminated for checks which - * require such. + * Inline compile the initial command. */ - str = (char *) ckalloc((unsigned) len + 1); - strncpy(str, varTokenPtr[1].start, (size_t) len); - str[len] = '\0'; - start = 0; + + BODY(startTokenPtr, 1); + TclEmitOpcode(INST_POP, envPtr); /* - * Check for anchored REs (ie ^foo$), so we can use string equal if - * possible. Do not alter the start of str so we can free it correctly. + * Jump to the evaluation of the condition. This code uses the "loop + * rotation" optimisation (which eliminates one branch from the loop). + * "for start cond next body" produces then: + * start + * goto A + * B: body : bodyCodeOffset + * next : nextCodeOffset, continueOffset + * A: cond -> result : testCodeOffset + * if (result) goto B */ - if (str[0] == '^') { - start++; - anchorLeft = 1; - } else { - anchorLeft = 0; - } - if ((str[len-1] == '$') && ((len == 1) || (str[len-2] != '\\'))) { - anchorRight = 1; - str[--len] = '\0'; - } else { - anchorRight = 0; - } + + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpEvalCondFixup); /* - * On the first (pattern) arg, check to see if any RE special characters - * are in the word. If not, this is the same as 'string equal'. + * Compile the loop body. */ - if ((len > (1+start)) && (str[start] == '.') && (str[start+1] == '*')) { - start += 2; - anchorLeft = 0; - } - if ((len > (2+start)) && (str[len-3] != '\\') - && (str[len-2] == '.') && (str[len-1] == '*')) { - len -= 2; - str[len] = '\0'; - anchorRight = 0; - } + + bodyRange = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); + bodyCodeOffset = ExceptionRangeStarts(envPtr, bodyRange); + BODY(bodyTokenPtr, 4); + ExceptionRangeEnds(envPtr, bodyRange); + TclEmitOpcode(INST_POP, envPtr); /* - * Don't do anything with REs with other special chars. Also check if - * this is a bad RE (do this at the end because it can be expensive). - * If so, let it complain at runtime. + * Compile the "next" subcommand. Note that this exception range will not + * have a continueOffset (other than -1) connected to it; it won't trap + * TCL_CONTINUE but rather just TCL_BREAK. */ - if ((strpbrk(str + start, "*+?{}()[].\\|^$") != NULL) - || (Tcl_RegExpCompile(NULL, str) == NULL)) { - ckfree((char *) str); - return TCL_OUT_LINE_COMPILE; - } - if (anchorLeft && anchorRight) { - TclEmitPush(TclRegisterNewLiteral(envPtr, str+start, len-start), - envPtr); - } else { - /* - * This needs to find the substring anywhere in the string, so - * use string match and *foo*, with appropriate anchoring. - */ - char *newStr = ckalloc((unsigned) len + 3); - len -= start; - if (anchorLeft) { - strncpy(newStr, str + start, (size_t) len); - } else { - newStr[0] = '*'; - strncpy(newStr + 1, str + start, (size_t) len++); - } - if (!anchorRight) { - newStr[len++] = '*'; - } - newStr[len] = '\0'; - TclEmitPush(TclRegisterNewLiteral(envPtr, newStr, len), envPtr); - ckfree((char *) newStr); - } - ckfree((char *) str); + nextRange = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); + envPtr->exceptAuxArrayPtr[nextRange].supportsContinue = 0; + nextCodeOffset = ExceptionRangeStarts(envPtr, nextRange); + BODY(nextTokenPtr, 3); + ExceptionRangeEnds(envPtr, nextRange); + TclEmitOpcode(INST_POP, envPtr); /* - * Push the string arg + * Compile the test expression then emit the conditional jump that + * terminates the for. */ - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, - varTokenPtr[1].start, varTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, varTokenPtr+1, - varTokenPtr->numComponents, envPtr); - } - if (anchorLeft && anchorRight && !nocase) { - TclEmitOpcode(INST_STR_EQ, envPtr); - } else { - TclEmitInstInt1(INST_STR_MATCH, nocase, envPtr); + if (TclFixupForwardJumpToHere(envPtr, &jumpEvalCondFixup, 127)) { + bodyCodeOffset += 3; + nextCodeOffset += 3; } - return TCL_OK; -} - -/* - *---------------------------------------------------------------------- - * - * TclCompileReturnCmd -- - * - * Procedure called to compile the "return" command. - * - * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. - * - * Side effects: - * Instructions are added to envPtr to execute the "return" command - * at runtime. - * - *---------------------------------------------------------------------- - */ + SetLineInformation(2); + TclCompileExprWords(interp, testTokenPtr, 1, envPtr); + TclClearNumConversion(envPtr); -int -TclCompileReturnCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ -{ - /* - * General syntax: [return ?-option value ...? ?result?] - * An even number of words means an explicit result argument is present. - */ - int level, code, status = TCL_OK; - int numWords = parsePtr->numWords; - int explicitResult = (0 == (numWords % 2)); - int numOptionWords = numWords - 1 - explicitResult; - Tcl_Obj *returnOpts; - Tcl_Token *wordTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); -#define NUM_STATIC_OBJS 20 - int objc; - Tcl_Obj *staticObjArray[NUM_STATIC_OBJS], **objv; - - if (numOptionWords > NUM_STATIC_OBJS) { - objv = (Tcl_Obj **) ckalloc(numOptionWords * sizeof(Tcl_Obj *)); + jumpDist = CurrentOffset(envPtr) - bodyCodeOffset; + if (jumpDist > 127) { + TclEmitInstInt4(INST_JUMP_TRUE4, -jumpDist, envPtr); } else { - objv = staticObjArray; - } - - /* - * Scan through the return options. If any are unknown at compile - * time, there is no value in bytecompiling. Save the option values - * known in an objv array for merging into a return options dictionary. - */ - - for (objc = 0; objc < numOptionWords; objc++) { - objv[objc] = Tcl_NewObj(); - Tcl_IncrRefCount(objv[objc]); - if (!TclWordKnownAtCompileTime(wordTokenPtr, objv[objc])) { - objc++; - status = TCL_ERROR; - goto cleanup; - } - wordTokenPtr += wordTokenPtr->numComponents + 1; - } - status = TclMergeReturnOptions(interp, objc, objv, - &returnOpts, &code, &level); -cleanup: - while (--objc >= 0) { - Tcl_DecrRefCount(objv[objc]); - } - if (numOptionWords > NUM_STATIC_OBJS) { - ckfree((char *)objv); - } - if (TCL_ERROR == status) { - /* - * Something was bogus in the return options. Clear the - * error message, and report back to the compiler that this - * must be interpreted at runtime. - */ - Tcl_ResetResult(interp); - return TCL_OUT_LINE_COMPILE; + TclEmitInstInt1(INST_JUMP_TRUE1, -jumpDist, envPtr); } /* - * All options are known at compile time, so we're going to bytecompile. - * Emit instructions to push the result on the stack + * Fix the starting points of the exception ranges (may have moved due to + * jump type modification) and set where the exceptions target. */ - if (explicitResult) { - if (wordTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - /* Simple word: compile quickly to a simple push */ - TclEmitPush(TclRegisterNewLiteral(envPtr, wordTokenPtr[1].start, - wordTokenPtr[1].size), envPtr); - } else { - /* More complex tokens get compiled */ - TclCompileTokens(interp, wordTokenPtr+1, - wordTokenPtr->numComponents, envPtr); - } - } else { - /* No explict result argument, so default result is empty string */ - TclEmitPush(TclRegisterNewLiteral(envPtr, "", 0), envPtr); - } - - /* - * Check for optimization: When [return] is in a proc, and there's - * no enclosing [catch], and there are no return options, then the - * INST_DONE instruction is equivalent, and may be more efficient. - */ - if (numOptionWords == 0) { - /* We have default return options... */ - if (envPtr->procPtr != NULL) { - /* ... and we're in a proc ... */ - int index = envPtr->exceptArrayNext - 1; - int enclosingCatch = 0; - while (index >= 0) { - ExceptionRange range = envPtr->exceptArrayPtr[index]; - if ((range.type == CATCH_EXCEPTION_RANGE) - && (range.catchOffset == -1)) { - enclosingCatch = 1; - break; - } - index--; - } - if (!enclosingCatch) { - /* ... and there is no enclosing catch. */ - Tcl_DecrRefCount(returnOpts); - TclEmitOpcode(INST_DONE, envPtr); - return TCL_OK; - } - } - } + envPtr->exceptArrayPtr[bodyRange].codeOffset = bodyCodeOffset; + envPtr->exceptArrayPtr[bodyRange].continueOffset = nextCodeOffset; + + envPtr->exceptArrayPtr[nextRange].codeOffset = nextCodeOffset; + + ExceptionRangeTarget(envPtr, bodyRange, breakOffset); + ExceptionRangeTarget(envPtr, nextRange, breakOffset); + TclFinalizeLoopExceptionRange(envPtr, bodyRange); + TclFinalizeLoopExceptionRange(envPtr, nextRange); /* - * Could not use the optimization, so we push the return options - * dictionary, and emit the INST_RETURN instruction with code - * and level as operands. + * The for command's result is an empty string. */ - TclEmitPush(TclAddLiteralObj(envPtr, returnOpts, NULL), envPtr); - TclEmitInstInt4(INST_RETURN, code, envPtr); - TclEmitInt4(level, envPtr); + PushStringLiteral(envPtr, ""); + return TCL_OK; } /* *---------------------------------------------------------------------- * - * TclCompileSetCmd -- + * TclCompileForeachCmd -- * - * Procedure called to compile the "set" command. + * Procedure called to compile the "foreach" command. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "set" command - * at runtime. + * Instructions are added to envPtr to execute the "foreach" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileSetCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileForeachCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - Tcl_Token *varTokenPtr, *valueTokenPtr; - int isAssignment, isScalar, simpleVarName, localIndex, numWords; - - numWords = parsePtr->numWords; - if ((numWords != 2) && (numWords != 3)) { - return TCL_OUT_LINE_COMPILE; - } - isAssignment = (numWords == 3); - - /* - * Decide if we can use a frame slot for the var/array name or if we - * need to emit code to compute and push the name at runtime. We use a - * frame slot (entry in the array of local vars) if we are compiling a - * procedure body and if the name is simple text that does not include - * namespace qualifiers. - */ - - varTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); - - PushVarName(interp, varTokenPtr, envPtr, TCL_CREATE_VAR, - &localIndex, &simpleVarName, &isScalar); - - /* - * If we are doing an assignment, push the new value. - */ - - if (isAssignment) { - valueTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - if (valueTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, valueTokenPtr[1].start, - valueTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, valueTokenPtr+1, - valueTokenPtr->numComponents, envPtr); - } - } - - /* - * Emit instructions to set/get the variable. - */ - - if (simpleVarName) { - if (isScalar) { - if (localIndex >= 0) { - if (localIndex <= 255) { - TclEmitInstInt1((isAssignment? - INST_STORE_SCALAR1 : INST_LOAD_SCALAR1), - localIndex, envPtr); - } else { - TclEmitInstInt4((isAssignment? - INST_STORE_SCALAR4 : INST_LOAD_SCALAR4), - localIndex, envPtr); - } - } else { - TclEmitOpcode((isAssignment? - INST_STORE_SCALAR_STK : INST_LOAD_SCALAR_STK), envPtr); - } - } else { - if (localIndex >= 0) { - if (localIndex <= 255) { - TclEmitInstInt1((isAssignment? - INST_STORE_ARRAY1 : INST_LOAD_ARRAY1), - localIndex, envPtr); - } else { - TclEmitInstInt4((isAssignment? - INST_STORE_ARRAY4 : INST_LOAD_ARRAY4), - localIndex, envPtr); - } - } else { - TclEmitOpcode((isAssignment? - INST_STORE_ARRAY_STK : INST_LOAD_ARRAY_STK), envPtr); - } - } - } else { - TclEmitOpcode((isAssignment? INST_STORE_STK : INST_LOAD_STK), envPtr); - } - - return TCL_OK; + return CompileEachloopCmd(interp, parsePtr, cmdPtr, envPtr, + TCL_EACH_KEEP_NONE); } /* *---------------------------------------------------------------------- * - * TclCompileStringCmd -- + * TclCompileLmapCmd -- * - * Procedure called to compile the "string" command. + * Procedure called to compile the "lmap" command. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "string" command - * at runtime. + * Instructions are added to envPtr to execute the "lmap" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileStringCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileLmapCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - Tcl_Token *opTokenPtr, *varTokenPtr; - Tcl_Obj *opObj; - int index; - - static CONST char *options[] = { - "bytelength", "compare", "equal", "first", - "index", "is", "last", "length", - "map", "match", "range", "repeat", - "replace", "tolower", "toupper", "totitle", - "trim", "trimleft", "trimright", - "wordend", "wordstart", (char *) NULL - }; - enum options { - STR_BYTELENGTH, STR_COMPARE, STR_EQUAL, STR_FIRST, - STR_INDEX, STR_IS, STR_LAST, STR_LENGTH, - STR_MAP, STR_MATCH, STR_RANGE, STR_REPEAT, - STR_REPLACE, STR_TOLOWER, STR_TOUPPER, STR_TOTITLE, - STR_TRIM, STR_TRIMLEFT, STR_TRIMRIGHT, - STR_WORDEND, STR_WORDSTART - }; - - if (parsePtr->numWords < 2) { - /* Fail at run time, not in compilation */ - return TCL_OUT_LINE_COMPILE; - } - opTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); - - opObj = Tcl_NewStringObj(opTokenPtr->start, opTokenPtr->size); - if (Tcl_GetIndexFromObj(interp, opObj, options, "option", 0, - &index) != TCL_OK) { - Tcl_DecrRefCount(opObj); - Tcl_ResetResult(interp); - return TCL_OUT_LINE_COMPILE; - } - Tcl_DecrRefCount(opObj); - - varTokenPtr = opTokenPtr + (opTokenPtr->numComponents + 1); - - switch ((enum options) index) { - case STR_BYTELENGTH: - case STR_FIRST: - case STR_IS: - case STR_LAST: - case STR_MAP: - case STR_RANGE: - case STR_REPEAT: - case STR_REPLACE: - case STR_TOLOWER: - case STR_TOUPPER: - case STR_TOTITLE: - case STR_TRIM: - case STR_TRIMLEFT: - case STR_TRIMRIGHT: - case STR_WORDEND: - case STR_WORDSTART: - /* - * All other cases: compile out of line. - */ - return TCL_OUT_LINE_COMPILE; - - case STR_COMPARE: - case STR_EQUAL: { - int i; - /* - * If there are any flags to the command, we can't byte compile it - * because the INST_STR_EQ bytecode doesn't support flags. - */ - - if (parsePtr->numWords != 4) { - return TCL_OUT_LINE_COMPILE; - } - - /* - * Push the two operands onto the stack. - */ - - for (i = 0; i < 2; i++) { - if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, - varTokenPtr[1].start, varTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, varTokenPtr+1, - varTokenPtr->numComponents, envPtr); - } - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - } - - TclEmitOpcode(((((enum options) index) == STR_COMPARE) ? - INST_STR_CMP : INST_STR_EQ), envPtr); - return TCL_OK; - } - case STR_INDEX: { - int i; - - if (parsePtr->numWords != 4) { - /* Fail at run time, not in compilation */ - return TCL_OUT_LINE_COMPILE; - } - - /* - * Push the two operands onto the stack. - */ - - for (i = 0; i < 2; i++) { - if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, - varTokenPtr[1].start, varTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, varTokenPtr+1, - varTokenPtr->numComponents, envPtr); - } - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - } - - TclEmitOpcode(INST_STR_INDEX, envPtr); - return TCL_OK; - } - case STR_LENGTH: { - if (parsePtr->numWords != 3) { - /* Fail at run time, not in compilation */ - return TCL_OUT_LINE_COMPILE; - } - - if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - /* - * Here someone is asking for the length of a static string. - * Just push the actual character (not byte) length. - */ - char buf[TCL_INTEGER_SPACE]; - int len = Tcl_NumUtfChars(varTokenPtr[1].start, - varTokenPtr[1].size); - len = sprintf(buf, "%d", len); - TclEmitPush(TclRegisterNewLiteral(envPtr, buf, len), envPtr); - return TCL_OK; - } else { - TclCompileTokens(interp, varTokenPtr+1, - varTokenPtr->numComponents, envPtr); - } - TclEmitOpcode(INST_STR_LEN, envPtr); - return TCL_OK; - } - case STR_MATCH: { - int i, length, exactMatch = 0, nocase = 0; - CONST char *str; - - if (parsePtr->numWords < 4 || parsePtr->numWords > 5) { - /* Fail at run time, not in compilation */ - return TCL_OUT_LINE_COMPILE; - } - - if (parsePtr->numWords == 5) { - if (varTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { - return TCL_OUT_LINE_COMPILE; - } - str = varTokenPtr[1].start; - length = varTokenPtr[1].size; - if ((length > 1) && - strncmp(str, "-nocase", (size_t) length) == 0) { - nocase = 1; - } else { - /* Fail at run time, not in compilation */ - return TCL_OUT_LINE_COMPILE; - } - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - } - - for (i = 0; i < 2; i++) { - if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - str = varTokenPtr[1].start; - length = varTokenPtr[1].size; - if (!nocase && (i == 0)) { - /* - * On the first (pattern) arg, check to see if any - * glob special characters are in the word '*[]?\\'. - * If not, this is the same as 'string equal'. We - * can use strpbrk here because the glob chars are all - * in the ascii-7 range. If -nocase was specified, - * we can't do this because INST_STR_EQ has no support - * for nocase. - */ - Tcl_Obj *copy = Tcl_NewStringObj(str, length); - Tcl_IncrRefCount(copy); - exactMatch = (strpbrk(Tcl_GetString(copy), - "*[]?\\") == NULL); - Tcl_DecrRefCount(copy); - } - TclEmitPush( - TclRegisterNewLiteral(envPtr, str, length), envPtr); - } else { - TclCompileTokens(interp, varTokenPtr+1, - varTokenPtr->numComponents, envPtr); - } - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); - } - - if (exactMatch) { - TclEmitOpcode(INST_STR_EQ, envPtr); - } else { - TclEmitInstInt1(INST_STR_MATCH, nocase, envPtr); - } - return TCL_OK; - } - } - - return TCL_OK; + return CompileEachloopCmd(interp, parsePtr, cmdPtr, envPtr, + TCL_EACH_COLLECT); } /* *---------------------------------------------------------------------- * - * TclCompileSwitchCmd -- + * CompileEachloopCmd -- * - * Procedure called to compile the "switch" command. + * Procedure called to compile the "foreach" and "lmap" commands. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "switch" command - * at runtime. + * Instructions are added to envPtr to execute the "foreach" command at + * runtime. * *---------------------------------------------------------------------- */ -int -TclCompileSwitchCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ -{ - Tcl_Token *tokenPtr; /* Pointer to tokens in command */ - Tcl_Token *valueTokenPtr; /* Token for the value to switch on. */ - int foundDefault; /* Flag to indicate whether a "default" - * clause is present. */ - enum {Switch_Exact, Switch_Glob} mode; - /* What kind of switch are we doing? */ - int i, j; /* Loop counter variables. */ - Tcl_DString bodyList; /* Used for splitting the pattern list. */ - int argc; /* Number of items in pattern list. */ - CONST char **argv; /* Array of copies of items in pattern list. */ - Tcl_Token *bodyTokenArray; /* Array of real pattern list items. */ - CONST char *tokenStartPtr; /* Used as part of synthesizing tokens. */ - int isTokenBraced; +static int +CompileEachloopCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr, /* Holds resulting instructions. */ + int collect) /* Select collecting or accumulating mode + * (TCL_EACH_*) */ +{ + Proc *procPtr = envPtr->procPtr; + ForeachInfo *infoPtr; /* Points to the structure describing this + * foreach command. Stored in a AuxData + * record in the ByteCode. */ + + Tcl_Token *tokenPtr, *bodyTokenPtr; + int jumpBackOffset, infoIndex, range; + int numWords, numLists, numVars, loopIndex, i, j, code; + DefineLineInformation; /* TIP #280 */ - JumpFixup *fixupArray; /* Array of forward-jump fixup records. */ - int *fixupTargetArray; /* Array of places for fixups to point at. */ - int fixupCount; /* Number of places to fix up. */ - int contFixIndex; /* Where the first of the jumps due to a - * group of continuation bodies starts, - * or -1 if there aren't any. */ - int contFixCount = 0; /* Number of continuation bodies pointing - * to the current (or next) real body. */ - int codeOffset; /* Cache of current bytecode offset. */ - int savedStackDepth = envPtr->currStackDepth; + /* + * We parse the variable list argument words and create two arrays: + * varcList[i] is number of variables in i-th var list. + * varvList[i] points to array of var names in i-th var list. + */ - tokenPtr = parsePtr->tokenPtr; + int *varcList; + const char ***varvList; /* - * Only handle the following versions: - * switch -- word {pattern body ...} - * switch -exact -- word {pattern body ...} - * switch -glob -- word {pattern body ...} + * If the foreach command isn't in a procedure, don't compile it inline: + * the payoff is too small. */ - if (parsePtr->numWords != 5 && - parsePtr->numWords != 4) { - return TCL_OUT_LINE_COMPILE; + if (procPtr == NULL) { + return TCL_ERROR; + } + + numWords = parsePtr->numWords; + if ((numWords < 4) || (numWords%2 != 0)) { + return TCL_ERROR; } /* - * We don't care how the command's word was generated; we're - * compiling it anyway! + * Bail out if the body requires substitutions in order to insure correct + * behaviour. [Bug 219166] */ - tokenPtr += tokenPtr->numComponents + 1; - if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { - return TCL_OUT_LINE_COMPILE; - } else { - register int size = tokenPtr[1].size; - register CONST char *chrs = tokenPtr[1].start; - - if (size < 2) { - return TCL_OUT_LINE_COMPILE; - } - if ((size <= 6) && (parsePtr->numWords == 5) - && !strncmp(chrs, "-exact", (unsigned) TclMin(size, 6))) { - mode = Switch_Exact; - tokenPtr += 2; - } else if ((size <= 5) && (parsePtr->numWords == 5) - && !strncmp(chrs, "-glob", (unsigned) TclMin(size, 5))) { - mode = Switch_Glob; - tokenPtr += 2; - } else if ((size == 2) && (parsePtr->numWords == 4) - && !strncmp(chrs, "--", 2)) { - /* - * If no control flag present, use exact matching (the default). - * - * We end up re-checking this word, but that's the way things are... - */ - mode = Switch_Exact; - } else { - return TCL_OUT_LINE_COMPILE; - } + for (i = 0, tokenPtr = parsePtr->tokenPtr; i < numWords-1; i++) { + tokenPtr = TokenAfter(tokenPtr); } - if ((tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) - || (tokenPtr[1].size != 2) || strncmp(tokenPtr[1].start, "--", 2)) { - return TCL_OUT_LINE_COMPILE; + bodyTokenPtr = tokenPtr; + if (bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { + return TCL_ERROR; } - tokenPtr += 2; /* - * The value to test against is going to always get pushed on the - * stack. But not yet; we need to verify that the rest of the - * command is compilable too. + * Allocate storage for the varcList and varvList arrays if necessary. */ - valueTokenPtr = tokenPtr; - tokenPtr += tokenPtr->numComponents + 1; + numLists = (numWords - 2)/2; + varcList = TclStackAlloc(interp, numLists * sizeof(int)); + memset(varcList, 0, numLists * sizeof(int)); + varvList = (const char ***) TclStackAlloc(interp, + numLists * sizeof(const char **)); + memset((char*) varvList, 0, numLists * sizeof(const char **)); /* - * Test that we've got a suitable body list as a simple (i.e. - * braced) word, and that the elements of the body are simple - * words too. This is really rather nasty indeed. + * Break up each var list and set the varcList and varvList arrays. Don't + * compile the foreach inline if any var name needs substitutions or isn't + * a scalar, or if any var list needs substitutions. */ - if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { - return TCL_OUT_LINE_COMPILE; - } - Tcl_DStringInit(&bodyList); - Tcl_DStringAppend(&bodyList, tokenPtr[1].start, tokenPtr[1].size); - if (Tcl_SplitList(NULL, Tcl_DStringValue(&bodyList), &argc, - &argv) != TCL_OK) { - Tcl_DStringFree(&bodyList); - return TCL_OUT_LINE_COMPILE; - } - Tcl_DStringFree(&bodyList); - if (argc == 0 || argc % 2) { - ckfree((char *)argv); - return TCL_OUT_LINE_COMPILE; - } - bodyTokenArray = (Tcl_Token *) ckalloc(sizeof(Tcl_Token) * argc); - tokenStartPtr = tokenPtr[1].start; - while (isspace(UCHAR(*tokenStartPtr))) { - tokenStartPtr++; - } - if (*tokenStartPtr == '{') { - tokenStartPtr++; - isTokenBraced = 1; - } else { - isTokenBraced = 0; - } - for (i=0 ; i<argc ; i++) { - bodyTokenArray[i].type = TCL_TOKEN_TEXT; - bodyTokenArray[i].start = tokenStartPtr; - bodyTokenArray[i].size = strlen(argv[i]); - bodyTokenArray[i].numComponents = 0; - tokenStartPtr += bodyTokenArray[i].size; + loopIndex = 0; + for (i = 0, tokenPtr = parsePtr->tokenPtr; + i < numWords-1; + i++, tokenPtr = TokenAfter(tokenPtr)) { + Tcl_DString varList; + + if (i%2 != 1) { + continue; + } + if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { + code = TCL_ERROR; + goto done; + } + /* - * Test to see if we have guessed the end of the word - * correctly; if not, we can't feed the real string to the - * sub-compilation engine, and we're then stuck and so have to - * punt out to doing everything at runtime. + * Lots of copying going on here. Need a ListObj wizard to show a + * better way. */ - if (isTokenBraced && *(tokenStartPtr++) != '}') { - ckfree((char *)argv); - ckfree((char *)bodyTokenArray); - return TCL_OUT_LINE_COMPILE; + + Tcl_DStringInit(&varList); + TclDStringAppendToken(&varList, &tokenPtr[1]); + code = Tcl_SplitList(NULL, Tcl_DStringValue(&varList), + &varcList[loopIndex], &varvList[loopIndex]); + Tcl_DStringFree(&varList); + if (code != TCL_OK) { + code = TCL_ERROR; + goto done; } - if ((tokenStartPtr < tokenPtr[1].start+tokenPtr[1].size) - && !isspace(UCHAR(*tokenStartPtr))) { - ckfree((char *)argv); - ckfree((char *)bodyTokenArray); - return TCL_OUT_LINE_COMPILE; + numVars = varcList[loopIndex]; + + /* + * If the variable list is empty, we can enter an infinite loop when + * the interpreted version would not. Take care to ensure this does + * not happen. [Bug 1671138] + */ + + if (numVars == 0) { + code = TCL_ERROR; + goto done; } - while (isspace(UCHAR(*tokenStartPtr))) { - tokenStartPtr++; - if (tokenStartPtr >= tokenPtr[1].start+tokenPtr[1].size) { - break; + + for (j = 0; j < numVars; j++) { + const char *varName = varvList[loopIndex][j]; + + if (!TclIsLocalScalar(varName, (int) strlen(varName))) { + code = TCL_ERROR; + goto done; } } - if (*tokenStartPtr == '{') { - tokenStartPtr++; - isTokenBraced = 1; - } else { - isTokenBraced = 0; - } - } - if (tokenStartPtr != tokenPtr[1].start+tokenPtr[1].size) { - ckfree((char *)argv); - ckfree((char *)bodyTokenArray); - fprintf(stderr, "BAD ASSUMPTION\n"); - return TCL_OUT_LINE_COMPILE; + loopIndex++; } /* - * Complain if the last body is a continuation. Note that this - * check assumes that the list is non-empty! + * We will compile the foreach command. */ - if (argc>0 && argv[argc-1][0]=='-' && argv[argc-1]=='\0') { - ckfree((char *)argv); - ckfree((char *)bodyTokenArray); - return TCL_OUT_LINE_COMPILE; - } + code = TCL_OK; /* - * Now we commit to generating code; the parsing stage per se is - * done. - * - * First, we push the value we're matching against on the stack. + * Create and initialize the ForeachInfo and ForeachVarList data + * structures describing this command. Then create a AuxData record + * pointing to the ForeachInfo structure. */ - if (valueTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - TclEmitPush(TclRegisterNewLiteral(envPtr, valueTokenPtr[1].start, - valueTokenPtr[1].size), envPtr); - } else { - TclCompileTokens(interp, valueTokenPtr+1, - valueTokenPtr->numComponents, envPtr); + infoPtr = ckalloc(sizeof(ForeachInfo) + + (numLists - 1) * sizeof(ForeachVarList *)); + infoPtr->numLists = numLists; + for (loopIndex = 0; loopIndex < numLists; loopIndex++) { + ForeachVarList *varListPtr; + + numVars = varcList[loopIndex]; + varListPtr = ckalloc(sizeof(ForeachVarList) + + (numVars - 1) * sizeof(int)); + varListPtr->numVars = numVars; + for (j = 0; j < numVars; j++) { + const char *varName = varvList[loopIndex][j]; + int nameChars = strlen(varName); + + varListPtr->varIndexes[j] = TclFindCompiledLocal(varName, + nameChars, /*create*/ 1, envPtr); + } + infoPtr->varLists[loopIndex] = varListPtr; } + infoIndex = TclCreateAuxData(infoPtr, &tclNewForeachInfoType, envPtr); /* - * Generate a test for each arm. + * Create the collecting object, unshared. + */ + + if (collect == TCL_EACH_COLLECT) { + TclEmitInstInt4(INST_LIST, 0, envPtr); + } + + /* + * Evaluate each value list and leave it on stack. */ - contFixIndex = -1; - fixupArray = (JumpFixup *) ckalloc(sizeof(JumpFixup) * argc); - fixupTargetArray = (int *) ckalloc(sizeof(int) * argc); - (VOID *) memset(fixupTargetArray, 0, argc * sizeof(int)); - fixupCount = 0; - foundDefault = 0; - for (i=0 ; i<argc ; i+=2) { - int nextArmFixupIndex = -1; - - /* - * Generate the test for the arm. - */ - - envPtr->currStackDepth = savedStackDepth + 1; - if (argv[i][0]!='d' || strcmp(argv[i], "default") || i!=argc-2) { - switch (mode) { - case Switch_Exact: - TclEmitOpcode(INST_DUP, envPtr); - TclEmitPush(TclRegisterNewLiteral(envPtr, argv[i], - (int) strlen(argv[i])), envPtr); - TclEmitOpcode(INST_STR_EQ, envPtr); - break; - case Switch_Glob: - TclEmitPush(TclRegisterNewLiteral(envPtr, argv[i], - (int) strlen(argv[i])), envPtr); - TclEmitInstInt4(INST_OVER, 1, envPtr); - TclEmitInstInt1(INST_STR_MATCH, /*nocase*/0, envPtr); - break; - default: - Tcl_Panic("unknown switch mode: %d",mode); - } - /* - * Process fall-through clauses here... - */ - if (argv[i+1][0]=='-' && argv[i+1][1]=='\0') { - if (contFixIndex == -1) { - contFixIndex = fixupCount; - contFixCount = 0; - } - TclEmitForwardJump(envPtr, TCL_TRUE_JUMP, - &fixupArray[contFixIndex+contFixCount]); - fixupCount++; - contFixCount++; - continue; - } - TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, - &fixupArray[fixupCount]); - nextArmFixupIndex = fixupCount; - fixupCount++; - } else { - /* - * Got a default clause; set a flag. - */ - foundDefault = 1; - /* - * Note that default clauses (which are always last - * clauses) cannot be fall-through clauses as well, - * because the last clause is never a fall-through clause. - */ + for (i = 0, tokenPtr = parsePtr->tokenPtr; + i < numWords-1; + i++, tokenPtr = TokenAfter(tokenPtr)) { + if ((i%2 == 0) && (i > 0)) { + CompileWord(envPtr, tokenPtr, interp, i); } + } - /* - * Generate the body for the arm. This is guaranteed not to - * be a fall-through case, but it might have preceding - * fall-through cases, so we must process those first. - */ - - if (contFixIndex != -1) { - codeOffset = envPtr->codeNext-envPtr->codeStart; - for (j=0 ; j<contFixCount ; j++) { - fixupTargetArray[contFixIndex+j] = codeOffset; - } - contFixIndex = -1; - } + TclEmitInstInt4(INST_FOREACH_START, infoIndex, envPtr); + + /* + * Inline compile the loop body. + */ - /* - * Now do the actual compilation. - */ + range = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); - TclEmitOpcode(INST_POP, envPtr); - envPtr->currStackDepth = savedStackDepth + 1; - TclCompileCmdWord(interp, bodyTokenArray+i+1, 1, envPtr); - - if (!foundDefault) { - TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, - &fixupArray[fixupCount]); - fixupCount++; - fixupTargetArray[nextArmFixupIndex] = - envPtr->codeNext-envPtr->codeStart; - } + ExceptionRangeStarts(envPtr, range); + BODY(bodyTokenPtr, numWords - 1); + ExceptionRangeEnds(envPtr, range); + + if (collect == TCL_EACH_COLLECT) { + TclEmitOpcode(INST_LMAP_COLLECT, envPtr); + } else { + TclEmitOpcode( INST_POP, envPtr); } - ckfree((char *)argv); - ckfree((char *)bodyTokenArray); /* - * Discard the value we are matching against unless we've had a - * default clause (in which case it will already be gone) and make - * the result of the command an empty string. + * Bottom of loop code: assign each loop variable and check whether + * to terminate the loop. Set the loop's break target. */ - if (!foundDefault) { - TclEmitOpcode(INST_POP, envPtr); - TclEmitPush(TclRegisterNewLiteral(envPtr, "", 0), envPtr); - } + ExceptionRangeTarget(envPtr, range, continueOffset); + TclEmitOpcode(INST_FOREACH_STEP, envPtr); + ExceptionRangeTarget(envPtr, range, breakOffset); + TclFinalizeLoopExceptionRange(envPtr, range); + TclEmitOpcode(INST_FOREACH_END, envPtr); + TclAdjustStackDepth(-(numLists+2), envPtr); /* - * Do jump fixups for arms that were executed. First, fill in the - * jumps of all jumps that don't point elsewhere to point to here. + * Set the jumpback distance from INST_FOREACH_STEP to the start of the + * body's code. Misuse loopCtTemp for storing the jump size. */ - codeOffset = envPtr->codeNext-envPtr->codeStart; - for (i=0 ; i<fixupCount ; i++) { - if (fixupTargetArray[i] == 0) { - fixupTargetArray[i] = codeOffset; - } - } + + jumpBackOffset = envPtr->exceptArrayPtr[range].continueOffset - + envPtr->exceptArrayPtr[range].codeOffset; + infoPtr->loopCtTemp = -jumpBackOffset; /* - * Now scan backwards over all the jumps (all of which are forward - * jumps) doing each one. When we do one and there is a size - * changes, we must scan back over all the previous ones and see - * if they need adjusting before proceeding with further jump - * fixups. + * The command's result is an empty string if not collecting. If + * collecting, it is automatically left on stack after FOREACH_END. */ - for (i=fixupCount-1 ; i>=0 ; i--) { - if (TclFixupForwardJump(envPtr, &fixupArray[i], - fixupTargetArray[i]-fixupArray[i].codeOffset, 127)) { - for (j=i-1 ; j>=0 ; j--) { - if (fixupTargetArray[j] > fixupArray[i].codeOffset) { - fixupTargetArray[j] += 3; - } - } + + if (collect != TCL_EACH_COLLECT) { + PushStringLiteral(envPtr, ""); + } + + done: + for (loopIndex = 0; loopIndex < numLists; loopIndex++) { + if (varvList[loopIndex] != NULL) { + ckfree(varvList[loopIndex]); } } - ckfree((char *)fixupArray); - ckfree((char *)fixupTargetArray); - - envPtr->currStackDepth = savedStackDepth + 1; - return TCL_OK; + TclStackFree(interp, (void *)varvList); + TclStackFree(interp, varcList); + return code; } /* *---------------------------------------------------------------------- * - * TclCompileVariableCmd -- + * DupForeachInfo -- * - * Procedure called to reserve the local variables for the - * "variable" command. The command itself is *not* compiled. + * This procedure duplicates a ForeachInfo structure created as auxiliary + * data during the compilation of a foreach command. * * Results: - * Always returns TCL_OUT_LINE_COMPILE. + * A pointer to a newly allocated copy of the existing ForeachInfo + * structure is returned. * * Side effects: - * Indexed local variables are added to the environment. + * Storage for the copied ForeachInfo record is allocated. If the + * original ForeachInfo structure pointed to any ForeachVarList records, + * these structures are also copied and pointers to them are stored in + * the new ForeachInfo record. * *---------------------------------------------------------------------- */ -int -TclCompileVariableCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ + +static ClientData +DupForeachInfo( + ClientData clientData) /* The foreach command's compilation auxiliary + * data to duplicate. */ { - Tcl_Token *varTokenPtr; - int i, numWords; - CONST char *varName, *tail; + register ForeachInfo *srcPtr = clientData; + ForeachInfo *dupPtr; + register ForeachVarList *srcListPtr, *dupListPtr; + int numVars, i, j, numLists = srcPtr->numLists; + + dupPtr = ckalloc(sizeof(ForeachInfo) + + numLists * sizeof(ForeachVarList *)); + dupPtr->numLists = numLists; + dupPtr->firstValueTemp = srcPtr->firstValueTemp; + dupPtr->loopCtTemp = srcPtr->loopCtTemp; - if (envPtr->procPtr == NULL) { - return TCL_OUT_LINE_COMPILE; + for (i = 0; i < numLists; i++) { + srcListPtr = srcPtr->varLists[i]; + numVars = srcListPtr->numVars; + dupListPtr = ckalloc(sizeof(ForeachVarList) + + numVars * sizeof(int)); + dupListPtr->numVars = numVars; + for (j = 0; j < numVars; j++) { + dupListPtr->varIndexes[j] = srcListPtr->varIndexes[j]; + } + dupPtr->varLists[i] = dupListPtr; } + return dupPtr; +} + +/* + *---------------------------------------------------------------------- + * + * FreeForeachInfo -- + * + * Procedure to free a ForeachInfo structure created as auxiliary data + * during the compilation of a foreach command. + * + * Results: + * None. + * + * Side effects: + * Storage for the ForeachInfo structure pointed to by the ClientData + * argument is freed as is any ForeachVarList record pointed to by the + * ForeachInfo structure. + * + *---------------------------------------------------------------------- + */ - numWords = parsePtr->numWords; +static void +FreeForeachInfo( + ClientData clientData) /* The foreach command's compilation auxiliary + * data to free. */ +{ + register ForeachInfo *infoPtr = clientData; + register ForeachVarList *listPtr; + int numLists = infoPtr->numLists; + register int i; + + for (i = 0; i < numLists; i++) { + listPtr = infoPtr->varLists[i]; + ckfree(listPtr); + } + ckfree(infoPtr); +} + +/* + *---------------------------------------------------------------------- + * + * PrintForeachInfo -- + * + * Function to write a human-readable representation of a ForeachInfo + * structure to stdout for debugging. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ - varTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); - for (i = 1; i < numWords; i += 2) { - if (varTokenPtr->type == TCL_TOKEN_SIMPLE_WORD) { - varName = varTokenPtr[1].start; - tail = varName + varTokenPtr[1].size - 1; - if ((*tail == ')') || (tail < varName)) continue; - while ((tail > varName) && ((*tail != ':') || (*(tail-1) != ':'))) { - tail--; +static void +PrintForeachInfo( + ClientData clientData, + Tcl_Obj *appendObj, + ByteCode *codePtr, + unsigned int pcOffset) +{ + register ForeachInfo *infoPtr = clientData; + register ForeachVarList *varsPtr; + int i, j; + + Tcl_AppendToObj(appendObj, "data=[", -1); + + for (i=0 ; i<infoPtr->numLists ; i++) { + if (i) { + Tcl_AppendToObj(appendObj, ", ", -1); + } + Tcl_AppendPrintfToObj(appendObj, "%%v%u", + (unsigned) (infoPtr->firstValueTemp + i)); + } + Tcl_AppendPrintfToObj(appendObj, "], loop=%%v%u", + (unsigned) infoPtr->loopCtTemp); + for (i=0 ; i<infoPtr->numLists ; i++) { + if (i) { + Tcl_AppendToObj(appendObj, ",", -1); + } + Tcl_AppendPrintfToObj(appendObj, "\n\t\t it%%v%u\t[", + (unsigned) (infoPtr->firstValueTemp + i)); + varsPtr = infoPtr->varLists[i]; + for (j=0 ; j<varsPtr->numVars ; j++) { + if (j) { + Tcl_AppendToObj(appendObj, ", ", -1); } - if ((*tail == ':') && (tail > varName)) { - tail++; + Tcl_AppendPrintfToObj(appendObj, "%%v%u", + (unsigned) varsPtr->varIndexes[j]); + } + Tcl_AppendToObj(appendObj, "]", -1); + } +} + +static void +PrintNewForeachInfo( + ClientData clientData, + Tcl_Obj *appendObj, + ByteCode *codePtr, + unsigned int pcOffset) +{ + register ForeachInfo *infoPtr = clientData; + register ForeachVarList *varsPtr; + int i, j; + + Tcl_AppendPrintfToObj(appendObj, "jumpOffset=%+d, vars=", + infoPtr->loopCtTemp); + for (i=0 ; i<infoPtr->numLists ; i++) { + if (i) { + Tcl_AppendToObj(appendObj, ",", -1); + } + Tcl_AppendToObj(appendObj, "[", -1); + varsPtr = infoPtr->varLists[i]; + for (j=0 ; j<varsPtr->numVars ; j++) { + if (j) { + Tcl_AppendToObj(appendObj, ",", -1); } - (void) TclFindCompiledLocal(tail, (tail-varName+1), - /*create*/ 1, /*flags*/ 0, envPtr->procPtr); - varTokenPtr = varTokenPtr + (varTokenPtr->numComponents + 1); + Tcl_AppendPrintfToObj(appendObj, "%%v%u", + (unsigned) varsPtr->varIndexes[j]); } + Tcl_AppendToObj(appendObj, "]", -1); } - return TCL_OUT_LINE_COMPILE; } /* *---------------------------------------------------------------------- * - * TclCompileWhileCmd -- + * TclCompileFormatCmd -- * - * Procedure called to compile the "while" command. + * Procedure called to compile the "format" command. Handles cases that + * can be done as constants or simple string concatenation only. * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * Returns TCL_OK for a successful compile. Returns TCL_ERROR to defer + * evaluation to runtime. * * Side effects: - * Instructions are added to envPtr to execute the "while" command - * at runtime. + * Instructions are added to envPtr to execute the "format" command at + * runtime. * *---------------------------------------------------------------------- */ int -TclCompileWhileCmd(interp, parsePtr, envPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Parse *parsePtr; /* Points to a parse structure for the - * command created by Tcl_ParseCommand. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ +TclCompileFormatCmd( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Parse *parsePtr, /* Points to a parse structure for the command + * created by Tcl_ParseCommand. */ + Command *cmdPtr, /* Points to defintion of command being + * compiled. */ + CompileEnv *envPtr) /* Holds resulting instructions. */ { - Tcl_Token *testTokenPtr, *bodyTokenPtr; - JumpFixup jumpEvalCondFixup; - int testCodeOffset, bodyCodeOffset, jumpDist; - int range, code; - int savedStackDepth = envPtr->currStackDepth; - int loopMayEnd = 1; /* This is set to 0 if it is recognized as - * an infinite loop. */ - Tcl_Obj *boolObj; - int boolVal; - - if (parsePtr->numWords != 3) { - return TCL_OUT_LINE_COMPILE; - } + DefineLineInformation; /* TIP #280 */ + Tcl_Token *tokenPtr = parsePtr->tokenPtr; + Tcl_Obj **objv, *formatObj, *tmpObj; + char *bytes, *start; + int i, j, len; /* - * If the test expression requires substitutions, don't compile the - * while command inline. E.g., the expression might cause the loop to - * never execute or execute forever, as in "while "$x < 5" {}". - * - * Bail out also if the body expression requires substitutions - * in order to insure correct behaviour [Bug 219166] + * Don't handle any guaranteed-error cases. */ - testTokenPtr = parsePtr->tokenPtr - + (parsePtr->tokenPtr->numComponents + 1); - bodyTokenPtr = testTokenPtr + (testTokenPtr->numComponents + 1); - if ((testTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) - || (bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD)) { - return TCL_OUT_LINE_COMPILE; + if (parsePtr->numWords < 2) { + return TCL_ERROR; } /* - * Find out if the condition is a constant. + * Check if the argument words are all compile-time-known literals; that's + * a case we can handle by compiling to a constant. */ - boolObj = Tcl_NewStringObj(testTokenPtr[1].start, testTokenPtr[1].size); - Tcl_IncrRefCount(boolObj); - code = Tcl_GetBooleanFromObj(NULL, boolObj, &boolVal); - Tcl_DecrRefCount(boolObj); - if (code == TCL_OK) { - if (boolVal) { - /* - * it is an infinite loop - */ - - loopMayEnd = 0; - } else { - /* - * This is an empty loop: "while 0 {...}" or such. - * Compile no bytecodes. - */ + formatObj = Tcl_NewObj(); + Tcl_IncrRefCount(formatObj); + tokenPtr = TokenAfter(tokenPtr); + if (!TclWordKnownAtCompileTime(tokenPtr, formatObj)) { + Tcl_DecrRefCount(formatObj); + return TCL_ERROR; + } - goto pushResult; + objv = ckalloc((parsePtr->numWords-2) * sizeof(Tcl_Obj *)); + for (i=0 ; i+2 < parsePtr->numWords ; i++) { + tokenPtr = TokenAfter(tokenPtr); + objv[i] = Tcl_NewObj(); + Tcl_IncrRefCount(objv[i]); + if (!TclWordKnownAtCompileTime(tokenPtr, objv[i])) { + goto checkForStringConcatCase; } } - /* - * Create a ExceptionRange record for the loop body. This is used to - * implement break and continue. + /* + * Everything is a literal, so the result is constant too (or an error if + * the format is broken). Do the format now. */ - envPtr->exceptDepth++; - envPtr->maxExceptDepth = - TclMax(envPtr->exceptDepth, envPtr->maxExceptDepth); - range = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr); + tmpObj = Tcl_Format(interp, Tcl_GetString(formatObj), + parsePtr->numWords-2, objv); + for (; --i>=0 ;) { + Tcl_DecrRefCount(objv[i]); + } + ckfree(objv); + Tcl_DecrRefCount(formatObj); + if (tmpObj == NULL) { + TclCompileSyntaxError(interp, envPtr); + return TCL_OK; + } /* - * Jump to the evaluation of the condition. This code uses the "loop - * rotation" optimisation (which eliminates one branch from the loop). - * "while cond body" produces then: - * goto A - * B: body : bodyCodeOffset - * A: cond -> result : testCodeOffset, continueOffset - * if (result) goto B - * - * The infinite loop "while 1 body" produces: - * B: body : all three offsets here - * goto B + * Not an error, always a constant result, so just push the result as a + * literal. Job done. */ - if (loopMayEnd) { - TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpEvalCondFixup); - testCodeOffset = 0; /* avoid compiler warning */ - } else { - testCodeOffset = (envPtr->codeNext - envPtr->codeStart); - } + bytes = Tcl_GetStringFromObj(tmpObj, &len); + PushLiteral(envPtr, bytes, len); + Tcl_DecrRefCount(tmpObj); + return TCL_OK; + checkForStringConcatCase: /* - * Compile the loop body. + * See if we can generate a sequence of things to concatenate. This + * requires that all the % sequences be %s or %%, as everything else is + * sufficiently complex that we don't bother. + * + * First, get the state of the system relatively sensible (cleaning up + * after our attempt to spot a literal). */ - bodyCodeOffset = (envPtr->codeNext - envPtr->codeStart); - TclCompileCmdWord(interp, bodyTokenPtr+1, - bodyTokenPtr->numComponents, envPtr); - envPtr->currStackDepth = savedStackDepth + 1; - envPtr->exceptArrayPtr[range].numCodeBytes = - (envPtr->codeNext - envPtr->codeStart) - bodyCodeOffset; - TclEmitOpcode(INST_POP, envPtr); + for (; i>=0 ; i--) { + Tcl_DecrRefCount(objv[i]); + } + ckfree(objv); + tokenPtr = TokenAfter(parsePtr->tokenPtr); + tokenPtr = TokenAfter(tokenPtr); + i = 0; /* - * Compile the test expression then emit the conditional jump that - * terminates the while. We already know it's a simple word. + * Now scan through and check for non-%s and non-%% substitutions. */ - if (loopMayEnd) { - testCodeOffset = (envPtr->codeNext - envPtr->codeStart); - jumpDist = testCodeOffset - jumpEvalCondFixup.codeOffset; - if (TclFixupForwardJump(envPtr, &jumpEvalCondFixup, jumpDist, 127)) { - bodyCodeOffset += 3; - testCodeOffset += 3; - } - envPtr->currStackDepth = savedStackDepth; - TclCompileExprWords(interp, testTokenPtr, 1, envPtr); - envPtr->currStackDepth = savedStackDepth + 1; - - jumpDist = (envPtr->codeNext - envPtr->codeStart) - bodyCodeOffset; - if (jumpDist > 127) { - TclEmitInstInt4(INST_JUMP_TRUE4, -jumpDist, envPtr); - } else { - TclEmitInstInt1(INST_JUMP_TRUE1, -jumpDist, envPtr); + for (bytes = Tcl_GetString(formatObj) ; *bytes ; bytes++) { + if (*bytes == '%') { + bytes++; + if (*bytes == 's') { + i++; + continue; + } else if (*bytes == '%') { + continue; + } + Tcl_DecrRefCount(formatObj); + return TCL_ERROR; } - } else { - jumpDist = (envPtr->codeNext - envPtr->codeStart) - bodyCodeOffset; - if (jumpDist > 127) { - TclEmitInstInt4(INST_JUMP4, -jumpDist, envPtr); - } else { - TclEmitInstInt1(INST_JUMP1, -jumpDist, envPtr); - } } + /* + * Check if the number of things to concatenate will fit in a byte. + */ + + if (i+2 != parsePtr->numWords || i > 125) { + Tcl_DecrRefCount(formatObj); + return TCL_ERROR; + } /* - * Set the loop's body, continue and break offsets. + * Generate the pushes of the things to concatenate, a sequence of + * literals and compiled tokens (of which at least one is non-literal or + * we'd have the case in the first half of this function) which we will + * concatenate. */ - envPtr->exceptArrayPtr[range].continueOffset = testCodeOffset; - envPtr->exceptArrayPtr[range].codeOffset = bodyCodeOffset; - envPtr->exceptArrayPtr[range].breakOffset = - (envPtr->codeNext - envPtr->codeStart); + i = 0; /* The count of things to concat. */ + j = 2; /* The index into the argument tokens, for + * TIP#280 handling. */ + start = Tcl_GetString(formatObj); + /* The start of the currently-scanned literal + * in the format string. */ + tmpObj = Tcl_NewObj(); /* The buffer used to accumulate the literal + * being built. */ + for (bytes = start ; *bytes ; bytes++) { + if (*bytes == '%') { + Tcl_AppendToObj(tmpObj, start, bytes - start); + if (*++bytes == '%') { + Tcl_AppendToObj(tmpObj, "%", 1); + } else { + char *b = Tcl_GetStringFromObj(tmpObj, &len); + + /* + * If there is a non-empty literal from the format string, + * push it and reset. + */ + + if (len > 0) { + PushLiteral(envPtr, b, len); + Tcl_DecrRefCount(tmpObj); + tmpObj = Tcl_NewObj(); + i++; + } + + /* + * Push the code to produce the string that would be + * substituted with %s, except we'll be concatenating + * directly. + */ + + CompileWord(envPtr, tokenPtr, interp, j); + tokenPtr = TokenAfter(tokenPtr); + j++; + i++; + } + start = bytes + 1; + } + } /* - * The while command's result is an empty string. + * Handle the case of a trailing literal. */ - pushResult: - envPtr->currStackDepth = savedStackDepth; - TclEmitPush(TclRegisterNewLiteral(envPtr, "", 0), envPtr); - envPtr->exceptDepth--; + Tcl_AppendToObj(tmpObj, start, bytes - start); + bytes = Tcl_GetStringFromObj(tmpObj, &len); + if (len > 0) { + PushLiteral(envPtr, bytes, len); + i++; + } + Tcl_DecrRefCount(tmpObj); + Tcl_DecrRefCount(formatObj); + + if (i > 1) { + /* + * Do the concatenation, which produces the result. + */ + + TclEmitInstInt1(INST_STR_CONCAT1, i, envPtr); + } else { + /* + * EVIL HACK! Force there to be a string representation in the case + * where there's just a "%s" in the format; case covered by the test + * format-20.1 (and it is horrible...) + */ + + TclEmitOpcode(INST_DUP, envPtr); + PushStringLiteral(envPtr, ""); + TclEmitOpcode(INST_STR_EQ, envPtr); + TclEmitOpcode(INST_POP, envPtr); + } return TCL_OK; } /* *---------------------------------------------------------------------- * - * PushVarName -- + * TclPushVarName -- * - * Procedure used in the compiling where pushing a variable name - * is necessary (append, lappend, set). + * Procedure used in the compiling where pushing a variable name is + * necessary (append, lappend, set). * * Results: - * Returns TCL_OK for a successful compile. - * Returns TCL_OUT_LINE_COMPILE to defer evaluation to runtime. + * The values written to *localIndexPtr and *isScalarPtr signal to + * the caller what the instructions emitted by this routine will do: + * + * *isScalarPtr (*localIndexPtr < 0) + * 1 1 Push the varname on the stack. (Stack +1) + * 1 0 *localIndexPtr is the index of the compiled + * local for this varname. No instructions + * emitted. (Stack +0) + * 0 1 Push part1 and part2 names of array element + * on the stack. (Stack +2) + * 0 0 *localIndexPtr is the index of the compiled + * local for this array. Element name is pushed + * on the stack. (Stack +1) * * Side effects: - * Instructions are added to envPtr to execute the "set" command - * at runtime. + * Instructions are added to envPtr. * *---------------------------------------------------------------------- */ -static int -PushVarName(interp, varTokenPtr, envPtr, flags, localIndexPtr, - simpleVarNamePtr, isScalarPtr) - Tcl_Interp *interp; /* Used for error reporting. */ - Tcl_Token *varTokenPtr; /* Points to a variable token. */ - CompileEnv *envPtr; /* Holds resulting instructions. */ - int flags; /* takes TCL_CREATE_VAR or - * TCL_NO_LARGE_INDEX */ - int *localIndexPtr; /* must not be NULL */ - int *simpleVarNamePtr; /* must not be NULL */ - int *isScalarPtr; /* must not be NULL */ +void +TclPushVarName( + Tcl_Interp *interp, /* Used for error reporting. */ + Tcl_Token *varTokenPtr, /* Points to a variable token. */ + CompileEnv *envPtr, /* Holds resulting instructions. */ + int flags, /* TCL_NO_LARGE_INDEX | TCL_NO_ELEMENT. */ + int *localIndexPtr, /* Must not be NULL. */ + int *isScalarPtr) /* Must not be NULL. */ { - register CONST char *p; - CONST char *name, *elName; + register const char *p; + const char *name, *elName; register int i, n; - int nameChars, elNameChars, simpleVarName, localIndex; - Tcl_Token *elemTokenPtr = NULL; - int elemTokenCount = 0; - int allocedTokens = 0; - int removedParen = 0; + int nameChars, elNameChars, simpleVarName, localIndex; + int elemTokenCount = 0, allocedTokens = 0, removedParen = 0; /* - * Decide if we can use a frame slot for the var/array name or if we - * need to emit code to compute and push the name at runtime. We use a - * frame slot (entry in the array of local vars) if we are compiling a - * procedure body and if the name is simple text that does not include - * namespace qualifiers. + * Decide if we can use a frame slot for the var/array name or if we need + * to emit code to compute and push the name at runtime. We use a frame + * slot (entry in the array of local vars) if we are compiling a procedure + * body and if the name is simple text that does not include namespace + * qualifiers. */ simpleVarName = 0; @@ -3355,8 +3174,8 @@ PushVarName(interp, varTokenPtr, envPtr, flags, localIndexPtr, /* * Check not only that the type is TCL_TOKEN_SIMPLE_WORD, but whether - * curly braces surround the variable name. - * This really matters for array elements to handle things like + * curly braces surround the variable name. This really matters for array + * elements to handle things like * set {x($foo)} 5 * which raises an undefined var error if we are not careful here. */ @@ -3367,12 +3186,13 @@ PushVarName(interp, varTokenPtr, envPtr, flags, localIndexPtr, * A simple variable name. Divide it up into "name" and "elName" * strings. If it is not a local variable, look it up at runtime. */ + simpleVarName = 1; name = varTokenPtr[1].start; nameChars = varTokenPtr[1].size; if (name[nameChars-1] == ')') { - /* + /* * last char is ')' => potential array reference. */ @@ -3387,11 +3207,11 @@ PushVarName(interp, varTokenPtr, envPtr, flags, localIndexPtr, if ((elName != NULL) && elNameChars) { /* - * An array element, the element name is a simple - * string: assemble the corresponding token. + * An array element, the element name is a simple string: + * assemble the corresponding token. */ - elemTokenPtr = (Tcl_Token *) ckalloc(sizeof(Tcl_Token)); + elemTokenPtr = TclStackAlloc(interp, sizeof(Tcl_Token)); allocedTokens = 1; elemTokenPtr->type = TCL_TOKEN_TEXT; elemTokenPtr->start = elName; @@ -3402,50 +3222,49 @@ PushVarName(interp, varTokenPtr, envPtr, flags, localIndexPtr, } } else if (((n = varTokenPtr->numComponents) > 1) && (varTokenPtr[1].type == TCL_TOKEN_TEXT) - && (varTokenPtr[n].type == TCL_TOKEN_TEXT) - && (varTokenPtr[n].start[varTokenPtr[n].size - 1] == ')')) { - - /* - * Check for parentheses inside first token + && (varTokenPtr[n].type == TCL_TOKEN_TEXT) + && (varTokenPtr[n].start[varTokenPtr[n].size - 1] == ')')) { + /* + * Check for parentheses inside first token. */ - simpleVarName = 0; - for (i = 0, p = varTokenPtr[1].start; - i < varTokenPtr[1].size; i++, p++) { - if (*p == '(') { - simpleVarName = 1; - break; - } - } - if (simpleVarName) { + simpleVarName = 0; + for (i = 0, p = varTokenPtr[1].start; + i < varTokenPtr[1].size; i++, p++) { + if (*p == '(') { + simpleVarName = 1; + break; + } + } + if (simpleVarName) { int remainingChars; /* - * Check the last token: if it is just ')', do not count - * it. Otherwise, remove the ')' and flag so that it is - * restored at the end. + * Check the last token: if it is just ')', do not count it. + * Otherwise, remove the ')' and flag so that it is restored at + * the end. */ if (varTokenPtr[n].size == 1) { - --n; + n--; } else { - --varTokenPtr[n].size; + varTokenPtr[n].size--; removedParen = n; } - name = varTokenPtr[1].start; - nameChars = p - varTokenPtr[1].start; - elName = p + 1; - remainingChars = (varTokenPtr[2].start - p) - 1; - elNameChars = (varTokenPtr[n].start - p) + varTokenPtr[n].size - 2; + name = varTokenPtr[1].start; + nameChars = p - varTokenPtr[1].start; + elName = p + 1; + remainingChars = (varTokenPtr[2].start - p) - 1; + elNameChars = (varTokenPtr[n].start-p) + varTokenPtr[n].size - 2; if (remainingChars) { /* - * Make a first token with the extra characters in the first + * Make a first token with the extra characters in the first * token. */ - elemTokenPtr = (Tcl_Token *) ckalloc(n * sizeof(Tcl_Token)); + elemTokenPtr = TclStackAlloc(interp, n * sizeof(Tcl_Token)); allocedTokens = 1; elemTokenPtr->type = TCL_TOKEN_TEXT; elemTokenPtr->start = elName; @@ -3457,15 +3276,15 @@ PushVarName(interp, varTokenPtr, envPtr, flags, localIndexPtr, * Copy the remaining tokens. */ - memcpy((void *) (elemTokenPtr+1), (void *) (&varTokenPtr[2]), - ((n-1) * sizeof(Tcl_Token))); + memcpy(elemTokenPtr+1, varTokenPtr+2, + (n-1) * sizeof(Tcl_Token)); } else { /* * Use the already available tokens. */ elemTokenPtr = &varTokenPtr[2]; - elemTokenCount = n - 1; + elemTokenCount = n - 1; } } } @@ -3476,6 +3295,7 @@ PushVarName(interp, varTokenPtr, envPtr, flags, localIndexPtr, */ int hasNsQualifiers = 0; + for (i = 0, p = name; i < nameChars; i++, p++) { if ((*p == ':') && ((i+1) < nameChars) && (*(p+1) == ':')) { hasNsQualifiers = 1; @@ -3484,34 +3304,36 @@ PushVarName(interp, varTokenPtr, envPtr, flags, localIndexPtr, } /* - * Look up the var name's index in the array of local vars in the - * proc frame. If retrieving the var's value and it doesn't already - * exist, push its name and look it up at runtime. + * Look up the var name's index in the array of local vars in the proc + * frame. If retrieving the var's value and it doesn't already exist, + * push its name and look it up at runtime. */ - if ((envPtr->procPtr != NULL) && !hasNsQualifiers) { - localIndex = TclFindCompiledLocal(name, nameChars, - /*create*/ (flags & TCL_CREATE_VAR), - /*flags*/ ((elName==NULL)? VAR_SCALAR : VAR_ARRAY), - envPtr->procPtr); + if (!hasNsQualifiers) { + localIndex = TclFindCompiledLocal(name, nameChars, 1, envPtr); if ((flags & TCL_NO_LARGE_INDEX) && (localIndex > 255)) { - /* we'll push the name */ + /* + * We'll push the name. + */ + localIndex = -1; } } if (localIndex < 0) { - TclEmitPush(TclRegisterNewLiteral(envPtr, name, nameChars), envPtr); + PushLiteral(envPtr, name, nameChars); } /* - * Compile the element script, if any. + * Compile the element script, if any, and only if not inhibited. [Bug + * 3600328] */ - if (elName != NULL) { + if (elName != NULL && !(flags & TCL_NO_ELEMENT)) { if (elNameChars) { - TclCompileTokens(interp, elemTokenPtr, elemTokenCount, envPtr); + TclCompileTokens(interp, elemTokenPtr, elemTokenCount, + envPtr); } else { - TclEmitPush(TclRegisterNewLiteral(envPtr, "", 0), envPtr); + PushStringLiteral(envPtr, ""); } } } else { @@ -3519,18 +3341,23 @@ PushVarName(interp, varTokenPtr, envPtr, flags, localIndexPtr, * The var name isn't simple: compile and push it. */ - TclCompileTokens(interp, varTokenPtr+1, - varTokenPtr->numComponents, envPtr); + CompileTokens(envPtr, varTokenPtr, interp); } if (removedParen) { - ++varTokenPtr[removedParen].size; + varTokenPtr[removedParen].size++; } if (allocedTokens) { - ckfree((char *) elemTokenPtr); + TclStackFree(interp, elemTokenPtr); } - *localIndexPtr = localIndex; - *simpleVarNamePtr = simpleVarName; - *isScalarPtr = (elName == NULL); - return TCL_OK; + *localIndexPtr = localIndex; + *isScalarPtr = (elName == NULL); } + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |