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