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

9
  • does it have to be expandable? (this makes it rather harder) you can check for { with \@ifnextchar but that's not expandable also if (as occurs in some documents F is catcode 1 and P is catode 2 then do you accept \frac F aP F b P Commented Oct 12, 2021 at 13:18
  • @DavidCarlisle Well, feel free to provide both expandable and non-expandable solutions.
    – Gaussler
    Commented Oct 12, 2021 at 13:21
  • 4
    “In many ways, this syntax would be so much better and cause much less confusion.” –The syntax would still be a big heap of cobbled-together hacks. If you want a real improvement over LaTeX's syntax, you need to switch to a frontend that actual has a proper syntax (grammar), with a proper AST structure instead of all that macro expansion havoc. Commented Oct 13, 2021 at 12:51
  • 1
    Uh, thanks for the edit... I changed foo to frac but I missed that.
    – campa
    Commented Oct 14, 2021 at 8:28
  • 1
    @Dr.ManuelKuehner The question was mostly TeX-theoretical. There is no real satisfactory solution to this problem with the current design of TeX. All of these solutions are good workarounds, but none of them are good enough for providing a robust interface for practical use.
    – Gaussler
    Commented Dec 7, 2021 at 17:14

4 Answers 4

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}
6
  • Maybe the error recovery is better if you place \foox at the end, so it's executed in any case.
    – egreg
    Commented Oct 12, 2021 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...) Commented Oct 12, 2021 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
    Commented Oct 12, 2021 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
    Commented Oct 12, 2021 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
    Commented Oct 13, 2021 at 10:07
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

3
  • 1
    A tokcycle solution from you – who would have seen that coming? ;-)
    – Gaussler
    Commented Oct 12, 2021 at 14:32
  • 3
    I would think many would have expected it, @Gaussler, except of course those who expected to see a stackengine solution from me. Commented Oct 12, 2021 at 14:45
  • 1
    Perhaps I should also provide a semantex solution.
    – Gaussler
    Commented Oct 12, 2021 at 14:46
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
6
  • With <general text> the left brace token before the <balanced text> 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. Commented Oct 17, 2021 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 Commented Oct 17, 2021 at 12:51
  • Do you have a trick for preventing unexpected/undesired expansion while TeX is scanning for <general text>'s <left brace> (which may as well be implicit)? (I've racked my brain over this, but so far haven't come up with a good idea.) Commented Oct 17, 2021 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} Commented Oct 18, 2021 at 7:23
  • I thought about \noexpand, too, but - as your \foo\hmm-case shows - treating things as <filler> 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 <right brace> 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. Commented Oct 18, 2021 at 11:50
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}%
}%


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

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

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

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

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

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

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

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

09. \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

11.
\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

12.
\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

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .