16

It is easy to get the first token of a macro argument. Ignoring some irrelevant complications, you do something like this:

\def\firsttokof#1{\first(#1)}
\def\first(#1#2){#1}

It's equally easy to get all the remaining tokens after the first one:

\def\nonfirsttoksof#1{\rest(#1)}
\def\rest(#1#2){#2}

As I understand it, this is because TeX does pattern matching from left to right, taking the shortest possible match. In \first and \rest, the shortest possible match for #1 will always be a single token.

But how can you get the last token of a macro argument?

5
  • 2
    Related question: How does one remove material from a token list?. The answers are mostly about removing a single token from the end of a list, so it's more or less the same issue :-) Commented Jul 18, 2012 at 17:55
  • 3
    basically you need to keep taking the first until there is nothing left and then the last is the last one you took off. Commented Jul 18, 2012 at 17:57
  • This is no doubt one of the irrelevant complications, but I want to point out that this method will only capture the first group in #1; i.e. \firsttokof{{12}34} = 12. Getting rid of those braces is a lot of work.
    – Ryan Reich
    Commented Jul 18, 2012 at 17:58
  • Actually, first/last group rather than first/last token is what I want. So you're right as far as terminology goes, but it's not an issue for the problem I'm actually trying to solve. Commented Jul 18, 2012 at 23:20
  • Kind of similar to tex core - Extract first, last and rest of tokens - TeX - LaTeX Stack Exchange
    – user202729
    Commented Jul 23, 2022 at 14:34

4 Answers 4

13

Assuming that you need only parameterless macros, here's a way that recognizes also a trailing space or closed brace.

\documentclass{article}
\usepackage{expl3,l3regex}

\def\A{abc}
\def\B{ab{c}}
\def\C{ab{c} }
\def\D{ab\linebreak}

\ExplSyntaxOn
\cs_new_protected:Npn \velleman_grab_last:N #1
 {
  \tl_set:Nx \l_velleman_testz_tl { \token_get_replacement_spec:N #1 }
  \tl_set_eq:NN \l_velleman_testy_tl \l_velleman_testz_tl
  \regex_replace_once:nnN { \A .* (.) \Z } { \1 } \l_velleman_testz_tl
  \regex_replace_once:nnN { \A .* (.) . \Z } { \1 } \l_velleman_testy_tl
  \prg_case_str:xxn { \l_velleman_testz_tl }
   {
    { \c_rbrace_str }{ \tl_set:Nn \l_velleman_last_tl { \c_group_end_token } }
    { \c_space_tl }{ \tl_set:Nn \l_velleman_last_tl { \c_space_token } \velleman_test_last:N #1 }
   }
   {
    \tl_set:Nx \l_velleman_last_tl { \tl_item:Vn #1 { -1 } }
   }
  \tl_show:N \l_velleman_last_tl
 }
\cs_new:Npn \velleman_test_last:N #1
 {
  \str_if_eq:xxF { \l_velleman_testy_tl } { \c_rbrace_str }
   {
    \tl_set:Nx \l_velleman_testz_tl { \tl_item:Vn #1 { -1 } }
    \token_if_cs:VT \l_velleman_testz_tl { \tl_set:NV \l_velleman_last_tl \l_velleman_testz_tl }
   }
 }

\cs_generate_variant:Nn \tl_item:nn {V}
\cs_generate_variant:Nn \token_if_cs:NT {V}

\velleman_grab_last:N \A
\velleman_grab_last:N \B
\velleman_grab_last:N \C
\velleman_grab_last:N \D

\ExplSyntaxOff

With the help of \regex_replace_once:nnN we leave in \l_velleman_test the last item in the "meaning" of the control sequence. Then we sort out the cases. In the case the last item is a space, another check has to be done; we keep also the last but one item; if it's a brace then the last item is surely a space; otherwise we look whether it's a control sequence.

There is still some small problem, but this should be enough for a start. For example the macros don't work for \space and the last test is inaccurate if the trailing space follows a control symbol.

14

With all due respect to egreg's impressive answer, I think reversing the token list and grabbing the now-first argument might be slightly easier albeit less general:

\documentclass{article}
\usepackage{expl3}
\begin{document}
\ExplSyntaxOn
\cs_new:Npn \my_tl_last:N #1
  {
    \tl_reverse:N #1
    \tl_head:N #1
  }
\tl_set:Nn \l_tmpa_tl {ab{cd}ef}
\my_tl_last:N \l_tmpa_tl
\ExplSyntaxOff
\end{document}

Storing the result might need a slight alteration depending on what you're looking for…

8

This answer is based on egreg's, simplified. If one wants to get the last item (brace group or single non-space non-[begin/end]-group token) in a list of tokens, simply use \tl_item:Nn \foo { -1 }. If one wants to get the last token, the easiest way is to use the (experimental) l3regex module, as egreg noted. Here I define \velleman_get_last:nN, which expects two arguments: some tokens, and a control sequence in which to store the result.

In most cases, \regex_extract_once:nnN { . \Z } { <tokens> } \result will do the trick: the regular expression means "any token (.), followed by the end (\Z) of the input ". The line just below that converts from the result of \regex_extract_once:nnN (currently a sequence) to a token list. The only case that needs to be treated specially is when the last token is an explicit end-group character. This cannot be put into a macro to give the result: we test for that with \regex_match:nnTF { \cE. \Z }, where the regex means "a catcode (\c) end-group (E) token with arbitrary character code (.), followed by the end (\Z) of the input", and in that case, we put \c_group_end_token, an implicit end-group token, into the token list.

\documentclass{article}
\usepackage{expl3,l3regex,l3str}

\def\A{abc}
\def\B{ab{c}}
\def\C{ab{c} }
\def\D{ab\linebreak}

\ExplSyntaxOn
%
\seq_new:N \l_velleman_last_seq
\tl_new:N \l_velleman_last_tl
\cs_new_protected:Npn \velleman_get_last:nN #1#2
  {
    \regex_match:nnTF { \cE. \Z } {#1}
      { \tl_set:Nn #2 { \c_group_end_token } }
      {
        \regex_extract_once:nnN { . \Z } {#1} \l_velleman_last_seq
        \tl_set:Nx #2 { \seq_item:Nn \l_velleman_last_seq { 1 } }
      }
  }
\cs_generate_variant:Nn \velleman_get_last:nN { V }
%
\cs_new_protected:Npn \test:N #1
  {
    \velleman_get_last:VN #1 \l_tmpa_tl
    \msg_term:n
      {
        Last ~ item: ~ ' \tl_item:Nn #1 { -1 } ' \\
        Last ~ token: ~ ' \tl_to_str:N \l_tmpa_tl '
      }
  }
\test:N \A
\test:N \B
\test:N \C
\test:N \D
\ExplSyntaxOff

\stop
1

To get the last argument from an argmument list, note that the argument found is reserved by \getlastarg@lastarg (on the temporary group stack):

\documentclass{article}

%------------------------------------------------------------------------------
%getlastargument
\makeatletter

\def\getlastarg#1{%
  \def\getlastarg@lastarg{}%
  \getlastarg@parse#1\@@@getlastarg@etokenlist
}

\def\@@@getlastarg@etokenlist{%
  \message{^^J!!Expanding macro `\noexpand\@@@getlastarg@etokenlist',
    be sure not to use it explicitly!!^^J}}
\def\getlastarg@parse{%
  \futurelet\@let@token\getlastarg@ifetokenlist
}
\def\getlastarg@ifetokenlist{%
  \ifx\@let@token\@@@getlastarg@etokenlist
    \let\next=\getlastarg@lastarg
  \else
    \let\next=\getlastarg@gobble
  \fi
  \next}
\def\getlastarg@gobble#1{%
  \def\getlastarg@lastarg{#1}%
  \getlastarg@parse}

To get the last token from a token list (the token list should be wrapped in a {,} pair, and the last found token is reserved by \getlasttoken@lasttoken. If the last token of the token list is a <right brace>, \getlasttoken@lasttoken may not be accessible):

\documentclass{article}

%------------------------------------------------------------------------------
%getlastargument
\makeatletter

\def\getlasttoken#1{%
  \getlasttoken@parse#1%
  \getlasttoken@e
}

\def\getlasttoken@e*getlasttoken*endtokenlist{}
\def\getlasttoken@parse{%
  \afterassignment\getlasttoken@ifetokenlist\let\@let@token= }
\def\getlasttoken@ifetokenlist{%
  \ifx\@let@token\getlasttoken@e
    \let\next=\getlasttoken@lasttoken
  \else
    \let\getlasttoken@lasttoken=\@let@token
    \let\next=\getlasttoken@parse
  \fi
  \next}

You must log in to answer this question.

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