8

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 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 --- This is probably caused by a \newcommand, \renewcommand, \newlength, 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.

Sean Allred
  • 27,421

1 Answers1

10

It's no different from usual TeX programming. In this case we can exploit the possibility of making a character math active and the advanced management of active characters provided by expl3.

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

\cs_new:Npn \sean_active_define:NN #1 #2
 {
  % define the active version of character #1 to do #2
  \char_set_active_eq:nN { `#1 } #2
  % make #1 math active
  \char_set_mathcode:nn { `#1 } { "8000 }
}

\NewDocumentCommand \Digits { m m }
 {
  % limit the scope of the math active characters
  \group_begin:
  \sean_active_define:NN ; \__sean_active_semicolon:
  \sean_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[3cm][l]{Desired syntax:}
  \verb$\Digits{1,2,3;4,5,6;.;7}{8}$

\makebox[3cm][l]{Obtained output:}
  $\Digits{1,2,3;4,5,6;.;7}{8}$

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

enter image description here

Note. The most recent version (September 2015) of the l3 kernel has improved the management of active characters.


OLD VERSION

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}

enter image description here

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.

egreg
  • 1,121,712
  • 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
  • 1
    @SeanAllred See http://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
  • @Manuel You're right, I forgot to change the name – egreg Jun 13 '14 at 21:11