9

In the following code, I would like to parse the argument of \parseanduse such as to "achieve" the examples shown.

Each time a o is met, the following integer should be used to call the macro \macroO. There is no upper bound for the integers.

There is a similar feature with r and a and the macros \macroR and \macroA.

What would be the best way to do that in LaTeX3?

\documentclass[12pt,a4paper]{article}

\NewDocumentCommand{\macroO}{m}{Option [#1] ,} \NewDocumentCommand{\macroR}{m}{Option $<$#1$>$ ,} \NewDocumentCommand{\macroA}{m}{Argument #1 ,}

\NewDocumentCommand{\parseanduse}{m}{???}

\begin{document}

\parseanduse{o1r2a3}

same as

\macroO{1}\macroR{2}\macroA{3}

\bigskip

\parseanduse{o1r2a3o11r222a3333}

same as

\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}

\end{document}

projetmbc
  • 13,315

4 Answers4

10

You can parse via regular expressions.

\documentclass{article}

\ExplSyntaxOn

\NewDocumentCommand{\parseanduse}{m} { \projetmbc_parseanduse:n { #1 } }

\NewDocumentCommand{\macroO}{m} { \projetmbc_macro_o:n { #1 } } \NewDocumentCommand{\macroR}{m} { \projetmbc_macro_r:n { #1 } } \NewDocumentCommand{\macroA}{m} { \projetmbc_macro_a:n { #1 } }

\tl_new:N \l__projetmbc_parseanduse_tl

\cs_new_protected:Nn \projetmbc_parseanduse:n { \tl_set:Nn \l__projetmbc_parseanduse_tl { #1 } \regex_replace_all:nnN { (o|r|a)([[:digit:]]*) } % search o or r or a followed by digits { \c{projetmbc_macro_\1:n} \cB{\2\cE} } % replace with macro and braces \l__projetmbc_parseanduse_tl \tl_use:N \l__projetmbc_parseanduse_tl }

\cs_new_protected:Nn \projetmbc_macro_o:n { Option~[#1]~, } \cs_new_protected:Nn \projetmbc_macro_r:n { Option~$<$#1$>$~, } \cs_new_protected:Nn \projetmbc_macro_a:n { Argument~#1~, }

\ExplSyntaxOff

\begin{document}

\parseanduse{o1r2a3}

same as

\macroO{1}\macroR{2}\macroA{3}

\bigskip

\parseanduse{o1r2a3o11r222a3333}

same as

\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}

\bigskip

\parseanduse{a1o2r3o11r222a3333}

same as

\macroA{1}\macroO{2}\macroR{3}\macroO{11}\macroR{222}\macroA{3333}

\end{document}

enter image description here

A fully expandable version that is quite readable.

\documentclass{article}

\ExplSyntaxOn

% these are just to check the output \NewDocumentCommand{\macroO}{m} { \projetmbc_macro_o:n { #1 } } \NewDocumentCommand{\macroR}{m} { \projetmbc_macro_r:n { #1 } } \NewDocumentCommand{\macroA}{m} { \projetmbc_macro_a:n { #1 } }

% the main command \NewExpandableDocumentCommand{\parseanduse}{m} { \projetmbc_parseanduse:n { #1 } }

% we assume that the argument starts with a letter % error checks can be added % \q_nil is appended to the list \cs_new:Nn \projetmbc_parseanduse:n { __projetmbc_parseanduse_start:N #1 \q_nil }

\cs_new:Nn __projetmbc_parseanduse_start:N {% #1 is a letter, start with empty second argument and scan the next token __projetmbc_parseanduse_next:nnN { #1 } { } }

\cs_new:Nn __projetmbc_parseanduse_next:nnN { \quark_if_nil:NTF #3 {% we reached the end, issue the macro and the collected argument \use:c { projetmbc_macro_#1:n } { #2 } } {% scan the next token \int_compare:nTF { 0 &lt;=#3 <= `9 } {% we found a digit, append it to the candidate second argument __projetmbc_parseanduse_next:nnN { #1 } { #2#3 } } {% we found a letter, issue the macro and the collected argument \use:c { projetmbc_macro_#1:n } { #2 } % restart the recursion __projetmbc_parseanduse_start:N #3 } } }

\cs_new_protected:Nn \projetmbc_macro_o:n { Option~[#1]~, } \cs_new_protected:Nn \projetmbc_macro_r:n { Option~$<$#1$>$~, } \cs_new_protected:Nn \projetmbc_macro_a:n { Argument~#1~, }

\ExplSyntaxOff

\begin{document}

\parseanduse{o1r2a3}

same as

\macroO{1}\macroR{2}\macroA{3}

\bigskip

\parseanduse{o1r2a3o11r222a3333}

same as

\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}

\bigskip

\parseanduse{a1o2r3o11r222a3333}

same as

\macroA{1}\macroO{2}\macroR{3}\macroO{11}\macroR{222}\macroA{3333}

\edef\test{\parseanduse{a1o2r3o11r222a3333}} \texttt{\meaning\test}

\end{document}

In the last lines the macros \projetmbc_macro_(o|r|a):n are not expanded because they're protected. But if they are fully expandable, there will be no problem.

enter image description here

egreg
  • 1,121,712
8

The following is pretty low-level code, but shows how one could achieve this with a fully expandable loop. The loop can do more than you originally asked for (it does work with spaces and braced parts inside the argument of the individual macros).

\documentclass[12pt,a4paper]{article}

\NewDocumentCommand{\macroO}{m}{Option [#1] ,} \NewDocumentCommand{\macroR}{m}{Option $<$#1$>$ ,} \NewDocumentCommand{\macroA}{m}{Argument #1 ,}

\ExplSyntaxOn \msg_new:nnn { projetmbc } { unknown-function } { Unknown~ function~ `#1'.~ Giving~ up. } \scan_new:N \s__projetmbc_stop \scan_new:N \s__projetmbc_mark \cs_new:Npn __projectmbc_gobble_to_stop:w #1 \s__projetmbc_stop {} \cs_new:Npn __projectmbc_gobble_to_mark:w #1 \s__projetmbc_mark {} \cs_new:Npn __projetmbc_brace_open: { { \if_false: } \fi: } \cs_new:Npn __projetmbc_brace_close: { \if_false: { \fi: } } \cs_new:Npn \projetmbc_parse_loop:n #1 { \use:e { __projetmbc_parse_loop:N #1 {\s__projetmbc_mark}~ \s__projetmbc_stop } } \cs_new:Npn __projetmbc_parse_loop:N #1 { \cs_if_exist:cTF { macro \str_uppercase:n {#1} } { \exp_not:c { macro \str_uppercase:n {#1} } __projetmbc_brace_open: __projetmbc_parse_loop_collect:w } { \msg_expandable_error:nnn { projetmbc } { unknown-function } {#1} __projectmbc_gobble_to_stop:w } } \cs_new:Npn __projetmbc_parse_loop_collect:w #1 \s__projetmbc_stop { \tl_if_head_is_group:nTF {#1} __projetmbc_parse_loop_group:nw { \tl_if_head_is_space:nTF {#1} __projetmbc_parse_loop_space:w __projetmbc_parse_loop_normal:Nw } #1 \s__projetmbc_stop } \cs_new:Npn __projetmbc_parse_loop_group:nw #1 { __projectmbc_gobble_to_mark:w #1 __projetmbc_parse_loop_final:w \s__projetmbc_mark { \exp_not:n {#1} } __projetmbc_parse_loop_collect:w } \use:n { \cs_new:Npn __projetmbc_parse_loop_space:w } ~ { ~ __projetmbc_parse_loop_collect:w } \cs_new:Npn __projetmbc_parse_loop_normal:Nw #1 { \cs_if_exist:cTF { macro \str_uppercase:n {#1} } { __projetmbc_brace_close: \exp_not:c { macro \str_uppercase:n {#1} } __projetmbc_brace_open: } { \exp_not:n {#1} } __projetmbc_parse_loop_collect:w } \cs_new:Npn __projetmbc_parse_loop_final:w #1 \s__projetmbc_stop { __projetmbc_brace_close: } \NewExpandableDocumentCommand \parseanduse { m } { \projetmbc_parse_loop:n {#1} } \ExplSyntaxOff

\usepackage{xcolor}

\begin{document}

\parseanduse{o1r2a3}

same as

\macroO{1}\macroR{2}\macroA{3}

\bigskip

\parseanduse{o1r2a3o11r222a3333}

same as

\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}

\parseanduse{o\textcolor{red}{abc}1r2a3}

same as

\macroO{\textcolor{red}{abc}1}\macroR{2}\macroA{3}

\end{document}

Also, a reduced version that only works for the original input syntax (only N-type arguments):

\documentclass[12pt,a4paper]{article}

\NewDocumentCommand{\macroO}{m}{Option [#1] ,} \NewDocumentCommand{\macroR}{m}{Option $<$#1$>$ ,} \NewDocumentCommand{\macroA}{m}{Argument #1 ,}

\ExplSyntaxOn \msg_new:nnn { projetmbc } { unknown-function } { Unknown~ function~ `#1'.~ Giving~ up. } \scan_new:N \s__projetmbc_stop \scan_new:N \s__projetmbc_mark \cs_new:Npn __projectmbc_gobble_to_stop:w #1 \s__projetmbc_stop {} \cs_new:Npn __projectmbc_gobble_to_mark:w #1 \s__projetmbc_mark {} % these two macros will leave one unmatched brace when fully expanded, this % works because TeX doesn't check for brace balancing when gobbling the % remainder of an \if-construct. \cs_new:Npn __projetmbc_brace_open: { { \if_false: } \fi: } \cs_new:Npn __projetmbc_brace_close: { \if_false: { \fi: } } \cs_new:Npn \projetmbc_parse_loop:n #1 { % the loop works inside an expansion context which is started here, as the % end marker we use \s__projetmbc_mark, and as the terminal marker we use % \s__projetmbc_stop (to gobble the remainder of the current iteration) \use:e { __projetmbc_parse_loop:N #1 \s__projetmbc_mark \s__projetmbc_stop } } % the first iteration differs from consecutive ones. It is enforced that the % argument starts with one of the macros, else an error is thrown and the % remainder gobbled \cs_new:Npn __projetmbc_parse_loop:N #1 { % check whether macro exists \cs_if_exist:cTF { macro \str_uppercase:n {#1} } { % if so build the name and leave an unmatched opening brace (since we're % in an expansion context this does no harm as we will have it closed % when we're done) \exp_not:c { macro \str_uppercase:n {#1} } __projetmbc_brace_open: __projetmbc_parse_loop_aux:N } { \msg_expandable_error:nnn { projetmbc } { unknown-function } {#1} __projectmbc_gobble_to_stop:w } } \cs_new:Npn __projetmbc_parse_loop_aux:N #1 { % all following iterations must check whether the loop is done. This is done % in a very fast manner. Assuming that no valid user input will contain the % end marker \s__projetmbc_mark, #1 can only be exactly that token when % we're done. So in that case only #1 is gobbled and the final action % executed, else everything until the first \s__projetmbc_mark is gobbled. __projectmbc_gobble_to_mark:w #1 __projetmbc_parse_loop_final:w \s__projetmbc_mark \cs_if_exist:cTF { macro \str_uppercase:n {#1} } { % if the current token is one of the macro starting ones we need to % close the argument of the preceding macro. Afterwards build the new % macro and start its argument. __projetmbc_brace_close: \exp_not:c { macro \str_uppercase:n {#1} } __projetmbc_brace_open: } % everything else should just remain there, we protect it from further % expanding. { \exp_not:n {#1} } % call the next iteration __projetmbc_parse_loop_aux:N } % the final action needs to close the argument of the last macro after removing % the remainder of the last iteration from the input \cs_new:Npn __projetmbc_parse_loop_final:w #1 \s__projetmbc_stop { __projetmbc_brace_close: } \NewExpandableDocumentCommand \parseanduse { m } { \projetmbc_parse_loop:n {#1} } \ExplSyntaxOff

\begin{document}

\parseanduse{o1r2a3}

same as

\macroO{1}\macroR{2}\macroA{3}

\bigskip

\parseanduse{o1r2a3o11r222a3333}

same as

\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333}

\end{document}

Result of the second version:

enter image description here

Skillmon
  • 60,462
  • If I understand well in your code you loop over the argument and when the good character is found you continue to find the integer after suchas to apply the good macro. Am I right? – projetmbc Jul 05 '21 at 20:05
  • Your code is hard to maintain but I will keep it as an exercise. I would like to write a french tutorial about LaTeX3. Being a noob, this should be an easy-to-follow tutorial. :-) – projetmbc Jul 05 '21 at 20:08
  • 1
    So no looping with brace tricks? How unfortunate :) If you want to understand this, you should start with the second, way more simple variant. If I find the time later today I'll add some explanations to the code. – Skillmon Jul 06 '21 at 06:49
  • Great! Thanks a lot for the clarifications. – projetmbc Jul 06 '21 at 07:39
  • @projetmbc I've included some wordy explanation. – Skillmon Jul 06 '21 at 19:01
5

Easy with a token cycle.

\documentclass[12pt,a4paper]{article}

\NewDocumentCommand{\macroO}{m}{Option [#1] ,} \NewDocumentCommand{\macroR}{m}{Option $<$#1$>$ ,} \NewDocumentCommand{\macroA}{m}{Argument #1 ,}

\usepackage{tokcycle} \newcommand\parseanduse[1]{\tokcycle{% \ifx o##1\def\z{}\parsearg{O}\else \ifx r##1\def\z{}\parsearg{R}\else \ifx a##1\def\z{}\parsearg{A}\fi\fi\fi }{}{}{}{#1}\the\cytoks}

\def\parsearg#1{% \tcpopappto\z \tcpeek\zz \tctestifcatnx 0\zz {\parsearg{#1}}% {\csname macro#1\expandafter\endcsname\expandafter{\z}}}

\begin{document}

\parseanduse{o1r2a3}

same as

\macroO{1}\macroR{2}\macroA{3}

\bigskip \parseanduse{o1r2a3o11r222a3333}

same as

\macroO{1}\macroR{2}\macroA{3}\macroO{11}\macroR{222}\macroA{3333} \end{document}

enter image description here

And if one adopted the convention of the macro suffix letters having the same letter-case as the shorthand (taken here as lowercase), the token cycle would be even easier:

\documentclass[12pt,a4paper]{article}

\NewDocumentCommand{\macroo}{m}{Option [#1] ,} \NewDocumentCommand{\macror}{m}{Option $<$#1$>$ ,} \NewDocumentCommand{\macroa}{m}{Argument #1 ,}

\usepackage{tokcycle} \newcommand\parseanduse[1]{% \tokcycle{\def\z{}\parsearg{##1}}{}{}{}{#1}\the\cytoks}

\def\parsearg#1{% \tcpopappto\z \tcpeek\zz \tctestifcatnx 0\zz {\parsearg{#1}}% {\csname macro#1\expandafter\endcsname\expandafter{\z}}}

\begin{document}

\parseanduse{o1r2a3}

same as

\macroo{1}\macror{2}\macroa{3}

\bigskip \parseanduse{o1r2a3o11r222a3333}

same as

\macroo{1}\macror{2}\macroa{3}\macroo{11}\macror{222}\macroa{3333} \end{document}

4

Just for comparison, what you can do only with TeX primitives:

\def\macroO#1{Option [#1], }
\def\macroR#1{Option $<$#1$>$, }
\def\macroA#1{Argument #1, }

\def\parseanduse#1{\parseanduseX#1or} \def\parseanduseX o{\parseanduseO} \def\parseanduseO #1r{\ifx^#1^\else\macroO{#1}\expandafter\parseanduseR\fi} \def\parseanduseR #1a{\macroR{#1}\parseanduseA} \def\parseanduseA #1o{\macroA{#1}\parseanduseO}

% Test: \parseanduse{o1r2a3o11r222a3333}

OK, the following second example accepts the "commands" o, r, a in arbitrary order and it is fully expandable and uses only TeX primitives. We don't need Expl3.

\def\afterfi#1#2\fi{\fi#1}
\def\isdigit #1#2{%
   \ifnum #21\else0\fi=\ifnum`#1>`9 0\else \ifnum`#1<`0 0\else 1\fi\fi\space
}
\def\scandigits#1#2#3{\isdigit#3\iftrue
   \afterfi{\scandigits#1{#2#3}}\else\afterfi{#1{#2}#3}\fi
}
\def\parseanduse#1{\parseanduseX#1E}
\def\parseanduseX#1{\ifx E#1\else
   \afterfi{\expandafter\scandigits\csname macro#1\endcsname{}}\fi
}
\def\macroo#1{\macroO{#1}\parseanduseX}
\def\macror#1{\macroR{#1}\parseanduseX}
\def\macroa#1{\macroA{#1}\parseanduseX}

\def\macroO#1{Option [#1], } \def\macroR#1{Option $<$#1$>$, } \def\macroA#1{Argument #1, }

\parseanduse{o1r2a3o11r222a3333a13}

wipet
  • 74,238