Take the 2-minute tour ×
TeX - LaTeX Stack Exchange is a question and answer site for users of TeX, LaTeX, ConTeXt, and related typesetting systems. It's 100% free, no registration required.

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?

share|improve this question
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 :-) –  Stephan Lehmke Jul 18 '12 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. –  David Carlisle Jul 18 '12 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 Jul 18 '12 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. –  Dan Velleman Jul 18 '12 at 23:20

3 Answers 3

up vote 11 down vote accepted

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,l3str}

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

share|improve this answer

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…

share|improve this answer

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
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.