From e56e35b7667194ff586da96dffe19f8174ea02e6 Mon Sep 17 00:00:00 2001 From: Karl Berry Date: Sun, 29 Aug 2021 20:25:20 +0000 Subject: etl (29aug21) git-svn-id: svn://tug.org/texlive/trunk@60364 c570f23f-e606-0410-a88d-b1316a301751 --- Master/texmf-dist/doc/latex/etl/README.md | 2 +- Master/texmf-dist/doc/latex/etl/etl.pdf | Bin 341109 -> 357936 bytes Master/texmf-dist/source/latex/etl/etl.dtx | 279 ++++++++++++++++++++++++++--- Master/texmf-dist/tex/latex/etl/etl.sty | 125 +++++++++++-- 4 files changed, 363 insertions(+), 43 deletions(-) (limited to 'Master') diff --git a/Master/texmf-dist/doc/latex/etl/README.md b/Master/texmf-dist/doc/latex/etl/README.md index 8279d34fb04..b45f0d813a3 100644 --- a/Master/texmf-dist/doc/latex/etl/README.md +++ b/Master/texmf-dist/doc/latex/etl/README.md @@ -1,7 +1,7 @@ ------------------------------------------------------------------------------- # etl -- expandable token list operations -Version 2021-08-20 v0.1 +Version 2021-08-28 v0.2 Released under the LaTeX Project Public License v1.3c or later See http://www.latex-project.org/lppl.txt diff --git a/Master/texmf-dist/doc/latex/etl/etl.pdf b/Master/texmf-dist/doc/latex/etl/etl.pdf index 36590e4a542..5bbf575b8aa 100644 Binary files a/Master/texmf-dist/doc/latex/etl/etl.pdf and b/Master/texmf-dist/doc/latex/etl/etl.pdf differ diff --git a/Master/texmf-dist/source/latex/etl/etl.dtx b/Master/texmf-dist/source/latex/etl/etl.dtx index 93b90ca9d00..4c15f43c130 100644 --- a/Master/texmf-dist/source/latex/etl/etl.dtx +++ b/Master/texmf-dist/source/latex/etl/etl.dtx @@ -110,9 +110,9 @@ and the derived files etl.pdf % to unexpandable functions found inside the \pkg{l3tl} module of \pkg{expl3}. % All user functions must not contain the tokens \cs{s__etl_stop}\footnote{At % any nesting level of groups} or \cs{__etl_act_result:n}\footnote{Outside of -% some local brace groups} in any argument (but there might be other forbidden -% tokens, all of which are internals to this package, and usually shouldn't -% somehow end up inside the input stream by accident). +% some local brace groups} in any argument unless specified otherwise (there +% might be other forbidden tokens, all of which are internals to this package, +% and usually shouldn't somehow end up inside the input stream by accident). % % There is another limitation of this package: There are tokens which cannot % expandably be differentiated from each other, those are active characters let @@ -406,7 +406,7 @@ and the derived files etl.pdf % % \begin{function}[EXP]{\etl_replace_once:nnn} % \begin{syntax} -% \cs{etl_replace_once:nnn} \meta{token list} \meta{search text} \meta{replacement} +% \cs{etl_replace_once:nnn} \Arg{token list} \Arg{search text} \Arg{replacement} % \end{syntax} % This function will replace the first occurrence of \meta{search text} % inside of \meta{token list} that is not hidden inside a group with @@ -422,7 +422,7 @@ and the derived files etl.pdf % % \begin{function}[EXP]{\etl_replace_all:nnn} % \begin{syntax} -% \cs{etl_replace_all:nnn} \meta{token list} \meta{search text} \meta{replacement} +% \cs{etl_replace_all:nnn} \Arg{token list} \Arg{search text} \Arg{replacement} % \end{syntax} % This function will replace all occurrences of \meta{search text} % inside of \meta{token list} that are not hidden inside a group with @@ -438,7 +438,7 @@ and the derived files etl.pdf % % \begin{function}[EXP]{\etl_replace_all_deep:nnn} % \begin{syntax} -% \cs{etl_replace_all_deep:nnn} \meta{token list} \meta{search text} \meta{replacement} +% \cs{etl_replace_all_deep:nnn} \Arg{token list} \Arg{search text} \Arg{replacement} % \end{syntax} % This function will replace all occurrences of \meta{search text} % inside of \meta{token list} with \meta{replacement}. @@ -452,14 +452,21 @@ and the derived files etl.pdf % \end{function} % % -% \subsection{New expandable fast tests} +% \subsection{New expandable functions} % -% Functions generated with the means in this section are as fast as the -% \pkg{l3tl} variants of them, but have at least one fixed argument. +% Functions generated with the means in this section are roughly as fast as the +% \pkg{l3tl} variants of them (there might be performance differences; in any +% case they are faster than the generic functions above), but have at least one +% fixed argument. They don't have the drawback of not being able to tell apart +% an active character from a token with the same character code and different +% category code if the active character was let to it and they don't normalise +% braces to \texttt{\{\textsubscript{1}} and \texttt{\}\textsubscript{2}}. +% +% \subsubsection{Conditionals} % % \begin{function}{\etl_new_if_in:Nnn} % \begin{syntax} -% \cs{etl_new_if_in:Nnn} \meta{function} \meta{search text} \meta{conditions} +% \cs{etl_new_if_in:Nnn} \meta{function} \Arg{search text} \Arg{conditions} % \end{syntax} % This will define a new \meta{function} which will act as a conditional and % search for \meta{search text} inside of an |n|-type argument completely @@ -479,6 +486,57 @@ and the derived files etl.pdf % \my_if_a_in:nTF { a text } { true } { false } % \my_if_a_in:nTF { text } { true } { false } % \end{verbatim} +% +% +% \subsubsection{Modifiers} +% +% \begin{function}{\etl_new_replace_once:Nn} +% \begin{syntax} +% \cs{etl_new_replace_once:Nn} \meta{function} \Arg{search text} +% \end{syntax} +% This defines a new \meta{function} that'll accept two arguments (the first +% being a token list, the second a replacement). The generated \meta{function} +% will replace the first occurrence of \meta{search text} inside the token +% list with replacement. It'll ignore things hidden inside a group in the +% token list. Neither the \meta{search text} nor the token list given to the +% generated \meta{function} can contain \cs{s__etl_stop} (this would result in +% undefined behaviour), the given replacement on the other hand might contain +% that token. Additionally \meta{search text} can't contain tokens of category +% group begin or group end (usually |{| and |}|) or macro parameters (usually +% |#|). +% +% \begin{texnote} +% The result of \meta{function} is returned within \cs{exp_not:n}, which +% means that the token list does not expand further when appearing in an +% \hbox{|x|- or} |e|-type argument expansion. The result will be returned +% after exactly two steps of expansion. +% \end{texnote} +% \end{function} +% So the following would yield |AcDC|: +% \begin{verbatim} +% \etl_new_replace_once:Nn \my_replace_C_once:nn { C } +% \my_replace_C_once:nn { ACDC } { c } +% \end{verbatim} +% +% \begin{function}{\etl_new_replace_all:Nn} +% \begin{syntax} +% \cs{etl_new_replace_all:Nn} \meta{function} \Arg{search text} +% \end{syntax} +% This behaves like \cs{etl_new_replace_once:Nn}, but the \meta{function} will +% replace all occurrences of \meta{search text} instead of just the first. +% +% \begin{texnote} +% The result of \meta{function} is returned within \cs{exp_not:n}, which +% means that the token list does not expand further when appearing in an +% \hbox{|x|- or} |e|-type argument expansion. The result will be returned +% after exactly two steps of expansion. +% \end{texnote} +% \end{function} +% So the following would yield |AcDc|: +% \begin{verbatim} +% \etl_new_replace_all:Nn \my_replace_C_all:nn { C } +% \my_replace_C_all:nn { ACDC } { c } +% \end{verbatim} % % % \subsection{Bugs and Feature Requests} @@ -506,7 +564,7 @@ and the derived files etl.pdf % Tell who we are: % \begin{macrocode} \ProvidesExplPackage{etl} - {2021-08-20} {0.1} + {2021-08-28} {0.2} {expandable token list manipulation} % \end{macrocode} % @@ -515,7 +573,7 @@ and the derived files etl.pdf \cs_if_exist:NF \tex_expanded:D { \msg_new:nnn { etl } { expanded-missing } - { The expanded primitive is required. } + { The~ expanded~ primitive~ is~ required. } \msg_fatal:nn { etl } { expanded-missing } } % \end{macrocode} @@ -666,12 +724,10 @@ and the derived files etl.pdf \cs_new:Npn \etl_act:nnnnnnn #1#2#3#4#5#6#7 { \@@_unexpanded:w \@@_expanded:w - { - { - \@@_act:w #7 {\s_@@_stop} . \s_@@_stop {#5} {#1} {#2} {#3} - \@@_act_result:n {#6} {#4} - } - } + {{ + \@@_act:w #7 {\s_@@_stop} . \s_@@_stop {#5} {#1} {#2} {#3} + \@@_act_result:n {#6} {#4} + }} } % \end{macrocode} % We also provide a version without the \cs{@@_unexpanded:w} around it for @@ -1298,6 +1354,9 @@ and the derived files etl.pdf % \end{macro} % \end{macro} % +% +% \subsection{Expandably modify token lists} +% % \begin{macro}[EXP]{\etl_token_replace_all:nNn} % \begin{macro}[EXP]{\@@_token_replace:NnnN} % Replaceing a single token (and in fact the same is true for all the @@ -1647,23 +1706,46 @@ and the derived files etl.pdf % \end{macrocode} % \end{macro} % +% +% \subsection{Defining new tests} +% % \begin{macro}{\etl_new_if_in:Nnn} -% \begin{macro}{\@@_new_if_in:NNnn} +% \begin{macro}{\@@_new_if_in:NnNnn, \@@_new_if_in:NNnn} % These tests work essentially in the same way as \cs{tl_if_in:nnTF}, but % instead they use a predefined internal macro so that no definition at use -% time is necessary. +% time is necessary. We use a small loop to get a unique auxiliary macro name +% for the search text. % \begin{macrocode} -\exp_args:Nno \use:n { \cs_new_protected:Npn \etl_new_if_in:Nnn #1#2 } +\exp_args:Nno \use:n { \cs_new_protected:Npn \etl_new_if_in:Nnn #1#2#3 } { - \exp_args:Nc \@@_new_if_in:NNnn - { @@_user_function ~ \cs_to_str:N #1 ~ \tl_to_str:n {#2} :w } + \scan_stop: + \if_false: { \fi: + \exp_args:Nc \@@_new_if_in:NnNnn + { @@_user_function ~ if_in ~ \tl_to_str:n {#2} :w } + ? #1 {#2} + {#3} + \if_false: } \fi: + } +\cs_new_protected:Npn \@@_new_if_in:NnNnn #1#2#3#4 + { + \cs_if_exist:NTF #1 + { + \cs_set:Npn \@@_tmp:w ##1 #4 {} + \cs_if_eq:NNTF #1 \@@_tmp:w + { \@@_new_if_in:NNnn #1 #3 {#4} } + { + \exp_args:Nc \@@_new_if_in:NnNnn + { @@_user_function ~ if_in #2 ~ \tl_to_str:n {#4} :w } + { #2? } + #3 {#4} + } + } + { \@@_new_if_in:NNnn #1 #3 {#4} } } \cs_new_protected:Npn \@@_new_if_in:NNnn #1#2#3#4 { - \scan_stop: - \if_false: { \fi: - \cs_new:Npn #1 ##1 #3 {} + \cs_gset:Npn #1 ##1 #3 {} \prg_new_conditional:Npnn #2 ##1 {#4} { \if:w @@ -1678,7 +1760,150 @@ and the derived files etl.pdf \prg_return_false: \fi: } - \if_false: } \fi: + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% +% \subsection{Defining new modifiers} +% +% The implementation of |replace_once| and |replace_all| is modelled closely on +% the implementation used in \pkg{l3tl}. The difference is that we use a hard +% coded delimiter (\cs{s_@@_stop}) instead of searching for one that is always +% legal (we can't do redefinitions, so can't change the delimiter later based on +% the token list input). +% +% +% \begin{macro} +% { +% \@@_new_replace_def:NNn, +% \@@_new_replace_def_aux:NnNnN, \@@_new_replace_def_aux:Nn +% } +% We need another loop to guarantee unique names, if everything's alright we +% go on and define the user function using |#1| of +% \cs{@@_new_replace_def:NNn}. An empty search pattern is forbidden and should +% throw an error. +% \begin{macrocode} +\msg_new:nnn { etl } { empty-search-text } + { The~ search~ text~ of~ #1 must~ not~ be~ empty. } +\cs_new_protected:Npn \@@_new_replace_def:NNn #1#2#3 + { + \tl_if_empty:nTF {#3} + { \msg_error:nnn { etl } { empty-search-text } { #2 } } + { + \scan_stop: + \if_false: { \fi: + \exp_args:Nc \@@_new_replace_def_aux:NnNnN + { @@_user_function ~ replace ~ \tl_to_str:n {#3} ~ :Nnw } + ? + #2 {#3} + #1 + \if_false: } \fi: + } + } +\cs_new_protected:Npn \@@_new_replace_def_aux:NnNnN #1#2#3#4#5 + { + \cs_if_exist:NTF #1 + { + \@@_new_replace_def_aux:Nn \@@_tmp:w {#4} + \cs_if_eq:NNTF #1 \@@_tmp:w + { #5 #1#3 {#4} } + { + \exp_args:Nc \@@_new_replace_def_aux:NnNnN + { @@_user_function ~ replace #2 ~ \tl_to_str:n {#4} ~ :Nnw } + { #2? } #3 {#4} #5 + } + } + { + \@@_new_replace_def_aux:Nn #1 {#4} + #5 #1#3 {#4} + } + } +% \end{macrocode} +% The auxiliary macro uses a loop for the replacement. This is also used for +% the |once| variant. This saves internal functions if both an |all| and a +% |once| function are generated for the same search text (though the |once| +% variant could be coded easier and faster otherwise, but the performance hit +% should be small). +% \begin{macrocode} +\cs_new_protected:Npn \@@_new_replace_def_aux:Nn #1#2 + { + \cs_gset:Npn #1 ##1##2 ##3#2 + { + \@@_new_replace_wrap:w ##3 \s_@@_stop \@@_unexpanded:w {##2} + ##1 #1 {##2} {}{} + } + } +% \end{macrocode} +% \end{macro} +% +% \begin{macro}{\@@_new_replace_wrap:w} +% \begin{macro}{\@@_new_replace_once:w} +% \begin{macro}{\@@_new_replace_done:w} +% We need a few auxiliaries for the two replacement variants here. The first +% just grabs the already processed part of the token list and protects it from +% further expanding. The second breaks the loop for the |once| variant by +% protecting the remainder of the token list from further expanding. The last +% just gobbles the remainder of the loop by using an unbalanced brace trick. +% \begin{macrocode} +\cs_new:Npn \@@_new_replace_wrap:w #1\s_@@_stop + { \@@_unexpanded:w \exp_after:wN { \use_none:nn #1 } } +\cs_new:Npn \@@_new_replace_once:w #1#2 #3\s_@@_stop + { \@@_unexpanded:w \exp_after:wN { \use_none:nn #3 } } +\cs_new:Npn \@@_new_replace_done:w + { \exp_after:wN \use_none:n \exp_after:wN { \if_false: } \fi: } +% \end{macrocode} +% \end{macro} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\etl_new_replace_once:Nn} +% \begin{macro}{\@@_new_replace_once:NNn} +% The |once| variant will use \cs{@@_new_replace_done_once:w} if the +% replacement is successful (that will remove the remainder of the loop, and +% protect both the replacement and the rest of the token list on which we work +% from further expanding). +% \begin{macrocode} +\cs_new_protected:Npn \etl_new_replace_once:Nn + { \@@_new_replace_def:NNn \@@_new_replace_once:NNn } +\cs_new_protected:Npn \@@_new_replace_once:NNn #1#2#3 + { + \cs_new:Npn #2 ##1##2 + { + \@@_unexpanded:w \@@_expanded:w + {{ + \if_false: { \fi: + #1 \@@_new_replace_once:w {##2} {}{} ##1 \s_@@_stop + \@@_new_replace_done:w #3 + } + }} + } + } +% \end{macrocode} +% \end{macro} +% \end{macro} +% +% \begin{macro}{\etl_new_replace_all:Nn} +% \begin{macro}{\@@_new_replace_all:NNn} +% The |all| variant will directly protect the replacement from further +% expanding and reiterate (due to the way the auxiliary is defined) until the +% replacement isn't found anymore. +% \begin{macrocode} +\cs_new_protected:Npn \etl_new_replace_all:Nn + { \@@_new_replace_def:NNn \@@_new_replace_all:NNn } +\cs_new_protected:Npn \@@_new_replace_all:NNn #1#2#3 + { + \cs_new:Npn #2 ##1##2 + { + \@@_unexpanded:w \@@_expanded:w + {{ + \if_false: { \fi: + #1 #1 {##2} {}{} ##1 \s_@@_stop + \@@_new_replace_done:w #3 + } + }} + } } % \end{macrocode} % \end{macro} diff --git a/Master/texmf-dist/tex/latex/etl/etl.sty b/Master/texmf-dist/tex/latex/etl/etl.sty index 55f55e73812..de2abad38c5 100644 --- a/Master/texmf-dist/tex/latex/etl/etl.sty +++ b/Master/texmf-dist/tex/latex/etl/etl.sty @@ -30,12 +30,12 @@ %% etl.sty %% \ProvidesExplPackage{etl} - {2021-08-20} {0.1} + {2021-08-28} {0.2} {expandable token list manipulation} \cs_if_exist:NF \tex_expanded:D { \msg_new:nnn { etl } { expanded-missing } - { The expanded primitive is required. } + { The~ expanded~ primitive~ is~ required. } \msg_fatal:nn { etl } { expanded-missing } } \cs_new_eq:NN \__etl_expanded:w \tex_expanded:D @@ -93,12 +93,10 @@ \cs_new:Npn \etl_act:nnnnnnn #1#2#3#4#5#6#7 { \__etl_unexpanded:w \__etl_expanded:w - { - { - \__etl_act:w #7 {\s__etl_stop} . \s__etl_stop {#5} {#1} {#2} {#3} - \__etl_act_result:n {#6} {#4} - } - } + {{ + \__etl_act:w #7 {\s__etl_stop} . \s__etl_stop {#5} {#1} {#2} {#3} + \__etl_act_result:n {#6} {#4} + }} } \cs_new:Npn \__etl_act:nnnnnnn #1#2#3#4#5#6#7 { @@ -684,17 +682,36 @@ { {#2} {#2} {} {#3} \s__etl_mark } {#1} } -\exp_args:Nno \use:n { \cs_new_protected:Npn \etl_new_if_in:Nnn #1#2 } +\exp_args:Nno \use:n { \cs_new_protected:Npn \etl_new_if_in:Nnn #1#2#3 } { - \exp_args:Nc \__etl_new_if_in:NNnn - { __etl_user_function ~ \cs_to_str:N #1 ~ \tl_to_str:n {#2} :w } + \scan_stop: + \if_false: { \fi: + \exp_args:Nc \__etl_new_if_in:NnNnn + { __etl_user_function ~ if_in ~ \tl_to_str:n {#2} :w } + ? #1 {#2} + {#3} + \if_false: } \fi: + } +\cs_new_protected:Npn \__etl_new_if_in:NnNnn #1#2#3#4 + { + \cs_if_exist:NTF #1 + { + \cs_set:Npn \__etl_tmp:w ##1 #4 {} + \cs_if_eq:NNTF #1 \__etl_tmp:w + { \__etl_new_if_in:NNnn #1 #3 {#4} } + { + \exp_args:Nc \__etl_new_if_in:NnNnn + { __etl_user_function ~ if_in #2 ~ \tl_to_str:n {#4} :w } + { #2? } + #3 {#4} + } + } + { \__etl_new_if_in:NNnn #1 #3 {#4} } } \cs_new_protected:Npn \__etl_new_if_in:NNnn #1#2#3#4 { - \scan_stop: - \if_false: { \fi: - \cs_new:Npn #1 ##1 #3 {} + \cs_gset:Npn #1 ##1 #3 {} \prg_new_conditional:Npnn #2 ##1 {#4} { \if:w @@ -709,7 +726,85 @@ \prg_return_false: \fi: } - \if_false: } \fi: + } +\msg_new:nnn { etl } { empty-search-text } + { The~ search~ text~ of~ #1 must~ not~ be~ empty. } +\cs_new_protected:Npn \__etl_new_replace_def:NNn #1#2#3 + { + \tl_if_empty:nTF {#3} + { \msg_error:nnn { etl } { empty-search-text } { #2 } } + { + \scan_stop: + \if_false: { \fi: + \exp_args:Nc \__etl_new_replace_def_aux:NnNnN + { __etl_user_function ~ replace ~ \tl_to_str:n {#3} ~ :Nnw } + ? + #2 {#3} + #1 + \if_false: } \fi: + } + } +\cs_new_protected:Npn \__etl_new_replace_def_aux:NnNnN #1#2#3#4#5 + { + \cs_if_exist:NTF #1 + { + \__etl_new_replace_def_aux:Nn \__etl_tmp:w {#4} + \cs_if_eq:NNTF #1 \__etl_tmp:w + { #5 #1#3 {#4} } + { + \exp_args:Nc \__etl_new_replace_def_aux:NnNnN + { __etl_user_function ~ replace #2 ~ \tl_to_str:n {#4} ~ :Nnw } + { #2? } #3 {#4} #5 + } + } + { + \__etl_new_replace_def_aux:Nn #1 {#4} + #5 #1#3 {#4} + } + } +\cs_new_protected:Npn \__etl_new_replace_def_aux:Nn #1#2 + { + \cs_gset:Npn #1 ##1##2 ##3#2 + { + \__etl_new_replace_wrap:w ##3 \s__etl_stop \__etl_unexpanded:w {##2} + ##1 #1 {##2} {}{} + } + } +\cs_new:Npn \__etl_new_replace_wrap:w #1\s__etl_stop + { \__etl_unexpanded:w \exp_after:wN { \use_none:nn #1 } } +\cs_new:Npn \__etl_new_replace_once:w #1#2 #3\s__etl_stop + { \__etl_unexpanded:w \exp_after:wN { \use_none:nn #3 } } +\cs_new:Npn \__etl_new_replace_done:w + { \exp_after:wN \use_none:n \exp_after:wN { \if_false: } \fi: } +\cs_new_protected:Npn \etl_new_replace_once:Nn + { \__etl_new_replace_def:NNn \__etl_new_replace_once:NNn } +\cs_new_protected:Npn \__etl_new_replace_once:NNn #1#2#3 + { + \cs_new:Npn #2 ##1##2 + { + \__etl_unexpanded:w \__etl_expanded:w + {{ + \if_false: { \fi: + #1 \__etl_new_replace_once:w {##2} {}{} ##1 \s__etl_stop + \__etl_new_replace_done:w #3 + } + }} + } + } +\cs_new_protected:Npn \etl_new_replace_all:Nn + { \__etl_new_replace_def:NNn \__etl_new_replace_all:NNn } +\cs_new_protected:Npn \__etl_new_replace_all:NNn #1#2#3 + { + \cs_new:Npn #2 ##1##2 + { + \__etl_unexpanded:w \__etl_expanded:w + {{ + \if_false: { \fi: + #1 #1 {##2} {}{} ##1 \s__etl_stop + \__etl_new_replace_done:w #3 + } + }} + } } %% %% -- cgit v1.2.3