3

\findtoken is a macro that finds the index (starting at zero) of (the full expansion of) its parameter #1 in a predefined list ("a", "b", "c", "d"); it stops compilation if this expansion isn't in the list.

It seems to work fine for typesetting this index, but I'd like to be able to also assign this number to a \count, such that \newcount\mycount \mycount=\findtoken{c}\relax \the\mycount would typeset "2".

I've tried many combinations of \expandafter in the assignment, and I've read Why does the TeX scanner process tokens for register numbers and macro names differently? and How to write a TeX macro that accepts a number or a \count register as argument? , but couldn't come up with a way of doing that.

So, is it possible?

\documentclass[margin=2mm]{standalone}
\usepackage[T1]{fontenc}
\makeatletter

\newcount\mycount \newcommand*{\findtoken}[1]{% % input (#1): a b c d <anything else> % output: 0 1 2 3 error \begingroup % protect scratch macros \edef@tempa{\expanded{#1}}% @tempswafalse % did anything match? @tempcnta=\z@ % index of the list % loop @for@tempb:={a,b,c,d}\do{% % if they match, record the current index \ifx@tempb@tempa @tempswatrue \expandafter\aftergroup\the@tempcnta \fi \advance@tempcnta@ne }% end of loop \if@tempswa\else \STOPCOMPILATION \fi \endgroup }

\makeatother \begin{document} \def\lettera{a} Checking whether \string\findtoken\ works: \findtoken{\lettera}, \findtoken{a}, \findtoken{b}, \findtoken{c}, \findtoken{d}. % uncomment the next line to stop compilation: % \findtoken{x}

% now, trying to assign \findtoken's expansion into \mycount: % \mycount\findtoken{c}\relax % ^ what to write above?! ^

\string\mycount's value is: \the\mycount. \end{document}

enter image description here

4 Answers4

4

Other answers mention the core: your macro, if used in the syntax \register=\macro, must expand to the macro value, because the assignment is performed after expansion. No un-expandable control sequences (like \begingroup or \edef) may be set here.

I add a very short example of an expandable alternative to your macro. TeX has a very compact language.

\newcount\mycount

\def\findtoken #1{% \ifcase\numexpr \expandafter#1-a\relax 0\or 1\or 2\or 3\else \STOPCOMPILATION \fi }

\def\lettera{a} Checking whether \string\findtoken\ works: \findtoken{\lettera}, \findtoken{a}, \findtoken{b}, \findtoken{c}, \findtoken{d}. % uncomment the next line to stop compilation: % \findtoken{x}

% now, trying to assign \findtoken's expansion into \mycount: \mycount=\findtoken{c}\relax

\string\mycount's value is: \the\mycount.

Edit: Due to the comment I show another compact and expandable solution:

\newcount\mycount
\def\declcode#1#2{\expandafter\def\csname findtoken:#1\endcsname{#2}}
\def\findtoken#1{\ifcsname findtoken:#1\endcsname
   \csname findtoken:#1\endcsname \else \STOPCOMPILATION \fi
}

\declcode{a}{0} \declcode{b}{1} \declcode{c}{2} \declcode{d}{3}

\def\lettera{a} Checking whether \string\findtoken\ works: \findtoken{\lettera}, \findtoken{a}, \findtoken{b}, \findtoken{c}, \findtoken{d}. % uncomment the next line to stop compilation: % \findtoken{x}

% now, trying to assign \findtoken's expansion into \mycount: \mycount=\findtoken{c}\relax

\string\mycount's value is: \the\mycount.

jarnosc
  • 4,266
wipet
  • 74,238
  • hm, it seems this depends upon the list chars having consecutive charcodes, which is the case in my MWE but not in my actual-case... but that's my fault when reducing the problem to a MWE, I shouldn't have used consecutive letters. – Daniel Diniz Nov 21 '22 at 20:26
  • 1
    @DanielDiniz OK, I added another solution. – wipet Nov 22 '22 at 15:43
  • @wipet Now you cannot say that your second version is more compact than mine. ;-) – egreg Nov 22 '22 at 16:34
  • @egreg it depends on how do you describe "compact": wipet does not need to load latex3 o_O – jarnosc Nov 23 '22 at 00:24
  • @DanielDiniz what does the "actual case" look like? – jarnosc Nov 23 '22 at 00:30
  • non-consecutive, single ASCII letters (could be "gakm" -> "0123", etc) – Daniel Diniz Nov 23 '22 at 00:44
  • So, say, "google" should count "6" (letters could be repeated, not assigned one to one in a map). – jarnosc Nov 23 '22 at 06:59
  • that's not how the macro works. it will never receive "google" as input; it only takes single letters (other macros take care of that). it's not a macro to count letters in a word, it just converts a single letter into a number 0 <= n <= 9. when I wrote "gakm" -> "0123" I meant that it could be the mapping is "g"->0, "a"->1, "k"->2 and "m"->3. – Daniel Diniz Nov 23 '22 at 16:53
  • I see: perhaps spaces could make that point much clearer: "g o o g l e" yields "0 1 2 3 4 5". – jarnosc Nov 23 '22 at 21:52
  • ...? no, "g o o g l e" can't yield "0 1 2 3 4 5" because the number that a letter receives is always the same, it can't assign "0" to a "g" and then "3" to a "g". all the answers to this question have provided different ways of implementing this mapping of letters to integers 0<=n<=9, so I'd say it's pretty clear what the problem is, it could hardly be any clearer. – Daniel Diniz Nov 23 '22 at 23:09
