aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--jim-namespace.c2
-rw-r--r--jim.c141
-rw-r--r--tests/apply.test136
-rw-r--r--tests/applyns.test99
4 files changed, 350 insertions, 28 deletions
diff --git a/jim-namespace.c b/jim-namespace.c
index 9b606c7..3413705 100644
--- a/jim-namespace.c
+++ b/jim-namespace.c
@@ -96,11 +96,13 @@ int Jim_CreateNamespaceVariable(Jim_Interp *interp, Jim_Obj *varNameObj, Jim_Obj
{
int rc;
Jim_IncrRefCount(varNameObj);
+ Jim_IncrRefCount(targetNameObj);
/* push non-namespace vars if in namespace eval? */
rc = Jim_SetVariableLink(interp, varNameObj, targetNameObj, interp->topFramePtr);
Jim_DecrRefCount(interp, varNameObj);
+ Jim_DecrRefCount(interp, targetNameObj);
return rc;
}
diff --git a/jim.c b/jim.c
index d5e04af..0eaafbb 100644
--- a/jim.c
+++ b/jim.c
@@ -3588,9 +3588,32 @@ static const Jim_HashTableType JimCommandsHashTableType = {
#ifdef jim_ext_namespace
/**
- * Qualifies 'name' with the current namespace if necessary and
- * returns the "unscoped" name (that is, without the leading ::).
- * The object stored in *objPtrPtr should be decremented after use.
+ * Returns the "unscoped" version of the given namespace.
+ * That is, the fully qualfied name without the leading ::
+ * The returned value is either nsObj, or an object with a zero ref count.
+ */
+static Jim_Obj *JimQualifyNameObj(Jim_Interp *interp, Jim_Obj *nsObj)
+{
+ const char *name = Jim_String(nsObj);
+ if (name[0] == ':' && name[1] == ':') {
+ /* This command is being defined in the global namespace */
+ while (*++name == ':') {
+ }
+ nsObj = Jim_NewStringObj(interp, name, -1);
+ }
+ else if (Jim_Length(interp->framePtr->nsObj)) {
+ /* This command is being defined in a non-global namespace */
+ nsObj = Jim_DuplicateObj(interp, interp->framePtr->nsObj);
+ Jim_AppendStrings(interp, nsObj, "::", name, NULL);
+ }
+ return nsObj;
+}
+
+/**
+ * An efficient version of JimQualifyNameObj() where the name is
+ * available (and needed) as a 'const char *'.
+ * Avoids creating an object if not necessary.
+ * The object stored in *objPtrPtr should be disposed of with JimFreeQualifiedName() after use.
*/
static const char *JimQualifyName(Jim_Interp *interp, const char *name, Jim_Obj **objPtrPtr)
{
@@ -3611,7 +3634,9 @@ static const char *JimQualifyName(Jim_Interp *interp, const char *name, Jim_Obj
*objPtrPtr = objPtr;
return name;
}
+
#define JimFreeQualifiedName(INTERP, OBJ) Jim_DecrRefCount((INTERP), (OBJ))
+
#else
/* We can be more efficient in the no-namespace case */
#define JimQualifyName(INTERP, NAME, DUMMY) (((NAME)[0] == ':' && (NAME)[1] == ':') ? (NAME) + 2 : (NAME))
@@ -3754,18 +3779,12 @@ static void JimUpdateProcNamespace(Jim_Interp *interp, Jim_Cmd *cmdPtr, const ch
#endif
}
-static int JimCreateProcedure(Jim_Interp *interp, Jim_Obj *cmdNameObj,
- Jim_Obj *argListObjPtr, Jim_Obj *staticsListObjPtr, Jim_Obj *bodyObjPtr)
+static Jim_Cmd *JimCreateProcedureCmd(Jim_Interp *interp, Jim_Obj *argListObjPtr,
+ Jim_Obj *staticsListObjPtr, Jim_Obj *bodyObjPtr, Jim_Obj *nsObj)
{
Jim_Cmd *cmdPtr;
int argListLen;
int i;
- Jim_Obj *qualifiedCmdNameObj;
- const char *cmdname;
-
- if (JimValidName(interp, "procedure", cmdNameObj) != JIM_OK) {
- return JIM_ERR;
- }
argListLen = Jim_ListLength(interp, argListObjPtr);
@@ -3779,7 +3798,7 @@ static int JimCreateProcedure(Jim_Interp *interp, Jim_Obj *cmdNameObj,
cmdPtr->u.proc.bodyObjPtr = bodyObjPtr;
cmdPtr->u.proc.argsPos = -1;
cmdPtr->u.proc.arglist = (struct Jim_ProcArg *)(cmdPtr + 1);
- cmdPtr->u.proc.nsObj = interp->emptyObj;
+ cmdPtr->u.proc.nsObj = nsObj ? nsObj : interp->emptyObj;
Jim_IncrRefCount(argListObjPtr);
Jim_IncrRefCount(bodyObjPtr);
Jim_IncrRefCount(cmdPtr->u.proc.nsObj);
@@ -3805,7 +3824,7 @@ static int JimCreateProcedure(Jim_Interp *interp, Jim_Obj *cmdNameObj,
Jim_SetResultString(interp, "argument with no name", -1);
err:
JimDecrCmdRefCount(interp, cmdPtr);
- return JIM_ERR;
+ return NULL;
}
if (len > 2) {
Jim_SetResultFormatted(interp, "too many fields in argument specifier \"%#s\"", argPtr);
@@ -3844,19 +3863,7 @@ err:
cmdPtr->u.proc.arglist[i].defaultObjPtr = defaultObjPtr;
}
- /* Add the new command */
- cmdname = JimQualifyName(interp, Jim_String(cmdNameObj), &qualifiedCmdNameObj);
-
- JimCreateCommand(interp, cmdname, cmdPtr);
-
- /* Calculate and set the namespace for this proc */
- JimUpdateProcNamespace(interp, cmdPtr, cmdname);
-
- JimFreeQualifiedName(interp, qualifiedCmdNameObj);
-
- /* Unlike Tcl, set the name of the proc as the result */
- Jim_SetResult(interp, cmdNameObj);
- return JIM_OK;
+ return cmdPtr;
}
int Jim_DeleteCommand(Jim_Interp *interp, const char *name)
@@ -12839,17 +12846,41 @@ static int Jim_AliasCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *ar
/* [proc] */
static int Jim_ProcCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
+ Jim_Cmd *cmd;
+
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;
+ }
+
if (argc == 4) {
- return JimCreateProcedure(interp, argv[1], argv[2], NULL, argv[3]);
+ cmd = JimCreateProcedureCmd(interp, argv[2], NULL, argv[3], NULL);
}
else {
- return JimCreateProcedure(interp, argv[1], argv[2], argv[3], argv[4]);
+ cmd = JimCreateProcedureCmd(interp, argv[2], argv[3], argv[4], NULL);
}
+
+ if (cmd) {
+ /* Add the new command */
+ Jim_Obj *qualifiedCmdNameObj;
+ const char *cmdname = JimQualifyName(interp, Jim_String(argv[1]), &qualifiedCmdNameObj);
+
+ JimCreateCommand(interp, cmdname, cmd);
+
+ /* Calculate and set the namespace for this proc */
+ JimUpdateProcNamespace(interp, cmd, cmdname);
+
+ JimFreeQualifiedName(interp, qualifiedCmdNameObj);
+
+ /* Unlike Tcl, set the name of the proc as the result */
+ Jim_SetResult(interp, argv[1]);
+ return JIM_OK;
+ }
+ return JIM_ERR;
}
/* [local] */
@@ -12911,6 +12942,59 @@ static int Jim_UpcallCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *a
}
}
+/* [apply] */
+static int Jim_ApplyCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ if (argc < 2) {
+ Jim_WrongNumArgs(interp, 1, argv, "lambdaExpr ?arg ...?");
+ return JIM_ERR;
+ }
+ else {
+ int ret;
+ Jim_Cmd *cmd;
+ Jim_Obj *argListObjPtr;
+ Jim_Obj *bodyObjPtr;
+ Jim_Obj *nsObj = NULL;
+ Jim_Obj **nargv;
+
+ int len = Jim_ListLength(interp, argv[1]);
+ if (len != 2 && len != 3) {
+ Jim_SetResultFormatted(interp, "can't interpret \"%#s\" as a lambda expression", argv[1]);
+ return JIM_ERR;
+ }
+
+ if (len == 3) {
+#ifdef jim_ext_namespace
+ /* Need to canonicalise the given namespace. */
+ nsObj = JimQualifyNameObj(interp, Jim_ListGetIndex(interp, argv[1], 2));
+#else
+ Jim_SetResultString(interp, "namespaces not enabled", -1);
+ return JIM_ERR;
+#endif
+ }
+ argListObjPtr = Jim_ListGetIndex(interp, argv[1], 0);
+ bodyObjPtr = Jim_ListGetIndex(interp, argv[1], 1);
+
+ cmd = JimCreateProcedureCmd(interp, argListObjPtr, NULL, bodyObjPtr, nsObj);
+
+ if (cmd) {
+ /* Create a new argv array with a dummy argv[0], for error messages */
+ nargv = Jim_Alloc((argc - 2 + 1) * sizeof(*nargv));
+ nargv[0] = Jim_NewStringObj(interp, "apply lambdaExpr", -1);
+ Jim_IncrRefCount(nargv[0]);
+ memcpy(&nargv[1], argv + 2, (argc - 2) * sizeof(*nargv));
+ ret = JimCallProcedure(interp, cmd, argc - 2 + 1, nargv);
+ Jim_DecrRefCount(interp, nargv[0]);
+ Jim_Free(nargv);
+
+ JimDecrCmdRefCount(interp, cmd);
+ return ret;
+ }
+ return JIM_ERR;
+ }
+}
+
+
/* [concat] */
static int Jim_ConcatCoreCommand(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
@@ -14744,6 +14828,7 @@ static const struct {
{"tailcall", Jim_TailcallCoreCommand},
{"local", Jim_LocalCoreCommand},
{"upcall", Jim_UpcallCoreCommand},
+ {"apply", Jim_ApplyCoreCommand},
{NULL, NULL},
};
diff --git a/tests/apply.test b/tests/apply.test
new file mode 100644
index 0000000..1087fec
--- /dev/null
+++ b/tests/apply.test
@@ -0,0 +1,136 @@
+# Commands covered: apply
+#
+# This file contains a collection of tests for one or more of the Tcl
+# built-in commands. Sourcing this file into Tcl runs the tests and
+# generates output for errors. No output means no errors were found.
+#
+# Copyright (c) 1991-1993 The Regents of the University of California.
+# Copyright (c) 1994-1996 Sun Microsystems, Inc.
+# Copyright (c) 1998-1999 by Scriptics Corporation.
+# Copyright (c) 2005-2006 Miguel Sofer
+#
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+
+source [file dirname [info script]]/testing.tcl
+
+needs cmd apply
+
+
+# Tests for wrong number of arguments
+
+test apply-1.1 {too few arguments} -returnCodes error -body {
+ apply
+} -result {wrong # args: should be "apply lambdaExpr ?arg ...?"}
+
+# Tests for malformed lambda
+
+test apply-2.0 {malformed lambda} -returnCodes error -body {
+ set lambda a
+ apply $lambda
+} -result {can't interpret "a" as a lambda expression}
+test apply-2.1 {malformed lambda} -returnCodes error -body {
+ set lambda [list a b c d]
+ apply $lambda
+} -result {can't interpret "a b c d" as a lambda expression}
+test apply-2.2 {malformed lambda} -body {
+ set lambda [list {{}} boo]
+ apply $lambda
+} -returnCodes error -match glob -result {*argument with no name}
+test apply-2.3 {malformed lambda} {
+ set lambda [list {{a b c}} boo]
+ list [catch {apply $lambda} msg] $msg
+} {1 {too many fields in argument specifier "a b c"}}
+
+# Note that Jim allow both of these
+test apply-2.4 {malformed lambda} tcl {
+ set lambda [list a(1) {return $a(1)}]
+ list [catch {apply $lambda x} msg] $msg
+} {1 {formal parameter "a(1)" is an array element}}
+test apply-2.5 {malformed lambda} tcl {
+ set lambda [list a::b {return $a::b}]
+ list [catch {apply $lambda x} msg] $msg
+} {1 {formal parameter "a::b" is not a simple name}}
+
+# Tests for runtime errors in the lambda expression
+
+test apply-4.1 {error in arguments to lambda expression} -body {
+ set lambda [list x {set x 1}]
+ apply $lambda
+} -returnCodes error -result {wrong # args: should be "apply lambdaExpr x"}
+test apply-4.2 {error in arguments to lambda expression} -body {
+ set lambda [list x {set x 1}]
+ apply $lambda a b
+} -returnCodes error -result {wrong # args: should be "apply lambdaExpr x"}
+
+test apply-5.1 {runtime error in lambda expression} {
+ set lambda [list {} {error foo}]
+ list [catch {apply $lambda} msg] $msg
+} {1 foo}
+
+# Tests for correct execution; as the implementation is the same as that for
+# procs, the general functionality is mostly tested elsewhere
+
+test apply-6.1 {info level} {
+ set lev [info level]
+ set lambda [list {} {info level}]
+ expr {[apply $lambda] - $lev}
+} 1
+test apply-6.2 {info level} tcl {
+ set lambda [list {} {info level 0}]
+ apply $lambda
+} {apply {{} {info level 0}}}
+test apply-6.3 {info level} tcl {
+ set lambda [list args {info level 0}]
+ apply $lambda x y
+} {apply {args {info level 0}} x y}
+
+# Tests for correct argument treatment
+
+set applyBody {
+ set res {}
+ foreach v [info locals] {
+ if {$v eq "res"} continue
+ lappend res [list $v [set $v]]
+ }
+ set res
+}
+
+test apply-8.1 {args treatment} {
+ apply [list args $applyBody] 1 2 3
+} {{args {1 2 3}}}
+test apply-8.2 {args treatment} {
+ apply [list {x args} $applyBody] 1 2
+} {{x 1} {args 2}}
+test apply-8.3 {args treatment} {
+ apply [list {x args} $applyBody] 1 2 3
+} {{x 1} {args {2 3}}}
+test apply-8.4 {default values} {
+ apply [list {{x 1} {y 2}} $applyBody]
+} {{x 1} {y 2}}
+test apply-8.5 {default values} {
+ apply [list {{x 1} {y 2}} $applyBody] 3 4
+} {{x 3} {y 4}}
+test apply-8.6 {default values} {
+ apply [list {{x 1} {y 2}} $applyBody] 3
+} {{x 3} {y 2}}
+test apply-8.7 {default values} {
+ apply [list {x {y 2}} $applyBody] 1
+} {{x 1} {y 2}}
+test apply-8.8 {default values} {
+ apply [list {x {y 2}} $applyBody] 1 3
+} {{x 1} {y 3}}
+test apply-8.9 {default values} {
+ apply [list {x {y 2} args} $applyBody] 1
+} {{x 1} {y 2} {args {}}}
+test apply-8.10 {default values} {
+ apply [list {x {y 2} args} $applyBody] 1 3
+} {{x 1} {y 3} {args {}}}
+
+::tcltest::cleanupTests
+return
+
+# Local Variables:
+# mode: tcl
+# fill-column: 78
+# End:
diff --git a/tests/applyns.test b/tests/applyns.test
new file mode 100644
index 0000000..0483692
--- /dev/null
+++ b/tests/applyns.test
@@ -0,0 +1,99 @@
+# Commands covered: apply
+#
+# This file contains a collection of tests for one or more of the Tcl
+# built-in commands. Sourcing this file into Tcl runs the tests and
+# generates output for errors. No output means no errors were found.
+#
+# Copyright (c) 1991-1993 The Regents of the University of California.
+# Copyright (c) 1994-1996 Sun Microsystems, Inc.
+# Copyright (c) 1998-1999 by Scriptics Corporation.
+# Copyright (c) 2005-2006 Miguel Sofer
+#
+# See the file "license.terms" for information on usage and redistribution
+# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+
+source [file dirname [info script]]/testing.tcl
+
+needs cmd apply
+needs cmd namespace
+
+# Tests for runtime errors in the lambda expression
+
+# Note: Jim doesn't have the concept of non-existent namespaces
+
+test apply-3.1 {non-existing namespace} -constraints tcl -body {
+ apply [list x {set x 1} ::NONEXIST::FOR::SURE] x
+} -returnCodes error -result {namespace "::NONEXIST::FOR::SURE" not found}
+test apply-3.2 {non-existing namespace} -constraints tcl -body {
+ namespace eval ::NONEXIST::FOR::SURE {}
+ set lambda [list x {set x 1} ::NONEXIST::FOR::SURE]
+ apply $lambda x
+ namespace delete ::NONEXIST
+ apply $lambda x
+} -returnCodes error -result {namespace "::NONEXIST::FOR::SURE" not found}
+test apply-3.3 {non-existing namespace} -constraints tcl -body {
+ apply [list x {set x 1} NONEXIST::FOR::SURE] x
+} -returnCodes error -result {namespace "::NONEXIST::FOR::SURE" not found}
+test apply-3.4 {non-existing namespace} -constraints tcl -body {
+ namespace eval ::NONEXIST::FOR::SURE {}
+ set lambda [list x {set x 1} NONEXIST::FOR::SURE]
+ apply $lambda x
+ namespace delete ::NONEXIST
+ apply $lambda x
+} -returnCodes error -result {namespace "::NONEXIST::FOR::SURE" not found}
+
+# Tests for correct namespace scope
+
+namespace eval ::testApply {
+ proc testApply args {return testApply}
+}
+
+test apply-7.1 {namespace access} {
+ set ::testApply::x 0
+ set body {set x 1; set x}
+ list [apply [list args $body ::testApply]] $::testApply::x
+} {1 0}
+test apply-7.2 {namespace access} {
+ set ::testApply::x 0
+ set body {variable x; set x}
+ list [apply [list args $body ::testApply]] $::testApply::x
+} {0 0}
+test apply-7.3 {namespace access} {
+ set ::testApply::x 0
+ set body {variable x; set x 1}
+ list [apply [list args $body ::testApply]] $::testApply::x
+} {1 1}
+test apply-7.4 {namespace access} {
+ set ::testApply::x 0
+ set body {testApply}
+ apply [list args $body ::testApply]
+} testApply
+test apply-7.5 {namespace access} {
+ set ::testApply::x 0
+ set body {set x 1; set x}
+ list [apply [list args $body testApply]] $::testApply::x
+} {1 0}
+test apply-7.6 {namespace access} {
+ set ::testApply::x 0
+ set body {variable x; set x}
+ list [apply [list args $body testApply]] $::testApply::x
+} {0 0}
+test apply-7.7 {namespace access} {
+ set ::testApply::x 0
+ set body {variable x; set x 1}
+ list [apply [list args $body testApply]] $::testApply::x
+} {1 1}
+test apply-7.8 {namespace access} {
+ set ::testApply::x 0
+ set body {testApply}
+ apply [list args $body testApply]
+} testApply
+
+namespace delete testApply
+
+testreport
+
+# Local Variables:
+# mode: tcl
+# fill-column: 78
+# End: