Tcl Source Code

Check-in [ff0766467e]
Login
Bounty program for improvements to Tcl and certain Tcl packages.
Tcl 2019 Conference, Houston/TX, US, Nov 4-8
Send your abstracts to [email protected]
or submit via the online form by Sep 9.

Many hyperlinks are disabled.
Use anonymous login to enable hyperlinks.

Overview
Comment:merge 8.7
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: ff0766467e3a2f2eef2e1ddd127422e59d364a1e3509bee885da31b400ae6423
User & Date: dgp 2018-03-13 17:30:00
Context
2018-03-14
05:51
merge 8.7 check-in: 3ba4c8e05a user: dgp tags: trunk
2018-03-13
17:30
merge 8.7 check-in: ff0766467e user: dgp tags: trunk
17:14
merge 8.6 check-in: 1a04240e68 user: dgp tags: core-8-branch
2018-03-12
16:00
merge 8.7 check-in: b8ddebf088 user: dgp tags: trunk
Changes
Hide Diffs Side-by-Side Diffs Ignore Whitespace Patch

Changes to changes.

  8878   8878   
  8879   8879   2017-09-02 (bug)[0e4d88] replace command, delete trace kills namespace (porter)
  8880   8880   
  8881   8881   --- Released 8.7a1, September 8, 2017 --- http://core.tcl.tk/tcl/ for details
  8882   8882   
  8883   8883   2018-03-12 (TIP 490) add oo support for msgcat => msgcat 1.7.0 (oehlmann)
  8884   8884   
  8885         -2018-03-12 (TIP 499) custom locale preference list (nijtmans)
         8885  +2018-03-12 (TIP 499) custom locale preference list (oehlmann)
  8886   8886   => msgcat 1.7.0

Changes to doc/msgcat.n.

     7      7   .TH "msgcat" n 1.5 msgcat "Tcl Bundled Packages"
     8      8   .so man.macros
     9      9   .BS
    10     10   '\" Note:  do not modify the .SH NAME line immediately below!
    11     11   .SH NAME
    12     12   msgcat \- Tcl message catalog
    13     13   .SH SYNOPSIS
    14         -\fBpackage require Tcl 8.5\fR
           14  +\fBpackage require Tcl 8.7\fR
    15     15   .sp
    16         -\fBpackage require msgcat 1.6\fR
           16  +\fBpackage require msgcat 1.7\fR
    17     17   .sp
    18     18   \fB::msgcat::mc \fIsrc-string\fR ?\fIarg arg ...\fR?
    19     19   .sp
    20     20   \fB::msgcat::mcmax ?\fIsrc-string src-string ...\fR?
    21     21   .sp
    22     22   .VS "TIP 412"
    23     23   \fB::msgcat::mcexists\fR ?\fB-exactnamespace\fR? ?\fB-exactlocale\fR? \fIsrc-string\fR
    24     24   .VE "TIP 412"
           25  +.sp
           26  +.VS "TIP 490"
           27  +\fB::msgcat::mcpackagenamespaceget\fR
           28  +.VE "TIP 490"
    25     29   .sp
    26     30   \fB::msgcat::mclocale \fR?\fInewLocale\fR?
    27     31   .sp
    28         -\fB::msgcat::mcpreferences\fR
           32  +.VS "TIP 499"
           33  +\fB::msgcat::mcpreferences\fR ?\fIlocale preference\fR? ...
           34  +.VE "TIP 499"
    29     35   .sp
    30     36   .VS "TIP 412"
    31     37   \fB::msgcat::mcloadedlocales subcommand\fR ?\fIlocale\fR?
    32     38   .VE "TIP 412"
    33     39   .sp
    34     40   \fB::msgcat::mcload \fIdirname\fR
    35     41   .sp
................................................................................
    46     52   .VS "TIP 412"
    47     53   \fB::msgcat::mcpackagelocale subcommand\fR ?\fIlocale\fR?
    48     54   .sp
    49     55   \fB::msgcat::mcpackageconfig subcommand\fR \fIoption\fR ?\fIvalue\fR?
    50     56   .sp
    51     57   \fB::msgcat::mcforgetpackage\fR
    52     58   .VE "TIP 412"
           59  +.sp
           60  +.VS "TIP 499"
           61  +\fB::msgcat::mcutil subcommand\fR ?\fIlocale\fR?
           62  +.VS "TIP 499"
    53     63   .BE
    54     64   .SH DESCRIPTION
    55     65   .PP
    56     66   The \fBmsgcat\fR package provides a set of functions
    57     67   that can be used to manage multi-lingual user interfaces.
    58     68   Text strings are defined in a
    59     69   .QW "message catalog"
................................................................................
    67     77   Each package has its own message catalog and configuration settings in \fBmsgcat\fR.
    68     78   .PP
    69     79   A \fIlocale\fR is a specification string describing a user language like \fBde_ch\fR for Swiss German.
    70     80   In \fBmsgcat\fR, there is a global locale initialized by the system locale of the current system.
    71     81   Each package may decide to use the global locale or to use a package specific locale.
    72     82   .PP
    73     83   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.
           84  +.PP
           85  +.VS tip490
           86  +Object oriented programming is supported by the use of a package namespace.
           87  +.VE tip490
           88  +.PP
    74     89   .SH COMMANDS
    75     90   .TP
    76     91   \fB::msgcat::mc \fIsrc-string\fR ?\fIarg arg ...\fR?
    77     92   .
    78     93   Returns a translation of \fIsrc-string\fR according to the
    79     94   current locale.  If additional arguments past \fIsrc-string\fR
    80     95   are given, the \fBformat\fR command is used to substitute the
