From ec6f24d1c6194c2ea9a6a128f03ec6ef8c5e3e3b Mon Sep 17 00:00:00 2001 From: dgp Date: Fri, 4 Sep 2009 17:33:11 +0000 Subject: * generic/tclCompCmds.c (TclCompileSubstCmd): Added a bytecode * generic/tclBasic.c: compiler routine for the [subst] command. * generic/tclCmdMZ.c: This is a partial solution to the need to * generic/tclCompile.c: NR-enable [subst] since bytecode execution is * generic/tclCompile.h: already NR-enabled. [Bug 2314561] Two new * generic/tclExecute.c: bytecode instructions, INST_NOP and * generic/tclInt.h: INST_RETURN_CODE_BRANCH were added to support * generic/tclParse.c: the new routine. INST_RETURN_CODE_BRANCH is * tests/basic.test: likely to be useful in any future effort to * tests/info.test: add a bytecode compiler routine for [try]. * tests/parse.test: --- ChangeLog | 14 +++ generic/tclBasic.c | 4 +- generic/tclCmdMZ.c | 50 ++++++---- generic/tclCompCmds.c | 271 +++++++++++++++++++++++++++++++++++++++++++++++++- generic/tclCompile.c | 172 +++++++++++++++++++------------- generic/tclCompile.h | 10 +- generic/tclExecute.c | 21 +++- generic/tclInt.h | 10 +- generic/tclParse.c | 61 +++++++----- tests/basic.test | 4 +- tests/info.test | 123 ++++++++++++++++++++++- tests/parse.test | 8 +- 12 files changed, 616 insertions(+), 132 deletions(-) diff --git a/ChangeLog b/ChangeLog index c913e45..eb36290 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,17 @@ +2009-09-04 Don Porter + + * generic/tclCompCmds.c (TclCompileSubstCmd): Added a bytecode + * generic/tclBasic.c: compiler routine for the [subst] command. + * generic/tclCmdMZ.c: This is a partial solution to the need to + * generic/tclCompile.c: NR-enable [subst] since bytecode execution is + * generic/tclCompile.h: already NR-enabled. [Bug 2314561] Two new + * generic/tclExecute.c: bytecode instructions, INST_NOP and + * generic/tclInt.h: INST_RETURN_CODE_BRANCH were added to support + * generic/tclParse.c: the new routine. INST_RETURN_CODE_BRANCH is + * tests/basic.test: likely to be useful in any future effort to + * tests/info.test: add a bytecode compiler routine for [try]. + * tests/parse.test: + 2009-09-03 Donal K. Fellows * doc/LinkVar.3: [Bug 2844962]: Added documentation of issues relating diff --git a/generic/tclBasic.c b/generic/tclBasic.c index d97194c..b5abbc2 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -16,7 +16,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclBasic.c,v 1.402 2009/08/25 21:03:25 andreas_kupries Exp $ + * RCS: @(#) $Id: tclBasic.c,v 1.403 2009/09/04 17:33:11 dgp Exp $ */ #include "tclInt.h" @@ -213,7 +213,7 @@ static const CmdInfo builtInCmds[] = { {"scan", Tcl_ScanObjCmd, NULL, NULL, 1}, {"set", Tcl_SetObjCmd, TclCompileSetCmd, NULL, 1}, {"split", Tcl_SplitObjCmd, NULL, NULL, 1}, - {"subst", Tcl_SubstObjCmd, NULL, NULL, 1}, + {"subst", Tcl_SubstObjCmd, TclCompileSubstCmd, NULL, 1}, {"switch", Tcl_SwitchObjCmd, TclCompileSwitchCmd, TclNRSwitchObjCmd, 1}, {"throw", Tcl_ThrowObjCmd, NULL, NULL, 1}, {"trace", Tcl_TraceObjCmd, NULL, NULL, 1}, diff --git a/generic/tclCmdMZ.c b/generic/tclCmdMZ.c index 2cce7be..a5a2f1b 100644 --- a/generic/tclCmdMZ.c +++ b/generic/tclCmdMZ.c @@ -15,7 +15,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclCmdMZ.c,v 1.191 2009/08/25 21:03:25 andreas_kupries Exp $ + * RCS: @(#) $Id: tclCmdMZ.c,v 1.192 2009/09/04 17:33:11 dgp Exp $ */ #include "tclInt.h" @@ -3373,30 +3373,24 @@ TclInitStringCmd( */ int -Tcl_SubstObjCmd( - ClientData dummy, /* Not used. */ - Tcl_Interp *interp, /* Current interpreter. */ - int objc, /* Number of arguments. */ - Tcl_Obj *const objv[]) /* Argument objects. */ +TclSubstOptions( + Tcl_Interp *interp, + int numOpts, + Tcl_Obj *const opts[], + int *flagPtr) { static const char *const substOptions[] = { "-nobackslashes", "-nocommands", "-novariables", NULL }; - enum substOptions { + enum { SUBST_NOBACKSLASHES, SUBST_NOCOMMANDS, SUBST_NOVARS }; - Tcl_Obj *resultPtr; - int flags, i; + int i, flags = TCL_SUBST_ALL; - /* - * Parse command-line options. - */ - - flags = TCL_SUBST_ALL; - for (i = 1; i < (objc-1); i++) { + for (i = 0; i < numOpts; i++) { int optionIndex; - if (Tcl_GetIndexFromObj(interp, objv[i], substOptions, "switch", 0, + if (Tcl_GetIndexFromObj(interp, opts[i], substOptions, "switch", 0, &optionIndex) != TCL_OK) { return TCL_ERROR; } @@ -3414,17 +3408,31 @@ Tcl_SubstObjCmd( Tcl_Panic("Tcl_SubstObjCmd: bad option index to SubstOptions"); } } - if (i != objc-1) { + *flagPtr = flags; + return TCL_OK; +} + +int +Tcl_SubstObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + Tcl_Obj *resultPtr; + int flags; + + if (objc < 2) { Tcl_WrongNumArgs(interp, 1, objv, "?-nobackslashes? ?-nocommands? ?-novariables? string"); return TCL_ERROR; } - /* - * Perform the substitution. - */ + if (TclSubstOptions(interp, objc-2, objv+1, &flags) != TCL_OK) { + return TCL_ERROR; + } - resultPtr = Tcl_SubstObj(interp, objv[i], flags); + resultPtr = Tcl_SubstObj(interp, objv[objc-1], flags); if (resultPtr == NULL) { return TCL_ERROR; diff --git a/generic/tclCompCmds.c b/generic/tclCompCmds.c index 5b5871f..ffcd22a 100644 --- a/generic/tclCompCmds.c +++ b/generic/tclCompCmds.c @@ -12,7 +12,7 @@ * 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.153 2009/08/25 21:03:25 andreas_kupries Exp $ + * RCS: @(#) $Id: tclCompCmds.c,v 1.154 2009/09/04 17:33:11 dgp Exp $ */ #include "tclInt.h" @@ -3844,6 +3844,275 @@ TclCompileStringLenCmd( /* *---------------------------------------------------------------------- * + * TclCompileSubstCmd -- + * + * Procedure called to compile the "subst" command. + * + * Results: + * Returns TCL_OK for successful compile, or TCL_ERROR to defer + * evaluation to runtime (either when it is too complex to get the + * semantics right, or when we know for sure that it is an error but need + * the error to happen at the right time). + * + * Side effects: + * Instructions are added to envPtr to execute the "subst" command at + * runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileSubstCmd( + 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 numArgs = parsePtr->numWords - 1; + int numOpts = numArgs - 1; + int objc, flags = TCL_SUBST_ALL; + Tcl_Obj **objv/*, *toSubst = NULL*/; + Tcl_Parse parse; + Tcl_InterpState state = NULL; + Tcl_Token *wordTokenPtr = TokenAfter(parsePtr->tokenPtr); + int breakOffset = 0, count = 0, code = TCL_OK; + Tcl_Token *endTokenPtr, *tokenPtr; + DefineLineInformation; /* TIP #280 */ + int bline = mapPtr->loc[eclIndex].line[numArgs]; + SetLineInformation(numArgs); + + if (numArgs == 0) { + return TCL_ERROR; + } + + objv = (Tcl_Obj **) TclStackAlloc(interp, /*numArgs*/ numOpts * sizeof(Tcl_Obj *)); + + for (objc = 0; objc < /*numArgs*/ numOpts; objc++) { + objv[objc] = Tcl_NewObj(); + Tcl_IncrRefCount(objv[objc]); + if (!TclWordKnownAtCompileTime(wordTokenPtr, objv[objc])) { + objc++; + goto cleanup; + } + wordTokenPtr = TokenAfter(wordTokenPtr); + } + +/* + if (TclSubstOptions(NULL, numOpts, objv, &flags) == TCL_OK) { + toSubst = objv[numOpts]; + Tcl_IncrRefCount(toSubst); + } +*/ + + /* TODO: Figure out expansion to cover WordKnownAtCompileTime + * The difficulty is that WKACT makes a copy, and if TclSubstParse + * below parses the copy of the original source string, some deep + * parts of the compile machinery get upset. They want all pointers + * stored in Tcl_Tokens to point back to the same original string. + */ + if (wordTokenPtr->type != TCL_TOKEN_SIMPLE_WORD) { + code = TCL_ERROR; + } + if (code == TCL_OK) { + code = TclSubstOptions(NULL, numOpts, objv, &flags); + } + + cleanup: + while (--objc >= 0) { + TclDecrRefCount(objv[objc]); + } + TclStackFree(interp, objv); + if (/*toSubst == NULL*/ code != TCL_OK) { + return TCL_ERROR; + } + + TclSubstParse(interp, /*toSubst,*/ wordTokenPtr[1].start, + wordTokenPtr[1].size, flags, &parse, &state); + + for (tokenPtr = parse.tokenPtr, endTokenPtr = tokenPtr + parse.numTokens; + tokenPtr < endTokenPtr; tokenPtr = TokenAfter(tokenPtr)) { + int length, literal, catchRange, breakJump; + char buf[TCL_UTF_MAX]; + JumpFixup startFixup, okFixup, returnFixup, breakFixup; + JumpFixup continueFixup, otherFixup, endFixup; + + switch (tokenPtr->type) { + case TCL_TOKEN_TEXT: + literal = TclRegisterNewLiteral(envPtr, + tokenPtr->start, tokenPtr->size); + TclEmitPush(literal, envPtr); + TclAdvanceLines(&bline, tokenPtr->start, + tokenPtr->start + tokenPtr->size); + count++; + continue; + case TCL_TOKEN_BS: + length = Tcl_UtfBackslash(tokenPtr->start, NULL, buf); + literal = TclRegisterNewLiteral(envPtr, buf, length); + TclEmitPush(literal, envPtr); + count++; + continue; + } + + while (count > 255) { + TclEmitInstInt1(INST_CONCAT1, 255, envPtr); + count -= 254; + } + if (count > 1) { + TclEmitInstInt1(INST_CONCAT1, count, envPtr); + count = 1; + } + + if (breakOffset == 0) { + /* Jump to the start (jump over the jump to end) */ + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &startFixup); + + /* Jump to the end (all BREAKs land here) */ + breakOffset = CurrentOffset(envPtr); + TclEmitInstInt4(INST_JUMP4, 0, envPtr); + + /* Start */ + if (TclFixupForwardJumpToHere(envPtr, &startFixup, 127)) { + Tcl_Panic("TclCompileSubstCmd: bad start jump distance %d", + CurrentOffset(envPtr) - startFixup.codeOffset); + } + } + + envPtr->line = bline; + catchRange = DeclareExceptionRange(envPtr, CATCH_EXCEPTION_RANGE); + TclEmitInstInt4(INST_BEGIN_CATCH4, catchRange, envPtr); + ExceptionRangeStarts(envPtr, catchRange); + + switch (tokenPtr->type) { + case TCL_TOKEN_COMMAND: + TclCompileScript(interp, tokenPtr->start+1, tokenPtr->size-2, + envPtr); + count++; + break; + case TCL_TOKEN_VARIABLE: + TclCompileVarSubst(interp, tokenPtr, envPtr); + count++; + break; + default: + Tcl_Panic("unexpected token type in TclCompileSubstCmd: %d", + tokenPtr->type); + } + + ExceptionRangeEnds(envPtr, catchRange); + + /* Substitution produced TCL_OK */ + TclEmitOpcode(INST_END_CATCH, envPtr); + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &okFixup); + + /* Exceptional return codes processed here */ + ExceptionRangeTarget(envPtr, catchRange, catchOffset); + TclEmitOpcode(INST_PUSH_RETURN_OPTIONS, envPtr); + TclEmitOpcode(INST_PUSH_RESULT, envPtr); + TclEmitOpcode(INST_PUSH_RETURN_CODE, envPtr); + TclEmitOpcode(INST_END_CATCH, envPtr); + TclEmitOpcode(INST_RETURN_CODE_BRANCH, envPtr); + + /* ERROR -> reraise it */ + TclEmitOpcode(INST_RETURN_STK, envPtr); + TclEmitOpcode(INST_NOP, envPtr); + + /* RETURN */ + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &returnFixup); + + /* BREAK */ + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &breakFixup); + + /* CONTINUE */ + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &continueFixup); + + /* OTHER */ + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &otherFixup); + + /* BREAK destination */ + if (TclFixupForwardJumpToHere(envPtr, &breakFixup, 127)) { + Tcl_Panic("TclCompileSubstCmd: bad break jump distance %d", + CurrentOffset(envPtr) - breakFixup.codeOffset); + } + TclEmitOpcode(INST_POP, envPtr); + TclEmitOpcode(INST_POP, envPtr); + + breakJump = CurrentOffset(envPtr) - breakOffset; + if (breakJump > 127) { + TclEmitInstInt4(INST_JUMP4, -breakJump, envPtr) + } else { + TclEmitInstInt1(INST_JUMP1, -breakJump, envPtr) + } + + /* CONTINUE destination */ + if (TclFixupForwardJumpToHere(envPtr, &continueFixup, 127)) { + Tcl_Panic("TclCompileSubstCmd: bad continue jump distance %d", + CurrentOffset(envPtr) - continueFixup.codeOffset); + } + TclEmitOpcode(INST_POP, envPtr); + TclEmitOpcode(INST_POP, envPtr); + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &endFixup); + + /* RETURN + other destination */ + if (TclFixupForwardJumpToHere(envPtr, &returnFixup, 127)) { + Tcl_Panic("TclCompileSubstCmd: bad return jump distance %d", + CurrentOffset(envPtr) - returnFixup.codeOffset); + } + if (TclFixupForwardJumpToHere(envPtr, &otherFixup, 127)) { + Tcl_Panic("TclCompileSubstCmd: bad other jump distance %d", + CurrentOffset(envPtr) - otherFixup.codeOffset); + } + /* Pull the result to top of stack, discard options dict */ + TclEmitInstInt4(INST_REVERSE, 2, envPtr); + TclEmitOpcode(INST_POP, envPtr); + + /* OK destination */ + if (TclFixupForwardJumpToHere(envPtr, &okFixup, 127)) { + Tcl_Panic("TclCompileSubstCmd: bad ok jump distance %d", + CurrentOffset(envPtr) - okFixup.codeOffset); + } + if (count > 1) { + TclEmitInstInt1(INST_CONCAT1, count, envPtr); + count = 1; + } + + /* CONTINUE jump to here */ + if (TclFixupForwardJumpToHere(envPtr, &endFixup, 127)) { + Tcl_Panic("TclCompileSubstCmd: bad end jump distance %d", + CurrentOffset(envPtr) - endFixup.codeOffset); + } + bline = envPtr->line; + } + + + while (count > 255) { + TclEmitInstInt1(INST_CONCAT1, 255, envPtr); + count -= 254; + } + if (count > 1) { + TclEmitInstInt1(INST_CONCAT1, count, envPtr); + } + + Tcl_FreeParse(&parse); +/* TclDecrRefCount(toSubst);*/ + + if (state != NULL) { + Tcl_RestoreInterpState(interp, state); + TclCompileSyntaxError(interp, envPtr); + } + + /* Final target of the multi-jump from all BREAKs */ + if (breakOffset > 0) { + TclUpdateInstInt4AtPc(INST_JUMP4, CurrentOffset(envPtr) - breakOffset, + envPtr->codeStart + breakOffset); + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * TclCompileSwitchCmd -- * * Procedure called to compile the "switch" command. diff --git a/generic/tclCompile.c b/generic/tclCompile.c index 970c1ef..b6b270b 100644 --- a/generic/tclCompile.c +++ b/generic/tclCompile.c @@ -11,7 +11,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclCompile.c,v 1.172 2009/08/25 23:20:36 das Exp $ + * RCS: @(#) $Id: tclCompile.c,v 1.173 2009/09/04 17:33:11 dgp Exp $ */ #include "tclInt.h" @@ -399,6 +399,13 @@ InstructionDesc const tclInstructionTable[] = { * stknext */ {"existStk", 1, 0, 0, {OPERAND_NONE}}, /* Test if general variable exists; unparsed variable name is stktop*/ + {"nop", 1, 0, 0, {OPERAND_NONE}}, + /* Do nothing */ + {"returnCodeBranch", 1, -1, 0, {OPERAND_NONE}}, + /* Jump to next instruction based on the return code on top of stack + * ERROR: +1; RETURN: +3; BREAK: +5; CONTINUE: +7; + * Other non-OK: +9 + */ {0} }; @@ -1277,6 +1284,17 @@ TclCompileScript( TclCompileSyntaxError(interp, envPtr); break; } + + /* + * TIP #280: We have to count newlines before the command even + * in the degenerate case when the command has no words. (See + * test info-30.33). So make that counting here, and not in + * the (numWords > 0) branch below. + */ + TclAdvanceLines(&cmdLine, p, parsePtr->commandStart); + TclAdvanceContinuations(&cmdLine, &clNext, + parsePtr->commandStart - envPtr->source); + if (parsePtr->numWords > 0) { int expand = 0; /* Set if there are dynamic expansions to * handle */ @@ -1361,9 +1379,6 @@ TclCompileScript( * 'wlines'. */ - TclAdvanceLines(&cmdLine, p, parsePtr->commandStart); - TclAdvanceContinuations (&cmdLine, &clNext, - parsePtr->commandStart - envPtr->source); EnterCmdWordData(eclPtr, parsePtr->commandStart - envPtr->source, parsePtr->tokenPtr, parsePtr->commandStart, parsePtr->commandSize, parsePtr->numWords, cmdLine, @@ -1633,6 +1648,14 @@ TclCompileScript( } while (bytesLeft > 0); /* + * TIP #280: Bring the line counts in the CompEnv up to date. + * See tests info-30.33,34,35 . + */ + + envPtr->line = cmdLine; + envPtr->clNext = clNext; + + /* * If the source script yielded no instructions (e.g., if it was empty), * push an empty string as the command's result. * @@ -1674,6 +1697,77 @@ TclCompileScript( */ void +TclCompileVarSubst( + Tcl_Interp *interp, + Tcl_Token *tokenPtr, + CompileEnv *envPtr) +{ + const char *p, *name = tokenPtr[1].start; + int nameBytes = tokenPtr[1].size; + int i, localVar, localVarName = 1; + + /* + * Determine how the variable name should be handled: if it + * contains any namespace qualifiers it is not a local variable + * (localVarName=-1); if it looks like an array element and the + * token has a single component, it should not be created here + * [Bug 569438] (localVarName=0); otherwise, the local variable + * can safely be created (localVarName=1). + */ + + for (i = 0, p = name; i < nameBytes; i++, p++) { + if ((*p == ':') && (i < nameBytes-1) && (*(p+1) == ':')) { + localVarName = -1; + break; + } else if ((*p == '(') + && (tokenPtr->numComponents == 1) + && (*(name + nameBytes - 1) == ')')) { + localVarName = 0; + break; + } + } + + /* + * Either push the variable's name, or find its index in the array + * of local variables in a procedure frame. + */ + + localVar = -1; + if (localVarName != -1) { + localVar = TclFindCompiledLocal(name, nameBytes, localVarName, envPtr); + } + if (localVar < 0) { + TclEmitPush(TclRegisterNewLiteral(envPtr, name, nameBytes), envPtr); + } + + /* + * Emit instructions to load the variable. + */ + + TclAdvanceLines(&(envPtr->line), tokenPtr[1].start, + tokenPtr[1].start + tokenPtr[1].size); + + if (tokenPtr->numComponents == 1) { + if (localVar < 0) { + TclEmitOpcode(INST_LOAD_SCALAR_STK, envPtr); + } else if (localVar <= 255) { + TclEmitInstInt1(INST_LOAD_SCALAR1, localVar, envPtr); + } else { + TclEmitInstInt4(INST_LOAD_SCALAR4, localVar, envPtr); + } + } else { + TclCompileTokens(interp, tokenPtr+2, tokenPtr->numComponents-1, envPtr); + if (localVar < 0) { + TclEmitOpcode(INST_LOAD_ARRAY_STK, envPtr); + } else if (localVar <= 255) { + TclEmitInstInt1(INST_LOAD_ARRAY1, localVar, envPtr); + } else { + TclEmitInstInt4(INST_LOAD_ARRAY4, localVar, envPtr); + } + } +} + +void TclCompileTokens( Tcl_Interp *interp, /* Used for error and status reporting. */ Tcl_Token *tokenPtr, /* Pointer to first in an array of tokens to @@ -1685,9 +1779,7 @@ TclCompileTokens( Tcl_DString textBuffer; /* Holds concatenated chars from adjacent * TCL_TOKEN_TEXT, TCL_TOKEN_BS tokens. */ char buffer[TCL_UTF_MAX]; - const char *name, *p; - int numObjsToConcat, nameBytes, localVarName, localVar; - int length, i; + int i, numObjsToConcat, length; unsigned char *entryCodeNext = envPtr->codeNext; #define NUM_STATIC_POS 20 int isLiteral, maxNumCL, numCL; @@ -1731,6 +1823,8 @@ TclCompileTokens( switch (tokenPtr->type) { case TCL_TOKEN_TEXT: Tcl_DStringAppend(&textBuffer, tokenPtr->start, tokenPtr->size); + TclAdvanceLines(&(envPtr->line), tokenPtr->start, + tokenPtr->start + tokenPtr->size); break; case TCL_TOKEN_BS: @@ -1810,69 +1904,7 @@ TclCompileTokens( Tcl_DStringFree(&textBuffer); } - /* - * Determine how the variable name should be handled: if it - * contains any namespace qualifiers it is not a local variable - * (localVarName=-1); if it looks like an array element and the - * token has a single component, it should not be created here - * [Bug 569438] (localVarName=0); otherwise, the local variable - * can safely be created (localVarName=1). - */ - - name = tokenPtr[1].start; - nameBytes = tokenPtr[1].size; - - localVarName = 1; - for (i = 0, p = name; i < nameBytes; i++, p++) { - if ((*p == ':') && (i < nameBytes-1) && (*(p+1) == ':')) { - localVarName = -1; - break; - } else if ((*p == '(') - && (tokenPtr->numComponents == 1) - && (*(name + nameBytes - 1) == ')')) { - localVarName = 0; - break; - } - } - - /* - * Either push the variable's name, or find its index in the array - * of local variables in a procedure frame. - */ - - localVar = -1; - if (localVarName != -1) { - localVar = TclFindCompiledLocal(name, nameBytes, localVarName, - envPtr); - } - if (localVar < 0) { - TclEmitPush(TclRegisterNewLiteral(envPtr, name, nameBytes), - envPtr); - } - - /* - * Emit instructions to load the variable. - */ - - if (tokenPtr->numComponents == 1) { - if (localVar < 0) { - TclEmitOpcode(INST_LOAD_SCALAR_STK, envPtr); - } else if (localVar <= 255) { - TclEmitInstInt1(INST_LOAD_SCALAR1, localVar, envPtr); - } else { - TclEmitInstInt4(INST_LOAD_SCALAR4, localVar, envPtr); - } - } else { - TclCompileTokens(interp, tokenPtr+2, - tokenPtr->numComponents-1, envPtr); - if (localVar < 0) { - TclEmitOpcode(INST_LOAD_ARRAY_STK, envPtr); - } else if (localVar <= 255) { - TclEmitInstInt1(INST_LOAD_ARRAY1, localVar, envPtr); - } else { - TclEmitInstInt4(INST_LOAD_ARRAY4, localVar, envPtr); - } - } + TclCompileVarSubst(interp, tokenPtr, envPtr); numObjsToConcat++; count -= tokenPtr->numComponents; tokenPtr += tokenPtr->numComponents; diff --git a/generic/tclCompile.h b/generic/tclCompile.h index 4d9dbd1..25dec86 100644 --- a/generic/tclCompile.h +++ b/generic/tclCompile.h @@ -9,7 +9,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclCompile.h,v 1.118 2009/08/25 21:03:25 andreas_kupries Exp $ + * RCS: @(#) $Id: tclCompile.h,v 1.119 2009/09/04 17:33:11 dgp Exp $ */ #ifndef _TCLCOMPILATION @@ -666,8 +666,12 @@ typedef struct ByteCode { #define INST_EXIST_ARRAY_STK 130 #define INST_EXIST_STK 131 +/* For [subst] compilation */ +#define INST_NOP 132 +#define INST_RETURN_CODE_BRANCH 133 + /* The last opcode */ -#define LAST_INST_OPCODE 131 +#define LAST_INST_OPCODE 133 /* * Table describing the Tcl bytecode instructions: their name (for displaying @@ -893,6 +897,8 @@ MODULE_SCOPE void TclCompileSyntaxError(Tcl_Interp *interp, MODULE_SCOPE void TclCompileTokens(Tcl_Interp *interp, Tcl_Token *tokenPtr, int count, CompileEnv *envPtr); +MODULE_SCOPE void TclCompileVarSubst(Tcl_Interp *interp, + Tcl_Token *tokenPtr, CompileEnv *envPtr); MODULE_SCOPE int TclCreateAuxData(ClientData clientData, const AuxDataType *typePtr, CompileEnv *envPtr); MODULE_SCOPE int TclCreateExceptRange(ExceptionRangeType type, diff --git a/generic/tclExecute.c b/generic/tclExecute.c index c668539..662d2a0 100644 --- a/generic/tclExecute.c +++ b/generic/tclExecute.c @@ -14,7 +14,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclExecute.c,v 1.444 2009/08/12 16:06:43 dgp Exp $ + * RCS: @(#) $Id: tclExecute.c,v 1.445 2009/09/04 17:33:11 dgp Exp $ */ #include "tclInt.h" @@ -2493,6 +2493,10 @@ TclExecuteByteCode( NEXT_INST_F(opnd, 0, -1); } + case INST_NOP: + pc += 1; + goto cleanup0; + case INST_DUP: objResultPtr = OBJ_AT_TOS; TRACE_WITH_OBJ(("=> "), objResultPtr); @@ -7163,6 +7167,21 @@ TclExecuteByteCode( TRACE_WITH_OBJ(("=> "), objResultPtr); NEXT_INST_F(1, 0, 1); + case INST_RETURN_CODE_BRANCH: { + int code; + + if (TclGetIntFromObj(NULL, OBJ_AT_TOS, &code) != TCL_OK) { + Tcl_Panic("INST_RETURN_CODE_BRANCH: TOS not a return code!"); + } + if (code == TCL_OK) { + Tcl_Panic("INST_RETURN_CODE_BRANCH: TOS is TCL_OK!"); + } + if (code < TCL_ERROR || code > TCL_CONTINUE) { + code = TCL_CONTINUE + 1; + } + NEXT_INST_F(2*code -1, 1, 0); + } + /* TODO: normalize "valPtr" to "valuePtr" */ { int opnd, opnd2, allocateDict; diff --git a/generic/tclInt.h b/generic/tclInt.h index dd073d2..97cb891 100644 --- a/generic/tclInt.h +++ b/generic/tclInt.h @@ -15,7 +15,7 @@ * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclInt.h,v 1.439 2009/09/04 09:38:20 dkf Exp $ + * RCS: @(#) $Id: tclInt.h,v 1.440 2009/09/04 17:33:12 dgp Exp $ */ #ifndef _TCLINT @@ -2950,6 +2950,11 @@ MODULE_SCOPE int TclStringMatch(const char *str, int strLen, MODULE_SCOPE int TclStringMatchObj(Tcl_Obj *stringObj, Tcl_Obj *patternObj, int flags); MODULE_SCOPE Tcl_Obj * TclStringObjReverse(Tcl_Obj *objPtr); +MODULE_SCOPE int TclSubstOptions(Tcl_Interp *interp, int numOpts, + Tcl_Obj *const opts[], int *flagPtr); +MODULE_SCOPE void TclSubstParse(Tcl_Interp *interp, const char *bytes, + int numBytes, int flags, Tcl_Parse *parsePtr, + Tcl_InterpState *statePtr); MODULE_SCOPE int TclSubstTokens(Tcl_Interp *interp, Tcl_Token *tokenPtr, int count, int *tokensLeftPtr, int line, int *clNextOuter, const char *outerScript); @@ -3370,6 +3375,9 @@ MODULE_SCOPE int TclCompileStringLenCmd(Tcl_Interp *interp, MODULE_SCOPE int TclCompileStringMatchCmd(Tcl_Interp *interp, Tcl_Parse *parsePtr, Command *cmdPtr, struct CompileEnv *envPtr); +MODULE_SCOPE int TclCompileSubstCmd(Tcl_Interp *interp, + Tcl_Parse *parsePtr, Command *cmdPtr, + struct CompileEnv *envPtr); MODULE_SCOPE int TclCompileSwitchCmd(Tcl_Interp *interp, Tcl_Parse *parsePtr, Command *cmdPtr, struct CompileEnv *envPtr); diff --git a/generic/tclParse.c b/generic/tclParse.c index aca6048..efb4422 100644 --- a/generic/tclParse.c +++ b/generic/tclParse.c @@ -1880,18 +1880,17 @@ Tcl_ParseQuotedString( *---------------------------------------------------------------------- */ -Tcl_Obj * -Tcl_SubstObj( - Tcl_Interp *interp, /* Interpreter in which substitution occurs */ - Tcl_Obj *objPtr, /* The value to be substituted. */ - int flags) /* What substitutions to do. */ +void +TclSubstParse( + Tcl_Interp *interp, + const char *bytes, + int numBytes, + int flags, + Tcl_Parse *parsePtr, + Tcl_InterpState *statePtr) { - int length, tokensLeft, code; - Tcl_Token *endTokenPtr; - Tcl_Obj *result, *errMsg = NULL; - const char *p = TclGetStringFromObj(objPtr, &length); - Tcl_Parse *parsePtr = (Tcl_Parse *) - TclStackAlloc(interp, sizeof(Tcl_Parse)); + int length = numBytes; + const char *p = bytes; TclParseInit(interp, p, length, parsePtr); @@ -1903,12 +1902,11 @@ Tcl_SubstObj( if (TCL_OK != ParseTokens(p, length, /* mask */ 0, flags, parsePtr)) { /* - * There was a parse error. Save the error message for possible - * reporting later. + * There was a parse error. Save the interpreter state for possible + * error reporting later. */ - errMsg = Tcl_GetObjResult(interp); - Tcl_IncrRefCount(errMsg); + *statePtr = Tcl_SaveInterpState(interp, TCL_ERROR); /* * We need to re-parse to get the portion of the string we can [subst] @@ -2054,6 +2052,23 @@ Tcl_SubstObj( Tcl_Panic("bad parse in Tcl_SubstObj: %c", p[length]); } } +} + +Tcl_Obj * +Tcl_SubstObj( + Tcl_Interp *interp, /* Interpreter in which substitution occurs */ + Tcl_Obj *objPtr, /* The value to be substituted. */ + int flags) /* What substitutions to do. */ +{ + int tokensLeft, code, numBytes; + Tcl_Token *endTokenPtr; + Tcl_Obj *result; + Tcl_Parse *parsePtr = (Tcl_Parse *) + TclStackAlloc(interp, sizeof(Tcl_Parse)); + Tcl_InterpState state = NULL; + const char *bytes = TclGetStringFromObj(objPtr, &numBytes); + + TclSubstParse(interp, bytes, numBytes, flags, parsePtr, &state); /* * Next, substitute the parsed tokens just as in normal Tcl evaluation. @@ -2066,9 +2081,8 @@ Tcl_SubstObj( if (code == TCL_OK) { Tcl_FreeParse(parsePtr); TclStackFree(interp, parsePtr); - if (errMsg != NULL) { - Tcl_SetObjResult(interp, errMsg); - Tcl_DecrRefCount(errMsg); + if (state != NULL) { + Tcl_RestoreInterpState(interp, state); return NULL; } return Tcl_GetObjResult(interp); @@ -2081,8 +2095,8 @@ Tcl_SubstObj( Tcl_FreeParse(parsePtr); TclStackFree(interp, parsePtr); Tcl_DecrRefCount(result); - if (errMsg != NULL) { - Tcl_DecrRefCount(errMsg); + if (state != NULL) { + Tcl_DiscardInterpState(state); } return NULL; case TCL_BREAK: @@ -2094,14 +2108,13 @@ Tcl_SubstObj( if (tokensLeft == 0) { Tcl_FreeParse(parsePtr); TclStackFree(interp, parsePtr); - if (errMsg != NULL) { + if (state != NULL) { if (code != TCL_BREAK) { Tcl_DecrRefCount(result); - Tcl_SetObjResult(interp, errMsg); - Tcl_DecrRefCount(errMsg); + Tcl_RestoreInterpState(interp, state); return NULL; } - Tcl_DecrRefCount(errMsg); + Tcl_DiscardInterpState(state); } return result; } diff --git a/tests/basic.test b/tests/basic.test index b8d608e..881b329 100644 --- a/tests/basic.test +++ b/tests/basic.test @@ -15,7 +15,7 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # -# RCS: @(#) $Id: basic.test,v 1.44 2007/04/20 05:51:11 kennykb Exp $ +# RCS: @(#) $Id: basic.test,v 1.45 2009/09/04 17:33:12 dgp Exp $ # package require tcltest 2 @@ -632,7 +632,7 @@ test basic-46.5 {Tcl_AllowExceptions: exception return not allowed} -setup { (file "*BREAKtest" line 2)} test basic-47.1 {Tcl_EvalEx: check for missing close-bracket} -body { - subst {a[set b [format cd]} + set subst subst; $subst {a[set b [format cd]} } -returnCodes error -result {missing close-bracket} # Some lists for expansion tests to work with diff --git a/tests/info.test b/tests/info.test index 65d71bc..e538a23 100644 --- a/tests/info.test +++ b/tests/info.test @@ -13,7 +13,7 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # -# RCS: @(#) $Id: info.test,v 1.65 2009/08/25 21:03:25 andreas_kupries Exp $ +# RCS: @(#) $Id: info.test,v 1.66 2009/09/04 17:33:12 dgp Exp $ if {[lsearch [namespace children] ::tcltest] == -1} { package require tcltest 2 @@ -1525,12 +1525,12 @@ test info-30.10 {bs+nl in computed word, key to array} { set res } { type source line 1523 file info.test cmd {info frame 0} proc ::tcltest::RunTest} -test info-30.11 {bs+nl in subst arguments, no true counting} { +test info-30.11 {bs+nl in subst arguments} { subst {[set \ res "\ [reduce \ - [info frame 0]]"]} -} { type eval line 1 cmd {info frame 0} proc ::tcltest::RunTest} + [info frame 0]]"]} ; #1532 +} { type source line 1532 file info.test cmd {info frame 0} proc ::tcltest::RunTest} test info-30.12 {bs+nl in computed word, nested eval} { eval { @@ -1708,6 +1708,121 @@ test info-30.24 {bs+nl in single-body switch, full compiled} { type source line 1696 file info.test cmd {info frame 0} proc ::a level 0 type source line 1700 file info.test cmd {info frame 0} proc ::a level 0} +test info-30.25 {TIP 280 for compiled [subst]} { + subst {[reduce [info frame 0]]} ; # 1712 +} {type source line 1712 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.26 {TIP 280 for compiled [subst]} { + subst \ + {[reduce [info frame 0]]} ; # 1716 +} {type source line 1716 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.27 {TIP 280 for compiled [subst]} { + subst { +[reduce [info frame 0]]} ; # 1720 +} { +type source line 1720 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.28 {TIP 280 for compiled [subst]} { + subst {\ +[reduce [info frame 0]]} ; # 1725 +} { type source line 1725 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.29 {TIP 280 for compiled [subst]} { + subst {foo\ +[reduce [info frame 0]]} ; # 1729 +} {foo type source line 1729 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.30 {TIP 280 for compiled [subst]} { + subst {foo +[reduce [info frame 0]]} ; # 1733 +} {foo +type source line 1733 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.31 {TIP 280 for compiled [subst]} { + subst {[][reduce [info frame 0]]} ; # 1737 +} {type source line 1737 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.32 {TIP 280 for compiled [subst]} { + subst {[\ +][reduce [info frame 0]]} ; # 1741 +} {type source line 1741 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.33 {TIP 280 for compiled [subst]} { + subst {[ +][reduce [info frame 0]]} ; # 1745 +} {type source line 1745 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.34 {TIP 280 for compiled [subst]} { + subst {[format %s {} +][reduce [info frame 0]]} ; # 1749 +} {type source line 1749 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.35 {TIP 280 for compiled [subst]} { + subst {[format %s {} +] +[reduce [info frame 0]]} ; # 1754 +} { +type source line 1754 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.36 {TIP 280 for compiled [subst]} { + subst { +[format %s {}][reduce [info frame 0]]} ; # 1759 +} { +type source line 1759 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.37 {TIP 280 for compiled [subst]} { + subst { +[format %s {}] +[reduce [info frame 0]]} ; # 1765 +} { + +type source line 1765 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.38 {TIP 280 for compiled [subst]} { + subst {\ +[format %s {}][reduce [info frame 0]]} ; # 1771 +} { type source line 1771 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.39 {TIP 280 for compiled [subst]} { + subst {\ +[format %s {}]\ +[reduce [info frame 0]]} ; # 1776 +} { type source line 1776 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.40 {TIP 280 for compiled [subst]} { + unset -nocomplain empty + set empty {} + subst {$empty[reduce [info frame 0]]} ; # 1781 +} {type source line 1781 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.41 {TIP 280 for compiled [subst]} { + unset -nocomplain empty + set empty {} + subst {$empty +[reduce [info frame 0]]} ; # 1787 +} { +type source line 1787 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.42 {TIP 280 for compiled [subst]} { + unset -nocomplain empty + set empty {} + subst {$empty\ +[reduce [info frame 0]]} ; # 1794 +} { type source line 1794 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.43 {TIP 280 for compiled [subst]} { + unset -nocomplain a\nb + set a\nb {} + subst {${a +b}[reduce [info frame 0]]} ; # 1800 +} {type source line 1800 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.44 {TIP 280 for compiled [subst]} { + unset -nocomplain a + set a(\n) {} + subst {$a( +)[reduce [info frame 0]]} ; # 1806 +} {type source line 1806 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.45 {TIP 280 for compiled [subst]} { + unset -nocomplain a + set a() {} + subst {$a([ +return -level 0])[reduce [info frame 0]]} ; # 1812 +} {type source line 1812 file info.test cmd {info frame 0} proc ::tcltest::RunTest} +test info-30.46 {TIP 280 for compiled [subst]} { + unset -nocomplain a + set a(1817) YES; set a(1816) 1816; set a(1818) 1818 + subst {$a([dict get [info frame 0] line])} ; # 1817 +} YES +test info-30.47 {TIP 280 for compiled [subst]} { + unset -nocomplain a + set a(\n1823) YES; set a(\n1822) 1822; set a(\n1824) 1824 + subst {$a( +[dict get [info frame 0] line])} ; # 1823 +} YES + # ------------------------------------------------------------------------- # cleanup diff --git a/tests/parse.test b/tests/parse.test index 9427254..b745a97 100644 --- a/tests/parse.test +++ b/tests/parse.test @@ -8,7 +8,7 @@ # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # -# RCS: @(#) $Id: parse.test,v 1.36 2008/11/27 08:23:52 ferrieux Exp $ +# RCS: @(#) $Id: parse.test,v 1.37 2009/09/04 17:33:12 dgp Exp $ if {[catch {package require tcltest 2.0.2}]} { puts stderr "Skipping tests in [info script]. tcltest 2.0.2 required." @@ -896,7 +896,7 @@ test parse-15.60 {CommandComplete procedure} { } 0 test parse-16.1 {Tcl_EvalEx, check termOffset is set correctly for non TCL_OK cases, bug 2535} { - subst {[eval {return foo}]bar} + set subst subst; $subst {[eval {return foo}]bar} } foobar test parse-17.1 {Correct return codes from errors during substitution} { @@ -1043,7 +1043,7 @@ test parse-19.3 {Bug 1115904: recursion limit in Tcl_EvalEx} -setup { i eval {proc {} args {}} interp recursionlimit i 3 } -body { - i eval {subst {[]}} + i eval {set subst subst; $subst {[]}} } -cleanup { interp delete i } @@ -1053,7 +1053,7 @@ test parse-19.4 {Bug 1115904: recursion limit in Tcl_EvalEx} -setup { i eval {proc {} args {}} interp recursionlimit i 2 } -body { - i eval {subst {[[]]}} + i eval {set subst subst; $subst {[[]]}} } -cleanup { interp delete i } -returnCodes error -match glob -result {too many nested*} -- cgit v0.12