OpenFPGA/libs/EXTERNAL/tcl8.6.12/generic/tclCompCmdsSZ.c

4533 lines
124 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* tclCompCmdsSZ.c --
*
* This file contains compilation procedures that compile various Tcl
* commands (beginning with the letters 's' through 'z', except for
* [upvar] and [variable]) into a sequence of instructions ("bytecodes").
* Also includes the operator command compilers.
*
* 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-2010 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.
*/
#include "tclInt.h"
#include "tclCompile.h"
#include "tclStringTrim.h"
/*
* Prototypes for procedures defined later in this file:
*/
static ClientData DupJumptableInfo(ClientData clientData);
static void FreeJumptableInfo(ClientData clientData);
static void PrintJumptableInfo(ClientData clientData,
Tcl_Obj *appendObj, ByteCode *codePtr,
unsigned int pcOffset);
static void DisassembleJumptableInfo(ClientData clientData,
Tcl_Obj *dictObj, ByteCode *codePtr,
unsigned int pcOffset);
static int CompileAssociativeBinaryOpCmd(Tcl_Interp *interp,
Tcl_Parse *parsePtr, const char *identity,
int instruction, CompileEnv *envPtr);
static int CompileComparisonOpCmd(Tcl_Interp *interp,
Tcl_Parse *parsePtr, int instruction,
CompileEnv *envPtr);
static int CompileStrictlyBinaryOpCmd(Tcl_Interp *interp,
Tcl_Parse *parsePtr, int instruction,
CompileEnv *envPtr);
static int CompileUnaryOpCmd(Tcl_Interp *interp,
Tcl_Parse *parsePtr, int instruction,
CompileEnv *envPtr);
static void IssueSwitchChainedTests(Tcl_Interp *interp,
CompileEnv *envPtr, int mode, int noCase,
int valueIndex, int numWords,
Tcl_Token **bodyToken, int *bodyLines,
int **bodyNext);
static void IssueSwitchJumpTable(Tcl_Interp *interp,
CompileEnv *envPtr, int valueIndex,
int numWords, Tcl_Token **bodyToken,
int *bodyLines, int **bodyContLines);
static int IssueTryClausesInstructions(Tcl_Interp *interp,
CompileEnv *envPtr, Tcl_Token *bodyToken,
int numHandlers, int *matchCodes,
Tcl_Obj **matchClauses, int *resultVarIndices,
int *optionVarIndices, Tcl_Token **handlerTokens);
static int IssueTryClausesFinallyInstructions(Tcl_Interp *interp,
CompileEnv *envPtr, Tcl_Token *bodyToken,
int numHandlers, int *matchCodes,
Tcl_Obj **matchClauses, int *resultVarIndices,
int *optionVarIndices, Tcl_Token **handlerTokens,
Tcl_Token *finallyToken);
static int IssueTryFinallyInstructions(Tcl_Interp *interp,
CompileEnv *envPtr, Tcl_Token *bodyToken,
Tcl_Token *finallyToken);
/*
* The structures below define the AuxData types defined in this file.
*/
const AuxDataType tclJumptableInfoType = {
"JumptableInfo", /* name */
DupJumptableInfo, /* dupProc */
FreeJumptableInfo, /* freeProc */
PrintJumptableInfo, /* printProc */
DisassembleJumptableInfo /* disassembleProc */
};
/*
* Shorthand macros for instruction issuing.
*/
#define OP(name) TclEmitOpcode(INST_##name, envPtr)
#define OP1(name,val) TclEmitInstInt1(INST_##name,(val),envPtr)
#define OP4(name,val) TclEmitInstInt4(INST_##name,(val),envPtr)
#define OP14(name,val1,val2) \
TclEmitInstInt1(INST_##name,(val1),envPtr);TclEmitInt4((val2),envPtr)
#define OP44(name,val1,val2) \
TclEmitInstInt4(INST_##name,(val1),envPtr);TclEmitInt4((val2),envPtr)
#define PUSH(str) \
PushStringLiteral(envPtr, str)
#define JUMP4(name,var) \
(var) = CurrentOffset(envPtr);TclEmitInstInt4(INST_##name##4,0,envPtr)
#define FIXJUMP4(var) \
TclStoreInt4AtPtr(CurrentOffset(envPtr)-(var),envPtr->codeStart+(var)+1)
#define JUMP1(name,var) \
(var) = CurrentOffset(envPtr);TclEmitInstInt1(INST_##name##1,0,envPtr)
#define FIXJUMP1(var) \
TclStoreInt1AtPtr(CurrentOffset(envPtr)-(var),envPtr->codeStart+(var)+1)
#define LOAD(idx) \
if ((idx)<256) {OP1(LOAD_SCALAR1,(idx));} else {OP4(LOAD_SCALAR4,(idx));}
#define STORE(idx) \
if ((idx)<256) {OP1(STORE_SCALAR1,(idx));} else {OP4(STORE_SCALAR4,(idx));}
#define INVOKE(name) \
TclEmitInvoke(envPtr,INST_##name)
/*
*----------------------------------------------------------------------
*
* TclCompileSetCmd --
*
* Procedure called to compile the "set" 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 "set" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileSetCmd(
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_Token *varTokenPtr, *valueTokenPtr;
int isAssignment, isScalar, localIndex, numWords;
numWords = parsePtr->numWords;
if ((numWords != 2) && (numWords != 3)) {
return TCL_ERROR;
}
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 = TokenAfter(parsePtr->tokenPtr);
PushVarNameWord(interp, varTokenPtr, envPtr, 0,
&localIndex, &isScalar, 1);
/*
* If we are doing an assignment, push the new value.
*/
if (isAssignment) {
valueTokenPtr = TokenAfter(varTokenPtr);
CompileWord(envPtr, valueTokenPtr, interp, 2);
}
/*
* Emit instructions to set/get the variable.
*/
if (isScalar) {
if (localIndex < 0) {
TclEmitOpcode((isAssignment?
INST_STORE_STK : INST_LOAD_STK), envPtr);
} else 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 {
if (localIndex < 0) {
TclEmitOpcode((isAssignment?
INST_STORE_ARRAY_STK : INST_LOAD_ARRAY_STK), envPtr);
} else if (localIndex <= 255) {
TclEmitInstInt1((isAssignment?
INST_STORE_ARRAY1 : INST_LOAD_ARRAY1),
localIndex, envPtr);
} else {
TclEmitInstInt4((isAssignment?
INST_STORE_ARRAY4 : INST_LOAD_ARRAY4),
localIndex, envPtr);
}
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclCompileString*Cmd --
*
* Procedures called to compile various subcommands of the "string"
* 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 "string" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileStringCatCmd(
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 */
int i, numWords = parsePtr->numWords, numArgs;
Tcl_Token *wordTokenPtr;
Tcl_Obj *obj, *folded;
/* Trivial case, no arg */
if (numWords<2) {
PushStringLiteral(envPtr, "");
return TCL_OK;
}
/* General case: issue CONCAT1's (by chunks of 254 if needed), folding
contiguous constants along the way */
numArgs = 0;
folded = NULL;
wordTokenPtr = TokenAfter(parsePtr->tokenPtr);
for (i = 1; i < numWords; i++) {
obj = Tcl_NewObj();
if (TclWordKnownAtCompileTime(wordTokenPtr, obj)) {
if (folded) {
Tcl_AppendObjToObj(folded, obj);
Tcl_DecrRefCount(obj);
} else {
folded = obj;
}
} else {
Tcl_DecrRefCount(obj);
if (folded) {
int len;
const char *bytes = Tcl_GetStringFromObj(folded, &len);
PushLiteral(envPtr, bytes, len);
Tcl_DecrRefCount(folded);
folded = NULL;
numArgs ++;
}
CompileWord(envPtr, wordTokenPtr, interp, i);
numArgs ++;
if (numArgs >= 254) { /* 254 to take care of the possible +1 of "folded" above */
TclEmitInstInt1(INST_STR_CONCAT1, numArgs, envPtr);
numArgs = 1; /* concat pushes 1 obj, the result */
}
}
wordTokenPtr = TokenAfter(wordTokenPtr);
}
if (folded) {
int len;
const char *bytes = Tcl_GetStringFromObj(folded, &len);
PushLiteral(envPtr, bytes, len);
Tcl_DecrRefCount(folded);
folded = NULL;
numArgs ++;
}
if (numArgs > 1) {
TclEmitInstInt1(INST_STR_CONCAT1, numArgs, envPtr);
}
return TCL_OK;
}
int
TclCompileStringCmpCmd(
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_Token *tokenPtr;
/*
* We don't support any flags; the bytecode isn't that sophisticated.
*/
if (parsePtr->numWords != 3) {
return TCL_ERROR;
}
/*
* Push the two operands onto the stack and then the test.
*/
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
TclEmitOpcode(INST_STR_CMP, envPtr);
return TCL_OK;
}
int
TclCompileStringEqualCmd(
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_Token *tokenPtr;
/*
* We don't support any flags; the bytecode isn't that sophisticated.
*/
if (parsePtr->numWords != 3) {
return TCL_ERROR;
}
/*
* Push the two operands onto the stack and then the test.
*/
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
TclEmitOpcode(INST_STR_EQ, envPtr);
return TCL_OK;
}
int
TclCompileStringFirstCmd(
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_Token *tokenPtr;
/*
* We don't support any flags; the bytecode isn't that sophisticated.
*/
if (parsePtr->numWords != 3) {
return TCL_ERROR;
}
/*
* Push the two operands onto the stack and then the test.
*/
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
OP(STR_FIND);
return TCL_OK;
}
int
TclCompileStringLastCmd(
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_Token *tokenPtr;
/*
* We don't support any flags; the bytecode isn't that sophisticated.
*/
if (parsePtr->numWords != 3) {
return TCL_ERROR;
}
/*
* Push the two operands onto the stack and then the test.
*/
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
OP(STR_FIND_LAST);
return TCL_OK;
}
int
TclCompileStringIndexCmd(
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_Token *tokenPtr;
if (parsePtr->numWords != 3) {
return TCL_ERROR;
}
/*
* Push the two operands onto the stack and then the index operation.
*/
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
TclEmitOpcode(INST_STR_INDEX, envPtr);
return TCL_OK;
}
int
TclCompileStringIsCmd(
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_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr);
static const char *const isClasses[] = {
"alnum", "alpha", "ascii", "control",
"boolean", "digit", "double", "entier",
"false", "graph", "integer", "list",
"lower", "print", "punct", "space",
"true", "upper", "wideinteger", "wordchar",
"xdigit", NULL
};
enum isClasses {
STR_IS_ALNUM, STR_IS_ALPHA, STR_IS_ASCII, STR_IS_CONTROL,
STR_IS_BOOL, STR_IS_DIGIT, STR_IS_DOUBLE, STR_IS_ENTIER,
STR_IS_FALSE, STR_IS_GRAPH, STR_IS_INT, STR_IS_LIST,
STR_IS_LOWER, STR_IS_PRINT, STR_IS_PUNCT, STR_IS_SPACE,
STR_IS_TRUE, STR_IS_UPPER, STR_IS_WIDE, STR_IS_WORD,
STR_IS_XDIGIT
};
int t, range, allowEmpty = 0, end;
InstStringClassType strClassType;
Tcl_Obj *isClass;
if (parsePtr->numWords < 3 || parsePtr->numWords > 6) {
return TCL_ERROR;
}
isClass = Tcl_NewObj();
if (!TclWordKnownAtCompileTime(tokenPtr, isClass)) {
Tcl_DecrRefCount(isClass);
return TCL_ERROR;
} else if (Tcl_GetIndexFromObj(interp, isClass, isClasses, "class", 0,
&t) != TCL_OK) {
Tcl_DecrRefCount(isClass);
TclCompileSyntaxError(interp, envPtr);
return TCL_OK;
}
Tcl_DecrRefCount(isClass);
#define GotLiteral(tokenPtr, word) \
((tokenPtr)->type == TCL_TOKEN_SIMPLE_WORD && \
(tokenPtr)[1].size > 1 && \
(tokenPtr)[1].start[0] == word[0] && \
strncmp((tokenPtr)[1].start, (word), (tokenPtr)[1].size) == 0)
/*
* Cannot handle the -failindex option at all, and that's the only legal
* way to have more than 4 arguments.
*/
if (parsePtr->numWords != 3 && parsePtr->numWords != 4) {
return TCL_ERROR;
}
tokenPtr = TokenAfter(tokenPtr);
if (parsePtr->numWords == 3) {
allowEmpty = 1;
} else {
if (!GotLiteral(tokenPtr, "-strict")) {
return TCL_ERROR;
}
tokenPtr = TokenAfter(tokenPtr);
}
#undef GotLiteral
/*
* Compile the code. There are several main classes of check here.
* 1. Character classes
* 2. Booleans
* 3. Integers
* 4. Floats
* 5. Lists
*/
CompileWord(envPtr, tokenPtr, interp, parsePtr->numWords-1);
switch ((enum isClasses) t) {
case STR_IS_ALNUM:
strClassType = STR_CLASS_ALNUM;
goto compileStrClass;
case STR_IS_ALPHA:
strClassType = STR_CLASS_ALPHA;
goto compileStrClass;
case STR_IS_ASCII:
strClassType = STR_CLASS_ASCII;
goto compileStrClass;
case STR_IS_CONTROL:
strClassType = STR_CLASS_CONTROL;
goto compileStrClass;
case STR_IS_DIGIT:
strClassType = STR_CLASS_DIGIT;
goto compileStrClass;
case STR_IS_GRAPH:
strClassType = STR_CLASS_GRAPH;
goto compileStrClass;
case STR_IS_LOWER:
strClassType = STR_CLASS_LOWER;
goto compileStrClass;
case STR_IS_PRINT:
strClassType = STR_CLASS_PRINT;
goto compileStrClass;
case STR_IS_PUNCT:
strClassType = STR_CLASS_PUNCT;
goto compileStrClass;
case STR_IS_SPACE:
strClassType = STR_CLASS_SPACE;
goto compileStrClass;
case STR_IS_UPPER:
strClassType = STR_CLASS_UPPER;
goto compileStrClass;
case STR_IS_WORD:
strClassType = STR_CLASS_WORD;
goto compileStrClass;
case STR_IS_XDIGIT:
strClassType = STR_CLASS_XDIGIT;
compileStrClass:
if (allowEmpty) {
OP1( STR_CLASS, strClassType);
} else {
int over, over2;
OP( DUP);
OP1( STR_CLASS, strClassType);
JUMP1( JUMP_TRUE, over);
OP( POP);
PUSH( "0");
JUMP1( JUMP, over2);
FIXJUMP1(over);
PUSH( "");
OP( STR_NEQ);
FIXJUMP1(over2);
}
return TCL_OK;
case STR_IS_BOOL:
case STR_IS_FALSE:
case STR_IS_TRUE:
OP( TRY_CVT_TO_BOOLEAN);
switch (t) {
int over, over2;
case STR_IS_BOOL:
if (allowEmpty) {
JUMP1( JUMP_TRUE, over);
PUSH( "");
OP( STR_EQ);
JUMP1( JUMP, over2);
FIXJUMP1(over);
OP( POP);
PUSH( "1");
FIXJUMP1(over2);
} else {
OP4( REVERSE, 2);
OP( POP);
}
return TCL_OK;
case STR_IS_TRUE:
JUMP1( JUMP_TRUE, over);
if (allowEmpty) {
PUSH( "");
OP( STR_EQ);
} else {
OP( POP);
PUSH( "0");
}
FIXJUMP1( over);
OP( LNOT);
OP( LNOT);
return TCL_OK;
case STR_IS_FALSE:
JUMP1( JUMP_TRUE, over);
if (allowEmpty) {
PUSH( "");
OP( STR_NEQ);
} else {
OP( POP);
PUSH( "1");
}
FIXJUMP1( over);
OP( LNOT);
return TCL_OK;
}
break;
case STR_IS_DOUBLE: {
int satisfied, isEmpty;
if (allowEmpty) {
OP( DUP);
PUSH( "");
OP( STR_EQ);
JUMP1( JUMP_TRUE, isEmpty);
OP( NUM_TYPE);
JUMP1( JUMP_TRUE, satisfied);
PUSH( "0");
JUMP1( JUMP, end);
FIXJUMP1( isEmpty);
OP( POP);
FIXJUMP1( satisfied);
} else {
OP( NUM_TYPE);
JUMP1( JUMP_TRUE, satisfied);
PUSH( "0");
JUMP1( JUMP, end);
TclAdjustStackDepth(-1, envPtr);
FIXJUMP1( satisfied);
}
PUSH( "1");
FIXJUMP1( end);
return TCL_OK;
}
case STR_IS_INT:
case STR_IS_WIDE:
case STR_IS_ENTIER:
if (allowEmpty) {
int testNumType;
OP( DUP);
OP( NUM_TYPE);
OP( DUP);
JUMP1( JUMP_TRUE, testNumType);
OP( POP);
PUSH( "");
OP( STR_EQ);
JUMP1( JUMP, end);
TclAdjustStackDepth(1, envPtr);
FIXJUMP1( testNumType);
OP4( REVERSE, 2);
OP( POP);
} else {
OP( NUM_TYPE);
OP( DUP);
JUMP1( JUMP_FALSE, end);
}
switch (t) {
case STR_IS_INT:
PUSH( "1");
OP( EQ);
break;
case STR_IS_WIDE:
PUSH( "2");
OP( LE);
break;
case STR_IS_ENTIER:
PUSH( "3");
OP( LE);
break;
}
FIXJUMP1( end);
return TCL_OK;
case STR_IS_LIST:
range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
OP4( BEGIN_CATCH4, range);
ExceptionRangeStarts(envPtr, range);
OP( DUP);
OP( LIST_LENGTH);
OP( POP);
ExceptionRangeEnds(envPtr, range);
ExceptionRangeTarget(envPtr, range, catchOffset);
OP( POP);
OP( PUSH_RETURN_CODE);
OP( END_CATCH);
OP( LNOT);
return TCL_OK;
}
return TclCompileBasicMin0ArgCmd(interp, parsePtr, cmdPtr, envPtr);
}
int
TclCompileStringMatchCmd(
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_Token *tokenPtr;
int i, length, exactMatch = 0, nocase = 0;
const char *str;
if (parsePtr->numWords < 3 || parsePtr->numWords > 4) {
return TCL_ERROR;
}
tokenPtr = TokenAfter(parsePtr->tokenPtr);
/*
* Check if we have a -nocase flag.
*/
if (parsePtr->numWords == 4) {
if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) {
return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr);
}
str = tokenPtr[1].start;
length = tokenPtr[1].size;
if ((length <= 1) || strncmp(str, "-nocase", length)) {
/*
* Fail at run time, not in compilation.
*/
return TclCompileBasic3ArgCmd(interp, parsePtr, cmdPtr, envPtr);
}
nocase = 1;
tokenPtr = TokenAfter(tokenPtr);
}
/*
* Push the strings to match against each other.
*/
for (i = 0; i < 2; i++) {
if (tokenPtr->type == TCL_TOKEN_SIMPLE_WORD) {
str = tokenPtr[1].start;
length = tokenPtr[1].size;
if (!nocase && (i == 0)) {
/*
* Trivial matches can be done by 'string equal'. 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 = TclMatchIsTrivial(TclGetString(copy));
TclDecrRefCount(copy);
}
PushLiteral(envPtr, str, length);
} else {
SetLineInformation(i+1+nocase);
CompileTokens(envPtr, tokenPtr, interp);
}
tokenPtr = TokenAfter(tokenPtr);
}
/*
* Push the matcher.
*/
if (exactMatch) {
TclEmitOpcode(INST_STR_EQ, envPtr);
} else {
TclEmitInstInt1(INST_STR_MATCH, nocase, envPtr);
}
return TCL_OK;
}
int
TclCompileStringLenCmd(
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_Token *tokenPtr;
Tcl_Obj *objPtr;
if (parsePtr->numWords != 2) {
return TCL_ERROR;
}
tokenPtr = TokenAfter(parsePtr->tokenPtr);
TclNewObj(objPtr);
if (TclWordKnownAtCompileTime(tokenPtr, objPtr)) {
/*
* Here someone is asking for the length of a static string (or
* something with backslashes). Just push the actual character (not
* byte) length.
*/
char buf[TCL_INTEGER_SPACE];
int len = Tcl_GetCharLength(objPtr);
len = sprintf(buf, "%d", len);
PushLiteral(envPtr, buf, len);
} else {
SetLineInformation(1);
CompileTokens(envPtr, tokenPtr, interp);
TclEmitOpcode(INST_STR_LEN, envPtr);
}
TclDecrRefCount(objPtr);
return TCL_OK;
}
int
TclCompileStringMapCmd(
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_Token *mapTokenPtr, *stringTokenPtr;
Tcl_Obj *mapObj, **objv;
char *bytes;
int len;
/*
* We only handle the case:
*
* string map {foo bar} $thing
*
* That is, a literal two-element list (doesn't need to be brace-quoted,
* but does need to be compile-time knowable) and any old argument (the
* thing to map).
*/
if (parsePtr->numWords != 3) {
return TCL_ERROR;
}
mapTokenPtr = TokenAfter(parsePtr->tokenPtr);
stringTokenPtr = TokenAfter(mapTokenPtr);
mapObj = Tcl_NewObj();
Tcl_IncrRefCount(mapObj);
if (!TclWordKnownAtCompileTime(mapTokenPtr, mapObj)) {
Tcl_DecrRefCount(mapObj);
return TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr);
} else if (Tcl_ListObjGetElements(NULL, mapObj, &len, &objv) != TCL_OK) {
Tcl_DecrRefCount(mapObj);
return TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr);
} else if (len != 2) {
Tcl_DecrRefCount(mapObj);
return TclCompileBasic2ArgCmd(interp, parsePtr, cmdPtr, envPtr);
}
/*
* Now issue the opcodes. Note that in the case that we know that the
* first word is an empty word, we don't issue the map at all. That is the
* correct semantics for mapping.
*/
bytes = Tcl_GetStringFromObj(objv[0], &len);
if (len == 0) {
CompileWord(envPtr, stringTokenPtr, interp, 2);
} else {
PushLiteral(envPtr, bytes, len);
bytes = Tcl_GetStringFromObj(objv[1], &len);
PushLiteral(envPtr, bytes, len);
CompileWord(envPtr, stringTokenPtr, interp, 2);
OP(STR_MAP);
}
Tcl_DecrRefCount(mapObj);
return TCL_OK;
}
int
TclCompileStringRangeCmd(
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_Token *stringTokenPtr, *fromTokenPtr, *toTokenPtr;
int idx1, idx2;
if (parsePtr->numWords != 4) {
return TCL_ERROR;
}
stringTokenPtr = TokenAfter(parsePtr->tokenPtr);
fromTokenPtr = TokenAfter(stringTokenPtr);
toTokenPtr = TokenAfter(fromTokenPtr);
/* Every path must push the string argument */
CompileWord(envPtr, stringTokenPtr, interp, 1);
/*
* Parse the two indices.
*/
if (TclGetIndexFromToken(fromTokenPtr, TCL_INDEX_START, TCL_INDEX_AFTER,
&idx1) != TCL_OK) {
goto nonConstantIndices;
}
/*
* Token parsed as an index expression. We treat all indices before
* the string the same as the start of the string.
*/
if (idx1 == TCL_INDEX_AFTER) {
/* [string range $s end+1 $last] must be empty string */
OP( POP);
PUSH( "");
return TCL_OK;
}
if (TclGetIndexFromToken(toTokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_END,
&idx2) != TCL_OK) {
goto nonConstantIndices;
}
/*
* Token parsed as an index expression. We treat all indices after
* the string the same as the end of the string.
*/
if (idx2 == TCL_INDEX_BEFORE) {
/* [string range $s $first -1] must be empty string */
OP( POP);
PUSH( "");
return TCL_OK;
}
/*
* Push the operand onto the stack and then the substring operation.
*/
OP44( STR_RANGE_IMM, idx1, idx2);
return TCL_OK;
/*
* Push the operands onto the stack and then the substring operation.
*/
nonConstantIndices:
CompileWord(envPtr, fromTokenPtr, interp, 2);
CompileWord(envPtr, toTokenPtr, interp, 3);
OP( STR_RANGE);
return TCL_OK;
}
int
TclCompileStringReplaceCmd(
Tcl_Interp *interp, /* Tcl interpreter for context. */
Tcl_Parse *parsePtr, /* Points to a parse structure for the
* command. */
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr) /* Holds the resulting instructions. */
{
DefineLineInformation; /* TIP #280 */
Tcl_Token *tokenPtr, *valueTokenPtr;
int first, last;
if (parsePtr->numWords < 4 || parsePtr->numWords > 5) {
return TCL_ERROR;
}
/* Bytecode to compute/push string argument being replaced */
valueTokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, valueTokenPtr, interp, 1);
/*
* Check for first index known and useful at compile time.
*/
tokenPtr = TokenAfter(valueTokenPtr);
if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_AFTER,
&first) != TCL_OK) {
goto genericReplace;
}
/*
* Check for last index known and useful at compile time.
*/
tokenPtr = TokenAfter(tokenPtr);
if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_AFTER,
&last) != TCL_OK) {
goto genericReplace;
}
/*
* [string replace] is an odd bird. For many arguments it is
* a conventional substring replacer. However it also goes out
* of its way to become a no-op for many cases where it would be
* replacing an empty substring. Precisely, it is a no-op when
*
* (last < first) OR
* (last < 0) OR
* (end < first)
*
* For some compile-time values we can detect these cases, and
* compile direct to bytecode implementing the no-op.
*/
if ((last == TCL_INDEX_BEFORE) /* Know (last < 0) */
|| (first == TCL_INDEX_AFTER) /* Know (first > end) */
/*
* Tricky to determine when runtime (last < first) can be
* certainly known based on the encoded values. Consider the
* cases...
*
* (first <= TCL_INDEX_END) &&
* (last == TCL_INDEX_AFTER) => cannot tell REJECT
* (last <= TCL_INDEX END) && (last < first) => ACCEPT
* else => cannot tell REJECT
*/
|| ((first <= TCL_INDEX_END) && (last <= TCL_INDEX_END)
&& (last < first)) /* Know (last < first) */
/*
* (first == TCL_INDEX_BEFORE) &&
* (last == TCL_INDEX_AFTER) => (first < last) REJECT
* (last <= TCL_INDEX_END) => cannot tell REJECT
* else => (first < last) REJECT
*
* else [[first >= TCL_INDEX_START]] &&
* (last == TCL_INDEX_AFTER) => cannot tell REJECT
* (last <= TCL_INDEX_END) => cannot tell REJECT
* else [[last >= TCL_INDEX START]] && (last < first) => ACCEPT
*/
|| ((first >= TCL_INDEX_START) && (last >= TCL_INDEX_START)
&& (last < first))) { /* Know (last < first) */
if (parsePtr->numWords == 5) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 4);
OP( POP); /* Pop newString */
}
/* Original string argument now on TOS as result */
return TCL_OK;
}
if (parsePtr->numWords == 5) {
/*
* When we have a string replacement, we have to take care about
* not replacing empty substrings that [string replace] promises
* not to replace
*
* The remaining index values might be suitable for conventional
* string replacement, but only if they cannot possibly meet the
* conditions described above at runtime. If there's a chance they
* might, we would have to emit bytecode to check and at that point
* we're paying more in bytecode execution time than would make
* things worthwhile. Trouble is we are very limited in
* how much we can detect that at compile time. After decoding,
* we need, first:
*
* (first <= end)
*
* The encoded indices (first <= TCL_INDEX END) and
* (first == TCL_INDEX_BEFORE) always meets this condition, but
* any other encoded first index has some list for which it fails.
*
* We also need, second:
*
* (last >= 0)
*
* The encoded indices (last >= TCL_INDEX_START) and
* (last == TCL_INDEX_AFTER) always meet this condition but any
* other encoded last index has some list for which it fails.
*
* Finally we need, third:
*
* (first <= last)
*
* Considered in combination with the constraints we already have,
* we see that we can proceed when (first == TCL_INDEX_BEFORE)
* or (last == TCL_INDEX_AFTER). These also permit simplification
* of the prefix|replace|suffix construction. The other constraints,
* though, interfere with getting a guarantee that first <= last.
*/
if ((first == TCL_INDEX_BEFORE) && (last >= TCL_INDEX_START)) {
/* empty prefix */
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 4);
OP4( REVERSE, 2);
if (last == TCL_INDEX_AFTER) {
OP( POP); /* Pop original */
} else {
OP44( STR_RANGE_IMM, last + 1, TCL_INDEX_END);
OP1( STR_CONCAT1, 2);
}
return TCL_OK;
}
if ((last == TCL_INDEX_AFTER) && (first <= TCL_INDEX_END)) {
OP44( STR_RANGE_IMM, 0, first-1);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 4);
OP1( STR_CONCAT1, 2);
return TCL_OK;
}
/* FLOW THROUGH TO genericReplace */
} else {
/*
* When we have no replacement string to worry about, we may
* have more luck, because the forbidden empty string replacements
* are harmless when they are replaced by another empty string.
*/
if ((first == TCL_INDEX_BEFORE) || (first == TCL_INDEX_START)) {
/* empty prefix - build suffix only */
if ((last == TCL_INDEX_END) || (last == TCL_INDEX_AFTER)) {
/* empty suffix too => empty result */
OP( POP); /* Pop original */
PUSH ( "");
return TCL_OK;
}
OP44( STR_RANGE_IMM, last + 1, TCL_INDEX_END);
return TCL_OK;
} else {
if ((last == TCL_INDEX_END) || (last == TCL_INDEX_AFTER)) {
/* empty suffix - build prefix only */
OP44( STR_RANGE_IMM, 0, first-1);
return TCL_OK;
}
OP( DUP);
OP44( STR_RANGE_IMM, 0, first-1);
OP4( REVERSE, 2);
OP44( STR_RANGE_IMM, last + 1, TCL_INDEX_END);
OP1( STR_CONCAT1, 2);
return TCL_OK;
}
}
genericReplace:
tokenPtr = TokenAfter(valueTokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 3);
if (parsePtr->numWords == 5) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 4);
} else {
PUSH( "");
}
OP( STR_REPLACE);
return TCL_OK;
}
int
TclCompileStringTrimLCmd(
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_Token *tokenPtr;
if (parsePtr->numWords != 2 && parsePtr->numWords != 3) {
return TCL_ERROR;
}
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
if (parsePtr->numWords == 3) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
} else {
PushLiteral(envPtr, tclDefaultTrimSet, strlen(tclDefaultTrimSet));
}
OP( STR_TRIM_LEFT);
return TCL_OK;
}
int
TclCompileStringTrimRCmd(
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_Token *tokenPtr;
if (parsePtr->numWords != 2 && parsePtr->numWords != 3) {
return TCL_ERROR;
}
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
if (parsePtr->numWords == 3) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
} else {
PushLiteral(envPtr, tclDefaultTrimSet, strlen(tclDefaultTrimSet));
}
OP( STR_TRIM_RIGHT);
return TCL_OK;
}
int
TclCompileStringTrimCmd(
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_Token *tokenPtr;
if (parsePtr->numWords != 2 && parsePtr->numWords != 3) {
return TCL_ERROR;
}
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
if (parsePtr->numWords == 3) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
} else {
PushLiteral(envPtr, tclDefaultTrimSet, strlen(tclDefaultTrimSet));
}
OP( STR_TRIM);
return TCL_OK;
}
int
TclCompileStringToUpperCmd(
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_Token *tokenPtr;
if (parsePtr->numWords != 2) {
return TclCompileBasic1To3ArgCmd(interp, parsePtr, cmdPtr, envPtr);
}
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
OP( STR_UPPER);
return TCL_OK;
}
int
TclCompileStringToLowerCmd(
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_Token *tokenPtr;
if (parsePtr->numWords != 2) {
return TclCompileBasic1To3ArgCmd(interp, parsePtr, cmdPtr, envPtr);
}
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
OP( STR_LOWER);
return TCL_OK;
}
int
TclCompileStringToTitleCmd(
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_Token *tokenPtr;
if (parsePtr->numWords != 2) {
return TclCompileBasic1To3ArgCmd(interp, parsePtr, cmdPtr, envPtr);
}
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
OP( STR_TITLE);
return TCL_OK;
}
/*
* Support definitions for the [string is] compilation.
*/
static int
UniCharIsAscii(
int character)
{
return (character >= 0) && (character < 0x80);
}
static int
UniCharIsHexDigit(
int character)
{
return (character >= 0) && (character < 0x80) && isxdigit(character);
}
StringClassDesc const tclStringClassTable[] = {
{"alnum", Tcl_UniCharIsAlnum},
{"alpha", Tcl_UniCharIsAlpha},
{"ascii", UniCharIsAscii},
{"control", Tcl_UniCharIsControl},
{"digit", Tcl_UniCharIsDigit},
{"graph", Tcl_UniCharIsGraph},
{"lower", Tcl_UniCharIsLower},
{"print", Tcl_UniCharIsPrint},
{"punct", Tcl_UniCharIsPunct},
{"space", Tcl_UniCharIsSpace},
{"upper", Tcl_UniCharIsUpper},
{"word", Tcl_UniCharIsWordChar},
{"xdigit", UniCharIsHexDigit},
{NULL, NULL}
};
/*
*----------------------------------------------------------------------
*
* 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. */
{
DefineLineInformation; /* TIP #280 */
int numArgs = parsePtr->numWords - 1;
int numOpts = numArgs - 1;
int objc, flags = TCL_SUBST_ALL;
Tcl_Obj **objv/*, *toSubst = NULL*/;
Tcl_Token *wordTokenPtr = TokenAfter(parsePtr->tokenPtr);
int code = TCL_ERROR;
if (numArgs == 0) {
return TCL_ERROR;
}
objv = 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 = TclSubstOptions(NULL, numOpts, objv, &flags);
}
cleanup:
while (--objc >= 0) {
TclDecrRefCount(objv[objc]);
}
TclStackFree(interp, objv);
if (/*toSubst == NULL*/ code != TCL_OK) {
return TCL_ERROR;
}
SetLineInformation(numArgs);
TclSubstCompile(interp, wordTokenPtr[1].start, wordTokenPtr[1].size,
flags, mapPtr->loc[eclIndex].line[numArgs], envPtr);
/* TclDecrRefCount(toSubst);*/
return TCL_OK;
}
void
TclSubstCompile(
Tcl_Interp *interp,
const char *bytes,
int numBytes,
int flags,
int line,
CompileEnv *envPtr)
{
Tcl_Token *endTokenPtr, *tokenPtr;
int breakOffset = 0, count = 0, bline = line;
Tcl_Parse parse;
Tcl_InterpState state = NULL;
TclSubstParse(interp, bytes, numBytes, flags, &parse, &state);
if (state != NULL) {
Tcl_ResetResult(interp);
}
/*
* Tricky point! If the first token does not result in a *guaranteed* push
* of a Tcl_Obj on the stack, we must push an empty object. Otherwise it
* is possible to get to an INST_STR_CONCAT1 or INST_DONE without enough
* values on the stack, resulting in a crash. Thanks to Joe Mistachkin for
* identifying a script that could trigger this case.
*/
tokenPtr = parse.tokenPtr;
if (tokenPtr->type != TCL_TOKEN_TEXT && tokenPtr->type != TCL_TOKEN_BS) {
PUSH("");
count++;
}
for (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 = TclParseBackslash(tokenPtr->start, tokenPtr->size,
NULL, buf);
literal = TclRegisterNewLiteral(envPtr, buf, length);
TclEmitPush(literal, envPtr);
count++;
continue;
case TCL_TOKEN_VARIABLE:
/*
* Check for simple variable access; see if we can only generate
* TCL_OK or TCL_ERROR from the substituted variable read; if so,
* there is no need to generate elaborate exception-management
* code. Note that the first component of TCL_TOKEN_VARIABLE is
* always TCL_TOKEN_TEXT...
*/
if (tokenPtr->numComponents > 1) {
int i, foundCommand = 0;
for (i=2 ; i<=tokenPtr->numComponents ; i++) {
if (tokenPtr[i].type == TCL_TOKEN_COMMAND) {
foundCommand = 1;
break;
}
}
if (foundCommand) {
break;
}
}
envPtr->line = bline;
TclCompileVarSubst(interp, tokenPtr, envPtr);
bline = envPtr->line;
count++;
continue;
}
while (count > 255) {
OP1( STR_CONCAT1, 255);
count -= 254;
}
if (count > 1) {
OP1( STR_CONCAT1, count);
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",
(int) (CurrentOffset(envPtr) - startFixup.codeOffset));
}
}
envPtr->line = bline;
catchRange = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
OP4( BEGIN_CATCH4, catchRange);
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 */
OP( END_CATCH);
TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &okFixup);
TclAdjustStackDepth(-1, envPtr);
/* Exceptional return codes processed here */
ExceptionRangeTarget(envPtr, catchRange, catchOffset);
OP( PUSH_RETURN_OPTIONS);
OP( PUSH_RESULT);
OP( PUSH_RETURN_CODE);
OP( END_CATCH);
OP( RETURN_CODE_BRANCH);
/* ERROR -> reraise it; NB: can't require BREAK/CONTINUE handling */
OP( RETURN_STK);
OP( NOP);
/* 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);
TclAdjustStackDepth(1, envPtr);
/* BREAK destination */
if (TclFixupForwardJumpToHere(envPtr, &breakFixup, 127)) {
Tcl_Panic("TclCompileSubstCmd: bad break jump distance %d",
(int) (CurrentOffset(envPtr) - breakFixup.codeOffset));
}
OP( POP);
OP( POP);
breakJump = CurrentOffset(envPtr) - breakOffset;
if (breakJump > 127) {
OP4(JUMP4, -breakJump);
} else {
OP1(JUMP1, -breakJump);
}
TclAdjustStackDepth(2, envPtr);
/* CONTINUE destination */
if (TclFixupForwardJumpToHere(envPtr, &continueFixup, 127)) {
Tcl_Panic("TclCompileSubstCmd: bad continue jump distance %d",
(int) (CurrentOffset(envPtr) - continueFixup.codeOffset));
}
OP( POP);
OP( POP);
TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &endFixup);
TclAdjustStackDepth(2, envPtr);
/* RETURN + other destination */
if (TclFixupForwardJumpToHere(envPtr, &returnFixup, 127)) {
Tcl_Panic("TclCompileSubstCmd: bad return jump distance %d",
(int) (CurrentOffset(envPtr) - returnFixup.codeOffset));
}
if (TclFixupForwardJumpToHere(envPtr, &otherFixup, 127)) {
Tcl_Panic("TclCompileSubstCmd: bad other jump distance %d",
(int) (CurrentOffset(envPtr) - otherFixup.codeOffset));
}
/*
* Pull the result to top of stack, discard options dict.
*/
OP4( REVERSE, 2);
OP( POP);
/* OK destination */
if (TclFixupForwardJumpToHere(envPtr, &okFixup, 127)) {
Tcl_Panic("TclCompileSubstCmd: bad ok jump distance %d",
(int) (CurrentOffset(envPtr) - okFixup.codeOffset));
}
if (count > 1) {
OP1(STR_CONCAT1, count);
count = 1;
}
/* CONTINUE jump to here */
if (TclFixupForwardJumpToHere(envPtr, &endFixup, 127)) {
Tcl_Panic("TclCompileSubstCmd: bad end jump distance %d",
(int) (CurrentOffset(envPtr) - endFixup.codeOffset));
}
bline = envPtr->line;
}
while (count > 255) {
OP1( STR_CONCAT1, 255);
count -= 254;
}
if (count > 1) {
OP1( STR_CONCAT1, count);
}
Tcl_FreeParse(&parse);
if (state != NULL) {
Tcl_RestoreInterpState(interp, state);
TclCompileSyntaxError(interp, envPtr);
TclAdjustStackDepth(-1, envPtr);
}
/* Final target of the multi-jump from all BREAKs */
if (breakOffset > 0) {
TclUpdateInstInt4AtPc(INST_JUMP4, CurrentOffset(envPtr) - breakOffset,
envPtr->codeStart + breakOffset);
}
}
/*
*----------------------------------------------------------------------
*
* TclCompileSwitchCmd --
*
* Procedure called to compile the "switch" 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 "switch" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileSwitchCmd(
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_Token *tokenPtr; /* Pointer to tokens in command. */
int numWords; /* Number of words in command. */
Tcl_Token *valueTokenPtr; /* Token for the value to switch on. */
enum {Switch_Exact, Switch_Glob, Switch_Regexp} mode;
/* What kind of switch are we doing? */
Tcl_Token *bodyTokenArray; /* Array of real pattern list items. */
Tcl_Token **bodyToken; /* Array of pointers to pattern list items. */
int *bodyLines; /* Array of line numbers for body list
* items. */
int **bodyContLines; /* Array of continuation line info. */
int noCase; /* Has the -nocase flag been given? */
int foundMode = 0; /* Have we seen a mode flag yet? */
int i, valueIndex;
int result = TCL_ERROR;
int *clNext = envPtr->clNext;
/*
* Only handle the following versions:
* switch ?--? word {pattern body ...}
* switch -exact ?--? word {pattern body ...}
* switch -glob ?--? word {pattern body ...}
* switch -regexp ?--? word {pattern body ...}
* switch -- word simpleWordPattern simpleWordBody ...
* switch -exact -- word simpleWordPattern simpleWordBody ...
* switch -glob -- word simpleWordPattern simpleWordBody ...
* switch -regexp -- word simpleWordPattern simpleWordBody ...
* When the mode is -glob, can also handle a -nocase flag.
*
* First off, we don't care how the command's word was generated; we're
* compiling it anyway! So skip it...
*/
tokenPtr = TokenAfter(parsePtr->tokenPtr);
valueIndex = 1;
numWords = parsePtr->numWords-1;
/*
* Check for options.
*/
noCase = 0;
mode = Switch_Exact;
if (numWords == 2) {
/*
* There's just the switch value and the bodies list. In that case, we
* can skip all option parsing and move on to consider switch values
* and the body list.
*/
goto finishedOptionParse;
}
/*
* There must be at least one option, --, because without that there is no
* way to statically avoid the problems you get from strings-to-be-matched
* that start with a - (the interpreted code falls apart if it encounters
* them, so we punt if we *might* encounter them as that is the easiest
* way of emulating the behaviour).
*/
for (; numWords>=3 ; tokenPtr=TokenAfter(tokenPtr),numWords--) {
unsigned size = tokenPtr[1].size;
const char *chrs = tokenPtr[1].start;
/*
* We only process literal options, and we assume that -e, -g and -n
* are unique prefixes of -exact, -glob and -nocase respectively (true
* at time of writing). Note that -exact and -glob may only be given
* at most once or we bail out (error case).
*/
if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD || size < 2) {
return TCL_ERROR;
}
if ((size <= 6) && !memcmp(chrs, "-exact", size)) {
if (foundMode) {
return TCL_ERROR;
}
mode = Switch_Exact;
foundMode = 1;
valueIndex++;
continue;
} else if ((size <= 5) && !memcmp(chrs, "-glob", size)) {
if (foundMode) {
return TCL_ERROR;
}
mode = Switch_Glob;
foundMode = 1;
valueIndex++;
continue;
} else if ((size <= 7) && !memcmp(chrs, "-regexp", size)) {
if (foundMode) {
return TCL_ERROR;
}
mode = Switch_Regexp;
foundMode = 1;
valueIndex++;
continue;
} else if ((size <= 7) && !memcmp(chrs, "-nocase", size)) {
noCase = 1;
valueIndex++;
continue;
} else if ((size == 2) && !memcmp(chrs, "--", 2)) {
valueIndex++;
break;
}
/*
* The switch command has many flags we cannot compile at all (e.g.
* all the RE-related ones) which we must have encountered. Either
* that or we have run off the end. The action here is the same: punt
* to interpreted version.
*/
return TCL_ERROR;
}
if (numWords < 3) {
return TCL_ERROR;
}
tokenPtr = TokenAfter(tokenPtr);
numWords--;
if (noCase && (mode == Switch_Exact)) {
/*
* Can't compile this case; no opcode for case-insensitive equality!
*/
return TCL_ERROR;
}
/*
* 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.
*/
finishedOptionParse:
valueTokenPtr = tokenPtr;
/* For valueIndex, see previous loop. */
tokenPtr = TokenAfter(tokenPtr);
numWords--;
/*
* Build an array of tokens for the matcher terms and script bodies. Note
* that in the case of the quoted bodies, this is tricky as we cannot use
* copies of the string from the input token for the generated tokens (it
* causes a crash during exception handling). When multiple tokens are
* available at this point, this is pretty easy.
*/
if (numWords == 1) {
const char *bytes;
int maxLen, numBytes;
int bline; /* TIP #280: line of the pattern/action list,
* and start of list for when tracking the
* location. This list comes immediately after
* the value we switch on. */
if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) {
return TCL_ERROR;
}
bytes = tokenPtr[1].start;
numBytes = tokenPtr[1].size;
/* Allocate enough space to work in. */
maxLen = TclMaxListLength(bytes, numBytes, NULL);
if (maxLen < 2) {
return TCL_ERROR;
}
bodyTokenArray = ckalloc(sizeof(Tcl_Token) * maxLen);
bodyToken = ckalloc(sizeof(Tcl_Token *) * maxLen);
bodyLines = ckalloc(sizeof(int) * maxLen);
bodyContLines = ckalloc(sizeof(int*) * maxLen);
bline = mapPtr->loc[eclIndex].line[valueIndex+1];
numWords = 0;
while (numBytes > 0) {
const char *prevBytes = bytes;
int literal;
if (TCL_OK != TclFindElement(NULL, bytes, numBytes,
&(bodyTokenArray[numWords].start), &bytes,
&(bodyTokenArray[numWords].size), &literal) || !literal) {
goto abort;
}
bodyTokenArray[numWords].type = TCL_TOKEN_TEXT;
bodyTokenArray[numWords].numComponents = 0;
bodyToken[numWords] = bodyTokenArray + numWords;
/*
* TIP #280: Now determine the line the list element starts on
* (there is no need to do it earlier, due to the possibility of
* aborting, see above).
*/
TclAdvanceLines(&bline, prevBytes, bodyTokenArray[numWords].start);
TclAdvanceContinuations(&bline, &clNext,
bodyTokenArray[numWords].start - envPtr->source);
bodyLines[numWords] = bline;
bodyContLines[numWords] = clNext;
TclAdvanceLines(&bline, bodyTokenArray[numWords].start, bytes);
TclAdvanceContinuations(&bline, &clNext, bytes - envPtr->source);
numBytes -= (bytes - prevBytes);
numWords++;
}
if (numWords % 2) {
abort:
ckfree((char *) bodyToken);
ckfree((char *) bodyTokenArray);
ckfree((char *) bodyLines);
ckfree((char *) bodyContLines);
return TCL_ERROR;
}
} else if (numWords % 2 || numWords == 0) {
/*
* Odd number of words (>1) available, or no words at all available.
* Both are error cases, so punt and let the interpreted-version
* generate the error message. Note that the second case probably
* should get caught earlier, but it's easy to check here again anyway
* because it'd cause a nasty crash otherwise.
*/
return TCL_ERROR;
} else {
/*
* Multi-word definition of patterns & actions.
*/
bodyToken = ckalloc(sizeof(Tcl_Token *) * numWords);
bodyLines = ckalloc(sizeof(int) * numWords);
bodyContLines = ckalloc(sizeof(int*) * numWords);
bodyTokenArray = NULL;
for (i=0 ; i<numWords ; i++) {
/*
* We only handle the very simplest case. Anything more complex is
* a good reason to go to the interpreted case anyway due to
* traces, etc.
*/
if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) {
goto freeTemporaries;
}
bodyToken[i] = tokenPtr+1;
/*
* TIP #280: Copy line information from regular cmd info.
*/
bodyLines[i] = mapPtr->loc[eclIndex].line[valueIndex+1+i];
bodyContLines[i] = mapPtr->loc[eclIndex].next[valueIndex+1+i];
tokenPtr = TokenAfter(tokenPtr);
}
}
/*
* Fall back to interpreted if the last body is a continuation (it's
* illegal, but this makes the error happen at the right time).
*/
if (bodyToken[numWords-1]->size == 1 &&
bodyToken[numWords-1]->start[0] == '-') {
goto freeTemporaries;
}
/*
* Now we commit to generating code; the parsing stage per se is done.
* Check if we can generate a jump table, since if so that's faster than
* doing an explicit compare with each body. Note that we're definitely
* over-conservative with determining whether we can do the jump table,
* but it handles the most common case well enough.
*/
/* Both methods push the value to match against onto the stack. */
CompileWord(envPtr, valueTokenPtr, interp, valueIndex);
if (mode == Switch_Exact) {
IssueSwitchJumpTable(interp, envPtr, valueIndex, numWords, bodyToken,
bodyLines, bodyContLines);
} else {
IssueSwitchChainedTests(interp, envPtr, mode, noCase, valueIndex,
numWords, bodyToken, bodyLines, bodyContLines);
}
result = TCL_OK;
/*
* Clean up all our temporary space and return.
*/
freeTemporaries:
ckfree(bodyToken);
ckfree(bodyLines);
ckfree(bodyContLines);
if (bodyTokenArray != NULL) {
ckfree(bodyTokenArray);
}
return result;
}
/*
*----------------------------------------------------------------------
*
* IssueSwitchChainedTests --
*
* Generate instructions for a [switch] command that is to be compiled
* into a sequence of tests. This is the generic handle-everything mode
* that inherently has performance that is (on average) linear in the
* number of tests. It is the only mode that can handle -glob and -regexp
* matches, or anything that is case-insensitive. It does not handle the
* wild-and-wooly end of regexp matching (i.e., capture of match results)
* so that's when we spill to the interpreted version.
*
*----------------------------------------------------------------------
*/
static void
IssueSwitchChainedTests(
Tcl_Interp *interp, /* Context for compiling script bodies. */
CompileEnv *envPtr, /* Holds resulting instructions. */
int mode, /* Exact, Glob or Regexp */
int noCase, /* Case-insensitivity flag. */
int valueIndex, /* The value to match against. */
int numBodyTokens, /* Number of tokens describing things the
* switch can match against and bodies to
* execute when the match succeeds. */
Tcl_Token **bodyToken, /* Array of pointers to pattern list items. */
int *bodyLines, /* Array of line numbers for body list
* items. */
int **bodyContLines) /* Array of continuation line info. */
{
enum {Switch_Exact, Switch_Glob, Switch_Regexp};
int foundDefault; /* Flag to indicate whether a "default" clause
* is present. */
JumpFixup *fixupArray; /* Array of forward-jump fixup records. */
unsigned 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; /* Number of continuation bodies pointing to
* the current (or next) real body. */
int nextArmFixupIndex;
int simple, exact; /* For extracting the type of regexp. */
int i;
/*
* Generate a test for each arm.
*/
contFixIndex = -1;
contFixCount = 0;
fixupArray = TclStackAlloc(interp, sizeof(JumpFixup) * numBodyTokens);
fixupTargetArray = TclStackAlloc(interp, sizeof(int) * numBodyTokens);
memset(fixupTargetArray, 0, numBodyTokens * sizeof(int));
fixupCount = 0;
foundDefault = 0;
for (i=0 ; i<numBodyTokens ; i+=2) {
nextArmFixupIndex = -1;
if (i!=numBodyTokens-2 || bodyToken[numBodyTokens-2]->size != 7 ||
memcmp(bodyToken[numBodyTokens-2]->start, "default", 7)) {
/*
* Generate the test for the arm.
*/
switch (mode) {
case Switch_Exact:
OP( DUP);
TclCompileTokens(interp, bodyToken[i], 1, envPtr);
OP( STR_EQ);
break;
case Switch_Glob:
TclCompileTokens(interp, bodyToken[i], 1, envPtr);
OP4( OVER, 1);
OP1( STR_MATCH, noCase);
break;
case Switch_Regexp:
simple = exact = 0;
/*
* Keep in sync with TclCompileRegexpCmd.
*/
if (bodyToken[i]->type == TCL_TOKEN_TEXT) {
Tcl_DString ds;
if (bodyToken[i]->size == 0) {
/*
* The semantics of regexps are that they always match
* when the RE == "".
*/
PUSH("1");
break;
}
/*
* Attempt to convert pattern to glob. If successful, push
* the converted pattern.
*/
if (TclReToGlob(NULL, bodyToken[i]->start,
bodyToken[i]->size, &ds, &exact, NULL) == TCL_OK){
simple = 1;
PushLiteral(envPtr, Tcl_DStringValue(&ds),
Tcl_DStringLength(&ds));
Tcl_DStringFree(&ds);
}
}
if (!simple) {
TclCompileTokens(interp, bodyToken[i], 1, envPtr);
}
OP4( OVER, 1);
if (!simple) {
/*
* Pass correct RE compile flags. We use only Int1
* (8-bit), but that handles all the flags we want to
* pass. Don't use TCL_REG_NOSUB as we may have backrefs
* or capture vars.
*/
int cflags = TCL_REG_ADVANCED
| (noCase ? TCL_REG_NOCASE : 0);
OP1(REGEXP, cflags);
} else if (exact && !noCase) {
OP( STR_EQ);
} else {
OP1(STR_MATCH, noCase);
}
break;
default:
Tcl_Panic("unknown switch mode: %d", mode);
}
/*
* In a fall-through case, we will jump on _true_ to the place
* where the body starts (generated later, with guarantee of this
* ensured earlier; the final body is never a fall-through).
*/
if (bodyToken[i+1]->size==1 && bodyToken[i+1]->start[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 to inhibit the generation of
* the jump after the body and the cleanup of the intermediate
* value that we are switching against.
*
* Note that default clauses (which are always terminal clauses)
* cannot be fall-through clauses as well, since the last clause
* is never a fall-through clause (which we have already
* verified).
*/
foundDefault = 1;
}
/*
* 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) {
int j;
for (j=0 ; j<contFixCount ; j++) {
fixupTargetArray[contFixIndex+j] = CurrentOffset(envPtr);
}
contFixIndex = -1;
}
/*
* Now do the actual compilation. Note that we do not use BODY()
* because we may have synthesized the tokens in a non-standard
* pattern.
*/
OP( POP);
envPtr->line = bodyLines[i+1]; /* TIP #280 */
envPtr->clNext = bodyContLines[i+1]; /* TIP #280 */
TclCompileCmdWord(interp, bodyToken[i+1], 1, envPtr);
if (!foundDefault) {
TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP,
&fixupArray[fixupCount]);
fixupCount++;
fixupTargetArray[nextArmFixupIndex] = CurrentOffset(envPtr);
}
}
/*
* Discard the value we are matching against unless we've had a default
* clause (in which case it will already be gone due to the code at the
* start of processing an arm, guaranteed) and make the result of the
* command an empty string.
*/
if (!foundDefault) {
OP( POP);
PUSH("");
}
/*
* 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.
*/
for (i=0 ; i<fixupCount ; i++) {
if (fixupTargetArray[i] == 0) {
fixupTargetArray[i] = envPtr->codeNext-envPtr->codeStart;
}
}
/*
* 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 interleaved nature of
* all the jumps makes this impossible to do without nested loops).
*/
for (i=fixupCount-1 ; i>=0 ; i--) {
if (TclFixupForwardJump(envPtr, &fixupArray[i],
fixupTargetArray[i] - fixupArray[i].codeOffset, 127)) {
int j;
for (j=i-1 ; j>=0 ; j--) {
if (fixupTargetArray[j] > fixupArray[i].codeOffset) {
fixupTargetArray[j] += 3;
}
}
}
}
TclStackFree(interp, fixupTargetArray);
TclStackFree(interp, fixupArray);
}
/*
*----------------------------------------------------------------------
*
* IssueSwitchJumpTable --
*
* Generate instructions for a [switch] command that is to be compiled
* into a jump table. This only handles the case where case-sensitive,
* exact matching is used, but this is actually the most common case in
* real code.
*
*----------------------------------------------------------------------
*/
static void
IssueSwitchJumpTable(
Tcl_Interp *interp, /* Context for compiling script bodies. */
CompileEnv *envPtr, /* Holds resulting instructions. */
int valueIndex, /* The value to match against. */
int numBodyTokens, /* Number of tokens describing things the
* switch can match against and bodies to
* execute when the match succeeds. */
Tcl_Token **bodyToken, /* Array of pointers to pattern list items. */
int *bodyLines, /* Array of line numbers for body list
* items. */
int **bodyContLines) /* Array of continuation line info. */
{
JumptableInfo *jtPtr;
int infoIndex, isNew, *finalFixups, numRealBodies = 0, jumpLocation;
int mustGenerate, foundDefault, jumpToDefault, i;
Tcl_DString buffer;
Tcl_HashEntry *hPtr;
/*
* Compile the switch by using a jump table, which is basically a
* hashtable that maps from literal values to match against to the offset
* (relative to the INST_JUMP_TABLE instruction) to jump to. The jump
* table itself is independent of any invokation of the bytecode, and as
* such is stored in an auxData block.
*
* Start by allocating the jump table itself, plus some workspace.
*/
jtPtr = ckalloc(sizeof(JumptableInfo));
Tcl_InitHashTable(&jtPtr->hashTable, TCL_STRING_KEYS);
infoIndex = TclCreateAuxData(jtPtr, &tclJumptableInfoType, envPtr);
finalFixups = TclStackAlloc(interp, sizeof(int) * (numBodyTokens/2));
foundDefault = 0;
mustGenerate = 1;
/*
* Next, issue the instruction to do the jump, together with what we want
* to do if things do not work out (jump to either the default clause or
* the "default" default, which just sets the result to empty). Note that
* we will come back and rewrite the jump's offset parameter when we know
* what it should be, and that all jumps we issue are of the wide kind
* because that makes the code much easier to debug!
*/
jumpLocation = CurrentOffset(envPtr);
OP4( JUMP_TABLE, infoIndex);
jumpToDefault = CurrentOffset(envPtr);
OP4( JUMP4, 0);
for (i=0 ; i<numBodyTokens ; i+=2) {
/*
* For each arm, we must first work out what to do with the match
* term.
*/
if (i!=numBodyTokens-2 || bodyToken[numBodyTokens-2]->size != 7 ||
memcmp(bodyToken[numBodyTokens-2]->start, "default", 7)) {
/*
* This is not a default clause, so insert the current location as
* a target in the jump table (assuming it isn't already there,
* which would indicate that this clause is probably masked by an
* earlier one). Note that we use a Tcl_DString here simply
* because the hash API does not let us specify the string length.
*/
Tcl_DStringInit(&buffer);
TclDStringAppendToken(&buffer, bodyToken[i]);
hPtr = Tcl_CreateHashEntry(&jtPtr->hashTable,
Tcl_DStringValue(&buffer), &isNew);
if (isNew) {
/*
* First time we've encountered this match clause, so it must
* point to here.
*/
Tcl_SetHashValue(hPtr, CurrentOffset(envPtr) - jumpLocation);
}
Tcl_DStringFree(&buffer);
} else {
/*
* This is a default clause, so patch up the fallthrough from the
* INST_JUMP_TABLE instruction to here.
*/
foundDefault = 1;
isNew = 1;
TclStoreInt4AtPtr(CurrentOffset(envPtr)-jumpToDefault,
envPtr->codeStart+jumpToDefault+1);
}
/*
* Now, for each arm we must deal with the body of the clause.
*
* If this is a continuation body (never true of a final clause,
* whether default or not) we're done because the next jump target
* will also point here, so we advance to the next clause.
*/
if (bodyToken[i+1]->size == 1 && bodyToken[i+1]->start[0] == '-') {
mustGenerate = 1;
continue;
}
/*
* Also skip this arm if its only match clause is masked. (We could
* probably be more aggressive about this, but that would be much more
* difficult to get right.)
*/
if (!isNew && !mustGenerate) {
continue;
}
mustGenerate = 0;
/*
* Compile the body of the arm.
*/
envPtr->line = bodyLines[i+1]; /* TIP #280 */
envPtr->clNext = bodyContLines[i+1]; /* TIP #280 */
TclCompileCmdWord(interp, bodyToken[i+1], 1, envPtr);
/*
* Compile a jump in to the end of the command if this body is
* anything other than a user-supplied default arm (to either skip
* over the remaining bodies or the code that generates an empty
* result).
*/
if (i+2 < numBodyTokens || !foundDefault) {
finalFixups[numRealBodies++] = CurrentOffset(envPtr);
/*
* Easier by far to issue this jump as a fixed-width jump, since
* otherwise we'd need to do a lot more (and more awkward)
* rewriting when we fixed this all up.
*/
OP4( JUMP4, 0);
TclAdjustStackDepth(-1, envPtr);
}
}
/*
* We're at the end. If we've not already done so through the processing
* of a user-supplied default clause, add in a "default" default clause
* now.
*/
if (!foundDefault) {
TclStoreInt4AtPtr(CurrentOffset(envPtr)-jumpToDefault,
envPtr->codeStart+jumpToDefault+1);
PUSH("");
}
/*
* No more instructions to be issued; everything that needs to jump to the
* end of the command is fixed up at this point.
*/
for (i=0 ; i<numRealBodies ; i++) {
TclStoreInt4AtPtr(CurrentOffset(envPtr)-finalFixups[i],
envPtr->codeStart+finalFixups[i]+1);
}
/*
* Clean up all our temporary space and return.
*/
TclStackFree(interp, finalFixups);
}
/*
*----------------------------------------------------------------------
*
* DupJumptableInfo, FreeJumptableInfo --
*
* Functions to duplicate, release and print a jump-table created for use
* with the INST_JUMP_TABLE instruction.
*
* Results:
* DupJumptableInfo: a copy of the jump-table
* FreeJumptableInfo: none
* PrintJumptableInfo: none
* DisassembleJumptableInfo: none
*
* Side effects:
* DupJumptableInfo: allocates memory
* FreeJumptableInfo: releases memory
* PrintJumptableInfo: none
* DisassembleJumptableInfo: none
*
*----------------------------------------------------------------------
*/
static ClientData
DupJumptableInfo(
ClientData clientData)
{
JumptableInfo *jtPtr = clientData;
JumptableInfo *newJtPtr = ckalloc(sizeof(JumptableInfo));
Tcl_HashEntry *hPtr, *newHPtr;
Tcl_HashSearch search;
int isNew;
Tcl_InitHashTable(&newJtPtr->hashTable, TCL_STRING_KEYS);
hPtr = Tcl_FirstHashEntry(&jtPtr->hashTable, &search);
while (hPtr != NULL) {
newHPtr = Tcl_CreateHashEntry(&newJtPtr->hashTable,
Tcl_GetHashKey(&jtPtr->hashTable, hPtr), &isNew);
Tcl_SetHashValue(newHPtr, Tcl_GetHashValue(hPtr));
}
return newJtPtr;
}
static void
FreeJumptableInfo(
ClientData clientData)
{
JumptableInfo *jtPtr = clientData;
Tcl_DeleteHashTable(&jtPtr->hashTable);
ckfree(jtPtr);
}
static void
PrintJumptableInfo(
ClientData clientData,
Tcl_Obj *appendObj,
ByteCode *codePtr,
unsigned int pcOffset)
{
JumptableInfo *jtPtr = clientData;
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
const char *keyPtr;
int offset, i = 0;
hPtr = Tcl_FirstHashEntry(&jtPtr->hashTable, &search);
for (; hPtr ; hPtr = Tcl_NextHashEntry(&search)) {
keyPtr = Tcl_GetHashKey(&jtPtr->hashTable, hPtr);
offset = PTR2INT(Tcl_GetHashValue(hPtr));
if (i++) {
Tcl_AppendToObj(appendObj, ", ", -1);
if (i%4==0) {
Tcl_AppendToObj(appendObj, "\n\t\t", -1);
}
}
Tcl_AppendPrintfToObj(appendObj, "\"%s\"->pc %d",
keyPtr, pcOffset + offset);
}
}
static void
DisassembleJumptableInfo(
ClientData clientData,
Tcl_Obj *dictObj,
ByteCode *codePtr,
unsigned int pcOffset)
{
JumptableInfo *jtPtr = clientData;
Tcl_Obj *mapping = Tcl_NewObj();
Tcl_HashEntry *hPtr;
Tcl_HashSearch search;
const char *keyPtr;
int offset;
hPtr = Tcl_FirstHashEntry(&jtPtr->hashTable, &search);
for (; hPtr ; hPtr = Tcl_NextHashEntry(&search)) {
keyPtr = Tcl_GetHashKey(&jtPtr->hashTable, hPtr);
offset = PTR2INT(Tcl_GetHashValue(hPtr));
Tcl_DictObjPut(NULL, mapping, Tcl_NewStringObj(keyPtr, -1),
Tcl_NewIntObj(offset));
}
Tcl_DictObjPut(NULL, dictObj, Tcl_NewStringObj("mapping", -1), mapping);
}
/*
*----------------------------------------------------------------------
*
* TclCompileTailcallCmd --
*
* Procedure called to compile the "tailcall" 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 "tailcall" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileTailcallCmd(
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_Token *tokenPtr = parsePtr->tokenPtr;
int i;
if (parsePtr->numWords < 2 || parsePtr->numWords > 256
|| envPtr->procPtr == NULL) {
return TCL_ERROR;
}
/* make room for the nsObjPtr */
/* TODO: Doesn't this have to be a known value? */
CompileWord(envPtr, tokenPtr, interp, 0);
for (i=1 ; i<parsePtr->numWords ; i++) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, i);
}
TclEmitInstInt1( INST_TAILCALL, parsePtr->numWords, envPtr);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclCompileThrowCmd --
*
* Procedure called to compile the "throw" 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 "throw" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileThrowCmd(
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 */
int numWords = parsePtr->numWords;
Tcl_Token *codeToken, *msgToken;
Tcl_Obj *objPtr;
int codeKnown, codeIsList, codeIsValid, len;
if (numWords != 3) {
return TCL_ERROR;
}
codeToken = TokenAfter(parsePtr->tokenPtr);
msgToken = TokenAfter(codeToken);
TclNewObj(objPtr);
Tcl_IncrRefCount(objPtr);
codeKnown = TclWordKnownAtCompileTime(codeToken, objPtr);
/*
* First we must emit the code to substitute the arguments. This
* must come first in case substitution raises errors.
*/
if (!codeKnown) {
CompileWord(envPtr, codeToken, interp, 1);
PUSH( "-errorcode");
}
CompileWord(envPtr, msgToken, interp, 2);
codeIsList = codeKnown && (TCL_OK ==
Tcl_ListObjLength(interp, objPtr, &len));
codeIsValid = codeIsList && (len != 0);
if (codeIsValid) {
Tcl_Obj *errPtr, *dictPtr;
TclNewLiteralStringObj(errPtr, "-errorcode");
TclNewObj(dictPtr);
Tcl_DictObjPut(NULL, dictPtr, errPtr, objPtr);
TclEmitPush(TclAddLiteralObj(envPtr, dictPtr, NULL), envPtr);
}
TclDecrRefCount(objPtr);
/*
* Simpler bytecodes when we detect invalid arguments at compile time.
*/
if (codeKnown && !codeIsValid) {
OP( POP);
if (codeIsList) {
/* Must be an empty list */
goto issueErrorForEmptyCode;
}
TclCompileSyntaxError(interp, envPtr);
return TCL_OK;
}
if (!codeKnown) {
/*
* Argument validity checking has to be done by bytecode at
* run time.
*/
OP4( REVERSE, 3);
OP( DUP);
OP( LIST_LENGTH);
OP1( JUMP_FALSE1, 16);
OP4( LIST, 2);
OP44( RETURN_IMM, TCL_ERROR, 0);
TclAdjustStackDepth(2, envPtr);
OP( POP);
OP( POP);
OP( POP);
issueErrorForEmptyCode:
PUSH( "type must be non-empty list");
PUSH( "-errorcode {TCL OPERATION THROW BADEXCEPTION}");
}
OP44( RETURN_IMM, TCL_ERROR, 0);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclCompileTryCmd --
*
* Procedure called to compile the "try" 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 "try" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileTryCmd(
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 numWords = parsePtr->numWords, numHandlers, result = TCL_ERROR;
Tcl_Token *bodyToken, *finallyToken, *tokenPtr;
Tcl_Token **handlerTokens = NULL;
Tcl_Obj **matchClauses = NULL;
int *matchCodes=NULL, *resultVarIndices=NULL, *optionVarIndices=NULL;
int i;
if (numWords < 2) {
return TCL_ERROR;
}
bodyToken = TokenAfter(parsePtr->tokenPtr);
if (numWords == 2) {
/*
* No handlers or finally; do nothing beyond evaluating the body.
*/
DefineLineInformation; /* TIP #280 */
BODY(bodyToken, 1);
return TCL_OK;
}
numWords -= 2;
tokenPtr = TokenAfter(bodyToken);
/*
* Extract information about what handlers there are.
*/
numHandlers = numWords >> 2;
numWords -= numHandlers * 4;
if (numHandlers > 0) {
handlerTokens = TclStackAlloc(interp, sizeof(Tcl_Token*)*numHandlers);
matchClauses = TclStackAlloc(interp, sizeof(Tcl_Obj *) * numHandlers);
memset(matchClauses, 0, sizeof(Tcl_Obj *) * numHandlers);
matchCodes = TclStackAlloc(interp, sizeof(int) * numHandlers);
resultVarIndices = TclStackAlloc(interp, sizeof(int) * numHandlers);
optionVarIndices = TclStackAlloc(interp, sizeof(int) * numHandlers);
for (i=0 ; i<numHandlers ; i++) {
Tcl_Obj *tmpObj, **objv;
int objc;
if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) {
goto failedToCompile;
}
if (tokenPtr[1].size == 4
&& !strncmp(tokenPtr[1].start, "trap", 4)) {
/*
* Parse the list of errorCode words to match against.
*/
matchCodes[i] = TCL_ERROR;
tokenPtr = TokenAfter(tokenPtr);
TclNewObj(tmpObj);
Tcl_IncrRefCount(tmpObj);
if (!TclWordKnownAtCompileTime(tokenPtr, tmpObj)
|| Tcl_ListObjLength(NULL, tmpObj, &objc) != TCL_OK
|| (objc == 0)) {
TclDecrRefCount(tmpObj);
goto failedToCompile;
}
Tcl_ListObjReplace(NULL, tmpObj, 0, 0, 0, NULL);
matchClauses[i] = tmpObj;
} else if (tokenPtr[1].size == 2
&& !strncmp(tokenPtr[1].start, "on", 2)) {
int code;
/*
* Parse the result code to look for.
*/
tokenPtr = TokenAfter(tokenPtr);
TclNewObj(tmpObj);
Tcl_IncrRefCount(tmpObj);
if (!TclWordKnownAtCompileTime(tokenPtr, tmpObj)) {
TclDecrRefCount(tmpObj);
goto failedToCompile;
}
if (TCL_ERROR == TclGetCompletionCodeFromObj(NULL, tmpObj, &code)) {
TclDecrRefCount(tmpObj);
goto failedToCompile;
}
matchCodes[i] = code;
TclDecrRefCount(tmpObj);
} else {
goto failedToCompile;
}
/*
* Parse the variable binding.
*/
tokenPtr = TokenAfter(tokenPtr);
TclNewObj(tmpObj);
Tcl_IncrRefCount(tmpObj);
if (!TclWordKnownAtCompileTime(tokenPtr, tmpObj)) {
TclDecrRefCount(tmpObj);
goto failedToCompile;
}
if (Tcl_ListObjGetElements(NULL, tmpObj, &objc, &objv) != TCL_OK
|| (objc > 2)) {
TclDecrRefCount(tmpObj);
goto failedToCompile;
}
if (objc > 0) {
int len;
const char *varname = Tcl_GetStringFromObj(objv[0], &len);
resultVarIndices[i] = LocalScalar(varname, len, envPtr);
if (resultVarIndices[i] < 0) {
TclDecrRefCount(tmpObj);
goto failedToCompile;
}
} else {
resultVarIndices[i] = -1;
}
if (objc == 2) {
int len;
const char *varname = Tcl_GetStringFromObj(objv[1], &len);
optionVarIndices[i] = LocalScalar(varname, len, envPtr);
if (optionVarIndices[i] < 0) {
TclDecrRefCount(tmpObj);
goto failedToCompile;
}
} else {
optionVarIndices[i] = -1;
}
TclDecrRefCount(tmpObj);
/*
* Extract the body for this handler.
*/
tokenPtr = TokenAfter(tokenPtr);
if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD) {
goto failedToCompile;
}
if (tokenPtr[1].size == 1 && tokenPtr[1].start[0] == '-') {
handlerTokens[i] = NULL;
} else {
handlerTokens[i] = tokenPtr;
}
tokenPtr = TokenAfter(tokenPtr);
}
if (handlerTokens[numHandlers-1] == NULL) {
goto failedToCompile;
}
}
/*
* Parse the finally clause
*/
if (numWords == 0) {
finallyToken = NULL;
} else if (numWords == 2) {
if (tokenPtr->type != TCL_TOKEN_SIMPLE_WORD || tokenPtr[1].size != 7
|| strncmp(tokenPtr[1].start, "finally", 7)) {
goto failedToCompile;
}
finallyToken = TokenAfter(tokenPtr);
} else {
goto failedToCompile;
}
/*
* Issue the bytecode.
*/
if (!finallyToken) {
result = IssueTryClausesInstructions(interp, envPtr, bodyToken,
numHandlers, matchCodes, matchClauses, resultVarIndices,
optionVarIndices, handlerTokens);
} else if (numHandlers == 0) {
result = IssueTryFinallyInstructions(interp, envPtr, bodyToken,
finallyToken);
} else {
result = IssueTryClausesFinallyInstructions(interp, envPtr, bodyToken,
numHandlers, matchCodes, matchClauses, resultVarIndices,
optionVarIndices, handlerTokens, finallyToken);
}
/*
* Delete any temporary state and finish off.
*/
failedToCompile:
if (numHandlers > 0) {
for (i=0 ; i<numHandlers ; i++) {
if (matchClauses[i]) {
TclDecrRefCount(matchClauses[i]);
}
}
TclStackFree(interp, optionVarIndices);
TclStackFree(interp, resultVarIndices);
TclStackFree(interp, matchCodes);
TclStackFree(interp, matchClauses);
TclStackFree(interp, handlerTokens);
}
return result;
}
/*
*----------------------------------------------------------------------
*
* IssueTryClausesInstructions, IssueTryClausesFinallyInstructions,
* IssueTryFinallyInstructions --
*
* The code generators for [try]. Split from the parsing engine for
* reasons of developer sanity, and also split between no-finally,
* just-finally and with-finally cases because so many of the details of
* generation vary between the three.
*
* The macros below make the instruction issuing easier to follow.
*
*----------------------------------------------------------------------
*/
static int
IssueTryClausesInstructions(
Tcl_Interp *interp,
CompileEnv *envPtr,
Tcl_Token *bodyToken,
int numHandlers,
int *matchCodes,
Tcl_Obj **matchClauses,
int *resultVars,
int *optionVars,
Tcl_Token **handlerTokens)
{
DefineLineInformation; /* TIP #280 */
int range, resultVar, optionsVar;
int i, j, len, forwardsNeedFixing = 0, trapZero = 0, afterBody = 0;
int *addrsToFix, *forwardsToFix, notCodeJumpSource, notECJumpSource;
int *noError;
char buf[TCL_INTEGER_SPACE];
resultVar = AnonymousLocal(envPtr);
optionsVar = AnonymousLocal(envPtr);
if (resultVar < 0 || optionsVar < 0) {
return TCL_ERROR;
}
/*
* Check if we're supposed to trap a normal TCL_OK completion of the body.
* If not, we can handle that case much more efficiently.
*/
for (i=0 ; i<numHandlers ; i++) {
if (matchCodes[i] == 0) {
trapZero = 1;
break;
}
}
/*
* Compile the body, trapping any error in it so that we can trap on it
* and/or run a finally clause. Note that there must be at least one
* on/trap clause; when none is present, this whole function is not called
* (and it's never called when there's a finally clause).
*/
range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
OP4( BEGIN_CATCH4, range);
ExceptionRangeStarts(envPtr, range);
BODY( bodyToken, 1);
ExceptionRangeEnds(envPtr, range);
if (!trapZero) {
OP( END_CATCH);
JUMP4( JUMP, afterBody);
TclAdjustStackDepth(-1, envPtr);
} else {
PUSH( "0");
OP4( REVERSE, 2);
OP1( JUMP1, 4);
TclAdjustStackDepth(-2, envPtr);
}
ExceptionRangeTarget(envPtr, range, catchOffset);
OP( PUSH_RETURN_CODE);
OP( PUSH_RESULT);
OP( PUSH_RETURN_OPTIONS);
OP( END_CATCH);
STORE( optionsVar);
OP( POP);
STORE( resultVar);
OP( POP);
/*
* Now we handle all the registered 'on' and 'trap' handlers in order.
* For us to be here, there must be at least one handler.
*
* Slight overallocation, but reduces size of this function.
*/
addrsToFix = TclStackAlloc(interp, sizeof(int)*numHandlers);
forwardsToFix = TclStackAlloc(interp, sizeof(int)*numHandlers);
noError = TclStackAlloc(interp, sizeof(int)*numHandlers);
for (i=0 ; i<numHandlers ; i++) {
noError[i] = -1;
sprintf(buf, "%d", matchCodes[i]);
OP( DUP);
PushLiteral(envPtr, buf, strlen(buf));
OP( EQ);
JUMP4( JUMP_FALSE, notCodeJumpSource);
if (matchClauses[i]) {
const char *p;
Tcl_ListObjLength(NULL, matchClauses[i], &len);
/*
* Match the errorcode according to try/trap rules.
*/
LOAD( optionsVar);
PUSH( "-errorcode");
OP4( DICT_GET, 1);
TclAdjustStackDepth(-1, envPtr);
OP44( LIST_RANGE_IMM, 0, len-1);
p = Tcl_GetStringFromObj(matchClauses[i], &len);
PushLiteral(envPtr, p, len);
OP( STR_EQ);
JUMP4( JUMP_FALSE, notECJumpSource);
} else {
notECJumpSource = -1; /* LINT */
}
OP( POP);
/*
* There is no finally clause, so we can avoid wrapping a catch
* context around the handler. That simplifies what instructions need
* to be issued a lot since we can let errors just fall through.
*/
if (resultVars[i] >= 0) {
LOAD( resultVar);
STORE( resultVars[i]);
OP( POP);
if (optionVars[i] >= 0) {
LOAD( optionsVar);
STORE( optionVars[i]);
OP( POP);
}
}
if (!handlerTokens[i]) {
forwardsNeedFixing = 1;
JUMP4( JUMP, forwardsToFix[i]);
TclAdjustStackDepth(1, envPtr);
} else {
int dontChangeOptions;
forwardsToFix[i] = -1;
if (forwardsNeedFixing) {
forwardsNeedFixing = 0;
for (j=0 ; j<i ; j++) {
if (forwardsToFix[j] == -1) {
continue;
}
FIXJUMP4(forwardsToFix[j]);
forwardsToFix[j] = -1;
}
}
range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
OP4( BEGIN_CATCH4, range);
ExceptionRangeStarts(envPtr, range);
BODY( handlerTokens[i], 5+i*4);
ExceptionRangeEnds(envPtr, range);
OP( END_CATCH);
JUMP4( JUMP, noError[i]);
ExceptionRangeTarget(envPtr, range, catchOffset);
TclAdjustStackDepth(-1, envPtr);
OP( PUSH_RESULT);
OP( PUSH_RETURN_OPTIONS);
OP( PUSH_RETURN_CODE);
OP( END_CATCH);
PUSH( "1");
OP( EQ);
JUMP1( JUMP_FALSE, dontChangeOptions);
LOAD( optionsVar);
OP4( REVERSE, 2);
STORE( optionsVar);
OP( POP);
PUSH( "-during");
OP4( REVERSE, 2);
OP44( DICT_SET, 1, optionsVar);
TclAdjustStackDepth(-1, envPtr);
FIXJUMP1( dontChangeOptions);
OP4( REVERSE, 2);
INVOKE( RETURN_STK);
}
JUMP4( JUMP, addrsToFix[i]);
if (matchClauses[i]) {
FIXJUMP4( notECJumpSource);
}
FIXJUMP4( notCodeJumpSource);
}
/*
* Drop the result code since it didn't match any clause, and reissue the
* exception. Note also that INST_RETURN_STK can proceed to the next
* instruction.
*/
OP( POP);
LOAD( optionsVar);
LOAD( resultVar);
INVOKE( RETURN_STK);
/*
* Fix all the jumps from taken clauses to here (which is the end of the
* [try]).
*/
if (!trapZero) {
FIXJUMP4(afterBody);
}
for (i=0 ; i<numHandlers ; i++) {
FIXJUMP4(addrsToFix[i]);
if (noError[i] != -1) {
FIXJUMP4(noError[i]);
}
}
TclStackFree(interp, noError);
TclStackFree(interp, forwardsToFix);
TclStackFree(interp, addrsToFix);
return TCL_OK;
}
static int
IssueTryClausesFinallyInstructions(
Tcl_Interp *interp,
CompileEnv *envPtr,
Tcl_Token *bodyToken,
int numHandlers,
int *matchCodes,
Tcl_Obj **matchClauses,
int *resultVars,
int *optionVars,
Tcl_Token **handlerTokens,
Tcl_Token *finallyToken) /* Not NULL */
{
DefineLineInformation; /* TIP #280 */
int range, resultVar, optionsVar, i, j, len, forwardsNeedFixing = 0;
int trapZero = 0, afterBody = 0, finalOK, finalError, noFinalError;
int *addrsToFix, *forwardsToFix, notCodeJumpSource, notECJumpSource;
char buf[TCL_INTEGER_SPACE];
resultVar = AnonymousLocal(envPtr);
optionsVar = AnonymousLocal(envPtr);
if (resultVar < 0 || optionsVar < 0) {
return TCL_ERROR;
}
/*
* Check if we're supposed to trap a normal TCL_OK completion of the body.
* If not, we can handle that case much more efficiently.
*/
for (i=0 ; i<numHandlers ; i++) {
if (matchCodes[i] == 0) {
trapZero = 1;
break;
}
}
/*
* Compile the body, trapping any error in it so that we can trap on it
* (if any trap matches) and run a finally clause.
*/
range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
OP4( BEGIN_CATCH4, range);
ExceptionRangeStarts(envPtr, range);
BODY( bodyToken, 1);
ExceptionRangeEnds(envPtr, range);
if (!trapZero) {
OP( END_CATCH);
STORE( resultVar);
OP( POP);
PUSH( "-level 0 -code 0");
STORE( optionsVar);
OP( POP);
JUMP4( JUMP, afterBody);
} else {
PUSH( "0");
OP4( REVERSE, 2);
OP1( JUMP1, 4);
TclAdjustStackDepth(-2, envPtr);
}
ExceptionRangeTarget(envPtr, range, catchOffset);
OP( PUSH_RETURN_CODE);
OP( PUSH_RESULT);
OP( PUSH_RETURN_OPTIONS);
OP( END_CATCH);
STORE( optionsVar);
OP( POP);
STORE( resultVar);
OP( POP);
/*
* Now we handle all the registered 'on' and 'trap' handlers in order.
*
* Slight overallocation, but reduces size of this function.
*/
addrsToFix = TclStackAlloc(interp, sizeof(int)*numHandlers);
forwardsToFix = TclStackAlloc(interp, sizeof(int)*numHandlers);
for (i=0 ; i<numHandlers ; i++) {
int noTrapError, trapError;
const char *p;
sprintf(buf, "%d", matchCodes[i]);
OP( DUP);
PushLiteral(envPtr, buf, strlen(buf));
OP( EQ);
JUMP4( JUMP_FALSE, notCodeJumpSource);
if (matchClauses[i]) {
Tcl_ListObjLength(NULL, matchClauses[i], &len);
/*
* Match the errorcode according to try/trap rules.
*/
LOAD( optionsVar);
PUSH( "-errorcode");
OP4( DICT_GET, 1);
TclAdjustStackDepth(-1, envPtr);
OP44( LIST_RANGE_IMM, 0, len-1);
p = Tcl_GetStringFromObj(matchClauses[i], &len);
PushLiteral(envPtr, p, len);
OP( STR_EQ);
JUMP4( JUMP_FALSE, notECJumpSource);
} else {
notECJumpSource = -1; /* LINT */
}
OP( POP);
/*
* There is a finally clause, so we need a fairly complex sequence of
* instructions to deal with an on/trap handler because we must call
* the finally handler *and* we need to substitute the result from a
* failed trap for the result from the main script.
*/
if (resultVars[i] >= 0 || handlerTokens[i]) {
range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
OP4( BEGIN_CATCH4, range);
ExceptionRangeStarts(envPtr, range);
}
if (resultVars[i] >= 0) {
LOAD( resultVar);
STORE( resultVars[i]);
OP( POP);
if (optionVars[i] >= 0) {
LOAD( optionsVar);
STORE( optionVars[i]);
OP( POP);
}
if (!handlerTokens[i]) {
/*
* No handler. Will not be the last handler (that is a
* condition that is checked by the caller). Chain to the next
* one.
*/
ExceptionRangeEnds(envPtr, range);
OP( END_CATCH);
forwardsNeedFixing = 1;
JUMP4( JUMP, forwardsToFix[i]);
goto finishTrapCatchHandling;
}
} else if (!handlerTokens[i]) {
/*
* No handler. Will not be the last handler (that condition is
* checked by the caller). Chain to the next one.
*/
forwardsNeedFixing = 1;
JUMP4( JUMP, forwardsToFix[i]);
goto endOfThisArm;
}
/*
* Got a handler. Make sure that any pending patch-up actions from
* previous unprocessed handlers are dealt with now that we know where
* they are to jump to.
*/
if (forwardsNeedFixing) {
forwardsNeedFixing = 0;
OP1( JUMP1, 7);
for (j=0 ; j<i ; j++) {
if (forwardsToFix[j] == -1) {
continue;
}
FIXJUMP4( forwardsToFix[j]);
forwardsToFix[j] = -1;
}
OP4( BEGIN_CATCH4, range);
}
BODY( handlerTokens[i], 5+i*4);
ExceptionRangeEnds(envPtr, range);
PUSH( "0");
OP( PUSH_RETURN_OPTIONS);
OP4( REVERSE, 3);
OP1( JUMP1, 5);
TclAdjustStackDepth(-3, envPtr);
forwardsToFix[i] = -1;
/*
* Error in handler or setting of variables; replace the stored
* exception with the new one. Note that we only push this if we have
* either a body or some variable setting here. Otherwise this code is
* unreachable.
*/
finishTrapCatchHandling:
ExceptionRangeTarget(envPtr, range, catchOffset);
OP( PUSH_RETURN_OPTIONS);
OP( PUSH_RETURN_CODE);
OP( PUSH_RESULT);
OP( END_CATCH);
STORE( resultVar);
OP( POP);
PUSH( "1");
OP( EQ);
JUMP1( JUMP_FALSE, noTrapError);
LOAD( optionsVar);
PUSH( "-during");
OP4( REVERSE, 3);
STORE( optionsVar);
OP( POP);
OP44( DICT_SET, 1, optionsVar);
TclAdjustStackDepth(-1, envPtr);
JUMP1( JUMP, trapError);
FIXJUMP1( noTrapError);
STORE( optionsVar);
FIXJUMP1( trapError);
/* Skip POP at end; can clean up with subsequent POP */
if (i+1 < numHandlers) {
OP( POP);
}
endOfThisArm:
if (i+1 < numHandlers) {
JUMP4( JUMP, addrsToFix[i]);
TclAdjustStackDepth(1, envPtr);
}
if (matchClauses[i]) {
FIXJUMP4( notECJumpSource);
}
FIXJUMP4( notCodeJumpSource);
}
/*
* Drop the result code, and fix all the jumps from taken clauses - which
* drop the result code as their first action - to point straight after
* (i.e., to the start of the finally clause).
*/
OP( POP);
for (i=0 ; i<numHandlers-1 ; i++) {
FIXJUMP4( addrsToFix[i]);
}
TclStackFree(interp, forwardsToFix);
TclStackFree(interp, addrsToFix);
/*
* Process the finally clause (at last!) Note that we do not wrap this in
* error handlers because we would just rethrow immediately anyway. Then
* (on normal success) we reissue the exception. Note also that
* INST_RETURN_STK can proceed to the next instruction; that'll be the
* next command (or some inter-command manipulation).
*/
if (!trapZero) {
FIXJUMP4( afterBody);
}
range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
OP4( BEGIN_CATCH4, range);
ExceptionRangeStarts(envPtr, range);
BODY( finallyToken, 3 + 4*numHandlers);
ExceptionRangeEnds(envPtr, range);
OP( END_CATCH);
OP( POP);
JUMP1( JUMP, finalOK);
ExceptionRangeTarget(envPtr, range, catchOffset);
OP( PUSH_RESULT);
OP( PUSH_RETURN_OPTIONS);
OP( PUSH_RETURN_CODE);
OP( END_CATCH);
PUSH( "1");
OP( EQ);
JUMP1( JUMP_FALSE, noFinalError);
LOAD( optionsVar);
PUSH( "-during");
OP4( REVERSE, 3);
STORE( optionsVar);
OP( POP);
OP44( DICT_SET, 1, optionsVar);
TclAdjustStackDepth(-1, envPtr);
OP( POP);
JUMP1( JUMP, finalError);
TclAdjustStackDepth(1, envPtr);
FIXJUMP1( noFinalError);
STORE( optionsVar);
OP( POP);
FIXJUMP1( finalError);
STORE( resultVar);
OP( POP);
FIXJUMP1( finalOK);
LOAD( optionsVar);
LOAD( resultVar);
INVOKE( RETURN_STK);
return TCL_OK;
}
static int
IssueTryFinallyInstructions(
Tcl_Interp *interp,
CompileEnv *envPtr,
Tcl_Token *bodyToken,
Tcl_Token *finallyToken)
{
DefineLineInformation; /* TIP #280 */
int range, jumpOK, jumpSplice;
/*
* Note that this one is simple enough that we can issue it without
* needing a local variable table, making it a universal compilation.
*/
range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
OP4( BEGIN_CATCH4, range);
ExceptionRangeStarts(envPtr, range);
BODY( bodyToken, 1);
ExceptionRangeEnds(envPtr, range);
OP1( JUMP1, 3);
TclAdjustStackDepth(-1, envPtr);
ExceptionRangeTarget(envPtr, range, catchOffset);
OP( PUSH_RESULT);
OP( PUSH_RETURN_OPTIONS);
OP( END_CATCH);
range = TclCreateExceptRange(CATCH_EXCEPTION_RANGE, envPtr);
OP4( BEGIN_CATCH4, range);
ExceptionRangeStarts(envPtr, range);
BODY( finallyToken, 3);
ExceptionRangeEnds(envPtr, range);
OP( END_CATCH);
OP( POP);
JUMP1( JUMP, jumpOK);
ExceptionRangeTarget(envPtr, range, catchOffset);
OP( PUSH_RESULT);
OP( PUSH_RETURN_OPTIONS);
OP( PUSH_RETURN_CODE);
OP( END_CATCH);
PUSH( "1");
OP( EQ);
JUMP1( JUMP_FALSE, jumpSplice);
PUSH( "-during");
OP4( OVER, 3);
OP4( LIST, 2);
OP( LIST_CONCAT);
FIXJUMP1( jumpSplice);
OP4( REVERSE, 4);
OP( POP);
OP( POP);
OP1( JUMP1, 7);
FIXJUMP1( jumpOK);
OP4( REVERSE, 2);
INVOKE( RETURN_STK);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclCompileUnsetCmd --
*
* Procedure called to compile the "unset" 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 "unset" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileUnsetCmd(
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_Token *varTokenPtr;
int isScalar, localIndex, flags = 1, i, varCount = 0, haveFlags = 0;
/* TODO: Consider support for compiling expanded args. */
/*
* Verify that all words - except the first non-option one - are known at
* compile time so that we can handle them without needing to do a nasty
* push/rotate. [Bug 3970f54c4e]
*/
for (i=1,varTokenPtr=parsePtr->tokenPtr ; i<parsePtr->numWords ; i++) {
Tcl_Obj *leadingWord = Tcl_NewObj();
varTokenPtr = TokenAfter(varTokenPtr);
if (!TclWordKnownAtCompileTime(varTokenPtr, leadingWord)) {
TclDecrRefCount(leadingWord);
/*
* We can tolerate non-trivial substitutions in the first variable
* to be unset. If a '--' or '-nocomplain' was present, anything
* goes in that one place! (All subsequent variable names must be
* constants since we don't want to have to push them all first.)
*/
if (varCount == 0) {
if (haveFlags) {
continue;
}
/*
* In fact, we're OK as long as we're the first argument *and*
* we provably don't start with a '-'. If that is true, then
* even if everything else is varying, we still can't be a
* flag. Otherwise we'll spill to runtime to place a limit on
* the trickiness.
*/
if (varTokenPtr->type == TCL_TOKEN_WORD
&& varTokenPtr[1].type == TCL_TOKEN_TEXT
&& varTokenPtr[1].size > 0
&& varTokenPtr[1].start[0] != '-') {
continue;
}
}
return TCL_ERROR;
}
if (varCount == 0) {
const char *bytes;
int len;
bytes = Tcl_GetStringFromObj(leadingWord, &len);
if (i == 1 && len == 11 && !strncmp("-nocomplain", bytes, 11)) {
flags = 0;
haveFlags++;
} else if (i == (2 - flags) && len == 2 && !strncmp("--", bytes, 2)) {
haveFlags++;
} else {
varCount++;
}
} else {
varCount++;
}
TclDecrRefCount(leadingWord);
}
/*
* Issue instructions to unset each of the named variables.
*/
varTokenPtr = TokenAfter(parsePtr->tokenPtr);
for (i=0; i<haveFlags;i++) {
varTokenPtr = TokenAfter(varTokenPtr);
}
for (i=1+haveFlags ; i<parsePtr->numWords ; i++) {
/*
* 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.
*/
PushVarNameWord(interp, varTokenPtr, envPtr, 0,
&localIndex, &isScalar, i);
/*
* Emit instructions to unset the variable.
*/
if (isScalar) {
if (localIndex < 0) {
OP1( UNSET_STK, flags);
} else {
OP14( UNSET_SCALAR, flags, localIndex);
}
} else {
if (localIndex < 0) {
OP1( UNSET_ARRAY_STK, flags);
} else {
OP14( UNSET_ARRAY, flags, localIndex);
}
}
varTokenPtr = TokenAfter(varTokenPtr);
}
PUSH("");
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclCompileWhileCmd --
*
* Procedure called to compile the "while" 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 "while" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileWhileCmd(
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_Token *testTokenPtr, *bodyTokenPtr;
JumpFixup jumpEvalCondFixup;
int testCodeOffset, bodyCodeOffset, jumpDist, range, code, boolVal;
int loopMayEnd = 1; /* This is set to 0 if it is recognized as an
* infinite loop. */
Tcl_Obj *boolObj;
if (parsePtr->numWords != 3) {
return TCL_ERROR;
}
/*
* 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]
*/
testTokenPtr = TokenAfter(parsePtr->tokenPtr);
bodyTokenPtr = TokenAfter(testTokenPtr);
if ((testTokenPtr->type != TCL_TOKEN_SIMPLE_WORD)
|| (bodyTokenPtr->type != TCL_TOKEN_SIMPLE_WORD)) {
return TCL_ERROR;
}
/*
* Find out if the condition is a constant.
*/
boolObj = Tcl_NewStringObj(testTokenPtr[1].start, testTokenPtr[1].size);
Tcl_IncrRefCount(boolObj);
code = Tcl_GetBooleanFromObj(NULL, boolObj, &boolVal);
TclDecrRefCount(boolObj);
if (code == TCL_OK) {
if (boolVal) {
/*
* It is an infinite loop; flag it so that we generate a more
* efficient body.
*/
loopMayEnd = 0;
} else {
/*
* This is an empty loop: "while 0 {...}" or such. Compile no
* bytecodes.
*/
goto pushResult;
}
}
/*
* Create a ExceptionRange record for the loop body. This is used to
* implement break and continue.
*/
range = TclCreateExceptRange(LOOP_EXCEPTION_RANGE, envPtr);
/*
* 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
*/
if (loopMayEnd) {
TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP,
&jumpEvalCondFixup);
testCodeOffset = 0; /* Avoid compiler warning. */
} else {
/*
* Make sure that the first command in the body is preceded by an
* INST_START_CMD, and hence counted properly. [Bug 1752146]
*/
envPtr->atCmdStart &= ~1;
testCodeOffset = CurrentOffset(envPtr);
}
/*
* Compile the loop body.
*/
bodyCodeOffset = ExceptionRangeStarts(envPtr, range);
if (!loopMayEnd) {
envPtr->exceptArrayPtr[range].continueOffset = testCodeOffset;
envPtr->exceptArrayPtr[range].codeOffset = bodyCodeOffset;
}
BODY(bodyTokenPtr, 2);
ExceptionRangeEnds(envPtr, range);
OP( POP);
/*
* Compile the test expression then emit the conditional jump that
* terminates the while. We already know it's a simple word.
*/
if (loopMayEnd) {
testCodeOffset = CurrentOffset(envPtr);
jumpDist = testCodeOffset - jumpEvalCondFixup.codeOffset;
if (TclFixupForwardJump(envPtr, &jumpEvalCondFixup, jumpDist, 127)) {
bodyCodeOffset += 3;
testCodeOffset += 3;
}
SetLineInformation(1);
TclCompileExprWords(interp, testTokenPtr, 1, envPtr);
jumpDist = CurrentOffset(envPtr) - bodyCodeOffset;
if (jumpDist > 127) {
TclEmitInstInt4(INST_JUMP_TRUE4, -jumpDist, envPtr);
} else {
TclEmitInstInt1(INST_JUMP_TRUE1, -jumpDist, envPtr);
}
} else {
jumpDist = CurrentOffset(envPtr) - bodyCodeOffset;
if (jumpDist > 127) {
TclEmitInstInt4(INST_JUMP4, -jumpDist, envPtr);
} else {
TclEmitInstInt1(INST_JUMP1, -jumpDist, envPtr);
}
}
/*
* Set the loop's body, continue and break offsets.
*/
envPtr->exceptArrayPtr[range].continueOffset = testCodeOffset;
envPtr->exceptArrayPtr[range].codeOffset = bodyCodeOffset;
ExceptionRangeTarget(envPtr, range, breakOffset);
TclFinalizeLoopExceptionRange(envPtr, range);
/*
* The while command's result is an empty string.
*/
pushResult:
PUSH("");
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclCompileYieldCmd --
*
* Procedure called to compile the "yield" 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 "yield" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileYieldCmd(
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. */
{
if (parsePtr->numWords < 1 || parsePtr->numWords > 2) {
return TCL_ERROR;
}
if (parsePtr->numWords == 1) {
PUSH("");
} else {
DefineLineInformation; /* TIP #280 */
Tcl_Token *valueTokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, valueTokenPtr, interp, 1);
}
OP( YIELD);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclCompileYieldToCmd --
*
* Procedure called to compile the "yieldto" 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 "yieldto" command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileYieldToCmd(
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_Token *tokenPtr = TokenAfter(parsePtr->tokenPtr);
int i;
if (parsePtr->numWords < 2) {
return TCL_ERROR;
}
OP( NS_CURRENT);
for (i = 1 ; i < parsePtr->numWords ; i++) {
CompileWord(envPtr, tokenPtr, interp, i);
tokenPtr = TokenAfter(tokenPtr);
}
OP4( LIST, i);
OP( YIELD_TO_INVOKE);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* CompileUnaryOpCmd --
*
* Utility routine to compile the unary operator commands.
*
* 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 compiled command at
* runtime.
*
*----------------------------------------------------------------------
*/
static int
CompileUnaryOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
int instruction,
CompileEnv *envPtr)
{
DefineLineInformation; /* TIP #280 */
Tcl_Token *tokenPtr;
if (parsePtr->numWords != 2) {
return TCL_ERROR;
}
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
TclEmitOpcode(instruction, envPtr);
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* CompileAssociativeBinaryOpCmd --
*
* Utility routine to compile the binary operator commands that accept an
* arbitrary number of arguments, and that are associative operations.
* Because of the associativity, we may combine operations from right to
* left, saving us any effort of re-ordering the arguments on the stack
* after substitutions are completed.
*
* 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 compiled command at
* runtime.
*
*----------------------------------------------------------------------
*/
static int
CompileAssociativeBinaryOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
const char *identity,
int instruction,
CompileEnv *envPtr)
{
DefineLineInformation; /* TIP #280 */
Tcl_Token *tokenPtr = parsePtr->tokenPtr;
int words;
/* TODO: Consider support for compiling expanded args. */
for (words=1 ; words<parsePtr->numWords ; words++) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, words);
}
if (parsePtr->numWords <= 2) {
PushLiteral(envPtr, identity, -1);
words++;
}
if (words > 3) {
/*
* Reverse order of arguments to get precise agreement with [expr] in
* calcuations, including roundoff errors.
*/
OP4( REVERSE, words-1);
}
while (--words > 1) {
TclEmitOpcode(instruction, envPtr);
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* CompileStrictlyBinaryOpCmd --
*
* Utility routine to compile the binary operator commands, that strictly
* accept exactly two arguments.
*
* 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 compiled command at
* runtime.
*
*----------------------------------------------------------------------
*/
static int
CompileStrictlyBinaryOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
int instruction,
CompileEnv *envPtr)
{
if (parsePtr->numWords != 3) {
return TCL_ERROR;
}
return CompileAssociativeBinaryOpCmd(interp, parsePtr,
NULL, instruction, envPtr);
}
/*
*----------------------------------------------------------------------
*
* CompileComparisonOpCmd --
*
* Utility routine to compile the n-ary comparison operator commands.
*
* 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 compiled command at
* runtime.
*
*----------------------------------------------------------------------
*/
static int
CompileComparisonOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
int instruction,
CompileEnv *envPtr)
{
DefineLineInformation; /* TIP #280 */
Tcl_Token *tokenPtr;
/* TODO: Consider support for compiling expanded args. */
if (parsePtr->numWords < 3) {
PUSH("1");
} else if (parsePtr->numWords == 3) {
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
TclEmitOpcode(instruction, envPtr);
} else if (envPtr->procPtr == NULL) {
/*
* No local variable space!
*/
return TCL_ERROR;
} else {
int tmpIndex = AnonymousLocal(envPtr);
int words;
tokenPtr = TokenAfter(parsePtr->tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 1);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, 2);
STORE(tmpIndex);
TclEmitOpcode(instruction, envPtr);
for (words=3 ; words<parsePtr->numWords ;) {
LOAD(tmpIndex);
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, words);
if (++words < parsePtr->numWords) {
STORE(tmpIndex);
}
TclEmitOpcode(instruction, envPtr);
}
for (; words>3 ; words--) {
OP( BITAND);
}
/*
* Drop the value from the temp variable; retaining that reference
* might be expensive elsewhere.
*/
OP14( UNSET_SCALAR, 0, tmpIndex);
}
return TCL_OK;
}
/*
*----------------------------------------------------------------------
*
* TclCompile*OpCmd --
*
* Procedures called to compile the corresponding "::tcl::mathop::*"
* commands. These are all wrappers around the utility operator command
* compiler functions, except for the compilers for subtraction and
* division, which are special.
*
* 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 compiled command at
* runtime.
*
*----------------------------------------------------------------------
*/
int
TclCompileInvertOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileUnaryOpCmd(interp, parsePtr, INST_BITNOT, envPtr);
}
int
TclCompileNotOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileUnaryOpCmd(interp, parsePtr, INST_LNOT, envPtr);
}
int
TclCompileAddOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileAssociativeBinaryOpCmd(interp, parsePtr, "0", INST_ADD,
envPtr);
}
int
TclCompileMulOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileAssociativeBinaryOpCmd(interp, parsePtr, "1", INST_MULT,
envPtr);
}
int
TclCompileAndOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileAssociativeBinaryOpCmd(interp, parsePtr, "-1", INST_BITAND,
envPtr);
}
int
TclCompileOrOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileAssociativeBinaryOpCmd(interp, parsePtr, "0", INST_BITOR,
envPtr);
}
int
TclCompileXorOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileAssociativeBinaryOpCmd(interp, parsePtr, "0", INST_BITXOR,
envPtr);
}
int
TclCompilePowOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
DefineLineInformation; /* TIP #280 */
Tcl_Token *tokenPtr = parsePtr->tokenPtr;
int words;
/*
* This one has its own implementation because the ** operator is the only
* one with right associativity.
*/
for (words=1 ; words<parsePtr->numWords ; words++) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, words);
}
if (parsePtr->numWords <= 2) {
PUSH("1");
words++;
}
while (--words > 1) {
TclEmitOpcode(INST_EXPON, envPtr);
}
return TCL_OK;
}
int
TclCompileLshiftOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_LSHIFT, envPtr);
}
int
TclCompileRshiftOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_RSHIFT, envPtr);
}
int
TclCompileModOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_MOD, envPtr);
}
int
TclCompileNeqOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_NEQ, envPtr);
}
int
TclCompileStrneqOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_STR_NEQ, envPtr);
}
int
TclCompileInOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_LIST_IN, envPtr);
}
int
TclCompileNiOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileStrictlyBinaryOpCmd(interp, parsePtr, INST_LIST_NOT_IN,
envPtr);
}
int
TclCompileLessOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileComparisonOpCmd(interp, parsePtr, INST_LT, envPtr);
}
int
TclCompileLeqOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileComparisonOpCmd(interp, parsePtr, INST_LE, envPtr);
}
int
TclCompileGreaterOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileComparisonOpCmd(interp, parsePtr, INST_GT, envPtr);
}
int
TclCompileGeqOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileComparisonOpCmd(interp, parsePtr, INST_GE, envPtr);
}
int
TclCompileEqOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileComparisonOpCmd(interp, parsePtr, INST_EQ, envPtr);
}
int
TclCompileStreqOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
return CompileComparisonOpCmd(interp, parsePtr, INST_STR_EQ, envPtr);
}
int
TclCompileMinusOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
DefineLineInformation; /* TIP #280 */
Tcl_Token *tokenPtr = parsePtr->tokenPtr;
int words;
/* TODO: Consider support for compiling expanded args. */
if (parsePtr->numWords == 1) {
/*
* Fallback to direct eval to report syntax error.
*/
return TCL_ERROR;
}
for (words=1 ; words<parsePtr->numWords ; words++) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, words);
}
if (words == 2) {
TclEmitOpcode(INST_UMINUS, envPtr);
return TCL_OK;
}
if (words == 3) {
TclEmitOpcode(INST_SUB, envPtr);
return TCL_OK;
}
/*
* Reverse order of arguments to get precise agreement with [expr] in
* calcuations, including roundoff errors.
*/
TclEmitInstInt4(INST_REVERSE, words-1, envPtr);
while (--words > 1) {
TclEmitInstInt4(INST_REVERSE, 2, envPtr);
TclEmitOpcode(INST_SUB, envPtr);
}
return TCL_OK;
}
int
TclCompileDivOpCmd(
Tcl_Interp *interp,
Tcl_Parse *parsePtr,
Command *cmdPtr, /* Points to defintion of command being
* compiled. */
CompileEnv *envPtr)
{
DefineLineInformation; /* TIP #280 */
Tcl_Token *tokenPtr = parsePtr->tokenPtr;
int words;
/* TODO: Consider support for compiling expanded args. */
if (parsePtr->numWords == 1) {
/*
* Fallback to direct eval to report syntax error.
*/
return TCL_ERROR;
}
if (parsePtr->numWords == 2) {
PUSH("1.0");
}
for (words=1 ; words<parsePtr->numWords ; words++) {
tokenPtr = TokenAfter(tokenPtr);
CompileWord(envPtr, tokenPtr, interp, words);
}
if (words <= 3) {
TclEmitOpcode(INST_DIV, envPtr);
return TCL_OK;
}
/*
* Reverse order of arguments to get precise agreement with [expr] in
* calcuations, including roundoff errors.
*/
TclEmitInstInt4(INST_REVERSE, words-1, envPtr);
while (--words > 1) {
TclEmitInstInt4(INST_REVERSE, 2, envPtr);
TclEmitOpcode(INST_DIV, envPtr);
}
return TCL_OK;
}
/*
* Local Variables:
* mode: c
* c-basic-offset: 4
* fill-column: 78
* End:
*/