Getting the last token of a macro argument

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…


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.


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

Tags:

Programming