# Token replacement within a group

A comment below has clarified my problem, it has nothing to do with lua. In the code snippet below:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\cs_new_protected:Npn \sops_format_set_expr:n #1 {
\tl_set:Nn \l_tmpa_tl {A+\overline{(B+A)}}
\tl_replace_all:Nnn \l_tmpa_tl {+}{\cup}
\tl_use:N \l_tmpa_tl
}

\NewDocumentCommand{\seval}{ m }{
$\sops_format_set_expr:n {#1}$
}
\ExplSyntaxOff

\begin{document}

\seval{A+-(B+A)}

\end{document}


Is there a better way to perform the replacement + to \cup, than (for example) iterating over the string A+\overline{(B+A)} with \tl_head, checking for head=group along the way, and then piecing it back together?

Background: I'm making a little package to evaluate and format some set expressions. I have code to do the evaluation, however printing is an issue. I have code in lua that will evaluate an expression of the form A+(B*C) (with + interpreted as set union, and * interpreted as intersection) and print the resulting set. Finding an appropriate way to handle complements, resulted in (what I felt was) the need to use pattern matching in lua to handle balanced delimiters properly. As a result, part of the code does some string manipulation in lua, and spits it back to tex to be formatted.

Question: The code below attempts to take a string of the form -(<stuff>), and replace it with \overline{(<stuff>)} in tex. See an earlier question of mine for reference.

\begingroup
\catcode\%=12\relax
\gdef\patmatch{"%-(%b())","\noexpand\\overline{%1}"}
\endgroup

\def\setcomp#1{%
\directlua{
local s, _ = string.gsub("\luatexluaescapestring{#1}",\patmatch)
tex.sprint(s)
%tex.sprint(-2,s)
}
}


I would like to have tex parse the resulting string (?) to replace * with \cap and + with \cup. The issue is that in the replacement, tex does not recognize any + that is handled by lua in the string substitution above. The code below "accomplishes" the substitution:

\cs_new_protected:Npn \sops_format_set_expr:n #1 {
\tl_set:Nx \l_tmpa_tl {\setcomp{#1}}
\tl_replace_all:Nnn \l_tmpa_tl {+}{\cup}
\tl_use:N \l_tmpa_tl
}

\NewDocumentCommand{\seval}{ m }{
$\sops_format_set_expr:n {#1}$
}


When \seval is called on something like \seval{A+-(B+A)}, the -(B+A) is first handled by the string substitution and replaced with \overline{(B+A)}, when we have A+\overline{(B+A)} to be scanned for replacement of + with \cup. The problem is that the first occurrence of + (the one not involved with the string substitution) is recognized, while the second one is not. I blindly attempted to use string.sprint(-2,s) as commented in the first code block above, in which case the second + is recognized, but the macro is not.

As usual, my puny brain is unable to handle the intricacies of tex/lua catcode interaction. Could someone please explain what the problem is, and how to fix it? MWE below.

\documentclass{article}
\usepackage{xparse}

\begingroup
\catcode\%=12\relax
\gdef\patmatch{"%-(%b())","\noexpand\\overline{%1}"}
\endgroup

\def\setcomp#1{%
\directlua{
local s, _ = string.gsub("\luatexluaescapestring{#1}",\patmatch)
tex.sprint(s)
}
}

\ExplSyntaxOn

\cs_new_protected:Npn \sops_format_set_expr:n #1 {
\tl_set:Nx \l_tmpa_tl {\setcomp{#1}}
\tl_replace_all:Nnn \l_tmpa_tl {+}{\cup}
\tl_use:N \l_tmpa_tl
}

\NewDocumentCommand{\seval}{ m }{
$\sops_format_set_expr:n {#1}$
}
\ExplSyntaxOff

\begin{document}

\seval{A+-(B+A)}

\end{document}

• \tl_replace works on a “item” basis, not on a token basis. So if you have {+} it's seen not as {, + and } (three tokens) but as {+} (one item), and you can't reach the + inside a group {}. Sep 18, 2014 at 22:43
• @Manuel Oh wow, I feel a little dumb now. My whole question could have been about 5 lines long haha. Sep 18, 2014 at 22:56
• @Manuel Thanks btw. Can you think of a better solution than iterating with \tl_head, handling a group as the head separately, and piecing back together? Sep 18, 2014 at 23:01

The only problem is replacing, that \tl_replace_… doesn't work as you expect (everything inside {} is hid and can't be found). So the problematic line is only

\tl_replace_all:Nnn \l_tmpa_tl { + } { \cup }


## Option 1

Load etoolbox and use

\patchcmd \l_tmpa_tl { + } { \cup } { } { }


instead. The problem is that this doesn't work like \tl_replace_all but \tl_replace_once.

## Option 2

Load l3regex and substitute that line by

\regex_replace_all:nnN { \+ } { \c{cup} } \l_tmpa_tl

• regexpatch offers \xpatchcmd* for that precise purpose. But since it's based on l3regex, it's simpler using it directly. Sep 18, 2014 at 23:23