Index: TODO =================================================================== diff -u -rec9e525c887a0ae430bdb35bef01f499b25d617f -rd40717ac3710f2123cb9359e3d0442ad4ff3da73 --- TODO (.../TODO) (revision ec9e525c887a0ae430bdb35bef01f499b25d617f) +++ TODO (.../TODO) (revision d40717ac3710f2123cb9359e3d0442ad4ff3da73) @@ -2586,7 +2586,13 @@ - nx::mongo: Initial commit of the experimental mongoDB interface for nx - nx.tcl: fix handling of arg in converter +- nx::mongo: + * first step towards handling embedded objects + * one more example script: example-nx-bi.tcl +- nsf:c: fix dispatch of setter without current method +- extended regression tests + TODO: - how to delete attributes? Index: generic/nsf.c =================================================================== diff -u -r8b9d3698ec4f11b2a8fd8c022db0429a3e14ec7d -rd40717ac3710f2123cb9359e3d0442ad4ff3da73 --- generic/nsf.c (.../nsf.c) (revision 8b9d3698ec4f11b2a8fd8c022db0429a3e14ec7d) +++ generic/nsf.c (.../nsf.c) (revision d40717ac3710f2123cb9359e3d0442ad4ff3da73) @@ -12073,11 +12073,28 @@ return (cl && cl->opt) ? cl->opt->clientData : NULL; } +/* + *---------------------------------------------------------------------- + * SetInstVar -- + * + * Set an instance variable of the specified object to the given value. + * + * Results: + * Tcl result code. + * + * Side effects: + * Set instance variable. + * + *---------------------------------------------------------------------- + */ static int SetInstVar(Tcl_Interp *interp, NsfObject *object, Tcl_Obj *nameObj, Tcl_Obj *valueObj) { - Tcl_Obj *resultObj; - int flags = (object->nsPtr) ? TCL_LEAVE_ERR_MSG|TCL_NAMESPACE_ONLY : TCL_LEAVE_ERR_MSG; CallFrame frame, *framePtr = &frame; + Tcl_Obj *resultObj; + int flags; + + assert(object); + flags = (object->nsPtr) ? TCL_LEAVE_ERR_MSG|TCL_NAMESPACE_ONLY : TCL_LEAVE_ERR_MSG; Nsf_PushFrameObj(interp, object, framePtr); if (valueObj == NULL) { @@ -12100,8 +12117,8 @@ SetterCmdClientData *cd = (SetterCmdClientData*)clientData; NsfObject *object = cd->object; - if (!object) return NsfObjErrType(interp, "setter", objv[0], "object", NULL); if (objc > 2) return NsfObjWrongArgs(interp, "wrong # args", object->cmdName, objv[0], "?value?"); + if (!object) return NsfNoDispatchObjectError(interp, ObjStr(objv[0])); if (cd->paramsPtr && objc == 2) { Tcl_Obj *outObjPtr; @@ -12385,7 +12402,9 @@ ForwardCmdClientData *tcd = (ForwardCmdClientData *)clientData; int result, inputArg = 1; - if (!tcd || !tcd->object) return NsfObjErrType(interp, "forwarder", objv[0], "object", NULL); + if (!tcd || !tcd->object) { + return NsfNoDispatchObjectError(interp, "forwarder"); + } if (tcd->passthrough) { /* two short cuts for simple cases */ /* early binding, cmd *resolved, we have to care only for objscope */ @@ -12552,9 +12571,7 @@ assert(tcd->object == GetSelfObj(interp)); if (self == NULL) { - return NsfPrintError(interp, "no object active for alias '%s'; " - "don't call aliased methods via namespace paths", - Tcl_GetCommandName(interp, tcd->aliasCmd)); + return NsfNoDispatchObjectError(interp, Tcl_GetCommandName(interp, tcd->aliasCmd)); } if (Tcl_Command_cmdEpoch(tcd->aliasedCmd)) { @@ -16579,6 +16596,7 @@ } setterClientData = NEW(SetterCmdClientData); + setterClientData->object = NULL; setterClientData->paramsPtr = NULL; length = strlen(methodName); Index: generic/nsf.h =================================================================== diff -u -r65abda7763aa24fe8a33728195cc3043d06ee660 -rd40717ac3710f2123cb9359e3d0442ad4ff3da73 --- generic/nsf.h (.../nsf.h) (revision 65abda7763aa24fe8a33728195cc3043d06ee660) +++ generic/nsf.h (.../nsf.h) (revision d40717ac3710f2123cb9359e3d0442ad4ff3da73) @@ -256,6 +256,9 @@ NsfArgumentError(Tcl_Interp *interp, CONST char *errorMsg, Nsf_Param CONST *paramPtr, Tcl_Obj *cmdNameObj, Tcl_Obj *methodObj); +extern int +NsfNoDispatchObjectError(Tcl_Interp *interp, char *methodName); + #define NSF_LOG_NOTICE 2 #define NSF_LOG_WARN 1 Index: generic/nsfError.c =================================================================== diff -u -rbf592f968ae84740d9ef7c40cddbcbf9f5a68283 -rd40717ac3710f2123cb9359e3d0442ad4ff3da73 --- generic/nsfError.c (.../nsfError.c) (revision bf592f968ae84740d9ef7c40cddbcbf9f5a68283) +++ generic/nsfError.c (.../nsfError.c) (revision d40717ac3710f2123cb9359e3d0442ad4ff3da73) @@ -176,7 +176,6 @@ * *---------------------------------------------------------------------- */ - int NsfArgumentError(Tcl_Interp *interp, CONST char *errorMsg, Nsf_Param CONST *paramPtr, Tcl_Obj *cmdNameObj, Tcl_Obj *methodObj) { @@ -188,6 +187,27 @@ return TCL_ERROR; } +/* + *---------------------------------------------------------------------- + * + * NsfNoDispatchObjectError -- + * + * Produce a error message when method was not dispatched on an object + * + * Results: + * TCL_ERROR + * + * Side effects: + * Sets the result message. + * + *---------------------------------------------------------------------- + */ +int +NsfNoDispatchObjectError(Tcl_Interp *interp, char *methodName) { + return NsfPrintError(interp, "Method %s not dispatched on object; " + "don't call aliased methods via namespace paths!", + methodName); +} extern int NsfObjErrType(Tcl_Interp *interp, Index: library/nx/nx.tcl =================================================================== diff -u -rb2460ba0f8fef2b988ae3f77d6c34e6d7af7e69c -rd40717ac3710f2123cb9359e3d0442ad4ff3da73 --- library/nx/nx.tcl (.../nx.tcl) (revision b2460ba0f8fef2b988ae3f77d6c34e6d7af7e69c) +++ library/nx/nx.tcl (.../nx.tcl) (revision d40717ac3710f2123cb9359e3d0442ad4ff3da73) @@ -309,6 +309,7 @@ if {[info exists returns]} {::nsf::method::property $(object) $r returns $returns} return $r } + Class public method forward { method -default -methodprefix -objframe:switch -onerror -returns -verbose:switch Fisheye: Tag d40717ac3710f2123cb9359e3d0442ad4ff3da73 refers to a dead (removed) revision in file `tests/method-modifiers.test'. Fisheye: No comparison available. Pass `N' to diff? Index: tests/methods.test =================================================================== diff -u --- tests/methods.test (revision 0) +++ tests/methods.test (revision d40717ac3710f2123cb9359e3d0442ad4ff3da73) @@ -0,0 +1,423 @@ +# -*- Tcl -*- +package require nx; namespace import ::nx::* +::nx::configure defaultMethodCallProtection false +package require nx::test + +Test parameter count 10 + +Class create C { + # methods + :method plain_method {} {return [current method]} + :public method public_method {} {return [current method]} + :protected method protected_method {} {return [current method]} + + # forwards + :forward plain_forward %self plain_method + :public forward public_forward %self public_method + :protected forward protected_forward %self protected_method + + # setter + :attribute plain_setter + :public attribute public_setter + :protected attribute protected_setter + + # alias + :alias plain_alias [C info method handle plain_method] + :public alias public_alias [C info method handle public_method] + :protected alias protected_alias [C info method handle protected_method] + + # class-object + :class method plain_object_method {} {return [current method]} + :public class method public_object_method {} {return [current method]} + :protected class method protected_object_method {} {return [current method]} + :class forward plain_object_forward %self plain_object_method + :public class forward public_object_forward %self public_object_method + :protected class forward protected_object_forward %self protected_object_method + :class attribute plain_object_setter + :public class attribute public_object_setter + :protected class attribute protected_object_setter + :class alias plain_object_alias [:class info method handle plain_object_method] + :public class alias public_object_alias [:class info method handle public_object_method] + :protected class alias protected_object_alias [:class info method handle protected_object_method] +} +C create c1 { + # methods + :method plain_object_method {} {return [current method]} + :public method public_object_method {} {return [current method]} + :protected method protected_object_method {} {return [current method]} + + # forwards + :forward plain_object_forward %self plain_object_method + :public forward public_object_forward %self public_object_method + :protected forward protected_object_forward %self protected_object_method + + # setter + :attribute plain_object_setter + :public attribute public_object_setter + :protected attribute protected_object_setter + + # alias + :alias plain_object_alias [:info method handle plain_object_method] + :public alias public_object_alias [:info method handle public_object_method] + :protected alias protected_object_alias [:info method handle protected_object_method] +} +C public attribute s0 +C protected attribute s1 +? {c1 s0 0} 0 +? {::nsf::dispatch c1 s1 1} 1 +C class attribute s3 +? {C s3 3} 3 + +# create a fresh object (different from c1) +C create c2 +# test scripted class level methods +Test case scripted-class-level-methods { + ? {c2 plain_method} "plain_method" + ? {c2 public_method} "public_method" + ? {catch {c2 protected_method}} 1 + ? {::nsf::dispatch c2 protected_method} "protected_method" +} + +# class level forwards +Test case class-level-forwards { + ? {c2 plain_forward} "plain_method" + ? {c2 public_forward} "public_method" + ? {catch {c2 protected_forward}} 1 + ? {::nsf::dispatch c2 protected_forward} "protected_method" +} + +# class level setter +Test case class-level-setter { + ? {c2 plain_setter 1} "1" + ? {c2 public_setter 2} "2" + ? {catch {c2 protected_setter 3}} 1 + ? {::nsf::dispatch c2 protected_setter 4} "4" +} + +# class level alias ....TODO: wanted behavior of [current method]? not "plain_alias"? +Test case class-level-alias { + ? {c2 plain_alias} "plain_method" + ? {c2 public_alias} "public_method" + ? {catch {c2 protected_alias}} 1 + ? {::nsf::dispatch c2 protected_alias} "protected_method" +} + +########### + +# scripted class level methods +Test case scripted-class-object-level { + ? {C plain_object_method} "plain_object_method" + ? {C public_object_method} "public_object_method" + ? {catch {C protected_object_method}} 1 + ? {::nsf::dispatch C protected_object_method} "protected_object_method" +} + +# class level forwards +Test case class-object-level-forwards { + ? {C plain_object_forward} "plain_object_method" + ? {C public_object_forward} "public_object_method" + ? {catch {C protected_object_forward}} 1 + ? {::nsf::dispatch C protected_object_forward} "protected_object_method" +} + +# class level setter +Test case class-object-level-setter { + ? {C plain_object_setter 1} "1" + ? {C public_object_setter 2} "2" + ? {catch {C protected_object_setter 3}} 1 + ? {::nsf::dispatch C protected_object_setter 4} "4" +} + +# class level alias ....TODO: wanted behavior of [current method]? not "plain_alias"? +Test case class-object-level-alias { + ? {C plain_object_alias} "plain_object_method" + ? {C public_object_alias} "public_object_method" + ? {catch {C protected_object_alias}} 1 + ? {::nsf::dispatch C protected_object_alias} "protected_object_method" +} + +########### + +# scripted object level methods +Test case scripted-object-level-methods { + ? {c1 plain_object_method} "plain_object_method" + ? {c1 public_object_method} "public_object_method" + ? {catch {c1 protected_object_method}} 1 + ? {::nsf::dispatch c1 protected_object_method} "protected_object_method" +} + +# object level forwards +Test case object-level-forwards { + ? {c1 plain_object_forward} "plain_object_method" + ? {c1 public_object_forward} "public_object_method" + ? {catch {c1 protected_object_forward}} 1 + ? {::nsf::dispatch c1 protected_object_forward} "protected_object_method" +} + +# object level setter +Test case object-level-setter { + ? {c1 plain_object_setter 1} "1" + ? {c1 public_object_setter 2} "2" + ? {catch {c1 protected_object_setter 3}} 1 + ? {::nsf::dispatch c1 protected_object_setter 4} "4" +} + +# object level alias ....TODO: wanted behavior of [current method]? not "plain_alias"? +Test case object-level-alias { + ? {c1 plain_object_alias} "plain_object_method" + ? {c1 public_object_alias} "public_object_method" + ? {catch {c1 protected_object_alias}} 1 + ? {::nsf::dispatch c1 protected_object_alias} "protected_object_method" + + ? {lsort [c1 info methods]} \ + "plain_object_alias plain_object_forward plain_object_method plain_object_setter public_object_alias public_object_forward public_object_method public_object_setter" + ? {lsort [C class info methods]} \ + "plain_object_alias plain_object_forward plain_object_method plain_object_setter public_object_alias public_object_forward public_object_method public_object_setter s3" +} + +C destroy + +Test case colondispatch { + Object create ::o { + #:public method foo args {;} + :public method bar args {;} + } + ? {o :bar} "::o: methodname ':bar' must not start with a colon" + ? {o eval :bar} "" + ? {o :foo} "::o: methodname ':foo' must not start with a colon" + ? {o eval :foo} "::o: unable to dispatch method 'foo'" +} + +Test case mixinguards { + # define a Class C and mixin class M + Class create C + Class create M + + # register the mixin on C as a class mixin and define a mixinguard + C mixin M + C mixin guard M {1 == 1} + ? {C info mixin guard M} "1 == 1" + C mixin guard M {} + ? {C info mixin guard M} "" + + # now the same as class mixin and class mixin guard + C class mixin M + C class mixin guard M {1 == 1} + ? {C class info mixin guard M} "1 == 1" + C class mixin guard M {} + ? {C class info mixin guard M} "" +} + +Test case mixin-via-objectparam { + # add an object and class mixin via object-parameter and via slots + Class create M1; Class create M2; Class create M3; Class create M4 + Class create C -mixin M1 -object-mixin M2 { + :mixin add M3 + :class mixin add M4 + } + + ? {lsort [C class info mixin classes]} "::M2 ::M4" + #? {lsort [C class info mixin classes]} "::M2" + + ? {lsort [C info mixin classes]} "::M1 ::M3" + #? {lsort [C info mixin classes]} "::M1" + C destroy + M1 destroy; M2 destroy; M3 destroy; M4 destroy; +} + +# testing next via nonpos-args +Test case next-from-nonpos-args { + + Object create o { + :method bar {-y:required -x:required} { + #puts stderr "+++ o x=$x, y=$y [current args] ... next [current next]" + return [list x $x y $y [current args]] + } + } + Class create M { + :method bar {-x:required -y:required} { + #puts stderr "+++ M x=$x, y=$y [current args] ... next [current next]" + return [list x $x y $y [current args] -- {*}[next]] + } + } + + o mixin M + ? {o bar -x 13 -y 14} "x 13 y 14 {-x 13 -y 14} -- x 13 y 14 {-x 13 -y 14}" + ? {o bar -y 14 -x 13} "x 13 y 14 {-y 14 -x 13} -- x 13 y 14 {-y 14 -x 13}" +} + +# +# test method attribute with protected/public +# +Test case attribute-method { + + Class create C { + set x [:attribute a] + + ? [list set _ $x] "::nsf::classes::C::a" + + # attribute with default + :attribute {b b1} + :public attribute {c c1} + :protected attribute {d d1} + + set X [:class attribute A] + ? [list set _ $X] "::C::A" + + # class attribute with default + :class attribute {B B2} + :public class attribute {C C2} + :protected class attribute {D D2} + } + + C create c1 -a 1 + ? {c1 a} 1 + ? {c1 b} b1 + ? {c1 c} c1 + ? {c1 d} "::c1: unable to dispatch method 'd'" + + ? {C A 2} 2 + ? {C A} 2 + ? {C B} B2 + ? {C C} C2 + ? {C D} "Method 'D' unknown for ::C. Consider '::C create D ' instead of '::C D '" + + Object create o { + set x [:attribute a] + ? [list set _ $x] "::o::a" + + # attribute with default + :attribute {b b1} + :public attribute {c c1} + :protected attribute {d d1} + } + ? {o a 2} 2 + ? {o b} b1 + ? {o c} c1 + ? {o d} "::o: unable to dispatch method 'd'" +} + +Test case subcmd { + + Class create Foo { + + :method "Info filter guard" {filter} {return [current object]-[current method]} + :method "Info filter methods" {-guards pattern:optional} {return [current object]-[current method]} + :method "Info args" {} {return [current object]-[current method]} + :method "Info foo" {} {return [current object]-[current method]} + + :class method "INFO filter guard" {a b} {return [current object]-[current method]} + :class method "INFO filter methods" {-guards pattern:optional} {return [current object]-[current method]} + } + + ? {Foo INFO filter guard 1 2} ::Foo-guard + ? {Foo INFO filter methods a*} ::Foo-methods + + Foo create f1 { + :method "list length" {} {return [current object]-[current method]} + :method "list reverse" {} {return [current object]-[current method]} + } + + ? {f1 Info filter guard x} "::f1-guard" + ? {f1 Info filter methods} "::f1-methods" + ? {f1 Info args} "::f1-args" + ? {f1 Info foo} "::f1-foo" + + ? {f1 list length} "::f1-length" + ? {f1 list reverse} "::f1-reverse" +} + +package req nx::serializer +Test case class-object-attribute { + Class create C { + :class attribute x + :attribute a:int + :create c1 + } + ? {C x 1} 1 + ? {C x} 1 + ? {C info methods} "a" + ? {C class info methods} x + ? {c1 a b} {expected integer but got "b" for parameter "a"} + + set s(C) [C serialize] + set s(c1) [c1 serialize] + + # Destroy object and class + c1 destroy + C destroy + + ? {nsf::isobject c1} 0 + ? {nsf::isobject C} 0 + + # create it from the serialized code + eval $s(C) + eval $s(c1) + + # tests should work as again + ? {C x} 1 + ? {C info methods} "a" + ? {C class info methods} x + ? {c1 a b} {expected integer but got "b" for parameter "a"} +} + +# +# Test method deletion +# +Test parameter count 1 + +Test case methoddelete { + nx::Class create C { + :public method foo {x} {return $x} + :public class method bar {x} {return $x} + :create c1 + } + + ? {::nsf::method::delete C x} "Object C: method x is not defined" + ? {::nsf::method::delete C -per-object x} "Object C: method x is not defined" + ? {::nsf::method::delete C foo} "" + ? {::nsf::method::delete C foo} "Object C: method foo is not defined" + ? {::nsf::method::delete C bar} "Object C: method bar is not defined" + ? {::nsf::method::delete C -per-object bar} "" + ? {::nsf::method::delete C -per-object bar} "Object C: method bar is not defined" +} + +# +# Test error message of method modifier +# +Test parameter count 1 + +Test case errormessage { + nx::Class create C + ? {C public method foo {x} {return $x}} "::nsf::classes::C::foo" + ? {C public object method bar {x} {return $x}} \ + "'object' is not a method defining method" + ? {C protected object method bar {x} {return $x}} \ + "'object' is not a method defining method" + ? {C object method bar {x} {return $x}} \ + {Method 'object' unknown for ::C. Consider '::C create object method bar x {return $x}' instead of '::C object method bar x {return $x}'} + ? {C public class object method bar {x} {return $x}} \ + "'object' not allowed to be modified by 'class'" +} + +# +# test dispatch without object +# +Test case dispatch-without-object { + + nx::Object create o { + # attribute defines a setter, we need a current object + :attribute {a v} + # the other methods don't require them as strong + :forward b ::o2 bar + :method foo {} {return [self]} + } + nx::Object create o2 { + :public method bar {} {return [self]} + } + + # dispatch methods without current object + ? ::o::a "Method ::o::a not dispatched on object; don't call aliased methods via namespace paths!" + ? ::o::b "::o2" + ? ::o::foo "No current object" +} \ No newline at end of file