Index: TODO =================================================================== diff -u -r38b96e36c03950b6d2617d8e0bb8477913172a89 -rcc94b154709f9bd3393fbbdb9af982a30b57dee0 --- TODO (.../TODO) (revision 38b96e36c03950b6d2617d8e0bb8477913172a89) +++ TODO (.../TODO) (revision cc94b154709f9bd3393fbbdb9af982a30b57dee0) @@ -2019,7 +2019,15 @@ (eg. Object create o; Object create o:x; o info children x" will return ::o::x) - extended regression test +- introduced a few forms of multiplicty + * 0..1 allow empty value + * 0..* list, can be empty (equivalent to 0..n) + * 1..* list, cannot be empty (equivalent to 1..n) +- deprecate multivalued in parameter specs in favor of multiplicty +- deprecate allowempty in parameter specs in favor of multiplicty +- adjust regression test + TODO: - "-returns" Index: generic/nsf.c =================================================================== diff -u -r38b96e36c03950b6d2617d8e0bb8477913172a89 -rcc94b154709f9bd3393fbbdb9af982a30b57dee0 --- generic/nsf.c (.../nsf.c) (revision 38b96e36c03950b6d2617d8e0bb8477913172a89) +++ generic/nsf.c (.../nsf.c) (revision cc94b154709f9bd3393fbbdb9af982a30b57dee0) @@ -6626,8 +6626,11 @@ if ((pPtr->flags & NSF_ARG_SUBST_DEFAULT)) { ParamDefsFormatOption(interp, nameStringObj, "substdefault", &colonWritten, &first); } - if ((pPtr->flags & NSF_ARG_ALLOW_EMPTY)) { - ParamDefsFormatOption(interp, nameStringObj, "allowempty", &colonWritten, &first); + if ((pPtr->flags & NSF_ARG_ALLOW_EMPTY) || (pPtr->flags & NSF_ARG_MULTIVALUED)) { + char option[10] = "...."; + option[0] = (pPtr->flags & NSF_ARG_ALLOW_EMPTY) ? '0' : '1'; + option[3] = (pPtr->flags & NSF_ARG_MULTIVALUED) ? '*' : '1'; + ParamDefsFormatOption(interp, nameStringObj, option, &colonWritten, &first); } if ((pPtr->flags & NSF_ARG_IS_CONVERTER)) { ParamDefsFormatOption(interp, nameStringObj, "convert", &colonWritten, &first); @@ -6638,8 +6641,6 @@ ParamDefsFormatOption(interp, nameStringObj, "method", &colonWritten, &first); } else if ((pPtr->flags & NSF_ARG_NOARG)) { ParamDefsFormatOption(interp, nameStringObj, "noarg", &colonWritten, &first); - } else if ((pPtr->flags & NSF_ARG_MULTIVALUED)) { - ParamDefsFormatOption(interp, nameStringObj, "multivalued", &colonWritten, &first); } innerListObj = Tcl_NewListObj(0, NULL); @@ -8259,8 +8260,19 @@ } static int -ParamOptionParse(Tcl_Interp *interp, CONST char *option, size_t length, int disallowedOptions, NsfParam *paramPtr) { - int result = TCL_OK; +ParamOptionParse(Tcl_Interp *interp, CONST char *argString, + size_t start, size_t length, + int disallowedOptions, NsfParam *paramPtr) { + int searchUntil, result = TCL_OK; + CONST char *dotdot, *option = argString + start; + char *firstComma = strchr(option, ','); + + if (firstComma == NULL) { + searchUntil = length; + } else { + searchUntil = firstComma - option; + } + /*fprintf(stderr, "ParamOptionParse name %s, option '%s' (%d) disallowed %.6x\n", paramPtr->name, option, length, disallowedOptions);*/ if (strncmp(option, "required", MAX(3,length)) == 0) { @@ -8270,14 +8282,40 @@ } else if (strncmp(option, "substdefault", 12) == 0) { paramPtr->flags |= NSF_ARG_SUBST_DEFAULT; } else if (strncmp(option, "allowempty", 10) == 0) { + fprintf(stderr, "******* allowempty is deprecated, use instead multiplicity 0..1\n"); paramPtr->flags |= NSF_ARG_ALLOW_EMPTY; } else if (strncmp(option, "convert", 7) == 0) { paramPtr->flags |= NSF_ARG_IS_CONVERTER; } else if (strncmp(option, "initcmd", 7) == 0) { paramPtr->flags |= NSF_ARG_INITCMD; } else if (strncmp(option, "method", 6) == 0) { paramPtr->flags |= NSF_ARG_METHOD; + } else if ((dotdot = strnstr(option, "..", searchUntil))) { + /* check lower bound */ + if (*option == '0') { + paramPtr->flags |= NSF_ARG_ALLOW_EMPTY; + } else if (*option != '1') { + return NsfVarErrMsg(interp, + "lower bound of multiplicty in ", argString, " not supported", + (char *) NULL); + } + /* check upper bound */ + option = dotdot + 2; + if (*option == '*' || *option == 'n') { + if ((paramPtr->flags & (NSF_ARG_INITCMD|NSF_ARG_RELATION|NSF_ARG_METHOD|NSF_ARG_SWITCH)) != 0) { + return NsfVarErrMsg(interp, + "option multivalued not allowed for \"initcmd\", \"method\", \"relation\" or \"switch\"\n", + (char *) NULL); + } + paramPtr->flags |= NSF_ARG_MULTIVALUED; + } else if (*option != '1') { + return NsfVarErrMsg(interp, + "upper bound of multiplicty in ", argString, " not supported", + (char *) NULL); + } + //fprintf(stderr, "%s set multivalued option %s\n", paramPtr->name, option); } else if (strncmp(option, "multivalued", 11) == 0) { + fprintf(stderr, "******* multivalued is deprecated, use instead multiplicity 1..*\n"); if ((paramPtr->flags & (NSF_ARG_INITCMD|NSF_ARG_RELATION|NSF_ARG_METHOD|NSF_ARG_SWITCH)) != 0) return NsfVarErrMsg(interp, "option multivalued not allowed for \"initcmd\", \"method\", \"relation\" or \"switch\"\n", @@ -8429,7 +8467,7 @@ if (argString[l] == ',') { /* skip space from end */ for (end = l; end>0 && isspace((int)argString[end-1]); end--); - result = ParamOptionParse(interp, argString+start, end-start, disallowedFlags, paramPtr); + result = ParamOptionParse(interp, argString, start, end-start, disallowedFlags, paramPtr); if (result != TCL_OK) { goto param_error; } @@ -8441,7 +8479,7 @@ /* skip space from end */ for (end = l; end>0 && isspace((int)argString[end-1]); end--); /* process last option */ - result = ParamOptionParse(interp, argString+start, end-start, disallowedFlags, paramPtr); + result = ParamOptionParse(interp, argString, start, end-start, disallowedFlags, paramPtr); if (result != TCL_OK) { goto param_error; } @@ -11734,6 +11772,11 @@ return result; } + if (objc == 0 && ((pPtr->flags & NSF_ARG_ALLOW_EMPTY) == 0)) { + return NsfVarErrMsg(interp, "invalid parameter value: list is not allowed to be empty", + (char *) NULL); + } + /* * Default assumption: outObjPtr is not modified, in cases where * necessary, we switch to the helper function Index: library/lib/doc-tools.tcl =================================================================== diff -u -r239888ee1ee6ed6d7b9afa5e170f49c5224a186d -rcc94b154709f9bd3393fbbdb9af982a30b57dee0 --- library/lib/doc-tools.tcl (.../doc-tools.tcl) (revision 239888ee1ee6ed6d7b9afa5e170f49c5224a186d) +++ library/lib/doc-tools.tcl (.../doc-tools.tcl) (revision cc94b154709f9bd3393fbbdb9af982a30b57dee0) @@ -517,7 +517,7 @@ next [list [list @doc:optional __initcmd:initcmd,optional]] } - :class-object attribute current_project:object,type=::nx::doc::@project,allowempty + :class-object attribute current_project:object,type=::nx::doc::@project,0..1 :public forward current_project [current] %method :attribute partof:object,type=::nx::doc::StructuredEntity Index: library/nx/nx.tcl =================================================================== diff -u -rcd96543a3a6fb25ec8e52548892d12d244208a1a -rcc94b154709f9bd3393fbbdb9af982a30b57dee0 --- library/nx/nx.tcl (.../nx.tcl) (revision cd96543a3a6fb25ec8e52548892d12d244208a1a) +++ library/nx/nx.tcl (.../nx.tcl) (revision cc94b154709f9bd3393fbbdb9af982a30b57dee0) @@ -145,7 +145,7 @@ # define method "method" for Class and Object ::nsf::method Class method { - name arguments:parameter,multivalued -returns body -precondition -postcondition + name arguments:parameter,0..* -returns body -precondition -postcondition } { set conditions [list] if {[info exists precondition]} {lappend conditions -precondition $precondition} @@ -162,7 +162,7 @@ } ::nsf::method Object method { - name arguments:parameter,multivalued -returns body -precondition -postcondition + name arguments:parameter,0..* -returns body -precondition -postcondition } { set conditions [list] if {[info exists precondition]} {lappend conditions -precondition $precondition} @@ -637,6 +637,9 @@ lappend opts -required 0 } elseif {$property eq "method"} { lappend opts -ismethod 1 -nosetter 1 + } elseif {[regexp {([01])[.][.]([1n*])} $property _ lower upper]} { + if {$lower eq "0"} {lappend opts -allowempty 1} + if {$upper ne "1"} {lappend opts -multivalued 1} } else { set type $property } @@ -848,19 +851,35 @@ } # TODO: remove multivalued check on relations by handling multivalued # not in relation, but in the converters + set objUpper 1 + set methodUpper 1 + set objLower 1 + set methodLower 1 if {[info exists :multivalued] && ${:multivalued}} { if {!([info exists :type] && ${:type} eq "relation")} { - lappend objopts multivalued + #lappend objopts multivalued + set objUpper * } else { #puts stderr "ignore multivalued for $name in relation" } } + if {[info exists :allowempty]} { + set objLower 0 + set methodLower 0 + } + if {$objLower != 1 || $objUpper != 1} { + lappend objopts "$objLower..$objUpper" + } + if {$methodLower != 1 || $methodUpper != 1} { + lappend methodopts "$methodLower..$methodUpper" + } + if {[info exists :arg]} { set prefix [expr {$type eq "object" || $type eq "class" ? "type" : "arg"}] lappend objopts $prefix=${:arg} lappend methodopts $prefix=${:arg} } - foreach att {convert allowempty} { + foreach att {convert} { if {[info exists :$att]} { lappend objopts $att lappend methodopts $att @@ -1148,7 +1167,7 @@ # set variable "body" to minimize problems with spacing, since # the body is literally compared by the slot optimizer. set body {::nsf::setvar $obj $var $value} - :public method assign [list obj var value:$(mparam),multivalued,slot=[::nsf::self]] \ + :public method assign [list obj var value:$(mparam),1..*,slot=[::nsf::self]] \ $body #puts stderr "adding add method for [::nsf::self] with value:$(mparam)" Index: library/xotcl/library/xotcl2.tcl =================================================================== diff -u -rcd96543a3a6fb25ec8e52548892d12d244208a1a -rcc94b154709f9bd3393fbbdb9af982a30b57dee0 --- library/xotcl/library/xotcl2.tcl (.../xotcl2.tcl) (revision cd96543a3a6fb25ec8e52548892d12d244208a1a) +++ library/xotcl/library/xotcl2.tcl (.../xotcl2.tcl) (revision cc94b154709f9bd3393fbbdb9af982a30b57dee0) @@ -242,7 +242,7 @@ # define instproc and proc ::nsf::method Class instproc { - name arguments:parameter,multivalued body precondition:optional postcondition:optional + name arguments:parameter,0..* body precondition:optional postcondition:optional } { set conditions [list] if {[info exists precondition]} {lappend conditions -precondition $precondition} @@ -260,10 +260,10 @@ } # define a minimal implementation of "method" - Object instproc method {name arguments:parameter,multivalued body} { + Object instproc method {name arguments:parameter,0..* body} { :proc $name $arguments $body } - Class instproc method {-per-object:switch name arguments:parameter,multivalued body} { + Class instproc method {-per-object:switch name arguments:parameter,0..* body} { if {${per-object}} { :proc $name $arguments $body } else { Index: library/xotcl/tests/slottest.xotcl =================================================================== diff -u -rcd96543a3a6fb25ec8e52548892d12d244208a1a -rcc94b154709f9bd3393fbbdb9af982a30b57dee0 --- library/xotcl/tests/slottest.xotcl (.../slottest.xotcl) (revision cd96543a3a6fb25ec8e52548892d12d244208a1a) +++ library/xotcl/tests/slottest.xotcl (.../slottest.xotcl) (revision cc94b154709f9bd3393fbbdb9af982a30b57dee0) @@ -359,7 +359,7 @@ Class Person -slots { Attribute create name Attribute create age -default 0 - Attribute create projects -default {} -multivalued true -incremental true + Attribute create projects -default {} -multivalued true -incremental true -allowempty true } Person p1 -name "Gustaf" @@ -403,7 +403,7 @@ } Person slots { - Attribute create projects -default "" -multivalued true -incremental true -type ::Project + Attribute create projects -default "" -multivalued true -incremental true -type ::Project -allowempty true Attribute create salary -type integer } Index: library/xotcl/tests/speedtest.xotcl =================================================================== diff -u -r38b96e36c03950b6d2617d8e0bb8477913172a89 -rcc94b154709f9bd3393fbbdb9af982a30b57dee0 --- library/xotcl/tests/speedtest.xotcl (.../speedtest.xotcl) (revision 38b96e36c03950b6d2617d8e0bb8477913172a89) +++ library/xotcl/tests/speedtest.xotcl (.../speedtest.xotcl) (revision cc94b154709f9bd3393fbbdb9af982a30b57dee0) @@ -301,6 +301,7 @@ Test new -cmd {llength [c info children]} -expected $ccount Test new -cmd {c info children ::c::5} -expected ::c::5 Test new -cmd {c info children 5} -expected ::c::5 +Test new -cmd {c info children 5*} -expected ::c::5 Test new -count 1 -cmd {llength [Object info instances]} -expected $ocount Index: tests/info-method.test =================================================================== diff -u -rcd96543a3a6fb25ec8e52548892d12d244208a1a -rcc94b154709f9bd3393fbbdb9af982a30b57dee0 --- tests/info-method.test (.../info-method.test) (revision cd96543a3a6fb25ec8e52548892d12d244208a1a) +++ tests/info-method.test (.../info-method.test) (revision cc94b154709f9bd3393fbbdb9af982a30b57dee0) @@ -49,7 +49,7 @@ {::C public method m-with-assertions {} {return proc-[self proc]} -precondition 1 -postcondition 2} ? {C info method parameter m} {x} ? {nx::Class info method parameter method} \ - {name arguments:parameter,multivalued -returns body -precondition -postcondition} + {name arguments:parameter,0..* -returns body -precondition -postcondition} ? {nx::Object info method parameter alias} \ {methodName -returns {-frame default} cmd} # raises currently an error Index: tests/parameters.test =================================================================== diff -u -rcd96543a3a6fb25ec8e52548892d12d244208a1a -rcc94b154709f9bd3393fbbdb9af982a30b57dee0 --- tests/parameters.test (.../parameters.test) (revision cd96543a3a6fb25ec8e52548892d12d244208a1a) +++ tests/parameters.test (.../parameters.test) (revision cc94b154709f9bd3393fbbdb9af982a30b57dee0) @@ -99,10 +99,10 @@ ? {::nsf::is -complain class o1} {expected class but got "o1" for parameter "value"} ? {::nsf::is class o1} 0 ? {::nsf::is -complain class Test} 1 - ? {::nsf::is -complain object,multivalued [list o1 Test]} 1 + ? {::nsf::is -complain object,1..* [list o1 Test]} 1 - ? {::nsf::is -complain integer,multivalued [list 1 2 3]} 1 - ? {::nsf::is -complain integer,multivalued [list 1 2 3 a]} \ + ? {::nsf::is -complain integer,1..* [list 1 2 3]} 1 + ? {::nsf::is -complain integer,1..* [list 1 2 3 a]} \ {invalid value in "1 2 3 a": expected integer but got "a" for parameter "value"} ? {::nsf::is -complain object,type=::C c1} 1 ? {::nsf::is -complain object,type=::C o} \ @@ -413,7 +413,7 @@ D create d1 Object create o - D public method foo {m:integer,multivalued} { + D public method foo {m:integer,0..n} { return $m } ? {d1 foo ""} "" "emtpy list" @@ -423,7 +423,7 @@ {invalid value in "1 a 2": expected integer but got "a" for parameter "m"} \ "multiple values with wrong value" - D public method foo {m:object,multivalued} { + D public method foo {m:object,0..n} { return $m } ? {d1 foo ""} "" "emtpy list" @@ -433,7 +433,7 @@ "multiple values" Class create Foo -attributes { - {ints:integer,multivalued} + {ints:integer,1..*} } ? {Foo create foo -ints {1 2}} "::foo" ? {Foo create foo -ints {1 a 2}} {invalid value in "1 a 2": expected integer but got "a" for parameter "-ints"} @@ -562,7 +562,7 @@ "query instparams with default, no paramdefs needed" ? {Class info method parameter method} \ - "name arguments:parameter,multivalued -returns body -precondition -postcondition" \ + "name arguments:parameter,0..* -returns body -precondition -postcondition" \ "query instparams for scripted method 'method'" ? {Object info method parameter ::nsf::forward} \ @@ -746,7 +746,7 @@ :public method baz {{x:integer,substdefault ${:y}}} { return $x } - :public method boz {{x:integer,multivalued,substdefault ${:z}}} { + :public method boz {{x:integer,0..n,substdefault ${:z}}} { return $x } } @@ -838,8 +838,8 @@ m:metaclass b:baseclass u:upper - us:upper,multivalued - {x:object,multivalued {o}} + us:upper,1..* + {x:object,1..* {o}} } # TODO: we have no good interface for querying the slot notation for parameters @@ -855,9 +855,9 @@ ? {::parameterFromSlot ParamTest d} "d:object,type=::C,slot=::ParamTest::slot::d" ? {::parameterFromSlot ParamTest d1} "d1:object,type=::C,slot=::ParamTest::slot::d1" #? {::parameterFromSlot ParamTest mix} "mix:hasmixin,arg=M,slot=::ParamTest::slot::mix" - ? {::parameterFromSlot ParamTest x} "x:object,multivalued,slot=::ParamTest::slot::x o" + ? {::parameterFromSlot ParamTest x} "x:object,1..*,slot=::ParamTest::slot::x o" ? {::parameterFromSlot ParamTest u} "u:upper,slot=::ParamTest::slot::u" - ? {::parameterFromSlot ParamTest us} "us:upper,multivalued,slot=::ParamTest::slot::us" + ? {::parameterFromSlot ParamTest us} "us:upper,1..*,slot=::ParamTest::slot::us" ? {ParamTest create p -o o} ::p ? {ParamTest create p -o xxx} \ @@ -941,8 +941,8 @@ } } Class create C { - :public method foo {s:sex,multivalued,convert} {return $s} - :public method bar {s:sex,multivalued} {return $s} + :public method foo {s:sex,0..*,convert} {return $s} + :public method bar {s:sex,0..*} {return $s} } C create c1 ? {c1 foo {male female mann frau}} "m f m f" @@ -956,11 +956,11 @@ # Note that this converter does NOT return a value; it converts all # values into emtpy strings. } - ? {::nsf::is -complain mType,slot=::tmpObj,multivalued {1 0}} \ + ? {::nsf::is -complain mType,slot=::tmpObj,0..* {1 0}} \ {invalid value in "1 0": expected false but got 1} \ "fail on first value" - ? {::nsf::is -complain mType,slot=::tmpObj,multivalued {0 0 0}} 1 "all pass" - ? {::nsf::is -complain mType,slot=::tmpObj,multivalued {0 1}} \ + ? {::nsf::is -complain mType,slot=::tmpObj,0..* {0 0 0}} 1 "all pass" + ? {::nsf::is -complain mType,slot=::tmpObj,0..* {0 1}} \ {invalid value in "0 1": expected false but got 1} \ "fail o last value" } @@ -995,23 +995,24 @@ Object create o3 Object create o { - :public method foo {x:integer,allowempty y:integer os:object,multivalued,allowempty} { + :public method foo {x:integer,0..1 y:integer os:object,0..*} { return $x } } ? {o foo 1 2 {o1 o2}} 1 "all values specified" ? {o foo "" 2 {o1 o2}} "" "first is empty" ? {o foo 1 "" {o1 o2}} {expected integer but got "" for parameter "y"} "second is empty" - ? {o foo 1 2 {}} 1 "empty list, does not require allowempty" + ? {o foo 1 2 {}} 1 "empty list" ? {o foo 1 2 {o1 "" o2}} 1 "list contains empty value" - ? {o info method parameter foo} "x:integer,allowempty y:integer os:object,multivalued,allowempty" + ? {o info method parameter foo} "x:integer,0..1 y:integer os:object,0..*" - o public method foo {x:integer,allowempty y:integer os:object,multivalued} {return $x} + o public method foo {x:integer,0..1 y:integer os:object,1..*} {return $x} ? {o foo 1 2 {o1 "" o2}} {invalid value in "o1 "" o2": expected object but got "" for parameter "os"} \ "list contains empty value" - + ? {o foo "" 2 {}} {invalid parameter value: list is not allowed to be empty} \ + "empty int, empty list of objects" } ####################################################### # slot specific converter @@ -1057,12 +1058,12 @@ ? {o a 1} "1" ? {::nsf::setter o a:integer} "::o::a" - ? {::nsf::setter o ints:integer,multivalued} "::o::ints" + ? {::nsf::setter o ints:integer,1..*} "::o::ints" ? {::nsf::setter o o:object} "::o::o" ? {o info method handle ints} "::o::ints" - ? {o info method definition ints} "::o public setter ints:integer,multivalued" - ? {o info method parameter ints} "ints:integer,multivalued" + ? {o info method definition ints} "::o public setter ints:integer,1..*" + ? {o info method parameter ints} "ints:integer,1..*" ? {o info method args ints} "ints" ? {o info method handle o} "::o::o" @@ -1124,7 +1125,7 @@ :public method noarg {} {return ""} :public method onearg {x} {return $x} :public method intarg {x:integer} {return $x} - :public method intsarg {x:integer,multivalued} {return $x} + :public method intsarg {x:integer,1..*} {return $x} :public method boolarg {x:boolean} {return $x} :public method classarg {x:class} {return $x} :public method upperarg {x:upper} {return $x} @@ -1200,7 +1201,7 @@ :public method noarg {} {return ""} :public method onearg {x} {return $x} :public method intarg {x:integer} {return $x} - :public method intsarg {x:integer,multivalued} {return $x} + :public method intsarg {x:integer,1..*} {return $x} :public method boolarg {x:boolean} {return $x} :public method classarg {x:class} {return $x} :public method upperarg {x:upper} {return $x} @@ -1302,7 +1303,7 @@ :method noarg {} {return ""} :method onearg {-x} {return $x} :method intarg {-x:integer} {return $x} - :method intsarg {-x:integer,multivalued} {return $x} + :method intsarg {-x:integer,1..*} {return $x} :method boolarg {-x:boolean} {return $x} :method classarg {-x:class} {return $x} :method upperarg {-x:upper} {return $x}