# How can I give active characters definitions with expl3?

It's very much glossed over in texdoc interface3

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
\NewDocumentCommand \Digits { m m }{
\group_begin:
\char_set_catcode_active:N ;
\char_set_catcode_active:N .

\def;{,\;}
\def.{\dots}

\langle #1 \rangle \sb #2
\group_end:
}
\ExplSyntaxOff

\begin{document}
Desired syntax:
$\Digits{1,2,3;4,5,6;.;,7}{8}$

Desired output:
$\langle 1,2,3, \; 4,5,6, \; \dots, \; 7 \rangle_8$
\end{document}


I will be doing fancy things with the comma list at a later point (using l3clist), but I'd like to get this syntax working first.

I imagined that this should work based on the UK TeX FAQ article, but I keep getting the following error:

ERROR: Missing control sequence inserted.

--- TeX said ---
<inserted text>
\inaccessible
l.18 $\Digits{1,2,3;4,5,6;.;,7}{8}$
--- HELP ---
or \newsavebox command whose first argument is not a command name.


Per cgnieder's comment, perhaps the following approach would work:

\NewDocumentCommand \Digits { s m m } {
\group_begin:
\seq_set_split:Nnn
\l_digit_seq
{ #2 }
{ \IfBooleanTF { #1 } { } { , } }

\char_set_catcode_active:N ;
\char_set_catcode_active:N .

\def;{,\;}
\def.{\dots}

\real_digits:nn
}

\cs_new:Npn \real_digits:nn #1 #2 {
\langle #1 \rangle \sb #2
\group_end:
}
\ExplSyntaxOff


but alas, it does not.

-
You can't change the catcode of tokens after they've been grabbed as argument... there must be questions on this already somewhere... –  clemens Jun 13 '14 at 19:40
Perhaps I can change it then to \NewDocumentCommand\Digits{}{(catcode stuff)\real_digits:nn? See edit. –  Sean Allred Jun 13 '14 at 19:42
Given my LaTeX3 knowledge, I would use something like \tl_replace_all:Nnn, which is “easier” (at least to me). Is there a reason why this wouldn't work? –  Manuel Jun 13 '14 at 20:25
@Manuel that is indeed much more straightforward as far as readability goes… leave it to me to make things complicated! –  Sean Allred Jun 13 '14 at 20:38

It's no different from usual TeX programming; category codes are fixed at the moment the definition is stored, and in your definition ; and . have category code 12.

The procedure requires using \tl_to_lowercase:n in order to obtain the requested characters as active, while being able to use them in the replacement text.

Here's a possible implementation, with a generic activation function; the definition of \Digits shouldn't have parameters, in order to avoid absorbing the arguments after activating the characters.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\group_begin:
\char_set_catcode_active:N \^^@
\cs_new:Npn \sean_active_define:NN #1 #2
{
\group_begin:
\char_set_lccode:nn { ^^@ } { #1 }
\tl_to_lowercase:n
{
\group_end:
\cs_set_eq:NN ^^@
}
#2 % the replacement text is better outside \tl_to_lowercase:n
\char_set_catcode_active:N #1
}
\group_end:

\NewDocumentCommand \Digits { }
{
\group_begin:
\sean_active_define:NN ; \__sean_active_semicolon:
\sean_active_define:NN . \__sean_active_period:
\sean_digits:nn
}

\cs_new_protected:Npn \sean_digits:nn #1 #2
{
\langle #1 \rangle\sb{#2}
\group_end:
}

\cs_new_protected:Npn \__sean_active_semicolon: { ,\; }
\cs_new_protected:Npn \__sean_active_period: { \dots }
\ExplSyntaxOff

\begin{document}
\makebox[2.5cm][l]{Desired syntax:}
$\Digits{1,2,3;4,5,6;.;7}{8}$

\makebox[2.5cm][l]{Desired output:}
$\langle 1,2,3, \; 4,5,6, \; \dots, \; 7 \rangle_8$
\end{document}


You may avoid activating the characters by making them “math active”, which doesn't require absorbing the arguments in two stages.

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn

\group_begin:
\char_set_catcode_active:N \^^@
\cs_new:Npn \sean_math_active_define:NN #1 #2
{
\group_begin:
\char_set_lccode:nn { ^^@ } { #1 }
\tl_to_lowercase:n
{
\group_end:
\cs_set_eq:NN ^^@
}
#2 % the replacement text is better outside \tl_to_lowercase:n
\char_set_mathcode:nn { #1 } { "8000 }
}
\group_end:

\NewDocumentCommand \Digits { m m }
{
\group_begin:
\sean_math_active_define:NN ; \__sean_active_semicolon:
\sean_math_active_define:NN . \__sean_active_period:
\langle #1 \rangle\sb{#2}
\group_end:
}

\cs_new_protected:Npn \__sean_active_semicolon: { ,\; }
\cs_new_protected:Npn \__sean_active_period: { \dots }

\ExplSyntaxOff

\begin{document}
\makebox[2.5cm][l]{Desired syntax:}
$\Digits{1,2,3;4,5,6;.;7}{8}$

\makebox[2.5cm][l]{Desired output:}
$\langle 1,2,3, \; 4,5,6, \; \dots, \; 7 \rangle_8$
\end{document}


NOTE: I changed slightly the implementation, making it a bit more powerful and easier to manage; now it's easy to define local active characters also with arguments.

-
Thanks, egreg! It seems the \group_begin: \tl_to_lowercase:n { \group_end: ... } pattern is a common one. Is there any place that comes to mind that explains what is going on here specifically? –  Sean Allred Jun 13 '14 at 19:50
@SeanAllred See tex.stackexchange.com/questions/156759/the-lowercase-trick –  egreg Jun 13 '14 at 19:52
@SeanAllred I changed slightly the code, making it easier to manage and perhaps to read. –  egreg Jun 13 '14 at 20:36
If you use \cs_set_eq:NN inside \tl_to_lowercase shouldn't \sean_math_active_define:Nn be :NN? In case the second argument is not a single token \cs_set_eq:NN won't work as expected? Or am I missing something? –  Manuel Jun 13 '14 at 21:01
N.B. the \char_(g)set_active:Npn and \char_(g)set_active_eq:NN function in l3candidates; how functions in that module get "promoted" into the main codebase is largely from whether people find them useful — so if you start using them, they'll most probably stick around :)` –  Will Robertson Jun 14 '14 at 3:05