Index: TODO =================================================================== diff -u -rb2edd7ca322d0135e310c1ee1ce0cc1b39e7c86d -rdd2352511413900de40068dafb06731b23e14891 --- TODO (.../TODO) (revision b2edd7ca322d0135e310c1ee1ce0cc1b39e7c86d) +++ TODO (.../TODO) (revision dd2352511413900de40068dafb06731b23e14891) @@ -3982,6 +3982,17 @@ KEEP_CALLER_SELF and no NSF_PER_OBJECT_DISPATCH - extend regression test +nsf.c: +- implement escaping for comma in value of parameter options: + escaping in values can be achived via duplicating the comma. + In the following example is the value for arg "1,3" + D public method foo {a:commaRange,arg=1,,3,optional} {..} + Escaping via \ whould actually require 4 backslashes + due to Tcl's escaping rules (two, to get a single backslash, + another two due to list-splitting to obtain default from arg). +- extend regression test + + ======================================================================== TODO: Index: generic/nsf.c =================================================================== diff -u -rb2edd7ca322d0135e310c1ee1ce0cc1b39e7c86d -rdd2352511413900de40068dafb06731b23e14891 --- generic/nsf.c (.../nsf.c) (revision b2edd7ca322d0135e310c1ee1ce0cc1b39e7c86d) +++ generic/nsf.c (.../nsf.c) (revision dd2352511413900de40068dafb06731b23e14891) @@ -11674,8 +11674,41 @@ return TCL_OK; } + /* *---------------------------------------------------------------------- + * Unescape -- + * + * Unescape double commas in the provided Tcl_Obj. + * + * Results: + * None + * + * Side effects: + * Potentially shortend string content + * + *---------------------------------------------------------------------- + */ + +static void +Unescape(Tcl_Obj *objPtr) { + int i, j, l = Tcl_GetCharLength(objPtr); + char *string = ObjStr(objPtr); + + for (i = 0; i < l; i++) { + if (string[i] == ',' && string[i+1] == ',') { + for (j = i+1; j < l; j++) { + string[j] = string[j+1]; + } + l--; + i++; + } + } + Tcl_SetObjLength(objPtr, l); +} + +/* + *---------------------------------------------------------------------- * ParamOptionParse -- * * Parse a single parameter option of a parameter. The parameter option @@ -11695,12 +11728,12 @@ static int ParamOptionParse(Tcl_Interp *interp, CONST char *argString, size_t start, size_t optionLength, - int disallowedOptions, Nsf_Param *paramPtr) { + int disallowedOptions, Nsf_Param *paramPtr, int unescape) { CONST char *dotdot, *option = argString + start; int result = TCL_OK; - /*fprintf(stderr, "ParamOptionParse name %s, option '%s' (%ld) disallowed %.6x\n", - paramPtr->name, option, remainder, disallowedOptions);*/ + /* fprintf(stderr, "ParamOptionParse name %s, option '%s' (%ld) disallowed %.6x\n", + paramPtr->name, option, start, disallowedOptions);*/ if (strncmp(option, "required", MAX(3, optionLength)) == 0) { paramPtr->flags |= NSF_ARG_REQUIRED; @@ -11781,12 +11814,17 @@ } else if (optionLength >= 4 && strncmp(option, "arg=", 4) == 0) { if (paramPtr->converter != ConvertViaCmd) { - fprintf(stderr, "type %s flags %.6x\n", paramPtr->type, paramPtr->flags); return NsfPrintError(interp, "parameter option 'arg=' only allowed for user-defined converter"); } if (paramPtr->converterArg) {DECR_REF_COUNT(paramPtr->converterArg);} paramPtr->converterArg = Tcl_NewStringObj(option + 4, optionLength - 4); + /* + * In case, we know that we have to unescape double commas, do it here... + */ + if (unlikely(unescape)) { + Unescape(paramPtr->converterArg); + } INCR_REF_COUNT(paramPtr->converterArg); } else if (strncmp(option, "switch", 6) == 0) { @@ -11843,11 +11881,17 @@ return NsfPrintError(interp, "parameter option 'type=' only allowed for parameter types 'object' and 'class'"); if (paramPtr->converterArg) {DECR_REF_COUNT(paramPtr->converterArg);} paramPtr->converterArg = Tcl_NewStringObj(option + 5, optionLength - 5); + if (unlikely(unescape)) { + Unescape(paramPtr->converterArg); + } INCR_REF_COUNT(paramPtr->converterArg); } else if (optionLength >= 6 && strncmp(option, "slot=", 5) == 0) { if (paramPtr->slotObj) {DECR_REF_COUNT(paramPtr->slotObj);} paramPtr->slotObj = Tcl_NewStringObj(option + 5, optionLength - 5); + if (unlikely(unescape)) { + Unescape(paramPtr->slotObj); + } INCR_REF_COUNT(paramPtr->slotObj); } else if (optionLength >= 6 && strncmp(option, "method=", 7) == 0) { @@ -11856,10 +11900,18 @@ } if (paramPtr->method) {DECR_REF_COUNT(paramPtr->method);} paramPtr->method = Tcl_NewStringObj(option + 7, optionLength - 7); + if (unlikely(unescape)) { + Unescape(paramPtr->method); + } INCR_REF_COUNT(paramPtr->method); } else { Tcl_DString ds, *dsPtr = &ds; + + if (option[0] == '\0') { + NsfLog(interp, NSF_LOG_WARN, "empty parameter option ignored"); + return TCL_OK; + } Tcl_DStringInit(dsPtr); Tcl_DStringAppend(dsPtr, option, optionLength); @@ -11905,7 +11957,7 @@ paramPtr->converterArg = Tcl_NewStringObj(stringTypeOpts[i], -1); INCR_REF_COUNT(paramPtr->converterArg); } else { - + /* * The parameter option is still unknown. We assume that the parameter * option identifies a user-defined argument checker, implemented as a @@ -11989,7 +12041,8 @@ paramPtr->flags |= NSF_ARG_REQUIRED; /* positional arguments are required unless we have a default */ } - /* fprintf(stderr, "... parsing '%s', name '%s' \n", ObjStr(arg), argName);*/ + /*fprintf(stderr, "... parsing '%s', name '%s' argString '%s' \n", + ObjStr(arg), argName, argString);*/ /* find the first ':' */ for (j=0; jname, argString, j); @@ -12008,25 +12062,43 @@ /* skip space at begin */ for (start = j+1; start0 && isspace((int)argString[end-1]); end--); - result = ParamOptionParse(interp, argString, start, end-start, disallowedFlags, paramPtr); - if (unlikely(result != TCL_OK)) { - goto param_error; - } - l++; - /* skip space from begin */ - for (start = l; start0 && isspace((int)argString[end-1]); end--); + result = ParamOptionParse(interp, argString, start, end-start, disallowedFlags, paramPtr, unescape); + unescape = 0; + if (unlikely(result != TCL_OK)) { + goto param_error; + } + l++; + /* skip space from begin */ + for (start = l; start0 && isspace((int)argString[end-1]); end--); /* process last option */ if (end-start > 0) { - result = ParamOptionParse(interp, argString, start, end-start, disallowedFlags, paramPtr); + result = ParamOptionParse(interp, argString, start, end-start, disallowedFlags, paramPtr, unescape); if (unlikely(result != TCL_OK)) { goto param_error; } @@ -21748,6 +21820,7 @@ * definition is varArgs. */ if (i < paramDefs->nrParams || !pc.varArgs) { + #if defined(CONFIGURE_ARGS_TRACE) fprintf(stderr, "*** %s SET %s '%s' // %p\n", ObjectName(object), ObjStr(paramPtr->nameObj), ObjStr(newValue), paramPtr->slotObj); Index: tests/parameters.test =================================================================== diff -u -r537b7cd99b6bc0a28b0f73c2691e08b8bd319147 -rdd2352511413900de40068dafb06731b23e14891 --- tests/parameters.test (.../parameters.test) (revision 537b7cd99b6bc0a28b0f73c2691e08b8bd319147) +++ tests/parameters.test (.../parameters.test) (revision dd2352511413900de40068dafb06731b23e14891) @@ -630,14 +630,15 @@ } ####################################################### -# user defined parameter types +# user defined parameter value checkers ####################################################### -nx::Test case user-types { +nx::Test case user-value-checker { nx::Class create D {:property d} D create d1 - # create a userdefined type + # Create a user-defined value checker for method parameters, + # without extra argument ::nx::methodParameterSlot method type=mytype {name value} { if {$value < 1 || $value > 3} { error "value '$value' of parameter $name is not between 1 and 3" @@ -661,14 +662,21 @@ "::nx::methodParameterSlot: unable to dispatch method 'type=unknowntype'" \ "missing type checker" - # create a userdefined type with a simple argument + # + # Create a user-defined value-checker for method parameters, + # with a extra argument + # ::nx::methodParameterSlot method type=in {name value arg} { if {$value ni [split $arg |]} { error "value '$value' of parameter $name not in permissible values $arg" } return $value } - + + # + # Trival test case + # + D public method foo {a:in,arg=a|b|c} { return a=$a } @@ -677,6 +685,10 @@ ? {d1 foo 10} \ "value '10' of parameter a not in permissible values a|b|c" \ "invalid value" + + # + # Test case with positional and non-positional arguments, and default + # D public method foo {a:in,arg=a|b|c b:in,arg=good|bad {-c:in,arg=a|b a}} { return a=$a,b=$b,c=$c @@ -687,9 +699,45 @@ ? {d1 foo b "very good"} \ "value 'very good' of parameter b not in permissible values good|bad" \ "invalid value (not included)" + + # + # Create a user-defined value checker for method parameters, + # without extra argument + # + ::nx::methodParameterSlot method type=commaRange {name value arg} { + lassign [split $arg ,] min max + if {$value < $min || $value > $max} { + error "value '$value' of parameter $name not between $min and $max" + } + return $value + } + + D public method foo {a:commaRange,arg=1,,3} { + return a=$a + } + ? {d1 foo 2} "a=2" + ? {d1 foo 10} \ + "value '10' of parameter a not between 1 and 3" \ + "invalid value" + + # + # two commas at the end + # + D public method foo {a:commaRange,arg=1,,} {return a=$a} + ? {d1 foo 2} {value '2' of parameter a not between 1 and } + + # + # one comma at the end + # + D public method foo {a:commaRange,arg=1,} {return a=$a} + ? {d1 foo 2} {value '2' of parameter a not between 1 and } + + # + # Classical range check + # ::nx::methodParameterSlot method type=range {name value arg} { - foreach {min max} [split $arg -] break + lassign [split $arg -] min max if {$value < $min || $value > $max} { error "value '$value' of parameter $name not between $min and $max" }