diff options
author | rjohnson <rjohnson> | 1998-03-26 14:45:59 (GMT) |
---|---|---|
committer | rjohnson <rjohnson> | 1998-03-26 14:45:59 (GMT) |
commit | 2b5738da524e944cda39e24c0a87b745a43bd8c3 (patch) | |
tree | 6e8c9473978f6dab66c601e911721a7bd9d70b1b /generic/tclCompile.c | |
parent | c6a259aeeca4814a97cf6694814c63e74e4e18fa (diff) | |
download | tcl-2b5738da524e944cda39e24c0a87b745a43bd8c3.zip tcl-2b5738da524e944cda39e24c0a87b745a43bd8c3.tar.gz tcl-2b5738da524e944cda39e24c0a87b745a43bd8c3.tar.bz2 |
Initial revision
Diffstat (limited to 'generic/tclCompile.c')
-rw-r--r-- | generic/tclCompile.c | 7745 |
1 files changed, 7745 insertions, 0 deletions
diff --git a/generic/tclCompile.c b/generic/tclCompile.c new file mode 100644 index 0000000..3291b3d --- /dev/null +++ b/generic/tclCompile.c @@ -0,0 +1,7745 @@ +/* + * tclCompile.c -- + * + * This file contains procedures that compile Tcl commands or parts + * of commands (like quoted strings or nested sub-commands) into a + * sequence of instructions ("bytecodes"). + * + * Copyright (c) 1996-1997 Sun Microsystems, Inc. + * + * See the file "license.terms" for information on usage and redistribution + * of this file, and for a DISCLAIMER OF ALL WARRANTIES. + * + * SCCS: @(#) tclCompile.c 1.80 97/09/18 18:23:30 + */ + +#include "tclInt.h" +#include "tclCompile.h" + +/* + * Variable that controls whether compilation tracing is enabled and, if so, + * what level of tracing is desired: + * 0: no compilation tracing + * 1: summarize compilation of top level cmds and proc bodies + * 2: display all instructions of each ByteCode compiled + * This variable is linked to the Tcl variable "tcl_traceCompile". + */ + +int tclTraceCompile = 0; +static int traceInitialized = 0; + +/* + * Count of the number of compilations and various other compilation- + * related statistics. + */ + +#ifdef TCL_COMPILE_STATS +long tclNumCompilations = 0; +double tclTotalSourceBytes = 0.0; +double tclTotalCodeBytes = 0.0; + +double tclTotalInstBytes = 0.0; +double tclTotalObjBytes = 0.0; +double tclTotalExceptBytes = 0.0; +double tclTotalAuxBytes = 0.0; +double tclTotalCmdMapBytes = 0.0; + +double tclCurrentSourceBytes = 0.0; +double tclCurrentCodeBytes = 0.0; + +int tclSourceCount[32]; +int tclByteCodeCount[32]; +#endif /* TCL_COMPILE_STATS */ + +/* + * A table describing the Tcl bytecode instructions. The entries in this + * table must correspond to the list of instructions in tclInt.h. The names + * "op1" and "op4" refer to an instruction's one or four byte first operand. + * Similarly, "stktop" and "stknext" refer to the topmost and next to + * topmost stack elements. + * + * Note that the load, store, and incr instructions do not distinguish local + * from global variables; the bytecode interpreter at runtime uses the + * existence of a procedure call frame to distinguish these. + */ + +InstructionDesc instructionTable[] = { + /* Name Bytes #Opnds Operand types Stack top, next */ + {"done", 1, 0, {OPERAND_NONE}}, + /* Finish ByteCode execution and return stktop (top stack item) */ + {"push1", 2, 1, {OPERAND_UINT1}}, + /* Push object at ByteCode objArray[op1] */ + {"push4", 5, 1, {OPERAND_UINT4}}, + /* Push object at ByteCode objArray[op4] */ + {"pop", 1, 0, {OPERAND_NONE}}, + /* Pop the topmost stack object */ + {"dup", 1, 0, {OPERAND_NONE}}, + /* Duplicate the topmost stack object and push the result */ + {"concat1", 2, 1, {OPERAND_UINT1}}, + /* Concatenate the top op1 items and push result */ + {"invokeStk1", 2, 1, {OPERAND_UINT1}}, + /* Invoke command named objv[0]; <objc,objv> = <op1,top op1> */ + {"invokeStk4", 5, 1, {OPERAND_UINT4}}, + /* Invoke command named objv[0]; <objc,objv> = <op4,top op4> */ + {"evalStk", 1, 0, {OPERAND_NONE}}, + /* Evaluate command in stktop using Tcl_EvalObj. */ + {"exprStk", 1, 0, {OPERAND_NONE}}, + /* Execute expression in stktop using Tcl_ExprStringObj. */ + + {"loadScalar1", 2, 1, {OPERAND_UINT1}}, + /* Load scalar variable at index op1 <= 255 in call frame */ + {"loadScalar4", 5, 1, {OPERAND_UINT4}}, + /* Load scalar variable at index op1 >= 256 in call frame */ + {"loadScalarStk", 1, 0, {OPERAND_NONE}}, + /* Load scalar variable; scalar's name is stktop */ + {"loadArray1", 2, 1, {OPERAND_UINT1}}, + /* Load array element; array at slot op1<=255, element is stktop */ + {"loadArray4", 5, 1, {OPERAND_UINT4}}, + /* Load array element; array at slot op1 > 255, element is stktop */ + {"loadArrayStk", 1, 0, {OPERAND_NONE}}, + /* Load array element; element is stktop, array name is stknext */ + {"loadStk", 1, 0, {OPERAND_NONE}}, + /* Load general variable; unparsed variable name is stktop */ + {"storeScalar1", 2, 1, {OPERAND_UINT1}}, + /* Store scalar variable at op1<=255 in frame; value is stktop */ + {"storeScalar4", 5, 1, {OPERAND_UINT4}}, + /* Store scalar variable at op1 > 255 in frame; value is stktop */ + {"storeScalarStk", 1, 0, {OPERAND_NONE}}, + /* Store scalar; value is stktop, scalar name is stknext */ + {"storeArray1", 2, 1, {OPERAND_UINT1}}, + /* Store array element; array at op1<=255, value is top then elem */ + {"storeArray4", 5, 1, {OPERAND_UINT4}}, + /* Store array element; array at op1>=256, value is top then elem */ + {"storeArrayStk", 1, 0, {OPERAND_NONE}}, + /* Store array element; value is stktop, then elem, array names */ + {"storeStk", 1, 0, {OPERAND_NONE}}, + /* Store general variable; value is stktop, then unparsed name */ + + {"incrScalar1", 2, 1, {OPERAND_UINT1}}, + /* Incr scalar at index op1<=255 in frame; incr amount is stktop */ + {"incrScalarStk", 1, 0, {OPERAND_NONE}}, + /* Incr scalar; incr amount is stktop, scalar's name is stknext */ + {"incrArray1", 2, 1, {OPERAND_UINT1}}, + /* Incr array elem; arr at slot op1<=255, amount is top then elem */ + {"incrArrayStk", 1, 0, {OPERAND_NONE}}, + /* Incr array element; amount is top then elem then array names */ + {"incrStk", 1, 0, {OPERAND_NONE}}, + /* Incr general variable; amount is stktop then unparsed var name */ + {"incrScalar1Imm", 3, 2, {OPERAND_UINT1, OPERAND_INT1}}, + /* Incr scalar at slot op1 <= 255; amount is 2nd operand byte */ + {"incrScalarStkImm", 2, 1, {OPERAND_INT1}}, + /* Incr scalar; scalar name is stktop; incr amount is op1 */ + {"incrArray1Imm", 3, 2, {OPERAND_UINT1, OPERAND_INT1}}, + /* Incr array elem; array at slot op1 <= 255, elem is stktop, + * amount is 2nd operand byte */ + {"incrArrayStkImm", 2, 1, {OPERAND_INT1}}, + /* Incr array element; elem is top then array name, amount is op1 */ + {"incrStkImm", 2, 1, {OPERAND_INT1}}, + /* Incr general variable; unparsed name is top, amount is op1 */ + + {"jump1", 2, 1, {OPERAND_INT1}}, + /* Jump relative to (pc + op1) */ + {"jump4", 5, 1, {OPERAND_INT4}}, + /* Jump relative to (pc + op4) */ + {"jumpTrue1", 2, 1, {OPERAND_INT1}}, + /* Jump relative to (pc + op1) if stktop expr object is true */ + {"jumpTrue4", 5, 1, {OPERAND_INT4}}, + /* Jump relative to (pc + op4) if stktop expr object is true */ + {"jumpFalse1", 2, 1, {OPERAND_INT1}}, + /* Jump relative to (pc + op1) if stktop expr object is false */ + {"jumpFalse4", 5, 1, {OPERAND_INT4}}, + /* Jump relative to (pc + op4) if stktop expr object is false */ + + {"lor", 1, 0, {OPERAND_NONE}}, + /* Logical or: push (stknext || stktop) */ + {"land", 1, 0, {OPERAND_NONE}}, + /* Logical and: push (stknext && stktop) */ + {"bitor", 1, 0, {OPERAND_NONE}}, + /* Bitwise or: push (stknext | stktop) */ + {"bitxor", 1, 0, {OPERAND_NONE}}, + /* Bitwise xor push (stknext ^ stktop) */ + {"bitand", 1, 0, {OPERAND_NONE}}, + /* Bitwise and: push (stknext & stktop) */ + {"eq", 1, 0, {OPERAND_NONE}}, + /* Equal: push (stknext == stktop) */ + {"neq", 1, 0, {OPERAND_NONE}}, + /* Not equal: push (stknext != stktop) */ + {"lt", 1, 0, {OPERAND_NONE}}, + /* Less: push (stknext < stktop) */ + {"gt", 1, 0, {OPERAND_NONE}}, + /* Greater: push (stknext || stktop) */ + {"le", 1, 0, {OPERAND_NONE}}, + /* Logical or: push (stknext || stktop) */ + {"ge", 1, 0, {OPERAND_NONE}}, + /* Logical or: push (stknext || stktop) */ + {"lshift", 1, 0, {OPERAND_NONE}}, + /* Left shift: push (stknext << stktop) */ + {"rshift", 1, 0, {OPERAND_NONE}}, + /* Right shift: push (stknext >> stktop) */ + {"add", 1, 0, {OPERAND_NONE}}, + /* Add: push (stknext + stktop) */ + {"sub", 1, 0, {OPERAND_NONE}}, + /* Sub: push (stkext - stktop) */ + {"mult", 1, 0, {OPERAND_NONE}}, + /* Multiply: push (stknext * stktop) */ + {"div", 1, 0, {OPERAND_NONE}}, + /* Divide: push (stknext / stktop) */ + {"mod", 1, 0, {OPERAND_NONE}}, + /* Mod: push (stknext % stktop) */ + {"uplus", 1, 0, {OPERAND_NONE}}, + /* Unary plus: push +stktop */ + {"uminus", 1, 0, {OPERAND_NONE}}, + /* Unary minus: push -stktop */ + {"bitnot", 1, 0, {OPERAND_NONE}}, + /* Bitwise not: push ~stktop */ + {"not", 1, 0, {OPERAND_NONE}}, + /* Logical not: push !stktop */ + {"callBuiltinFunc1", 2, 1, {OPERAND_UINT1}}, + /* Call builtin math function with index op1; any args are on stk */ + {"callFunc1", 2, 1, {OPERAND_UINT1}}, + /* Call non-builtin func objv[0]; <objc,objv>=<op1,top op1> */ + {"tryCvtToNumeric", 1, 0, {OPERAND_NONE}}, + /* Try converting stktop to first int then double if possible. */ + + {"break", 1, 0, {OPERAND_NONE}}, + /* Abort closest enclosing loop; if none, return TCL_BREAK code. */ + {"continue", 1, 0, {OPERAND_NONE}}, + /* Skip to next iteration of closest enclosing loop; if none, + * return TCL_CONTINUE code. */ + + {"foreach_start4", 5, 1, {OPERAND_UINT4}}, + /* Initialize execution of a foreach loop. Operand is aux data index + * of the ForeachInfo structure for the foreach command. */ + {"foreach_step4", 5, 1, {OPERAND_UINT4}}, + /* "Step" or begin next iteration of foreach loop. Push 0 if to + * terminate loop, else push 1. */ + + {"beginCatch4", 5, 1, {OPERAND_UINT4}}, + /* Record start of catch with the operand's exception range index. + * Push the current stack depth onto a special catch stack. */ + {"endCatch", 1, 0, {OPERAND_NONE}}, + /* End of last catch. Pop the bytecode interpreter's catch stack. */ + {"pushResult", 1, 0, {OPERAND_NONE}}, + /* Push the interpreter's object result onto the stack. */ + {"pushReturnCode", 1, 0, {OPERAND_NONE}}, + /* Push interpreter's return code (e.g. TCL_OK or TCL_ERROR) as + * a new object onto the stack. */ + {0} +}; + +/* + * The following table assigns a type to each character. Only types + * meaningful to Tcl parsing are represented here. The table is + * designed to be referenced with either signed or unsigned characters, + * so it has 384 entries. The first 128 entries correspond to negative + * character values, the next 256 correspond to positive character + * values. The last 128 entries are identical to the first 128. The + * table is always indexed with a 128-byte offset (the 128th entry + * corresponds to a 0 character value). + */ + +unsigned char tclTypeTable[] = { + /* + * Negative character values, from -128 to -1: + */ + + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + + /* + * Positive character values, from 0-127: + */ + + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_SPACE, TCL_COMMAND_END, TCL_SPACE, + TCL_SPACE, TCL_SPACE, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_SPACE, TCL_NORMAL, TCL_QUOTE, TCL_NORMAL, + TCL_DOLLAR, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_COMMAND_END, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_OPEN_BRACKET, + TCL_BACKSLASH, TCL_COMMAND_END, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_OPEN_BRACE, + TCL_NORMAL, TCL_CLOSE_BRACE, TCL_NORMAL, TCL_NORMAL, + + /* + * Large unsigned character values, from 128-255: + */ + + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, + TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, TCL_NORMAL, +}; + +/* + * Prototypes for procedures defined later in this file: + */ + +static void AdvanceToNextWord _ANSI_ARGS_((char *string, + CompileEnv *envPtr)); +static int CollectArgInfo _ANSI_ARGS_((Tcl_Interp *interp, + char *string, char *lastChar, int flags, + ArgInfo *argInfoPtr)); +static int CompileBraces _ANSI_ARGS_((Tcl_Interp *interp, + char *string, char *lastChar, int flags, + CompileEnv *envPtr)); +static int CompileCmdWordInline _ANSI_ARGS_(( + Tcl_Interp *interp, char *string, + char *lastChar, int flags, CompileEnv *envPtr)); +static int CompileExprWord _ANSI_ARGS_((Tcl_Interp *interp, + char *string, char *lastChar, int flags, + CompileEnv *envPtr)); +static int CompileMultipartWord _ANSI_ARGS_(( + Tcl_Interp *interp, char *string, + char *lastChar, int flags, CompileEnv *envPtr)); +static int CompileWord _ANSI_ARGS_((Tcl_Interp *interp, + char *string, char *lastChar, int flags, + CompileEnv *envPtr)); +static int CreateExceptionRange _ANSI_ARGS_(( + ExceptionRangeType type, CompileEnv *envPtr)); +static void DupByteCodeInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr, + Tcl_Obj *copyPtr)); +static ClientData DupForeachInfo _ANSI_ARGS_((ClientData clientData)); +static unsigned char * EncodeCmdLocMap _ANSI_ARGS_(( + CompileEnv *envPtr, ByteCode *codePtr, + unsigned char *startPtr)); +static void EnterCmdExtentData _ANSI_ARGS_(( + CompileEnv *envPtr, int cmdNumber, + int numSrcChars, int numCodeBytes)); +static void EnterCmdStartData _ANSI_ARGS_(( + CompileEnv *envPtr, int cmdNumber, + int srcOffset, int codeOffset)); +static void ExpandObjectArray _ANSI_ARGS_((CompileEnv *envPtr)); +static void FreeForeachInfo _ANSI_ARGS_(( + ClientData clientData)); +static void FreeByteCodeInternalRep _ANSI_ARGS_(( + Tcl_Obj *objPtr)); +static void FreeArgInfo _ANSI_ARGS_((ArgInfo *argInfoPtr)); +static int GetCmdLocEncodingSize _ANSI_ARGS_(( + CompileEnv *envPtr)); +static void InitArgInfo _ANSI_ARGS_((ArgInfo *argInfoPtr)); +static int LookupCompiledLocal _ANSI_ARGS_(( + char *name, int nameChars, int createIfNew, + int flagsIfCreated, Proc *procPtr)); +static int SetByteCodeFromAny _ANSI_ARGS_((Tcl_Interp *interp, + Tcl_Obj *objPtr)); +static void UpdateStringOfByteCode _ANSI_ARGS_((Tcl_Obj *objPtr)); + +/* + * The structure below defines the bytecode Tcl object type by + * means of procedures that can be invoked by generic object code. + */ + +Tcl_ObjType tclByteCodeType = { + "bytecode", /* name */ + FreeByteCodeInternalRep, /* freeIntRepProc */ + DupByteCodeInternalRep, /* dupIntRepProc */ + UpdateStringOfByteCode, /* updateStringProc */ + SetByteCodeFromAny /* setFromAnyProc */ +}; + +/* + *---------------------------------------------------------------------- + * + * TclPrintByteCodeObj -- + * + * This procedure prints ("disassembles") the instructions of a + * bytecode object to stdout. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +void +TclPrintByteCodeObj(interp, objPtr) + Tcl_Interp *interp; /* Used only for Tcl_GetStringFromObj. */ + Tcl_Obj *objPtr; /* The bytecode object to disassemble. */ +{ + ByteCode* codePtr = (ByteCode *) objPtr->internalRep.otherValuePtr; + unsigned char *codeStart, *codeLimit, *pc; + unsigned char *codeDeltaNext, *codeLengthNext; + unsigned char *srcDeltaNext, *srcLengthNext; + int codeOffset, codeLen, srcOffset, srcLen; + int numCmds, numObjs, delta, objBytes, i; + + if (codePtr->refCount <= 0) { + return; /* already freed */ + } + + codeStart = codePtr->codeStart; + codeLimit = (codeStart + codePtr->numCodeBytes); + numCmds = codePtr->numCommands; + numObjs = codePtr->numObjects; + + objBytes = (numObjs * sizeof(Tcl_Obj)); + for (i = 0; i < numObjs; i++) { + Tcl_Obj *litObjPtr = codePtr->objArrayPtr[i]; + if (litObjPtr->bytes != NULL) { + objBytes += litObjPtr->length; + } + } + + /* + * Print header lines describing the ByteCode. + */ + + fprintf(stdout, "\nByteCode 0x%x, ref ct %u, epoch %u, interp 0x%x(epoch %u)\n", + (unsigned int) codePtr, codePtr->refCount, + codePtr->compileEpoch, (unsigned int) codePtr->iPtr, + codePtr->iPtr->compileEpoch); + fprintf(stdout, " Source "); + TclPrintSource(stdout, codePtr->source, + TclMin(codePtr->numSrcChars, 70)); + fprintf(stdout, "\n Cmds %d, chars %d, inst %d, objs %u, aux %d, stk depth %u, code/src %.2f\n", + numCmds, codePtr->numSrcChars, codePtr->numCodeBytes, numObjs, + codePtr->numAuxDataItems, codePtr->maxStackDepth, + (codePtr->numSrcChars? + ((float)codePtr->totalSize)/((float)codePtr->numSrcChars) : 0.0)); + fprintf(stdout, " Code %d = %d(header)+%d(inst)+%d(objs)+%d(exc)+%d(aux)+%d(cmd map)\n", + codePtr->totalSize, sizeof(ByteCode), codePtr->numCodeBytes, + objBytes, (codePtr->numExcRanges * sizeof(ExceptionRange)), + (codePtr->numAuxDataItems * sizeof(AuxData)), + codePtr->numCmdLocBytes); + + /* + * If the ByteCode is the compiled body of a Tcl procedure, print + * information about that procedure. Note that we don't know the + * procedure's name since ByteCode's can be shared among procedures. + */ + + if (codePtr->procPtr != NULL) { + Proc *procPtr = codePtr->procPtr; + int numCompiledLocals = procPtr->numCompiledLocals; + fprintf(stdout, + " Proc 0x%x, ref ct %d, args %d, compiled locals %d\n", + (unsigned int) procPtr, procPtr->refCount, procPtr->numArgs, + numCompiledLocals); + if (numCompiledLocals > 0) { + CompiledLocal *localPtr = procPtr->firstLocalPtr; + for (i = 0; i < numCompiledLocals; i++) { + fprintf(stdout, " %d: slot %d%s%s%s%s%s", + i, localPtr->frameIndex, + ((localPtr->flags & VAR_SCALAR)? ", scalar" : ""), + ((localPtr->flags & VAR_ARRAY)? ", array" : ""), + ((localPtr->flags & VAR_LINK)? ", link" : ""), + (localPtr->isArg? ", arg" : ""), + (localPtr->isTemp? ", temp" : "")); + if (localPtr->isTemp) { + fprintf(stdout, "\n"); + } else { + fprintf(stdout, ", name=\"%s\"\n", localPtr->name); + } + localPtr = localPtr->nextPtr; + } + } + } + + /* + * Print the ExceptionRange array. + */ + + if (codePtr->numExcRanges > 0) { + fprintf(stdout, " Exception ranges %d, depth %d:\n", + codePtr->numExcRanges, codePtr->maxExcRangeDepth); + for (i = 0; i < codePtr->numExcRanges; i++) { + ExceptionRange *rangePtr = &(codePtr->excRangeArrayPtr[i]); + fprintf(stdout, " %d: level %d, %s, pc %d-%d, ", + i, rangePtr->nestingLevel, + ((rangePtr->type == LOOP_EXCEPTION_RANGE)? "loop":"catch"), + rangePtr->codeOffset, + (rangePtr->codeOffset + rangePtr->numCodeBytes - 1)); + switch (rangePtr->type) { + case LOOP_EXCEPTION_RANGE: + fprintf(stdout, "continue %d, break %d\n", + rangePtr->continueOffset, rangePtr->breakOffset); + break; + case CATCH_EXCEPTION_RANGE: + fprintf(stdout, "catch %d\n", rangePtr->catchOffset); + break; + default: + panic("TclPrintSource: unrecognized ExceptionRange type %d\n", + rangePtr->type); + } + } + } + + /* + * If there were no commands (e.g., an expression or an empty string + * was compiled), just print all instructions and return. + */ + + if (numCmds == 0) { + pc = codeStart; + while (pc < codeLimit) { + fprintf(stdout, " "); + pc += TclPrintInstruction(codePtr, pc); + } + return; + } + + /* + * Print table showing the code offset, source offset, and source + * length for each command. These are encoded as a sequence of bytes. + */ + + fprintf(stdout, " Commands %d:", numCmds); + codeDeltaNext = codePtr->codeDeltaStart; + codeLengthNext = codePtr->codeLengthStart; + srcDeltaNext = codePtr->srcDeltaStart; + srcLengthNext = codePtr->srcLengthStart; + codeOffset = srcOffset = 0; + for (i = 0; i < numCmds; i++) { + if ((unsigned int) (*codeDeltaNext) == (unsigned int) 0xFF) { + codeDeltaNext++; + delta = TclGetInt4AtPtr(codeDeltaNext); + codeDeltaNext += 4; + } else { + delta = TclGetInt1AtPtr(codeDeltaNext); + codeDeltaNext++; + } + codeOffset += delta; + + if ((unsigned int) (*codeLengthNext) == (unsigned int) 0xFF) { + codeLengthNext++; + codeLen = TclGetInt4AtPtr(codeLengthNext); + codeLengthNext += 4; + } else { + codeLen = TclGetInt1AtPtr(codeLengthNext); + codeLengthNext++; + } + + if ((unsigned int) (*srcDeltaNext) == (unsigned int) 0xFF) { + srcDeltaNext++; + delta = TclGetInt4AtPtr(srcDeltaNext); + srcDeltaNext += 4; + } else { + delta = TclGetInt1AtPtr(srcDeltaNext); + srcDeltaNext++; + } + srcOffset += delta; + + if ((unsigned int) (*srcLengthNext) == (unsigned int) 0xFF) { + srcLengthNext++; + srcLen = TclGetInt4AtPtr(srcLengthNext); + srcLengthNext += 4; + } else { + srcLen = TclGetInt1AtPtr(srcLengthNext); + srcLengthNext++; + } + + fprintf(stdout, "%s%4d: pc %d-%d, source %d-%d", + ((i % 2)? " " : "\n "), + (i+1), codeOffset, (codeOffset + codeLen - 1), + srcOffset, (srcOffset + srcLen - 1)); + } + if ((numCmds > 0) && ((numCmds % 2) != 0)) { + fprintf(stdout, "\n"); + } + + /* + * Print each instruction. If the instruction corresponds to the start + * of a command, print the command's source. Note that we don't need + * the code length here. + */ + + codeDeltaNext = codePtr->codeDeltaStart; + srcDeltaNext = codePtr->srcDeltaStart; + srcLengthNext = codePtr->srcLengthStart; + codeOffset = srcOffset = 0; + pc = codeStart; + for (i = 0; i < numCmds; i++) { + if ((unsigned int) (*codeDeltaNext) == (unsigned int) 0xFF) { + codeDeltaNext++; + delta = TclGetInt4AtPtr(codeDeltaNext); + codeDeltaNext += 4; + } else { + delta = TclGetInt1AtPtr(codeDeltaNext); + codeDeltaNext++; + } + codeOffset += delta; + + if ((unsigned int) (*srcDeltaNext) == (unsigned int) 0xFF) { + srcDeltaNext++; + delta = TclGetInt4AtPtr(srcDeltaNext); + srcDeltaNext += 4; + } else { + delta = TclGetInt1AtPtr(srcDeltaNext); + srcDeltaNext++; + } + srcOffset += delta; + + if ((unsigned int) (*srcLengthNext) == (unsigned int) 0xFF) { + srcLengthNext++; + srcLen = TclGetInt4AtPtr(srcLengthNext); + srcLengthNext += 4; + } else { + srcLen = TclGetInt1AtPtr(srcLengthNext); + srcLengthNext++; + } + + /* + * Print instructions before command i. + */ + + while ((pc-codeStart) < codeOffset) { + fprintf(stdout, " "); + pc += TclPrintInstruction(codePtr, pc); + } + + fprintf(stdout, " Command %d: ", (i+1)); + TclPrintSource(stdout, (codePtr->source + srcOffset), + TclMin(srcLen, 70)); + fprintf(stdout, "\n"); + } + if (pc < codeLimit) { + /* + * Print instructions after the last command. + */ + + while (pc < codeLimit) { + fprintf(stdout, " "); + pc += TclPrintInstruction(codePtr, pc); + } + } +} + +/* + *---------------------------------------------------------------------- + * + * TclPrintInstruction -- + * + * This procedure prints ("disassembles") one instruction from a + * bytecode object to stdout. + * + * Results: + * Returns the length in bytes of the current instruiction. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +TclPrintInstruction(codePtr, pc) + ByteCode* codePtr; /* Bytecode containing the instruction. */ + unsigned char *pc; /* Points to first byte of instruction. */ +{ + Proc *procPtr = codePtr->procPtr; + unsigned char opCode = *pc; + register InstructionDesc *instDesc = &instructionTable[opCode]; + unsigned char *codeStart = codePtr->codeStart; + unsigned int pcOffset = (pc - codeStart); + int opnd, elemLen, i, j; + Tcl_Obj *elemPtr; + char *string; + + fprintf(stdout, "(%u) %s ", pcOffset, instDesc->name); + for (i = 0; i < instDesc->numOperands; i++) { + switch (instDesc->opTypes[i]) { + case OPERAND_INT1: + opnd = TclGetInt1AtPtr(pc+1+i); + if ((i == 0) && ((opCode == INST_JUMP1) + || (opCode == INST_JUMP_TRUE1) + || (opCode == INST_JUMP_FALSE1))) { + fprintf(stdout, "%d # pc %u", opnd, (pcOffset + opnd)); + } else { + fprintf(stdout, "%d", opnd); + } + break; + case OPERAND_INT4: + opnd = TclGetInt4AtPtr(pc+1+i); + if ((i == 0) && ((opCode == INST_JUMP4) + || (opCode == INST_JUMP_TRUE4) + || (opCode == INST_JUMP_FALSE4))) { + fprintf(stdout, "%d # pc %u", opnd, (pcOffset + opnd)); + } else { + fprintf(stdout, "%d", opnd); + } + break; + case OPERAND_UINT1: + opnd = TclGetUInt1AtPtr(pc+1+i); + if ((i == 0) && (opCode == INST_PUSH1)) { + elemPtr = codePtr->objArrayPtr[opnd]; + string = Tcl_GetStringFromObj(elemPtr, &elemLen); + fprintf(stdout, "%u # ", (unsigned int) opnd); + TclPrintSource(stdout, string, TclMin(elemLen, 40)); + } else if ((i == 0) && ((opCode == INST_LOAD_SCALAR1) + || (opCode == INST_LOAD_ARRAY1) + || (opCode == INST_STORE_SCALAR1) + || (opCode == INST_STORE_ARRAY1))) { + int localCt = procPtr->numCompiledLocals; + CompiledLocal *localPtr = procPtr->firstLocalPtr; + if (opnd >= localCt) { + panic("TclPrintInstruction: bad local var index %u (%u locals)\n", + (unsigned int) opnd, localCt); + return instDesc->numBytes; + } + for (j = 0; j < opnd; j++) { + localPtr = localPtr->nextPtr; + } + if (localPtr->isTemp) { + fprintf(stdout, "%u # temp var %u", + (unsigned int) opnd, (unsigned int) opnd); + } else { + fprintf(stdout, "%u # var ", (unsigned int) opnd); + TclPrintSource(stdout, localPtr->name, 40); + } + } else { + fprintf(stdout, "%u ", (unsigned int) opnd); + } + break; + case OPERAND_UINT4: + opnd = TclGetUInt4AtPtr(pc+1+i); + if (opCode == INST_PUSH4) { + elemPtr = codePtr->objArrayPtr[opnd]; + string = Tcl_GetStringFromObj(elemPtr, &elemLen); + fprintf(stdout, "%u # ", opnd); + TclPrintSource(stdout, string, TclMin(elemLen, 40)); + } else if ((i == 0) && ((opCode == INST_LOAD_SCALAR4) + || (opCode == INST_LOAD_ARRAY4) + || (opCode == INST_STORE_SCALAR4) + || (opCode == INST_STORE_ARRAY4))) { + int localCt = procPtr->numCompiledLocals; + CompiledLocal *localPtr = procPtr->firstLocalPtr; + if (opnd >= localCt) { + panic("TclPrintInstruction: bad local var index %u (%u locals)\n", + (unsigned int) opnd, localCt); + return instDesc->numBytes; + } + for (j = 0; j < opnd; j++) { + localPtr = localPtr->nextPtr; + } + if (localPtr->isTemp) { + fprintf(stdout, "%u # temp var %u", + (unsigned int) opnd, (unsigned int) opnd); + } else { + fprintf(stdout, "%u # var ", (unsigned int) opnd); + TclPrintSource(stdout, localPtr->name, 40); + } + } else { + fprintf(stdout, "%u ", (unsigned int) opnd); + } + break; + case OPERAND_NONE: + default: + break; + } + } + fprintf(stdout, "\n"); + return instDesc->numBytes; +} + +/* + *---------------------------------------------------------------------- + * + * TclPrintSource -- + * + * This procedure prints up to a specified number of characters from + * the argument string to a specified file. It tries to produce legible + * output by adding backslashes as necessary. + * + * Results: + * None. + * + * Side effects: + * Outputs characters to the specified file. + * + *---------------------------------------------------------------------- + */ + +void +TclPrintSource(outFile, string, maxChars) + FILE *outFile; /* The file to print the source to. */ + char *string; /* The string to print. */ + int maxChars; /* Maximum number of chars to print. */ +{ + register char *p; + register int i = 0; + + if (string == NULL) { + fprintf(outFile, "\"\""); + return; + } + + fprintf(outFile, "\""); + p = string; + for (; (*p != '\0') && (i < maxChars); p++, i++) { + switch (*p) { + case '"': + fprintf(outFile, "\\\""); + continue; + case '\f': + fprintf(outFile, "\\f"); + continue; + case '\n': + fprintf(outFile, "\\n"); + continue; + case '\r': + fprintf(outFile, "\\r"); + continue; + case '\t': + fprintf(outFile, "\\t"); + continue; + case '\v': + fprintf(outFile, "\\v"); + continue; + default: + fprintf(outFile, "%c", *p); + continue; + } + } + fprintf(outFile, "\""); +} + +/* + *---------------------------------------------------------------------- + * + * FreeByteCodeInternalRep -- + * + * Part of the bytecode Tcl object type implementation. Frees the + * storage associated with a bytecode object's internal representation + * unless its code is actively being executed. + * + * Results: + * None. + * + * Side effects: + * The bytecode object's internal rep is marked invalid and its + * code gets freed unless the code is actively being executed. + * In that case the cleanup is delayed until the last execution + * of the code completes. + * + *---------------------------------------------------------------------- + */ + +static void +FreeByteCodeInternalRep(objPtr) + register Tcl_Obj *objPtr; /* Object whose internal rep to free. */ +{ + register ByteCode *codePtr = + (ByteCode *) objPtr->internalRep.otherValuePtr; + + codePtr->refCount--; + if (codePtr->refCount <= 0) { + TclCleanupByteCode(codePtr); + } + objPtr->typePtr = NULL; + objPtr->internalRep.otherValuePtr = NULL; +} + +/* + *---------------------------------------------------------------------- + * + * CleanupByteCode -- + * + * This procedure does all the real work of freeing up a bytecode + * object's ByteCode structure. It's called only when the structure's + * reference count becomes zero. + * + * Results: + * None. + * + * Side effects: + * Frees objPtr's bytecode internal representation and sets + * its type and objPtr->internalRep.otherValuePtr NULL. Also + * decrements the ref counts on each object in its object array, + * and frees its auxiliary data items. + * + *---------------------------------------------------------------------- + */ + +void +TclCleanupByteCode(codePtr) + ByteCode *codePtr; /* ByteCode to free. */ +{ + Tcl_Obj **objArrayPtr = codePtr->objArrayPtr; + int numObjects = codePtr->numObjects; + int numAuxDataItems = codePtr->numAuxDataItems; + register AuxData *auxDataPtr; + register Tcl_Obj *elemPtr; + register int i; + +#ifdef TCL_COMPILE_STATS + tclCurrentSourceBytes -= (double) codePtr->numSrcChars; + tclCurrentCodeBytes -= (double) codePtr->totalSize; +#endif /* TCL_COMPILE_STATS */ + + /* + * A single heap object holds the ByteCode structure and its code, + * object, command location, and auxiliary data arrays. This means we + * only need to 1) decrement the ref counts on the objects in its + * object array, 2) call the free procs for the auxiliary data items, + * and 3) free the ByteCode structure's heap object. + */ + + for (i = 0; i < numObjects; i++) { + elemPtr = objArrayPtr[i]; + TclDecrRefCount(elemPtr); + } + + auxDataPtr = codePtr->auxDataArrayPtr; + for (i = 0; i < numAuxDataItems; i++) { + if (auxDataPtr->freeProc != NULL) { + auxDataPtr->freeProc(auxDataPtr->clientData); + } + auxDataPtr++; + } + + ckfree((char *) codePtr); +} + +/* + *---------------------------------------------------------------------- + * + * DupByteCodeInternalRep -- + * + * Part of the bytecode Tcl object type implementation. However, it + * does not copy the internal representation of a bytecode Tcl_Obj, but + * instead leaves the new object untyped (with a NULL type pointer). + * Code will be compiled for the new object only if necessary. + * + * Results: + * None. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static void +DupByteCodeInternalRep(srcPtr, copyPtr) + Tcl_Obj *srcPtr; /* Object with internal rep to copy. */ + Tcl_Obj *copyPtr; /* Object with internal rep to set. */ +{ + return; +} + +/* + *----------------------------------------------------------------------- + * + * SetByteCodeFromAny -- + * + * Part of the bytecode Tcl object type implementation. Attempts to + * generate an byte code internal form for the Tcl object "objPtr" by + * compiling its string representation. + * + * Results: + * The return value is a standard Tcl object result. If an error occurs + * during compilation, an error message is left in the interpreter's + * result unless "interp" is NULL. + * + * Side effects: + * Frees the old internal representation. If no error occurs, then the + * compiled code is stored as "objPtr"s bytecode representation. + * Also, if debugging, initializes the "tcl_traceCompile" Tcl variable + * used to trace compilations. + * + *---------------------------------------------------------------------- + */ + +static int +SetByteCodeFromAny(interp, objPtr) + Tcl_Interp *interp; /* The interpreter for which the code is + * compiled. */ + Tcl_Obj *objPtr; /* The object to convert. */ +{ + Interp *iPtr = (Interp *) interp; + char *string; + CompileEnv compEnv; /* Compilation environment structure + * allocated in frame. */ + AuxData *auxDataPtr; + register int i; + int length, result; + + if (!traceInitialized) { + if (Tcl_LinkVar(interp, "tcl_traceCompile", + (char *) &tclTraceCompile, TCL_LINK_INT) != TCL_OK) { + panic("SetByteCodeFromAny: unable to create link for tcl_traceCompile variable"); + } + traceInitialized = 1; + } + + string = Tcl_GetStringFromObj(objPtr, &length); + TclInitCompileEnv(interp, &compEnv, string); + result = TclCompileString(interp, string, string+length, + iPtr->evalFlags, &compEnv); + if (result == TCL_OK) { + /* + * Add a "done" instruction at the end of the instruction sequence. + */ + + TclEmitOpcode(INST_DONE, &compEnv); + + /* + * Convert the object to a ByteCode object. + */ + + TclInitByteCodeObj(objPtr, &compEnv); + } else { + /* + * Compilation errors. Decrement the ref counts on any objects in + * the object array and free any aux data items prior to freeing + * the compilation environment. + */ + + for (i = 0; i < compEnv.objArrayNext; i++) { + Tcl_Obj *elemPtr = compEnv.objArrayPtr[i]; + Tcl_DecrRefCount(elemPtr); + } + + auxDataPtr = compEnv.auxDataArrayPtr; + for (i = 0; i < compEnv.auxDataArrayNext; i++) { + if (auxDataPtr->freeProc != NULL) { + auxDataPtr->freeProc(auxDataPtr->clientData); + } + auxDataPtr++; + } + } + TclFreeCompileEnv(&compEnv); + + if (result == TCL_OK) { + if (tclTraceCompile == 2) { + TclPrintByteCodeObj(interp, objPtr); + } + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * UpdateStringOfByteCode -- + * + * Part of the bytecode Tcl object type implementation. Called to + * update the string representation for a byte code object. + * Note: This procedure does not free an existing old string rep + * so storage will be lost if this has not already been done. + * + * Results: + * None. + * + * Side effects: + * Generates a panic. + * + *---------------------------------------------------------------------- + */ + +static void +UpdateStringOfByteCode(objPtr) + register Tcl_Obj *objPtr; /* ByteCode object with string rep that + * needs updating. */ +{ + /* + * This procedure is never invoked since the internal representation of + * a bytecode object is never modified. + */ + + panic("UpdateStringOfByteCode should never be called."); +} + +/* + *---------------------------------------------------------------------- + * + * TclInitCompileEnv -- + * + * Initializes a CompileEnv compilation environment structure for the + * compilation of a string in an interpreter. + * + * Results: + * None. + * + * Side effects: + * The CompileEnv structure is initialized. + * + *---------------------------------------------------------------------- + */ + +void +TclInitCompileEnv(interp, envPtr, string) + Tcl_Interp *interp; /* The interpreter for which a CompileEnv + * structure is initialized. */ + register CompileEnv *envPtr; /* Points to the CompileEnv structure to + * initialize. */ + char *string; /* The source string to be compiled. */ +{ + Interp *iPtr = (Interp *) interp; + + envPtr->iPtr = iPtr; + envPtr->source = string; + envPtr->procPtr = iPtr->compiledProcPtr; + envPtr->numCommands = 0; + envPtr->excRangeDepth = 0; + envPtr->maxExcRangeDepth = 0; + envPtr->maxStackDepth = 0; + Tcl_InitHashTable(&(envPtr->objTable), TCL_STRING_KEYS); + envPtr->pushSimpleWords = 1; + envPtr->wordIsSimple = 0; + envPtr->numSimpleWordChars = 0; + envPtr->exprIsJustVarRef = 0; + envPtr->exprIsComparison = 0; + envPtr->termOffset = 0; + + envPtr->codeStart = envPtr->staticCodeSpace; + envPtr->codeNext = envPtr->codeStart; + envPtr->codeEnd = (envPtr->codeStart + COMPILEENV_INIT_CODE_BYTES); + envPtr->mallocedCodeArray = 0; + + envPtr->objArrayPtr = envPtr->staticObjArraySpace; + envPtr->objArrayNext = 0; + envPtr->objArrayEnd = COMPILEENV_INIT_NUM_OBJECTS; + envPtr->mallocedObjArray = 0; + + envPtr->excRangeArrayPtr = envPtr->staticExcRangeArraySpace; + envPtr->excRangeArrayNext = 0; + envPtr->excRangeArrayEnd = COMPILEENV_INIT_EXCEPT_RANGES; + envPtr->mallocedExcRangeArray = 0; + + envPtr->cmdMapPtr = envPtr->staticCmdMapSpace; + envPtr->cmdMapEnd = COMPILEENV_INIT_CMD_MAP_SIZE; + envPtr->mallocedCmdMap = 0; + + envPtr->auxDataArrayPtr = envPtr->staticAuxDataArraySpace; + envPtr->auxDataArrayNext = 0; + envPtr->auxDataArrayEnd = COMPILEENV_INIT_AUX_DATA_SIZE; + envPtr->mallocedAuxDataArray = 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclFreeCompileEnv -- + * + * Free the storage allocated in a CompileEnv compilation environment + * structure. + * + * Results: + * None. + * + * Side effects: + * Allocated storage in the CompileEnv structure is freed. Note that + * ref counts for Tcl objects in its object table are not decremented. + * In addition, any storage referenced by any auxiliary data items + * in the CompileEnv structure are not freed either. The expectation + * is that when compilation is successful, "ownership" (i.e., the + * pointers to) these objects and aux data items will just be handed + * over to the corresponding ByteCode structure. + * + *---------------------------------------------------------------------- + */ + +void +TclFreeCompileEnv(envPtr) + register CompileEnv *envPtr; /* Points to the CompileEnv structure. */ +{ + Tcl_DeleteHashTable(&(envPtr->objTable)); + if (envPtr->mallocedCodeArray) { + ckfree((char *) envPtr->codeStart); + } + if (envPtr->mallocedObjArray) { + ckfree((char *) envPtr->objArrayPtr); + } + if (envPtr->mallocedExcRangeArray) { + ckfree((char *) envPtr->excRangeArrayPtr); + } + if (envPtr->mallocedCmdMap) { + ckfree((char *) envPtr->cmdMapPtr); + } + if (envPtr->mallocedAuxDataArray) { + ckfree((char *) envPtr->auxDataArrayPtr); + } +} + +/* + *---------------------------------------------------------------------- + * + * TclInitByteCodeObj -- + * + * Create a ByteCode structure and initialize it from a CompileEnv + * compilation environment structure. The ByteCode structure is + * smaller and contains just that information needed to execute + * the bytecode instructions resulting from compiling a Tcl script. + * The resulting structure is placed in the specified object. + * + * Results: + * A newly constructed ByteCode object is stored in the internal + * representation of the objPtr. + * + * Side effects: + * A single heap object is allocated to hold the new ByteCode structure + * and its code, object, command location, and aux data arrays. Note + * that "ownership" (i.e., the pointers to) the Tcl objects and aux + * data items will be handed over to the new ByteCode structure from + * the CompileEnv structure. + * + *---------------------------------------------------------------------- + */ + +void +TclInitByteCodeObj(objPtr, envPtr) + Tcl_Obj *objPtr; /* Points object that should be + * initialized, and whose string rep + * contains the source code. */ + register CompileEnv *envPtr; /* Points to the CompileEnv structure from + * which to create a ByteCode structure. */ +{ + register ByteCode *codePtr; + size_t codeBytes, objArrayBytes, exceptArrayBytes, cmdLocBytes; + size_t auxDataArrayBytes; + register size_t size, objBytes, totalSize; + register unsigned char *p; + unsigned char *nextPtr; + int srcLen = envPtr->termOffset; + int numObjects, i; +#ifdef TCL_COMPILE_STATS + int srcLenLog2, sizeLog2; +#endif /*TCL_COMPILE_STATS*/ + + codeBytes = (envPtr->codeNext - envPtr->codeStart); + numObjects = envPtr->objArrayNext; + objArrayBytes = (envPtr->objArrayNext * sizeof(Tcl_Obj *)); + exceptArrayBytes = (envPtr->excRangeArrayNext * sizeof(ExceptionRange)); + auxDataArrayBytes = (envPtr->auxDataArrayNext * sizeof(AuxData)); + cmdLocBytes = GetCmdLocEncodingSize(envPtr); + + size = sizeof(ByteCode); + size += TCL_ALIGN(codeBytes); /* align object array */ + size += TCL_ALIGN(objArrayBytes); /* align exception range array */ + size += TCL_ALIGN(exceptArrayBytes); /* align AuxData array */ + size += auxDataArrayBytes; + size += cmdLocBytes; + + /* + * Compute the total number of bytes needed for this bytecode + * including the storage for the Tcl objects in its object array. + */ + + objBytes = (numObjects * sizeof(Tcl_Obj)); + for (i = 0; i < numObjects; i++) { + Tcl_Obj *litObjPtr = envPtr->objArrayPtr[i]; + if (litObjPtr->bytes != NULL) { + objBytes += litObjPtr->length; + } + } + totalSize = (size + objBytes); + +#ifdef TCL_COMPILE_STATS + tclNumCompilations++; + tclTotalSourceBytes += (double) srcLen; + tclTotalCodeBytes += (double) totalSize; + + tclTotalInstBytes += (double) codeBytes; + tclTotalObjBytes += (double) objBytes; + tclTotalExceptBytes += exceptArrayBytes; + tclTotalAuxBytes += (double) auxDataArrayBytes; + tclTotalCmdMapBytes += (double) cmdLocBytes; + + tclCurrentSourceBytes += (double) srcLen; + tclCurrentCodeBytes += (double) totalSize; + + srcLenLog2 = TclLog2(srcLen); + sizeLog2 = TclLog2((int) totalSize); + if ((srcLenLog2 > 31) || (sizeLog2 > 31)) { + panic("TclInitByteCodeObj: bad source or code sizes\n"); + } + tclSourceCount[srcLenLog2]++; + tclByteCodeCount[sizeLog2]++; +#endif /* TCL_COMPILE_STATS */ + + p = (unsigned char *) ckalloc(size); + codePtr = (ByteCode *) p; + codePtr->iPtr = envPtr->iPtr; + codePtr->compileEpoch = envPtr->iPtr->compileEpoch; + codePtr->refCount = 1; + codePtr->source = envPtr->source; + codePtr->procPtr = envPtr->procPtr; + codePtr->totalSize = totalSize; + codePtr->numCommands = envPtr->numCommands; + codePtr->numSrcChars = srcLen; + codePtr->numCodeBytes = codeBytes; + codePtr->numObjects = numObjects; + codePtr->numExcRanges = envPtr->excRangeArrayNext; + codePtr->numAuxDataItems = envPtr->auxDataArrayNext; + codePtr->auxDataArrayPtr = NULL; + codePtr->numCmdLocBytes = cmdLocBytes; + codePtr->maxExcRangeDepth = envPtr->maxExcRangeDepth; + codePtr->maxStackDepth = envPtr->maxStackDepth; + + p += sizeof(ByteCode); + codePtr->codeStart = p; + memcpy((VOID *) p, (VOID *) envPtr->codeStart, codeBytes); + + p += TCL_ALIGN(codeBytes); /* align object array */ + codePtr->objArrayPtr = (Tcl_Obj **) p; + memcpy((VOID *) p, (VOID *) envPtr->objArrayPtr, objArrayBytes); + + p += TCL_ALIGN(objArrayBytes); /* align exception range array */ + if (exceptArrayBytes > 0) { + codePtr->excRangeArrayPtr = (ExceptionRange *) p; + memcpy((VOID *) p, (VOID *) envPtr->excRangeArrayPtr, + exceptArrayBytes); + } + + p += TCL_ALIGN(exceptArrayBytes); /* align AuxData array */ + if (auxDataArrayBytes > 0) { + codePtr->auxDataArrayPtr = (AuxData *) p; + memcpy((VOID *) p, (VOID *) envPtr->auxDataArrayPtr, + auxDataArrayBytes); + } + + p += auxDataArrayBytes; + nextPtr = EncodeCmdLocMap(envPtr, codePtr, (unsigned char *) p); + if (((size_t)(nextPtr - p)) != cmdLocBytes) { + panic("TclInitByteCodeObj: encoded cmd location bytes %d != expected size %d\n", (nextPtr - p), cmdLocBytes); + } + + /* + * Free the old internal rep then convert the object to a + * bytecode object by making its internal rep point to the just + * compiled ByteCode. + */ + + if ((objPtr->typePtr != NULL) && + (objPtr->typePtr->freeIntRepProc != NULL)) { + objPtr->typePtr->freeIntRepProc(objPtr); + } + objPtr->internalRep.otherValuePtr = (VOID *) codePtr; + objPtr->typePtr = &tclByteCodeType; +} + +/* + *---------------------------------------------------------------------- + * + * GetCmdLocEncodingSize -- + * + * Computes the total number of bytes needed to encode the command + * location information for some compiled code. + * + * Results: + * The byte count needed to encode the compiled location information. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetCmdLocEncodingSize(envPtr) + CompileEnv *envPtr; /* Points to compilation environment + * structure containing the CmdLocation + * structure to encode. */ +{ + register CmdLocation *mapPtr = envPtr->cmdMapPtr; + int numCmds = envPtr->numCommands; + int codeDelta, codeLen, srcDelta, srcLen; + int codeDeltaNext, codeLengthNext, srcDeltaNext, srcLengthNext; + /* The offsets in their respective byte + * sequences where the next encoded offset + * or length should go. */ + int prevCodeOffset, prevSrcOffset, i; + + codeDeltaNext = codeLengthNext = srcDeltaNext = srcLengthNext = 0; + prevCodeOffset = prevSrcOffset = 0; + for (i = 0; i < numCmds; i++) { + codeDelta = (mapPtr[i].codeOffset - prevCodeOffset); + if (codeDelta < 0) { + panic("GetCmdLocEncodingSize: bad code offset"); + } else if (codeDelta <= 127) { + codeDeltaNext++; + } else { + codeDeltaNext += 5; /* 1 byte for 0xFF, 4 for positive delta */ + } + prevCodeOffset = mapPtr[i].codeOffset; + + codeLen = mapPtr[i].numCodeBytes; + if (codeLen < 0) { + panic("GetCmdLocEncodingSize: bad code length"); + } else if (codeLen <= 127) { + codeLengthNext++; + } else { + codeLengthNext += 5; /* 1 byte for 0xFF, 4 for length */ + } + + srcDelta = (mapPtr[i].srcOffset - prevSrcOffset); + if ((-127 <= srcDelta) && (srcDelta <= 127)) { + srcDeltaNext++; + } else { + srcDeltaNext += 5; /* 1 byte for 0xFF, 4 for delta */ + } + prevSrcOffset = mapPtr[i].srcOffset; + + srcLen = mapPtr[i].numSrcChars; + if (srcLen < 0) { + panic("GetCmdLocEncodingSize: bad source length"); + } else if (srcLen <= 127) { + srcLengthNext++; + } else { + srcLengthNext += 5; /* 1 byte for 0xFF, 4 for length */ + } + } + + return (codeDeltaNext + codeLengthNext + srcDeltaNext + srcLengthNext); +} + +/* + *---------------------------------------------------------------------- + * + * EncodeCmdLocMap -- + * + * Encode the command location information for some compiled code into + * a ByteCode structure. The encoded command location map is stored as + * three adjacent byte sequences. + * + * Results: + * Pointer to the first byte after the encoded command location + * information. + * + * Side effects: + * The encoded information is stored into the block of memory headed + * by codePtr. Also records pointers to the start of the four byte + * sequences in fields in codePtr's ByteCode header structure. + * + *---------------------------------------------------------------------- + */ + +static unsigned char * +EncodeCmdLocMap(envPtr, codePtr, startPtr) + CompileEnv *envPtr; /* Points to compilation environment + * structure containing the CmdLocation + * structure to encode. */ + ByteCode *codePtr; /* ByteCode in which to encode envPtr's + * command location information. */ + unsigned char *startPtr; /* Points to the first byte in codePtr's + * memory block where the location + * information is to be stored. */ +{ + register CmdLocation *mapPtr = envPtr->cmdMapPtr; + int numCmds = envPtr->numCommands; + register unsigned char *p = startPtr; + int codeDelta, codeLen, srcDelta, srcLen, prevOffset; + register int i; + + /* + * Encode the code offset for each command as a sequence of deltas. + */ + + codePtr->codeDeltaStart = p; + prevOffset = 0; + for (i = 0; i < numCmds; i++) { + codeDelta = (mapPtr[i].codeOffset - prevOffset); + if (codeDelta < 0) { + panic("EncodeCmdLocMap: bad code offset"); + } else if (codeDelta <= 127) { + TclStoreInt1AtPtr(codeDelta, p); + p++; + } else { + TclStoreInt1AtPtr(0xFF, p); + p++; + TclStoreInt4AtPtr(codeDelta, p); + p += 4; + } + prevOffset = mapPtr[i].codeOffset; + } + + /* + * Encode the code length for each command. + */ + + codePtr->codeLengthStart = p; + for (i = 0; i < numCmds; i++) { + codeLen = mapPtr[i].numCodeBytes; + if (codeLen < 0) { + panic("EncodeCmdLocMap: bad code length"); + } else if (codeLen <= 127) { + TclStoreInt1AtPtr(codeLen, p); + p++; + } else { + TclStoreInt1AtPtr(0xFF, p); + p++; + TclStoreInt4AtPtr(codeLen, p); + p += 4; + } + } + + /* + * Encode the source offset for each command as a sequence of deltas. + */ + + codePtr->srcDeltaStart = p; + prevOffset = 0; + for (i = 0; i < numCmds; i++) { + srcDelta = (mapPtr[i].srcOffset - prevOffset); + if ((-127 <= srcDelta) && (srcDelta <= 127)) { + TclStoreInt1AtPtr(srcDelta, p); + p++; + } else { + TclStoreInt1AtPtr(0xFF, p); + p++; + TclStoreInt4AtPtr(srcDelta, p); + p += 4; + } + prevOffset = mapPtr[i].srcOffset; + } + + /* + * Encode the source length for each command. + */ + + codePtr->srcLengthStart = p; + for (i = 0; i < numCmds; i++) { + srcLen = mapPtr[i].numSrcChars; + if (srcLen < 0) { + panic("EncodeCmdLocMap: bad source length"); + } else if (srcLen <= 127) { + TclStoreInt1AtPtr(srcLen, p); + p++; + } else { + TclStoreInt1AtPtr(0xFF, p); + p++; + TclStoreInt4AtPtr(srcLen, p); + p += 4; + } + } + + return p; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileString -- + * + * Compile a Tcl script in a null-terminated binary string. + * + * Results: + * The return value is TCL_OK on a successful compilation and TCL_ERROR + * on failure. If TCL_ERROR is returned, then the interpreter's result + * contains an error message. + * + * envPtr->termOffset and interp->termOffset are filled in with the + * offset of the character in the string just after the last one + * successfully processed; this might be the offset of the ']' (if + * flags & TCL_BRACKET_TERM), or the offset of the '\0' at the end of + * the string. Also updates envPtr->maxStackDepth with the maximum + * number of stack elements needed to execute the string's commands. + * + * Side effects: + * Adds instructions to envPtr to evaluate the string at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileString(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + Interp *iPtr = (Interp *) interp; + register char *src = string;/* Points to current source char. */ + register char c = *src; /* The current char. */ + register int type; /* Current char's CHAR_TYPE type. */ + char termChar = (char)((flags & TCL_BRACKET_TERM)? ']' : '\0'); + /* Return when this character is found + * (either ']' or '\0'). Zero means newlines + * terminate cmds. */ + int isFirstCmd = 1; /* 1 if compiling the first cmd. */ + char *cmdSrcStart = NULL; /* Points to first non-blank char in each + * command. Initialized to avoid compiler + * warning. */ + int cmdIndex; /* The index of the current command in the + * compilation environment's command + * location table. */ + int lastTopLevelCmdIndex = -1; + /* Index of most recent toplevel command in + * the command location table. Initialized + * to avoid compiler warning. */ + int cmdCodeOffset = -1; /* Offset of first byte of current command's + * code. Initialized to avoid compiler + * warning. */ + int cmdWords; /* Number of words in current command. */ + Tcl_Command cmd; /* Used to search for commands. */ + Command *cmdPtr; /* Points to command's Command structure if + * first word is simple and command was + * found; else NULL. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute all cmds. */ + char *termPtr; /* Points to char that terminated word. */ + char savedChar; /* Holds the character from string + * termporarily replaced by a null character + * during processing of words. */ + int objIndex = -1; /* The object array index for a pushed + * object holding a word or word part + * Initialized to avoid compiler warning. */ + unsigned char *entryCodeNext = envPtr->codeNext; + /* Value of envPtr's current instruction + * pointer at entry. Used to tell if any + * instructions generated. */ + char *ellipsis = ""; /* Used to set errorInfo variable; "..." + * indicates that not all of offending + * command is included in errorInfo. "" + * means that the command is all there. */ + Tcl_Obj *objPtr; + int numChars; + int result = TCL_OK; + int savePushSimpleWords = envPtr->pushSimpleWords; + + /* + * commands: command {(';' | '\n') command} + */ + + while ((src != lastChar) && (c != termChar)) { + /* + * Skip white space, semicolons, backslash-newlines (treated as + * spaces), and comments before command. + */ + + type = CHAR_TYPE(src, lastChar); + while ((type & (TCL_SPACE | TCL_BACKSLASH)) + || (c == '\n') || (c == ';')) { + if (type == TCL_BACKSLASH) { + if (src[1] == '\n') { + src += 2; + } else { + break; + } + } else { + src++; + } + c = *src; + type = CHAR_TYPE(src, lastChar); + } + + if (c == '#') { + while (src != lastChar) { + if (c == '\\') { + int numRead; + Tcl_Backslash(src, &numRead); + src += numRead; + } else if (c == '\n') { + src++; + c = *src; + envPtr->termOffset = (src - string); + break; + } else { + src++; + } + c = *src; + } + continue; /* end of comment, restart outer command loop */ + } + + /* + * Compile one command: zero or more words terminated by a '\n', + * ';', ']' (if command is terminated by close bracket), or + * the end of string. + * + * command: word* + */ + + type = CHAR_TYPE(src, lastChar); + if ((type == TCL_COMMAND_END) + && ((c != ']') || (flags & TCL_BRACKET_TERM))) { + continue; /* empty command; restart outer cmd loop */ + } + + /* + * If not the first command, discard the previous command's result. + */ + + if (!isFirstCmd) { + TclEmitOpcode(INST_POP, envPtr); + if (!(flags & TCL_BRACKET_TERM)) { + /* + * We are compiling a top level command. Update the number + * of code bytes for the last command to account for the pop + * instruction. + */ + + (envPtr->cmdMapPtr[lastTopLevelCmdIndex]).numCodeBytes = + (envPtr->codeNext-envPtr->codeStart) - cmdCodeOffset; + } + } + + /* + * Compile the words of the command. Process the first word + * specially, since it is the name of a command. If it is a "simple" + * string (just a sequence of characters), look it up in the table + * of compilation procedures. If a word other than the first is + * simple and represents an integer whose formatted representation + * is the same as the word, just push an integer object. Also record + * starting source and object information for the command. + */ + + envPtr->numCommands++; + cmdIndex = (envPtr->numCommands - 1); + if (!(flags & TCL_BRACKET_TERM)) { + lastTopLevelCmdIndex = cmdIndex; + } + + cmdSrcStart = src; + cmdCodeOffset = (envPtr->codeNext - envPtr->codeStart); + cmdWords = 0; + EnterCmdStartData(envPtr, cmdIndex, src-envPtr->source, + cmdCodeOffset); + + if ((!(flags & TCL_BRACKET_TERM)) + && (tclTraceCompile >= 1) && (envPtr->procPtr == NULL)) { + /* + * Display a line summarizing the top level command we are about + * to compile. + */ + + char *p = cmdSrcStart; + int numChars, complete; + + while ((CHAR_TYPE(p, lastChar) != TCL_COMMAND_END) + || ((*p == ']') && !(flags & TCL_BRACKET_TERM))) { + p++; + } + numChars = (p - cmdSrcStart); + complete = 1; + if (numChars > 60) { + numChars = 60; + complete = 0; + } else if ((numChars >= 2) && (*p == '\n') && (*(p-1) == '{')) { + complete = 0; + } + fprintf(stdout, "Compiling: %.*s%s\n", + numChars, cmdSrcStart, (complete? "" : " ...")); + } + + while ((type != TCL_COMMAND_END) + || ((c == ']') && !(flags & TCL_BRACKET_TERM))) { + /* + * Skip any leading white space at the start of a word. Note + * that a backslash-newline is treated as a space. + */ + + while (type & (TCL_SPACE | TCL_BACKSLASH)) { + if (type == TCL_BACKSLASH) { + if (src[1] == '\n') { + src += 2; + } else { + break; + } + } else { + src++; + } + c = *src; + type = CHAR_TYPE(src, lastChar); + } + if ((type == TCL_COMMAND_END) + && ((c != ']') || (flags & TCL_BRACKET_TERM))) { + break; /* no words remain for command. */ + } + + /* + * Compile one word. We use an inline version of CompileWord to + * avoid an extra procedure call. + */ + + envPtr->pushSimpleWords = 0; + if (type & (TCL_QUOTE | TCL_OPEN_BRACE)) { + src++; + if (type == TCL_QUOTE) { + result = TclCompileQuotes(interp, src, lastChar, + '"', flags, envPtr); + } else { + result = CompileBraces(interp, src, lastChar, + flags, envPtr); + } + termPtr = (src + envPtr->termOffset); + if (result != TCL_OK) { + src = termPtr; + goto done; + } + + /* + * Make sure terminating character of the quoted or braced + * string is the end of word. + */ + + c = *termPtr; + if ((c == '\\') && (*(termPtr+1) == '\n')) { + /* + * Line is continued on next line; the backslash- + * newline turns into space, which terminates the word. + */ + } else { + type = CHAR_TYPE(termPtr, lastChar); + if ((type != TCL_SPACE) && (type != TCL_COMMAND_END)) { + Tcl_ResetResult(interp); + if (*(src-1) == '"') { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "extra characters after close-quote", -1); + } else { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "extra characters after close-brace", -1); + } + result = TCL_ERROR; + } + } + } else { + result = CompileMultipartWord(interp, src, lastChar, + flags, envPtr); + termPtr = (src + envPtr->termOffset); + } + if (result != TCL_OK) { + ellipsis = "..."; + src = termPtr; + goto done; + } + + if (envPtr->wordIsSimple) { + /* + * A simple word. Temporarily replace the terminating + * character with a null character. + */ + + numChars = envPtr->numSimpleWordChars; + savedChar = src[numChars]; + src[numChars] = '\0'; + + if ((cmdWords == 0) + && (!(iPtr->flags & DONT_COMPILE_CMDS_INLINE))) { + /* + * The first word of a command and inline command + * compilation has not been disabled (e.g., by command + * traces). Look up the first word in the interpreter's + * hashtable of commands. If a compilation procedure is + * found, let it compile the command after resetting + * error logging information. Note that if we are + * compiling a procedure, we must look up the command + * in the procedure's namespace and not the current + * namespace. + */ + + Namespace *cmdNsPtr; + + if (envPtr->procPtr != NULL) { + cmdNsPtr = envPtr->procPtr->cmdPtr->nsPtr; + } else { + cmdNsPtr = NULL; + } + + cmdPtr = NULL; + cmd = Tcl_FindCommand(interp, src, + (Tcl_Namespace *) cmdNsPtr, /*flags*/ 0); + if (cmd != (Tcl_Command) NULL) { + cmdPtr = (Command *) cmd; + } + if ((cmdPtr != NULL) && (cmdPtr->compileProc != NULL)) { + char *firstArg = termPtr; + src[numChars] = savedChar; + iPtr->flags &= ~(ERR_ALREADY_LOGGED | ERR_IN_PROGRESS + | ERROR_CODE_SET); + result = (*(cmdPtr->compileProc))(interp, + firstArg, lastChar, flags, envPtr); + if (result == TCL_OK) { + src = (firstArg + envPtr->termOffset); + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + goto finishCommand; + } else if (result == TCL_OUT_LINE_COMPILE) { + result = TCL_OK; + src[numChars] = '\0'; + } else { + src = firstArg; + goto done; /* an error */ + } + } + + /* + * No compile procedure was found for the command: push + * the word and continue to compile the remaining + * words. If a hashtable entry was found for the + * command, push a CmdName object instead to avoid + * runtime lookups. If necessary, convert the pushed + * object to be a CmdName object. If this is the first + * CmdName object in this code unit that refers to the + * command, increment the reference count in the + * Command structure to reflect the new reference from + * the CmdName object and, if the command is deleted + * later, to keep the Command structure from being freed + * until TclExecuteByteCode has a chance to recognize + * that the command was deleted. + */ + + objIndex = TclObjIndexForString(src, numChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + if (cmdPtr != NULL) { + objPtr = envPtr->objArrayPtr[objIndex]; + if ((objPtr->typePtr != &tclCmdNameType) + && (objPtr->bytes != NULL)) { + ResolvedCmdName *resPtr = (ResolvedCmdName *) + ckalloc(sizeof(ResolvedCmdName)); + Namespace *nsPtr = (Namespace *) + Tcl_GetCurrentNamespace(interp); + + resPtr->cmdPtr = cmdPtr; + resPtr->refNsPtr = nsPtr; + resPtr->refNsId = nsPtr->nsId; + resPtr->refNsCmdEpoch = nsPtr->cmdRefEpoch; + resPtr->cmdEpoch = cmdPtr->cmdEpoch; + resPtr->refCount = 1; + objPtr->internalRep.twoPtrValue.ptr1 = + (VOID *) resPtr; + objPtr->internalRep.twoPtrValue.ptr2 = NULL; + objPtr->typePtr = &tclCmdNameType; + cmdPtr->refCount++; + } + } + } else { + /* + * See if the word represents an integer whose formatted + * representation is the same as the word (e.g., this is + * true for 123 and -1 but not for 00005). If so, just + * push an integer object. + */ + + int isCompilableInt = 0; + long n; + char buf[40]; + + if (TclLooksLikeInt(src)) { + int code = TclGetLong(interp, src, &n); + if (code == TCL_OK) { + TclFormatInt(buf, n); + if (strcmp(src, buf) == 0) { + isCompilableInt = 1; + objIndex = TclObjIndexForString(src, + numChars, /*allocStrRep*/ 0, + /*inHeap*/ 0, envPtr); + objPtr = envPtr->objArrayPtr[objIndex]; + + Tcl_InvalidateStringRep(objPtr); + objPtr->internalRep.longValue = n; + objPtr->typePtr = &tclIntType; + } + } else { + Tcl_ResetResult(interp); + } + } + if (!isCompilableInt) { + objIndex = TclObjIndexForString(src, numChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + } + } + src[numChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth = TclMax((cmdWords + 1), maxDepth); + } else { /* not a simple word */ + maxDepth = TclMax((cmdWords + envPtr->maxStackDepth), + maxDepth); + } + src = termPtr; + c = *src; + type = CHAR_TYPE(src, lastChar); + cmdWords++; + } + + /* + * Emit an invoke instruction for the command. If a compile command + * was found for the command we called it and skipped this. + */ + + if (cmdWords > 0) { + if (cmdWords <= 255) { + TclEmitInstUInt1(INST_INVOKE_STK1, cmdWords, envPtr); + } else { + TclEmitInstUInt4(INST_INVOKE_STK4, cmdWords, envPtr); + } + } + + /* + * Update the compilation environment structure. Record + * source/object information for the command. + */ + + finishCommand: + EnterCmdExtentData(envPtr, cmdIndex, src-cmdSrcStart, + (envPtr->codeNext-envPtr->codeStart) - cmdCodeOffset); + + isFirstCmd = 0; + envPtr->termOffset = (src - string); + c = *src; + } + + done: + if (result == TCL_OK) { + /* + * If the source string yielded no instructions (e.g., if it was + * empty), push an empty string object as the command's result. + */ + + if (entryCodeNext == envPtr->codeNext) { + int objIndex = TclObjIndexForString("", 0, /*allocStrRep*/ 0, + /*inHeap*/ 0, envPtr); + TclEmitPush(objIndex, envPtr); + maxDepth = 1; + } + } else { + /* + * Add additional error information. First compute the line number + * where the error occurred. + */ + + register char *p; + int numChars; + char buf[200]; + + iPtr->errorLine = 1; + for (p = string; p != cmdSrcStart; p++) { + if (*p == '\n') { + iPtr->errorLine++; + } + } + for ( ; isspace(UCHAR(*p)) || (*p == ';'); p++) { + if (*p == '\n') { + iPtr->errorLine++; + } + } + + /* + * Figure out how much of the command to print (up to a certain + * number of characters, or up to the end of the command). + */ + + p = cmdSrcStart; + while ((CHAR_TYPE(p, lastChar) != TCL_COMMAND_END) + || ((*p == ']') && !(flags & TCL_BRACKET_TERM))) { + p++; + } + numChars = (p - cmdSrcStart); + if (numChars > 150) { + numChars = 150; + ellipsis = " ..."; + } else if ((numChars >= 2) && (*p == '\n') && (*(p-1) == '{')) { + ellipsis = " ..."; + } + + sprintf(buf, "\n while compiling\n\"%.*s%s\"", + numChars, cmdSrcStart, ellipsis); + Tcl_AddObjErrorInfo(interp, buf, -1); + } + + envPtr->termOffset = (src - string); + iPtr->termOffset = envPtr->termOffset; + envPtr->maxStackDepth = maxDepth; + envPtr->pushSimpleWords = savePushSimpleWords; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * CompileWord -- + * + * This procedure compiles one word from a command string. It skips + * any leading white space. + * + * Ordinarily, callers set envPtr->pushSimpleWords to 1 and this + * procedure emits push and other instructions to compute the + * word on the Tcl evaluation stack at execution time. If a caller sets + * envPtr->pushSimpleWords to 0, CompileWord will _not_ compile + * "simple" words: words that are just a sequence of characters without + * backslashes. It will leave their compilation up to the caller. + * + * As an important special case, if the word is simple, this procedure + * sets envPtr->wordIsSimple to 1 and envPtr->numSimpleWordChars to the + * number of characters in the simple word. This allows the caller to + * process these words specially. + * + * Results: + * The return value is a standard Tcl result. If an error occurs, an + * error message is left in the interpreter's result. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed in the last + * word. This is normally the character just after the last one in a + * word (perhaps the command terminator), or the vicinity of an error + * (if the result is not TCL_OK). + * + * envPtr->wordIsSimple is set 1 if the word is simple: just a + * sequence of characters without backslashes. If so, the word's + * characters are the envPtr->numSimpleWordChars characters starting + * at string. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to evaluate the word. This is not changed if + * the word is simple and envPtr->pushSimpleWords was 0 (false). + * + * Side effects: + * Instructions are added to envPtr to compute and push the word + * at runtime. + * + *---------------------------------------------------------------------- + */ + +static int +CompileWord(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Interpreter to use for nested command + * evaluations and error messages. */ + char *string; /* First character of word. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same values + * passed to Tcl_EvalObj). */ + CompileEnv *envPtr; /* Holds the resulting instructions. */ +{ + /* + * Compile one word: approximately + * + * word: quoted_string | braced_string | multipart_word + * quoted_string: '"' char* '"' + * braced_string: '{' char* '}' + * multipart_word (see CompileMultipartWord below) + */ + + register char *src = string; /* Points to current source char. */ + register int type = CHAR_TYPE(src, lastChar); + /* Current char's CHAR_TYPE type. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to compute and push the word. */ + char *termPtr = src; /* Points to the character that terminated + * the word. */ + int result = TCL_OK; + + /* + * Skip any leading white space at the start of a word. Note that a + * backslash-newline is treated as a space. + */ + + while (type & (TCL_SPACE | TCL_BACKSLASH)) { + if (type == TCL_BACKSLASH) { + if (src[1] == '\n') { + src += 2; + } else { + break; /* no longer white space */ + } + } else { + src++; + } + type = CHAR_TYPE(src, lastChar); + } + if (type == TCL_COMMAND_END) { + goto done; + } + + /* + * Compile the word. Handle quoted and braced string words here in order + * to avoid an extra procedure call. + */ + + if (type & (TCL_QUOTE | TCL_OPEN_BRACE)) { + src++; + if (type == TCL_QUOTE) { + result = TclCompileQuotes(interp, src, lastChar, '"', flags, + envPtr); + } else { + result = CompileBraces(interp, src, lastChar, flags, envPtr); + } + termPtr = (src + envPtr->termOffset); + if (result != TCL_OK) { + goto done; + } + + /* + * Make sure terminating character of the quoted or braced string is + * the end of word. + */ + + if ((*termPtr == '\\') && (*(termPtr+1) == '\n')) { + /* + * Line is continued on next line; the backslash-newline turns + * into space, which terminates the word. + */ + } else { + type = CHAR_TYPE(termPtr, lastChar); + if (!(type & (TCL_SPACE | TCL_COMMAND_END))) { + Tcl_ResetResult(interp); + if (*(src-1) == '"') { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "extra characters after close-quote", -1); + } else { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "extra characters after close-brace", -1); + } + result = TCL_ERROR; + goto done; + } + } + maxDepth = envPtr->maxStackDepth; + } else { + result = CompileMultipartWord(interp, src, lastChar, flags, envPtr); + termPtr = (src + envPtr->termOffset); + maxDepth = envPtr->maxStackDepth; + } + + /* + * Done processing the word. The values of envPtr->wordIsSimple and + * envPtr->numSimpleWordChars are left at the values returned by + * TclCompileQuotes/Braces/MultipartWord. + */ + + done: + envPtr->termOffset = (termPtr - string); + envPtr->maxStackDepth = maxDepth; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * CompileMultipartWord -- + * + * This procedure compiles one multipart word: a word comprised of some + * number of nested commands, variable references, or arbitrary + * characters. This procedure assumes that quoted string and braced + * string words and the end of command have already been handled by its + * caller. It also assumes that any leading white space has already + * been consumed. + * + * Ordinarily, callers set envPtr->pushSimpleWords to 1 and this + * procedure emits push and other instructions to compute the word on + * the Tcl evaluation stack at execution time. If a caller sets + * envPtr->pushSimpleWords to 0, it will _not_ compile "simple" words: + * words that are just a sequence of characters without backslashes. + * It will leave their compilation up to the caller. This is done, for + * example, to provide special support for the first word of commands, + * which are almost always the (simple) name of a command. + * + * As an important special case, if the word is simple, this procedure + * sets envPtr->wordIsSimple to 1 and envPtr->numSimpleWordChars to the + * number of characters in the simple word. This allows the caller to + * process these words specially. + * + * Results: + * The return value is a standard Tcl result. If an error occurs, an + * error message is left in the interpreter's result. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed in the last + * word. This is normally the character just after the last one in a + * word (perhaps the command terminator), or the vicinity of an error + * (if the result is not TCL_OK). + * + * envPtr->wordIsSimple is set 1 if the word is simple: just a + * sequence of characters without backslashes. If so, the word's + * characters are the envPtr->numSimpleWordChars characters starting + * at string. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to evaluate the word. This is not changed if + * the word is simple and envPtr->pushSimpleWords was 0 (false). + * + * Side effects: + * Instructions are added to envPtr to compute and push the word + * at runtime. + * + *---------------------------------------------------------------------- + */ + +static int +CompileMultipartWord(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Interpreter to use for nested command + * evaluations and error messages. */ + char *string; /* First character of word. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same values + * passed to Tcl_EvalObj). */ + CompileEnv *envPtr; /* Holds the resulting instructions. */ +{ + /* + * Compile one multi_part word: + * + * multi_part_word: word_part+ + * word_part: nested_cmd | var_reference | char+ + * nested_cmd: '[' command ']' + * var_reference: '$' name | '$' name '(' index_string ')' | + * '$' '{' braced_name '}') + * name: (letter | digit | underscore)+ + * braced_name: (non_close_brace_char)* + * index_string: (non_close_paren_char)* + */ + + register char *src = string; /* Points to current source char. */ + register char c = *src; /* The current char. */ + register int type; /* Current char's CHAR_TYPE type. */ + int bracketNormal = !(flags & TCL_BRACKET_TERM); + int simpleWord = 0; /* Set 1 if word is simple. */ + int numParts = 0; /* Count of word_part objs pushed. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to compute and push the word. */ + char *start; /* Starting position of char+ word_part. */ + int hasBackslash; /* Nonzero if '\' in char+ word_part. */ + int numChars; /* Number of chars in char+ word_part. */ + char savedChar; /* Holds the character from string + * termporarily replaced by a null character + * during word_part processing. */ + int objIndex; /* The object array index for a pushed + * object holding a word_part. */ + int savePushSimpleWords = envPtr->pushSimpleWords; + int result = TCL_OK; + int numRead; + + type = CHAR_TYPE(src, lastChar); + while (1) { + /* + * Process a word_part: a sequence of chars, a var reference, or + * a nested command. + */ + + if ((type & (TCL_NORMAL | TCL_CLOSE_BRACE | TCL_BACKSLASH | + TCL_QUOTE | TCL_OPEN_BRACE)) || + ((c == ']') && bracketNormal)) { + /* + * A char+ word part. Scan first looking for any backslashes. + * Note that a backslash-newline must be treated as a word + * separator, as if the backslash-newline had been collapsed + * before command parsing began. + */ + + start = src; + hasBackslash = 0; + do { + if (type == TCL_BACKSLASH) { + hasBackslash = 1; + Tcl_Backslash(src, &numRead); + if (src[1] == '\n') { + src += numRead; + type = TCL_SPACE; /* force word end */ + break; + } + src += numRead; + } else { + src++; + } + c = *src; + type = CHAR_TYPE(src, lastChar); + } while (type & (TCL_NORMAL | TCL_BACKSLASH | TCL_QUOTE | + TCL_OPEN_BRACE | TCL_CLOSE_BRACE) + || ((c == ']') && bracketNormal)); + + if ((numParts == 0) && !hasBackslash + && (type & (TCL_SPACE | TCL_COMMAND_END))) { + /* + * The word is "simple": just a sequence of characters + * without backslashes terminated by a TCL_SPACE or + * TCL_COMMAND_END. Just return if we are not to compile + * simple words. + */ + + simpleWord = 1; + if (!envPtr->pushSimpleWords) { + envPtr->wordIsSimple = 1; + envPtr->numSimpleWordChars = (src - string); + envPtr->termOffset = envPtr->numSimpleWordChars; + envPtr->pushSimpleWords = savePushSimpleWords; + return TCL_OK; + } + } + + /* + * Create and push a string object for the char+ word_part, + * which starts at "start" and ends at the char just before + * src. If backslashes were found, copy the word_part's + * characters with substituted backslashes into a heap-allocated + * buffer and use it to create the string object. Temporarily + * replace the terminating character with a null character. + */ + + numChars = (src - start); + savedChar = start[numChars]; + start[numChars] = '\0'; + if ((numChars > 0) && (hasBackslash)) { + char *buffer = ckalloc((unsigned) numChars + 1); + register char *dst = buffer; + register char *p = start; + while (p < src) { + if (*p == '\\') { + *dst = Tcl_Backslash(p, &numRead); + if (p[1] == '\n') { + break; + } + p += numRead; + dst++; + } else { + *dst++ = *p++; + } + } + *dst = '\0'; + objIndex = TclObjIndexForString(buffer, dst-buffer, + /*allocStrRep*/ 1, /*inHeap*/ 1, envPtr); + } else { + objIndex = TclObjIndexForString(start, numChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + } + start[numChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth = TclMax((numParts + 1), maxDepth); + } else if (type == TCL_DOLLAR) { + result = TclCompileDollarVar(interp, src, lastChar, + flags, envPtr); + src += envPtr->termOffset; + if (result != TCL_OK) { + goto done; + } + maxDepth = TclMax((numParts + envPtr->maxStackDepth), maxDepth); + c = *src; + type = CHAR_TYPE(src, lastChar); + } else if (type == TCL_OPEN_BRACKET) { + char *termPtr; + envPtr->pushSimpleWords = 1; + src++; + result = TclCompileString(interp, src, lastChar, + (flags | TCL_BRACKET_TERM), envPtr); + termPtr = (src + envPtr->termOffset); + if (*termPtr == ']') { + termPtr++; + } else if (*termPtr == '\0') { + /* + * Missing ] at end of nested command. + */ + + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "missing close-bracket", -1); + result = TCL_ERROR; + } + src = termPtr; + if (result != TCL_OK) { + goto done; + } + maxDepth = TclMax((numParts + envPtr->maxStackDepth), maxDepth); + c = *src; + type = CHAR_TYPE(src, lastChar); + } else if (type & (TCL_SPACE | TCL_COMMAND_END)) { + goto wordEnd; + } + numParts++; + } /* end of infinite loop */ + + wordEnd: + /* + * End of a non-simple word: TCL_SPACE, TCL_COMMAND_END, or + * backslash-newline. Concatenate the word_parts if necessary. + */ + + while (numParts > 255) { + TclEmitInstUInt1(INST_CONCAT1, 255, envPtr); + numParts -= 254; /* concat pushes 1 obj, the result */ + } + if (numParts > 1) { + TclEmitInstUInt1(INST_CONCAT1, numParts, envPtr); + } + + done: + if (simpleWord) { + envPtr->wordIsSimple = 1; + envPtr->numSimpleWordChars = (src - string); + } else { + envPtr->wordIsSimple = 0; + envPtr->numSimpleWordChars = 0; + } + envPtr->termOffset = (src - string); + envPtr->maxStackDepth = maxDepth; + envPtr->pushSimpleWords = savePushSimpleWords; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileQuotes -- + * + * This procedure compiles a double-quoted string such as a quoted Tcl + * command argument or a quoted value in a Tcl expression. This + * procedure is also used to compile array element names within + * parentheses (where the termChar will be ')' instead of '"'), or + * anything else that needs the substitutions that happen in quotes. + * + * Ordinarily, callers set envPtr->pushSimpleWords to 1 and + * TclCompileQuotes always emits push and other instructions to compute + * the word on the Tcl evaluation stack at execution time. If a caller + * sets envPtr->pushSimpleWords to 0, TclCompileQuotes will not compile + * "simple" words: words that are just a sequence of characters without + * backslashes. It will leave their compilation up to the caller. This + * is done to provide special support for the first word of commands, + * which are almost always the (simple) name of a command. + * + * As an important special case, if the word is simple, this procedure + * sets envPtr->wordIsSimple to 1 and envPtr->numSimpleWordChars to the + * number of characters in the simple word. This allows the caller to + * process these words specially. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while parsing the quoted string. If an error + * occurs then the interpreter's result contains a standard error + * message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed; this is + * usually the character just after the matching close-quote. + * + * envPtr->wordIsSimple is set 1 if the word is simple: just a + * sequence of characters without backslashes. If so, the word's + * characters are the envPtr->numSimpleWordChars characters starting + * at string. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to evaluate the word. This is not changed if + * the word is simple and envPtr->pushSimpleWords was 0 (false). + * + * Side effects: + * Instructions are added to envPtr to push the quoted-string + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileQuotes(interp, string, lastChar, termChar, flags, envPtr) + Tcl_Interp *interp; /* Interpreter to use for nested command + * evaluations and error messages. */ + char *string; /* Points to the character just after + * the opening '"' or '('. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int termChar; /* Character that terminates the "quoted" + * string (usually double-quote, but might + * be right-paren or something else). */ + int flags; /* Flags to control compilation (same + * values passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds the resulting instructions. */ +{ + register char *src = string; /* Points to current source char. */ + register char c = *src; /* The current char. */ + int simpleWord = 0; /* Set 1 if a simple quoted string word. */ + char *start; /* Start position of char+ string_part. */ + int hasBackslash; /* 1 if '\' found in char+ string_part. */ + int numRead; /* Count of chars read by Tcl_Backslash. */ + int numParts = 0; /* Count of string_part objs pushed. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to compute and push the string. */ + char savedChar; /* Holds the character from string + * termporarily replaced by a null + * char during string_part processing. */ + int objIndex; /* The object array index for a pushed + * object holding a string_part. */ + int numChars; /* Number of chars in string_part. */ + int savePushSimpleWords = envPtr->pushSimpleWords; + int result = TCL_OK; + + /* + * quoted_string: '"' string_part* '"' (or termChar instead of ") + * string_part: var_reference | nested_cmd | char+ + */ + + + while ((src != lastChar) && (c != termChar)) { + if (c == '$') { + result = TclCompileDollarVar(interp, src, lastChar, flags, + envPtr); + src += envPtr->termOffset; + if (result != TCL_OK) { + goto done; + } + maxDepth = TclMax((numParts + envPtr->maxStackDepth), maxDepth); + c = *src; + } else if (c == '[') { + char *termPtr; + envPtr->pushSimpleWords = 1; + src++; + result = TclCompileString(interp, src, lastChar, + (flags | TCL_BRACKET_TERM), envPtr); + termPtr = (src + envPtr->termOffset); + if (*termPtr == ']') { + termPtr++; + } + src = termPtr; + if (result != TCL_OK) { + goto done; + } + if (termPtr == lastChar) { + /* + * Missing ] at end of nested command. + */ + + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "missing close-bracket", -1); + result = TCL_ERROR; + goto done; + } + maxDepth = TclMax((numParts + envPtr->maxStackDepth), maxDepth); + c = *src; + } else { + /* + * Start of a char+ string_part. Scan first looking for any + * backslashes. + */ + + start = src; + hasBackslash = 0; + do { + if (c == '\\') { + hasBackslash = 1; + Tcl_Backslash(src, &numRead); + src += numRead; + } else { + src++; + } + c = *src; + } while ((src != lastChar) && (c != '$') && (c != '[') + && (c != termChar)); + + if ((numParts == 0) && !hasBackslash + && ((src == lastChar) && (c == termChar))) { + /* + * The quoted string is "simple": just a sequence of + * characters without backslashes terminated by termChar or + * a null character. Just return if we are not to compile + * simple words. + */ + + simpleWord = 1; + if (!envPtr->pushSimpleWords) { + if ((src == lastChar) && (termChar != '\0')) { + char buf[40]; + sprintf(buf, "missing %c", termChar); + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), buf, -1); + result = TCL_ERROR; + } else { + src++; + } + envPtr->wordIsSimple = 1; + envPtr->numSimpleWordChars = (src - string - 1); + envPtr->termOffset = (src - string); + envPtr->pushSimpleWords = savePushSimpleWords; + return result; + } + } + + /* + * Create and push a string object for the char+ string_part + * that starts at "start" and ends at the char just before + * src. If backslashes were found, copy the string_part's + * characters with substituted backslashes into a heap-allocated + * buffer and use it to create the string object. Temporarily + * replace the terminating character with a null character. + */ + + numChars = (src - start); + savedChar = start[numChars]; + start[numChars] = '\0'; + if ((numChars > 0) && (hasBackslash)) { + char *buffer = ckalloc((unsigned) numChars + 1); + register char *dst = buffer; + register char *p = start; + while (p < src) { + if (*p == '\\') { + *dst++ = Tcl_Backslash(p, &numRead); + p += numRead; + } else { + *dst++ = *p++; + } + } + *dst = '\0'; + objIndex = TclObjIndexForString(buffer, (dst - buffer), + /*allocStrRep*/ 1, /*inHeap*/ 1, envPtr); + } else { + objIndex = TclObjIndexForString(start, numChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + } + start[numChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth = TclMax((numParts + 1), maxDepth); + } + numParts++; + } + + /* + * End of the quoted string: src points at termChar or '\0'. If + * necessary, concatenate the string_part objects on the stack. + */ + + if ((src == lastChar) && (termChar != '\0')) { + char buf[40]; + sprintf(buf, "missing %c", termChar); + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), buf, -1); + result = TCL_ERROR; + goto done; + } else { + src++; + } + + if (numParts == 0) { + /* + * The quoted string was empty. Push an empty string object. + */ + + int objIndex = TclObjIndexForString("", 0, /*allocStrRep*/ 0, + /*inHeap*/ 0, envPtr); + TclEmitPush(objIndex, envPtr); + } else { + /* + * Emit any needed concat instructions. + */ + + while (numParts > 255) { + TclEmitInstUInt1(INST_CONCAT1, 255, envPtr); + numParts -= 254; /* concat pushes 1 obj, the result */ + } + if (numParts > 1) { + TclEmitInstUInt1(INST_CONCAT1, numParts, envPtr); + } + } + + done: + if (simpleWord) { + envPtr->wordIsSimple = 1; + envPtr->numSimpleWordChars = (src - string - 1); + } else { + envPtr->wordIsSimple = 0; + envPtr->numSimpleWordChars = 0; + } + envPtr->termOffset = (src - string); + envPtr->maxStackDepth = maxDepth; + envPtr->pushSimpleWords = savePushSimpleWords; + return result; +} + +/* + *-------------------------------------------------------------- + * + * CompileBraces -- + * + * This procedure compiles characters between matching curly braces. + * + * Ordinarily, callers set envPtr->pushSimpleWords to 1 and + * CompileBraces always emits a push instruction to compute the word on + * the Tcl evaluation stack at execution time. However, if a caller + * sets envPtr->pushSimpleWords to 0, CompileBraces will _not_ compile + * "simple" words: words that are just a sequence of characters without + * backslash-newlines. It will leave their compilation up to the + * caller. + * + * As an important special case, if the word is simple, this procedure + * sets envPtr->wordIsSimple to 1 and envPtr->numSimpleWordChars to the + * number of characters in the simple word. This allows the caller to + * process these words specially. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while parsing string. If an error occurs then + * the interpreter's result contains a standard error message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. This is + * usually the character just after the matching close-brace. + * + * envPtr->wordIsSimple is set 1 if the word is simple: just a + * sequence of characters without backslash-newlines. If so, the word's + * characters are the envPtr->numSimpleWordChars characters starting + * at string. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to evaluate the word. This is not changed if + * the word is simple and envPtr->pushSimpleWords was 0 (false). + * + * Side effects: + * Instructions are added to envPtr to push the braced string + * at runtime. + * + *-------------------------------------------------------------- + */ + +static int +CompileBraces(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Interpreter to use for nested command + * evaluations and error messages. */ + char *string; /* Character just after opening bracket. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same + * values passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds the resulting instructions. */ +{ + register char *src = string; /* Points to current source char. */ + register char c; /* The current char. */ + int simpleWord = 0; /* Set 1 if a simple braced string word. */ + int level = 1; /* {} nesting level. Initially 1 since { + * was parsed before we were called. */ + int hasBackslashNewline = 0; /* Nonzero if '\' found. */ + char *last; /* Points just before terminating '}'. */ + int numChars; /* Number of chars in braced string. */ + char savedChar; /* Holds the character from string + * termporarily replaced by a null + * char during braced string processing. */ + int objIndex; /* The object array index for a pushed + * object holding a braced string. */ + int numRead; + int result = TCL_OK; + + /* + * Check for any backslash-newlines, since we must treat + * backslash-newlines specially (they must be replaced by spaces). + */ + + while (1) { + c = *src; + if (src == lastChar) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "missing close-brace", -1); + result = TCL_ERROR; + goto done; + } + if (CHAR_TYPE(src, lastChar) != TCL_NORMAL) { + if (c == '{') { + level++; + } else if (c == '}') { + --level; + if (level == 0) { + src++; + last = (src - 2); /* point just before terminating } */ + break; + } + } else if (c == '\\') { + if (*(src+1) == '\n') { + hasBackslashNewline = 1; + } + (void) Tcl_Backslash(src, &numRead); + src += numRead - 1; + } + } + src++; + } + + if (!hasBackslashNewline) { + /* + * The braced word is "simple": just a sequence of characters + * without backslash-newlines. Just return if we are not to compile + * simple words. + */ + + simpleWord = 1; + if (!envPtr->pushSimpleWords) { + envPtr->wordIsSimple = 1; + envPtr->numSimpleWordChars = (src - string - 1); + envPtr->termOffset = (src - string); + return TCL_OK; + } + } + + /* + * Create and push a string object for the braced string. This starts at + * "string" and ends just after "last" (which points to the final + * character before the terminating '}'). If backslash-newlines were + * found, we copy characters one at a time into a heap-allocated buffer + * and do backslash-newline substitutions. + */ + + numChars = (last - string + 1); + savedChar = string[numChars]; + string[numChars] = '\0'; + if ((numChars > 0) && (hasBackslashNewline)) { + char *buffer = ckalloc((unsigned) numChars + 1); + register char *dst = buffer; + register char *p = string; + while (p <= last) { + c = *dst++ = *p++; + if (c == '\\') { + if (*p == '\n') { + dst[-1] = Tcl_Backslash(p-1, &numRead); + p += numRead - 1; + } else { + (void) Tcl_Backslash(p-1, &numRead); + while (numRead > 1) { + *dst++ = *p++; + numRead--; + } + } + } + } + *dst = '\0'; + objIndex = TclObjIndexForString(buffer, (dst - buffer), + /*allocStrRep*/ 1, /*inHeap*/ 1, envPtr); + } else { + objIndex = TclObjIndexForString(string, numChars, /*allocStrRep*/ 1, + /*inHeap*/ 0, envPtr); + } + string[numChars] = savedChar; + TclEmitPush(objIndex, envPtr); + + done: + if (simpleWord) { + envPtr->wordIsSimple = 1; + envPtr->numSimpleWordChars = (src - string - 1); + } else { + envPtr->wordIsSimple = 0; + envPtr->numSimpleWordChars = 0; + } + envPtr->termOffset = (src - string); + envPtr->maxStackDepth = 1; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileDollarVar -- + * + * Given a string starting with a $ sign, parse a variable name + * and compile instructions to push its value. If the variable + * reference is just a '$' (i.e. the '$' isn't followed by anything + * that could possibly be a variable name), just push a string object + * containing '$'. + * + * Results: + * The return value is a standard Tcl result. If an error occurs + * then an error message is left in the interpreter's result. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one in the variable reference. + * + * envPtr->wordIsSimple is set 0 (false) because the word is not + * simple: it is not just a sequence of characters without backslashes. + * For the same reason, envPtr->numSimpleWordChars is set 0. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the string's commands. + * + * Side effects: + * Instructions are added to envPtr to look up the variable and + * push its value at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileDollarVar(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Interpreter to use for nested command + * evaluations and error messages. */ + char *string; /* First char (i.e. $) of var reference. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same + * values passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds the resulting instructions. */ +{ + register char *src = string; /* Points to current source char. */ + register char c; /* The current char. */ + char *name; /* Start of 1st part of variable name. */ + int nameChars; /* Count of chars in name. */ + int nameHasNsSeparators = 0; /* Set 1 if name contains "::"s. */ + char savedChar; /* Holds the character from string + * termporarily replaced by a null + * char during name processing. */ + int objIndex; /* The object array index for a pushed + * object holding a name part. */ + int isArrayRef = 0; /* 1 if reference to array element. */ + int localIndex = -1; /* Frame index of local if found. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to push the variable. */ + int savePushSimpleWords = envPtr->pushSimpleWords; + int result = TCL_OK; + + /* + * var_reference: '$' '{' braced_name '}' | + * '$' name ['(' index_string ')'] + * + * There are three cases: + * 1. The $ sign is followed by an open curly brace. Then the variable + * name is everything up to the next close curly brace, and the + * variable is a scalar variable. + * 2. The $ sign is not followed by an open curly brace. Then the + * variable name is everything up to the next character that isn't + * a letter, digit, underscore, or a "::" namespace separator. If the + * following character is an open parenthesis, then the information + * between parentheses is the array element name, which can include + * any of the substitutions permissible between quotes. + * 3. The $ sign is followed by something that isn't a letter, digit, + * underscore, or a "::" namespace separator: in this case, + * there is no variable name, and "$" is pushed. + */ + + src++; /* advance over the '$'. */ + + /* + * Collect the first part of the variable's name into "name" and + * determine if it is an array reference and if it contains any + * namespace separator (::'s). + */ + + if (*src == '{') { + /* + * A scalar name in braces. + */ + + char *p; + + src++; + name = src; + c = *src; + while (c != '}') { + if (src == lastChar) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "missing close-brace for variable name", -1); + result = TCL_ERROR; + goto done; + } + src++; + c = *src; + } + nameChars = (src - name); + for (p = name; p < src; p++) { + if ((*p == ':') && (*(p+1) == ':')) { + nameHasNsSeparators = 1; + break; + } + } + src++; /* advance over the '}'. */ + } else { + /* + * Scalar name or array reference not in braces. + */ + + name = src; + c = *src; + while (isalnum(UCHAR(c)) || (c == '_') || (c == ':')) { + if (c == ':') { + if (*(src+1) == ':') { + nameHasNsSeparators = 1; + src += 2; + while (*src == ':') { + src++; + } + c = *src; + } else { + break; /* : by itself */ + } + } else { + src++; + c = *src; + } + } + if (src == name) { + /* + * A '$' by itself, not a name reference. Push a "$" string. + */ + + objIndex = TclObjIndexForString("$", 1, /*allocStrRep*/ 1, + /*inHeap*/ 0, envPtr); + TclEmitPush(objIndex, envPtr); + maxDepth = 1; + goto done; + } + nameChars = (src - name); + isArrayRef = (c == '('); + } + + /* + * Now emit instructions to load the variable. First either push the + * name of the scalar or array, or determine its index in the array of + * local variables in a procedure frame. Push the name if we are not + * compiling a procedure body or if the name has namespace + * qualifiers ("::"s). + */ + + if (!isArrayRef) { /* scalar reference */ + if ((envPtr->procPtr == NULL) || nameHasNsSeparators) { + savedChar = name[nameChars]; + name[nameChars] = '\0'; + objIndex = TclObjIndexForString(name, nameChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + name[nameChars] = savedChar; + TclEmitPush(objIndex, envPtr); + TclEmitOpcode(INST_LOAD_SCALAR_STK, envPtr); + maxDepth = 1; + } else { + localIndex = LookupCompiledLocal(name, nameChars, + /*createIfNew*/ 0, /*flagsIfCreated*/ 0, + envPtr->procPtr); + if (localIndex >= 0) { + if (localIndex <= 255) { + TclEmitInstUInt1(INST_LOAD_SCALAR1, localIndex, envPtr); + } else { + TclEmitInstUInt4(INST_LOAD_SCALAR4, localIndex, envPtr); + } + maxDepth = 0; + } else { + savedChar = name[nameChars]; + name[nameChars] = '\0'; + objIndex = TclObjIndexForString(name, nameChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + name[nameChars] = savedChar; + TclEmitPush(objIndex, envPtr); + TclEmitOpcode(INST_LOAD_SCALAR_STK, envPtr); + maxDepth = 1; + } + } + } else { /* array reference */ + if ((envPtr->procPtr == NULL) || nameHasNsSeparators) { + savedChar = name[nameChars]; + name[nameChars] = '\0'; + objIndex = TclObjIndexForString(name, nameChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + name[nameChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth = 1; + } else { + localIndex = LookupCompiledLocal(name, nameChars, + /*createIfNew*/ 0, /*flagsIfCreated*/ 0, + envPtr->procPtr); + if (localIndex < 0) { + savedChar = name[nameChars]; + name[nameChars] = '\0'; + objIndex = TclObjIndexForString(name, nameChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + name[nameChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth = 1; + } + } + + /* + * Parse and push the array element. Perform substitutions on it, + * just as is done for quoted strings. + */ + + src++; + envPtr->pushSimpleWords = 1; + result = TclCompileQuotes(interp, src, lastChar, ')', flags, + envPtr); + src += envPtr->termOffset; + if (result != TCL_OK) { + char msg[200]; + sprintf(msg, "\n (parsing index for array \"%.*s\")", + (nameChars > 100? 100 : nameChars), name); + Tcl_AddObjErrorInfo(interp, msg, -1); + goto done; + } + maxDepth += envPtr->maxStackDepth; + + /* + * Now emit the appropriate load instruction for the array element. + */ + + if (localIndex < 0) { /* a global or an unknown local */ + TclEmitOpcode(INST_LOAD_ARRAY_STK, envPtr); + } else { + if (localIndex <= 255) { + TclEmitInstUInt1(INST_LOAD_ARRAY1, localIndex, envPtr); + } else { + TclEmitInstUInt4(INST_LOAD_ARRAY4, localIndex, envPtr); + } + } + } + + done: + envPtr->termOffset = (src - string); + envPtr->wordIsSimple = 0; + envPtr->numSimpleWordChars = 0; + envPtr->maxStackDepth = maxDepth; + envPtr->pushSimpleWords = savePushSimpleWords; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileBreakCmd -- + * + * Procedure called to compile the "break" command. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while parsing string. If an error occurs then + * the interpreter's result contains a standard error message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "break" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileBreakCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + register char *src = string;/* Points to current source char. */ + register int type; /* Current char's CHAR_TYPE type. */ + int result = TCL_OK; + + /* + * There should be no argument after the "break". + */ + + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"break\"", -1); + result = TCL_ERROR; + goto done; + } + } + + /* + * Emit a break instruction. + */ + + TclEmitOpcode(INST_BREAK, envPtr); + + done: + envPtr->termOffset = (src - string); + envPtr->maxStackDepth = 0; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileCatchCmd -- + * + * Procedure called to compile the "catch" command. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK if + * compilation was successful. If an error occurs then the + * interpreter's result contains a standard error message and TCL_ERROR + * is returned. If compilation failed because the command is too + * complex for TclCompileCatchCmd, TCL_OUT_LINE_COMPILE is returned + * indicating that the catch command should be compiled "out of line" + * by emitting code to invoke its command procedure at runtime. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "catch" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileCatchCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + Proc *procPtr = envPtr->procPtr; + /* Points to structure describing procedure + * containing the catch cmd, else NULL. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute cmd. */ + ArgInfo argInfo; /* Structure holding information about the + * start and end of each argument word. */ + int range = -1; /* If we compile the catch command, the + * index for its catch range record in the + * ExceptionRange array. -1 if we are not + * compiling the command. */ + char *name; /* If a var name appears for a scalar local + * to a procedure, this points to the name's + * 1st char and nameChars is its length. */ + int nameChars; /* Length of the variable name, if any. */ + int localIndex = -1; /* Index of the variable in the current + * procedure's array of local variables. + * Otherwise -1 if not in a procedure or + * the variable wasn't found. */ + char savedChar; /* Holds the character from string + * termporarily replaced by a null character + * during processing of words. */ + JumpFixup jumpFixup; /* Used to emit the jump after the "no + * errors" epilogue code. */ + int numWords, objIndex, jumpDist, result; + char *bodyStart, *bodyEnd; + Tcl_Obj *objPtr; + int savePushSimpleWords = envPtr->pushSimpleWords; + + /* + * Scan the words of the command and record the start and finish of + * each argument word. + */ + + InitArgInfo(&argInfo); + result = CollectArgInfo(interp, string, lastChar, flags, &argInfo); + numWords = argInfo.numArgs; /* i.e., the # after the command name */ + if (result != TCL_OK) { + goto done; + } + if ((numWords != 1) && (numWords != 2)) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"catch command ?varName?\"", -1); + result = TCL_ERROR; + goto done; + } + + /* + * If a variable was specified and the catch command is at global level + * (not in a procedure), don't compile it inline: the payoff is + * too small. + */ + + if ((numWords == 2) && (procPtr == NULL)) { + result = TCL_OUT_LINE_COMPILE; + goto done; + } + + /* + * Make sure the variable name, if any, has no substitutions and just + * refers to a local scaler. + */ + + if (numWords == 2) { + char *firstChar = argInfo.startArray[1]; + char *lastChar = argInfo.endArray[1]; + + if (*firstChar == '{') { + if (*lastChar != '}') { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "extra characters after close-brace", -1); + result = TCL_ERROR; + goto done; + } + firstChar++; + lastChar--; + } + + nameChars = (lastChar - firstChar + 1); + if (nameChars > 0) { + char *p = firstChar; + while (p != lastChar) { + if (CHAR_TYPE(p, lastChar) != TCL_NORMAL) { + result = TCL_OUT_LINE_COMPILE; + goto done; + } + if (*p == '(') { + if (*lastChar == ')') { /* we have an array element */ + result = TCL_OUT_LINE_COMPILE; + goto done; + } + } + p++; + } + } + + name = firstChar; + localIndex = LookupCompiledLocal(name, nameChars, + /*createIfNew*/ 1, /*flagsIfCreated*/ VAR_SCALAR, + procPtr); + } + + /* + *==== At this point we believe we can compile the catch command ==== + */ + + /* + * Create and initialize a ExceptionRange record to hold information + * about this catch command. + */ + + envPtr->excRangeDepth++; + envPtr->maxExcRangeDepth = + TclMax(envPtr->excRangeDepth, envPtr->maxExcRangeDepth); + range = CreateExceptionRange(CATCH_EXCEPTION_RANGE, envPtr); + + /* + * Emit the instruction to mark the start of the catch command. + */ + + TclEmitInstUInt4(INST_BEGIN_CATCH4, range, envPtr); + + /* + * Inline compile the catch's body word: the command it controls. Also + * register the body's starting PC offset and byte length in the + * ExceptionRange record. + */ + + envPtr->excRangeArrayPtr[range].codeOffset = TclCurrCodeOffset(); + + bodyStart = argInfo.startArray[0]; + bodyEnd = argInfo.endArray[0]; + savedChar = *(bodyEnd+1); + *(bodyEnd+1) = '\0'; + result = CompileCmdWordInline(interp, bodyStart, (bodyEnd+1), + flags, envPtr); + *(bodyEnd+1) = savedChar; + + if (result != TCL_OK) { + if (result == TCL_ERROR) { + char msg[60]; + sprintf(msg, "\n (\"catch\" body line %d)", + interp->errorLine); + Tcl_AddObjErrorInfo(interp, msg, -1); + } + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + envPtr->excRangeArrayPtr[range].numCodeBytes = + TclCurrCodeOffset() - envPtr->excRangeArrayPtr[range].codeOffset; + + /* + * Now emit the "no errors" epilogue code for the catch. First, if a + * variable was specified, store the body's result into the + * variable; otherwise, just discard the body's result. Then push + * a "0" object as the catch command's "no error" TCL_OK result, + * and jump around the "error case" epilogue code. + */ + + if (localIndex != -1) { + if (localIndex <= 255) { + TclEmitInstUInt1(INST_STORE_SCALAR1, localIndex, envPtr); + } else { + TclEmitInstUInt4(INST_STORE_SCALAR4, localIndex, envPtr); + } + } + TclEmitOpcode(INST_POP, envPtr); + + objIndex = TclObjIndexForString("0", 1, /*allocStrRep*/ 0, /*inHeap*/ 0, + envPtr); + objPtr = envPtr->objArrayPtr[objIndex]; + + Tcl_InvalidateStringRep(objPtr); + objPtr->internalRep.longValue = 0; + objPtr->typePtr = &tclIntType; + + TclEmitPush(objIndex, envPtr); + if (maxDepth == 0) { + maxDepth = 1; /* since we just pushed one object */ + } + + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); + + /* + * Now emit the "error case" epilogue code. First, if a variable was + * specified, emit instructions to push the interpreter's object result + * and store it into the variable. Then emit an instruction to push the + * nonzero error result. Note that the initial PC offset here is the + * catch's error target. + */ + + envPtr->excRangeArrayPtr[range].catchOffset = TclCurrCodeOffset(); + if (localIndex != -1) { + TclEmitOpcode(INST_PUSH_RESULT, envPtr); + if (localIndex <= 255) { + TclEmitInstUInt1(INST_STORE_SCALAR1, localIndex, envPtr); + } else { + TclEmitInstUInt4(INST_STORE_SCALAR4, localIndex, envPtr); + } + TclEmitOpcode(INST_POP, envPtr); + } + TclEmitOpcode(INST_PUSH_RETURN_CODE, envPtr); + + /* + * Now that we know the target of the jump after the "no errors" + * epilogue, update it with the correct distance. This is less + * than 127 bytes. + */ + + jumpDist = (TclCurrCodeOffset() - jumpFixup.codeOffset); + if (TclFixupForwardJump(envPtr, &jumpFixup, jumpDist, 127)) { + panic("TclCompileCatchCmd: bad jump distance %d\n", jumpDist); + } + + /* + * Emit the instruction to mark the end of the catch command. + */ + + TclEmitOpcode(INST_END_CATCH, envPtr); + + done: + if (numWords == 0) { + envPtr->termOffset = 0; + } else { + envPtr->termOffset = (argInfo.endArray[numWords-1] + 1 - string); + } + if (range != -1) { /* we compiled the catch command */ + envPtr->excRangeDepth--; + } + envPtr->pushSimpleWords = savePushSimpleWords; + envPtr->maxStackDepth = maxDepth; + FreeArgInfo(&argInfo); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileContinueCmd -- + * + * Procedure called to compile the "continue" command. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while parsing string. If an error occurs then + * the interpreter's result contains a standard error message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "continue" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileContinueCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + register char *src = string;/* Points to current source char. */ + register int type; /* Current char's CHAR_TYPE type. */ + int result = TCL_OK; + + /* + * There should be no argument after the "continue". + */ + + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"continue\"", -1); + result = TCL_ERROR; + goto done; + } + } + + /* + * Emit a continue instruction. + */ + + TclEmitOpcode(INST_CONTINUE, envPtr); + + done: + envPtr->termOffset = (src - string); + envPtr->maxStackDepth = 0; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileExprCmd -- + * + * Procedure called to compile the "expr" command. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK + * unless there was an error while parsing string. If an error occurs + * then the interpreter's result contains a standard error message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the "expr" command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "expr" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileExprCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute cmd. */ + ArgInfo argInfo; /* Structure holding information about the + * start and end of each argument word. */ + Tcl_DString buffer; /* Holds the concatenated expr command + * argument words. */ + int firstWord; /* 1 if processing the first word; 0 if + * processing subsequent words. */ + char *first, *last; /* Points to the first and last significant + * chars of the concatenated expression. */ + int inlineCode; /* 1 if inline "optimistic" code is + * emitted for the expression; else 0. */ + int range = -1; /* If we inline compile the concatenated + * expression, the index for its catch range + * record in the ExceptionRange array. + * Initialized to avoid compile warning. */ + JumpFixup jumpFixup; /* Used to emit the "success" jump after + * the inline concat. expression's code. */ + char savedChar; /* Holds the character termporarily replaced + * by a null character during compilation + * of the concatenated expression. */ + int numWords, objIndex, i, result; + char *wordStart, *wordEnd, *p; + char c; + int savePushSimpleWords = envPtr->pushSimpleWords; + int saveExprIsJustVarRef = envPtr->exprIsJustVarRef; + int saveExprIsComparison = envPtr->exprIsComparison; + + /* + * Scan the words of the command and record the start and finish of + * each argument word. + */ + + InitArgInfo(&argInfo); + result = CollectArgInfo(interp, string, lastChar, flags, &argInfo); + numWords = argInfo.numArgs; /* i.e., the # after the command name */ + if (result != TCL_OK) { + goto done; + } + if (numWords == 0) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"expr arg ?arg ...?\"", -1); + result = TCL_ERROR; + goto done; + } + + /* + * If there is a single argument word and it is enclosed in {}s, we may + * strip them off and safely compile the expr command into an inline + * sequence of instructions using TclCompileExpr. We know these + * instructions will have the right Tcl7.x expression semantics. + * + * Otherwise, if the word is not enclosed in {}s, or there are multiple + * words, we may need to call the expr command (Tcl_ExprObjCmd) at + * runtime. This recompiles the expression each time (typically) and so + * is slow. However, there are some circumstances where we can still + * compile inline instructions "optimistically" and check, during their + * execution, for double substitutions (these appear as nonnumeric + * operands). We check for any backslash or command substitutions. If + * none appear, and only variable substitutions are found, we generate + * inline instructions. If there is a compilation error, we must emit + * instructions that return the error at runtime, since this is when + * scripts in Tcl7.x would "see" the error. + * + * For now, if there are multiple words, or the single argument word is + * not in {}s, we concatenate the argument words and strip off any + * enclosing {}s or ""s. We call the expr command at runtime if + * either command or backslash substitutions appear (but not if + * only variable substitutions appear). + */ + + if (numWords == 1) { + wordStart = argInfo.startArray[0]; /* start of 1st arg word */ + wordEnd = argInfo.endArray[0]; /* last char of 1st arg word */ + if ((*wordStart == '{') && (*wordEnd == '}')) { + /* + * Simple case: a single argument word in {}'s. + */ + + *wordEnd = '\0'; + result = TclCompileExpr(interp, (wordStart + 1), wordEnd, + flags, envPtr); + *wordEnd = '}'; + + envPtr->termOffset = (wordEnd + 1) - string; + envPtr->pushSimpleWords = savePushSimpleWords; + FreeArgInfo(&argInfo); + return result; + } + } + + /* + * There are multiple words or no braces around the single word. + * Concatenate the expression's argument words while stripping off + * any enclosing {}s or ""s. + */ + + Tcl_DStringInit(&buffer); + firstWord = 1; + for (i = 0; i < numWords; i++) { + wordStart = argInfo.startArray[i]; + wordEnd = argInfo.endArray[i]; + if (((*wordStart == '{') && (*wordEnd == '}')) + || ((*wordStart == '"') && (*wordEnd == '"'))) { + wordStart++; + wordEnd--; + } + if (!firstWord) { + Tcl_DStringAppend(&buffer, " ", 1); + } + firstWord = 0; + if (wordEnd >= wordStart) { + Tcl_DStringAppend(&buffer, wordStart, (wordEnd-wordStart+1)); + } + } + + /* + * Scan the concatenated expression's characters looking for any + * '['s or (for now) '\'s. If any are found, just call the expr cmd + * at runtime. + */ + + inlineCode = 1; + first = Tcl_DStringValue(&buffer); + last = first + (Tcl_DStringLength(&buffer) - 1); + for (p = first; p <= last; p++) { + c = *p; + if ((c == '[') || (c == '\\')) { + inlineCode = 0; + break; + } + } + + if (inlineCode) { + /* + * Inline compile the concatenated expression inside a "catch" + * so that a runtime error will back off to a (slow) call on expr. + */ + + int startCodeOffset = (envPtr->codeNext - envPtr->codeStart); + int startRangeNext = envPtr->excRangeArrayNext; + + /* + * Create a ExceptionRange record to hold information about the + * "catch" range for the expression's inline code. Also emit the + * instruction to mark the start of the range. + */ + + envPtr->excRangeDepth++; + envPtr->maxExcRangeDepth = + TclMax(envPtr->excRangeDepth, envPtr->maxExcRangeDepth); + range = CreateExceptionRange(CATCH_EXCEPTION_RANGE, envPtr); + TclEmitInstUInt4(INST_BEGIN_CATCH4, range, envPtr); + + /* + * Inline compile the concatenated expression. + */ + + envPtr->excRangeArrayPtr[range].codeOffset = TclCurrCodeOffset(); + savedChar = *(last + 1); + *(last + 1) = '\0'; + result = TclCompileExpr(interp, first, last + 1, flags, envPtr); + *(last + 1) = savedChar; + + maxDepth = envPtr->maxStackDepth; + envPtr->excRangeArrayPtr[range].numCodeBytes = + TclCurrCodeOffset() - envPtr->excRangeArrayPtr[range].codeOffset; + + if ((result != TCL_OK) || (envPtr->exprIsJustVarRef) + || (envPtr->exprIsComparison)) { + /* + * We must call the expr command at runtime. Either there was a + * compilation error or the inline code might fail to give the + * correct 2 level substitution semantics. + * + * The latter can happen if the expression consisted of just a + * single variable reference or if the top-level operator in the + * expr is a comparison (which might operate on strings). In the + * latter case, the expression's code might execute (apparently) + * successfully but produce the wrong result. We depend on its + * execution failing if a second level of substitutions is + * required. This causes the "catch" code we generate around the + * inline code to back off to a call on the expr command at + * runtime, and this always gives the right 2 level substitution + * semantics. + * + * We delete the inline code by backing up the code pc and catch + * index. Note that if there was a compilation error, we can't + * report the error yet since the expression might be valid + * after the second round of substitutions. + */ + + envPtr->codeNext = (envPtr->codeStart + startCodeOffset); + envPtr->excRangeArrayNext = startRangeNext; + inlineCode = 0; + } else { + TclEmitOpcode(INST_END_CATCH, envPtr); /* for ok case */ + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); + envPtr->excRangeArrayPtr[range].catchOffset = TclCurrCodeOffset(); + TclEmitOpcode(INST_END_CATCH, envPtr); /* for error case */ + } + } + + /* + * Emit code for the (slow) call on the expr command at runtime. + * Generate code to concatenate the (already substituted once) + * expression words with a space between each word. + */ + + for (i = 0; i < numWords; i++) { + wordStart = argInfo.startArray[i]; + wordEnd = argInfo.endArray[i]; + savedChar = *(wordEnd + 1); + *(wordEnd + 1) = '\0'; + envPtr->pushSimpleWords = 1; + result = CompileWord(interp, wordStart, wordEnd+1, flags, envPtr); + *(wordEnd + 1) = savedChar; + if (result != TCL_OK) { + break; + } + if (i != (numWords - 1)) { + objIndex = TclObjIndexForString(" ", 1, /*allocStrRep*/ 1, + /*inHeap*/ 0, envPtr); + TclEmitPush(objIndex, envPtr); + maxDepth = TclMax((envPtr->maxStackDepth + 1), maxDepth); + } else { + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + } + } + if (result == TCL_OK) { + int concatItems = 2*numWords - 1; + while (concatItems > 255) { + TclEmitInstUInt1(INST_CONCAT1, 255, envPtr); + concatItems -= 254; /* concat pushes 1 obj, the result */ + } + if (concatItems > 1) { + TclEmitInstUInt1(INST_CONCAT1, concatItems, envPtr); + } + TclEmitOpcode(INST_EXPR_STK, envPtr); + } + + /* + * If emitting inline code, update the target of the jump after + * that inline code. + */ + + if (inlineCode) { + int jumpDist = (TclCurrCodeOffset() - jumpFixup.codeOffset); + if (TclFixupForwardJump(envPtr, &jumpFixup, jumpDist, 127)) { + /* + * Update the inline expression code's catch ExceptionRange + * target since it, being after the jump, also moved down. + */ + + envPtr->excRangeArrayPtr[range].catchOffset += 3; + } + } + Tcl_DStringFree(&buffer); + + done: + if (numWords == 0) { + envPtr->termOffset = 0; + } else { + envPtr->termOffset = (argInfo.endArray[numWords-1] + 1 - string); + } + if (range != -1) { /* we inline compiled the expr */ + envPtr->excRangeDepth--; + } + envPtr->pushSimpleWords = savePushSimpleWords; + envPtr->exprIsJustVarRef = saveExprIsJustVarRef; + envPtr->exprIsComparison = saveExprIsComparison; + envPtr->maxStackDepth = maxDepth; + FreeArgInfo(&argInfo); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileForCmd -- + * + * Procedure called to compile the "for" command. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while parsing string. If an error occurs then + * the interpreter's result contains a standard error message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "for" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileForCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute cmd. */ + ArgInfo argInfo; /* Structure holding information about the + * start and end of each argument word. */ + int range1, range2; /* Indexes in the ExceptionRange array of + * the loop ranges for this loop: one for + * its body and one for its "next" cmd. */ + JumpFixup jumpFalseFixup; /* Used to update or replace the ifFalse + * jump after the "for" test when its target + * PC is determined. */ + int jumpBackDist, jumpBackOffset, testCodeOffset, jumpDist, objIndex; + unsigned char *jumpPc; + int savePushSimpleWords = envPtr->pushSimpleWords; + int numWords, result; + + /* + * Scan the words of the command and record the start and finish of + * each argument word. + */ + + InitArgInfo(&argInfo); + result = CollectArgInfo(interp, string, lastChar, flags, &argInfo); + numWords = argInfo.numArgs; /* i.e., the # after the command name */ + if (result != TCL_OK) { + goto done; + } + if (numWords != 4) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"for start test next command\"", -1); + result = TCL_ERROR; + goto done; + } + + /* + * If the test expression is enclosed in quotes (""s), don't compile + * the for inline. As a result of Tcl's two level substitution + * semantics for expressions, the expression might have a constant + * value that results in the loop never executing, or executing forever. + * Consider "set x 0; for {} "$x > 5" {incr x} {}": the loop body + * should never be executed. + */ + + if (*(argInfo.startArray[1]) == '"') { + result = TCL_OUT_LINE_COMPILE; + goto done; + } + + /* + * Create a ExceptionRange record for the for loop's body. This is used + * to implement break and continue commands inside the body. + * Then create a second ExceptionRange record for the "next" command in + * order to implement break (but not continue) inside it. The second, + * "next" ExceptionRange will always have a -1 continueOffset. + */ + + envPtr->excRangeDepth++; + envPtr->maxExcRangeDepth = + TclMax(envPtr->excRangeDepth, envPtr->maxExcRangeDepth); + range1 = CreateExceptionRange(LOOP_EXCEPTION_RANGE, envPtr); + range2 = CreateExceptionRange(LOOP_EXCEPTION_RANGE, envPtr); + + /* + * Compile inline the next word: the initial command. + */ + + result = CompileCmdWordInline(interp, argInfo.startArray[0], + (argInfo.endArray[0] + 1), flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + Tcl_AddObjErrorInfo(interp, "\n (\"for\" initial command)", -1); + } + goto done; + } + maxDepth = envPtr->maxStackDepth; + + /* + * Discard the start command's result. + */ + + TclEmitOpcode(INST_POP, envPtr); + + /* + * Compile the next word: the test expression. + */ + + testCodeOffset = TclCurrCodeOffset(); + envPtr->pushSimpleWords = 1; /* process words normally */ + result = CompileExprWord(interp, argInfo.startArray[1], + (argInfo.endArray[1] + 1), flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + Tcl_AddObjErrorInfo(interp, "\n (\"for\" test expression)", -1); + } + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + + /* + * Emit the jump that terminates the for command if the test was + * false. We emit a one byte (relative) jump here, and replace it later + * with a four byte jump if the jump target is > 127 bytes away. + */ + + TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, &jumpFalseFixup); + + /* + * Compile the loop body word inline. Also register the loop body's + * starting PC offset and byte length in the its ExceptionRange record. + */ + + envPtr->excRangeArrayPtr[range1].codeOffset = TclCurrCodeOffset(); + result = CompileCmdWordInline(interp, argInfo.startArray[3], + (argInfo.endArray[3] + 1), flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + char msg[60]; + sprintf(msg, "\n (\"for\" body line %d)", interp->errorLine); + Tcl_AddObjErrorInfo(interp, msg, -1); + } + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + envPtr->excRangeArrayPtr[range1].numCodeBytes = + (TclCurrCodeOffset() - envPtr->excRangeArrayPtr[range1].codeOffset); + + /* + * Discard the loop body's result. + */ + + TclEmitOpcode(INST_POP, envPtr); + + /* + * Finally, compile the "next" subcommand word inline. + */ + + envPtr->excRangeArrayPtr[range1].continueOffset = TclCurrCodeOffset(); + envPtr->excRangeArrayPtr[range2].codeOffset = TclCurrCodeOffset(); + result = CompileCmdWordInline(interp, argInfo.startArray[2], + (argInfo.endArray[2] + 1), flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + Tcl_AddObjErrorInfo(interp, "\n (\"for\" loop-end command)", -1); + } + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + envPtr->excRangeArrayPtr[range2].numCodeBytes = + TclCurrCodeOffset() - envPtr->excRangeArrayPtr[range2].codeOffset; + + /* + * Discard the "next" subcommand's result. + */ + + TclEmitOpcode(INST_POP, envPtr); + + /* + * Emit the unconditional jump back to the test at the top of the for + * loop. We generate a four byte jump if the distance to the test is + * greater than 120 bytes. This is conservative, and ensures that we + * won't have to replace this unconditional jump if we later need to + * replace the ifFalse jump with a four-byte jump. + */ + + jumpBackOffset = TclCurrCodeOffset(); + jumpBackDist = (jumpBackOffset - testCodeOffset); + if (jumpBackDist > 120) { + TclEmitInstInt4(INST_JUMP4, /*offset*/ -jumpBackDist, envPtr); + } else { + TclEmitInstInt1(INST_JUMP1, /*offset*/ -jumpBackDist, envPtr); + } + + /* + * Now that we know the target of the jumpFalse after the test, update + * it with the correct distance. If the distance is too great (more + * than 127 bytes), replace that jump with a four byte instruction and + * move the instructions after the jump down. + */ + + jumpDist = (TclCurrCodeOffset() - jumpFalseFixup.codeOffset); + if (TclFixupForwardJump(envPtr, &jumpFalseFixup, jumpDist, 127)) { + /* + * Update the loop body's ExceptionRange record since it moved down: + * i.e., increment both its start and continue PC offsets. Also, + * update the "next" command's start PC offset in its ExceptionRange + * record since it also moved down. + */ + + envPtr->excRangeArrayPtr[range1].codeOffset += 3; + envPtr->excRangeArrayPtr[range1].continueOffset += 3; + envPtr->excRangeArrayPtr[range2].codeOffset += 3; + + /* + * Update the distance for the unconditional jump back to the test + * at the top of the loop since it moved down 3 bytes too. + */ + + jumpBackOffset += 3; + jumpPc = (envPtr->codeStart + jumpBackOffset); + if (jumpBackDist > 120) { + jumpBackDist += 3; + TclUpdateInstInt4AtPc(INST_JUMP4, /*offset*/ -jumpBackDist, + jumpPc); + } else { + jumpBackDist += 3; + TclUpdateInstInt1AtPc(INST_JUMP1, /*offset*/ -jumpBackDist, + jumpPc); + } + } + + /* + * The current PC offset (after the loop's body and "next" subcommand) + * is the loop's break target. + */ + + envPtr->excRangeArrayPtr[range1].breakOffset = + envPtr->excRangeArrayPtr[range2].breakOffset = TclCurrCodeOffset(); + + /* + * Push an empty string object as the for command's result. + */ + + objIndex = TclObjIndexForString("", 0, /*allocStrRep*/ 0, /*inHeap*/ 0, + envPtr); + TclEmitPush(objIndex, envPtr); + if (maxDepth == 0) { + maxDepth = 1; + } + + done: + if (numWords == 0) { + envPtr->termOffset = 0; + } else { + envPtr->termOffset = (argInfo.endArray[numWords-1] + 1 - string); + } + envPtr->pushSimpleWords = savePushSimpleWords; + envPtr->maxStackDepth = maxDepth; + envPtr->excRangeDepth--; + FreeArgInfo(&argInfo); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileForeachCmd -- + * + * Procedure called to compile the "foreach" command. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK if + * compilation was successful. If an error occurs then the + * interpreter's result contains a standard error message and TCL_ERROR + * is returned. If complation failed because the command is too complex + * for TclCompileForeachCmd, TCL_OUT_LINE_COMPILE is returned + * indicating that the foreach command should be compiled "out of line" + * by emitting code to invoke its command procedure at runtime. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the "while" command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "foreach" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileForeachCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + Proc *procPtr = envPtr->procPtr; + /* Points to structure describing procedure + * containing foreach command, else NULL. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute cmd. */ + ArgInfo argInfo; /* Structure holding information about the + * start and end of each argument word. */ + int numLists = 0; /* Count of variable (and value) lists. */ + int range; /* Index in the ExceptionRange array of the + * ExceptionRange record for this loop. */ + ForeachInfo *infoPtr; /* Points to the structure describing this + * foreach command. Stored in a AuxData + * record in the ByteCode. */ + JumpFixup jumpFalseFixup; /* Used to update or replace the ifFalse + * jump after test when its target PC is + * determined. */ + char savedChar; /* Holds the char from string termporarily + * replaced by a null character during + * processing of argument words. */ + int firstListTmp = -1; /* If we decide to compile this foreach + * command, this is the index or "slot + * number" for the first temp var allocated + * in the proc frame that holds a pointer to + * a value list. Initialized to avoid a + * compiler warning. */ + int loopIterNumTmp; /* If we decide to compile this foreach + * command, the index for the temp var that + * holds the current iteration count. */ + char *varListStart, *varListEnd, *valueListStart, *bodyStart, *bodyEnd; + unsigned char *jumpPc; + int jumpDist, jumpBackDist, jumpBackOffset; + int numWords, numVars, infoIndex, tmpIndex, objIndex, i, j, result; + int savePushSimpleWords = envPtr->pushSimpleWords; + + /* + * We parse the variable list argument words and create two arrays: + * varcList[i] gives the number of variables in the i-th var list + * varvList[i] points to an array of the names in the i-th var list + * These are initially allocated on the stack, and are allocated on + * the heap if necessary. + */ + +#define STATIC_VAR_LIST_SIZE 4 + int varcListStaticSpace[STATIC_VAR_LIST_SIZE]; + char **varvListStaticSpace[STATIC_VAR_LIST_SIZE]; + + int *varcList = varcListStaticSpace; + char ***varvList = varvListStaticSpace; + + /* + * If the foreach command is at global level (not in a procedure), + * don't compile it inline: the payoff is too small. + */ + + if (procPtr == NULL) { + return TCL_OUT_LINE_COMPILE; + } + + /* + * Scan the words of the command and record the start and finish of + * each argument word. + */ + + InitArgInfo(&argInfo); + result = CollectArgInfo(interp, string, lastChar, flags, &argInfo); + numWords = argInfo.numArgs; + if (result != TCL_OK) { + goto done; + } + if ((numWords < 3) || (numWords%2 != 1)) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"foreach varList list ?varList list ...? command\"", -1); + result = TCL_ERROR; + goto done; + } + + /* + * Initialize the varcList and varvList arrays; allocate heap storage, + * if necessary, for them. Also make sure the variable names + * have no substitutions: that they're just "var" or "var(elem)" + */ + + numLists = (numWords - 1)/2; + if (numLists > STATIC_VAR_LIST_SIZE) { + varcList = (int *) ckalloc(numLists * sizeof(int)); + varvList = (char ***) ckalloc(numLists * sizeof(char **)); + } + for (i = 0; i < numLists; i++) { + varcList[i] = 0; + varvList[i] = (char **) NULL; + } + for (i = 0; i < numLists; i++) { + /* + * Break each variable list into its component variables. If the + * lists is enclosed in {}s or ""s, strip them off first. + */ + + varListStart = argInfo.startArray[i*2]; + varListEnd = argInfo.endArray[i*2]; + if ((*varListStart == '{') || (*varListStart == '"')) { + if ((*varListEnd != '}') && (*varListEnd != '"')) { + Tcl_ResetResult(interp); + if (*varListStart == '"') { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "extra characters after close-quote", -1); + } else { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "extra characters after close-brace", -1); + } + result = TCL_ERROR; + goto done; + } + varListStart++; + varListEnd--; + } + + /* + * NOTE: THIS NEEDS TO BE CONVERTED TO AN OBJECT LIST. + */ + + savedChar = *(varListEnd+1); + *(varListEnd+1) = '\0'; + result = Tcl_SplitList(interp, varListStart, + &varcList[i], &varvList[i]); + *(varListEnd+1) = savedChar; + if (result != TCL_OK) { + goto done; + } + + /* + * Check that each variable name has no substitutions and that + * it is a scalar name. + */ + + numVars = varcList[i]; + for (j = 0; j < numVars; j++) { + char *varName = varvList[i][j]; + char *p = varName; + while (*p != '\0') { + if (CHAR_TYPE(p, p+1) != TCL_NORMAL) { + result = TCL_OUT_LINE_COMPILE; + goto done; + } + if (*p == '(') { + char *q = p; + do { + q++; + } while (*q != '\0'); + q--; + if (*q == ')') { /* we have an array element */ + result = TCL_OUT_LINE_COMPILE; + goto done; + } + } + p++; + } + } + } + + /* + *==== At this point we believe we can compile the foreach command ==== + */ + + /* + * Create and initialize a ExceptionRange record to hold information + * about this loop. This is used to implement break and continue. + */ + + envPtr->excRangeDepth++; + envPtr->maxExcRangeDepth = + TclMax(envPtr->excRangeDepth, envPtr->maxExcRangeDepth); + range = CreateExceptionRange(LOOP_EXCEPTION_RANGE, envPtr); + + /* + * Reserve (numLists + 1) temporary variables: + * - numLists temps for each value list + * - a temp for the "next value" index into each value list + * At this time we don't try to reuse temporaries; if there are two + * nonoverlapping foreach loops, they don't share any temps. + */ + + for (i = 0; i < numLists; i++) { + tmpIndex = LookupCompiledLocal(NULL, /*nameChars*/ 0, + /*createIfNew*/ 1, /*flagsIfCreated*/ VAR_SCALAR, procPtr); + if (i == 0) { + firstListTmp = tmpIndex; + } + } + loopIterNumTmp = LookupCompiledLocal(NULL, /*nameChars*/ 0, + /*createIfNew*/ 1, /*flagsIfCreated*/ VAR_SCALAR, procPtr); + + /* + * Create and initialize the ForeachInfo and ForeachVarList data + * structures describing this command. Then create a AuxData record + * pointing to the ForeachInfo structure in the compilation environment. + */ + + infoPtr = (ForeachInfo *) ckalloc((unsigned) + (sizeof(ForeachInfo) + (numLists * sizeof(ForeachVarList *)))); + infoPtr->numLists = numLists; + infoPtr->firstListTmp = firstListTmp; + infoPtr->loopIterNumTmp = loopIterNumTmp; + for (i = 0; i < numLists; i++) { + ForeachVarList *varListPtr; + numVars = varcList[i]; + varListPtr = (ForeachVarList *) ckalloc((unsigned) + sizeof(ForeachVarList) + numVars*sizeof(int)); + varListPtr->numVars = numVars; + for (j = 0; j < numVars; j++) { + char *varName = varvList[i][j]; + int nameChars = strlen(varName); + varListPtr->varIndexes[j] = LookupCompiledLocal(varName, + nameChars, /*createIfNew*/ 1, + /*flagsIfCreated*/ VAR_SCALAR, procPtr); + } + infoPtr->varLists[i] = varListPtr; + } + infoIndex = TclCreateAuxData((ClientData) infoPtr, + DupForeachInfo, FreeForeachInfo, envPtr); + + /* + * Emit code to store each value list into the associated temporary. + */ + + for (i = 0; i < numLists; i++) { + valueListStart = argInfo.startArray[2*i + 1]; + envPtr->pushSimpleWords = 1; + result = CompileWord(interp, valueListStart, lastChar, flags, + envPtr); + if (result != TCL_OK) { + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + + tmpIndex = (firstListTmp + i); + if (tmpIndex <= 255) { + TclEmitInstUInt1(INST_STORE_SCALAR1, tmpIndex, envPtr); + } else { + TclEmitInstUInt4(INST_STORE_SCALAR4, tmpIndex, envPtr); + } + TclEmitOpcode(INST_POP, envPtr); + } + + /* + * Emit the instruction to initialize the foreach loop's index temp var. + */ + + TclEmitInstUInt4(INST_FOREACH_START4, infoIndex, envPtr); + + /* + * Emit the top of loop code that assigns each loop variable and checks + * whether to terminate the loop. + */ + + envPtr->excRangeArrayPtr[range].continueOffset = TclCurrCodeOffset(); + TclEmitInstUInt4(INST_FOREACH_STEP4, infoIndex, envPtr); + + /* + * Emit the ifFalse jump that terminates the foreach if all value lists + * are exhausted. We emit a one byte (relative) jump here, and replace + * it later with a four byte jump if the jump target is more than + * 127 bytes away. + */ + + TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, &jumpFalseFixup); + + /* + * Compile the loop body word inline. Also register the loop body's + * starting PC offset and byte length in the ExceptionRange record. + */ + + bodyStart = argInfo.startArray[numWords - 1]; + bodyEnd = argInfo.endArray[numWords - 1]; + savedChar = *(bodyEnd+1); + *(bodyEnd+1) = '\0'; + envPtr->excRangeArrayPtr[range].codeOffset = TclCurrCodeOffset(); + result = CompileCmdWordInline(interp, bodyStart, bodyEnd+1, flags, + envPtr); + *(bodyEnd+1) = savedChar; + if (result != TCL_OK) { + if (result == TCL_ERROR) { + char msg[60]; + sprintf(msg, "\n (\"foreach\" body line %d)", + interp->errorLine); + Tcl_AddObjErrorInfo(interp, msg, -1); + } + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + envPtr->excRangeArrayPtr[range].numCodeBytes = + TclCurrCodeOffset() - envPtr->excRangeArrayPtr[range].codeOffset; + + /* + * Discard the loop body's result. + */ + + TclEmitOpcode(INST_POP, envPtr); + + /* + * Emit the unconditional jump back to the test at the top of the + * loop. We generate a four byte jump if the distance to the to of + * the foreach is greater than 120 bytes. This is conservative and + * ensures that we won't have to replace this unconditional jump if + * we later need to replace the ifFalse jump with a four-byte jump. + */ + + jumpBackOffset = TclCurrCodeOffset(); + jumpBackDist = + (jumpBackOffset - envPtr->excRangeArrayPtr[range].continueOffset); + if (jumpBackDist > 120) { + TclEmitInstInt4(INST_JUMP4, /*offset*/ -jumpBackDist, envPtr); + } else { + TclEmitInstInt1(INST_JUMP1, /*offset*/ -jumpBackDist, envPtr); + } + + /* + * Now that we know the target of the jumpFalse after the foreach_step + * test, update it with the correct distance. If the distance is too + * great (more than 127 bytes), replace that jump with a four byte + * instruction and move the instructions after the jump down. + */ + + jumpDist = (TclCurrCodeOffset() - jumpFalseFixup.codeOffset); + if (TclFixupForwardJump(envPtr, &jumpFalseFixup, jumpDist, 127)) { + /* + * Update the loop body's starting PC offset since it moved down. + */ + + envPtr->excRangeArrayPtr[range].codeOffset += 3; + + /* + * Update the distance for the unconditional jump back to the test + * at the top of the loop since it moved down 3 bytes too. + */ + + jumpBackOffset += 3; + jumpPc = (envPtr->codeStart + jumpBackOffset); + if (jumpBackDist > 120) { + jumpBackDist += 3; + TclUpdateInstInt4AtPc(INST_JUMP4, /*offset*/ -jumpBackDist, + jumpPc); + } else { + jumpBackDist += 3; + TclUpdateInstInt1AtPc(INST_JUMP1, /*offset*/ -jumpBackDist, + jumpPc); + } + } + + /* + * The current PC offset (after the loop's body) is the loop's + * break target. + */ + + envPtr->excRangeArrayPtr[range].breakOffset = TclCurrCodeOffset(); + + /* + * Push an empty string object as the foreach command's result. + */ + + objIndex = TclObjIndexForString("", 0, /*allocStrRep*/ 0, /*inHeap*/ 0, + envPtr); + TclEmitPush(objIndex, envPtr); + if (maxDepth == 0) { + maxDepth = 1; + } + + done: + for (i = 0; i < numLists; i++) { + if (varvList[i] != (char **) NULL) { + ckfree((char *) varvList[i]); + } + } + if (varcList != varcListStaticSpace) { + ckfree((char *) varcList); + ckfree((char *) varvList); + } + envPtr->termOffset = (argInfo.endArray[numWords-1] + 1 - string); + envPtr->pushSimpleWords = savePushSimpleWords; + envPtr->maxStackDepth = maxDepth; + envPtr->excRangeDepth--; + FreeArgInfo(&argInfo); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * DupForeachInfo -- + * + * This procedure duplicates a ForeachInfo structure created as + * auxiliary data during the compilation of a foreach command. + * + * Results: + * A pointer to a newly allocated copy of the existing ForeachInfo + * structure is returned. + * + * Side effects: + * Storage for the copied ForeachInfo record is allocated. If the + * original ForeachInfo structure pointed to any ForeachVarList + * records, these structures are also copied and pointers to them + * are stored in the new ForeachInfo record. + * + *---------------------------------------------------------------------- + */ + +static ClientData +DupForeachInfo(clientData) + ClientData clientData; /* The foreach command's compilation + * auxiliary data to duplicate. */ +{ + register ForeachInfo *srcPtr = (ForeachInfo *) clientData; + ForeachInfo *dupPtr; + register ForeachVarList *srcListPtr, *dupListPtr; + int numLists = srcPtr->numLists; + int numVars, i, j; + + dupPtr = (ForeachInfo *) ckalloc((unsigned) + (sizeof(ForeachInfo) + (numLists * sizeof(ForeachVarList *)))); + dupPtr->numLists = numLists; + dupPtr->firstListTmp = srcPtr->firstListTmp; + dupPtr->loopIterNumTmp = srcPtr->loopIterNumTmp; + + for (i = 0; i < numLists; i++) { + srcListPtr = srcPtr->varLists[i]; + numVars = srcListPtr->numVars; + dupListPtr = (ForeachVarList *) ckalloc((unsigned) + sizeof(ForeachVarList) + numVars*sizeof(int)); + dupListPtr->numVars = numVars; + for (j = 0; j < numVars; j++) { + dupListPtr->varIndexes[j] = srcListPtr->varIndexes[j]; + } + dupPtr->varLists[i] = dupListPtr; + } + return (ClientData) dupPtr; +} + +/* + *---------------------------------------------------------------------- + * + * FreeForeachInfo -- + * + * Procedure to free a ForeachInfo structure created as auxiliary data + * during the compilation of a foreach command. + * + * Results: + * None. + * + * Side effects: + * Storage for the ForeachInfo structure pointed to by the ClientData + * argument is freed as is any ForeachVarList record pointed to by the + * ForeachInfo structure. + * + *---------------------------------------------------------------------- + */ + +static void +FreeForeachInfo(clientData) + ClientData clientData; /* The foreach command's compilation + * auxiliary data to free. */ +{ + register ForeachInfo *infoPtr = (ForeachInfo *) clientData; + register ForeachVarList *listPtr; + int numLists = infoPtr->numLists; + register int i; + + for (i = 0; i < numLists; i++) { + listPtr = infoPtr->varLists[i]; + ckfree((char *) listPtr); + } + ckfree((char *) infoPtr); +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileIfCmd -- + * + * Procedure called to compile the "if" command. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while parsing string. If an error occurs then + * the interpreter's result contains a standard error message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "if" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileIfCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + register char *src = string;/* Points to current source char. */ + register int type; /* Current char's CHAR_TYPE type. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute cmd. */ + JumpFixupArray jumpFalseFixupArray; + /* Used to fix up the ifFalse jump after + * each "if"/"elseif" test when its target + * PC is determined. */ + JumpFixupArray jumpEndFixupArray; + /* Used to fix up the unconditional jump + * after each "then" command to the end of + * the "if" when that PC is determined. */ + char *testSrcStart; + int jumpDist, jumpFalseDist, jumpIndex, objIndex, j, result; + unsigned char *ifFalsePc; + unsigned char opCode; + int savePushSimpleWords = envPtr->pushSimpleWords; + + /* + * Loop compiling "expr then body" clauses after an "if" or "elseif". + */ + + TclInitJumpFixupArray(&jumpFalseFixupArray); + TclInitJumpFixupArray(&jumpEndFixupArray); + while (1) { + /* + * At this point in the loop, we have an expression to test, either + * the main expression or an expression following an "elseif". + * The arguments after the expression must be "then" (optional) and + * a script to execute if the expression is true. + */ + + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type == TCL_COMMAND_END) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: no expression after \"if\" argument", -1); + result = TCL_ERROR; + goto done; + } + + /* + * Compile the "if"/"elseif" test expression. + */ + + testSrcStart = src; + envPtr->pushSimpleWords = 1; + result = CompileExprWord(interp, src, lastChar, flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + Tcl_AddObjErrorInfo(interp, + "\n (\"if\" test expression)", -1); + } + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + src += envPtr->termOffset; + + /* + * Emit the ifFalse jump around the "then" part if the test was + * false. We emit a one byte (relative) jump here, and replace it + * later with a four byte jump if the jump target is more than 127 + * bytes away. + */ + + if (jumpFalseFixupArray.next >= jumpFalseFixupArray.end) { + TclExpandJumpFixupArray(&jumpFalseFixupArray); + } + jumpIndex = jumpFalseFixupArray.next; + jumpFalseFixupArray.next++; + TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, + &(jumpFalseFixupArray.fixup[jumpIndex])); + + /* + * Skip over the optional "then" before the then clause. + */ + + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type == TCL_COMMAND_END) { + char buf[100]; + sprintf(buf, "wrong # args: no script following \"%.20s\" argument", testSrcStart); + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), buf, -1); + result = TCL_ERROR; + goto done; + } + if ((*src == 't') && (strncmp(src, "then", 4) == 0)) { + type = CHAR_TYPE(src+4, lastChar); + if ((type == TCL_SPACE) || (type == TCL_COMMAND_END)) { + src += 4; + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type == TCL_COMMAND_END) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: no script following \"then\" argument", -1); + result = TCL_ERROR; + goto done; + } + } + } + + /* + * Compile the "then" command word inline. + */ + + result = CompileCmdWordInline(interp, src, lastChar, flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + char msg[60]; + sprintf(msg, "\n (\"if\" then script line %d)", + interp->errorLine); + Tcl_AddObjErrorInfo(interp, msg, -1); + } + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + src += envPtr->termOffset; + + /* + * Emit an unconditional jump to the end of the "if" command. We + * emit a one byte jump here, and replace it later with a four byte + * jump if the jump target is more than 127 bytes away. Note that + * both the jumpFalseFixupArray and the jumpEndFixupArray are + * indexed by the same index, "jumpIndex". + */ + + if (jumpEndFixupArray.next >= jumpEndFixupArray.end) { + TclExpandJumpFixupArray(&jumpEndFixupArray); + } + jumpEndFixupArray.next++; + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, + &(jumpEndFixupArray.fixup[jumpIndex])); + + /* + * Now that we know the target of the jumpFalse after the if test, + * update it with the correct distance. We generate a four byte + * jump if the distance is greater than 120 bytes. This is + * conservative, and ensures that we won't have to replace this + * jump if we later also need to replace the preceeding + * unconditional jump to the end of the "if" with a four-byte jump. + */ + + jumpDist = (TclCurrCodeOffset() - jumpFalseFixupArray.fixup[jumpIndex].codeOffset); + if (TclFixupForwardJump(envPtr, + &(jumpFalseFixupArray.fixup[jumpIndex]), jumpDist, 120)) { + /* + * Adjust the code offset for the unconditional jump at the end + * of the last "then" clause. + */ + + jumpEndFixupArray.fixup[jumpIndex].codeOffset += 3; + } + + /* + * Check now for a "elseif" word. If we find one, keep looping. + */ + + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if ((type != TCL_COMMAND_END) + && ((*src == 'e') && (strncmp(src, "elseif", 6) == 0))) { + type = CHAR_TYPE(src+6, lastChar); + if ((type == TCL_SPACE) || (type == TCL_COMMAND_END)) { + src += 6; + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type == TCL_COMMAND_END) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: no expression after \"elseif\" argument", -1); + result = TCL_ERROR; + goto done; + } + continue; /* continue the "expr then body" loop */ + } + } + break; + } /* end of the "expr then body" loop */ + + /* + * No more "elseif expr then body" clauses. Check now for an "else" + * clause. If there is another word, we are at its start. + */ + + if (type != TCL_COMMAND_END) { + if ((*src == 'e') && (strncmp(src, "else", 4) == 0)) { + type = CHAR_TYPE(src+4, lastChar); + if ((type == TCL_SPACE) || (type == TCL_COMMAND_END)) { + src += 4; + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type == TCL_COMMAND_END) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: no script following \"else\" argument", -1); + result = TCL_ERROR; + goto done; + } + } + } + + /* + * Compile the "else" command word inline. + */ + + result = CompileCmdWordInline(interp, src, lastChar, flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + char msg[60]; + sprintf(msg, "\n (\"if\" else script line %d)", + interp->errorLine); + Tcl_AddObjErrorInfo(interp, msg, -1); + } + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + src += envPtr->termOffset; + + /* + * Skip over white space until the end of the command. + */ + + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: extra words after \"else\" clause in \"if\" command", -1); + result = TCL_ERROR; + goto done; + } + } + } else { + /* + * The "if" command has no "else" clause: push an empty string + * object as its result. + */ + + objIndex = TclObjIndexForString("", 0, /*allocStrRep*/ 0, + /*inHeap*/ 0, envPtr); + TclEmitPush(objIndex, envPtr); + maxDepth = TclMax(1, maxDepth); + } + + /* + * Now that we know the target of the unconditional jumps to the end of + * the "if" command, update them with the correct distance. If the + * distance is too great (> 127 bytes), replace the jump with a four + * byte instruction and move instructions after the jump down. + */ + + for (j = jumpEndFixupArray.next; j > 0; j--) { + jumpIndex = (j - 1); /* i.e. process the closest jump first */ + jumpDist = (TclCurrCodeOffset() - jumpEndFixupArray.fixup[jumpIndex].codeOffset); + if (TclFixupForwardJump(envPtr, + &(jumpEndFixupArray.fixup[jumpIndex]), jumpDist, 127)) { + /* + * Adjust the jump distance for the "ifFalse" jump that + * immediately preceeds this jump. We've moved it's target + * (just after this unconditional jump) three bytes down. + */ + + ifFalsePc = (envPtr->codeStart + jumpFalseFixupArray.fixup[jumpIndex].codeOffset); + opCode = *ifFalsePc; + if (opCode == INST_JUMP_FALSE1) { + jumpFalseDist = TclGetInt1AtPtr(ifFalsePc + 1); + jumpFalseDist += 3; + TclStoreInt1AtPtr(jumpFalseDist, (ifFalsePc + 1)); + } else if (opCode == INST_JUMP_FALSE4) { + jumpFalseDist = TclGetInt4AtPtr(ifFalsePc + 1); + jumpFalseDist += 3; + TclStoreInt4AtPtr(jumpFalseDist, (ifFalsePc + 1)); + } else { + panic("TclCompileIfCmd: unexpected opcode updating ifFalse jump"); + } + } + } + + /* + * Free the jumpFixupArray array if malloc'ed storage was used. + */ + + done: + TclFreeJumpFixupArray(&jumpFalseFixupArray); + TclFreeJumpFixupArray(&jumpEndFixupArray); + envPtr->termOffset = (src - string); + envPtr->maxStackDepth = maxDepth; + envPtr->pushSimpleWords = savePushSimpleWords; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileIncrCmd -- + * + * Procedure called to compile the "incr" command. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while parsing string. If an error occurs then + * the interpreter's result contains a standard error message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the "incr" command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "incr" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileIncrCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + Proc *procPtr = envPtr->procPtr; + /* Points to structure describing procedure + * containing incr command, else NULL. */ + register char *src = string; + /* Points to current source char. */ + register int type; /* Current char's CHAR_TYPE type. */ + int simpleVarName; /* 1 if name is just sequence of chars with + * an optional element name in parens. */ + char *name = NULL; /* If simpleVarName, points to first char of + * variable name and nameChars is length. + * Otherwise NULL. */ + char *elName = NULL; /* If simpleVarName, points to first char of + * element name and elNameChars is length. + * Otherwise NULL. */ + int nameChars = 0; /* Length of the var name. Initialized to + * avoid a compiler warning. */ + int elNameChars = 0; /* Length of array's element name, if any. + * Initialized to avoid a compiler + * warning. */ + int incrementGiven; /* 1 if an increment amount was given. */ + int isImmIncrValue = 0; /* 1 if increment amount is a literal + * integer in [-127..127]. */ + int immIncrValue = 0; /* if isImmIncrValue is 1, the immediate + * integer value. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute cmd. */ + int localIndex = -1; /* Index of the variable in the current + * procedure's array of local variables. + * Otherwise -1 if not in a procedure or + * the variable wasn't found. */ + char savedChar; /* Holds the character from string + * termporarily replaced by a null char + * during name processing. */ + int objIndex; /* The object array index for a pushed + * object holding a name part. */ + int savePushSimpleWords = envPtr->pushSimpleWords; + char *p; + int i, result; + + /* + * Parse the next word: the variable name. If it is "simple" (requires + * no substitutions at runtime), divide it up into a simple "name" plus + * an optional "elName". Otherwise, if not simple, just push the name. + */ + + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type == TCL_COMMAND_END) { + badArgs: + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"incr varName ?increment?\"", -1); + result = TCL_ERROR; + goto done; + } + + envPtr->pushSimpleWords = 0; + result = CompileWord(interp, src, lastChar, flags, envPtr); + if (result != TCL_OK) { + goto done; + } + simpleVarName = envPtr->wordIsSimple; + if (simpleVarName) { + name = src; + nameChars = envPtr->numSimpleWordChars; + if (type & (TCL_QUOTE | TCL_OPEN_BRACE)) { + name++; + } + elName = NULL; + elNameChars = 0; + p = name; + for (i = 0; i < nameChars; i++) { + if (*p == '(') { + char *openParen = p; + p = (src + nameChars-1); + if (*p == ')') { /* last char is ')' => array reference */ + nameChars = (openParen - name); + elName = openParen+1; + elNameChars = (p - elName); + } + break; + } + p++; + } + } else { + maxDepth = envPtr->maxStackDepth; + } + src += envPtr->termOffset; + + /* + * See if there is a next word. If so, we are incrementing the variable + * by that value (which must be an integer). + */ + + incrementGiven = 0; + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + incrementGiven = (type != TCL_COMMAND_END); + } + + /* + * Non-simple names have already been pushed. If this is a simple + * variable, either push its name (if a global or an unknown local + * variable) or look up the variable's local frame index. If a local is + * not found, push its name and do the lookup at runtime. If this is an + * array reference, also push the array element. + */ + + if (simpleVarName) { + if (procPtr == NULL) { + savedChar = name[nameChars]; + name[nameChars] = '\0'; + objIndex = TclObjIndexForString(name, nameChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + name[nameChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth = 1; + } else { + localIndex = LookupCompiledLocal(name, nameChars, + /*createIfNew*/ 0, /*flagsIfCreated*/ 0, + envPtr->procPtr); + if ((localIndex < 0) || (localIndex > 255)) { + if (localIndex > 255) { /* we'll push the name */ + localIndex = -1; + } + savedChar = name[nameChars]; + name[nameChars] = '\0'; + objIndex = TclObjIndexForString(name, nameChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + name[nameChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth = 1; + } else { + maxDepth = 0; + } + } + + if (elName != NULL) { + /* + * Parse and push the array element's name. Perform + * substitutions on it, just as is done for quoted strings. + */ + + savedChar = elName[elNameChars]; + elName[elNameChars] = '\0'; + envPtr->pushSimpleWords = 1; + result = TclCompileQuotes(interp, elName, elName+elNameChars, + 0, flags, envPtr); + elName[elNameChars] = savedChar; + if (result != TCL_OK) { + char msg[200]; + sprintf(msg, "\n (parsing index for array \"%.*s\")", + TclMin(nameChars, 100), name); + Tcl_AddObjErrorInfo(interp, msg, -1); + goto done; + } + maxDepth += envPtr->maxStackDepth; + } + } + + /* + * If an increment was given, push the new value. + */ + + if (incrementGiven) { + type = CHAR_TYPE(src, lastChar); + envPtr->pushSimpleWords = 0; + result = CompileWord(interp, src, lastChar, flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + Tcl_AddObjErrorInfo(interp, + "\n (increment expression)", -1); + } + goto done; + } + if (type & (TCL_QUOTE | TCL_OPEN_BRACE)) { + src++; + } + if (envPtr->wordIsSimple) { + /* + * See if the word represents an integer whose formatted + * representation is the same as the word (e.g., this is + * true for 123 and -1 but not for 00005). If so, just + * push an integer object. + */ + + int isCompilableInt = 0; + int numChars = envPtr->numSimpleWordChars; + char savedChar = src[numChars]; + char buf[40]; + Tcl_Obj *objPtr; + long n; + + src[numChars] = '\0'; + if (TclLooksLikeInt(src)) { + int code = TclGetLong(interp, src, &n); + if (code == TCL_OK) { + if ((-127 <= n) && (n <= 127)) { + isCompilableInt = 1; + isImmIncrValue = 1; + immIncrValue = n; + } else { + TclFormatInt(buf, n); + if (strcmp(src, buf) == 0) { + isCompilableInt = 1; + isImmIncrValue = 0; + objIndex = TclObjIndexForString(src, numChars, + /*allocStrRep*/ 0, /*inHeap*/ 0, envPtr); + objPtr = envPtr->objArrayPtr[objIndex]; + + Tcl_InvalidateStringRep(objPtr); + objPtr->internalRep.longValue = n; + objPtr->typePtr = &tclIntType; + + TclEmitPush(objIndex, envPtr); + maxDepth += 1; + } + } + } else { + Tcl_ResetResult(interp); + } + } + if (!isCompilableInt) { + objIndex = TclObjIndexForString(src, numChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + TclEmitPush(objIndex, envPtr); + maxDepth += 1; + } + src[numChars] = savedChar; + } else { + maxDepth += envPtr->maxStackDepth; + } + if (type & (TCL_QUOTE | TCL_OPEN_BRACE)) { + src += (envPtr->termOffset - 1); /* already advanced 1 above */ + } else { + src += envPtr->termOffset; + } + } else { /* no incr amount given so use 1 */ + isImmIncrValue = 1; + immIncrValue = 1; + } + + /* + * Now emit instructions to increment the variable. + */ + + if (simpleVarName) { + if (elName == NULL) { /* scalar */ + if (localIndex >= 0) { + if (isImmIncrValue) { + TclEmitInstUInt1(INST_INCR_SCALAR1_IMM, localIndex, + envPtr); + TclEmitInt1(immIncrValue, envPtr); + } else { + TclEmitInstUInt1(INST_INCR_SCALAR1, localIndex, envPtr); + } + } else { + if (isImmIncrValue) { + TclEmitInstInt1(INST_INCR_SCALAR_STK_IMM, immIncrValue, + envPtr); + } else { + TclEmitOpcode(INST_INCR_SCALAR_STK, envPtr); + } + } + } else { /* array */ + if (localIndex >= 0) { + if (isImmIncrValue) { + TclEmitInstUInt1(INST_INCR_ARRAY1_IMM, localIndex, + envPtr); + TclEmitInt1(immIncrValue, envPtr); + } else { + TclEmitInstUInt1(INST_INCR_ARRAY1, localIndex, envPtr); + } + } else { + if (isImmIncrValue) { + TclEmitInstInt1(INST_INCR_ARRAY_STK_IMM, immIncrValue, + envPtr); + } else { + TclEmitOpcode(INST_INCR_ARRAY_STK, envPtr); + } + } + } + } else { /* non-simple variable name */ + if (isImmIncrValue) { + TclEmitInstInt1(INST_INCR_STK_IMM, immIncrValue, envPtr); + } else { + TclEmitOpcode(INST_INCR_STK, envPtr); + } + } + + /* + * Skip over white space until the end of the command. + */ + + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + goto badArgs; + } + } + + done: + envPtr->termOffset = (src - string); + envPtr->maxStackDepth = maxDepth; + envPtr->pushSimpleWords = savePushSimpleWords; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileSetCmd -- + * + * Procedure called to compile the "set" command. + * + * Results: + * The return value is a standard Tcl result, which is normally TCL_OK + * unless there was an error while parsing string. If an error occurs + * then the interpreter's result contains a standard error message. If + * complation fails because the set command requires a second level of + * substitutions, TCL_OUT_LINE_COMPILE is returned indicating that the + * set command should be compiled "out of line" by emitting code to + * invoke its command procedure (Tcl_SetCmd) at runtime. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the incr command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "set" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileSetCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + Proc *procPtr = envPtr->procPtr; + /* Points to structure describing procedure + * containing the set command, else NULL. */ + ArgInfo argInfo; /* Structure holding information about the + * start and end of each argument word. */ + int simpleVarName; /* 1 if name is just sequence of chars with + * an optional element name in parens. */ + char *elName = NULL; /* If simpleVarName, points to first char of + * element name and elNameChars is length. + * Otherwise NULL. */ + int isAssignment; /* 1 if assigning value to var, else 0. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute cmd. */ + int localIndex = -1; /* Index of the variable in the current + * procedure's array of local variables. + * Otherwise -1 if not in a procedure, the + * name contains "::"s, or the variable + * wasn't found. */ + char savedChar; /* Holds the character from string + * termporarily replaced by a null char + * during name processing. */ + int objIndex = -1; /* The object array index for a pushed + * object holding a name part. Initialized + * to avoid a compiler warning. */ + char *wordStart, *p; + int numWords, isCompilableInt, i, result; + Tcl_Obj *objPtr; + int savePushSimpleWords = envPtr->pushSimpleWords; + + /* + * Scan the words of the command and record the start and finish of + * each argument word. + */ + + InitArgInfo(&argInfo); + result = CollectArgInfo(interp, string, lastChar, flags, &argInfo); + numWords = argInfo.numArgs; /* i.e., the # after the command name */ + if (result != TCL_OK) { + goto done; + } + if ((numWords < 1) || (numWords > 2)) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"set varName ?newValue?\"", -1); + result = TCL_ERROR; + goto done; + } + isAssignment = (numWords == 2); + + /* + * Parse the next word: the variable name. If the name is enclosed in + * quotes or braces, we return TCL_OUT_LINE_COMPILE and call the set + * command procedure at runtime since this makes sure that a second + * round of substitutions is done properly. + */ + + wordStart = argInfo.startArray[0]; /* start of 1st arg word: varname */ + if ((*wordStart == '{') || (*wordStart == '"')) { + result = TCL_OUT_LINE_COMPILE; + goto done; + } + + /* + * Check whether the name is "simple": requires no substitutions at + * runtime. + */ + + envPtr->pushSimpleWords = 0; + result = CompileWord(interp, wordStart, argInfo.endArray[0] + 1, + flags, envPtr); + if (result != TCL_OK) { + goto done; + } + simpleVarName = envPtr->wordIsSimple; + + if (!simpleVarName) { + /* + * The name isn't simple. CompileWord already pushed it. + */ + + maxDepth = envPtr->maxStackDepth; + } else { + char *name; /* If simpleVarName, points to first char of + * variable name and nameChars is length. + * Otherwise NULL. */ + int nameChars; /* Length of the var name. */ + int nameHasNsSeparators = 0; + /* Set 1 if name contains "::"s. */ + int elNameChars; /* Length of array's element name if any. */ + + /* + * A simple name. First divide it up into "name" plus "elName" + * for an array element name, if any. + */ + + name = wordStart; + nameChars = envPtr->numSimpleWordChars; + elName = NULL; + elNameChars = 0; + + p = name; + for (i = 0; i < nameChars; i++) { + if (*p == '(') { + char *openParen = p; + p = (name + nameChars-1); + if (*p == ')') { /* last char is ')' => array reference */ + nameChars = (openParen - name); + elName = openParen+1; + elNameChars = (p - elName); + } + break; + } + p++; + } + + /* + * Determine if name has any namespace separators (::'s). + */ + + p = name; + for (i = 0; i < nameChars; i++) { + if ((*p == ':') && ((i+1) < nameChars) && (*(p+1) == ':')) { + nameHasNsSeparators = 1; + break; + } + p++; + } + + /* + * Now either push the name or determine its index in the array of + * local variables in a procedure frame. Note that if we are + * compiling a procedure the variable must be local unless its + * name has namespace separators ("::"s). Note also that global + * variables are implemented by a local variable that "points" to + * the real global. There are two cases: + * 1) We are not compiling a procedure body. Push the global + * variable's name and do the lookup at runtime. + * 2) We are compiling a procedure and the name has "::"s. + * Push the namespace variable's name and do the lookup at + * runtime. + * 3) We are compiling a procedure and the name has no "::"s. + * If the variable has already been allocated an local index, + * just look it up. If the variable is unknown and we are + * doing an assignment, allocate a new index. Otherwise, + * push the name and try to do the lookup at runtime. + */ + + if ((procPtr == NULL) || nameHasNsSeparators) { + savedChar = name[nameChars]; + name[nameChars] = '\0'; + objIndex = TclObjIndexForString(name, nameChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + name[nameChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth = 1; + } else { + localIndex = LookupCompiledLocal(name, nameChars, + /*createIfNew*/ isAssignment, + /*flagsIfCreated*/ + ((elName == NULL)? VAR_SCALAR : VAR_ARRAY), + envPtr->procPtr); + if (localIndex >= 0) { + maxDepth = 0; + } else { + savedChar = name[nameChars]; + name[nameChars] = '\0'; + objIndex = TclObjIndexForString(name, nameChars, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + name[nameChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth = 1; + } + } + + /* + * If we are dealing with a reference to an array element, push the + * array element. Perform substitutions on it, just as is done + * for quoted strings. + */ + + if (elName != NULL) { + savedChar = elName[elNameChars]; + elName[elNameChars] = '\0'; + envPtr->pushSimpleWords = 1; + result = TclCompileQuotes(interp, elName, elName+elNameChars, + 0, flags, envPtr); + elName[elNameChars] = savedChar; + if (result != TCL_OK) { + char msg[200]; + sprintf(msg, "\n (parsing index for array \"%.*s\")", + TclMin(nameChars, 100), name); + Tcl_AddObjErrorInfo(interp, msg, -1); + goto done; + } + maxDepth += envPtr->maxStackDepth; + } + } + + /* + * If we are doing an assignment, push the new value. + */ + + if (isAssignment) { + wordStart = argInfo.startArray[1]; /* start of 2nd arg word */ + envPtr->pushSimpleWords = 0; /* we will handle simple words */ + result = CompileWord(interp, wordStart, argInfo.endArray[1] + 1, + flags, envPtr); + if (result != TCL_OK) { + goto done; + } + if (!envPtr->wordIsSimple) { + /* + * The value isn't simple. CompileWord already pushed it. + */ + + maxDepth += envPtr->maxStackDepth; + } else { + /* + * The value is simple. See if the word represents an integer + * whose formatted representation is the same as the word (e.g., + * this is true for 123 and -1 but not for 00005). If so, just + * push an integer object. + */ + + char buf[40]; + long n; + + p = wordStart; + if ((*wordStart == '"') || (*wordStart == '{')) { + p++; + } + savedChar = p[envPtr->numSimpleWordChars]; + p[envPtr->numSimpleWordChars] = '\0'; + isCompilableInt = 0; + if (TclLooksLikeInt(p)) { + int code = TclGetLong(interp, p, &n); + if (code == TCL_OK) { + TclFormatInt(buf, n); + if (strcmp(p, buf) == 0) { + isCompilableInt = 1; + objIndex = TclObjIndexForString(p, + envPtr->numSimpleWordChars, + /*allocStrRep*/ 0, /*inHeap*/ 0, envPtr); + objPtr = envPtr->objArrayPtr[objIndex]; + + Tcl_InvalidateStringRep(objPtr); + objPtr->internalRep.longValue = n; + objPtr->typePtr = &tclIntType; + } + } else { + Tcl_ResetResult(interp); + } + } + if (!isCompilableInt) { + objIndex = TclObjIndexForString(p, + envPtr->numSimpleWordChars, /*allocStrRep*/ 1, + /*inHeap*/ 0, envPtr); + } + p[envPtr->numSimpleWordChars] = savedChar; + TclEmitPush(objIndex, envPtr); + maxDepth += 1; + } + } + + /* + * Now emit instructions to set/retrieve the variable. + */ + + if (simpleVarName) { + if (elName == NULL) { /* scalar */ + if (localIndex >= 0) { + if (localIndex <= 255) { + TclEmitInstUInt1((isAssignment? + INST_STORE_SCALAR1 : INST_LOAD_SCALAR1), + localIndex, envPtr); + } else { + TclEmitInstUInt4((isAssignment? + INST_STORE_SCALAR4 : INST_LOAD_SCALAR4), + localIndex, envPtr); + } + } else { + TclEmitOpcode((isAssignment? + INST_STORE_SCALAR_STK : INST_LOAD_SCALAR_STK), + envPtr); + } + } else { /* array */ + if (localIndex >= 0) { + if (localIndex <= 255) { + TclEmitInstUInt1((isAssignment? + INST_STORE_ARRAY1 : INST_LOAD_ARRAY1), + localIndex, envPtr); + } else { + TclEmitInstUInt4((isAssignment? + INST_STORE_ARRAY4 : INST_LOAD_ARRAY4), + localIndex, envPtr); + } + } else { + TclEmitOpcode((isAssignment? + INST_STORE_ARRAY_STK : INST_LOAD_ARRAY_STK), + envPtr); + } + } + } else { /* non-simple variable name */ + TclEmitOpcode((isAssignment? INST_STORE_STK : INST_LOAD_STK), envPtr); + } + + done: + if (numWords == 0) { + envPtr->termOffset = 0; + } else { + envPtr->termOffset = (argInfo.endArray[numWords-1] + 1 - string); + } + envPtr->pushSimpleWords = savePushSimpleWords; + envPtr->maxStackDepth = maxDepth; + FreeArgInfo(&argInfo); + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclCompileWhileCmd -- + * + * Procedure called to compile the "while" command. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK if + * compilation was successful. If an error occurs then the + * interpreter's result contains a standard error message and TCL_ERROR + * is returned. If compilation failed because the command is too + * complex for TclCompileWhileCmd, TCL_OUT_LINE_COMPILE is returned + * indicating that the while command should be compiled "out of line" + * by emitting code to invoke its command procedure at runtime. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the "while" command. + * + * Side effects: + * Instructions are added to envPtr to evaluate the "while" command + * at runtime. + * + *---------------------------------------------------------------------- + */ + +int +TclCompileWhileCmd(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + register char *src = string;/* Points to current source char. */ + register int type; /* Current char's CHAR_TYPE type. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute cmd. */ + int range; /* Index in the ExceptionRange array of the + * ExceptionRange record for this loop. */ + JumpFixup jumpFalseFixup; /* Used to update or replace the ifFalse + * jump after test when its target PC is + * determined. */ + unsigned char *jumpPc; + int jumpDist, jumpBackDist, jumpBackOffset, objIndex, result; + int savePushSimpleWords = envPtr->pushSimpleWords; + + envPtr->excRangeDepth++; + envPtr->maxExcRangeDepth = + TclMax(envPtr->excRangeDepth, envPtr->maxExcRangeDepth); + + /* + * Create and initialize a ExceptionRange record to hold information + * about this loop. This is used to implement break and continue. + */ + + range = CreateExceptionRange(LOOP_EXCEPTION_RANGE, envPtr); + envPtr->excRangeArrayPtr[range].continueOffset = TclCurrCodeOffset(); + + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type == TCL_COMMAND_END) { + badArgs: + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "wrong # args: should be \"while test command\"", -1); + result = TCL_ERROR; + goto done; + } + + /* + * If the test expression is enclosed in quotes (""s), don't compile + * the while inline. As a result of Tcl's two level substitution + * semantics for expressions, the expression might have a constant + * value that results in the loop never executing, or executing forever. + * Consider "set x 0; while "$x < 5" {incr x}": the loop body should + * never be executed. + */ + + if (*src == '"') { + result = TCL_OUT_LINE_COMPILE; + goto done; + } + + /* + * Compile the next word: the test expression. + */ + + envPtr->pushSimpleWords = 1; + result = CompileExprWord(interp, src, lastChar, flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + Tcl_AddObjErrorInfo(interp, "\n (\"while\" test expression)", -1); + } + goto done; + } + maxDepth = envPtr->maxStackDepth; + src += envPtr->termOffset; + + /* + * Emit the ifFalse jump that terminates the while if the test was + * false. We emit a one byte (relative) jump here, and replace it + * later with a four byte jump if the jump target is more than + * 127 bytes away. + */ + + TclEmitForwardJump(envPtr, TCL_FALSE_JUMP, &jumpFalseFixup); + + /* + * Compile the loop body word inline. Also register the loop body's + * starting PC offset and byte length in the its ExceptionRange record. + */ + + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type == TCL_COMMAND_END) { + goto badArgs; + } + + envPtr->excRangeArrayPtr[range].codeOffset = TclCurrCodeOffset(); + result = CompileCmdWordInline(interp, src, lastChar, + flags, envPtr); + if (result != TCL_OK) { + if (result == TCL_ERROR) { + char msg[60]; + sprintf(msg, "\n (\"while\" body line %d)", interp->errorLine); + Tcl_AddObjErrorInfo(interp, msg, -1); + } + goto done; + } + maxDepth = TclMax(envPtr->maxStackDepth, maxDepth); + src += envPtr->termOffset; + envPtr->excRangeArrayPtr[range].numCodeBytes = + (TclCurrCodeOffset() - envPtr->excRangeArrayPtr[range].codeOffset); + + /* + * Discard the loop body's result. + */ + + TclEmitOpcode(INST_POP, envPtr); + + /* + * Emit the unconditional jump back to the test at the top of the + * loop. We generate a four byte jump if the distance to the while's + * test is greater than 120 bytes. This is conservative, and ensures + * that we won't have to replace this unconditional jump if we later + * need to replace the ifFalse jump with a four-byte jump. + */ + + jumpBackOffset = TclCurrCodeOffset(); + jumpBackDist = + (jumpBackOffset - envPtr->excRangeArrayPtr[range].continueOffset); + if (jumpBackDist > 120) { + TclEmitInstInt4(INST_JUMP4, /*offset*/ -jumpBackDist, envPtr); + } else { + TclEmitInstInt1(INST_JUMP1, /*offset*/ -jumpBackDist, envPtr); + } + + /* + * Now that we know the target of the jumpFalse after the test, update + * it with the correct distance. If the distance is too great (more + * than 127 bytes), replace that jump with a four byte instruction and + * move the instructions after the jump down. + */ + + jumpDist = (TclCurrCodeOffset() - jumpFalseFixup.codeOffset); + if (TclFixupForwardJump(envPtr, &jumpFalseFixup, jumpDist, 127)) { + /* + * Update the loop body's starting PC offset since it moved down. + */ + + envPtr->excRangeArrayPtr[range].codeOffset += 3; + + /* + * Update the distance for the unconditional jump back to the test + * at the top of the loop since it moved down 3 bytes too. + */ + + jumpBackOffset += 3; + jumpPc = (envPtr->codeStart + jumpBackOffset); + if (jumpBackDist > 120) { + jumpBackDist += 3; + TclUpdateInstInt4AtPc(INST_JUMP4, /*offset*/ -jumpBackDist, + jumpPc); + } else { + jumpBackDist += 3; + TclUpdateInstInt1AtPc(INST_JUMP1, /*offset*/ -jumpBackDist, + jumpPc); + } + } + + /* + * The current PC offset (after the loop's body) is the loop's + * break target. + */ + + envPtr->excRangeArrayPtr[range].breakOffset = TclCurrCodeOffset(); + + /* + * Push an empty string object as the while command's result. + */ + + objIndex = TclObjIndexForString("", 0, /*allocStrRep*/ 0, /*inHeap*/ 0, + envPtr); + TclEmitPush(objIndex, envPtr); + if (maxDepth == 0) { + maxDepth = 1; + } + + /* + * Skip over white space until the end of the command. + */ + + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type != TCL_COMMAND_END) { + goto badArgs; + } + } + + done: + envPtr->termOffset = (src - string); + envPtr->pushSimpleWords = savePushSimpleWords; + envPtr->maxStackDepth = maxDepth; + envPtr->excRangeDepth--; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * CompileExprWord -- + * + * Procedure that compiles a Tcl expression in a command word. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while compiling string. If an error occurs then + * the interpreter's result contains a standard error message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the "expr" word. + * + * Side effects: + * Instructions are added to envPtr to evaluate the expression word + * at runtime. + * + *---------------------------------------------------------------------- + */ + +static int +CompileExprWord(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + register char *src = string;/* Points to current source char. */ + register int type; /* Current char's CHAR_TYPE type. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute the expression. */ + int nestedCmd = (flags & TCL_BRACKET_TERM); + /* 1 if script being compiled is a nested + * command and is terminated by a ']'; + * otherwise 0. */ + char *first, *last; /* Points to the first and last significant + * characters of the word. */ + char savedChar; /* Holds the character termporarily replaced + * by a null character during compilation + * of the expression. */ + int inlineCode; /* 1 if inline "optimistic" code is + * emitted for the expression; else 0. */ + int range = -1; /* If we inline compile an un-{}'d + * expression, the index for its catch range + * record in the ExceptionRange array. + * Initialized to avoid compile warning. */ + JumpFixup jumpFixup; /* Used to emit the "success" jump after + * the inline expression code. */ + char *p; + char c; + int savePushSimpleWords = envPtr->pushSimpleWords; + int saveExprIsJustVarRef = envPtr->exprIsJustVarRef; + int saveExprIsComparison = envPtr->exprIsComparison; + int numChars, result; + + /* + * Skip over leading white space. + */ + + AdvanceToNextWord(src, envPtr); + src += envPtr->termOffset; + type = CHAR_TYPE(src, lastChar); + if (type == TCL_COMMAND_END) { + badArgs: + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "malformed expression word", -1); + result = TCL_ERROR; + goto done; + } + + /* + * If the word is enclosed in {}s, we may strip them off and safely + * compile the expression into an inline sequence of instructions using + * TclCompileExpr. We know these instructions will have the right Tcl7.x + * expression semantics. + * + * Otherwise, if the word is not enclosed in {}s, we may need to call + * the expr command (Tcl_ExprObjCmd) at runtime. This recompiles the + * expression each time (typically) and so is slow. However, there are + * some circumstances where we can still compile inline instructions + * "optimistically" and check, during their execution, for double + * substitutions (these appear as nonnumeric operands). We check for any + * backslash or command substitutions. If none appear, and only variable + * substitutions are found, we generate inline instructions. + * + * For now, if the expression is not enclosed in {}s, we call the expr + * command at runtime if either command or backslash substitutions + * appear (but not if only variable substitutions appear). + */ + + if (*src == '{') { + /* + * Inline compile the expression inside {}s. + */ + + first = src+1; + src = TclWordEnd(src, lastChar, nestedCmd, NULL); + if (*src == 0) { + goto badArgs; + } + if (*src != '}') { + goto badArgs; + } + last = (src-1); + + numChars = (last - first + 1); + savedChar = first[numChars]; + first[numChars] = '\0'; + result = TclCompileExpr(interp, first, first+numChars, + flags, envPtr); + first[numChars] = savedChar; + + src++; + maxDepth = envPtr->maxStackDepth; + } else { + /* + * No braces. If the expression is enclosed in '"'s, call the expr + * cmd at runtime. Otherwise, scan the word's characters looking for + * any '['s or (for now) '\'s. If any are found, just call expr cmd + * at runtime. + */ + + first = src; + last = TclWordEnd(first, lastChar, nestedCmd, NULL); + if (*last == 0) { /* word doesn't end properly. */ + src = last; + goto badArgs; + } + + inlineCode = 1; + if ((*first == '"') && (*last == '"')) { + inlineCode = 0; + } else { + for (p = first; p <= last; p++) { + c = *p; + if ((c == '[') || (c == '\\')) { + inlineCode = 0; + break; + } + } + } + + if (inlineCode) { + /* + * Inline compile the expression inside a "catch" so that a + * runtime error will back off to make a (slow) call on expr. + */ + + int startCodeOffset = (envPtr->codeNext - envPtr->codeStart); + int startRangeNext = envPtr->excRangeArrayNext; + + /* + * Create a ExceptionRange record to hold information about + * the "catch" range for the expression's inline code. Also + * emit the instruction to mark the start of the range. + */ + + envPtr->excRangeDepth++; + envPtr->maxExcRangeDepth = + TclMax(envPtr->excRangeDepth, envPtr->maxExcRangeDepth); + range = CreateExceptionRange(CATCH_EXCEPTION_RANGE, envPtr); + TclEmitInstUInt4(INST_BEGIN_CATCH4, range, envPtr); + + /* + * Inline compile the expression. + */ + + envPtr->excRangeArrayPtr[range].codeOffset = TclCurrCodeOffset(); + numChars = (last - first + 1); + savedChar = first[numChars]; + first[numChars] = '\0'; + result = TclCompileExpr(interp, first, first + numChars, + flags, envPtr); + first[numChars] = savedChar; + + envPtr->excRangeArrayPtr[range].numCodeBytes = + TclCurrCodeOffset() - envPtr->excRangeArrayPtr[range].codeOffset; + + if ((result != TCL_OK) || (envPtr->exprIsJustVarRef) + || (envPtr->exprIsComparison)) { + /* + * We must call the expr command at runtime. Either there + * was a compilation error or the inline code might fail to + * give the correct 2 level substitution semantics. + * + * The latter can happen if the expression consisted of just + * a single variable reference or if the top-level operator + * in the expr is a comparison (which might operate on + * strings). In the latter case, the expression's code might + * execute (apparently) successfully but produce the wrong + * result. We depend on its execution failing if a second + * level of substitutions is required. This causes the + * "catch" code we generate around the inline code to back + * off to a call on the expr command at runtime, and this + * always gives the right 2 level substitution semantics. + * + * We delete the inline code by backing up the code pc and + * catch index. Note that if there was a compilation error, + * we can't report the error yet since the expression might + * be valid after the second round of substitutions. + */ + + envPtr->codeNext = (envPtr->codeStart + startCodeOffset); + envPtr->excRangeArrayNext = startRangeNext; + inlineCode = 0; + } else { + TclEmitOpcode(INST_END_CATCH, envPtr); + TclEmitForwardJump(envPtr, TCL_UNCONDITIONAL_JUMP, &jumpFixup); + envPtr->excRangeArrayPtr[range].catchOffset = TclCurrCodeOffset(); + } + } + + /* + * Arrange to call expr at runtime with the (already substituted + * once) expression word on the stack. + */ + + envPtr->pushSimpleWords = 1; + result = CompileWord(interp, first, lastChar, flags, envPtr); + src += envPtr->termOffset; + maxDepth = envPtr->maxStackDepth; + if (result == TCL_OK) { + TclEmitOpcode(INST_EXPR_STK, envPtr); + } + + /* + * If emitting inline code for this non-{}'d expression, update + * the target of the jump after that inline code. + */ + + if (inlineCode) { + int jumpDist = (TclCurrCodeOffset() - jumpFixup.codeOffset); + if (TclFixupForwardJump(envPtr, &jumpFixup, jumpDist, 127)) { + /* + * Update the inline expression code's catch ExceptionRange + * target since it, being after the jump, also moved down. + */ + + envPtr->excRangeArrayPtr[range].catchOffset += 3; + } + } + } /* if expression isn't in {}s */ + + done: + envPtr->termOffset = (src - string); + envPtr->maxStackDepth = maxDepth; + envPtr->pushSimpleWords = savePushSimpleWords; + envPtr->exprIsJustVarRef = saveExprIsJustVarRef; + envPtr->exprIsComparison = saveExprIsComparison; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * CompileCmdWordInline -- + * + * Procedure that compiles a Tcl command word inline. If the word is + * enclosed in quotes or braces, we call TclCompileString to compile it + * after stripping them off. Otherwise, we normally push the word's + * value and call eval at runtime, but if the word is just a sequence + * of alphanumeric characters, we emit an invoke instruction + * directly. This procedure assumes that string points to the start of + * the word to compile. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while compiling string. If an error occurs then + * the interpreter's result contains a standard error message. + * + * envPtr->termOffset is filled in with the offset of the character in + * "string" just after the last one successfully processed. + * + * envPtr->maxStackDepth is updated with the maximum number of stack + * elements needed to execute the command. + * + * Side effects: + * Instructions are added to envPtr to execute the command word + * at runtime. + * + *---------------------------------------------------------------------- + */ + +static int +CompileCmdWordInline(interp, string, lastChar, flags, envPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source string to compile. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + Interp *iPtr = (Interp *) interp; + register char *src = string;/* Points to current source char. */ + register int type; /* Current char's CHAR_TYPE type. */ + int maxDepth = 0; /* Maximum number of stack elements needed + * to execute cmd. */ + char *termPtr; /* Points to char that terminated braced + * string. */ + char savedChar; /* Holds the character termporarily replaced + * by a null character during compilation + * of the command. */ + int savePushSimpleWords = envPtr->pushSimpleWords; + int objIndex; + int result = TCL_OK; + register char c; + + type = CHAR_TYPE(src, lastChar); + if (type & (TCL_QUOTE | TCL_OPEN_BRACE)) { + src++; + envPtr->pushSimpleWords = 0; + if (type == TCL_QUOTE) { + result = TclCompileQuotes(interp, src, lastChar, + '"', flags, envPtr); + } else { + result = CompileBraces(interp, src, lastChar, flags, envPtr); + } + if (result != TCL_OK) { + goto done; + } + + /* + * Make sure the terminating character is the end of word. + */ + + termPtr = (src + envPtr->termOffset); + c = *termPtr; + if ((c == '\\') && (*(termPtr+1) == '\n')) { + /* + * Line is continued on next line; the backslash-newline turns + * into space, which terminates the word. + */ + } else { + type = CHAR_TYPE(termPtr, lastChar); + if ((type != TCL_SPACE) && (type != TCL_COMMAND_END)) { + Tcl_ResetResult(interp); + if (*(src-1) == '"') { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "extra characters after close-quote", -1); + } else { + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "extra characters after close-brace", -1); + } + result = TCL_ERROR; + goto done; + } + } + + if (envPtr->wordIsSimple) { + /* + * A simple word enclosed in "" or {}s. Call TclCompileString to + * compile it inline. Add a null character after the end of the + * quoted or braced string: i.e., at the " or }. Turn the + * flag bit TCL_BRACKET_TERM off since the recursively + * compiled subcommand is now terminated by a null character. + */ + char *closeCharPos = (termPtr - 1); + + savedChar = *closeCharPos; + *closeCharPos = '\0'; + result = TclCompileString(interp, src, closeCharPos, + (flags & ~TCL_BRACKET_TERM), envPtr); + *closeCharPos = savedChar; + if (result != TCL_OK) { + goto done; + } + } else { + /* + * The braced string contained a backslash-newline. Call eval + * at runtime. + */ + TclEmitOpcode(INST_EVAL_STK, envPtr); + } + src = termPtr; + maxDepth = envPtr->maxStackDepth; + } else { + /* + * Not a braced or quoted string. We normally push the word's + * value and call eval at runtime. However, if the word is just + * a sequence of alphanumeric characters, we call its compile + * procedure, if any, or otherwise just emit an invoke instruction. + */ + + char *p = src; + c = *p; + while (isalnum(UCHAR(c)) || (c == '_')) { + p++; + c = *p; + } + type = CHAR_TYPE(p, lastChar); + if ((p > src) && (type == TCL_COMMAND_END)) { + /* + * Look for a compile procedure and call it. Otherwise emit an + * invoke instruction to call the command at runtime. + */ + + Tcl_Command cmd; + Command *cmdPtr = NULL; + int wasCompiled = 0; + + savedChar = *p; + *p = '\0'; + + cmd = Tcl_FindCommand(interp, src, (Tcl_Namespace *) NULL, + /*flags*/ 0); + if (cmd != (Tcl_Command) NULL) { + cmdPtr = (Command *) cmd; + } + if (cmdPtr != NULL && cmdPtr->compileProc != NULL) { + *p = savedChar; + src = p; + iPtr->flags &= ~(ERR_ALREADY_LOGGED | ERR_IN_PROGRESS + | ERROR_CODE_SET); + result = (*(cmdPtr->compileProc))(interp, src, lastChar, flags, envPtr); + if (result != TCL_OK) { + goto done; + } + wasCompiled = 1; + src += envPtr->termOffset; + maxDepth = envPtr->maxStackDepth; + } + if (!wasCompiled) { + objIndex = TclObjIndexForString(src, p-src, + /*allocStrRep*/ 1, /*inHeap*/ 0, envPtr); + *p = savedChar; + TclEmitPush(objIndex, envPtr); + TclEmitInstUInt1(INST_INVOKE_STK1, 1, envPtr); + src = p; + maxDepth = 1; + } + } else { + /* + * Push the word and call eval at runtime. + */ + + envPtr->pushSimpleWords = 1; + result = CompileWord(interp, src, lastChar, flags, envPtr); + if (result != TCL_OK) { + goto done; + } + TclEmitOpcode(INST_EVAL_STK, envPtr); + src += envPtr->termOffset; + maxDepth = envPtr->maxStackDepth; + } + } + + done: + envPtr->termOffset = (src - string); + envPtr->maxStackDepth = maxDepth; + envPtr->pushSimpleWords = savePushSimpleWords; + return result; +} + +/* + *---------------------------------------------------------------------- + * + * LookupCompiledLocal -- + * + * This procedure is called at compile time to look up and optionally + * allocate an entry ("slot") for a variable in a procedure's array of + * local variables. If the variable's name is NULL, a new temporary + * variable is always created. (Such temporary variables can only be + * referenced using their slot index.) + * + * Results: + * If createIfNew is 0 (false) and the name is non-NULL, then if the + * variable is found, the index of its entry in the procedure's array + * of local variables is returned; otherwise -1 is returned. + * If name is NULL, the index of a new temporary variable is returned. + * Finally, if createIfNew is 1 and name is non-NULL, the index of a + * new entry is returned. + * + * Side effects: + * Creates and registers a new local variable if createIfNew is 1 and + * the variable is unknown, or if the name is NULL. + * + *---------------------------------------------------------------------- + */ + +static int +LookupCompiledLocal(name, nameChars, createIfNew, flagsIfCreated, procPtr) + register char *name; /* Points to first character of the name of + * a scalar or array variable. If NULL, a + * temporary var should be created. */ + int nameChars; /* The length of the name excluding the + * terminating null character. */ + int createIfNew; /* 1 to allocate a local frame entry for the + * variable if it is new. */ + int flagsIfCreated; /* Flag bits for the compiled local if + * created. Only VAR_SCALAR, VAR_ARRAY, and + * VAR_LINK make sense. */ + register Proc *procPtr; /* Points to structure describing procedure + * containing the variable reference. */ +{ + register CompiledLocal *localPtr; + int localIndex = -1; + register int i; + + /* + * If not creating a temporary, does a local variable of the specified + * name already exist? + */ + + if (name != NULL) { + int localCt = procPtr->numCompiledLocals; + localPtr = procPtr->firstLocalPtr; + for (i = 0; i < localCt; i++) { + if (!localPtr->isTemp) { + char *localName = localPtr->name; + if ((name[0] == localName[0]) + && (nameChars == localPtr->nameLength) + && (strncmp(name, localName, (unsigned) nameChars) == 0)) { + return i; + } + } + localPtr = localPtr->nextPtr; + } + } + + /* + * Create a new variable if appropriate. + */ + + if (createIfNew || (name == NULL)) { + localIndex = procPtr->numCompiledLocals; + localPtr = (CompiledLocal *) ckalloc((unsigned) + (sizeof(CompiledLocal) - sizeof(localPtr->name) + + nameChars+1)); + if (procPtr->firstLocalPtr == NULL) { + procPtr->firstLocalPtr = procPtr->lastLocalPtr = localPtr; + } else { + procPtr->lastLocalPtr->nextPtr = localPtr; + procPtr->lastLocalPtr = localPtr; + } + localPtr->nextPtr = NULL; + localPtr->nameLength = nameChars; + localPtr->frameIndex = localIndex; + localPtr->isArg = 0; + localPtr->isTemp = (name == NULL); + localPtr->flags = flagsIfCreated; + localPtr->defValuePtr = NULL; + if (name != NULL) { + memcpy((VOID *) localPtr->name, (VOID *) name, (size_t) nameChars); + } + localPtr->name[nameChars] = '\0'; + procPtr->numCompiledLocals++; + } + return localIndex; +} + +/* + *---------------------------------------------------------------------- + * + * AdvanceToNextWord -- + * + * This procedure is called to skip over any leading white space at the + * start of a word. Note that a backslash-newline is treated as a + * space. + * + * Results: + * None. + * + * Side effects: + * Updates envPtr->termOffset with the offset of the first + * character in "string" that was not white space or a + * backslash-newline. This might be the offset of the character that + * ends the command: a newline, null, semicolon, or close-bracket. + * + *---------------------------------------------------------------------- + */ + +static void +AdvanceToNextWord(string, envPtr) + char *string; /* The source string to compile. */ + CompileEnv *envPtr; /* Holds resulting instructions. */ +{ + register char *src; /* Points to current source char. */ + register int type; /* Current char's CHAR_TYPE type. */ + + src = string; + type = CHAR_TYPE(src, src+1); + while (type & (TCL_SPACE | TCL_BACKSLASH)) { + if (type == TCL_BACKSLASH) { + if (src[1] == '\n') { + src += 2; + } else { + break; /* exit loop; no longer white space */ + } + } else { + src++; + } + type = CHAR_TYPE(src, src+1); + } + envPtr->termOffset = (src - string); +} + +/* + *---------------------------------------------------------------------- + * + * Tcl_Backslash -- + * + * Figure out how to handle a backslash sequence. + * + * Results: + * The return value is the character that should be substituted + * in place of the backslash sequence that starts at src. If + * readPtr isn't NULL then it is filled in with a count of the + * number of characters in the backslash sequence. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +char +Tcl_Backslash(src, readPtr) + CONST char *src; /* Points to the backslash character of + * a backslash sequence. */ + int *readPtr; /* Fill in with number of characters read + * from src, unless NULL. */ +{ + CONST char *p = src + 1; + char result; + int count; + + count = 2; + + switch (*p) { + /* + * Note: in the conversions below, use absolute values (e.g., + * 0xa) rather than symbolic values (e.g. \n) that get converted + * by the compiler. It's possible that compilers on some + * platforms will do the symbolic conversions differently, which + * could result in non-portable Tcl scripts. + */ + + case 'a': + result = 0x7; + break; + case 'b': + result = 0x8; + break; + case 'f': + result = 0xc; + break; + case 'n': + result = 0xa; + break; + case 'r': + result = 0xd; + break; + case 't': + result = 0x9; + break; + case 'v': + result = 0xb; + break; + case 'x': + if (isxdigit(UCHAR(p[1]))) { + char *end; + + result = (char) strtoul(p+1, &end, 16); + count = end - src; + } else { + count = 2; + result = 'x'; + } + break; + case '\n': + do { + p++; + } while ((*p == ' ') || (*p == '\t')); + result = ' '; + count = p - src; + break; + case 0: + result = '\\'; + count = 1; + break; + default: + if (isdigit(UCHAR(*p))) { + result = (char)(*p - '0'); + p++; + if (!isdigit(UCHAR(*p))) { + break; + } + count = 3; + result = (char)((result << 3) + (*p - '0')); + p++; + if (!isdigit(UCHAR(*p))) { + break; + } + count = 4; + result = (char)((result << 3) + (*p - '0')); + break; + } + result = *p; + count = 2; + break; + } + + if (readPtr != NULL) { + *readPtr = count; + } + return result; +} + +/* + *---------------------------------------------------------------------- + * + * TclObjIndexForString -- + * + * Procedure to find, or if necessary create, an object in a + * CompileEnv's object array that has a string representation + * matching the argument string. + * + * Results: + * The index in the CompileEnv's object array of an object with a + * string representation matching the argument "string". The object is + * created if necessary. If inHeap is 1, then string is heap allocated + * and ownership of the string is passed to TclObjIndexForString; + * otherwise, the string is owned by the caller and must not be + * modified or freed by TclObjIndexForString. Typically, a caller sets + * inHeap 1 if string is an already heap-allocated buffer holding the + * result of backslash substitutions. + * + * Side effects: + * A new Tcl object will be created if no existing object matches the + * input string. If allocStrRep is 1 then if a new object is created, + * its string representation is allocated in the heap, else it is left + * NULL. If inHeap is 1, this procedure is given ownership of the + * string: if an object is created and allocStrRep is 1 then its + * string representation is set directly from string, otherwise + * the string is freed. + * + *---------------------------------------------------------------------- + */ + +int +TclObjIndexForString(string, length, allocStrRep, inHeap, envPtr) + register char *string; /* Points to string for which an object is + * found or created in CompileEnv's object + * array. */ + int length; /* Length of string. */ + int allocStrRep; /* If 1 then the object's string rep should + * be allocated in the heap. */ + int inHeap; /* If 1 then string is heap allocated and + * its ownership is passed to + * TclObjIndexForString. */ + CompileEnv *envPtr; /* Points to the CompileEnv in whose object + * array an object is found or created. */ +{ + register Tcl_Obj *objPtr; /* Points to the object created for + * the string, if one was created. */ + int objIndex; /* Index of matching object. */ + Tcl_HashEntry *hPtr; + int strLength, new; + + /* + * Look up the string in the code's object hashtable. If found, just + * return the associated object array index. Note that if the string + * has embedded nulls, we don't create a hash table entry. This + * should be fixed, but we need to update hash tables, first. + */ + + strLength = strlen(string); + if (length == -1) { + length = strLength; + } + if (strLength != length) { + hPtr = NULL; + } else { + hPtr = Tcl_CreateHashEntry(&envPtr->objTable, string, &new); + if (!new) { /* already in object table and array */ + objIndex = (int) Tcl_GetHashValue(hPtr); + if (inHeap) { + ckfree(string); + } + return objIndex; + } + } + + /* + * Create a new object holding the string, add it to the object array, + * and register its index in the object hashtable. + */ + + objPtr = Tcl_NewObj(); + if (allocStrRep) { + if (inHeap) { /* use input string for obj's string rep */ + objPtr->bytes = string; + } else { + if (length > 0) { + objPtr->bytes = ckalloc((unsigned) length + 1); + memcpy((VOID *) objPtr->bytes, (VOID *) string, + (size_t) length); + objPtr->bytes[length] = '\0'; + } + } + objPtr->length = length; + } else { /* leave the string rep NULL */ + if (inHeap) { + ckfree(string); + } + } + + if (envPtr->objArrayNext >= envPtr->objArrayEnd) { + ExpandObjectArray(envPtr); + } + objIndex = envPtr->objArrayNext; + envPtr->objArrayPtr[objIndex] = objPtr; + Tcl_IncrRefCount(objPtr); + envPtr->objArrayNext++; + + if (hPtr) { + Tcl_SetHashValue(hPtr, objIndex); + } + return objIndex; +} + +/* + *---------------------------------------------------------------------- + * + * TclExpandCodeArray -- + * + * Procedure that uses malloc to allocate more storage for a + * CompileEnv's code array. + * + * Results: + * None. + * + * Side effects: + * The byte code array in *envPtr is reallocated to a new array of + * double the size, and if envPtr->mallocedCodeArray is non-zero the + * old array is freed. Byte codes are copied from the old array to the + * new one. + * + *---------------------------------------------------------------------- + */ + +void +TclExpandCodeArray(envPtr) + CompileEnv *envPtr; /* Points to the CompileEnv whose code array + * must be enlarged. */ +{ + /* + * envPtr->codeNext is equal to envPtr->codeEnd. The currently defined + * code bytes are stored between envPtr->codeStart and + * (envPtr->codeNext - 1) [inclusive]. + */ + + size_t currBytes = TclCurrCodeOffset(); + size_t newBytes = 2*(envPtr->codeEnd - envPtr->codeStart); + unsigned char *newPtr = (unsigned char *) ckalloc((unsigned) newBytes); + + /* + * Copy from old code array to new, free old code array if needed, and + * mark new code array as malloced. + */ + + memcpy((VOID *) newPtr, (VOID *) envPtr->codeStart, currBytes); + if (envPtr->mallocedCodeArray) { + ckfree((char *) envPtr->codeStart); + } + envPtr->codeStart = newPtr; + envPtr->codeNext = (newPtr + currBytes); + envPtr->codeEnd = (newPtr + newBytes); + envPtr->mallocedCodeArray = 1; +} + +/* + *---------------------------------------------------------------------- + * + * ExpandObjectArray -- + * + * Procedure that uses malloc to allocate more storage for a + * CompileEnv's object array. + * + * Results: + * None. + * + * Side effects: + * The object array in *envPtr is reallocated to a new array of + * double the size, and if envPtr->mallocedObjArray is non-zero the + * old array is freed. Tcl_Obj pointers are copied from the old array + * to the new one. + * + *---------------------------------------------------------------------- + */ + +static void +ExpandObjectArray(envPtr) + CompileEnv *envPtr; /* Points to the CompileEnv whose object + * array must be enlarged. */ +{ + /* + * envPtr->objArrayNext is equal to envPtr->objArrayEnd. The currently + * allocated Tcl_Obj pointers are stored between elements + * 0 and (envPtr->objArrayNext - 1) [inclusive] in the object array + * pointed to by objArrayPtr. + */ + + size_t currBytes = envPtr->objArrayNext * sizeof(Tcl_Obj *); + int newElems = 2*envPtr->objArrayEnd; + size_t newBytes = newElems * sizeof(Tcl_Obj *); + Tcl_Obj **newPtr = (Tcl_Obj **) ckalloc((unsigned) newBytes); + + /* + * Copy from old object array to new, free old object array if needed, + * and mark new object array as malloced. + */ + + memcpy((VOID *) newPtr, (VOID *) envPtr->objArrayPtr, currBytes); + if (envPtr->mallocedObjArray) { + ckfree((char *) envPtr->objArrayPtr); + } + envPtr->objArrayPtr = (Tcl_Obj **) newPtr; + envPtr->objArrayEnd = newElems; + envPtr->mallocedObjArray = 1; +} + +/* + *---------------------------------------------------------------------- + * + * EnterCmdStartData -- + * + * Registers the starting source and bytecode location of a + * command. This information is used at runtime to map between + * instruction pc and source locations. + * + * Results: + * None. + * + * Side effects: + * Inserts source and code location information into the compilation + * environment envPtr for the command at index cmdIndex. The + * compilation environment's CmdLocation array is grown if necessary. + * + *---------------------------------------------------------------------- + */ + +static void +EnterCmdStartData(envPtr, cmdIndex, srcOffset, codeOffset) + CompileEnv *envPtr; /* Points to the compilation environment + * structure in which to enter command + * location information. */ + int cmdIndex; /* Index of the command whose start data + * is being set. */ + int srcOffset; /* Offset of first char of the command. */ + int codeOffset; /* Offset of first byte of command code. */ +{ + CmdLocation *cmdLocPtr; + + if ((cmdIndex < 0) || (cmdIndex >= envPtr->numCommands)) { + panic("EnterCmdStartData: bad command index %d\n", cmdIndex); + } + + if (cmdIndex >= envPtr->cmdMapEnd) { + /* + * Expand the command location array by allocating more storage from + * the heap. The currently allocated CmdLocation entries are stored + * from cmdMapPtr[0] up to cmdMapPtr[envPtr->cmdMapEnd] (inclusive). + */ + + size_t currElems = envPtr->cmdMapEnd; + size_t newElems = 2*currElems; + size_t currBytes = currElems * sizeof(CmdLocation); + size_t newBytes = newElems * sizeof(CmdLocation); + CmdLocation *newPtr = (CmdLocation *) ckalloc((unsigned) newBytes); + + /* + * Copy from old command location array to new, free old command + * location array if needed, and mark new array as malloced. + */ + + memcpy((VOID *) newPtr, (VOID *) envPtr->cmdMapPtr, currBytes); + if (envPtr->mallocedCmdMap) { + ckfree((char *) envPtr->cmdMapPtr); + } + envPtr->cmdMapPtr = (CmdLocation *) newPtr; + envPtr->cmdMapEnd = newElems; + envPtr->mallocedCmdMap = 1; + } + + if (cmdIndex > 0) { + if (codeOffset < envPtr->cmdMapPtr[cmdIndex-1].codeOffset) { + panic("EnterCmdStartData: cmd map table not sorted by code offset"); + } + } + + cmdLocPtr = &(envPtr->cmdMapPtr[cmdIndex]); + cmdLocPtr->codeOffset = codeOffset; + cmdLocPtr->srcOffset = srcOffset; + cmdLocPtr->numSrcChars = -1; + cmdLocPtr->numCodeBytes = -1; +} + +/* + *---------------------------------------------------------------------- + * + * EnterCmdExtentData -- + * + * Registers the source and bytecode length for a command. This + * information is used at runtime to map between instruction pc and + * source locations. + * + * Results: + * None. + * + * Side effects: + * Inserts source and code length information into the compilation + * environment envPtr for the command at index cmdIndex. Starting + * source and bytecode information for the command must already + * have been registered. + * + *---------------------------------------------------------------------- + */ + +static void +EnterCmdExtentData(envPtr, cmdIndex, numSrcChars, numCodeBytes) + CompileEnv *envPtr; /* Points to the compilation environment + * structure in which to enter command + * location information. */ + int cmdIndex; /* Index of the command whose source and + * code length data is being set. */ + int numSrcChars; /* Number of command source chars. */ + int numCodeBytes; /* Offset of last byte of command code. */ +{ + CmdLocation *cmdLocPtr; + + if ((cmdIndex < 0) || (cmdIndex >= envPtr->numCommands)) { + panic("EnterCmdStartData: bad command index %d\n", cmdIndex); + } + + if (cmdIndex > envPtr->cmdMapEnd) { + panic("EnterCmdStartData: no start data registered for command with index %d\n", cmdIndex); + } + + cmdLocPtr = &(envPtr->cmdMapPtr[cmdIndex]); + cmdLocPtr->numSrcChars = numSrcChars; + cmdLocPtr->numCodeBytes = numCodeBytes; +} + +/* + *---------------------------------------------------------------------- + * + * InitArgInfo -- + * + * Initializes a ArgInfo structure to hold information about + * some number of argument words in a command. + * + * Results: + * None. + * + * Side effects: + * The ArgInfo structure is initialized. + * + *---------------------------------------------------------------------- + */ + +static void +InitArgInfo(argInfoPtr) + register ArgInfo *argInfoPtr; /* Points to the ArgInfo structure + * to initialize. */ +{ + argInfoPtr->numArgs = 0; + argInfoPtr->startArray = argInfoPtr->staticStartSpace; + argInfoPtr->endArray = argInfoPtr->staticEndSpace; + argInfoPtr->allocArgs = ARGINFO_INIT_ENTRIES; + argInfoPtr->mallocedArrays = 0; +} + +/* + *---------------------------------------------------------------------- + * + * CollectArgInfo -- + * + * Procedure to scan the argument words of a command and record the + * start and finish of each argument word in a ArgInfo structure. + * + * Results: + * The return value is a standard Tcl result, which is TCL_OK unless + * there was an error while scanning string. If an error occurs then + * the interpreter's result contains a standard error message. + * + * Side effects: + * If necessary, the argument start and end arrays in *argInfoPtr + * are grown and reallocated to a new arrays of double the size, and + * if argInfoPtr->mallocedArray is non-zero the old arrays are freed. + * + *---------------------------------------------------------------------- + */ + +static int +CollectArgInfo(interp, string, lastChar, flags, argInfoPtr) + Tcl_Interp *interp; /* Used for error reporting. */ + char *string; /* The source command string to scan. */ + char *lastChar; /* Pointer to terminating character of + * string. */ + int flags; /* Flags to control compilation (same as + * passed to Tcl_Eval). */ + register ArgInfo *argInfoPtr; + /* Points to the ArgInfo structure in which + * to record the arg word information. */ +{ + register char *src = string;/* Points to current source char. */ + register int type; /* Current char's CHAR_TYPE type. */ + int nestedCmd = (flags & TCL_BRACKET_TERM); + /* 1 if string being scanned is a nested + * command and is terminated by a ']'; + * otherwise 0. */ + int scanningArgs; /* 1 if still scanning argument words to + * determine their start and end. */ + char *wordStart, *wordEnd; /* Points to the first and last significant + * characters of each word. */ + CompileEnv tempCompEnv; /* Only used to hold the termOffset field + * updated by AdvanceToNextWord. */ + char *prev; + + argInfoPtr->numArgs = 0; + scanningArgs = 1; + while (scanningArgs) { + AdvanceToNextWord(src, &tempCompEnv); + src += tempCompEnv.termOffset; + type = CHAR_TYPE(src, lastChar); + + if ((type == TCL_COMMAND_END) && ((*src != ']') || nestedCmd)) { + break; /* done collecting argument words */ + } else if (*src == '"') { + wordStart = src; + src = TclWordEnd(src, lastChar, nestedCmd, NULL); + if (src == lastChar) { + badStringTermination: + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "quoted string doesn't terminate properly", -1); + return TCL_ERROR; + } + prev = (src-1); + if (*src == '"') { + wordEnd = src; + src++; + } else if ((*src == ';') && (*prev == '"')) { + scanningArgs = 0; + wordEnd = prev; + } else { + goto badStringTermination; + } + } else if (*src == '{') { + wordStart = src; + src = TclWordEnd(src, lastChar, nestedCmd, NULL); + if (src == lastChar) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "missing close-brace", -1); + return TCL_ERROR; + } + prev = (src-1); + if (*src == '}') { + wordEnd = src; + src++; + } else if ((*src == ';') && (*prev == '}')) { + scanningArgs = 0; + wordEnd = prev; + } else { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "argument word in braces doesn't terminate properly", -1); + return TCL_ERROR; + } + } else { + wordStart = src; + src = TclWordEnd(src, lastChar, nestedCmd, NULL); + prev = (src-1); + if (src == lastChar) { + Tcl_ResetResult(interp); + Tcl_AppendToObj(Tcl_GetObjResult(interp), + "missing close-bracket or close-brace", -1); + return TCL_ERROR; + } else if (*src == ';') { + scanningArgs = 0; + wordEnd = prev; + } else { + wordEnd = src; + src++; + if ((src == lastChar) || (*src == '\n') + || ((*src == ']') && nestedCmd)) { + scanningArgs = 0; + } + } + } /* end of test on each kind of word */ + + if (argInfoPtr->numArgs == argInfoPtr->allocArgs) { + int newArgs = 2*argInfoPtr->numArgs; + size_t currBytes = argInfoPtr->numArgs * sizeof(char *); + size_t newBytes = newArgs * sizeof(char *); + char **newStartArrayPtr = + (char **) ckalloc((unsigned) newBytes); + char **newEndArrayPtr = + (char **) ckalloc((unsigned) newBytes); + + /* + * Copy from the old arrays to the new, free the old arrays if + * needed, and mark the new arrays as malloc'ed. + */ + + memcpy((VOID *) newStartArrayPtr, + (VOID *) argInfoPtr->startArray, currBytes); + memcpy((VOID *) newEndArrayPtr, + (VOID *) argInfoPtr->endArray, currBytes); + if (argInfoPtr->mallocedArrays) { + ckfree((char *) argInfoPtr->startArray); + ckfree((char *) argInfoPtr->endArray); + } + argInfoPtr->startArray = newStartArrayPtr; + argInfoPtr->endArray = newEndArrayPtr; + argInfoPtr->allocArgs = newArgs; + argInfoPtr->mallocedArrays = 1; + } + argInfoPtr->startArray[argInfoPtr->numArgs] = wordStart; + argInfoPtr->endArray[argInfoPtr->numArgs] = wordEnd; + argInfoPtr->numArgs++; + } + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * FreeArgInfo -- + * + * Free any storage allocated in a ArgInfo structure. + * + * Results: + * None. + * + * Side effects: + * Allocated storage in the ArgInfo structure is freed. + * + *---------------------------------------------------------------------- + */ + +static void +FreeArgInfo(argInfoPtr) + register ArgInfo *argInfoPtr; /* Points to the ArgInfo structure + * to free. */ +{ + if (argInfoPtr->mallocedArrays) { + ckfree((char *) argInfoPtr->startArray); + ckfree((char *) argInfoPtr->endArray); + } +} + +/* + *---------------------------------------------------------------------- + * + * CreateLoopExceptionRange -- + * + * Procedure that allocates and initializes a new ExceptionRange + * structure of the specified kind in a CompileEnv's ExceptionRange + * array. + * + * Results: + * Returns the index for the newly created ExceptionRange. + * + * Side effects: + * If there is not enough room in the CompileEnv's ExceptionRange + * array, the array in expanded: a new array of double the size is + * allocated, if envPtr->mallocedExcRangeArray is non-zero the old + * array is freed, and ExceptionRange entries are copied from the old + * array to the new one. + * + *---------------------------------------------------------------------- + */ + +static int +CreateExceptionRange(type, envPtr) + ExceptionRangeType type; /* The kind of ExceptionRange desired. */ + register CompileEnv *envPtr;/* Points to the CompileEnv for which a new + * loop ExceptionRange structure is to be + * allocated. */ +{ + int index; /* Index for the newly-allocated + * ExceptionRange structure. */ + register ExceptionRange *rangePtr; + /* Points to the new ExceptionRange + * structure */ + + index = envPtr->excRangeArrayNext; + if (index >= envPtr->excRangeArrayEnd) { + /* + * Expand the ExceptionRange array. The currently allocated entries + * are stored between elements 0 and (envPtr->excRangeArrayNext - 1) + * [inclusive]. + */ + + size_t currBytes = + envPtr->excRangeArrayNext * sizeof(ExceptionRange); + int newElems = 2*envPtr->excRangeArrayEnd; + size_t newBytes = newElems * sizeof(ExceptionRange); + ExceptionRange *newPtr = (ExceptionRange *) + ckalloc((unsigned) newBytes); + + /* + * Copy from old ExceptionRange array to new, free old + * ExceptionRange array if needed, and mark the new ExceptionRange + * array as malloced. + */ + + memcpy((VOID *) newPtr, (VOID *) envPtr->excRangeArrayPtr, + currBytes); + if (envPtr->mallocedExcRangeArray) { + ckfree((char *) envPtr->excRangeArrayPtr); + } + envPtr->excRangeArrayPtr = (ExceptionRange *) newPtr; + envPtr->excRangeArrayEnd = newElems; + envPtr->mallocedExcRangeArray = 1; + } + envPtr->excRangeArrayNext++; + + rangePtr = &(envPtr->excRangeArrayPtr[index]); + rangePtr->type = type; + rangePtr->nestingLevel = envPtr->excRangeDepth; + rangePtr->codeOffset = -1; + rangePtr->numCodeBytes = -1; + rangePtr->breakOffset = -1; + rangePtr->continueOffset = -1; + rangePtr->catchOffset = -1; + return index; +} + +/* + *---------------------------------------------------------------------- + * + * TclCreateAuxData -- + * + * Procedure that allocates and initializes a new AuxData structure in + * a CompileEnv's array of compilation auxiliary data records. These + * AuxData records hold information created during compilation by + * CompileProcs and used by instructions during execution. + * + * Results: + * Returns the index for the newly created AuxData structure. + * + * Side effects: + * If there is not enough room in the CompileEnv's AuxData array, + * the AuxData array in expanded: a new array of double the size + * is allocated, if envPtr->mallocedAuxDataArray is non-zero + * the old array is freed, and AuxData entries are copied from + * the old array to the new one. + * + *---------------------------------------------------------------------- + */ + +int +TclCreateAuxData(clientData, dupProc, freeProc, envPtr) + ClientData clientData; /* The compilation auxiliary data to store + * in the new aux data record. */ + AuxDataDupProc *dupProc; /* Procedure to call to duplicate the + * compilation aux data when the containing + * ByteCode structure is duplicated. */ + AuxDataFreeProc *freeProc; /* Procedure to call to free the + * compilation aux data when the containing + * ByteCode structure is freed. */ + register CompileEnv *envPtr;/* Points to the CompileEnv for which a new + * aux data structure is to be allocated. */ +{ + int index; /* Index for the new AuxData structure. */ + register AuxData *auxDataPtr; + /* Points to the new AuxData structure */ + + index = envPtr->auxDataArrayNext; + if (index >= envPtr->auxDataArrayEnd) { + /* + * Expand the AuxData array. The currently allocated entries are + * stored between elements 0 and (envPtr->auxDataArrayNext - 1) + * [inclusive]. + */ + + size_t currBytes = envPtr->auxDataArrayNext * sizeof(AuxData); + int newElems = 2*envPtr->auxDataArrayEnd; + size_t newBytes = newElems * sizeof(AuxData); + AuxData *newPtr = (AuxData *) ckalloc((unsigned) newBytes); + + /* + * Copy from old AuxData array to new, free old AuxData array if + * needed, and mark the new AuxData array as malloced. + */ + + memcpy((VOID *) newPtr, (VOID *) envPtr->auxDataArrayPtr, + currBytes); + if (envPtr->mallocedAuxDataArray) { + ckfree((char *) envPtr->auxDataArrayPtr); + } + envPtr->auxDataArrayPtr = newPtr; + envPtr->auxDataArrayEnd = newElems; + envPtr->mallocedAuxDataArray = 1; + } + envPtr->auxDataArrayNext++; + + auxDataPtr = &(envPtr->auxDataArrayPtr[index]); + auxDataPtr->clientData = clientData; + auxDataPtr->dupProc = dupProc; + auxDataPtr->freeProc = freeProc; + return index; +} + +/* + *---------------------------------------------------------------------- + * + * TclInitJumpFixupArray -- + * + * Initializes a JumpFixupArray structure to hold some number of + * jump fixup entries. + * + * Results: + * None. + * + * Side effects: + * The JumpFixupArray structure is initialized. + * + *---------------------------------------------------------------------- + */ + +void +TclInitJumpFixupArray(fixupArrayPtr) + register JumpFixupArray *fixupArrayPtr; + /* Points to the JumpFixupArray structure + * to initialize. */ +{ + fixupArrayPtr->fixup = fixupArrayPtr->staticFixupSpace; + fixupArrayPtr->next = 0; + fixupArrayPtr->end = (JUMPFIXUP_INIT_ENTRIES - 1); + fixupArrayPtr->mallocedArray = 0; +} + +/* + *---------------------------------------------------------------------- + * + * TclExpandJumpFixupArray -- + * + * Procedure that uses malloc to allocate more storage for a + * jump fixup array. + * + * Results: + * None. + * + * Side effects: + * The jump fixup array in *fixupArrayPtr is reallocated to a new array + * of double the size, and if fixupArrayPtr->mallocedArray is non-zero + * the old array is freed. Jump fixup structures are copied from the + * old array to the new one. + * + *---------------------------------------------------------------------- + */ + +void +TclExpandJumpFixupArray(fixupArrayPtr) + register JumpFixupArray *fixupArrayPtr; + /* Points to the JumpFixupArray structure + * to enlarge. */ +{ + /* + * The currently allocated jump fixup entries are stored from fixup[0] + * up to fixup[fixupArrayPtr->fixupNext] (*not* inclusive). We assume + * fixupArrayPtr->fixupNext is equal to fixupArrayPtr->fixupEnd. + */ + + size_t currBytes = fixupArrayPtr->next * sizeof(JumpFixup); + int newElems = 2*(fixupArrayPtr->end + 1); + size_t newBytes = newElems * sizeof(JumpFixup); + JumpFixup *newPtr = (JumpFixup *) ckalloc((unsigned) newBytes); + + /* + * Copy from the old array to new, free the old array if needed, + * and mark the new array as malloced. + */ + + memcpy((VOID *) newPtr, (VOID *) fixupArrayPtr->fixup, currBytes); + if (fixupArrayPtr->mallocedArray) { + ckfree((char *) fixupArrayPtr->fixup); + } + fixupArrayPtr->fixup = (JumpFixup *) newPtr; + fixupArrayPtr->end = newElems; + fixupArrayPtr->mallocedArray = 1; +} + +/* + *---------------------------------------------------------------------- + * + * TclFreeJumpFixupArray -- + * + * Free any storage allocated in a jump fixup array structure. + * + * Results: + * None. + * + * Side effects: + * Allocated storage in the JumpFixupArray structure is freed. + * + *---------------------------------------------------------------------- + */ + +void +TclFreeJumpFixupArray(fixupArrayPtr) + register JumpFixupArray *fixupArrayPtr; + /* Points to the JumpFixupArray structure + * to free. */ +{ + if (fixupArrayPtr->mallocedArray) { + ckfree((char *) fixupArrayPtr->fixup); + } +} + +/* + *---------------------------------------------------------------------- + * + * TclEmitForwardJump -- + * + * Procedure to emit a two-byte forward jump of kind "jumpType". Since + * the jump may later have to be grown to five bytes if the jump target + * is more than, say, 127 bytes away, this procedure also initializes a + * JumpFixup record with information about the jump. + * + * Results: + * None. + * + * Side effects: + * The JumpFixup record pointed to by "jumpFixupPtr" is initialized + * with information needed later if the jump is to be grown. Also, + * a two byte jump of the designated type is emitted at the current + * point in the bytecode stream. + * + *---------------------------------------------------------------------- + */ + +void +TclEmitForwardJump(envPtr, jumpType, jumpFixupPtr) + CompileEnv *envPtr; /* Points to the CompileEnv structure that + * holds the resulting instruction. */ + TclJumpType jumpType; /* Indicates the kind of jump: if true or + * false or unconditional. */ + JumpFixup *jumpFixupPtr; /* Points to the JumpFixup structure to + * initialize with information about this + * forward jump. */ +{ + /* + * Initialize the JumpFixup structure: + * - codeOffset is offset of first byte of jump below + * - cmdIndex is index of the command after the current one + * - excRangeIndex is the index of the first ExceptionRange after + * the current one. + */ + + jumpFixupPtr->jumpType = jumpType; + jumpFixupPtr->codeOffset = TclCurrCodeOffset(); + jumpFixupPtr->cmdIndex = envPtr->numCommands; + jumpFixupPtr->excRangeIndex = envPtr->excRangeArrayNext; + + switch (jumpType) { + case TCL_UNCONDITIONAL_JUMP: + TclEmitInstInt1(INST_JUMP1, /*offset*/ 0, envPtr); + break; + case TCL_TRUE_JUMP: + TclEmitInstInt1(INST_JUMP_TRUE1, /*offset*/ 0, envPtr); + break; + default: + TclEmitInstInt1(INST_JUMP_FALSE1, /*offset*/ 0, envPtr); + break; + } +} + +/* + *---------------------------------------------------------------------- + * + * TclFixupForwardJump -- + * + * Procedure that updates a previously-emitted forward jump to jump + * a specified number of bytes, "jumpDist". If necessary, the jump is + * grown from two to five bytes; this is done if the jump distance is + * greater than "distThreshold" (normally 127 bytes). The jump is + * described by a JumpFixup record previously initialized by + * TclEmitForwardJump. + * + * Results: + * 1 if the jump was grown and subsequent instructions had to be moved; + * otherwise 0. This result is returned to allow callers to update + * any additional code offsets they may hold. + * + * Side effects: + * The jump may be grown and subsequent instructions moved. If this + * happens, the code offsets for any commands and any ExceptionRange + * records between the jump and the current code address will be + * updated to reflect the moved code. Also, the bytecode instruction + * array in the CompileEnv structure may be grown and reallocated. + * + *---------------------------------------------------------------------- + */ + +int +TclFixupForwardJump(envPtr, jumpFixupPtr, jumpDist, distThreshold) + CompileEnv *envPtr; /* Points to the CompileEnv structure that + * holds the resulting instruction. */ + JumpFixup *jumpFixupPtr; /* Points to the JumpFixup structure that + * describes the forward jump. */ + int jumpDist; /* Jump distance to set in jump + * instruction. */ + int distThreshold; /* Maximum distance before the two byte + * jump is grown to five bytes. */ +{ + unsigned char *jumpPc, *p; + int firstCmd, lastCmd, firstRange, lastRange, k; + unsigned int numBytes; + + if (jumpDist <= distThreshold) { + jumpPc = (envPtr->codeStart + jumpFixupPtr->codeOffset); + switch (jumpFixupPtr->jumpType) { + case TCL_UNCONDITIONAL_JUMP: + TclUpdateInstInt1AtPc(INST_JUMP1, jumpDist, jumpPc); + break; + case TCL_TRUE_JUMP: + TclUpdateInstInt1AtPc(INST_JUMP_TRUE1, jumpDist, jumpPc); + break; + default: + TclUpdateInstInt1AtPc(INST_JUMP_FALSE1, jumpDist, jumpPc); + break; + } + return 0; + } + + /* + * We must grow the jump then move subsequent instructions down. + */ + + TclEnsureCodeSpace(3, envPtr); /* NB: might change code addresses! */ + jumpPc = (envPtr->codeStart + jumpFixupPtr->codeOffset); + for (numBytes = envPtr->codeNext-jumpPc-2, p = jumpPc+2+numBytes-1; + numBytes > 0; numBytes--, p--) { + p[3] = p[0]; + } + envPtr->codeNext += 3; + jumpDist += 3; + switch (jumpFixupPtr->jumpType) { + case TCL_UNCONDITIONAL_JUMP: + TclUpdateInstInt4AtPc(INST_JUMP4, jumpDist, jumpPc); + break; + case TCL_TRUE_JUMP: + TclUpdateInstInt4AtPc(INST_JUMP_TRUE4, jumpDist, jumpPc); + break; + default: + TclUpdateInstInt4AtPc(INST_JUMP_FALSE4, jumpDist, jumpPc); + break; + } + + /* + * Adjust the code offsets for any commands and any ExceptionRange + * records between the jump and the current code address. + */ + + firstCmd = jumpFixupPtr->cmdIndex; + lastCmd = (envPtr->numCommands - 1); + if (firstCmd < lastCmd) { + for (k = firstCmd; k <= lastCmd; k++) { + (envPtr->cmdMapPtr[k]).codeOffset += 3; + } + } + + firstRange = jumpFixupPtr->excRangeIndex; + lastRange = (envPtr->excRangeArrayNext - 1); + for (k = firstRange; k <= lastRange; k++) { + ExceptionRange *rangePtr = &(envPtr->excRangeArrayPtr[k]); + rangePtr->codeOffset += 3; + + switch (rangePtr->type) { + case LOOP_EXCEPTION_RANGE: + rangePtr->breakOffset += 3; + if (rangePtr->continueOffset != -1) { + rangePtr->continueOffset += 3; + } + break; + case CATCH_EXCEPTION_RANGE: + rangePtr->catchOffset += 3; + break; + default: + panic("TclFixupForwardJump: unrecognized ExceptionRange type %d\n", rangePtr->type); + } + } + return 1; /* the jump was grown */ +} + + |