................................................................................
    91    106   \fB::msgcat::mc\fR is the main function used to localize an
    92    107   application.  Instead of using an English string directly, an
    93    108   application can pass the English string through \fB::msgcat::mc\fR and
    94    109   use the result.  If an application is written for a single language in
    95    110   this fashion, then it is easy to add support for additional languages
    96    111   later simply by defining new message catalog entries.
    97    112   .RE
          113  +.VS "TIP 490"
          114  +.TP
          115  +\fB::msgcat::mcn \fInamespace\fR \fIsrc-string\fR ?\fIarg arg ...\fR?
          116  +.
          117  +Like \fB::msgcat::mc\fR, but with the message namespace specified as first argument.
          118  +.PP
          119  +.RS
          120  +\fBmcn\fR may be used for cases where the package namespace is not the namespace of the caller.
          121  +An example is shown within the description of the command \fB::msgcat::mcpackagenamespaceget\fR below.
          122  +.RE
          123  +.PP
    98    124   .TP
    99    125   \fB::msgcat::mcmax ?\fIsrc-string src-string ...\fR?
   100    126   .
   101    127   Given several source strings, \fB::msgcat::mcmax\fR returns the length
   102    128   of the longest translated string.  This is useful when designing
   103    129   localized GUIs, which may require that all buttons, for example, be a
   104    130   fixed width (which will be the width of the widest button).
   105    131   .TP
   106         -\fB::msgcat::mcexists\fR ?\fB-exactnamespace\fR? ?\fB-exactlocale\fR? \fIsrc-string\fR
   107         -.
   108    132   .VS "TIP 412"
          133  +\fB::msgcat::mcexists\fR ?\fB-exactnamespace\fR? ?\fB-exactlocale\fR? ?\fB-namespace\fR \fInamespace\fR? \fIsrc-string\fR
          134  +.
   109    135   Return true, if there is a translation for the given \fIsrc-string\fR.
   110    136   .PP
   111    137   .RS
   112    138   The search may be limited by the option \fB\-exactnamespace\fR to only check the current namespace and not any parent namespaces.
   113    139   .PP
   114    140   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).
   115         -.RE
          141  +.PP
   116    142   .VE "TIP 412"
          143  +.VS "TIP 490"
          144  +An explicit package namespace may be specified by the option \fB-namespace\fR.
          145  +The namespace of the caller is used if not explicitly specified.
          146  +.RE
          147  +.PP
          148  +.VE "TIP 490"
          149  +.VS "TIP 490"
          150  +.TP
          151  +\fB::msgcat::mcpackagenamespaceget\fR
          152  +.
          153  +Return the package namespace of the caller.
          154  +This command handles all cases described in section \fBOBJECT ORIENTED PROGRAMMING\fR.
          155  +.PP
          156  +.RS
          157  +Example usage is a tooltip package, which saves the caller package namespace to update the translation each time the tooltip is shown:
          158  +.CS
          159  +proc ::tooltip::tooltip {widget message} {
          160  +    ...
          161  +    set messagenamespace [uplevel 1 {::msgcat::mcpackagenamespaceget}]
          162  +    ...
          163  +    bind $widget  [list ::tooltip::show $widget $messagenamespace $message]
          164  +}
          165  +
          166  +proc ::tooltip::show {widget messagenamespace message} {
          167  +    ...
          168  +    set message [::msgcat::mcn $messagenamespace $message]
          169  +    ...
          170  +}
          171  +.CE
          172  +.RE
          173  +.PP
          174  +.VE "TIP 490"
   117    175   .TP
   118    176   \fB::msgcat::mclocale \fR?\fInewLocale\fR?
   119    177   .
   120         -This function sets the locale to \fInewLocale\fR.  If \fInewLocale\fR
   121         -is omitted, the current locale is returned, otherwise the current locale
   122         -is set to \fInewLocale\fR.  msgcat stores and compares the locale in a
          178  +If \fInewLocale\fR is omitted, the current locale is returned, otherwise the current locale
          179  +is set to \fInewLocale\fR.
          180  +.PP
          181  +.RS
          182  +If the new locale is set to \fInewLocale\fR, the corresponding preferences are calculated and set.
          183  +For example, if the current locale is en_US_funky, then \fB::msgcat::mcpreferences\fR returns \fB{en_us_funky en_us en {}}\fR.
          184  +.PP
          185  +The same result may be acheved by \fB::msgcat::mcpreferences\fR {*}[\fB::msgcat::mcutil getpreferences\fR \fInewLocale\fR].
          186  +.PP
          187  +The current locale is always the first element of the list returned by \fBmcpreferences\fR.
          188  +.PP
          189  +msgcat stores and compares the locale in a
   123    190   case-insensitive manner, and returns locales in lowercase.
   124    191   The initial locale is determined by the locale specified in
   125    192   the user's environment.  See \fBLOCALE SPECIFICATION\fR
   126    193   below for a description of the locale string format.
   127         -.RS
   128    194   .PP
   129    195   .VS "TIP 412"
   130    196   If the locale is set, the preference list of locales is evaluated.
   131    197   Locales in this list are loaded now, if not jet loaded.
   132    198   .VE "TIP 412"
   133    199   .RE
   134    200   .TP
   135         -\fB::msgcat::mcpreferences\fR
          201  +\fB::msgcat::mcpreferences\fR ?\fIlocale preference\fR? ...
   136    202   .
   137         -Returns an ordered list of the locales preferred by
   138         -the user, based on the user's language specification.
   139         -The list is ordered from most specific to least
   140         -preference.  The list is derived from the current
   141         -locale set in msgcat by \fB::msgcat::mclocale\fR, and
   142         -cannot be set independently.  For example, if the
   143         -current locale is en_US_funky, then \fB::msgcat::mcpreferences\fR
   144         -returns \fB{en_us_funky en_us en {}}\fR.
          203  +Without arguments, returns an ordered list of the locales preferred by
          204  +the user.
          205  +The list is ordered from most specific to least preference.
          206  +.PP
          207  +.VS "TIP 499"
          208  +.RS
          209  +A set of locale preferences may be given to set the list of locale preferences.
          210  +The current locale is also set, which is the first element of the locale preferences list.
          211  +.PP
          212  +Locale preferences are loaded now, if not jet loaded.
          213  +.PP
          214  +As an example, the user may prefer French or English text. This may be configured by:
          215  +.CS
          216  +::msgcat::mcpreferences fr en {}
          217  +.CE
          218  +.RE
          219  +.PP
          220  +.VS "TIP 499"
   145    221   .TP
   146    222   \fB::msgcat:mcloadedlocales subcommand\fR ?\fIlocale\fR?
   147    223   .
   148    224   This group of commands manage the list of loaded locales for packages not setting a package locale.
   149    225   .PP
   150    226   .RS
   151    227   The subcommand \fBget\fR returns the list of currently loaded locales.
