From 4f49b16079b27d7ce633619e4cddfdf2bffbc952 Mon Sep 17 00:00:00 2001 From: cashmere Date: Fri, 4 Sep 2026 09:05:24 +0200 Subject: initial commit --- src/th_lang.c | 1470 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1470 insertions(+) create mode 100644 src/th_lang.c (limited to 'src/th_lang.c') diff --git a/src/th_lang.c b/src/th_lang.c new file mode 100644 index 0000000..a72a219 --- /dev/null +++ b/src/th_lang.c @@ -0,0 +1,1470 @@ + +/* +** This file contains the implementation of all of the TH language +** built-in commands. +** +** All built-in commands are implemented using the public interface +** declared in th.h, so this file serves as both a part of the language +** implementation and an example of how to extend the language with +** new commands. +*/ + +#include "config.h" +#include "th.h" +#include +#include + +int Th_WrongNumArgs(Th_Interp *interp, const char *zMsg){ + Th_ErrorMessage(interp, "wrong # args: should be \"", zMsg, -1); + return TH_ERROR; +} + +/* +** Syntax: +** +** catch script ?varname? +*/ +static int catch_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int rc; + + if( argc!=2 && argc!=3 ){ + return Th_WrongNumArgs(interp, "catch script ?varname?"); + } + + rc = Th_Eval(interp, 0, argv[1], -1); + if( argc==3 ){ + int nResult; + const char *zResult = Th_GetResult(interp, &nResult); + Th_SetVar(interp, argv[2], TH1_LEN(argl[2]), zResult, nResult); + } + + Th_SetResultInt(interp, rc); + return TH_OK; +} + +/* +** TH Syntax: +** +** if expr1 body1 ?elseif expr2 body2? ? ?else? bodyN? +*/ +static int if_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int rc = TH_OK; + + int iCond; /* Result of evaluating expression */ + int i; + + const char *zResult; + int nResult; + + if( argc<3 ){ + goto wrong_args; + } + + for(i=0; iargc-3 ){ + i = argc-3; + iCond = 1; + }else{ + if( TH_OK!=Th_Expr(interp, argv[i+1], argl[i+1]) ){ + return TH_ERROR; + } + zResult = Th_GetResult(interp, &nResult); + rc = Th_ToInt(interp, zResult, nResult, &iCond); + } + if( iCond && rc==TH_OK ){ + rc = Th_Eval(interp, 0, argv[i+2], -1); + break; + } + } + + return rc; + +wrong_args: + return Th_WrongNumArgs(interp, "if ..."); +} + +/* +** TH Syntax: +** +** expr expr +*/ +static int expr_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + if( argc!=2 ){ + return Th_WrongNumArgs(interp, "expr expression"); + } + + return Th_Expr(interp, argv[1], argl[1]); +} + +/* +** Evaluate the th1 script (zBody, nBody) in the local stack frame. +** Return the result of the evaluation, except if the result +** is TH_CONTINUE, return TH_OK instead. +*/ +static int eval_loopbody(Th_Interp *interp, const char *zBody, int nBody){ + int rc = Th_Eval(interp, 0, zBody, nBody); + if( rc==TH_CONTINUE ){ + rc = TH_OK; + } + return rc; +} + +/* +** TH Syntax: +** +** for init condition incr script +*/ +static int for_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int rc; + int iCond; + + if( argc!=5 ){ + return Th_WrongNumArgs(interp, "for init condition incr script"); + } + + /* Evaluate the 'init' script */ + rc = Th_Eval(interp, 0, argv[1], -1); + + while( rc==TH_OK + && TH_OK==(rc = Th_Expr(interp, argv[2], -1)) + && TH_OK==(rc = Th_ToInt(interp, Th_GetResult(interp, 0), -1, &iCond)) + && iCond + && TH_OK==(rc = eval_loopbody(interp, argv[4], argl[4])) + ){ + rc = Th_Eval(interp, 0, argv[3], -1); + } + + if( rc==TH_BREAK ) rc = TH_OK; + return rc; +} + +/* +** TH Syntax: +** +** foreach VARLIST LIST SCRIPT +*/ +static int foreach_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int rc; + char **azVar = 0; + int *anVar; + int nVar; + char **azValue = 0; + int *anValue; + int nValue; + int ii, jj; + int bTaint = 0; + + if( argc!=4 ){ + return Th_WrongNumArgs(interp, "foreach varlist list script"); + } + rc = Th_SplitList(interp, argv[1], argl[1], &azVar, &anVar, &nVar); + if( rc ) return rc; + TH1_XFER_TAINT(bTaint, argl[2]); + rc = Th_SplitList(interp, argv[2], argl[2], &azValue, &anValue, &nValue); + for(ii=0; rc==TH_OK && ii<=nValue-nVar; ii+=nVar){ + for(jj=0; jj=0 ){ + int sz = anElem[iElem]; + TH1_XFER_TAINT(sz, bTaint); + Th_SetResult(interp, azElem[iElem], sz); + }else{ + Th_SetResult(interp, 0, 0); + } + Th_Free(interp, azElem); + } + + return rc; +} + +/* +** TH Syntax: +** +** llength list +*/ +static int llength_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int nElem; + int rc; + + if( argc!=2 ){ + return Th_WrongNumArgs(interp, "llength list"); + } + + rc = Th_SplitList(interp, argv[1], argl[1], 0, 0, &nElem); + if( rc==TH_OK ){ + Th_SetResultInt(interp, nElem); + } + + return rc; +} + +/* +** TH Syntax: +** +** lsearch list string +*/ +static int lsearch_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int rc; + char **azElem; + int *anElem; + int nCount; + int i; + + if( argc!=3 ){ + return Th_WrongNumArgs(interp, "lsearch list string"); + } + + rc = Th_SplitList(interp, argv[1], argl[1], &azElem, &anElem, &nCount); + if( rc==TH_OK ){ + int nn = TH1_LEN(argl[2]); + Th_SetResultInt(interp, -1); + for(i=0; iargc>(p->nParam+1) && !p->hasArgs) + || (pArgs->argc<=(p->nParam) && !p->azDefault[pArgs->argc-1]) + ){ + char *zUsage = 0; + int nUsage = 0; + Th_StringAppend(interp, &zUsage, &nUsage, pArgs->argv[0], pArgs->argl[0]); + Th_StringAppend(interp, &zUsage, &nUsage, p->zUsage, p->nUsage); + Th_StringAppend(interp, &zUsage, &nUsage, (const char *)"", 1); + Th_WrongNumArgs(interp, zUsage); + Th_Free(interp, zUsage); + return TH_ERROR; + } + + /* Populate the formal proc parameters. */ + for(i=0; inParam; i++){ + const char *zVal; + int nVal; + if( pArgs->argc>(i+1) ){ + zVal = pArgs->argv[i+1]; + nVal = pArgs->argl[i+1]; + }else{ + zVal = p->azDefault[i]; + nVal = p->anDefault[i]; + } + Th_SetVar(interp, p->azParam[i], p->anParam[i], zVal, nVal); + } + + /* Populate the "args" parameter, if it exists */ + if( p->hasArgs ){ + char *zArgs = 0; + int nArgs = 0; + for(i=p->nParam+1; iargc; i++){ + Th_ListAppend(interp, &zArgs, &nArgs, pArgs->argv[i], pArgs->argl[i]); + } + Th_SetVar(interp, (const char *)"args", -1, zArgs, nArgs); + if(zArgs){ + Th_Free(interp, zArgs); + } + } + + Th_SetResult(interp, 0, 0); + return Th_Eval(interp, 0, p->zProgram, p->nProgram); +} + +/* +** This function is the command callback registered for all commands +** created using the [proc] command. The second argument, pContext, +** is a pointer to the associated ProcDefn structure. +*/ +static int proc_call1( + Th_Interp *interp, + void *pContext, + int argc, + const char **argv, + int *argl +){ + int rc; + + ProcDefn *p = (ProcDefn *)pContext; + ProcArgs procargs; + + /* Call function proc_call2(), which will call Th_Eval() to evaluate + ** the body of the [proc], in a new Th stack frame. This is so that + ** the proc body has its own local variable context. + */ + procargs.argc = argc; + procargs.argv = argv; + procargs.argl = argl; + rc = Th_InFrame(interp, proc_call2, (void *)p, (void *)&procargs); + + if( rc==TH_RETURN ){ + rc = TH_OK; + } + if( rc==TH_RETURN2 ){ + rc = TH_RETURN; + } + return rc; +} + +/* +** This function is registered as the delete callback for all commands +** created using the built-in [proc] command. It is called automatically +** when a command created using [proc] is deleted. +** +** It frees the ProcDefn structure allocated when the command was created. +*/ +static void proc_del(Th_Interp *interp, void *pContext){ + ProcDefn *p = (ProcDefn *)pContext; + Th_Free(interp, (void *)p->zUsage); + Th_Free(interp, (void *)p); +} + +/* +** TH Syntax: +** +** proc name arglist code +*/ +static int proc_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int rc; + const char *zName; + + ProcDefn *p; + int nByte; + int i; + char *zSpace; + + char **azParam; + int *anParam; + int nParam; + + char *zUsage = 0; /* Build up a usage message here */ + int nUsage = 0; /* Number of bytes at zUsage */ + + if( argc!=4 ){ + return Th_WrongNumArgs(interp, "proc name arglist code"); + } + if( Th_SplitList(interp, argv[2], TH1_LEN(argl[2]), + &azParam, &anParam, &nParam) ){ + return TH_ERROR; + } + + /* Allocate the new ProcDefn structure. */ + nByte = sizeof(ProcDefn) + /* ProcDefn structure */ + (sizeof(char *) + sizeof(int)) * nParam + /* azParam, anParam */ + (sizeof(char *) + sizeof(int)) * nParam + /* azDefault, anDefault */ + TH1_LEN(argl[3]) + /* zProgram */ + TH1_LEN(argl[2]); /* Space for copies of param names and dflt values */ + p = (ProcDefn *)Th_Malloc(interp, nByte); + + /* If the last parameter in the parameter list is "args", then set the + ** ProcDefn.hasArgs flag. The "args" parameter does not require an + ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays. + */ + if( nParam>0 ){ + if( TH1_LEN(anParam[nParam-1])==4 + && 0==memcmp(azParam[nParam-1], "args", 4) + ){ + p->hasArgs = 1; + nParam--; + } + } + + p->nParam = nParam; + p->azParam = (char **)&p[1]; + p->anParam = (int *)&p->azParam[nParam]; + p->azDefault = (char **)&p->anParam[nParam]; + p->anDefault = (int *)&p->azDefault[nParam]; + p->zProgram = (char *)&p->anDefault[nParam]; + memcpy(p->zProgram, argv[3], TH1_LEN(argl[3])); + p->nProgram = TH1_LEN(argl[3]); + zSpace = &p->zProgram[p->nProgram]; + + for(i=0; i2 ){ + const char expected[] = "expected parameter, got \""; + Th_ErrorMessage(interp, expected, azParam[i], anParam[i]); + Th_Free(interp, az); + goto error_out; + } + p->anParam[i] = an[0]; + p->azParam[i] = zSpace; + memcpy(zSpace, az[0], an[0]); + zSpace += an[0]; + if( n==2 ){ + p->anDefault[i] = an[1]; + p->azDefault[i] = zSpace; + memcpy(zSpace, az[1], an[1]); + zSpace += an[1]; + } + + Th_StringAppend(interp, &zUsage, &nUsage, (const char *)" ", 1); + if( n==2 ){ + Th_StringAppend(interp, &zUsage, &nUsage, (const char *)"?", 1); + Th_StringAppend(interp, &zUsage, &nUsage, az[0], an[0]); + Th_StringAppend(interp, &zUsage, &nUsage, (const char *)"?", 1); + }else{ + Th_StringAppend(interp, &zUsage, &nUsage, az[0], an[0]); + } + + Th_Free(interp, az); + } + assert( zSpace-(char *)p<=nByte ); + + /* If there is an "args" parameter, append it to the end of the usage + ** message. Set ProcDefn.zUsage to point at the usage message. It will + ** be freed along with the rest of the proc-definition by proc_del(). + */ + if( p->hasArgs ){ + Th_StringAppend(interp, &zUsage, &nUsage, (const char *)" ?args...?", -1); + } + p->zUsage = zUsage; + p->nUsage = nUsage; + + /* Register the new command with the th1 interpreter. */ + zName = argv[1]; + rc = Th_CreateCommand(interp, zName, proc_call1, (void *)p, proc_del); + if( rc==TH_OK ){ + Th_SetResult(interp, 0, 0); + } + + Th_Free(interp, azParam); + return TH_OK; + + error_out: + Th_Free(interp, azParam); + Th_Free(interp, zUsage); + return TH_ERROR; +} + +/* +** TH Syntax: +** +** rename oldcmd newcmd +*/ +static int rename_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + if( argc!=3 ){ + return Th_WrongNumArgs(interp, "rename oldcmd newcmd"); + } + return Th_RenameCommand(interp, argv[1], TH1_LEN(argl[1]), + argv[2], TH1_LEN(argl[2])); +} + +/* +** TH Syntax: +** +** break ?value...? +** continue ?value...? +** ok ?value...? +** error ?value...? +*/ +static int simple_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + if( argc!=1 && argc!=2 ){ + return Th_WrongNumArgs(interp, "return ?value?"); + } + if( argc==2 ){ + Th_SetResult(interp, argv[1], argl[1]); + } + return FOSSIL_PTR_TO_INT(ctx); +} + +/* +** TH Syntax: +** +** return ?-code code? ?value? +*/ +static int return_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int iCode = TH_RETURN; + if( argc<1 || argc>4 ){ + return Th_WrongNumArgs(interp, "return ?-code code? ?value?"); + } + if( argc>2 ){ + int rc = Th_ToInt(interp, argv[2], argl[2], &iCode); + if( rc!=TH_OK ){ + return rc; + } + } + if( argc==2 || argc==4 ){ + Th_SetResult(interp, argv[argc-1], argl[argc-1]); + } + return iCode; +} + +/* +** TH Syntax: +** +** string compare STRING1 STRING2 +*/ +static int string_compare_command( + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl +){ + const char *zRight; int nRight; + const char *zLeft; int nLeft; + + int i; + int iRes = 0; + + if( argc!=4 ){ + return Th_WrongNumArgs(interp, "string compare str1 str2"); + } + + zLeft = argv[2]; + nLeft = TH1_LEN(argl[2]); + zRight = argv[3]; + nRight = TH1_LEN(argl[3]); + + for(i=0; iRes==0 && i0 ) iRes = 1; + + return Th_SetResultInt(interp, iRes); +} + +/* +** TH Syntax: +** +** string first NEEDLE HAYSTACK +*/ +static int string_first_command( + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl +){ + int nNeedle; + int nHaystack; + int iRes = -1; + + if( argc!=4 ){ + return Th_WrongNumArgs(interp, "string first needle haystack"); + } + + nNeedle = TH1_LEN(argl[2]); + nHaystack = TH1_LEN(argl[3]); + + if( nNeedle && nHaystack && nNeedle<=nHaystack ){ + const char *zNeedle = argv[2]; + const char *zHaystack = argv[3]; + int i; + + for(i=0; i<=(nHaystack-nNeedle); i++){ + if( 0==memcmp(zNeedle, &zHaystack[i], nNeedle) ){ + iRes = i; + break; + } + } + } + + return Th_SetResultInt(interp, iRes); +} + +/* +** TH Syntax: +** +** string index STRING INDEX +*/ +static int string_index_command( + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl +){ + int iIndex; + + if( argc!=4 ){ + return Th_WrongNumArgs(interp, "string index string index"); + } + + if( TH1_LEN(argl[3])==3 && 0==memcmp("end", argv[3], 3) ){ + iIndex = TH1_LEN(argl[2])-1; + }else if( Th_ToInt(interp, argv[3], argl[3], &iIndex) ){ + Th_ErrorMessage( + interp, "Expected \"end\" or integer, got:", argv[3], argl[3]); + return TH_ERROR; + } + + if( iIndex>=0 && iIndex=0; i--){ + if( 0==memcmp(zNeedle, &zHaystack[i], nNeedle) ){ + iRes = i; + break; + } + } + } + + return Th_SetResultInt(interp, iRes); +} + +/* +** TH Syntax: +** +** string length STRING +*/ +static int string_length_command( + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl +){ + if( argc!=3 ){ + return Th_WrongNumArgs(interp, "string length string"); + } + return Th_SetResultInt(interp, TH1_LEN(argl[2])); +} + +/* +** TH Syntax: +** +** string match PATTERN STRING +** +*/ +static int string_match_command( + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl +){ + extern char *fossil_strndup(const char*,ssize_t); + extern void fossil_free(void*); + char *zPat, *zStr; + int rc; + if( argc!=4 ){ + return Th_WrongNumArgs(interp, "string match pattern string"); + } + zPat = fossil_strndup(argv[2],TH1_LEN(argl[2])); + zStr = fossil_strndup(argv[3],TH1_LEN(argl[3])); + rc = sqlite3_strglob(zPat,zStr); + fossil_free(zPat); + fossil_free(zStr); + return Th_SetResultInt(interp, !rc); +} + +/* +** TH Syntax: +** +** string range STRING FIRST LAST +*/ +static int string_range_command( + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl +){ + int iStart; + int iEnd; + int sz; + + if( argc!=5 ){ + return Th_WrongNumArgs(interp, "string range string first last"); + } + + if( TH1_LEN(argl[4])==3 && 0==memcmp("end", argv[4], 3) ){ + iEnd = TH1_LEN(argl[2]); + }else if( Th_ToInt(interp, argv[4], argl[4], &iEnd) ){ + Th_ErrorMessage( + interp, "Expected \"end\" or integer, got:", argv[4], TH1_LEN(argl[4])); + return TH_ERROR; + } + if( Th_ToInt(interp, argv[3], argl[3], &iStart) ){ + return TH_ERROR; + } + + if( iStart<0 ) iStart = 0; + if( iEnd>=TH1_LEN(argl[2]) ) iEnd = TH1_LEN(argl[2])-1; + if( iStart>iEnd ) iEnd = iStart-1; + sz = iEnd - iStart + 1; + TH1_XFER_TAINT(sz, argl[2]); + + return Th_SetResult(interp, &argv[2][iStart], sz); +} + +/* +** TH Syntax: +** +** string repeat STRING COUNT +*/ +static int string_repeat_command( + Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl +){ + int n; + int i; + int sz; + long long int nByte; + char *zByte; + + if( argc!=4 ){ + return Th_WrongNumArgs(interp, "string repeat string n"); + } + if( Th_ToInt(interp, argv[3], argl[3], &n) ){ + return TH_ERROR; + } + + nByte = n; + sz = TH1_LEN(argl[2]); + nByte *= sz; + TH1_SIZECHECK(nByte+1); + zByte = Th_Malloc(interp, nByte+1); + for(i=0; i1 ){ + int i; + for(i=0; aSub[i].zName; i++){ + const char *zName = aSub[i].zName; + if( th_strlen(zName)==TH1_LEN(argl[1]) + && 0==memcmp(zName, argv[1], TH1_LEN(argl[1])) ){ + return aSub[i].xProc(interp, ctx, argc, argv, argl); + } + } + } + if(argc<2){ + Th_ErrorMessage(interp, "Expected sub-command for", + argv[0], TH1_LEN(argl[0])); + }else{ + Th_ErrorMessage(interp, "Expected sub-command, got:", + argv[1], TH1_LEN(argl[1])); + } + return TH_ERROR; +} + +/* +** TH Syntax: +** +** string compare STR1 STR2 +** string first NEEDLE HAYSTACK ?STARTINDEX? +** string index STRING INDEX +** string is CLASS STRING +** string last NEEDLE HAYSTACK ?STARTINDEX? +** string length STRING +** string range STRING FIRST LAST +** string repeat STRING COUNT +** string trim STRING +** string trimleft STRING +** string trimright STRING +*/ +static int string_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + static const Th_SubCommand aSub[] = { + { "compare", string_compare_command }, + { "first", string_first_command }, + { "index", string_index_command }, + { "is", string_is_command }, + { "last", string_last_command }, + { "length", string_length_command }, + { "match", string_match_command }, + { "range", string_range_command }, + { "repeat", string_repeat_command }, + { "trim", string_trim_command }, + { "trimleft", string_trim_command }, + { "trimright", string_trim_command }, + { 0, 0 } + }; + return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub); +} + +/* +** TH Syntax: +** +** info commands +** info exists VARNAME +** info vars +*/ +static int info_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + static const Th_SubCommand aSub[] = { + { "commands", info_commands_command }, + { "exists", info_exists_command }, + { "vars", info_vars_command }, + { 0, 0 } + }; + return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub); +} + +/* +** TH Syntax: +** +** array exists VARNAME +** array names VARNAME +*/ +static int array_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + static const Th_SubCommand aSub[] = { + { "exists", array_exists_command }, + { "names", array_names_command }, + { 0, 0 } + }; + return Th_CallSubCommand(interp, ctx, argc, argv, argl, aSub); +} + +/* +** Convert the script level frame specification (used by the commands +** [uplevel] and [upvar]) in (zFrame, nFrame) to an integer frame as +** used by Th_LinkVar() and Th_Eval(). If successful, write the integer +** frame level to *piFrame and return TH_OK. Otherwise, return TH_ERROR +** and leave an error message in the interpreter result. +*/ +static int thToFrame( + Th_Interp *interp, + const char *zFrame, + int nFrame, + int *piFrame +){ + int iFrame; + if( th_isdigit(zFrame[0]) ){ + int rc = Th_ToInt(interp, zFrame, nFrame, &iFrame); + if( rc!=TH_OK ) return rc; + iFrame = iFrame * -1; + }else if( zFrame[0]=='#' ){ + int rc = Th_ToInt(interp, &zFrame[1], nFrame-1, &iFrame); + if( rc!=TH_OK ) return rc; + iFrame = iFrame + 1; + }else{ + return TH_ERROR; + } + *piFrame = iFrame; + return TH_OK; +} + +/* +** TH Syntax: +** +** uplevel ?LEVEL? SCRIPT +*/ +static int uplevel_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int iFrame = -1; + + if( argc!=2 && argc!=3 ){ + return Th_WrongNumArgs(interp, "uplevel ?level? script..."); + } + if( argc==3 && TH_OK!=thToFrame(interp, argv[1], TH1_LEN(argl[1]), &iFrame) ){ + return TH_ERROR; + } + return Th_Eval(interp, iFrame, argv[argc-1], -1); +} + +/* +** TH Syntax: +** +** upvar ?FRAME? OTHERVAR MYVAR ?OTHERVAR MYVAR ...? +*/ +static int upvar_command( + Th_Interp *interp, + void *ctx, + int argc, + const char **argv, + int *argl +){ + int iVar = 1; + int iFrame = -1; + int rc = TH_OK; + int i; + + if( TH_OK==thToFrame(0, argv[1], TH1_LEN(argl[1]), &iFrame) ){ + iVar++; + } + if( argc==iVar || (argc-iVar)%2 ){ + return Th_WrongNumArgs(interp, + "upvar frame othervar myvar ?othervar myvar...?"); + } + for(i=iVar; rc==TH_OK && i