5

Partly inspired by this question, I got interested in the question of whether we could (hypothetically) switch to a new LaTeX syntax where braces around arguments were mandatory. That is, no more \frac12; always \frac{1}{2}. In many ways, this syntax would be so much better and cause much less confusion.

Anyway, the first (very ugly) solution that came to my mind was to abuse g-type arguments (see below). What would be the best/most robust solution?

\documentclass{article}

\usepackage{xparse}

\NewDocumentCommand\foo{g}{% \IfValueTF{#1}{% foo(#1)% }{% \PackageError{foo}{Missing braces around argument}% {This is the newest trend in LaTeX syntax}% }% }

\begin{document}

\foo{bar} % prints foo(bar)

\foo1 % issues an error

\end{document}

enter image description here

Gaussler
  • 12,801

4 Answers4

9

Error recovery if you scroll past the error is a bit brutal but..

\foo 1 gives:

 ! Package foo Error: unexpected text before brace.
\documentclass{article}

\begin{document}

\long\def\foo#1#{% \if\relax\detokenize{#1}\relax\expandafter\foox \else\PackageError{foo}{unexpected text before brace}{you are doomed}\fi} \def\foox#1{foo(#1)}

\foo{bar}

\foo1

\end{document}

David Carlisle
  • 757,742
  • Maybe the error recovery is better if you place \foox at the end, so it's executed in any case. – egreg Oct 12 '21 at 13:49
  • @egreg ah yes perhaps. (I also had a version that tried to re-instate the tokens before the brace if they were there but if error recovery gets to be more than half of the actual code sympathy for people who scroll past an error starts to wane...) – David Carlisle Oct 12 '21 at 13:52
  • @egreg I’m very disappointed that you didn’t provide your own take on this problem. Perhaps you were satisfied with David’s solution? ;-) – Gaussler Oct 12 '21 at 18:55
  • 1
    @Gaussler I don't think it's worth the pain: users will not do as recommended anyway and many even ignore error messages. – egreg Oct 12 '21 at 19:52
  • @egreg No, but this is a theoretical question about how to make TeX macros behaving in a specific way, not a proposal on how to change it. – Gaussler Oct 13 '21 at 10:07
  • If the .tex-input-file in question after \foo doesn't have anything whose tokenization yields explicit {-character-tokens of catcode 1, then the resulting error-messages may be confusing for the unexperienced user. – Ulrich Diez Oct 18 '21 at 12:26
5

With a token cycle.

\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{tokcycle}
\newcommand\abortfoo{\tcpush{\empty\endfoo}}
\xtokcycleenvironment\foo
  {Bad syntax (unbraced character)\abortfoo}
  {\addcytoks{\fooaux{##1}}\abortfoo}
  {Bad syntax (unbraced control sequence)\abortfoo}
  {Bad syntax (unbraced space)\abortfoo}
  {\stripgroupingtrue}
  {}
\newcommand\fooaux[1]{Foo argument ``\detokenize{#1}''}
\begin{document}
\foo A

\foo\today

\foo\tcsptoken

\foo{X}

\foo{\today} \end{document}

enter image description here

4

Another mechanism that automatically gives a missing { error and doesn't eat the whole document looking for a { is

\documentclass{article}

\def\foo{\afterassignment\foox\toks0 } \def\foox{foo(\the\toks0)}

\begin{document}

\foo{bar}

\foo1 \end{document}

which gives

! Missing { inserted.
<to be read again> 
                   1
l.10 \foo1
David Carlisle
  • 757,742
  • With the left brace token before the doesn't need to be explicit - after \let\bgroup={ you can do: \foo\bgroup bar}. But the questioner didn't specify "explicitness" as a part of the requirement, so this is just nitpicking from my side. – Ulrich Diez Oct 17 '21 at 12:48
  • 1
    @UlrichDiez true I also never got an answer to my question in comments under the question about the xii catcode regime. jfooFxP – David Carlisle Oct 17 '21 at 12:51
  • Do you have a trick for preventing unexpected/undesired expansion while TeX is scanning for 's (which may as well be implicit)? (I've racked my brain over this, but so far haven't come up with a good idea.) – Ulrich Diez Oct 17 '21 at 19:06
  • 1
    @UlrichDiez you can do this which sort of does what you say in the \hmm case with the brace hidden in a macro, although it doesn't stop the \empty case (as the noexpand token is <filler> `\documentclass{article}

    \newtoks\zztoks

    \def\foo{\zztoks = \noexpand}

    \begin{document}

    \foo{abc} \showthe\zztoks

    \foo\empty{abc empty} \showthe\zztoks

    \def\hmm{{xyz hmm}}

    \foo\hmm \showthe\zztoks

    \end{document}`

    – David Carlisle Oct 18 '21 at 07:23
  • I thought about \noexpand, too, but - as your \foo\hmm-case shows - treating things as due to applying \noexpand may cause ! Missing { inserted and thus annihilate prevention of probably "eating" the entire remaining .tex-input-file while scanning for a that matches the inserted left brace. Besides this \foo\hmm{ghi} sort of works out (even if \hmm is \outer) in a way probably not expected by the user - \showthe\zztoks reveals > ghi. – Ulrich Diez Oct 18 '21 at 11:50
  • @UlrichDiez yes I know, but best I could manage in the time:-) – David Carlisle Oct 18 '21 at 13:03
1

Besides the problem of detecting the desired kind (explicit/implicit/specific character code) of catcode-1-left braces and catcode-2-right braces another problem might be detecting proper nesting of left braces and right braces.

(Lua-extensions come to my mind but afaik when looking ahead via Lua-code, category-codes of what will be character tokens for the TeX-engine will not be taken into account.)

A mechanism which works out 100%-reliably in expansion-contexts also and which serves for detecting whether the next token in the token stream is a(n explicit) character-token of category code 1(beginning of group), probably of character-code 123 ({) ), and issuing only a tailored error-message (and nothing else) if this is not the case, requires 100%-reliable expandable look-ahead at the next token of the token-stream.
This is not possible with traditional TeX-engines.

Using {-delimited arguments via #{-notation may result in TeX-generated error-messages about things not matching definitions preceding/following your tailored error-message in case of no explicit {1 being present.

Methods based on grabbing ⟨general text⟩ tend either (\toks0=...} to be not expandable or (\scantokens etc ) to not preserve catcode-régime or (\uppercase/\lowercase) to not preserve character-codes. Besides this, with ⟨general text⟩ the left-brace-token before the ⟨balanced text⟩ can be explicit or implicit and expansion is not suppressed while scanning for the ⟨general text⟩'s left-brace-token.

Methods based on \@ifnextchar or \let or \futurelet are based on assignments and thus are not expandable. If expandability is not of interest, then e.g., using \futurelet, obeying its subtleties, you can have TeX look ahead at the meaning of the next token and if that denotes a catcode-1-token probably apply \string or \meaning before examination of (the character-code) of subsequent tokens by means of argument-processing macros before doing whatsoever trickery for re-inserting the stringified token under correct catcode-régime. Problems with distinguishing explicit character-tokens from control-symbol-tokens/single-letter-control-word-tokens in case of \escapechar currently having a negative value might occur. Problems with distinguishing explicit character-tokens from active pendants that are let equal to them might occur.


In your question you referred to Syntax of TeX for primitives that demand braces where with

\def\seq{abcdef}%
\uppercase\seq  %

problems occur because while scanning for \upppercase's ⟨general text⟩'s left-brace-token expansion with \seq as "starting-point" does not yield a token-sequence beginning with ⟨filler⟩ trailed by a left-brace-token (which may be implicit or explicit), trailed by ⟨balanced text⟩ trailed by a ⟨right brace⟩ (which must be explicit).

While both expandably and 100%-reliably detecting whether the next token of the token-stream produced by TeX's mouth is, e.g., explicit {1 is not possible, it is possible to test in the stage of expansion, i.e., in TeX's gullet, if a set of tokens coming from an already grabbed macro argument (where outermost surrounding braces were already removed during grabbing) forms ⟨balanced text⟩ that is nested between explicit character-tokens of catcode 1/2:

% A TeX-engine is needed which brings along the \expanded-primitive.

\tt \hyphenchar\font=`-\relax \emergencystretch 3em \frenchspacing \parindent=0pt

\chardef\stopromannumeral=`^^00 \long\def\firstoftwo#1#2{#1}% \long\def\secondoftwo#1#2{#2}% %%----------------------------------------------------------------------------- %% Check whether argument forms <balanced text> that is nested %% between a pair of matching explicit catcode1/2-character- %% tokens: %%............................................................................. %% \CheckWhetherNestedInExplicitBraces{<Argument which is to be checked>}% %% {<Tokens to be delivered in case that argument %% which is to be checked is nested between %% whatsoever explicit braces>}% %% {<Tokens to be delivered in case that argument %% which is to be checked is not nested between %% whatsoever explicit braces>}% \long\def\CheckWhetherNestedInExplicitBraces#1{% \romannumeral\expandafter\secondoftwo\expandafter{\expandafter{\string#1.}% \expandafter\firstoftwo\expandafter{\expandafter\secondoftwo\string}% \expandafter\secondoftwo\string{\expandafter\expandafter\expandafter \secondoftwo\expandafter\expandafter\expandafter{\expandafter\expandafter \expandafter{\expandafter\string\firstoftwo{}#1}\expandafter\secondoftwo \string}\expandafter\firstoftwo\expandafter{\expandafter\secondoftwo\string}% \expandafter\stopromannumeral\secondoftwo}{\expandafter\stopromannumeral \firstoftwo}}{\expandafter\stopromannumeral\secondoftwo}% }%

  1. \CheckWhetherNestedInExplicitBraces{A{B}C}{Nested in explicit braces}{Not nested in explicit braces}%

  2. \CheckWhetherNestedInExplicitBraces{}{Nested in explicit braces}{Not nested in explicit braces}%

  3. \CheckWhetherNestedInExplicitBraces{ }{Nested in explicit braces}{Not nested in explicit braces}%

  4. \CheckWhetherNestedInExplicitBraces{{A}BC}{Nested in explicit braces}{Not nested in explicit braces}%

  5. \CheckWhetherNestedInExplicitBraces{ABC}{Nested in explicit braces}{Not nested in explicit braces}%

  6. \CheckWhetherNestedInExplicitBraces{{A}{B}{C}}{Nested in explicit braces}{Not nested in explicit braces}%

  7. \CheckWhetherNestedInExplicitBraces{{ABC} }{Nested in explicit braces}{Not nested in explicit braces}%

  8. \CheckWhetherNestedInExplicitBraces{ {ABC} }{Nested in explicit braces}{Not nested in explicit braces}%

  9. \CheckWhetherNestedInExplicitBraces{ {ABC}}{Nested in explicit braces}{Not nested in explicit braces}%

  10. \CheckWhetherNestedInExplicitBraces{{ABC}}{Nested in explicit braces}{Not nested in explicit braces}%

\noindent\hrulefill\null

\def\seq{{abcdef}}% \expandafter\CheckWhetherNestedInExplicitBraces\expandafter{\expanded{\seq}}{% \uppercase\seq }{% %\errmessage {Here could be a customized error-message about things not being nested between explicit brace tokens (although with \string\uppercase\space an implicit left brace would do as well.)}% %Or try one of the following: %\uppercase\expandafter{\seq}% %\uppercase\expandafter{\expanded{\seq}}% }%

\noindent\hrulefill\null

\def\seq{abcdef}% \expandafter\CheckWhetherNestedInExplicitBraces\expandafter{\expanded{\seq}}{% \uppercase\seq }{% %\errmessage {Here could be a customized error-message about things not being nested between explicit brace tokens (although with \string\uppercase\space an implicit left brace would do as well.)}% %Or try one of the following: %\uppercase\expandafter{\seq}% %\uppercase\expandafter{\expanded{\seq}}% }%

\bye

enter image description here

Ulrich Diez
  • 28,770