................................................................................
   227    303   Note that this routine is only called if the concerned package did not set a package locale unknown command name.
   228    304   .RE
   229    305   .TP
   230    306   \fB::msgcat::mcforgetpackage\fR
   231    307   .
   232    308   The calling package clears all its state within the \fBmsgcat\fR package including all settings and translations.
   233    309   .VE "TIP 412"
          310  +.PP
          311  +.VS "TIP 499"
          312  +.TP
          313  +\fB::msgcat::mcutil getpreferences\fR \fIlocale\fR
          314  +.
          315  +Return the preferences list of the given locale as described in section \fBLOCALE SPECIFICATION\fR.
          316  +An example is the composition of a preference list for the bilingual region "Biel/Bienne" as a concatenation of swiss german and swiss french:
          317  +.CS
          318  +% concat [lrange [msgcat::mcutil getpreferences fr_CH] 0 end-1] [msgcat::mcutil getpreferences de_CH]
          319  +fr_ch fr de_ch de {}
          320  +.CE
          321  +.TP
          322  +\fB::msgcat::mcutil getsystemlocale\fR
          323  +.
          324  +The system locale is returned as described by the section \fBLOCALE SPECIFICATION\fR.
          325  +.VE "TIP 499"
   234    326   .PP
   235    327   .SH "LOCALE SPECIFICATION"
   236    328   .PP
   237    329   The locale is specified to \fBmsgcat\fR by a locale string
   238    330   passed to \fB::msgcat::mclocale\fR.
   239    331   The locale string consists of
   240    332   a language code, an optional country code, and an optional
................................................................................
   433    525   .PP
   434    526   .CS
   435    527   \fBmsgcat::mc\fR {Produced %1$d at %2$s} $num $city
   436    528   # ... where that key is mapped to one of the
   437    529   # human-oriented versions by \fBmsgcat::mcset\fR
   438    530   .CE
   439    531   .VS "TIP 412"
   440         -.SH Package private locale
          532  +.SH "PACKAGE PRIVATE LOCALE"
   441    533   .PP
   442    534   A package using \fBmsgcat\fR may choose to use its own package private
   443    535   locale and its own set of loaded locales, independent to the global
   444    536   locale set by \fB::msgcat::mclocale\fR.
   445    537   .PP
   446    538   This allows a package to change its locale without causing any locales load or removal in other packages and not to invoke the global locale change callback (see below).
   447    539   .PP
................................................................................
   457    549   This command may cause the load of locales.
   458    550   .RE
   459    551   .TP
   460    552   \fB::msgcat::mcpackagelocale get\fR
   461    553   .
   462    554   Return the package private locale or the global locale, if no package private locale is set.
   463    555   .TP
   464         -\fB::msgcat::mcpackagelocale preferences\fR
          556  +\fB::msgcat::mcpackagelocale preferences\fR ?\fIlocale preference\fR? ...
   465    557   .
   466         -Return the package private preferences or the global preferences,
          558  +With no parameters, return the package private preferences or the global preferences,
   467    559   if no package private locale is set.
          560  +The package locale state (set or not) is not changed (in contrast to the command \fB::msgcat::mcpackagelocale set\fR).
          561  +.PP
          562  +.RS
          563  +.VS "TIP 499"
          564  +If a set of locale preferences is given, it is set as package locale preference list.
          565  +The package locale is set to the first element of the preference list.
          566  +A package locale is activated, if it was not set so far.
          567  +.PP
          568  +Locale preferences are loaded now for the package, if not jet loaded.
          569  +.VE "TIP 499"
          570  +.RE
          571  +.PP
   468    572   .TP
   469    573   \fB::msgcat::mcpackagelocale loaded\fR
   470    574   .
   471    575   Return the list of locales loaded for this package.
   472    576   .TP
   473    577   \fB::msgcat::mcpackagelocale isset\fR
   474    578   .
................................................................................
   484    588   .
   485    589   Returns true, if the given locale is loaded for the package.
   486    590   .TP
   487    591   \fB::msgcat::mcpackagelocale clear\fR
   488    592   .
   489    593   Clear any loaded locales of the package not present in the package preferences.
   490    594   .PP
   491         -.SH Changing package options
          595  +.SH "CHANGING PACKAGE OPTIONS"
   492    596   .PP
   493    597   Each package using msgcat has a set of options within \fBmsgcat\fR.
   494    598   The package options are described in the next sectionPackage options.
   495    599   Each package option may be set or unset individually using the following ensemble:
   496    600   .TP
   497    601   \fB::msgcat::mcpackageconfig get\fR \fIoption\fR
   498    602   .
