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}
6
  • 1
    if you just want to support official latex syntax so x^{3+4} or even x^3 then it's fairly easy to get b at least, just make ^ math active as done by tex4ht for example. If you want to support the primitive tex parsing and things like x^\frac12 or x^\mathrm{abc} then it's much harder. getting the base is hard, and what do you want from (x+y)^2 is \macro{)}{2} acceptable? Jul 16 '20 at 20:34
  • I have updated my question.
    – projetmbc
    Jul 16 '20 at 20:41
  • 6
    Classic XY problem. You should tell us what you actually want to do. Jul 16 '20 at 20:43
  • This is to make a macro writing partial derivations like $f_{xxy}$ which is sometimes used by physicians.
    – projetmbc
    Jul 16 '20 at 20:45
  • 2
    The froggy french is lost in translation... Thanks for pointing that.
    – projetmbc
    Jul 16 '20 at 21:20
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

5
  • 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 at 10:13
  • @user202729 Does {x^2}^3 make sense?
    – egreg
    Aug 31 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 at 10:17
  • Also note that in this case the alpha regex will make +^6 not work (but {+}^6 still works
    – user202729
    Aug 31 at 10:18
10

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}
8
  • 1
    More natural for me but unfortunatly I can't use in my project.
    – projetmbc
    Jul 17 '20 at 8:54
  • The two last comments are good ones. Regex fixes easily the forgot tente escaping cases.
    – projetmbc
    Jul 24 '20 at 7: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
  • 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
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
3
  • Thanks for this proposition. I will try it as soon as possible.
    – projetmbc
    Jul 24 '20 at 7: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. Jul 26 '20 at 2: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 7:59
1

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 <= #toks do
        -- We have to scan balanced braces, so we in/decrease the
        -- currentgrouplevel on every brace
        if toks[n].cmdname == "left_brace" then
            currentgrouplevel = currentgrouplevel + 1
        elseif toks[n].cmdname == "right_brace" 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 == "sup_mark" and
               toks[n + 2] and toks[n + 2].cmdname == "other_char" then
                -- Convert the token right after ^ to a number by looking it up
                local rep = assert(numbers[toks[n + 2].tok], "Token is not a number")
                -- 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

1
  • 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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.