# How can I create an oddly-delimited macro, using xparse?

I have a partial version of this macro working, without xparse, but I needed to tweak it and it got too unwieldy, so I'm trying to use xparse instead. It's admittedly a farfetched macro, but I'm trying to learn how xparse works... Suppose I'm trying to typeset references to sentences and their variants, like so:

X1
X2*
X3**
X4'
X56''


where:

• There can be some number of stars (indicating malformed variants), the number of these is unknown a priori
• There can be some number of primes (indicating well-formed variants), the number of these is also unknown
• The number afterwards may be more than one digit. The number is terminated by a space, a comma, semicolon, or other punctuation. For example, $(\X'3; \X**45)$.
• X could be any alphabetic string.
• It should preferably work in both text and math modes.

I'd like to input these as \X**3, or \X'4. (I'm choosing to place the punctuation first, because it's easier to convince TeX to parse things that way...) Suppose I already have an internal macro to typeset the actual thing:

\newcommand\@X[2]{ % #1 is the decorations, #2 is the number ...
\mbox{\ifmmode\text{X#2#1}\else X#2#1\fi}
}


Now I'm trying to define the user visible macro. My first hunch was

\DeclareDocumentCommand\X{ s s t' t' m }{ stuff }


but:

• This only handles two stars and two primes
• Actually, I can't seem to make it work even with just those two stars and primes :( It's giving me errors about missing \$ signs, but the math-mode code I had in \@X was working before...
• The m argument is only grabbing the first digit, rather than the whole number token as my old version used to do.

Is there a way to program this using xparse, or is this just too weird a macro to try to support?

Edited to add: I got the following definitions working last night. They work for only ' and numbers, but don't use xparse.

\newcommand\X{\@ifnextchar'\@Xprime{\@X{}}}
\newcommand\@Xprime[1]{\@ifnextchar'{\def\temp{\@Xprime{#1{'}}}\ex\temp\@gobble}{\@Xdigit{#1}}}
\newcommand\@Xdigit[2]{%
\@ifnextchar1{\def\temp{\@Xdigit{#1}{{#2}1}}\ex\temp\@gobble}{%
\@ifnextchar2{\def\temp{\@Xdigit{#1}{{#2}2}}\ex\temp\@gobble}{%
\@ifnextchar3{\def\temp{\@Xdigit{#1}{{#2}3}}\ex\temp\@gobble}{%
\@ifnextchar4{\def\temp{\@Xdigit{#1}{{#2}4}}\ex\temp\@gobble}{%
\@ifnextchar5{\def\temp{\@Xdigit{#1}{{#2}5}}\ex\temp\@gobble}{%
\@ifnextchar6{\def\temp{\@Xdigit{#1}{{#2}6}}\ex\temp\@gobble}{%
\@ifnextchar7{\def\temp{\@Xdigit{#1}{{#2}7}}\ex\temp\@gobble}{%
\@ifnextchar8{\def\temp{\@Xdigit{#1}{{#2}8}}\ex\temp\@gobble}{%
\@ifnextchar9{\def\temp{\@Xdigit{#1}{{#2}9}}\ex\temp\@gobble}{%
\@ifnextchar0{\def\temp{\@Xdigit{#1}{{#2}0}}\ex\temp\@gobble}{%
\@X{#1}{#2}\xspace}%
}}}}}}}}}}


The idea was for \@Xprime to scrape up all the primes, one at a time and in brace groups to prevent them from turning into double-quotes, and accumulate them into its argument. It then passes that string off to \@Xdigit, which scrapes up all digits (digits don't have their own catcode, so I couldn't figure out any shorter test than this one...) and accumulates them into its second argument. (I didn't intend for each digit to be in a brace group, but got stuck on how to append a digit to #2.) Finally, once it's got both the primes and the digits, it calls \@X to typeset them, and then uses \xspace to restore space handling.

I couldn't figure out an equivalent \@Xstar, so to speak, because \@ifnextchar* didn't seem to do what I meant, and I think \@ifstar only gobbles spaces following a command, not following the arguments to a command.

Edited I've figured out the following version, which behaves correctly, but still doesn't use xparse. It uses \futuredef from etextools, and has the advantage of being much more concise and understandable than the previous version... I'm still curious how this would work with 'xparse.

\DeclareRobustCommand\X{\@ifstar\@Xstar\@Xnostar}
\newcommand\@Xstar{\futuredef[*]\@Xstars{\futuredef[1234567890]\@Xdigits{\@X{\@Xstars}{\@Xdigits}}}}
\newcommand\@Xnostar{\@ifnextchar'\@Xprime{\futuredef[1234567890]\@Xdigits{\@X{}{\@Xdigits}}}}
\newcommand\@Xprime{\futuredef[']\@Xprimes{\futuredef[1234567890]\@Xdigits{\@X{\@Xprimes}{\@Xdigits}}}}
\newcommand\@X[2]{\mbox{\ifmmode\text{X#2#1}\else X#2#1\fi}\xspace}

-
Can I clarify the nature of your input. I take it that the number of * and ' is variable, and we don't know the maximum in advance? How is the end of the numerical part of the argument delimited? – Joseph Wright May 27 '11 at 7:29
@JosephWright, elaborating my question above to answer yours. A meta-question: besides @-tags in comments, how can I notify someone that editing my question was in response to their comment? – Ben Lerner May 27 '11 at 18:30
@Joseph: perhaps this could be achieved if xparse provided some "generic" argument specifier (I couldn't think of a good syntax)? Or the possibility of grabbing tokens one by one as long as they obey some property? – Bruno Le Floch May 27 '11 at 22:18
@BrunoLeFloch, @JosephWright: pondering out loud here, maybe something like ?{\test}, where ? is a mnemonic for "predicate", and \test is used as "\ifthenelse{\test{char}}{grab next char and keep going}{argument is completed}"? (Or something like that, appropriately latex3-ified...) – Ben Lerner Jun 15 '11 at 23:02
We discussed this on the LaTeX3 mailing list and neither @JosephWright nor me are very keen on supporting this kind of argument type. I do have some code, but it hacks deep into xparse, and needs some extra tweaking of parts of xparse that I don't know well in order to have a good syntax. In your case, do you have a reason not to prefer a more conventional syntax (e.g., \X**{23} defined by an argument spec l m)? – Bruno Le Floch Jun 15 '11 at 23:14

I'm not sure to understand exactly what you are looking for. Pershaps a simple parser like this?

\documentclass{article}
\usepackage[T1]{fontenc}
\makeatletter
\newcount\x@cnt
\def\X{\let\extra@arg\@empty\afterassignment\X@i\x@cnt0}
\def\X@i{\futurelet\nxt@tok\X@ii}
\def\X@ii{%
\else
\else\printargs
\fi
\fi}
\def\printargs{I saw \string\X\ then \fbox{\number\x@cnt} and \fbox{\extra@arg}.}
\makeatother
\begin{document}
\X54**text

\X2*text

\X123''text

\X text% the integer is 0''

\X4'text
\end{document}

-

After letting this sit a while, and with @BrunoLeFloch and @JosephWright weighing in, it appears the answer to this question is twofold:

1. Don't do that! (Certainly, the example in this question is not a solid use-case, and could better be done in other ways.) Part of the intent of xparse is to help transition to LaTeX3, and it does not intend to encourage even more arcane syntaxes if they can be avoided.
2. If you really want to do something like in this question, you can use \futuredef from etextools, as follows:

\DeclareRobustCommand\X{\@ifstar\@Xstar\@Xnostar}
\newcommand\@Xstar{\futuredef[*]\@Xstars{\futuredef[1234567890]\@Xdigits{\@X{\@Xstars}{\@Xdigits}}}}
\newcommand\@Xnostar{\@ifnextchar'\@Xprime{\futuredef[1234567890]\@Xdigits{\@X{}{\@Xdigits}}}}
\newcommand\@Xprime{\futuredef[']\@Xprimes{\futuredef[1234567890]\@Xdigits{\@X{\@Xprimes}{\@Xdigits}}}}
\newcommand\@X[2]{\mbox{\ifmmode\text{X#2#1}\else X#2#1\fi}\xspace}


How it works: \futuredef[chars]\x\y will collect all subsequent characters following \y as long as they are one of the characters in [chars], store the result in \x, and then execute \y. Under the hood, \futuredef uses the one-character \futurelet primitive, as does \@ifstar and \@ifnextchar, though the exact details of how they all work are mind-warping.

So in this definition, the initial \@ifstar chooses between accepting stars or primes, but does not consume any characters. \@Xstar uses \futuredef[*]\@Xstars to collect all stars into \@Xstars, then proceeds with the bracegroup, which uses \futuredef[1234567890]\@Xdigits to collect all digits into \@Xdigits, and then calls \@X accordingly. \@Xnostar uses \@ifnextchar' to do the same thing for primes as \@ifstar did for stars. Finally, \@Xprime does for primes what \@Xstar did for stars.

(In general, etextools` has some interesting, powerful, low-level tools that I had no idea were achievable in current TeX compilers; it's a handy package to know.)

-