7

In the following code I would like to "hack" a^b suchas to apply \macro{a}{b} instead. The solution should work with natural exponent and letters eventually indexed like in {abcd}^4 or {x_1}^2 for example.

\documentclass{article}
\usepackage{forloop}

\newcounter{power}

\newcommand\macro[2]{% \forloop[1]{power}{0}{\value{power} < #2}{#1\kern0.3ex}% \kern-0.3ex% }

\newcommand\test[1]{ % Lost in translation... }

\begin{document}

\test{x} % ---> x

\test{x y} % ---> x y

\test{x y^2} % ---> x y y

\test{x^3 y^2 z} % ---> x x x y y z

\end{document}

projetmbc
  • 13,315

4 Answers4

12

The code should be self-explaining:

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\NewDocumentCommand{\test}{m} { \projetmbc_test:n { #1 } }

\tl_new:N \l__projetmbc_test_tl

\cs_new_protected:Nn \projetmbc_test:n { \tl_set:Nn \l__projetmbc_test_tl { #1 } \regex_replace_all:nnN { (\cB. .*? \cE.|[[:alpha:]])^ } % search a braced group or single letter followed by ^ { \c{projetmbc_power:nn} \1 } % prepend \projetmbc_power:nn and remove ^ \l__projetmbc_test_tl \ensuremath { \tl_use:N \l__projetmbc_test_tl } }

\cs_new:Nn \projetmbc_power:nn { \prg_replicate:nn { #2 } { #1 } }

\ExplSyntaxOff

\begin{document}

\test{x} % ---> x

\test{x y} % ---> x y

\test{x y^2} % ---> x y y

\test{x^3 y^2 z} % ---> x x x y y z

\test{{x_1}^3 {abcde}^2}

\end{document}

enter image description here

egreg
  • 1,121,712
  • 3
    Thanks. Regex and their groups... Not so natural for me to use it with LaTeX but I will have to improve on this subject. – projetmbc Jul 16 '20 at 21:05
  • Note that this does not work in cases such as {x^2}^3 because \projetmbc_test:n is not recursively applied to \1. – user202729 Aug 31 '21 at 10:13
  • @user202729 Does {x^2}^3 make sense? – egreg Aug 31 '21 at 10:15
  • Perhaps it can expand to x^6; but other users may want to adapt the code to some other cases which need recursive expansion. Also the user may want {x^2 y}^3 which expands to x x y x x y x x y – user202729 Aug 31 '21 at 10:17
  • Also note that in this case the alpha regex will make +^6 not work (but {+}^6 still works – user202729 Aug 31 '21 at 10:18
9

Here's a LuaLaTeX-based solution.

enter image description here

% !TEX TS-program = lualatex
\documentclass{article}
\usepackage{amsmath} % for '\ensuremath' macro
\usepackage{luacode} % for 'luacode' env. and '\luastringN' macro
\begin{luacode}
function test ( s )
   s = s:gsub ( "(\\%a+) ^(%d+)", string.rep ) -- e.g., '\alpha^3'
   s = s:gsub ( "(%a)^(%d+)"    , string.rep ) -- e.g., 'x^2'
   s = s:gsub ( "(%b{})^(%d+)"  , string.rep ) -- e.g., '{x_1}^4'
   tex.sprint ( s ) 
end
\end{luacode}
% Define a LaTeX wrapper macro:
\newcommand\test[1]{\directlua{test(\luastringN{#1})}}

\begin{document} \obeylines \test{$x$} \test{$x y$} \test{$x^1 y^12$} \test{$x^3 y^2 z$} \test{${x_1}^3 {abcde}^2$} % courtesy of @egreg's posting \test{$\alpha^2\lambda^3\omega^4$} \end{document}

Henri Menke
  • 109,596
Mico
  • 506,678
  • 1
    More natural for me but unfortunatly I can't use in my project. – projetmbc Jul 17 '20 at 08:54
  • The two last comments are good ones. Regex fixes easily the forgot tente escaping cases. – projetmbc Jul 24 '20 at 07:38
  • Can Lua work with token instead of text ? – projetmbc Jul 24 '20 at 17:39
  • @projetmbc - Please clarify your use case. – Mico Jul 24 '20 at 17:40
  • Can Lua ignore unescaped good balanced curly braces for example ? – projetmbc Jul 24 '20 at 17:45
  • @projetmbc - Short answer: yes. (The code in my answer, which employs the pattern matching criterion %b{}, obviously does not ignore matching curly braces.) Please give an example of a use case that calls for ignoring matched curly braces. – Mico Jul 24 '20 at 17:47
  • Thanks for your answer. My question is just a general one. I will play soon with luatex. – projetmbc Jul 24 '20 at 20:44
  • 1
    To be more specific I will like to split a text regarding & and \ as in a table environment but I want to ignore this characters if they are used inside curly braces as element of a macro's argument for example. – projetmbc Jul 24 '20 at 21:10
  • Sure, there's scan_toks() function (already explained in the answer below), but if you just want to parse balanced just do a ++ when see a character { and -- when you see a character }. – user202729 Jan 27 '22 at 13:52
  • @user202729 - I'm not sure I understood what you're referring to. Please clarify. – Mico Jan 27 '22 at 14:15
  • @Mico That was just (another, compared to the LuaTeX token-parsing method) reply to projetmbc's comment on how to parse balanced group, and also in case someone come across the question... not targeted to you. – user202729 Jan 27 '22 at 14:16
  • @user202729 - Oh, ok. – Mico Jan 27 '22 at 14:24
4

You can also do it fully-expandably in expl3 by absorbing tokens one-by-one. This ignores spaces, but since you are planning to use this for partial derivatives in math mode, that shouldn't be a problem. It might be quite slow, though. It also doesn't work recursively, i.e. \test{{x^3}} will not be repeated.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

\cs_new:Npn \mbc_process_powers:w #1 #2 #3 { \str_if_eq:nnF { #1 } { \q_stop } { \str_if_eq:nnTF { #2 } { ^ } { \prg_replicate:nn { #3 } { #1 } \mbc_process_powers:w } { #1 \mbc_process_powers:w { #2 } { #3 } } } }

\NewExpandableDocumentCommand \test { m } { \mbc_process_powers:w #1 \q_stop \q_stop \q_stop }

\ExplSyntaxOff

\begin{document}

\ttfamily % nicer font for \meaning

\edef\x{\test{x}} \meaning\x % ---> x

\edef\x{\test{x y}} \meaning\x % ---> xy

\edef\x{\test{x y^2}} \meaning\x % ---> xyy

\edef\x{\test{x^3 y^2 z}} \meaning\x % ---> xxxyyz

\edef\x{\test{{abcd}^4 or {x_1}^3}} \meaning\x % ---> abcdabcdabcdabcdorx_1x_1x_1

\end{document}

If you can't or don't want to use expl3, you can also implement it in normal LaTeX, but you'll need a few helper macros:

\makeatletter

\protected\def@qstop{@qstop}

\ifdefined\directlua % LuaTeX doesn't have \pdfstrcmp. \directlua{ function pdfstrcmp(a, b) if a < b then tex.sprint("-1") elseif a > b then tex.sprint("1") else tex.sprint("0") end end } \long\def\pdfstrcmp#1#2{\directlua{pdfstrcmp("\luaescapestring{#1}", "\luaescapestring{#2}")}} \fi

\def@ifstrequal#1#2{% \ifnum\pdfstrcmp{\unexpanded{#1}}{\unexpanded{#2}}=0 \expandafter@firstoftwo \else \expandafter@secondoftwo \fi }

\def\replicate#1#2{% \ifnum\numexpr#1\relax>0 #2% \expandafter\replicate\expandafter{\number\numexpr(#1)-1\relax}{#2}% \fi }

\def\processpowers#1#2#3{% @ifstrequal{#1}{@qstop}{}{% @ifstrequal{#2}{^}{% \replicate{#3}{#1}% \processpowers }{% #1\processpowers{#2}{#3}% }% }% }

\newcommand\test[1]{\processpowers#1@qstop@qstop@qstop}

\makeatother

Henri Menke
  • 109,596
  • Thanks for this proposition. I will try it as soon as possible. – projetmbc Jul 24 '20 at 07:40
  • @projetmbc Regarding your question on the other answer. Yes, LuaTeX can work with TeX tokens instead of text, but that is quite tricky. If you are really interested, I can try to craft an answer. – Henri Menke Jul 26 '20 at 02:46
  • That is a useful thing so if you have the time to make a example it would be great. – projetmbc Jul 26 '20 at 07:59
3

In the comments you asked whether LuaTeX can also work with TeX tokens. Instead of asking for you to explain the use-case, I will consider this as academic interest and provide an example showcasing how to do this in principle.

LuaTeX comes with the built-in token library which provides facilities to work with an manipulate TeX tokens. In particular it has the function scan_toks() which allows to scan a list tokens delimited by balanced braces.

\directlua{t = token.scan_toks()}{...}

After this call the variable t will contain whatever is in .... Tokens are represented as Lua tables and you can query the token's properties as elements of said table. The elements that I use here are

  • cmdname The name of the internal TeX command that the token represents
  • tok The unique token identifier that TeX assigns

To compare whether two tokens are the same you can compare their tok properties (although care must be taken when comparing control sequences this way, since they can have additional properties such as \protected, \long or \outer).

Finally we can put tokens back into the input stream using token.put_next (which also accepts a table in which case it simple traverses the table and puts each token into the input stream).

In the example I do not wrap \directlua but I define a luacall by putting the Lua function definition into the global function table retrieved using lua.get_functions_table() and subsequently registering the luacall in TeX using token.set_lua. This has some benefits which are not really relevant here but nice to have, e.g. the \test macro defined this way expands in a single step.

One big annoyance with this solution is that LuaTeX tokens do not “know” what input they originate from, i.e. to check whether a token contains a number we have to compare it with all tokens that result in a number as well. For this I defined a lookup table which maps the tok property of the tokens to the corresponding numbers.

\documentclass{article}
\usepackage{luacode}
\begin{luacode}
-- Lookup table to convert tokens to numbers
local numbers = {
    [token.create(string.byte("1")).tok] = 1,
    [token.create(string.byte("2")).tok] = 2,
    [token.create(string.byte("3")).tok] = 3,
    [token.create(string.byte("4")).tok] = 4,
    [token.create(string.byte("5")).tok] = 5,
    [token.create(string.byte("6")).tok] = 6,
    [token.create(string.byte("7")).tok] = 7,
    [token.create(string.byte("8")).tok] = 8,
    [token.create(string.byte("9")).tok] = 9,
    [token.create(string.byte("0")).tok] = 0,
}

-- Register a new Lua function with TeX local lft = lua.get_functions_table() lft[#lft + 1] = function() -- Scan a list of tokens delimited by balanced braces local toks = token.scan_toks()

local result = {}
local stack = {}
local currentgrouplevel = 0
local n = 1
while n &lt;= #toks do
    -- We have to scan balanced braces, so we in/decrease the
    -- currentgrouplevel on every brace
    if toks[n].cmdname == &quot;left_brace&quot; then
        currentgrouplevel = currentgrouplevel + 1
    elseif toks[n].cmdname == &quot;right_brace&quot; then
        currentgrouplevel = currentgrouplevel - 1
    end

    -- Collect tokens on a stack
    table.insert(stack, toks[n])

    -- If we are not inside braces, check for the ^
    if currentgrouplevel == 0 then
        if toks[n + 1] and toks[n + 1].cmdname == &quot;sup_mark&quot; and
           toks[n + 2] and toks[n + 2].cmdname == &quot;other_char&quot; then
            -- Convert the token right after ^ to a number by looking it up
            local rep = assert(numbers[toks[n + 2].tok], &quot;Token is not a number&quot;)
            -- Append the stack to the result rep times
            for i = 1, rep do
                for _, t in ipairs(stack) do
                    table.insert(result, t)
                end
            end
            -- Flush the stack
            stack = {}
            -- Skip the next two tokens (^ and number)
            n = n + 2
        else
            -- We are not inside braces but there is also no ^, so we flush the stack
            for _, t in ipairs(stack) do
                table.insert(result, t)
            end
            stack = {}
        end
    end

    -- Move on to the next token
    n = n + 1
end

-- Flush whatever is still on the stack
for _, t in ipairs(stack) do
    table.insert(result, t)
end

-- Put the result back into the input stream
token.put_next(result)

end

-- Bind the registered function to "test" token.set_lua("test", #lft, "global") -- The "global" definition (similar to gdef) is needed because the luacode* -- environment is an implicit TeX group and set_lua obey TeX grouping \end{luacode}

\begin{document} \test{$x$}

\test{$x y$}

\test{$x^1 y^12$}

\test{$x^3 y^2 z$}

\test{${x_1}^3 {a{bc}de}^2$}

\test{${x_1}^3$}

\test{$\alpha^2\lambda^3\omega^4$} \end{document}

enter image description here

Henri Menke
  • 109,596
  • Thanks for your example and explanations. :-) This is so powerful. I will start seriously to use LuaTeX to do advanced formattings. – projetmbc Jul 27 '20 at 12:32