texlive[58684] Master/texmf-dist: babel (24mar21)

commits+karl at tug.org commits+karl at tug.org
Wed Mar 24 22:27:50 CET 2021


Revision: 58684
          http://tug.org/svn/texlive?view=revision&revision=58684
Author:   karl
Date:     2021-03-24 22:27:50 +0100 (Wed, 24 Mar 2021)
Log Message:
-----------
babel (24mar21)

Modified Paths:
--------------
    trunk/Master/texmf-dist/doc/latex/babel/README.md
    trunk/Master/texmf-dist/doc/latex/babel/babel.pdf
    trunk/Master/texmf-dist/source/latex/babel/babel.dtx
    trunk/Master/texmf-dist/source/latex/babel/babel.ins
    trunk/Master/texmf-dist/source/latex/babel/bbcompat.dtx
    trunk/Master/texmf-dist/source/latex/babel/locale.zip
    trunk/Master/texmf-dist/tex/generic/babel/babel.def
    trunk/Master/texmf-dist/tex/generic/babel/babel.sty
    trunk/Master/texmf-dist/tex/generic/babel/hyphen.cfg
    trunk/Master/texmf-dist/tex/generic/babel/locale/es/babel-es.ini
    trunk/Master/texmf-dist/tex/generic/babel/locale/mr/babel-mr.ini
    trunk/Master/texmf-dist/tex/generic/babel/luababel.def
    trunk/Master/texmf-dist/tex/generic/babel/nil.ldf

Modified: trunk/Master/texmf-dist/doc/latex/babel/README.md
===================================================================
--- trunk/Master/texmf-dist/doc/latex/babel/README.md	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/doc/latex/babel/README.md	2021-03-24 21:27:50 UTC (rev 58684)
@@ -1,4 +1,4 @@
-## Babel 3.55
+## Babel 3.56
 
 This package manages culturally-determined typographical (and other)
 rules, and hyphenation patterns for a wide range of languages. Many
@@ -8,9 +8,9 @@
 
 The latest stable version is available on <https://ctan.org/pkg/babel>.
 
-Changes in version 3.55 are described in:
+Changes in version 3.56 are described in:
 
-https://github.com/latex3/babel/blob/master/news-guides/news/whats-new-in-babel-3.55.md
+https://github.com/latex3/babel/blob/master/news-guides/news/whats-new-in-babel-3.56.md
 
 Apart from the manual, you can find information on some aspects of babel at:
 
@@ -46,16 +46,19 @@
 
 ### Summary of Latest changes
 ```
-3.55   2021-03-03??
-       * Captions for Uyghur.
+3.56   2021-03-24
+       * Transforms (\babelprehyphenation, \babelposthyphenation)
+         - Key 'space', in em units.
+         - Key 'spacefactor', based on the current word separation.
+         - Multiple 'insert's allowed.
+         - Definable in 'ini' files.
+         - Arbitrary characters in pattern.
        * Fixes:
-         - Wrong caption in Romanian for Index (#117).
-         - Missing lines in ngermanb.sty (#118).
-       * There is also some tentative code to improve pgfpicture with
-         RTL texts.
+         - 'insert' and 'data' didn't always set/get the expected node.
+         - Hyphen was not shown in Marathi (#123)
 ```
 
 ### Previous changes
 
-See https://github.com/latex3/babel/blob/master/news-guides/news/
+See https://github.com/latex3/babel/blob/master/news-guides/README.md#whats-new
 

Modified: trunk/Master/texmf-dist/doc/latex/babel/babel.pdf
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/source/latex/babel/babel.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/babel/babel.dtx	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/source/latex/babel/babel.dtx	2021-03-24 21:27:50 UTC (rev 58684)
@@ -31,7 +31,7 @@
 %
 % \iffalse
 %<*filedriver>
-\ProvidesFile{babel.dtx}[2021/03/03 v3.55 The Babel package]
+\ProvidesFile{babel.dtx}[2021/03/24 v3.56 The Babel package]
 \documentclass{ltxdoc}
 \GetFileInfo{babel.dtx}
 \usepackage{fontspec}
@@ -750,22 +750,6 @@
 set -- in this case, |\foreignlanguage| emits a |\leavevmode|, while 
 |otherlanguage*| does not.
 
