diff options
author | Steve Bennett <steveb@workware.net.au> | 2011-06-16 10:27:27 +1000 |
---|---|---|
committer | Steve Bennett <steveb@workware.net.au> | 2011-07-08 05:41:58 +1000 |
commit | 4a5e4965e2c208375a77d40b831a07897f80ee50 (patch) | |
tree | 8d9ea6efb78a2a884ac347b59b3d48740eda0349 | |
parent | e3639458879e363cf012a1f7a00fdfab92f0f7ce (diff) | |
download | jimtcl-4a5e4965e2c208375a77d40b831a07897f80ee50.zip jimtcl-4a5e4965e2c208375a77d40b831a07897f80ee50.tar.gz jimtcl-4a5e4965e2c208375a77d40b831a07897f80ee50.tar.bz2 |
Better proc optional arg handling
Allows args and optional parameters in any location,
in addition to being smaller and faster.
Signed-off-by: Steve Bennett <steveb@workware.net.au>
-rw-r--r-- | jim.c | 343 | ||||
-rw-r--r-- | jim.h | 20 | ||||
-rw-r--r-- | jim_tcl.txt | 48 | ||||
-rw-r--r-- | tests/proc-new.test | 15 |
4 files changed, 208 insertions, 218 deletions
@@ -3528,28 +3528,32 @@ int Jim_CreateCommand(Jim_Interp *interp, const char *cmdName, return JIM_OK; } -static int JimCreateProcedure(Jim_Interp *interp, const char *cmdName, - Jim_Obj *argListObjPtr, Jim_Obj *staticsListObjPtr, Jim_Obj *bodyObjPtr, - int leftArity, int optionalArgs, int args, int rightArity) +static int JimCreateProcedure(Jim_Interp *interp, Jim_Obj *cmdName, + Jim_Obj *argListObjPtr, Jim_Obj *staticsListObjPtr, Jim_Obj *bodyObjPtr) { Jim_Cmd *cmdPtr; Jim_HashEntry *he; + int argListLen; + int i; - cmdPtr = Jim_Alloc(sizeof(*cmdPtr)); + if (JimValidName(interp, "procedure", cmdName) != JIM_OK) { + return JIM_ERR; + } + + argListLen = Jim_ListLength(interp, argListObjPtr); + + /* Allocate space for both the command pointer and the arg list */ + cmdPtr = Jim_Alloc(sizeof(*cmdPtr) + sizeof(struct Jim_ProcArg) * argListLen); memset(cmdPtr, 0, sizeof(*cmdPtr)); cmdPtr->inUse = 1; cmdPtr->isproc = 1; cmdPtr->u.proc.argListObjPtr = argListObjPtr; + cmdPtr->u.proc.argListLen = argListLen; cmdPtr->u.proc.bodyObjPtr = bodyObjPtr; + cmdPtr->u.proc.argsPos = -1; + cmdPtr->u.proc.arglist = (struct Jim_ProcArg *)(cmdPtr + 1); Jim_IncrRefCount(argListObjPtr); Jim_IncrRefCount(bodyObjPtr); - cmdPtr->u.proc.leftArity = leftArity; - cmdPtr->u.proc.optionalArgs = optionalArgs; - cmdPtr->u.proc.args = args; - cmdPtr->u.proc.rightArity = rightArity; - cmdPtr->u.proc.staticVars = NULL; - cmdPtr->u.proc.prevCmd = NULL; - cmdPtr->inUse = 1; /* Create the statics hash table. */ if (staticsListObjPtr) { @@ -3609,6 +3613,59 @@ static int JimCreateProcedure(Jim_Interp *interp, const char *cmdName, } } + /* Parse the args out into arglist, validating as we go */ + /* Examine the argument list for default parameters and 'args' */ + for (i = 0; i < argListLen; i++) { + Jim_Obj *argPtr; + Jim_Obj *nameObjPtr; + Jim_Obj *defaultObjPtr; + int len; + int n = 1; + + /* Examine a parameter */ + Jim_ListIndex(interp, argListObjPtr, i, &argPtr, JIM_NONE); + len = Jim_ListLength(interp, argPtr); + if (len == 0) { + Jim_SetResultString(interp, "procedure has argument with no name", -1); + goto err; + } + if (len > 2) { + Jim_SetResultString(interp, "procedure has argument with too many fields", -1); + goto err; + } + + if (len == 2) { + /* Optional parameter */ + Jim_ListIndex(interp, argPtr, 0, &nameObjPtr, JIM_NONE); + Jim_ListIndex(interp, argPtr, 1, &defaultObjPtr, JIM_NONE); + } + else { + /* Required parameter */ + nameObjPtr = argPtr; + defaultObjPtr = NULL; + } + + + if (Jim_CompareStringImmediate(interp, nameObjPtr, "args")) { + if (cmdPtr->u.proc.argsPos >= 0) { + Jim_SetResultString(interp, "procedure has 'args' specified more than once", -1); + goto err; + } + cmdPtr->u.proc.argsPos = i; + } + else { + if (len == 2) { + cmdPtr->u.proc.optArity += n; + } + else { + cmdPtr->u.proc.reqArity += n; + } + } + + cmdPtr->u.proc.arglist[i].nameObjPtr = nameObjPtr; + cmdPtr->u.proc.arglist[i].defaultObjPtr = defaultObjPtr; + } + /* Add the new command */ /* It may already exist, so we try to delete the old one. @@ -3618,7 +3675,7 @@ static int JimCreateProcedure(Jim_Interp *interp, const char *cmdName, * BUT, if 'local' is in force, instead of deleting the existing * proc, we stash a reference to the old proc here. */ - he = Jim_FindHashEntry(&interp->commands, cmdName); + he = Jim_FindHashEntry(&interp->commands, Jim_String(cmdName)); if (he) { /* There was an old procedure with the same name, this requires * a 'proc epoch' update. */ @@ -3638,18 +3695,20 @@ static int JimCreateProcedure(Jim_Interp *interp, const char *cmdName, else { if (he) { /* Replace the existing proc */ - Jim_DeleteHashEntry(&interp->commands, cmdName); + Jim_DeleteHashEntry(&interp->commands, Jim_String(cmdName)); } - Jim_AddHashEntry(&interp->commands, cmdName, cmdPtr); + Jim_AddHashEntry(&interp->commands, Jim_String(cmdName), cmdPtr); } /* Unlike Tcl, set the name of the proc as the result */ - Jim_SetResultString(interp, cmdName, -1); + Jim_SetResult(interp, cmdName); return JIM_OK; err: - Jim_FreeHashTable(cmdPtr->u.proc.staticVars); + if (cmdPtr->u.proc.staticVars) { + Jim_FreeHashTable(cmdPtr->u.proc.staticVars); + } Jim_Free(cmdPtr->u.proc.staticVars); Jim_DecrRefCount(interp, argListObjPtr); Jim_DecrRefCount(interp, bodyObjPtr); @@ -10115,6 +10174,45 @@ static int JimSetProcArg(Jim_Interp *interp, Jim_Obj *argNameObj, Jim_Obj *argVa return retcode; } +/** + * Sets the interp result to be an error message indicating the required proc args. + */ +static void JimSetProcWrongArgs(Jim_Interp *interp, Jim_Obj *procNameObj, Jim_Cmd *cmd) +{ + /* Create a nice error message, consistent with Tcl 8.5 */ + Jim_Obj *argmsg = Jim_NewStringObj(interp, "", 0); + int i; + + for (i = 0; i < cmd->u.proc.argListLen; i++) { + Jim_AppendString(interp, argmsg, " ", 1); + + if (i == cmd->u.proc.argsPos) { + if (cmd->u.proc.arglist[i].defaultObjPtr) { + /* Renamed args */ + Jim_AppendString(interp, argmsg, "?", 1); + Jim_AppendObj(interp, argmsg, cmd->u.proc.arglist[i].defaultObjPtr); + Jim_AppendString(interp, argmsg, " ...?", -1); + } + else { + /* We have plain args */ + Jim_AppendString(interp, argmsg, "?argument ...?", -1); + } + } + else { + if (cmd->u.proc.arglist[i].defaultObjPtr) { + Jim_AppendString(interp, argmsg, "?", 1); + Jim_AppendObj(interp, argmsg, cmd->u.proc.arglist[i].nameObjPtr); + Jim_AppendString(interp, argmsg, "?", 1); + } + else { + Jim_AppendObj(interp, argmsg, cmd->u.proc.arglist[i].nameObjPtr); + } + } + } + Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s%#s\"", procNameObj, argmsg); + Jim_FreeNewObj(interp, argmsg); +} + /* Call a procedure implemented in Tcl. * It's possible to speed-up a lot this function, currently * the callframes are not cached, but allocated and @@ -10123,52 +10221,17 @@ static int JimSetProcArg(Jim_Interp *interp, Jim_Obj *argNameObj, Jim_Obj *argVa * * This can be fixed just implementing callframes caching * in JimCreateCallFrame() and JimFreeCallFrame(). */ -int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, const char *filename, int linenr, int argc, +static int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, const char *filename, int linenr, int argc, Jim_Obj *const *argv) { - int i, d, retcode; Jim_CallFrame *callFramePtr; - Jim_Obj *argObjPtr; - Jim_Obj *procname = argv[0]; Jim_Stack *prevLocalProcs; + int i, d, retcode, optargs; /* Check arity */ - if (argc - 1 < cmd->u.proc.leftArity + cmd->u.proc.rightArity || - (!cmd->u.proc.args && argc - 1 > cmd->u.proc.leftArity + cmd->u.proc.rightArity + cmd->u.proc.optionalArgs)) { - /* Create a nice error message, consistent with Tcl 8.5 */ - Jim_Obj *argmsg = Jim_NewStringObj(interp, "", 0); - int arglen = Jim_ListLength(interp, cmd->u.proc.argListObjPtr); - - for (i = 0; i < arglen; i++) { - Jim_Obj *objPtr; - Jim_ListIndex(interp, cmd->u.proc.argListObjPtr, i, &argObjPtr, JIM_NONE); - - Jim_AppendString(interp, argmsg, " ", 1); - - if (i < cmd->u.proc.leftArity || i >= arglen - cmd->u.proc.rightArity) { - Jim_AppendObj(interp, argmsg, argObjPtr); - } - else if (i == arglen - cmd->u.proc.rightArity - cmd->u.proc.args) { - if (Jim_ListLength(interp, argObjPtr) == 1) { - /* We have plain args */ - Jim_AppendString(interp, argmsg, "?argument ...?", -1); - } - else { - Jim_AppendString(interp, argmsg, "?", 1); - Jim_ListIndex(interp, argObjPtr, 1, &objPtr, JIM_NONE); - Jim_AppendObj(interp, argmsg, objPtr); - Jim_AppendString(interp, argmsg, " ...?", -1); - } - } - else { - Jim_AppendString(interp, argmsg, "?", 1); - Jim_ListIndex(interp, argObjPtr, 0, &objPtr, JIM_NONE); - Jim_AppendObj(interp, argmsg, objPtr); - Jim_AppendString(interp, argmsg, "?", 1); - } - } - Jim_SetResultFormatted(interp, "wrong # args: should be \"%#s%#s\"", procname, argmsg); - Jim_FreeNewObj(interp, argmsg); + if (argc - 1 < cmd->u.proc.reqArity || + (cmd->u.proc.argsPos < 0 && argc - 1 > cmd->u.proc.reqArity + cmd->u.proc.optArity)) { + JimSetProcWrongArgs(interp, argv[0], cmd); return JIM_ERR; } @@ -10191,77 +10254,42 @@ int JimCallProcedure(Jim_Interp *interp, Jim_Cmd *cmd, const char *filename, int Jim_IncrRefCount(cmd->u.proc.bodyObjPtr); interp->framePtr = callFramePtr; - /* Simplify arg counting */ - argv++; - argc--; - - /* Set arguments */ + /* How many optional args are available */ + optargs = (argc - 1 - cmd->u.proc.reqArity); - /* Assign in this order: - * leftArity required args. - * rightArity required args (but actually do it last for simplicity) - * optionalArgs optional args - * remaining args into 'args' if 'args' - */ + /* Step 'i' along the actual args, and step 'd' along the formal args */ + i = 1; + for (d = 0; d < cmd->u.proc.argListLen; d++) { + Jim_Obj *nameObjPtr = cmd->u.proc.arglist[d].nameObjPtr; + if (d == cmd->u.proc.argsPos) { + /* assign $args */ + int argsLen = 0; + if (cmd->u.proc.reqArity + cmd->u.proc.optArity < argc - 1) { + argsLen = argc - 1 - (cmd->u.proc.reqArity + cmd->u.proc.optArity); + } + Jim_Obj *listObjPtr = Jim_NewListObj(interp, &argv[i], argsLen); - /* Note that 'd' steps along the arg list, whilst argc/argv follow the supplied args */ + /* It is possible to rename args. */ + if (cmd->u.proc.arglist[d].defaultObjPtr) { + nameObjPtr =cmd->u.proc.arglist[d].defaultObjPtr; + } + retcode = Jim_SetVariable(interp, nameObjPtr, listObjPtr); + if (retcode != JIM_OK) { + goto badargset; + } - /* leftArity required args */ - for (d = 0; d < cmd->u.proc.leftArity; d++) { - Jim_ListIndex(interp, cmd->u.proc.argListObjPtr, d, &argObjPtr, JIM_NONE); - retcode = JimSetProcArg(interp, argObjPtr, *argv++); - if (retcode != JIM_OK) { - goto badargset; + i += argsLen; + continue; } - argc--; - } - - /* Shorten our idea of the number of supplied args */ - argc -= cmd->u.proc.rightArity; - - /* optionalArgs optional args */ - for (i = 0; i < cmd->u.proc.optionalArgs; i++) { - Jim_Obj *nameObjPtr; - Jim_Obj *valueObjPtr; - Jim_ListIndex(interp, cmd->u.proc.argListObjPtr, d++, &argObjPtr, JIM_NONE); - - /* The name is the first element of the list */ - Jim_ListIndex(interp, argObjPtr, 0, &nameObjPtr, JIM_NONE); - if (argc) { - valueObjPtr = *argv++; - argc--; + /* Optional or required? */ + if (cmd->u.proc.arglist[d].defaultObjPtr == NULL || optargs-- > 0) { + retcode = JimSetProcArg(interp, nameObjPtr, argv[i++]); } else { - /* No more values, so use default */ - /* The value is the second element of the list */ - Jim_ListIndex(interp, argObjPtr, 1, &valueObjPtr, JIM_NONE); + /* Ran out, so use the default */ + retcode = Jim_SetVariable(interp, nameObjPtr, cmd->u.proc.arglist[d].defaultObjPtr); } - Jim_SetVariable(interp, nameObjPtr, valueObjPtr); - } - - /* Any remaining args go to 'args' */ - if (cmd->u.proc.args) { - Jim_Obj *listObjPtr = Jim_NewListObj(interp, argv, argc); - - /* Get the 'args' name from the procedure args */ - Jim_ListIndex(interp, cmd->u.proc.argListObjPtr, d, &argObjPtr, JIM_NONE); - - /* It is possible to rename args. */ - i = Jim_ListLength(interp, argObjPtr); - if (i == 2) { - Jim_ListIndex(interp, argObjPtr, 1, &argObjPtr, JIM_NONE); - } - - Jim_SetVariable(interp, argObjPtr, listObjPtr); - argv += argc; - d++; - } - - /* rightArity required args */ - for (i = 0; i < cmd->u.proc.rightArity; i++) { - Jim_ListIndex(interp, cmd->u.proc.argListObjPtr, d++, &argObjPtr, JIM_NONE); - retcode = JimSetProcArg(interp, argObjPtr, *argv++); if (retcode != JIM_OK) { goto badargset; } @@ -10307,7 +10335,7 @@ badargset: else if (retcode == JIM_ERR) { interp->addStackTrace++; Jim_DecrRefCount(interp, interp->errorProc); - interp->errorProc = procname; + interp->errorProc = argv[0]; Jim_IncrRefCount(interp->errorProc); } return retcode; @@ -12453,87 +12481,16 @@ static int Jim_TailcallCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const /* [proc] */ static int Jim_ProcCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv) { - int argListLen; - int leftArity, rightArity; - int i; - int optionalArgs = 0; - int args = 0; - if (argc != 4 && argc != 5) { Jim_WrongNumArgs(interp, 1, argv, "name arglist ?statics? body"); return JIM_ERR; } - if (JimValidName(interp, "procedure", argv[1]) != JIM_OK) { - return JIM_ERR; - } - - argListLen = Jim_ListLength(interp, argv[2]); - leftArity = 0; - rightArity = 0; - - /* Examine the argument list for default parameters and 'args' */ - for (i = 0; i < argListLen; i++) { - Jim_Obj *argPtr; - int len; - - /* Examine a parameter */ - Jim_ListIndex(interp, argv[2], i, &argPtr, JIM_NONE); - len = Jim_ListLength(interp, argPtr); - if (len == 0) { - Jim_SetResultString(interp, "procedure has argument with no name", -1); - return JIM_ERR; - } - if (len > 2) { - Jim_SetResultString(interp, "procedure has argument with too many fields", -1); - return JIM_ERR; - } - - if (len == 2) { - /* May be {args newname} */ - Jim_ListIndex(interp, argPtr, 0, &argPtr, JIM_NONE); - } - - if (Jim_CompareStringImmediate(interp, argPtr, "args")) { - if (args) { - Jim_SetResultString(interp, "procedure has 'args' specified more than once", -1); - return JIM_ERR; - } - if (rightArity) { - Jim_SetResultString(interp, "procedure has 'args' in invalid position", -1); - return JIM_ERR; - } - args = 1; - continue; - } - - /* Does this parameter have a default? */ - if (len == 1) { - /* A required arg. Is it part of leftArity or rightArity? */ - if (optionalArgs || args) { - rightArity++; - } - else { - leftArity++; - } - } - else { - /* Optional arg. Can't be after rightArity */ - if (rightArity || args) { - Jim_SetResultString(interp, "procedure has optional arg in invalid position", -1); - return JIM_ERR; - } - optionalArgs++; - } - } - if (argc == 4) { - return JimCreateProcedure(interp, Jim_String(argv[1]), - argv[2], NULL, argv[3], leftArity, optionalArgs, args, rightArity); + return JimCreateProcedure(interp, argv[1], argv[2], NULL, argv[3]); } else { - return JimCreateProcedure(interp, Jim_String(argv[1]), - argv[2], argv[3], argv[4], leftArity, optionalArgs, args, rightArity); + return JimCreateProcedure(interp, argv[1], argv[2], argv[3], argv[4]); } } @@ -466,6 +466,8 @@ typedef int (*Jim_CmdProc)(struct Jim_Interp *interp, int argc, Jim_Obj *const *argv); typedef void (*Jim_DelCmdProc)(struct Jim_Interp *interp, void *privData); + + /* A command is implemented in C if funcPtr is != NULL, otherwise * it's a Tcl procedure with the arglist and body represented by the * two objects referenced by arglistObjPtr and bodyoObjPtr. */ @@ -483,13 +485,17 @@ typedef struct Jim_Cmd { /* Tcl procedure */ Jim_Obj *argListObjPtr; Jim_Obj *bodyObjPtr; - Jim_HashTable *staticVars; /* Static vars hash table. NULL if no statics. */ - int leftArity; /* Required args assigned from the left */ - int optionalArgs; /* Number of optional args (default values) */ - int rightArity; /* Required args assigned from the right */ - int args; /* True if 'args' specified */ - struct Jim_Cmd *prevCmd; /* Previous command defn if proc created 'local' */ - int upcall; /* True if proc is currently in upcall */ + Jim_HashTable *staticVars; /* Static vars hash table. NULL if no statics. */ + struct Jim_Cmd *prevCmd; /* Previous command defn if proc created 'local' */ + int argListLen; /* Length of argListObjPtr */ + int reqArity; /* Number of required parameters */ + int optArity; /* Number of optional parameters */ + int argsPos; /* Position of 'args', if specified, or -1 */ + int upcall; /* True if proc is currently in upcall */ + struct Jim_ProcArg { + Jim_Obj *nameObjPtr; /* Name of this arg */ + Jim_Obj *defaultObjPtr; /* Default value, (or rename for $args) */ + } *arglist; } proc; } u; } Jim_Cmd; diff --git a/jim_tcl.txt b/jim_tcl.txt index cffbae1..dbcb34a 100644 --- a/jim_tcl.txt +++ b/jim_tcl.txt @@ -55,6 +55,10 @@ The major differences with Tcl 8.5/8.6 are: RECENT CHANGES -------------- +Changes between 0.71 and 0.72 +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +1. Allow 'args' and optional parameters in any position + Changes between 0.70 and 0.71 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 1. Allow 'args' to be renamed in procs @@ -1052,14 +1056,14 @@ Tcl commands. The 'proc' command is used to create a new Tcl command procedure: -+*proc* 'name args ?statics? body'+ ++*proc* 'name arglist ?statics? body'+ The new command is name *name*, and it replaces any existing command there may have been by that name. Whenever the new command is invoked, the contents of *body* will be executed by the Tcl interpreter. -*args* specifies the formal arguments to the procedure. +*arglist* specifies the formal arguments to the procedure. It consists of a list, possibly empty, of the following argument specifiers: @@ -1078,20 +1082,12 @@ argument specifiers: +*args*+:: Variable Argument - The special name *args*, which is - assigned all remaining arguments (including none). The + assigned all remaining arguments (including none) as a list. The variable argument may only be specified once. Note that the syntax +args newname+ may be used to retain the special behaviour of *args* with a different local name. In this case, the variable is named *newname* rather than *args*. -Arguments must be provided in the following order, any of which -may be omitted: - -1. Required Arguments (Left) -2. Optional Arguments -3. Variable Argument -4. Required Arguments (Right) - When the command is invoked, a local variable will be created for each of the formal arguments to the procedure; its value will be the value of corresponding argument in the invoking command or the argument's @@ -1102,14 +1098,30 @@ invocation. However, there must be enough actual arguments for all required arguments, and there must not be any extra actual arguments (unless the Variable Argument is specified). -Actual arguments are assigned to formal arguments as follows: +Actual arguments are assigned to formal arguments as in left-to-right +order with the following precedence. + +1. Required Arguments (including Reference Arguments) +2. Optional Arguments +3. Variable Argument + +The following example illustrates precedence. Assume a procedure declaration: + + proc p {{a A} args b {c C} d} {...} + +This procedure requires at least two arguments, but can accept an unlimited number. +The following table shows how various numbers of arguments are assigned. +Values marked as '-' are assigned the default value. -1. Left Required Arguments are assigned from the left -2. Right Required Arguments are assigned from the right -3. Default Arguments are assigned from the left, following the Left Required Arguments. -4. A list is formed from any remaining arguments, which are then - are assigned to the 'args' Variable Argument (if specified). The list will be empty - if there are no remaining arguments. +[width="40%",frame="topbot",options="header"] +|============== +|Number of arguments|a|args|b|c|d +|2|-|-|1|-|2 +|3|1|-|2|-|3 +|4|1|-|2|3|4 +|5|1|2|3|4|5 +|6|1|2,3|4|5|6 +|============== When *body* is being executed, variable names normally refer to local variables, which are created automatically when referenced and deleted diff --git a/tests/proc-new.test b/tests/proc-new.test index 9f18f64..8703748 100644 --- a/tests/proc-new.test +++ b/tests/proc-new.test @@ -28,6 +28,14 @@ proc hproc {{a aa} args} { list a $a args $args } +proc iproc {{a aa} b {c cc}} { + list a $a b $b c $c +} + +proc jproc {args {a aa} b {c cc} d} { + list a $a b $b c $c d $d args $args +} + set n 1 foreach {proc params result} { aproc {} {} @@ -48,6 +56,13 @@ foreach {proc params result} { hproc {} {a aa args {}} hproc {A} {a A args {}} hproc {A X Y Z} {a A args {X Y Z}} + iproc {B} {a aa b B c cc} + iproc {A B} {a A b B c cc} + iproc {A B C} {a A b B c C} + jproc {B D} {a aa b B c cc d D args {}} + jproc {A B D} {a A b B c cc d D args {}} + jproc {A B C D} {a A b B c C d D args {}} + jproc {E F A B C D} {a A b B c C d D args {E F}} } { test proc-1.$n "Proc args combos" [list $proc {*}$params] $result incr n |