From 23178bb2087e384cb320634e4974b3dc6104eb89 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 13 Mar 2018 03:15:28 +0000 Subject: Tidy up and comment [string replace] and its corner cases. --- generic/tclCmdMZ.c | 29 +++++++++++++++++++++-------- tests/string.test | 6 ++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/generic/tclCmdMZ.c b/generic/tclCmdMZ.c index 30586b1..706d71c 100644 --- a/generic/tclCmdMZ.c +++ b/generic/tclCmdMZ.c @@ -2262,7 +2262,7 @@ StringRplcCmd( Tcl_Obj *const objv[]) /* Argument objects. */ { Tcl_UniChar *ustring; - int first, last, length; + int first, last, length, end; if (objc < 4 || objc > 5) { Tcl_WrongNumArgs(interp, 1, objv, "string first last ?string?"); @@ -2270,20 +2270,33 @@ StringRplcCmd( } ustring = Tcl_GetUnicodeFromObj(objv[1], &length); - length--; + end = length - 1; - if (TclGetIntForIndexM(interp, objv[2], length, &first) != TCL_OK || - TclGetIntForIndexM(interp, objv[3], length, &last) != TCL_OK){ + if (TclGetIntForIndexM(interp, objv[2], end, &first) != TCL_OK || + TclGetIntForIndexM(interp, objv[3], end, &last) != TCL_OK){ return TCL_ERROR; } - if ((last < first) || (last < 0) || (first > length)) { + /* + * The following test screens out most empty substrings as + * candidates for replacement. When they are detected, no + * replacement is done, and the result is the original string, + */ + if ((last < 0) || /* Range ends before start of string */ + (first > end) || /* Range begins after end of string */ + (last < first)) { /* Range begins after it starts */ + + /* + * BUT!!! when (end < 0) -- an empty original string -- we can + * have (first <= end < 0 <= last) and an empty string is permitted + * to be replaced. + */ Tcl_SetObjResult(interp, objv[1]); } else { Tcl_Obj *resultPtr; ustring = Tcl_GetUnicodeFromObj(objv[1], &length); - length--; + end = length-1; if (first < 0) { first = 0; @@ -2293,9 +2306,9 @@ StringRplcCmd( if (objc == 5) { Tcl_AppendObjToObj(resultPtr, objv[4]); } - if (last < length) { + if (last < end) { Tcl_AppendUnicodeToObj(resultPtr, ustring + last + 1, - length - last); + end - last); } Tcl_SetObjResult(interp, resultPtr); } diff --git a/tests/string.test b/tests/string.test index 39abd86..f3bb366 100644 --- a/tests/string.test +++ b/tests/string.test @@ -1294,6 +1294,12 @@ test string-14.16 {string replace} { test string-14.17 {string replace} { string replace abcdefghijklmnop end end-1 } {abcdefghijklmnop} +test string-14.18 {string replace} { + string replace abcdefghijklmnop 10 9 XXX +} {abcdefghijklmnop} +test string-14.19 {string replace} { + string replace {} -1 0 A +} A test string-15.1 {string tolower too few args} { list [catch {string tolower} msg] $msg -- cgit v0.12 From debafda1e72e4b9c63b64dce27e4948c7f448ebc Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 13 Mar 2018 04:01:03 +0000 Subject: Repair the INST_STR_REPLACE instruction. --- generic/tclExecute.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/generic/tclExecute.c b/generic/tclExecute.c index ef64ac1..2ef627d 100644 --- a/generic/tclExecute.c +++ b/generic/tclExecute.c @@ -5701,18 +5701,18 @@ TEBCresume( { Tcl_UniChar *ustring1, *ustring2, *ustring3, *end, *p; - int length3; + int length3, endIdx; Tcl_Obj *value3Ptr; case INST_STR_REPLACE: value3Ptr = POP_OBJECT(); valuePtr = OBJ_AT_DEPTH(2); - length = Tcl_GetCharLength(valuePtr) - 1; + endIdx = Tcl_GetCharLength(valuePtr) - 1; TRACE(("\"%.20s\" %s %s \"%.20s\" => ", O2S(valuePtr), O2S(OBJ_UNDER_TOS), O2S(OBJ_AT_TOS), O2S(value3Ptr))); - if (TclGetIntForIndexM(interp, OBJ_UNDER_TOS, length, + if (TclGetIntForIndexM(interp, OBJ_UNDER_TOS, endIdx, &fromIdx) != TCL_OK - || TclGetIntForIndexM(interp, OBJ_AT_TOS, length, + || TclGetIntForIndexM(interp, OBJ_AT_TOS, endIdx, &toIdx) != TCL_OK) { TclDecrRefCount(value3Ptr); TRACE_ERROR(interp); @@ -5722,21 +5722,24 @@ TEBCresume( (void) POP_OBJECT(); TclDecrRefCount(OBJ_AT_TOS); (void) POP_OBJECT(); - if (fromIdx < 0) { - fromIdx = 0; - } - if (fromIdx > toIdx || fromIdx > length) { + if ((toIdx < 0) || + (fromIdx > endIdx) || + (toIdx < fromIdx)) { TRACE_APPEND(("\"%.30s\"\n", O2S(valuePtr))); TclDecrRefCount(value3Ptr); NEXT_INST_F(1, 0, 0); } - if (toIdx > length) { - toIdx = length; + if (fromIdx < 0) { + fromIdx = 0; + } + + if (toIdx > endIdx) { + toIdx = endIdx; } - if (fromIdx == 0 && toIdx == length) { + if (fromIdx == 0 && toIdx == endIdx) { TclDecrRefCount(OBJ_AT_TOS); OBJ_AT_TOS = value3Ptr; TRACE_APPEND(("\"%.30s\"\n", O2S(value3Ptr))); -- cgit v0.12 From 962427e8cd982fd685ca30f221083da2e7b62cea Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 13 Mar 2018 14:53:16 +0000 Subject: tip490 oo for msgcat: added documentation --- changes | 2 +- doc/msgcat.n | 107 +++++++++++++++++++++++++++++++++++++++++++--- library/msgcat/msgcat.tcl | 4 +- 3 files changed, 103 insertions(+), 10 deletions(-) diff --git a/changes b/changes index feed968..01d4c7a 100644 --- a/changes +++ b/changes @@ -8882,5 +8882,5 @@ in this changeset (new minor version) rather than bug fixes: 2018-03-12 (TIP 490) add oo support for msgcat => msgcat 1.7.0 (oehlmann) -2018-03-12 (TIP 499) custom locale preference list (nijtmans) +2018-03-12 (TIP 499) custom locale preference list (oehlmann) => msgcat 1.7.0 diff --git a/doc/msgcat.n b/doc/msgcat.n index 2fc1eee..fbea20f 100644 --- a/doc/msgcat.n +++ b/doc/msgcat.n @@ -11,9 +11,9 @@ .SH NAME msgcat \- Tcl message catalog .SH SYNOPSIS -\fBpackage require Tcl 8.5\fR +\fBpackage require Tcl 8.7\fR .sp -\fBpackage require msgcat 1.6\fR +\fBpackage require msgcat 1.7\fR .sp \fB::msgcat::mc \fIsrc-string\fR ?\fIarg arg ...\fR? .sp @@ -23,6 +23,10 @@ msgcat \- Tcl message catalog \fB::msgcat::mcexists\fR ?\fB-exactnamespace\fR? ?\fB-exactlocale\fR? \fIsrc-string\fR .VE "TIP 412" .sp +.VS "TIP 490" +\fB::msgcat::mcpackagenamespaceget\fR +.VE "TIP 490" +.sp \fB::msgcat::mclocale \fR?\fInewLocale\fR? .sp \fB::msgcat::mcpreferences\fR @@ -71,6 +75,11 @@ In \fBmsgcat\fR, there is a global locale initialized by the system locale of th Each package may decide to use the global locale or to use a package specific locale. .PP The global locale may be changed on demand, for example by a user initiated language change or within a multi user application like a web server. +.PP +.VS tip490 +Object oriented programming is supported by the use of a package namespace. +.VE tip490 +.PP .SH COMMANDS .TP \fB::msgcat::mc \fIsrc-string\fR ?\fIarg arg ...\fR? @@ -95,6 +104,17 @@ use the result. If an application is written for a single language in this fashion, then it is easy to add support for additional languages later simply by defining new message catalog entries. .RE +.VS "TIP 490" +.TP +\fB::msgcat::mcn \fInamespace\fR \fIsrc-string\fR ?\fIarg arg ...\fR? +. +Like \fB::msgcat::mc\fR, but with the message namespace specified as first argument. +.PP +.RS +\fBmcn\fR may be used for cases where the package namespace is not the namespace of the caller. +An example is shown within the description of the command \fB::msgcat::mcpackagenamespaceget\fR below. +.RE +.PP .TP \fB::msgcat::mcmax ?\fIsrc-string src-string ...\fR? . @@ -103,17 +123,49 @@ of the longest translated string. This is useful when designing localized GUIs, which may require that all buttons, for example, be a fixed width (which will be the width of the widest button). .TP -\fB::msgcat::mcexists\fR ?\fB-exactnamespace\fR? ?\fB-exactlocale\fR? \fIsrc-string\fR -. .VS "TIP 412" +\fB::msgcat::mcexists\fR ?\fB-exactnamespace\fR? ?\fB-exactlocale\fR? ?\fB-namespace\fR \fInamespace\fR? \fIsrc-string\fR +. Return true, if there is a translation for the given \fIsrc-string\fR. .PP .RS The search may be limited by the option \fB\-exactnamespace\fR to only check the current namespace and not any parent namespaces. .PP It may also be limited by the option \fB\-exactlocale\fR to only check the first prefered locale (e.g. first element returned by \fB::msgcat::mcpreferences\fR if global locale is used). -.RE +.PP .VE "TIP 412" +.VS "TIP 490" +An explicit package namespace may be specified by the option \fB-namespace\fR. +The namespace of the caller is used if not explicitly specified. +.RE +.PP +.VE "TIP 490" +.VS "TIP 490" +.TP +\fB::msgcat::mcpackagenamespaceget\fR +. +Return the package namespace of the caller. +This command handles all cases described in section \fBObject oriented programming\fR. +.PP +.RS +Example usage is a tooltip package, which saves the caller package namespace to update the translation each time the tooltip is shown: +.CS +proc ::tooltip::tooltip {widget message} { + ... + set messagenamespace [uplevel 1 {::msgcat::mcpackagenamespaceget}] + ... + bind $widget [list ::tooltip::show $widget $messagenamespace $message] +} + +proc ::tooltip::show {widget messagenamespace message} { + ... + set message [::msgcat::mcn $messagenamespace $message] + ... +} +.CE +.RE +.PP +.VE "TIP 490" .TP \fB::msgcat::mclocale \fR?\fInewLocale\fR? . @@ -563,7 +615,7 @@ A generic unknown handler is used if set to the empty string. This consists in r See section \fBcallback invocation\fR below. The appended arguments are identical to \fB::msgcat::mcunknown\fR. .RE -.SS Callback invocation +.SH Callback invocation A package may decide to register one or multiple callbacks, as described above. .PP Callbacks are invoked, if: @@ -577,7 +629,48 @@ Callbacks are invoked, if: If a called routine fails with an error, the \fBbgerror\fR routine for the interpreter is invoked after command completion. Only exception is the callback \fBunknowncmd\fR, where an error causes the invoking \fBmc\fR-command to fail with that error. .PP -.SS Examples +.VS tip490 +.SH Object oriented programming +\fBmsgcat\fR supports packages implemented by object oriented programming. +Objects and classes should be defined within a package namespace. +.PP +There are 3 supported cases where package namespace sensitive commands of msgcat (\fBmc\fR, \fBmcexists\fR, \fBmcpackagelocale\fR, \fBmcforgetpackage\fR, \fBmcpackagenamespaceget\fR, \fBmcpackageconfig\fR, \fBmcset\fR and \fBmcmset\fR) may be called: +.PP +.SH 1) In class definition script +\fBmsgcat\fR command is called within a class definition script. +.CS +namespace eval ::N2 { + mcload $dir/msgs + oo::class create C1 {puts [mc Hi!]} +} +.CE +.PP +.SH 2) method defined in a class +\fBmsgcat\fR command is called from a method in an object and the method is defined in a class. +.CS +namespace eval ::N3Class { + mcload $dir/msgs + oo::class create C1 + oo::define C1 method m1 { + puts [mc Hi!] + } +} +.CE +.PP +.SH 3) method defined in a classless object +\fBmsgcat\fR command is called from a method of a classless object. +.CS +namespace eval ::N4 { + mcload $dir/msgs + oo::object create O1 + oo::objdefine O1 method m1 {} { + puts [mc Hi!] + } +} +.CE +.PP +.VE tip490 +.SH Examples Packages which display a GUI may update their widgets when the global locale changes. To register to a callback, use: .CS diff --git a/library/msgcat/msgcat.tcl b/library/msgcat/msgcat.tcl index 50a9064..100f425 100644 --- a/library/msgcat/msgcat.tcl +++ b/library/msgcat/msgcat.tcl @@ -4,7 +4,7 @@ # message catalog facility for Tcl programs. It should be # loaded with the command "package require msgcat". # -# Copyright (c) 2010-2015 by Harald Oehlmann. +# Copyright (c) 2010-2018 by Harald Oehlmann. # Copyright (c) 1998-2000 by Ajuba Solutions. # Copyright (c) 1998 by Mark Harrison. # @@ -1218,7 +1218,7 @@ proc msgcat::mcutil::ConvertLocale {value} { # - called from a classless oo object proc ::msgcat::PackageNamespaceGet {} { uplevel 2 { - # Check for no object + # Check self namespace to determine environment switch -exact -- [namespace which self] { {::oo::define::self} { # We are within a class definition -- cgit v0.12 From a275b8a5ea188f0633caab60b3ab709de4239f1f Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 13 Mar 2018 16:03:39 +0000 Subject: Rewrite the [string replace] compiler to take advantage of the richer set of encoded index values. --- generic/tclCompCmdsSZ.c | 241 +++++++++++++++++++++++++++++------------------- 1 file changed, 145 insertions(+), 96 deletions(-) diff --git a/generic/tclCompCmdsSZ.c b/generic/tclCompCmdsSZ.c index e84f382..c13376b 100644 --- a/generic/tclCompCmdsSZ.c +++ b/generic/tclCompCmdsSZ.c @@ -995,148 +995,197 @@ TclCompileStringReplaceCmd( * compiled. */ CompileEnv *envPtr) /* Holds the resulting instructions. */ { - Tcl_Token *tokenPtr, *valueTokenPtr, *replacementTokenPtr = NULL; + Tcl_Token *tokenPtr, *valueTokenPtr; DefineLineInformation; /* TIP #280 */ - int idx1, idx2; + int first, last; if (parsePtr->numWords < 4 || parsePtr->numWords > 5) { return TCL_ERROR; } + + /* Bytecode to compute/push string argument being replaced */ valueTokenPtr = TokenAfter(parsePtr->tokenPtr); - if (parsePtr->numWords == 5) { - tokenPtr = TokenAfter(valueTokenPtr); - tokenPtr = TokenAfter(tokenPtr); - replacementTokenPtr = TokenAfter(tokenPtr); - } - goto genericReplace; + CompileWord(envPtr, valueTokenPtr, interp, 1); + /* + * Check for first index known and useful at compile time. + */ tokenPtr = TokenAfter(valueTokenPtr); - if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_START, TCL_INDEX_AFTER, - &idx1) != TCL_OK) { + if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_AFTER, + &first) != TCL_OK) { goto genericReplace; } + /* - * Token parsed as an index value. Indices before the string are - * treated as index of start of string. + * Check for last index known and useful at compile time. */ - tokenPtr = TokenAfter(tokenPtr); - if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_END, - &idx2) != TCL_OK) { + if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_AFTER, + &last) != TCL_OK) { goto genericReplace; } - /* - * Token parsed as an index value. Indices after the string are - * treated as index of end of string. - */ -/* TODO...... */ - /* - * We handle these replacements specially: first character (where - * idx1=idx2=0) and last character (where idx1=idx2=TCL_INDEX_END). Anything - * else and the semantics get rather screwy. + /* + * [string replace] is an odd bird. For many arguments it is + * a conventional substring replacer. However it also goes out + * of its way to become a no-op for many cases where it would be + * replacing an empty substring. Precisely, it is a no-op when * - * TODO: These seem to be very narrow cases. They are not even - * covered by the test suite, and any programming that ends up - * here could have been coded by the programmer using [string range] - * and [string cat]. [*] Not clear at all to me that the bytecode - * generated here is worthwhile. + * (last < first) OR + * (last < 0) OR + * (end < first) * - * [*] Except for the empty string exceptions. UGGGGHHHH. + * For some compile-time values we can detect these cases, and + * compile direct to bytecode implementing the no-op. */ - if (idx1 == 0 && idx2 == 0) { - int notEq, end; + if ((last == TCL_INDEX_BEFORE) /* Know (last < 0) */ + || (first == TCL_INDEX_AFTER) /* Know (first > end) */ /* - * Just working with the first character. + * Tricky to determine when runtime (last < first) can be + * certainly known based on the encoded values. Consider the + * cases... + * + * (first <= TCL_INDEX_END) && + * (last == TCL_INDEX_AFTER) => cannot tell REJECT + * (last <= TCL_INDEX END) && (last < first) => ACCEPT + * else => cannot tell REJECT */ - - CompileWord(envPtr, valueTokenPtr, interp, 1); - if (replacementTokenPtr == NULL) { - /* Drop first */ - OP44( STR_RANGE_IMM, 1, TCL_INDEX_END); - return TCL_OK; - } - /* Replace first */ - CompileWord(envPtr, replacementTokenPtr, interp, 4); - + || ((first <= TCL_INDEX_END) && (last <= TCL_INDEX_END) + && (last < first)) /* Know (last < first) */ /* - * NOTE: The following tower of bullshit is present because - * [string replace] was boneheadedly defined not to replace - * empty strings, so we actually have to detect the empty - * string case and treat it differently. + * (first == TCL_INDEX_BEFORE) && + * (last == TCL_INDEX_AFTER) => (first < last) REJECT + * (last <= TCL_INDEX_END) => cannot tell REJECT + * else => (first < last) REJECT + * + * else [[first >= TCL_INDEX_START]] && + * (last == TCL_INDEX_AFTER) => cannot tell REJECT + * (last <= TCL_INDEX_END) => cannot tell REJECT + * else [[last >= TCL_INDEX START]] && (last < first) => ACCEPT */ - - OP4( OVER, 1); - PUSH( ""); - OP( STR_EQ); - JUMP1( JUMP_FALSE, notEq); - OP( POP); - JUMP1( JUMP, end); - FIXJUMP1(notEq); - TclAdjustStackDepth(1, envPtr); - OP4( REVERSE, 2); - OP44( STR_RANGE_IMM, 1, TCL_INDEX_END); - OP1( STR_CONCAT1, 2); - FIXJUMP1(end); + || ((first >= TCL_INDEX_START) && (last >= TCL_INDEX_START) + && (last < first))) { /* Know (last < first) */ + if (parsePtr->numWords == 5) { + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, 4); + OP( POP); /* Pop newString */ + } + /* Original string argument now on TOS as result */ return TCL_OK; + } - } else if (idx1 == TCL_INDEX_END && idx2 == TCL_INDEX_END) { - int notEq, end; - - /* - * Just working with the last character. - */ + if (parsePtr->numWords == 5) { + /* + * When we have a string replacement, we have to take care about + * not replacing empty substrings that [string replace] promises + * not to replace + * + * The remaining index values might be suitable for conventional + * string replacement, but only if they cannot possibly meet the + * conditions described above at runtime. If there's a chance they + * might, we would have to emit bytecode to check and at that point + * we're paying more in bytecode execution time than would make + * things worthwhile. Trouble is we are very limited in + * how much we can detect that at compile time. After decoding, + * we need, first: + * + * (first <= end) + * + * The encoded indices (first <= TCL_INDEX END) and + * (first == TCL_INDEX_BEFORE) always meets this condition, but + * any other encoded first index has some list for which it fails. + * + * We also need, second: + * + * (last >= 0) + * + * The encoded indices (last >= TCL_INDEX_START) and + * (last == TCL_INDEX_AFTER) always meet this condition but any + * other encoded last index has some list for which it fails. + * + * Finally we need, third: + * + * (first <= last) + * + * Considered in combination with the constraints we already have, + * we see that we can proceed when (first == TCL_INDEX_BEFORE) + * or (last == TCL_INDEX_AFTER). These also permit simplification + * of the prefix|replace|suffix construction. The other constraints, + * though, interfere with getting a guarantee that first <= last. + */ - CompileWord(envPtr, valueTokenPtr, interp, 1); - if (replacementTokenPtr == NULL) { - /* Drop last */ - OP44( STR_RANGE_IMM, 0, TCL_INDEX_END-1); - return TCL_OK; + if ((first == TCL_INDEX_BEFORE) && (last >= TCL_INDEX_START)) { + /* empty prefix */ + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, 4); + OP4( REVERSE, 2); + if (last == TCL_INDEX_AFTER) { + OP( POP); /* Pop original */ + } else { + OP44( STR_RANGE_IMM, last + 1, TCL_INDEX_END); + OP1( STR_CONCAT1, 2); } - /* Replace last */ - CompileWord(envPtr, replacementTokenPtr, interp, 4); - - /* More bullshit; see NOTE above. */ + return TCL_OK; + } - OP4( OVER, 1); - PUSH( ""); - OP( STR_EQ); - JUMP1( JUMP_FALSE, notEq); - OP( POP); - JUMP1( JUMP, end); - FIXJUMP1(notEq); - TclAdjustStackDepth(1, envPtr); - OP4( REVERSE, 2); - OP44( STR_RANGE_IMM, 0, TCL_INDEX_END-1); - OP4( REVERSE, 2); + if ((last == TCL_INDEX_AFTER) && (first <= TCL_INDEX_END)) { + OP44( STR_RANGE_IMM, 0, first-1); + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, 4); OP1( STR_CONCAT1, 2); - FIXJUMP1(end); return TCL_OK; + } + + /* FLOW THROUGH TO genericReplace */ } else { - /* - * Need to process indices at runtime. This could be because the - * indices are not constants, or because we need to resolve them to - * absolute indices to work out if a replacement is going to happen. - * In any case, to runtime it is. + /* + * When we have no replacement string to worry about, we may + * have more luck, because the forbidden empty string replacements + * are harmless when they are replaced by another empty string. */ + if ((first == TCL_INDEX_BEFORE) || (first == TCL_INDEX_START)) { + /* empty prefix - build suffix only */ + + if ((last == TCL_INDEX_END) || (last == TCL_INDEX_AFTER)) { + /* empty suffix too => empty result */ + OP( POP); /* Pop original */ + PUSH ( ""); + return TCL_OK; + } + OP44( STR_RANGE_IMM, last + 1, TCL_INDEX_END); + return TCL_OK; + } else { + if ((last == TCL_INDEX_END) || (last == TCL_INDEX_AFTER)) { + /* empty suffix - build prefix only */ + OP44( STR_RANGE_IMM, 0, first-1); + return TCL_OK; + } + OP( DUP); + OP44( STR_RANGE_IMM, 0, first-1); + OP4( REVERSE, 2); + OP44( STR_RANGE_IMM, last + 1, TCL_INDEX_END); + OP1( STR_CONCAT1, 2); + return TCL_OK; + } + } + genericReplace: - CompileWord(envPtr, valueTokenPtr, interp, 1); tokenPtr = TokenAfter(valueTokenPtr); CompileWord(envPtr, tokenPtr, interp, 2); tokenPtr = TokenAfter(tokenPtr); CompileWord(envPtr, tokenPtr, interp, 3); - if (replacementTokenPtr != NULL) { - CompileWord(envPtr, replacementTokenPtr, interp, 4); + if (parsePtr->numWords == 5) { + tokenPtr = TokenAfter(tokenPtr); + CompileWord(envPtr, tokenPtr, interp, 4); } else { PUSH( ""); } OP( STR_REPLACE); return TCL_OK; - } } int -- cgit v0.12 From bbae63cfa9695583ae13a24a1bf42d5931bbb116 Mon Sep 17 00:00:00 2001 From: oehhar Date: Tue, 13 Mar 2018 16:13:02 +0000 Subject: tip 499 msgcat custom preferences: documentation added --- doc/msgcat.n | 112 +++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 85 insertions(+), 27 deletions(-) diff --git a/doc/msgcat.n b/doc/msgcat.n index fbea20f..9074725 100644 --- a/doc/msgcat.n +++ b/doc/msgcat.n @@ -29,7 +29,9 @@ msgcat \- Tcl message catalog .sp \fB::msgcat::mclocale \fR?\fInewLocale\fR? .sp -\fB::msgcat::mcpreferences\fR +.VS "TIP 499" +\fB::msgcat::mcpreferences\fR ?\fIlocale preference\fR? ... +.VE "TIP 499" .sp .VS "TIP 412" \fB::msgcat::mcloadedlocales subcommand\fR ?\fIlocale\fR? @@ -54,6 +56,10 @@ msgcat \- Tcl message catalog .sp \fB::msgcat::mcforgetpackage\fR .VE "TIP 412" +.sp +.VS "TIP 499" +\fB::msgcat::mcutil subcommand\fR ?\fIlocale\fR? +.VS "TIP 499" .BE .SH DESCRIPTION .PP @@ -145,7 +151,7 @@ The namespace of the caller is used if not explicitly specified. \fB::msgcat::mcpackagenamespaceget\fR . Return the package namespace of the caller. -This command handles all cases described in section \fBObject oriented programming\fR. +This command handles all cases described in section \fBOBJECT ORIENTED PROGRAMMING\fR. .PP .RS Example usage is a tooltip package, which saves the caller package namespace to update the translation each time the tooltip is shown: @@ -169,14 +175,22 @@ proc ::tooltip::show {widget messagenamespace message} { .TP \fB::msgcat::mclocale \fR?\fInewLocale\fR? . -This function sets the locale to \fInewLocale\fR. If \fInewLocale\fR -is omitted, the current locale is returned, otherwise the current locale -is set to \fInewLocale\fR. msgcat stores and compares the locale in a +If \fInewLocale\fR is omitted, the current locale is returned, otherwise the current locale +is set to \fInewLocale\fR. +.PP +.RS +If the new locale is set to \fInewLocale\fR, the corresponding preferences are calculated and set. +For example, if the current locale is en_US_funky, then \fB::msgcat::mcpreferences\fR returns \fB{en_us_funky en_us en {}}\fR. +.PP +The same result may be acheved by \fB::msgcat::mcpreferences\fR {*}[\fB::msgcat::mcutil getpreferences\fR \fInewLocale\fR]. +.PP +The current locale is always the first element of the list returned by \fBmcpreferences\fR. +.PP +msgcat stores and compares the locale in a case-insensitive manner, and returns locales in lowercase. The initial locale is determined by the locale specified in the user's environment. See \fBLOCALE SPECIFICATION\fR below for a description of the locale string format. -.RS .PP .VS "TIP 412" If the locale is set, the preference list of locales is evaluated. @@ -184,16 +198,26 @@ Locales in this list are loaded now, if not jet loaded. .VE "TIP 412" .RE .TP -\fB::msgcat::mcpreferences\fR +\fB::msgcat::mcpreferences\fR ?\fIlocale preference\fR? ... . -Returns an ordered list of the locales preferred by -the user, based on the user's language specification. -The list is ordered from most specific to least -preference. The list is derived from the current -locale set in msgcat by \fB::msgcat::mclocale\fR, and -cannot be set independently. For example, if the -current locale is en_US_funky, then \fB::msgcat::mcpreferences\fR -returns \fB{en_us_funky en_us en {}}\fR. +Without arguments, returns an ordered list of the locales preferred by +the user. +The list is ordered from most specific to least preference. +.PP +.VS "TIP 499" +.RS +A set of locale preferences may be given to set the list of locale preferences. +The current locale is also set, which is the first element of the locale preferences list. +.PP +Locale preferences are loaded now, if not jet loaded. +.PP +As an example, the user may prefer French or English text. This may be configured by: +.CS +::msgcat::mcpreferences fr en {} +.CE +.RE +.PP +.VS "TIP 499" .TP \fB::msgcat:mcloadedlocales subcommand\fR ?\fIlocale\fR? . @@ -284,6 +308,22 @@ Note that this routine is only called if the concerned package did not set a pac The calling package clears all its state within the \fBmsgcat\fR package including all settings and translations. .VE "TIP 412" .PP +.VS "TIP 499" +.TP +\fB::msgcat::mcutil getpreferences\fR \fIlocale\fR +. +Return the preferences list of the given locale as described in section \fBLOCALE SPECIFICATION\fR. +An example is the composition of a preference list for the bilingual region "Biel/Bienne" as a concatenation of swiss german and swiss french: +.CS +% concat [lrange [msgcat::mcutil getpreferences fr_CH] 0 end-1] [msgcat::mcutil getpreferences de_CH] +fr_ch fr de_ch de {} +.CE +.TP +\fB::msgcat::mcutil getsystemlocale\fR +. +The system locale is returned as described by the section \fBLOCALE SPECIFICATION\fR. +.VE "TIP 499" +.PP .SH "LOCALE SPECIFICATION" .PP The locale is specified to \fBmsgcat\fR by a locale string @@ -489,7 +529,7 @@ formatting substitution is done directly. # human-oriented versions by \fBmsgcat::mcset\fR .CE .VS "TIP 412" -.SH Package private locale +.SH "PACKAGE PRIVATE LOCALE" .PP A package using \fBmsgcat\fR may choose to use its own package private locale and its own set of loaded locales, independent to the global @@ -513,10 +553,22 @@ This command may cause the load of locales. . Return the package private locale or the global locale, if no package private locale is set. .TP -\fB::msgcat::mcpackagelocale preferences\fR +\fB::msgcat::mcpackagelocale preferences\fR ?\fIlocale preference\fR? ... . -Return the package private preferences or the global preferences, +With no parameters, return the package private preferences or the global preferences, if no package private locale is set. +The package locale state (set or not) is not changed (in contrast to the command \fB::msgcat::mcpackagelocale set\fR). +.PP +.RS +.VS "TIP 499" +If a set of locale preferences is given, it is set as package locale preference list. +The package locale is set to the first element of the preference list. +A package locale is activated, if it was not set so far. +.PP +Locale preferences are loaded now for the package, if not jet loaded. +.VE "TIP 499" +.RE +.PP .TP \fB::msgcat::mcpackagelocale loaded\fR . @@ -540,7 +592,7 @@ Returns true, if the given locale is loaded for the package. . Clear any loaded locales of the package not present in the package preferences. .PP -.SH Changing package options +.SH "CHANGING PACKAGE OPTIONS" .PP Each package using msgcat has a set of options within \fBmsgcat\fR. The package options are described in the next sectionPackage options. @@ -615,7 +667,7 @@ A generic unknown handler is used if set to the empty string. This consists in r See section \fBcallback invocation\fR below. The appended arguments are identical to \fB::msgcat::mcunknown\fR. .RE -.SH Callback invocation +.SH "Callback invocation" A package may decide to register one or multiple callbacks, as described above. .PP Callbacks are invoked, if: @@ -630,13 +682,15 @@ If a called routine fails with an error, the \fBbgerror\fR routine for the inter Only exception is the callback \fBunknowncmd\fR, where an error causes the invoking \fBmc\fR-command to fail with that error. .PP .VS tip490 -.SH Object oriented programming +.SH "OBJECT ORIENTED PROGRAMMING" \fBmsgcat\fR supports packages implemented by object oriented programming. Objects and classes should be defined within a package namespace. .PP There are 3 supported cases where package namespace sensitive commands of msgcat (\fBmc\fR, \fBmcexists\fR, \fBmcpackagelocale\fR, \fBmcforgetpackage\fR, \fBmcpackagenamespaceget\fR, \fBmcpackageconfig\fR, \fBmcset\fR and \fBmcmset\fR) may be called: .PP -.SH 1) In class definition script +.TP +\fB1) In class definition script\fR +. \fBmsgcat\fR command is called within a class definition script. .CS namespace eval ::N2 { @@ -645,7 +699,9 @@ namespace eval ::N2 { } .CE .PP -.SH 2) method defined in a class +.TP +\fB2) method defined in a class\fR +. \fBmsgcat\fR command is called from a method in an object and the method is defined in a class. .CS namespace eval ::N3Class { @@ -657,7 +713,9 @@ namespace eval ::N3Class { } .CE .PP -.SH 3) method defined in a classless object +.TP +\fB3) method defined in a classless object\fR +. \fBmsgcat\fR command is called from a method of a classless object. .CS namespace eval ::N4 { @@ -670,7 +728,7 @@ namespace eval ::N4 { .CE .PP .VE tip490 -.SH Examples +.SH EXAMPLES Packages which display a GUI may update their widgets when the global locale changes. To register to a callback, use: .CS @@ -736,9 +794,9 @@ proc ::tcl::clock::LocalizeFormat { locale format } { .PP The message catalog code was developed by Mark Harrison. .SH "SEE ALSO" -format(n), scan(n), namespace(n), package(n) +format(n), scan(n), namespace(n), package(n), oo::class(n), oo::object .SH KEYWORDS -internationalization, i18n, localization, l10n, message, text, translation +internationalization, i18n, localization, l10n, message, text, translation, class, object .\" Local Variables: .\" mode: nroff .\" End: -- cgit v0.12 From 9ac074bd097e9924cd88d9fa603e507e9380c2a7 Mon Sep 17 00:00:00 2001 From: dgp Date: Tue, 13 Mar 2018 16:28:43 +0000 Subject: Fix bugs in msgcat that prevent clean test suite run. --- library/msgcat/msgcat.tcl | 2 +- tests/msgcat.test | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/library/msgcat/msgcat.tcl b/library/msgcat/msgcat.tcl index 100f425..598330d 100644 --- a/library/msgcat/msgcat.tcl +++ b/library/msgcat/msgcat.tcl @@ -1260,7 +1260,7 @@ proc msgcat::mcutil::getsystemlocale {} { # On Darwin, fallback to current CFLocale identifier if available. # if {[info exists ::tcl::mac::locale] && $::tcl::mac::locale ne ""} { - if {![catch { ConvertLocale $::tcl::mac::locale] } locale]} { + if {![catch { ConvertLocale $::tcl::mac::locale } locale]} { return $locale } } diff --git a/tests/msgcat.test b/tests/msgcat.test index 7ab9bcf..0d2f928 100644 --- a/tests/msgcat.test +++ b/tests/msgcat.test @@ -55,8 +55,13 @@ namespace eval ::msgcat::test { set result [string tolower [lindex $setVars 0]] if {[string length $result] == 0} { if {[info exists ::tcl::mac::locale]} { +if {[package vsatisfies [package provide msgcat] 1.7]} { + set result [string tolower \ + [msgcat::mcutil::ConvertLocale $::tcl::mac::locale]] +} else { set result [string tolower \ [msgcat::ConvertLocale $::tcl::mac::locale]] +} } else { if {([info sharedlibextension] eq ".dll") && ![catch {package require registry}]} { -- cgit v0.12