................................................................................
   559    663   The called procedure must return the formatted message which will finally be returned by msgcat::mc.
   560    664   .PP
   561    665   A generic unknown handler is used if set to the empty string. This consists in returning the key if no arguments are given. With given arguments, format is used to process the arguments.
   562    666   .PP
   563    667   See section \fBcallback invocation\fR below.
   564    668   The appended arguments are identical to \fB::msgcat::mcunknown\fR.
   565    669   .RE
   566         -.SS Callback invocation
          670  +.SH "Callback invocation"
   567    671   A package may decide to register one or multiple callbacks, as described above.
   568    672   .PP
   569    673   Callbacks are invoked, if:
   570    674   .PP
   571    675   1. the callback command is set,
   572    676   .PP
   573    677   2. the command is not the empty string,
   574    678   .PP
   575    679   3. the registering namespace exists.
   576    680   .PP
   577    681   If a called routine fails with an error, the \fBbgerror\fR routine for the interpreter is invoked after command completion.
   578    682   Only exception is the callback \fBunknowncmd\fR, where an error causes the invoking \fBmc\fR-command to fail with that error.
   579    683   .PP
   580         -.SS Examples
          684  +.VS tip490
          685  +.SH "OBJECT ORIENTED PROGRAMMING"
          686  +\fBmsgcat\fR supports packages implemented by object oriented programming.
          687  +Objects and classes should be defined within a package namespace.
          688  +.PP
          689  +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:
          690  +.PP
          691  +.TP
          692  +\fB1) In class definition script\fR
          693  +.
          694  +\fBmsgcat\fR command is called within a class definition script.
          695  +.CS
          696  +namespace eval ::N2 {
          697  +    mcload $dir/msgs
          698  +    oo::class create C1 {puts [mc Hi!]}
          699  +}
          700  +.CE
          701  +.PP
          702  +.TP
          703  +\fB2) method defined in a class\fR
          704  +.
          705  +\fBmsgcat\fR command is called from a method in an object and the method is defined in a class.
          706  +.CS
          707  +namespace eval ::N3Class {
          708  +    mcload $dir/msgs
          709  +    oo::class create C1
          710  +    oo::define C1 method m1 {
          711  +        puts [mc Hi!]
          712  +    }
          713  +}
          714  +.CE
          715  +.PP
          716  +.TP
          717  +\fB3) method defined in a classless object\fR
          718  +.
          719  +\fBmsgcat\fR command is called from a method of a classless object.
          720  +.CS
          721  +namespace eval ::N4 {
          722  +    mcload $dir/msgs
          723  +    oo::object create O1
          724  +    oo::objdefine O1 method m1 {} {
          725  +        puts [mc Hi!]
          726  +    }
          727  +}
          728  +.CE
          729  +.PP
          730  +.VE tip490
          731  +.SH EXAMPLES
   581    732   Packages which display a GUI may update their widgets when the global locale changes.
   582    733   To register to a callback, use:
   583    734   .CS
   584    735   namespace eval gui {
   585    736       msgcat::mcpackageconfig changecmd updateGUI
   586    737   
   587    738       proc updateGui args {
................................................................................
   639    790   }
   640    791   .CE
   641    792   .VE "TIP 412"
   642    793   .SH CREDITS
   643    794   .PP
   644    795   The message catalog code was developed by Mark Harrison.
   645    796   .SH "SEE ALSO"
   646         -format(n), scan(n), namespace(n), package(n)
          797  +format(n), scan(n), namespace(n), package(n), oo::class(n), oo::object
   647    798   .SH KEYWORDS
   648         -internationalization, i18n, localization, l10n, message, text, translation
          799  +internationalization, i18n, localization, l10n, message, text, translation, class, object
   649    800   .\" Local Variables:
   650    801   .\" mode: nroff
   651    802   .\" End:

Changes to generic/tclCmdMZ.c.

  2299   2299   static int
  2300   2300   StringRplcCmd(
  2301   2301       ClientData dummy,		/* Not used. */
  2302   2302       Tcl_Interp *interp,		/* Current interpreter. */
  2303   2303       int objc,			/* Number of arguments. */
  2304   2304       Tcl_Obj *const objv[])	/* Argument objects. */
  2305   2305   {
         2306  +    Tcl_UniChar *ustring;
  2306   2307       int first, last, length, end;
  2307   2308   
  2308   2309       if (objc < 4 || objc > 5) {
  2309   2310   	Tcl_WrongNumArgs(interp, 1, objv, "string first last ?string?");
  2310   2311   	return TCL_ERROR;
  2311   2312       }
  2312   2313   
  2313         -    (void) Tcl_GetUnicodeFromObj(objv[1], &length);
         2314  +    ustring = Tcl_GetUnicodeFromObj(objv[1], &length);
  2314   2315       end = length - 1;
  2315   2316   
  2316   2317       if (TclGetIntForIndexM(interp, objv[2], end, &first) != TCL_OK ||
  2317   2318   	    TclGetIntForIndexM(interp, objv[3], end, &last) != TCL_OK){
  2318   2319   	return TCL_ERROR;
  2319   2320       }
  2320   2321   
  2321   2322       /*
  2322         -     * [string replace] does not replace empty strings.  This is
  2323         -     * unwise, but since it is true, here we quickly screen out
  2324         -     * index pairs that demarcate an empty substring.
         2323  +     * The following test screens out most empty substrings as
         2324  +     * candidates for replacement. When they are detected, no
         2325  +     * replacement is done, and the result is the original string,
  2325   2326        */
  2326         -
  2327   2327       if ((last < 0) ||		/* Range ends before start of string */
  2328   2328   	    (first > end) ||	/* Range begins after end of string */
  2329   2329   	    (last < first)) {	/* Range begins after it starts */
         2330  +
         2331  +	/*
         2332  +	 * BUT!!! when (end < 0) -- an empty original string -- we can
         2333  +	 * have (first <= end < 0 <= last) and an empty string is permitted
         2334  +	 * to be replaced.
         2335  +	 */
  2330   2336   	Tcl_SetObjResult(interp, objv[1]);
  2331   2337       } else {
  2332   2338   	Tcl_Obj *resultPtr;
  2333   2339   
  2334   2340   	/*
  2335   2341   	 * We are re-fetching in case the string argument is same value as 
  2336   2342   	 * an index argument, and shimmering cost us our ustring.
  2337   2343   	 */
  2338   2344   
  2339         -	Tcl_UniChar *ustring = Tcl_GetUnicodeFromObj(objv[1], &length);
  2340         -
  2341         -	end = length - 1;
         2345  +	ustring = Tcl_GetUnicodeFromObj(objv[1], &length);
         2346  +	end = length-1;
  2342   2347   
  2343   2348   	if (first < 0) {
  2344   2349   	    first = 0;
  2345   2350   	}
  2346   2351   
  2347   2352   	resultPtr = Tcl_NewUnicodeObj(ustring, first);
  2348   2353   	if (objc == 5) {

Changes to generic/tclCompCmdsSZ.c.

   991    991       Tcl_Interp *interp,		/* Tcl interpreter for context. */
   992    992       Tcl_Parse *parsePtr,	/* Points to a parse structure for the
   993    993   				 * command. */
   994    994       Command *cmdPtr,		/* Points to defintion of command being
   995    995   				 * compiled. */
   996    996       CompileEnv *envPtr)		/* Holds the resulting instructions. */
   997    997   {
   998         -    Tcl_Token *tokenPtr, *valueTokenPtr, *replacementTokenPtr = NULL;
          998  +    Tcl_Token *tokenPtr, *valueTokenPtr;
   999    999       DefineLineInformation;	/* TIP #280 */
  1000         -    int idx1, idx2;
         1000  +    int first, last;
  1001   1001   
  1002   1002       if (parsePtr->numWords < 4 || parsePtr->numWords > 5) {
  1003   1003   	return TCL_ERROR;
  1004   1004       }
         1005  + 
         1006  +    /* Bytecode to compute/push string argument being replaced */
  1005   1007       valueTokenPtr = TokenAfter(parsePtr->tokenPtr);
  1006         -    if (parsePtr->numWords == 5) {
  1007         -	tokenPtr = TokenAfter(valueTokenPtr);
  1008         -	tokenPtr = TokenAfter(tokenPtr);
  1009         -	replacementTokenPtr = TokenAfter(tokenPtr);
  1010         -    }
         1008  +    CompileWord(envPtr, valueTokenPtr, interp, 1);
  1011   1009   
         1010  +    /*
         1011  +     * Check for first index known and useful at compile time. 
         1012  +     */
  1012   1013       tokenPtr = TokenAfter(valueTokenPtr);
  1013         -    if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_START, TCL_INDEX_AFTER,
  1014         -	    &idx1) != TCL_OK) {
         1014  +    if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_AFTER,
         1015  +	    &first) != TCL_OK) {
  1015   1016   	goto genericReplace;
  1016   1017       }
         1018  +
  1017   1019       /*
  1018         -     * Token parsed as an index value. Indices before the string are
  1019         -     * treated as index of start of string.
         1020  +     * Check for last index known and useful at compile time. 
  1020   1021        */
  1021         -
  1022   1022       tokenPtr = TokenAfter(tokenPtr);
  1023         -    if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_END,
  1024         -	    &idx2) != TCL_OK) {
         1023  +    if (TclGetIndexFromToken(tokenPtr, TCL_INDEX_BEFORE, TCL_INDEX_AFTER,
         1024  +	    &last) != TCL_OK) {
  1025   1025   	goto genericReplace;
  1026   1026       }
  1027         -    /*
  1028         -     * Token parsed as an index value. Indices after the string are
  1029         -     * treated as index of end of string.
  1030         -     */
  1031         -
  1032         -/* TODO...... */
  1033         -    /*
  1034         -     * We handle these replacements specially: first character (where
  1035         -     * idx1=idx2=0) and last character (where idx1=idx2=TCL_INDEX_END). Anything
  1036         -     * else and the semantics get rather screwy.
         1027  +
         1028  +    /* 
         1029  +     * [string replace] is an odd bird.  For many arguments it is
         1030  +     * a conventional substring replacer.  However it also goes out
         1031  +     * of its way to become a no-op for many cases where it would be
         1032  +     * replacing an empty substring.  Precisely, it is a no-op when
         1033  +     *
         1034  +     *		(last < first)		OR
         1035  +     *		(last < 0)		OR
         1036  +     *		(end < first)
         1037  +     *
         1038  +     * For some compile-time values we can detect these cases, and
         1039  +     * compile direct to bytecode implementing the no-op.
         1040  +     */
         1041  +
         1042  +    if ((last == TCL_INDEX_BEFORE)		/* Know (last < 0) */
         1043  +	    || (first == TCL_INDEX_AFTER)	/* Know (first > end) */
         1044  +
         1045  +	/*
         1046  +	 * Tricky to determine when runtime (last < first) can be
         1047  +	 * certainly known based on the encoded values. Consider the
         1048  +	 * cases...
         1049  +	 *
         1050  +	 * (first <= TCL_INDEX_END) &&
         1051  +	 *	(last == TCL_INDEX_AFTER) => cannot tell REJECT
         1052  +	 *	(last <= TCL_INDEX END) && (last < first) => ACCEPT
         1053  +	 *	else => cannot tell REJECT
         1054  +	 */
         1055  +	    || ((first <= TCL_INDEX_END) && (last <= TCL_INDEX_END)
         1056  +		&& (last < first))		/* Know (last < first) */
         1057  +	/*
         1058  +	 * (first == TCL_INDEX_BEFORE) &&
         1059  +	 *	(last == TCL_INDEX_AFTER) => (first < last) REJECT
         1060  +	 *	(last <= TCL_INDEX_END) => cannot tell REJECT
         1061  +	 *	else		=> (first < last) REJECT
         1062  +	 *
         1063  +	 * else [[first >= TCL_INDEX_START]] &&
         1064  +	 *	(last == TCL_INDEX_AFTER) => cannot tell REJECT
         1065  +	 *	(last <= TCL_INDEX_END) => cannot tell REJECT
         1066  +	 *	else [[last >= TCL_INDEX START]] && (last < first) => ACCEPT
         1067  +	 */
         1068  +	    || ((first >= TCL_INDEX_START) && (last >= TCL_INDEX_START)
         1069  +		&& (last < first))) {		/* Know (last < first) */
         1070  +	if (parsePtr->numWords == 5) {
         1071  +	    tokenPtr = TokenAfter(tokenPtr);
         1072  +	    CompileWord(envPtr, tokenPtr, interp, 4);
         1073  +	    OP(		POP);		/* Pop newString */
         1074  +	}
         1075  +	/* Original string argument now on TOS as result */
         1076  +	return TCL_OK;
         1077  +    }
         1078  +
         1079  +    if (parsePtr->numWords == 5) {
         1080  +    /*
         1081  +     * When we have a string replacement, we have to take care about
         1082  +     * not replacing empty substrings that [string replace] promises
         1083  +     * not to replace
         1084  +     *
         1085  +     * The remaining index values might be suitable for conventional
         1086  +     * string replacement, but only if they cannot possibly meet the
         1087  +     * conditions described above at runtime. If there's a chance they
         1088  +     * might, we would have to emit bytecode to check and at that point
         1089  +     * we're paying more in bytecode execution time than would make
         1090  +     * things worthwhile. Trouble is we are very limited in
         1091  +     * how much we can detect that at compile time. After decoding,
         1092  +     * we need, first:
         1093  +     *
         1094  +     *		(first <= end)
         1095  +     *
         1096  +     * The encoded indices (first <= TCL_INDEX END) and
         1097  +     * (first == TCL_INDEX_BEFORE) always meets this condition, but
         1098  +     * any other encoded first index has some list for which it fails.
         1099  +     *
         1100  +     * We also need, second:
         1101  +     *
         1102  +     *		(last >= 0)
         1103  +     *
         1104  +     * The encoded indices (last >= TCL_INDEX_START) and
         1105  +     * (last == TCL_INDEX_AFTER) always meet this condition but any
         1106  +     * other encoded last index has some list for which it fails.
  1037   1107        *
  1038         -     * TODO: These seem to be very narrow cases.  They are not even
  1039         -     * covered by the test suite, and any programming that ends up
  1040         -     * here could have been coded by the programmer using [string range]
  1041         -     * and [string cat]. [*]  Not clear at all to me that the bytecode
  1042         -     * generated here is worthwhile.
         1108  +     * Finally we need, third:
  1043   1109        *
  1044         -     *  [*] Except for the empty string exceptions.  UGGGGHHHH.
  1045         -     */
  1046         -
  1047         -    if (idx1 == 0 && idx2 == 0) {
  1048         -	int notEq, end;
  1049         -
  1050         -	/*
  1051         -	 * Just working with the first character.
  1052         -	 */
  1053         -
  1054         -	CompileWord(envPtr, valueTokenPtr, interp, 1);
  1055         -	if (replacementTokenPtr == NULL) {
  1056         -	    /* Drop first */
  1057         -	    OP44(	STR_RANGE_IMM, 1, TCL_INDEX_END);
  1058         -	    return TCL_OK;
  1059         -	}
  1060         -	/* Replace first */
  1061         -	CompileWord(envPtr, replacementTokenPtr, interp, 4);
  1062         -
  1063         -	/*
  1064         -	 * NOTE: The following tower of bullshit is present because
  1065         -	 * [string replace] was boneheadedly defined not to replace
  1066         -	 * empty strings, so we actually have to detect the empty
  1067         -	 * string case and treat it differently.
  1068         -	 */
  1069         -
  1070         -	OP4(		OVER, 1);
  1071         -	PUSH(		"");
  1072         -	OP(		STR_EQ);
  1073         -	JUMP1(		JUMP_FALSE, notEq);
  1074         -	OP(		POP);
  1075         -	JUMP1(		JUMP, end);
  1076         -	FIXJUMP1(notEq);
  1077         -	TclAdjustStackDepth(1, envPtr);
         1110  +     *		(first <= last)
         1111  +     * 
         1112  +     * Considered in combination with the constraints we already have,
         1113  +     * we see that we can proceed when (first == TCL_INDEX_BEFORE)
         1114  +     * or (last == TCL_INDEX_AFTER). These also permit simplification
         1115  +     * of the prefix|replace|suffix construction. The other constraints,
         1116  +     * though, interfere with getting a guarantee that first <= last. 
         1117  +     */
         1118  +
         1119  +    if ((first == TCL_INDEX_BEFORE) && (last >= TCL_INDEX_START)) {
         1120  +	/* empty prefix */
         1121  +	tokenPtr = TokenAfter(tokenPtr);
         1122  +	CompileWord(envPtr, tokenPtr, interp, 4);
  1078   1123   	OP4(		REVERSE, 2);
  1079         -	OP44(		STR_RANGE_IMM, 1, TCL_INDEX_END);
         1124  +	if (last == TCL_INDEX_AFTER) {
         1125  +	    OP(		POP);		/* Pop  original */
         1126  +	} else {
         1127  +	    OP44(	STR_RANGE_IMM, last + 1, TCL_INDEX_END);
         1128  +	    OP1(	STR_CONCAT1, 2);
         1129  +	}
         1130  +	return TCL_OK;
         1131  +    }
         1132  +
         1133  +    if ((last == TCL_INDEX_AFTER) && (first <= TCL_INDEX_END)) {
         1134  +	OP44(		STR_RANGE_IMM, 0, first-1);
         1135  +	tokenPtr = TokenAfter(tokenPtr);
         1136  +	CompileWord(envPtr, tokenPtr, interp, 4);
  1080   1137   	OP1(		STR_CONCAT1, 2);
  1081         -	FIXJUMP1(end);
  1082   1138   	return TCL_OK;
         1139  +    }
  1083   1140   
  1084         -    } else if (idx1 == TCL_INDEX_END && idx2 == TCL_INDEX_END) {
  1085         -	int notEq, end;
         1141  +	/* FLOW THROUGH TO genericReplace */
  1086   1142   
  1087         -	/*
  1088         -	 * Just working with the last character.
         1143  +    } else {
         1144  +	/* 
         1145  +	 * When we have no replacement string to worry about, we may
         1146  +	 * have more luck, because the forbidden empty string replacements
         1147  +	 * are harmless when they are replaced by another empty string.
  1089   1148   	 */
  1090   1149   
  1091         -	CompileWord(envPtr, valueTokenPtr, interp, 1);
  1092         -	if (replacementTokenPtr == NULL) {
  1093         -	    /* Drop last */
  1094         -	    OP44(	STR_RANGE_IMM, 0, TCL_INDEX_END-1);
         1150  +	if ((first == TCL_INDEX_BEFORE) || (first == TCL_INDEX_START)) {
         1151  +	    /* empty prefix - build suffix only */
         1152  +
         1153  +	    if ((last == TCL_INDEX_END) || (last == TCL_INDEX_AFTER)) {
         1154  +		/* empty suffix too => empty result */
         1155  +		OP(	POP);		/* Pop  original */
         1156  +		PUSH	(	"");
         1157  +		return TCL_OK;
         1158  +	    }
         1159  +	    OP44(	STR_RANGE_IMM, last + 1, TCL_INDEX_END);
         1160  +	    return TCL_OK;
         1161  +	} else {
         1162  +	    if ((last == TCL_INDEX_END) || (last == TCL_INDEX_AFTER)) {
         1163  +		/* empty suffix - build prefix only */
         1164  +		OP44(	STR_RANGE_IMM, 0, first-1);
         1165  +		return TCL_OK;
         1166  +	    }
         1167  +	    OP(		DUP);
         1168  +	    OP44(	STR_RANGE_IMM, 0, first-1);
         1169  +	    OP4(	REVERSE, 2);
         1170  +	    OP44(	STR_RANGE_IMM, last + 1, TCL_INDEX_END);
         1171  +	    OP1(	STR_CONCAT1, 2);
  1095   1172   	    return TCL_OK;
  1096   1173   	}
  1097         -	/* Replace last */
  1098         -	CompileWord(envPtr, replacementTokenPtr, interp, 4);
  1099         -
  1100         -	/* More bullshit; see NOTE above. */
  1101         -
  1102         -	OP4(		OVER, 1);
  1103         -	PUSH(		"");
  1104         -	OP(		STR_EQ);
  1105         -	JUMP1(		JUMP_FALSE, notEq);
  1106         -	OP(		POP);
  1107         -	JUMP1(		JUMP, end);
  1108         -	FIXJUMP1(notEq);
  1109         -	TclAdjustStackDepth(1, envPtr);
  1110         -	OP4(		REVERSE, 2);
  1111         -	OP44(		STR_RANGE_IMM, 0, TCL_INDEX_END-1);
  1112         -	OP4(		REVERSE, 2);
  1113         -	OP1(		STR_CONCAT1, 2);
  1114         -	FIXJUMP1(end);
  1115         -	return TCL_OK;
  1116         -
  1117         -    } else {
  1118         -	/*
  1119         -	 * Need to process indices at runtime. This could be because the
  1120         -	 * indices are not constants, or because we need to resolve them to
  1121         -	 * absolute indices to work out if a replacement is going to happen.
  1122         -	 * In any case, to runtime it is.
  1123         -	 */
         1174  +    }
  1124   1175   
  1125   1176       genericReplace:
  1126         -	CompileWord(envPtr, valueTokenPtr, interp, 1);
  1127   1177   	tokenPtr = TokenAfter(valueTokenPtr);
  1128   1178   	CompileWord(envPtr, tokenPtr, interp, 2);
  1129   1179   	tokenPtr = TokenAfter(tokenPtr);
  1130   1180   	CompileWord(envPtr, tokenPtr, interp, 3);
  1131         -	if (replacementTokenPtr != NULL) {
  1132         -	    CompileWord(envPtr, replacementTokenPtr, interp, 4);
         1181  +	if (parsePtr->numWords == 5) {
         1182  +	    tokenPtr = TokenAfter(tokenPtr);
         1183  +	    CompileWord(envPtr, tokenPtr, interp, 4);
  1133   1184   	} else {
  1134   1185   	    PUSH(	"");
  1135   1186   	}
  1136   1187   	OP(		STR_REPLACE);
  1137   1188   	return TCL_OK;
  1138         -    }
  1139   1189   }
  1140   1190   
  1141   1191   int
  1142   1192   TclCompileStringTrimLCmd(
  1143   1193       Tcl_Interp *interp,		/* Used for error reporting. */
  1144   1194       Tcl_Parse *parsePtr,	/* Points to a parse structure for the command
  1145   1195   				 * created by Tcl_ParseCommand. */

Changes to generic/tclExecute.c.

  5208   5208   	    TclNewObj(objResultPtr);
  5209   5209   	}
  5210   5210   	TRACE_APPEND(("%.30s\n", O2S(objResultPtr)));
  5211   5211   	NEXT_INST_F(9, 1, 1);
  5212   5212   
  5213   5213       {
  5214   5214   	Tcl_UniChar *ustring1, *ustring2, *ustring3, *end, *p;
  5215         -	int length3;
         5215  +	int length3, endIdx;
  5216   5216   	Tcl_Obj *value3Ptr;
  5217   5217   
  5218   5218       case INST_STR_REPLACE:
  5219   5219   	value3Ptr = POP_OBJECT();
  5220   5220   	valuePtr = OBJ_AT_DEPTH(2);
  5221         -	length = Tcl_GetCharLength(valuePtr) - 1;
         5221  +	endIdx = Tcl_GetCharLength(valuePtr) - 1;
  5222   5222   	TRACE(("\"%.20s\" %s %s \"%.20s\" => ", O2S(valuePtr),
  5223   5223   		O2S(OBJ_UNDER_TOS), O2S(OBJ_AT_TOS), O2S(value3Ptr)));
  5224         -	if (TclGetIntForIndexM(interp, OBJ_UNDER_TOS, length,
         5224  +	if (TclGetIntForIndexM(interp, OBJ_UNDER_TOS, endIdx,
  5225   5225   		    &fromIdx) != TCL_OK
  5226         -	    || TclGetIntForIndexM(interp, OBJ_AT_TOS, length,
         5226  +	    || TclGetIntForIndexM(interp, OBJ_AT_TOS, endIdx,
  5227   5227   		    &toIdx) != TCL_OK) {
  5228   5228   	    TclDecrRefCount(value3Ptr);
  5229   5229   	    TRACE_ERROR(interp);
  5230   5230   	    goto gotError;
  5231   5231   	}
  5232   5232   	TclDecrRefCount(OBJ_AT_TOS);
  5233   5233   	(void) POP_OBJECT();
  5234   5234   	TclDecrRefCount(OBJ_AT_TOS);
  5235   5235   	(void) POP_OBJECT();
  5236         -	if (fromIdx < 0) {
  5237         -	    fromIdx = 0;
  5238         -	}
  5239   5236   
  5240         -	if (fromIdx > toIdx || fromIdx > length) {
         5237  +	if ((toIdx < 0) ||
         5238  +		(fromIdx > endIdx) ||
         5239  +		(toIdx < fromIdx)) {
  5241   5240   	    TRACE_APPEND(("\"%.30s\"\n", O2S(valuePtr)));
  5242   5241   	    TclDecrRefCount(value3Ptr);
  5243   5242   	    NEXT_INST_F(1, 0, 0);
  5244   5243   	}
  5245   5244   
  5246         -	if (toIdx > length) {
  5247         -	    toIdx = length;
         5245  +	if (fromIdx < 0) {
         5246  +	    fromIdx = 0;
  5248   5247   	}
  5249   5248   
  5250         -	if (fromIdx == 0 && toIdx == length) {
         5249  +	if (toIdx > endIdx) {
         5250  +	    toIdx = endIdx;
         5251  +	}
         5252  +
         5253  +	if (fromIdx == 0 && toIdx == endIdx) {
  5251   5254   	    TclDecrRefCount(OBJ_AT_TOS);
  5252   5255   	    OBJ_AT_TOS = value3Ptr;
  5253   5256   	    TRACE_APPEND(("\"%.30s\"\n", O2S(value3Ptr)));
  5254   5257   	    NEXT_INST_F(1, 0, 0);
  5255   5258   	}
  5256   5259   
  5257   5260   	length3 = Tcl_GetCharLength(value3Ptr);

Changes to library/msgcat/msgcat.tcl.

     1      1   # msgcat.tcl --
     2      2   #
     3      3   #	This file defines various procedures which implement a
     4      4   #	message catalog facility for Tcl programs.  It should be
     5      5   #	loaded with the command "package require msgcat".
     6      6   #
     7         -# Copyright (c) 2010-2015 by Harald Oehlmann.
            7  +# Copyright (c) 2010-2018 by Harald Oehlmann.
     8      8   # Copyright (c) 1998-2000 by Ajuba Solutions.
     9      9   # Copyright (c) 1998 by Mark Harrison.
    10     10   #
    11     11   # See the file "license.terms" for information on usage and redistribution
    12     12   # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
    13     13   
    14     14   # We use oo::define::self, which is new in Tcl 8.7
................................................................................
  1214   1214   # There are 4 possibilities:
  1215   1215   # - called from a proc
  1216   1216   # - called within a class definition script
  1217   1217   # - called from an class defined oo object
  1218   1218   # - called from a classless oo object
  1219   1219   proc ::msgcat::PackageNamespaceGet {} {
  1220   1220       uplevel 2 {
  1221         -	# Check for no object
         1221  +	# Check self namespace to determine environment
  1222   1222   	switch -exact -- [namespace which self] {
  1223   1223   	    {::oo::define::self} {
  1224   1224   		# We are within a class definition
  1225   1225   		return [namespace qualifiers [self]]
  1226   1226   	    }
  1227   1227   	    {::oo::Helpers::self} {
  1228   1228   		# We are within an object
................................................................................
  1256   1256   	    }
  1257   1257   	}
  1258   1258       }
  1259   1259       #
  1260   1260       # On Darwin, fallback to current CFLocale identifier if available.
  1261   1261       #
  1262   1262       if {[info exists ::tcl::mac::locale] && $::tcl::mac::locale ne ""} {
  1263         -	if {![catch { ConvertLocale $::tcl::mac::locale] } locale]} {
         1263  +	if {![catch { ConvertLocale $::tcl::mac::locale } locale]} {
  1264   1264   	    return $locale
  1265   1265   	}
  1266   1266       }
  1267   1267       #
  1268   1268       # The rest of this routine is special processing for Windows or
  1269   1269       # Cygwin. All other platforms, get out now.
  1270   1270       #

Changes to tests/msgcat.test.

    51     51       variable body
    52     52       variable result
    53     53       variable setVars
    54     54       foreach setVars [PowerSet $envVars] {
    55     55   	set result [string tolower [lindex $setVars 0]]
    56     56   	if {[string length $result] == 0} {
    57     57   	    if {[info exists ::tcl::mac::locale]} {
           58  +if {[package vsatisfies [package provide msgcat] 1.7]} {
           59  +		set result [string tolower \
           60  +			[msgcat::mcutil::ConvertLocale $::tcl::mac::locale]]
           61  +} else {
    58     62   		set result [string tolower \
    59     63   			[msgcat::ConvertLocale $::tcl::mac::locale]]
           64  +}
    60     65   	    } else {
    61     66   		if {([info sharedlibextension] eq ".dll")
    62     67   			&& ![catch {package require registry}]} {
    63     68   		    # Windows and Cygwin have other ways to determine the
    64     69   		    # locale when the environment variables are missing
    65     70   		    # and the registry package is present
    66     71   		    continue

Changes to tests/string.test.

  1389   1389   } {foo}
  1390   1390   test string-14.17 {string replace} {
  1391   1391       string replace abcdefghijklmnop end end-1
  1392   1392   } {abcdefghijklmnop}
  1393   1393   test string-14.18 {string replace} {
  1394   1394       string replace abcdefghijklmnop 10 9 XXX
  1395   1395   } {abcdefghijklmnop}
         1396  +test string-14.19 {string replace} {
         1397  +    string replace {} -1 0 A
         1398  +} A
  1396   1399   
  1397   1400   test string-15.1 {string tolower too few args} {
  1398   1401       list [catch {string tolower} msg] $msg
  1399   1402   } {1 {wrong # args: should be "string tolower string ?first? ?last?"}}
  1400   1403   test string-15.2 {string tolower bad args} {
  1401   1404       list [catch {string tolower a b} msg] $msg
  1402   1405   } {1 {bad index "b": must be integer?[+-]integer? or end?[+-]integer?}}