In case you are interested in some sort of syntax-error-management—which I would probably not be as the code of David Carlisle is considerably shorter and faster as long as the user obeys the rules;-)—I can offer a rather slow \romannumeral
-expansion-based routine
\expandtoA{⟨sequence of numbers denoted by digits and separated from each other by properly matched not nested parentheses⟩}{⟨tokens in case of error⟩}
which does without whatsoever TeX-extensions and delivers the result after two expansion-steps/after two "hits" by \expandafter
.
Digits and parentheses in the 1st argument must be explicit character tokens of category code 12(other).
Empty pairs of matched unnested parentheses are allowed.
Spaces are not allowed.
In case the 1st argument is not of the described pattern, ⟨tokens in case of error⟩ will be delivered instead of a "conversion" to "A".
You can use ⟨tokens in case of error⟩ for triggering a LaTeX-error-message or whatever.
There definitely is room for improvement/shortcuts.
\documentclass{article}
\makeatletter
%%=============================================================================
%% Paraphernalia:
%% \UD@firstoftwo, \UD@secondoftwo, \UD@Exchange, \UD@PassFirstToSecond,
%% \UD@stopromannumeral, \UD@CheckWhetherNull,
%% \UD@CheckWhetherLeadingTokens, \UD@replicate
%%=============================================================================
\newcommand\UD@firstoftwo[2]{#1}%
\newcommand\UD@secondoftwo[2]{#2}%
\newcommand\UD@Exchange[2]{#2#1}%
\newcommand\UD@PassFirstToSecond[2]{#2{#1}}%
\@ifdefinable\UD@stopromannumeral{\chardef\UD@stopromannumeral=`\^^00}%
%%-----------------------------------------------------------------------------
%% Check whether argument is empty:
%%.............................................................................
%% \UD@CheckWhetherNull{<Argument which is to be checked>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is empty>}%
%% {<Tokens to be delivered in case that argument
%% which is to be checked is not empty>}%
%%
%% The gist of this macro comes from Robert R. Schneck's \ifempty-macro:
%% <https://groups.google.com/forum/#!original/comp.text.tex/kuOEIQIrElc/lUg37FmhA74J>
\newcommand\UD@CheckWhetherNull[1]{%
\romannumeral\expandafter\UD@secondoftwo\string{\expandafter
\UD@secondoftwo\expandafter{\expandafter{\string#1}\expandafter
\UD@secondoftwo\string}\expandafter\UD@firstoftwo\expandafter{\expandafter
\UD@secondoftwo\string}\expandafter\UD@stopromannumeral\UD@secondoftwo}{%
\expandafter\UD@stopromannumeral\UD@firstoftwo}%
}%
%%-----------------------------------------------------------------------------
%% Check whether argument's leading tokens form a specific
%% token-sequence that does neither contain explicit character tokens of
%% category code 1 or 2 nor contain tokens of category code 6:
%%.............................................................................
%% \UD@CheckWhetherLeadingTokens{<argument which is to be checked>}%
%% {<a <token sequence> without explicit
%% character tokens of category code
%% 1 or 2 and without tokens of
%% category code 6>}%
%% {<internal token-check-macro>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked> has
%% <token sequence> as leading tokens>}%
%% {<tokens to be delivered in case
%% <argument which is to be checked>
%% does not have <token sequence> as
%% leading tokens>}%
\newcommand\UD@CheckWhetherLeadingTokens[3]{%
\romannumeral\UD@CheckWhetherNull{#1}%
{\expandafter\UD@stopromannumeral\UD@secondoftwo}%
{%
% Let's nest things into \UD@firstoftwo{...}{} to make sure they are nested in braces
% and thus do not disturb when the test is carried out within \halign/\valign:
\expandafter\UD@firstoftwo\expandafter{%
\expandafter\expandafter\expandafter\UD@stopromannumeral
\romannumeral
\expandafter\UD@secondoftwo\string{\expandafter\UD@@CheckWhetherLeadingTokens#3{\relax}#1#2}{}}{}%
}%
}%
\newcommand\UD@@CheckWhetherLeadingTokens[1]{%
\expandafter\UD@CheckWhetherNull\expandafter{\UD@firstoftwo{}#1}%
{\UD@Exchange{\UD@firstoftwo}}{\UD@Exchange{\UD@secondoftwo}}%
{\expandafter\expandafter\expandafter\UD@stopromannumeral
\expandafter\expandafter\expandafter}%
\expandafter\UD@secondoftwo\expandafter{\string}%
}%
%%-----------------------------------------------------------------------------
%% \UD@internaltokencheckdefiner{<internal token-check-macro>}%
%% {<token sequence>}%
%% Defines <internal token-check-macro> to snap everything
%% until reaching <token sequence>-sequence and spit that out
%% nested in braces.
%%-----------------------------------------------------------------------------
\newcommand\UD@internaltokencheckdefiner[2]{%
\@ifdefinable#1{\long\def#1##1#2{{##1}}}%
}%
%------------------------------------------------------------------------------
% \UD@replicate{<number>}{<tokens>}
%------------------------------------------------------------------------------
\newcommand\UD@replicateloop[3]{%
\if m#3\expandafter\UD@firstoftwo\else\expandafter\UD@secondoftwo\fi
{\UD@replicateloop{#1}{#2#1}}{\UD@stopromannumeral#2}%
}%
\newcommand\UD@replicate[2]{%
\romannumeral
\expandafter\UD@Exchange\expandafter{\romannumeral\number\number#1 000}%
{\UD@replicateloop{#2}{}}\relax
}%
%------------------------------------------------------------------------------
\UD@internaltokencheckdefiner{\UD@CheckLeftParen}{(}%
\UD@internaltokencheckdefiner{\UD@CheckRightParen}{)}%
\UD@internaltokencheckdefiner{\UD@CheckZero}{0}%
\UD@internaltokencheckdefiner{\UD@CheckOne}{1}%
\UD@internaltokencheckdefiner{\UD@CheckTwo}{2}%
\UD@internaltokencheckdefiner{\UD@CheckThree}{3}%
\UD@internaltokencheckdefiner{\UD@CheckFour}{4}%
\UD@internaltokencheckdefiner{\UD@CheckFive}{5}%
\UD@internaltokencheckdefiner{\UD@CheckSix}{6}%
\UD@internaltokencheckdefiner{\UD@CheckSeven}{7}%
\UD@internaltokencheckdefiner{\UD@CheckEight}{8}%
\UD@internaltokencheckdefiner{\UD@CheckNine}{9}%
%------------------------------------------------------------------------------
\newcommand\expandtoA[2]{%
\romannumeral\expandtoALoop{#1}{#2}{}{}{}%
}%
%------------------------------------------------------------------------------
\newcommand\expandtoALoop[5]{%
% #1 Remaining argument to examine
% #2 Tokens in case of error
% #3 Flag denoting how next number must terminate:
% Empty -> next number must terminate due to opening parenthesis or emptiness of remaining argument to examine.
% Not empty -> next number must terminate due to closing parenthesis.
% #4 Digits of next number gathered so far.
% #5 Result gathered so far.
\UD@CheckWhetherNull{#1}{%
\UD@CheckWhetherNull{#3}{%
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{\UD@replicate{#4}{A}}%
{\UD@stopromannumeral#5}%
}{\UD@stopromannumeral#2}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{(}{\UD@CheckLeftParen}{%
\UD@CheckWhetherNull{#3}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{\UD@replicate{#4}{A}}{\UD@stopromannumeral#5}%
}%
{\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{\relax}{}}%
}{\UD@stopromannumeral#2}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{)}{\UD@CheckRightParen}{%
\UD@CheckWhetherNull{#3}{\UD@stopromannumeral#2}{%
\expandafter\UD@PassFirstToSecond\expandafter{%
\romannumeral
\expandafter\expandafter\expandafter\UD@Exchange
\expandafter\expandafter\expandafter{\UD@replicate{#4}{A}}{\UD@stopromannumeral#5(})%
}%
{\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{}{}}%
}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{0}{\UD@CheckZero}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#40}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{1}{\UD@CheckOne}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#41}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{2}{\UD@CheckTwo}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#42}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{3}{\UD@CheckThree}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#43}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{4}{\UD@CheckFour}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#44}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{5}{\UD@CheckFive}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#45}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{6}{\UD@CheckSix}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#46}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{7}{\UD@CheckSeven}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#47}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{8}{\UD@CheckEight}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#48}{#5}%
}{%
\UD@CheckWhetherLeadingTokens{#1}{9}{\UD@CheckNine}{%
\expandafter\expandtoALoop\expandafter{\UD@firstoftwo{}#1}{#2}{#3}{#49}{#5}%
}{%
\UD@stopromannumeral#2%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
}%
%------------------------------------------------------------------------------
\makeatother
\begin{document}
\message{\detokenize\expandafter\expandafter\expandafter{\expandtoA{1(2)3(4)5(6)7(8)9(10)()(11)}{ErrorText}}}%
\message{\detokenize\expandafter\expandafter\expandafter{\expandtoA{1(2)3(4)5(6)7(89(10)()(11)}{ErrorText}}}%
\end{document}
The above code delivers the messages
A(AA)AAA(AAAA)AAAAA(AAAAAA)AAAAAAA(AAAAAAAA)AAAAAAAAA(AAAAAAAAAA)()(AAAAAAAAAAA)
and
ErrorText
to the terminal. (ErrorText with the second call as here parentheses are not properly matched.)
2(11)
? Should it read "one and one" (AA(AA)
) or "eleven" (AA(AAAAAAAAAAA)
)? – Phelype Oleinik Jan 23 at 18:28AA(AAAAAAAAAAA)
. But, If you have a solution for any of them, I'm taking it. :-) – perror Jan 23 at 18:30\foreach
does not work by expansion so the name you suggest here is misleading. – David Carlisle Jan 23 at 18:55