2

If the macro is (as here) not written using expansion, you can work like xstring or pgf and leave the result in a macro which does expand:

\documentclass[margin=2mm]{standalone}
\usepackage[T1]{fontenc}
\makeatletter

\newcount\mycount \newcommand*{\findtoken}[1]{% % input (#1): a b c d <anything else> % output: 0 1 2 3 error \begingroup % protect scratch macros \edef@tempa{\expanded{#1}}% @tempswafalse % did anything match? @tempcnta=\z@ % index of the list % loop \xdef\findtokenresult{}% @for@tempb:={a,b,c,d}\do{% % if they match, record the current index \ifx@tempb@tempa @tempswatrue % \expandafter\aftergroup\the@tempcnta \xdef\findtokenresult{\the@tempcnta}% \fi \advance@tempcnta@ne }% end of loop \if@tempswa\else \STOPCOMPILATION \fi \endgroup }

\makeatother \begin{document} \def\lettera{a} Checking whether \string\findtoken\ works: \findtoken{\lettera}\findtokenresult, \findtoken{a}\findtokenresult, \findtoken{b}\findtokenresult, \findtoken{c}\findtokenresult, \findtoken{d}\findtokenresult. % uncomment the next line to stop compilation: % \findtoken{x}

% now, trying to assign \findtoken's expansion into \mycount: \findtoken{c}\mycount\findtokenresult\relax % ^ what to write above?! ^

\string\mycount's value is: \the\mycount. \end{document}

David Carlisle
  • 757,742
2

Use an expandable implementation.

\documentclass{article}
\usepackage[T1]{fontenc}

\ExplSyntaxOn

\NewExpandableDocumentCommand{\findtoken}{m} { \str_case_e:nnF { #1 } { {a}{0} {b}{1} {c}{2} {d}{3} } {\errorstopmode\ERROR} }

\ExplSyntaxOff

\newcount\mycount

\begin{document}

\def\lettera{a} Checking whether \string\findtoken\ works: \findtoken{\lettera}, \findtoken{a}, \findtoken{b}, \findtoken{c}, \findtoken{d}. % uncomment the next line to stop compilation: % \findtoken{x}

% now, trying to assign \findtoken's expansion into \mycount: \mycount\findtoken{c}\relax

\string\mycount's value is: \the\mycount.

\end{document}

enter image description here

egreg
  • 1,121,712
1
  • Is it possible? Yes.

  • Is it possible if you insist on using the \mycount\findtoken{a} syntax? No. (unless you rewrite the \findtoken macro to be fully expandable)

    See the following code.

    %! TEX program = pdflatex
    \documentclass[margin=2mm]{standalone}
    \usepackage[T1]{fontenc}
    \makeatletter
    

    \newcount\mycount \newcommand*{\findtokenx}[2]{% % input (#1): a b c d <anything else> % input (#2): a single token to be placed immediately before the value % output: 0 1 2 3 error \begingroup % protect scratch macros \aftergroup#2% \edef@tempa{\expanded{#1}}% @tempswafalse % did anything match? @tempcnta=\z@ % index of the list % loop @for@tempb:={a,b,c,d}\do{% % if they match, record the current index \ifx@tempb@tempa @tempswatrue \expandafter\aftergroup\the@tempcnta \fi \advance@tempcnta@ne }% end of loop \if@tempswa\else \STOPCOMPILATION \fi \endgroup }

    \newcommand*{\findtoken}[1]{% \findtokenx{#1}\empty }

    \makeatother \begin{document} \def\lettera{a} Checking whether \string\findtoken\ works: \findtoken{\lettera}, \findtoken{a}, \findtoken{b}, \findtoken{c}, \findtoken{d}. % uncomment the next line to stop compilation: % \findtoken{x}

    % now, trying to assign \findtoken's expansion into \mycount: \findtokenx{c}\mycount % ^ what to write above?! ^

    \string\mycount's value is: \the\mycount. \end{document}

    How it works should be relatively obvious, just trace through the logic.

  • What's wrong with the original attempt? That \begingroup is not expandable.

    Read more: What is "expansion"?, Why can't I use <some macro> inside the argument of <some other macro>?.


By the way, \expandafter\aftergroup\the\counta will only work as you expect if \the\counta expands to a single token (i.e. a one-digit number)

There are more quirks with the code (e.g. \expanded is probably not what you want to write there), but either way I avoid modifying unrelated parts so it's easy to compare with the original attempt.

user202729
  • 7,143
  • 1
    Plus, reusing LaTeX's internal scratch variables for your own purpose is not a good idea, it might interfere with arbitrary other code. Without memory limitation it's better to make your own. – user202729 Nov 21 '22 at 17:11