-\Describe{\begin\menv{hyphenrules}}{\marg{language}\Eenv{hyphenrules}}
-
-The environment \Lenv{hyphenrules} can be used to select \emph{only} the
-hyphenation rules to be used (it can be used as command, too). This can
-for instance be used to select `nohyphenation', provided that in
-\file{language.dat} the `language' \textsf{nohyphenation} is defined by
-loading \file{zerohyph.tex}. It deactivates language shorthands, too
-(but not user shorthands).
-
-Except for these simple uses, |hyphenrules| is deprecated and
-|otherlanguage*| (the starred version) is preferred, because the former
-does not take into account possible changes in encodings of characters
-like, say, |'| done by some languages (eg, \textsf{italian},
-\textsf{french}, \textsf{ukraineb}). To set hyphenation exceptions, use
-|\babelhyphenation| (see below).
-
 \subsection{More on selection}
 
 \Describe{\babeltags}{\char`\{\m{tag1} \texttt{=} \m{language1}, \m{tag2}
@@ -2967,6 +2951,28 @@
   language, you can add at least some typical cases.
 \end{note}
 
+\begin{note}
+  To set hyphenation exceptions in the preamble before any language is
+  explicitly set with a selector, use |\babelhyphenation| instead of
+  |\hyphenation|. In the preamble the hyphenation rules are not always
+  fully set up and an error can be raised.
+\end{note}
+
+\Describe{\begin\menv{hyphenrules}}{\marg{language}\Eenv{hyphenrules}}
+
+The environment \Lenv{hyphenrules} can be used to select \emph{only} the
+hyphenation rules to be used (it can be used as command, too). This can
+for instance be used to select `nohyphenation', provided that in
+\file{language.dat} the `language' \textsf{nohyphenation} is defined by
+loading \file{zerohyph.tex}. It deactivates language shorthands, too
+(but not user shorthands).
+
+Except for these simple uses, |hyphenrules| is deprecated and
+|otherlanguage*| (the starred version) is preferred, because the former
+does not take into account possible changes in encodings of characters
+like, say, |'| done by some languages (eg, \textsf{italian},
+\textsf{french}, \textsf{ukraineb}).
+
 \Describe{\babelpatterns}{\texttt{[}\langlist\texttt{]}%
     \marg{patterns}}
 
@@ -3002,6 +3008,18 @@
 of the previous char in \luatex, and the font size set by the last
 |\selectfont| in \xetex).
 
+\subsection{Transforms}
+
+Transforms (only \luatex) provide a way to process the text on the
+typesetting level in several language-dependent ways, like non-standard
+hyphenation, special line breaking rules, script to script conversion,
+spacing conventions and so on.\footnote{They are similar in concept,
+but not the same, as those in Unicode.}
+
+It currently embraces |\babelprehyphenation| and
+|\babelposthyphenation|, which have been available for several months.
+\New{3.56} In this version they can be defined in |ini| files, too.
+
 \Describe{\babelposthyphenation}{\marg{hyphenrules-name}%
           \marg{lua-pattern}\marg{replacement}}
 
@@ -3042,20 +3060,15 @@
 
 \Describe{\babelprehyphenation}{\marg{locale-name}%
           \marg{lua-pattern}\marg{replacement}}
-          
-\New{3.44-3-52} This command is not strictly about hyphenation, but
-it is included here because it is a clear counterpart of
-|\babelposthyphenation|. It is similar to the latter, but (as its name
-implies) applied before hyphenation. There are other differences: (1)
-the first argument is the locale instead the name of hyphenation
-patterns; (2) in the search patterns |=| has no special meaning, while
-\verb+|+ stands for an ordinary space; (3) in the replacement,
-discretionaries are not accepted.
 
-It handles glyphs and spaces (but you can not insert spaces).
+\New{3.44-3-52} It is similar to the latter, but (as its name implies)
+applied before hyphenation. There are other differences: (1) the first
+argument is the locale instead the name of hyphenation patterns; (2) in
+the search patterns |=| has no special meaning, while \verb+|+ stands
+for an ordinary space; (3) in the replacement, discretionaries are not
+accepted.
 
-Performance is still somewhat poor in some cases, but it is fast in the
-typical ones.
+It handles glyphs and spaces.
 
 This feature is activated with the first |\babelposthyphenation| or
 |\babelprehyphenation|.
@@ -4849,8 +4862,8 @@
 % \section{Tools}
 %
 %    \begin{macrocode}
-%<<version=3.55>>
-%<<date=2021/03/03>>
+%<<version=3.56>>
+%<<date=2021/03/24>>
 %    \end{macrocode}
 %
 % \textbf{Do not use the following macros in \texttt{ldf} files. They
@@ -10657,6 +10670,7 @@
   \let\bbl at KVP@intraspace\@nil
   \let\bbl at KVP@intrapenalty\@nil
   \let\bbl at KVP@onchar\@nil
+  \let\bbl at KVP@transforms\@nil
   \let\bbl at KVP@alph\@nil
   \let\bbl at KVP@Alph\@nil
   \let\bbl at KVP@labels\@nil
@@ -10699,6 +10713,10 @@
   \ifx\bbl at KVP@captions\@nil
     \let\bbl at KVP@captions\bbl at KVP@import
   \fi
+  % ==
+  \ifx\bbl at KVP@transforms\@nil\else
+    \bbl at replace\bbl at KVP@transforms{ }{,}%
+  \fi
   % Load ini
   \bbl at ifunset{date#2}%
     {\bbl at provide@new{#2}}%
@@ -11294,6 +11312,8 @@
        \bbl at cs{@kv at identification.warning#1}\\%
        Reported }}}
 %
+\let\bbl at release@transforms\@empty
+%
 \def\bbl at ini@exports#1{%
   % Identification always exported
   \bbl at iniwarning{}%
@@ -11319,6 +11339,8 @@
   \ifbbl at bcptoname
     \bbl at csarg\xdef{bcp at map@\bbl at cl{tbcp}}{\languagename}%
   \fi
+  % Finish here transforms, too
+  \bbl at release@transforms\relax  % \relax closes the last item.
   % Conditional
   \ifnum#1>\z@         % 0 = only info, 1, 2 = basic, (re)new
     \bbl at exportkey{lnbrk}{typography.linebreaking}{h}%
@@ -11676,6 +11698,39 @@
 \def\bbl at xdatecntr[#1|#2]{\localenumeral{#2}{#1}}
 %    \end{macrocode}
 %
+% \textbf{Transforms.}
+%
+%    \begin{macrocode}
+\let\bbl at release@transforms\@empty
+\@namedef{bbl at inikv@transforms.prehyphenation}{%
+  \bbl at transforms\babelprehyphenation}
+\@namedef{bbl at inikv@transforms.posthyphenation}{%
+  \bbl at transforms\babelposthyphenation}
+\def\bbl at transforms@aux#1#2#3,#4\relax{#1{#2}{#3}{#4}}
+\begingroup
+  \catcode`\%=12
+  \catcode`\&=14
+  \gdef\bbl at transforms#1#2#3{&%
+    \ifx\bbl at KVP@transforms\@nil\else
+      \directlua{
+         str = [==[#2]==]
+         str = str:gsub('%.%d+%.%d+$', '')
+         tex.print([[\def\string\babeltempa{]] .. str .. [[}]])
+      }&%
+      \bbl at xin@{,\babeltempa,}{,\bbl at KVP@transforms,}&%
+      \ifin@
+        \in@{.0$}{#2$}&%
+        \ifin@
+            \bbl at add\bbl at release@transforms{&%
+              \relax\bbl at transforms@aux#1{\languagename}{#3}}&%
+          \else
+            \bbl at add\bbl at release@transforms{, {#3}}&%
+        \fi
+      \fi
+    \fi}
+\endgroup
+%    \end{macrocode}
+%
 % Language and Script values to be used when defining a font or
 % setting the direction are set with the following macros.
 %
@@ -13881,6 +13936,31 @@
     Babel.hyphenate_replace(head, 1)
   end
 
+  function Babel.debug_hyph(w, wn, sc, first, last, last_match)
+    local ss = ''
+    for pp = 1, 40 do
+      if wn[pp] then
+        if wn[pp].id == 29 then
+          ss = ss .. unicode.utf8.char(wn[pp].char)
+        else
+          ss = ss .. '{' .. wn[pp].id .. '}'
+        end
+      end
+    end
+    print('nod', ss)
+    print('lst_m',
+      string.rep(' ', unicode.utf8.len(
+         string.sub(w, 1, last_match))-1) .. '>')
+    print('str', w)
+    print('sc', string.rep(' ', sc-1) .. '^')
+    if first == last then
+      print('f=l', string.rep(' ', first-1) .. '!')
+    else
+      print('f/l', string.rep(' ', first-1) .. '[' ..
+        string.rep(' ', last-first-1) .. ']')
+    end
+  end
+
   Babel.us_char = string.char(31)
 
   function Babel.hyphenate_replace(head, mode)
@@ -13891,11 +13971,11 @@
 
     while true do  &% for each subtext block
 
-      local w, wn, nw, lang = Babel.fetch_subtext[mode](word_head)
+      local w, w_nodes, nw, lang = Babel.fetch_subtext[mode](word_head)
 
       if Babel.debug then
         print()
-        print('@@@@@', w, nw)
+        print((mode == 0) and '@@@@<' or '@@@@>', w)
       end
 
       if nw == nil and w == '' then break end
@@ -13906,11 +13986,11 @@
       &% For each saved (pre|post)hyphenation. TODO. Reconsider how
       &% loops are nested.
       for k=1, #lbkr[lang] do
-        local p = lbkr[lang][k].pattern 
+        local p = lbkr[lang][k].pattern
         local r = lbkr[lang][k].replace
 
         if Babel.debug then
-          print('=====', p, mode)
+          print('*****', p, mode)
         end
 
         &% This variable is set in some cases below to the first *byte*
@@ -13918,10 +13998,10 @@
         &% computed position based on sc if w has changed.
         local last_match = 0
 
-        &% For every match. 
+        &% For every match.
         while true do
           if Babel.debug then
-            print('-----')
+            print('=====')
           end
           local new  &% used when inserting and removing nodes
           local refetch = false
@@ -13933,7 +14013,7 @@
           &% number with the position), and keep actual captures
           &% (from (...)), if any, in matches.
           local first = table.remove(matches, 1)
-          local last  = table.remove(matches, #matches)         
+          local last  = table.remove(matches, #matches)
           &% Non re-fetched substrings may contain \31, which separates
           &% subsubstrings.
           if string.find(w:sub(first, last-1), Babel.us_char) then break end
@@ -13944,9 +14024,15 @@
           first = u.len(w:sub(1, first-1)) + 1
           last  = u.len(w:sub(1, last-1)) &% now last points to C
 
-          if Babel.debug then
-            print(p)
-            print('', 'sc', 'first', 'last', 'last_m', 'w')
+          &% This loop stores in n small table the nodes
+          &% corresponding to the pattern. Used by 'data' to provide a
+          &% predictable behavior with 'insert' (now w_nodes is modified on
+          &% the fly), and also access to 'remove'd nodes.
+          local sc = first-1           &% Used below, too
+          local data_nodes = {}
+
+          for q = 1, last-first+1 do
+            data_nodes[q] = w_nodes[sc+q]
           end
 
           &% This loop traverses the matched substring and takes the
@@ -13953,117 +14039,156 @@
           &% corresponding action stored in the replacement list.
           &% sc = the position in substr nodes / string
           &% rc = the replacement table index
-          local sc = first-1
           local rc = 0
+
           while rc < last-first+1 do &% for each replacement
             if Babel.debug then
-              print('.....')
+              print('.....', rc + 1)
             end
             sc = sc + 1
             rc = rc + 1
+
+            if Babel.debug then
+              Babel.debug_hyph(w, w_nodes, sc, first, last, last_match)
+            end
+
             local crep = r[rc]
-            local char_node = wn[sc]
-            local char_base = char_node
-            local end_replacement = false
+            local item = w_nodes[sc]
+            local item_base = item
+            local placeholder = Babel.us_char
+            local d
 
             if crep and crep.data then
-              char_base = wn[crep.data+first-1]
+              item_base = data_nodes[crep.data]
             end
 
-            if Babel.debug then
-              print('*', sc, first, last, last_match, w)
-            end
+            if crep and next(crep) == nil then &% = {}
+              last_match = save_last    &% Optimization
+              goto next
 
-            if crep and next(crep) == nil then &% {}
-              last_match = save_last
-
-            elseif crep == nil then &% remove
-              node.remove(head, char_node)
-              table.remove(wn, sc)
+            elseif crep == nil or crep.remove then
+              node.remove(head, item)
+              table.remove(w_nodes, sc)
               w = u.sub(w, 1, sc-1) .. u.sub(w, sc+1)
-              last_match = utf8.offset(w, sc)
-              sc = sc - 1  &% Nothing has been inserted
+              sc = sc - 1  &% Nothing has been inserted.
+              last_match = utf8.offset(w, sc+1)
+              goto next
 
+            elseif crep and crep.string then
+              local str = crep.string(matches)
+              if str == '' then  &% Gather with nil
+                node.remove(head, item)
+                table.remove(w_nodes, sc)
+                w = u.sub(w, 1, sc-1) .. u.sub(w, sc+1)
+                sc = sc - 1  &% Nothing has been inserted.
+              else
+                local loop_first = true
+                for s in string.utfvalues(str) do
+                  d = node.copy(item_base)
+                  d.char = s
+                  if loop_first then
+                    loop_first = false
+                    head, new = node.insert_before(head, item, d)
+                    if sc == 1 then
+                      word_head = head
+                    end
+                    w_nodes[sc] = d
+                    w = u.sub(w, 1, sc-1) .. u.char(s) .. u.sub(w, sc+1)
+                  else
+                    sc = sc + 1
+                    head, new = node.insert_before(head, item, d)
+                    table.insert(w_nodes, sc, new)
+                    w = u.sub(w, 1, sc-1) .. u.char(s) .. u.sub(w, sc)
+                  end
+                  if Babel.debug then
+                    print('.....', 'str')
+                    Babel.debug_hyph(w, w_nodes, sc, first, last, last_match)
+                  end
+                end  &% for
+                node.remove(head, item)
+              end  &% if ''
+              last_match = utf8.offset(w, sc+1)
+              goto next
+
             elseif mode == 1 and crep and (crep.pre or crep.no or crep.post) then
-              local d = node.new(7, 0)   &% (disc, discretionary)
-              d.pre     = Babel.str_to_nodes(crep.pre, matches, char_base)
-              d.post    = Babel.str_to_nodes(crep.post, matches, char_base)
-              d.replace = Babel.str_to_nodes(crep.no, matches, char_base)
-              d.attr = char_base.attr
+              d = node.new(7, 0)   &% (disc, discretionary)
+              d.pre     = Babel.str_to_nodes(crep.pre, matches, item_base)
+              d.post    = Babel.str_to_nodes(crep.post, matches, item_base)
+              d.replace = Babel.str_to_nodes(crep.no, matches, item_base)
+              d.attr = item_base.attr
               if crep.pre == nil then  &% TeXbook p96
                 d.penalty = crep.penalty or tex.hyphenpenalty
               else
                 d.penalty = crep.penalty or tex.exhyphenpenalty
               end
-              head, new = node.insert_before(head, char_node, d)
-              end_replacement = true
+              placeholder = '|'
+              head, new = node.insert_before(head, item, d)
 
+            elseif mode == 0 and crep and (crep.pre or crep.no or crep.post) then
+              &% ERROR
+
             elseif crep and crep.penalty then
-              local d = node.new(14, 0)   &% (penalty, userpenalty)
-              d.attr = char_base.attr
+              d = node.new(14, 0)   &% (penalty, userpenalty)
+              d.attr = item_base.attr
               d.penalty = crep.penalty
-              head, new = node.insert_before(head, char_node, d)
-              end_replacement = true
+              head, new = node.insert_before(head, item, d)
 
-            elseif crep and crep.string then
-              local str = crep.string(matches)
-              if str == '' then  &% Gather with nil 
-                refetch = true
-                if sc == 1 then
-                  word_head = char_node.next
-                end
-                head, new = node.remove(head, char_node)
-              elseif char_node.id == 29 and u.len(str) == 1 then
-                char_node.char = string.utfvalue(str)
-                w = u.sub(w, 1, sc-1) .. str .. u.sub(w, sc+1)
-                last_match = utf8.offset(w, sc+1)
-              else          
-                refetch = true
-                local n
-                for s in string.utfvalues(str) do
-                  if char_node.id == 7 then
-                    &% TODO. Remove this limitation.
-                    texio.write_nl('Automatic hyphens cannot be replaced, just removed.')
-                  else
-                    n = node.copy(char_base)
-                  end
-                  n.char = s
-                  if sc == 1 then
-                    head, new = node.insert_before(head, char_node, n)
-                    word_head = new
-                  else 
-                    node.insert_before(head, char_node, n)
-                  end
-                end
-                node.remove(head, char_node)
-              end  &% string length
-            end  &% if char and char.string (ie replacement cases)
+            elseif crep and crep.space then
+              &% 655360 = 10 pt = 10 * 65536 sp
+              d = node.new(12, 13)      &% (glue, spaceskip)
+              local quad = font.getfont(item_base.font).size or 655360
+              node.setglue(d, crep.space[1] * quad,
+                              crep.space[2] * quad,
+                              crep.space[3] * quad)
+              if mode == 0 then
+                placeholder = '|'
+              end
+              head, new = node.insert_before(head, item, d)
 
-            &% Shared by disc and penalty.
-            if end_replacement then
-              if sc == 1 then
-                word_head = new
+            elseif crep and crep.spacefactor then
+              d = node.new(12, 13)      &% (glue, spaceskip)
+              local base_font = font.getfont(item_base.font)
+              node.setglue(d,
+                crep.spacefactor[1] * base_font.parameters['space'],
+                crep.spacefactor[2] * base_font.parameters['space_stretch'],
+                crep.spacefactor[3] * base_font.parameters['space_shrink'])
+              if mode == 0 then
+                placeholder = '|'
               end
-              if crep.insert then
-                last_match = save_last
-              else
-                node.remove(head, char_node)
-                w = u.sub(w, 1, sc-1) .. Babel.us_char .. u.sub(w, sc+1)
-                last_match = utf8.offset(w, sc)
-              end
+              head, new = node.insert_before(head, item, d)
+
+            elseif mode == 0 and crep and crep.space then
+              &% ERROR
+
+            end  &% ie replacement cases
+
+            &% Shared by disc, space and penalty. 
+            if sc == 1 then
+              word_head = head
             end
+            if crep.insert then
+              w = u.sub(w, 1, sc-1) .. placeholder .. u.sub(w, sc)
+              table.insert(w_nodes, sc, new)
+              last = last + 1
+            else
+              w_nodes[sc] = d
+              node.remove(head, item)
+              w = u.sub(w, 1, sc-1) .. placeholder .. u.sub(w, sc+1)
+            end
+
+            last_match = utf8.offset(w, sc+1)
+
+            ::next::
+
           end  &% for each replacement
 
           if Babel.debug then
-            print('/', sc, first, last, last_match, w)
+              print('.....', '/')
+              Babel.debug_hyph(w, w_nodes, sc, first, last, last_match)
           end
 
-          &% TODO. refetch will be eventually unnecesary. 
-          if refetch then
-            w, wn, nw, lang = Babel.fetch_subtext[mode](word_head)
-          end              
+        end  &% for match
 
-        end  &% for match
       end  &% for patterns
 
       ::next::
@@ -14128,11 +14253,14 @@
   \begingroup
     \def\babeltempa{\bbl at add@list\babeltempb}&%
     \let\babeltempb\@empty
-    \bbl at foreach{#3}{&%
+    \def\bbl at tempa{#3}&% TODO. Ugly trick to preserve {}:
+    \bbl at replace\bbl at tempa{,}{ ,}&%
+    \expandafter\bbl at foreach\expandafter{\bbl at tempa}{&%
       \bbl at ifsamestring{##1}{remove}&%
         {\bbl at add@list\babeltempb{nil}}&%
         {\directlua{
-           local rep = [[##1]]     
+           local rep = [=[##1]=]             
+           rep = rep:gsub('^%s*(remove)%s*$', 'remove = true')
            rep = rep:gsub('^%s*(insert)%s*,', 'insert = true, ')
            rep = rep:gsub(    '(no)%s*=%s*([^%s,]*)', Babel.capture_func)
            rep = rep:gsub(   '(pre)%s*=%s*([^%s,]*)', Babel.capture_func)
@@ -14143,6 +14271,7 @@
     \directlua{
       local lbkr = Babel.linebreaking.replacements[1]
       local u = unicode.utf8
+      local id = \the\csname l@#1\endcsname
       &% Convert pattern:
       local patt = string.gsub([==[#2]==], '%s', '')
       if not u.find(patt, '()', nil, true) then
@@ -14151,12 +14280,15 @@
       patt = string.gsub(patt, '%(%)%^', '^()')
       patt = string.gsub(patt, '%$%(%)', '()$')
       patt = u.gsub(patt, '{(.)}', 
-                function (n)
-                  return '%' .. (tonumber(n) and (tonumber(n)+1) or n)
-                end)
-      lbkr[\the\csname l@#1\endcsname] = lbkr[\the\csname l@#1\endcsname] or {}
-      table.insert(lbkr[\the\csname l@#1\endcsname],
-                   { pattern = patt, replace = { \babeltempb } })
+             function (n)
+               return '%' .. (tonumber(n) and (tonumber(n)+1) or n)
+             end)
+      patt = u.gsub(patt, '{(%x%x%x%x+)}', 
+             function (n)
+               return u.gsub(u.char(tonumber(n, 16)), '(%p)', '%%%1')
+             end)
+      lbkr[id] = lbkr[id] or {}
+      table.insert(lbkr[id], { pattern = patt, replace = { \babeltempb } })
     }&%
   \endgroup}
 % TODO. Copypaste pattern. 
@@ -14165,18 +14297,26 @@
   \begingroup
     \def\babeltempa{\bbl at add@list\babeltempb}&%
     \let\babeltempb\@empty
-    \bbl at foreach{#3}{&%
+    \def\bbl at tempa{#3}&% TODO. Ugly trick to preserve {}:
+    \bbl at replace\bbl at tempa{,}{ ,}&%
+    \expandafter\bbl at foreach\expandafter{\bbl at tempa}{&%
       \bbl at ifsamestring{##1}{remove}&%
         {\bbl at add@list\babeltempb{nil}}&%
         {\directlua{
-           local rep = [[##1]]
+           local rep = [=[##1]=]
+           rep = rep:gsub('^%s*(remove)%s*$', 'remove = true')
            rep = rep:gsub('^%s*(insert)%s*,', 'insert = true, ')
            rep = rep:gsub('(string)%s*=%s*([^%s,]*)', Babel.capture_func)
+           rep = rep:gsub( '(space)%s*=%s*([%d%.]+)%s+([%d%.]+)%s+([%d%.]+)',
+             'space = {' .. '%2, %3, %4' .. '}')
+           rep = rep:gsub( '(spacefactor)%s*=%s*([%d%.]+)%s+([%d%.]+)%s+([%d%.]+)',
+             'spacefactor = {' .. '%2, %3, %4' .. '}')
            tex.print([[\string\babeltempa{{]] .. rep .. [[}}]])
          }}}&%
     \directlua{
       local lbkr = Babel.linebreaking.replacements[0]
       local u = unicode.utf8
+      local id = \the\csname bbl at id@@#1\endcsname
       &% Convert pattern:
       local patt = string.gsub([==[#2]==], '%s', '')
       if not u.find(patt, '()', nil, true) then
@@ -14185,12 +14325,15 @@
       &% patt = string.gsub(patt, '%(%)%^', '^()')
       &% patt = string.gsub(patt, '([^%%])%$%(%)', '%1()$')
       patt = u.gsub(patt, '{(.)}', 
-                function (n)
-                  return '%' .. (tonumber(n) and (tonumber(n)+1) or n)
-                end)
-      lbkr[\the\csname bbl at id@@#1\endcsname] = lbkr[\the\csname  bbl at id@@#1\endcsname] or {}
-      table.insert(lbkr[\the\csname bbl at id@@#1\endcsname],
-                   { pattern = patt, replace = { \babeltempb } })
+             function (n)
+               return '%' .. (tonumber(n) and (tonumber(n)+1) or n)
+             end)
+      patt = u.gsub(patt, '{(%x%x%x%x+)}', 
+             function (n)
+               return u.gsub(u.char(tonumber(n, 16)), '(%p)', '%%%1')
+             end)
+      lbkr[id] = lbkr[id] or {}
+      table.insert(lbkr[id], { pattern = patt, replace = { \babeltempb } })
     }&%
   \endgroup}
 \endgroup
@@ -21080,13 +21223,13 @@
       first_et = nil
       has_en = false
     end
-    
+
     -- Force mathdir in math if ON (currently works as expected only
     -- with 'l')
     if inmath and d == 'on' then
       d = ('TRT' == tex.mathdir) and 'r' or 'l'
     end
-      
+
     if d then
       if d == 'al' then
         d = 'r'

Modified: trunk/Master/texmf-dist/source/latex/babel/babel.ins
===================================================================
--- trunk/Master/texmf-dist/source/latex/babel/babel.ins	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/source/latex/babel/babel.ins	2021-03-24 21:27:50 UTC (rev 58684)
@@ -26,7 +26,7 @@
 %% and covered by LPPL is defined by the unpacking scripts (with
 %% extension .ins) which are part of the distribution.
 %%
-\def\filedate{2021/03/03}
+\def\filedate{2021/03/24}
 \def\batchfile{babel.ins}
 \input docstrip.tex
 

Modified: trunk/Master/texmf-dist/source/latex/babel/bbcompat.dtx
===================================================================
--- trunk/Master/texmf-dist/source/latex/babel/bbcompat.dtx	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/source/latex/babel/bbcompat.dtx	2021-03-24 21:27:50 UTC (rev 58684)
@@ -30,7 +30,7 @@
 %
 % \iffalse
 %<*dtx>
-\ProvidesFile{bbcompat.dtx}[2021/03/03 v3.55]
+\ProvidesFile{bbcompat.dtx}[2021/03/24 v3.56]
 %</dtx>
 %
 %% File 'bbcompat.dtx'

Modified: trunk/Master/texmf-dist/source/latex/babel/locale.zip
===================================================================
(Binary files differ)

Modified: trunk/Master/texmf-dist/tex/generic/babel/babel.def
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/babel.def	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/tex/generic/babel/babel.def	2021-03-24 21:27:50 UTC (rev 58684)
@@ -39,7 +39,7 @@
     \wlog{File: #1 #4 #3 <#2>}%
     \let\ProvidesFile\@undefined}
 \fi
-\ProvidesFile{babel.def}[2021/03/03 3.55 Babel common definitions]
+\ProvidesFile{babel.def}[2021/03/24 3.56 Babel common definitions]
 \ifx\AtBeginDocument\@undefined  % TODO. change test.
     % == Code for plain ==
 \def\@empty{}
@@ -397,8 +397,8 @@
 \fi
 \countdef\last at language=19  % TODO. why? remove?
 \def\addlanguage{\csname newlanguage\endcsname}
-\def\bbl at version{3.55}
-\def\bbl at date{2021/03/03}
+\def\bbl at version{3.56}
+\def\bbl at date{2021/03/24}
 \def\adddialect#1#2{%
   \global\chardef#1#2\relax
   \bbl at usehooks{adddialect}{{#1}{#2}}%
@@ -2335,6 +2335,7 @@
   \let\bbl at KVP@intraspace\@nil
   \let\bbl at KVP@intrapenalty\@nil
   \let\bbl at KVP@onchar\@nil
+  \let\bbl at KVP@transforms\@nil
   \let\bbl at KVP@alph\@nil
   \let\bbl at KVP@Alph\@nil
   \let\bbl at KVP@labels\@nil
@@ -2377,6 +2378,10 @@
   \ifx\bbl at KVP@captions\@nil
     \let\bbl at KVP@captions\bbl at KVP@import
   \fi
+  % ==
+  \ifx\bbl at KVP@transforms\@nil\else
+    \bbl at replace\bbl at KVP@transforms{ }{,}%
+  \fi
   % Load ini
   \bbl at ifunset{date#2}%
     {\bbl at provide@new{#2}}%
@@ -2893,6 +2898,7 @@
        From babel-\bbl at cs{lini@\languagename}.ini:\\%
        \bbl at cs{@kv at identification.warning#1}\\%
        Reported }}}
+\let\bbl at release@transforms\@empty
 \def\bbl at ini@exports#1{%
   % Identification always exported
   \bbl at iniwarning{}%
@@ -2918,6 +2924,8 @@
   \ifbbl at bcptoname
     \bbl at csarg\xdef{bcp at map@\bbl at cl{tbcp}}{\languagename}%
   \fi
+  % Finish here transforms, too
+  \bbl at release@transforms\relax  % \relax closes the last item.
   % Conditional
   \ifnum#1>\z@         % 0 = only info, 1, 2 = basic, (re)new
     \bbl at exportkey{lnbrk}{typography.linebreaking}{h}%
@@ -3208,6 +3216,34 @@
   \bbl at replace@finish at iii\bbl at toreplace}
 \def\bbl at datecntr{\expandafter\bbl at xdatecntr\expandafter}
 \def\bbl at xdatecntr[#1|#2]{\localenumeral{#2}{#1}}
+\let\bbl at release@transforms\@empty
+\@namedef{bbl at inikv@transforms.prehyphenation}{%
+  \bbl at transforms\babelprehyphenation}
+\@namedef{bbl at inikv@transforms.posthyphenation}{%
+  \bbl at transforms\babelposthyphenation}
+\def\bbl at transforms@aux#1#2#3,#4\relax{#1{#2}{#3}{#4}}
+\begingroup
+  \catcode`\%=12
+  \catcode`\&=14
+  \gdef\bbl at transforms#1#2#3{&%
+    \ifx\bbl at KVP@transforms\@nil\else
+      \directlua{
+         str = [==[#2]==]
+         str = str:gsub('%.%d+%.%d+$', '')
+         tex.print([[\def\string\babeltempa{]] .. str .. [[}]])
+      }&%
+      \bbl at xin@{,\babeltempa,}{,\bbl at KVP@transforms,}&%
+      \ifin@
+        \in@{.0$}{#2$}&%
+        \ifin@
+            \bbl at add\bbl at release@transforms{&%
+              \relax\bbl at transforms@aux#1{\languagename}{#3}}&%
+          \else
+            \bbl at add\bbl at release@transforms{, {#3}}&%
+        \fi
+      \fi
+    \fi}
+\endgroup
 \def\bbl at provide@lsys#1{%
   \bbl at ifunset{bbl at lname@#1}%
     {\bbl at load@info{#1}}%

Modified: trunk/Master/texmf-dist/tex/generic/babel/babel.sty
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/babel.sty	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/tex/generic/babel/babel.sty	2021-03-24 21:27:50 UTC (rev 58684)
@@ -33,7 +33,7 @@
 %%
 
 \NeedsTeXFormat{LaTeX2e}[2005/12/01]
-\ProvidesPackage{babel}[2021/03/03 3.55 The Babel package]
+\ProvidesPackage{babel}[2021/03/24 3.56 The Babel package]
 \@ifpackagewith{babel}{debug}
   {\providecommand\bbl at trace[1]{\message{^^J[ #1 ]}}%
    \let\bbl at debug\@firstofone

Modified: trunk/Master/texmf-dist/tex/generic/babel/hyphen.cfg
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/hyphen.cfg	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/tex/generic/babel/hyphen.cfg	2021-03-24 21:27:50 UTC (rev 58684)
@@ -37,10 +37,10 @@
     \wlog{File: #1 #4 #3 <#2>}%
     \let\ProvidesFile\@undefined}
 \fi
-\ProvidesFile{hyphen.cfg}[2021/03/03 3.55 Babel hyphens]
+\ProvidesFile{hyphen.cfg}[2021/03/24 3.56 Babel hyphens]
 \xdef\bbl at format{\jobname}
-\def\bbl at version{3.55}
-\def\bbl at date{2021/03/03}
+\def\bbl at version{3.56}
+\def\bbl at date{2021/03/24}
 \ifx\AtBeginDocument\@undefined
   \def\@empty{}
   \let\orig at dump\dump

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/es/babel-es.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/es/babel-es.ini	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/es/babel-es.ini	2021-03-24 21:27:50 UTC (rev 58684)
@@ -183,3 +183,10 @@
 superscriptingExponent = ×
 
 [counters]
+
+[transforms.prehyphenation]
+; Experimental. Don't use in production
+percent.space.1.0 = { {d}{0025} }
+percent.space.1.1 = {}
+percent.space.1.2 = { insert, spacefactor = .5 .3 .5, data = 1 }
+percent.space.1.3 = {}

Modified: trunk/Master/texmf-dist/tex/generic/babel/locale/mr/babel-mr.ini
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/locale/mr/babel-mr.ini	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/tex/generic/babel/locale/mr/babel-mr.ini	2021-03-24 21:27:50 UTC (rev 58684)
@@ -13,8 +13,8 @@
 
 [identification]
 charset = utf8
-version = 1.8
-date = 2020-10-11
+version = 1.9
+date = 2021-03-16
 name.local = मराठी
 name.english = Marathi
 name.babel = marathi
@@ -118,7 +118,7 @@
 lefthyphenmin = 2
 righthyphenmin = 2
 hyphenchar = 
-prehyphenchar = 0
+prehyphenchar =
 posthyphenchar = 
 exhyphenchar = 
 preexhyphenchar = 

Modified: trunk/Master/texmf-dist/tex/generic/babel/luababel.def
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/luababel.def	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/tex/generic/babel/luababel.def	2021-03-24 21:27:50 UTC (rev 58684)
@@ -1046,6 +1046,31 @@
     Babel.hyphenate_replace(head, 1)
   end
 
+  function Babel.debug_hyph(w, wn, sc, first, last, last_match)
+    local ss = ''
+    for pp = 1, 40 do
+      if wn[pp] then
+        if wn[pp].id == 29 then
+          ss = ss .. unicode.utf8.char(wn[pp].char)
+        else
+          ss = ss .. '{' .. wn[pp].id .. '}'
+        end
+      end
+    end
+    print('nod', ss)
+    print('lst_m',
+      string.rep(' ', unicode.utf8.len(
+         string.sub(w, 1, last_match))-1) .. '>')
+    print('str', w)
+    print('sc', string.rep(' ', sc-1) .. '^')
+    if first == last then
+      print('f=l', string.rep(' ', first-1) .. '!')
+    else
+      print('f/l', string.rep(' ', first-1) .. '[' ..
+        string.rep(' ', last-first-1) .. ']')
+    end
+  end
+
   Babel.us_char = string.char(31)
 
   function Babel.hyphenate_replace(head, mode)
@@ -1056,11 +1081,11 @@
 
     while true do  &% for each subtext block
 
-      local w, wn, nw, lang = Babel.fetch_subtext[mode](word_head)
+      local w, w_nodes, nw, lang = Babel.fetch_subtext[mode](word_head)
 
       if Babel.debug then
         print()
-        print('@@@@@', w, nw)
+        print((mode == 0) and '@@@@<' or '@@@@>', w)
       end
 
       if nw == nil and w == '' then break end
@@ -1075,7 +1100,7 @@
         local r = lbkr[lang][k].replace
 
         if Babel.debug then
-          print('=====', p, mode)
+          print('*****', p, mode)
         end
 
         &% This variable is set in some cases below to the first *byte*
@@ -1086,7 +1111,7 @@
         &% For every match.
         while true do
           if Babel.debug then
-            print('-----')
+            print('=====')
           end
           local new  &% used when inserting and removing nodes
           local refetch = false
@@ -1109,9 +1134,15 @@
           first = u.len(w:sub(1, first-1)) + 1
           last  = u.len(w:sub(1, last-1)) &% now last points to C
 
-          if Babel.debug then
-            print(p)
-            print('', 'sc', 'first', 'last', 'last_m', 'w')
+          &% This loop stores in n small table the nodes
+          &% corresponding to the pattern. Used by 'data' to provide a
+          &% predictable behavior with 'insert' (now w_nodes is modified on
+          &% the fly), and also access to 'remove'd nodes.
+          local sc = first-1           &% Used below, too
+          local data_nodes = {}
+
+          for q = 1, last-first+1 do
+            data_nodes[q] = w_nodes[sc+q]
           end
 
           &% This loop traverses the matched substring and takes the
@@ -1118,117 +1149,156 @@
           &% corresponding action stored in the replacement list.
           &% sc = the position in substr nodes / string
           &% rc = the replacement table index
-          local sc = first-1
           local rc = 0
+
           while rc < last-first+1 do &% for each replacement
             if Babel.debug then
-              print('.....')
+              print('.....', rc + 1)
             end
             sc = sc + 1
             rc = rc + 1
+
+            if Babel.debug then
+              Babel.debug_hyph(w, w_nodes, sc, first, last, last_match)
+            end
+
             local crep = r[rc]
-            local char_node = wn[sc]
-            local char_base = char_node
-            local end_replacement = false
+            local item = w_nodes[sc]
+            local item_base = item
+            local placeholder = Babel.us_char
+            local d
 
             if crep and crep.data then
-              char_base = wn[crep.data+first-1]
+              item_base = data_nodes[crep.data]
             end
 
-            if Babel.debug then
-              print('*', sc, first, last, last_match, w)
-            end
+            if crep and next(crep) == nil then &% = {}
+              last_match = save_last    &% Optimization
+              goto next
 
-            if crep and next(crep) == nil then &% {}
-              last_match = save_last
-
-            elseif crep == nil then &% remove
-              node.remove(head, char_node)
-              table.remove(wn, sc)
+            elseif crep == nil or crep.remove then
+              node.remove(head, item)
+              table.remove(w_nodes, sc)
               w = u.sub(w, 1, sc-1) .. u.sub(w, sc+1)
-              last_match = utf8.offset(w, sc)
-              sc = sc - 1  &% Nothing has been inserted
+              sc = sc - 1  &% Nothing has been inserted.
+              last_match = utf8.offset(w, sc+1)
+              goto next
 
+            elseif crep and crep.string then
+              local str = crep.string(matches)
+              if str == '' then  &% Gather with nil
+                node.remove(head, item)
+                table.remove(w_nodes, sc)
+                w = u.sub(w, 1, sc-1) .. u.sub(w, sc+1)
+                sc = sc - 1  &% Nothing has been inserted.
+              else
+                local loop_first = true
+                for s in string.utfvalues(str) do
+                  d = node.copy(item_base)
+                  d.char = s
+                  if loop_first then
+                    loop_first = false
+                    head, new = node.insert_before(head, item, d)
+                    if sc == 1 then
+                      word_head = head
+                    end
+                    w_nodes[sc] = d
+                    w = u.sub(w, 1, sc-1) .. u.char(s) .. u.sub(w, sc+1)
+                  else
+                    sc = sc + 1
+                    head, new = node.insert_before(head, item, d)
+                    table.insert(w_nodes, sc, new)
+                    w = u.sub(w, 1, sc-1) .. u.char(s) .. u.sub(w, sc)
+                  end
+                  if Babel.debug then
+                    print('.....', 'str')
+                    Babel.debug_hyph(w, w_nodes, sc, first, last, last_match)
+                  end
+                end  &% for
+                node.remove(head, item)
+              end  &% if ''
+              last_match = utf8.offset(w, sc+1)
+              goto next
+
             elseif mode == 1 and crep and (crep.pre or crep.no or crep.post) then
-              local d = node.new(7, 0)   &% (disc, discretionary)
-              d.pre     = Babel.str_to_nodes(crep.pre, matches, char_base)
-              d.post    = Babel.str_to_nodes(crep.post, matches, char_base)
-              d.replace = Babel.str_to_nodes(crep.no, matches, char_base)
-              d.attr = char_base.attr
+              d = node.new(7, 0)   &% (disc, discretionary)
+              d.pre     = Babel.str_to_nodes(crep.pre, matches, item_base)
+              d.post    = Babel.str_to_nodes(crep.post, matches, item_base)
+              d.replace = Babel.str_to_nodes(crep.no, matches, item_base)
+              d.attr = item_base.attr
               if crep.pre == nil then  &% TeXbook p96
                 d.penalty = crep.penalty or tex.hyphenpenalty
               else
                 d.penalty = crep.penalty or tex.exhyphenpenalty
               end
-              head, new = node.insert_before(head, char_node, d)
-              end_replacement = true
+              placeholder = '|'
+              head, new = node.insert_before(head, item, d)
 
+            elseif mode == 0 and crep and (crep.pre or crep.no or crep.post) then
+              &% ERROR
+
             elseif crep and crep.penalty then
-              local d = node.new(14, 0)   &% (penalty, userpenalty)
-              d.attr = char_base.attr
+              d = node.new(14, 0)   &% (penalty, userpenalty)
+              d.attr = item_base.attr
               d.penalty = crep.penalty
-              head, new = node.insert_before(head, char_node, d)
-              end_replacement = true
+              head, new = node.insert_before(head, item, d)
 
-            elseif crep and crep.string then
-              local str = crep.string(matches)
-              if str == '' then  &% Gather with nil
-                refetch = true
-                if sc == 1 then
-                  word_head = char_node.next
-                end
-                head, new = node.remove(head, char_node)
-              elseif char_node.id == 29 and u.len(str) == 1 then
-                char_node.char = string.utfvalue(str)
-                w = u.sub(w, 1, sc-1) .. str .. u.sub(w, sc+1)
-                last_match = utf8.offset(w, sc+1)
-              else
-                refetch = true
-                local n
-                for s in string.utfvalues(str) do
-                  if char_node.id == 7 then
-                    &% TODO. Remove this limitation.
-                    texio.write_nl('Automatic hyphens cannot be replaced, just removed.')
-                  else
-                    n = node.copy(char_base)
-                  end
-                  n.char = s
-                  if sc == 1 then
-                    head, new = node.insert_before(head, char_node, n)
-                    word_head = new
-                  else
-                    node.insert_before(head, char_node, n)
-                  end
-                end
-                node.remove(head, char_node)
-              end  &% string length
-            end  &% if char and char.string (ie replacement cases)
+            elseif crep and crep.space then
+              &% 655360 = 10 pt = 10 * 65536 sp
+              d = node.new(12, 13)      &% (glue, spaceskip)
+              local quad = font.getfont(item_base.font).size or 655360
+              node.setglue(d, crep.space[1] * quad,
+                              crep.space[2] * quad,
+                              crep.space[3] * quad)
+              if mode == 0 then
+                placeholder = '|'
+              end
+              head, new = node.insert_before(head, item, d)
 
-            &% Shared by disc and penalty.
-            if end_replacement then
-              if sc == 1 then
-                word_head = new
+            elseif crep and crep.spacefactor then
+              d = node.new(12, 13)      &% (glue, spaceskip)
+              local base_font = font.getfont(item_base.font)
+              node.setglue(d,
+                crep.spacefactor[1] * base_font.parameters['space'],
+                crep.spacefactor[2] * base_font.parameters['space_stretch'],
+                crep.spacefactor[3] * base_font.parameters['space_shrink'])
+              if mode == 0 then
+                placeholder = '|'
               end
-              if crep.insert then
-                last_match = save_last
-              else
-                node.remove(head, char_node)
-                w = u.sub(w, 1, sc-1) .. Babel.us_char .. u.sub(w, sc+1)
-                last_match = utf8.offset(w, sc)
-              end
+              head, new = node.insert_before(head, item, d)
+
+            elseif mode == 0 and crep and crep.space then
+              &% ERROR
+
+            end  &% ie replacement cases
+
+            &% Shared by disc, space and penalty.
+            if sc == 1 then
+              word_head = head
             end
+            if crep.insert then
+              w = u.sub(w, 1, sc-1) .. placeholder .. u.sub(w, sc)
+              table.insert(w_nodes, sc, new)
+              last = last + 1
+            else
+              w_nodes[sc] = d
+              node.remove(head, item)
+              w = u.sub(w, 1, sc-1) .. placeholder .. u.sub(w, sc+1)
+            end
+
+            last_match = utf8.offset(w, sc+1)
+
+            ::next::
+
           end  &% for each replacement
 
           if Babel.debug then
-            print('/', sc, first, last, last_match, w)
+              print('.....', '/')
+              Babel.debug_hyph(w, w_nodes, sc, first, last, last_match)
           end
 
-          &% TODO. refetch will be eventually unnecesary.
-          if refetch then
-            w, wn, nw, lang = Babel.fetch_subtext[mode](word_head)
-          end
+        end  &% for match
 
-        end  &% for match
       end  &% for patterns
 
       ::next::
@@ -1276,11 +1346,14 @@
   \begingroup
     \def\babeltempa{\bbl at add@list\babeltempb}&%
     \let\babeltempb\@empty
-    \bbl at foreach{#3}{&%
+    \def\bbl at tempa{#3}&% TODO. Ugly trick to preserve {}:
+    \bbl at replace\bbl at tempa{,}{ ,}&%
+    \expandafter\bbl at foreach\expandafter{\bbl at tempa}{&%
       \bbl at ifsamestring{##1}{remove}&%
         {\bbl at add@list\babeltempb{nil}}&%
         {\directlua{
-           local rep = [[##1]]
+           local rep = [=[##1]=]
+           rep = rep:gsub('^%s*(remove)%s*$', 'remove = true')
            rep = rep:gsub('^%s*(insert)%s*,', 'insert = true, ')
            rep = rep:gsub(    '(no)%s*=%s*([^%s,]*)', Babel.capture_func)
            rep = rep:gsub(   '(pre)%s*=%s*([^%s,]*)', Babel.capture_func)
@@ -1291,6 +1364,7 @@
     \directlua{
       local lbkr = Babel.linebreaking.replacements[1]
       local u = unicode.utf8
+      local id = \the\csname l@#1\endcsname
       &% Convert pattern:
       local patt = string.gsub([==[#2]==], '%s', '')
       if not u.find(patt, '()', nil, true) then
@@ -1299,12 +1373,15 @@
       patt = string.gsub(patt, '%(%)%^', '^()')
       patt = string.gsub(patt, '%$%(%)', '()$')
       patt = u.gsub(patt, '{(.)}',
-                function (n)
-                  return '%' .. (tonumber(n) and (tonumber(n)+1) or n)
-                end)
-      lbkr[\the\csname l@#1\endcsname] = lbkr[\the\csname l@#1\endcsname] or {}
-      table.insert(lbkr[\the\csname l@#1\endcsname],
-                   { pattern = patt, replace = { \babeltempb } })
+             function (n)
+               return '%' .. (tonumber(n) and (tonumber(n)+1) or n)
+             end)
+      patt = u.gsub(patt, '{(%x%x%x%x+)}',
+             function (n)
+               return u.gsub(u.char(tonumber(n, 16)), '(%p)', '%%%1')
+             end)
+      lbkr[id] = lbkr[id] or {}
+      table.insert(lbkr[id], { pattern = patt, replace = { \babeltempb } })
     }&%
   \endgroup}
 \gdef\babelprehyphenation#1#2#3{&%
@@ -1312,18 +1389,26 @@
   \begingroup
     \def\babeltempa{\bbl at add@list\babeltempb}&%
     \let\babeltempb\@empty
-    \bbl at foreach{#3}{&%
+    \def\bbl at tempa{#3}&% TODO. Ugly trick to preserve {}:
+    \bbl at replace\bbl at tempa{,}{ ,}&%
+    \expandafter\bbl at foreach\expandafter{\bbl at tempa}{&%
       \bbl at ifsamestring{##1}{remove}&%
         {\bbl at add@list\babeltempb{nil}}&%
         {\directlua{
-           local rep = [[##1]]
+           local rep = [=[##1]=]
+           rep = rep:gsub('^%s*(remove)%s*$', 'remove = true')
            rep = rep:gsub('^%s*(insert)%s*,', 'insert = true, ')
            rep = rep:gsub('(string)%s*=%s*([^%s,]*)', Babel.capture_func)
+           rep = rep:gsub( '(space)%s*=%s*([%d%.]+)%s+([%d%.]+)%s+([%d%.]+)',
+             'space = {' .. '%2, %3, %4' .. '}')
+           rep = rep:gsub( '(spacefactor)%s*=%s*([%d%.]+)%s+([%d%.]+)%s+([%d%.]+)',
+             'spacefactor = {' .. '%2, %3, %4' .. '}')
            tex.print([[\string\babeltempa{{]] .. rep .. [[}}]])
          }}}&%
     \directlua{
       local lbkr = Babel.linebreaking.replacements[0]
       local u = unicode.utf8
+      local id = \the\csname bbl at id@@#1\endcsname
       &% Convert pattern:
       local patt = string.gsub([==[#2]==], '%s', '')
       if not u.find(patt, '()', nil, true) then
@@ -1332,12 +1417,15 @@
       &% patt = string.gsub(patt, '%(%)%^', '^()')
       &% patt = string.gsub(patt, '([^%%])%$%(%)', '%1()$')
       patt = u.gsub(patt, '{(.)}',
-                function (n)
-                  return '%' .. (tonumber(n) and (tonumber(n)+1) or n)
-                end)
-      lbkr[\the\csname bbl at id@@#1\endcsname] = lbkr[\the\csname  bbl at id@@#1\endcsname] or {}
-      table.insert(lbkr[\the\csname bbl at id@@#1\endcsname],
-                   { pattern = patt, replace = { \babeltempb } })
+             function (n)
+               return '%' .. (tonumber(n) and (tonumber(n)+1) or n)
+             end)
+      patt = u.gsub(patt, '{(%x%x%x%x+)}',
+             function (n)
+               return u.gsub(u.char(tonumber(n, 16)), '(%p)', '%%%1')
+             end)
+      lbkr[id] = lbkr[id] or {}
+      table.insert(lbkr[id], { pattern = patt, replace = { \babeltempb } })
     }&%
   \endgroup}
 \endgroup

Modified: trunk/Master/texmf-dist/tex/generic/babel/nil.ldf
===================================================================
--- trunk/Master/texmf-dist/tex/generic/babel/nil.ldf	2021-03-24 21:26:32 UTC (rev 58683)
+++ trunk/Master/texmf-dist/tex/generic/babel/nil.ldf	2021-03-24 21:27:50 UTC (rev 58684)
@@ -32,7 +32,7 @@
 %% extension |.ins|) which are part of the distribution.
 %%
 
-\ProvidesLanguage{nil}[2021/03/03 3.55 Nil language]
+\ProvidesLanguage{nil}[2021/03/24 3.56 Nil language]
 \LdfInit{nil}{datenil}
 \ifx\l at nil\@undefined
   \newlanguage\l at nil



More information about the tex-live-commits mailing list.