8

I have a command that I can easily write using a mixture of resources from xparse, etoolbox, and pgffor.

\documentclass{article}
\usepackage{amsmath}
\usepackage{etoolbox}
\usepackage{xparse}
\usepackage{pgffor}

\makeatletter

\def\ae@tmp@frontmatter{}
\def\ae@list{}
\NewDocumentCommand\qchoice{ mom }
  {%%
    \def\ae@tmp@frontmatter{#2}
    \let\ae@list\relax
    \foreach \myn in {#3}
    {%%
      \ifx\ae@list\relax
        \xdef\ae@list{\noexpand#1{\expandonce\ae@tmp@frontmatter\expandonce\myn}}%%
      \else
        \xdef\ae@list{\expandonce\ae@list
                      \noexpand\par
                      \noexpand#1{\expandonce\ae@tmp@frontmatter\expandonce\myn}}%%
      \fi
    }%%
    \ae@list}

\makeatother

\newcommand\aebf[1]{$\rightarrow$ \textbf{#1} DONE}
\pagestyle{empty}
\begin{document}

\qchoice\aebf[front matter::]{a,b,c,d}

\end{document}

However, I would like to be able to write the equivalent of the above using Expl3 syntax. (Among other things, the mixing of \makeatletter in conjunction with xparse just seems philosophically wrong.)

It seems I should be able to do this without having to use either pgffor or etoolbox. But I keep running into problems.

Here's my latest attempt:

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}

\ExplSyntaxOn

\tl_new:N  \l_qchoice_command_sequence_tl
\tl_new:N  \l_qchoice_front_matter_tl
\tl_new:N  \l_qchoice_csv_choices_tl
\tl_new:N  \l_qchoice_tmp_tl
\seq_new:N \l_qchoice_choices_seq 

\NewDocumentCommand\qchoice{ mom }
  {%%
    \tl_set:Nn  \l_qchoice_command_sequence_tl {#1}
    \tl_clear:N \l_qchoice_front_matter_tl
    \tl_clear:N \l_qchoice_csv_choices_tl
    \IfNoValueF{#2}
      {
        \tl_set:Nn \l_qchoice_front_matter_tl {#2}
      }
    \seq_set_split:Nnn   \l_qchoice_choices_seq {,} {#3}
    \seq_map_function:NN \l_qchoice_choices_seq \qchoice_parse_choices:n
    \l_qchoice_csv_choices_tl
  }

\cs_new:Npn \qchoice_parse_choices:n #1 
  {
    \tl_clear:N      \l_qchoice_tmp_tl
    \tl_set:No       \l_qchoice_tmp_tl \l_qchoice_front_matter_tl
    \tl_put_right:Nn \l_qchoice_tmp_tl {#1}
    \tl_set:No       \l_qchoice_tmp_tl {{\l_qchoice_tmp_tl}}
    \tl_put_left:Nn  \l_qchoice_tmp_tl  \l_qchoice_command_sequence_tl
    \tl_if_empty:NTF \l_qchoice_csv_choices_tl
      {
        \tl_set:No        \l_qchoice_csv_choices_tl \l_qchoice_tmp_tl
      }
      {
        \tl_put_right:Nn  \l_qchoice_csv_choices_tl {\par}
        \tl_put_right:No  \l_qchoice_csv_choices_tl {\l_qchoice_tmp_tl}
      }
  }

\ExplSyntaxOff

\newcommand\aebf[1]{$\rightarrow$ \textbf{#1} DONE}
\pagestyle{empty}
\begin{document}

\qchoice\aebf[front matter::]{a,b,c,d}

\end{document}

There seems to be a problem with the line

    \tl_set:No       \l_qchoice_tmp_tl {{\l_qchoice_tmp_tl}}

resulting in exceeding TeX capacity error. But the syntax seems fine to me since \l_qchoice_tmp_tl should be expanded once leaving its content to be assigned back to \l_qchoice_tmp_tl. But that's not what seems to be happening.

The Expl3 code seems to be getting very complicated, which is fine if that's merely because I'm restricting myself to only using LaTeX3 resources.

Update

Ultimately what I want to do is when given

\qchoices<control sequence>[<optional front matter>]{<item a>,<item b>,<item c>,<item d>}

is produce a string of tokens like

<control sequence>{<optional front matter><item a>}<inter-item matter>
<control sequence>{<optional front matter><item b>}<inter-item matter>
<control sequence>{<optional front matter><item c>}<inter-item matter>
<control sequence>{<optional front matter><item d>}

where in the above examples I'm using \par as my <inter-item matter>. Notice that <inter-item matter> should not follow the last item passed.

What I'm having difficulty doing with the Expl3 code is getting the contents to be passed to <control sequence> wrapped in brackets.

  • Do you need the result stored in a list: if not the outcome is trivially easy. – Joseph Wright Nov 26 '14 at 21:21
  • @JosephWright In my original document, I have a function that expects to receive a string of tokens separated by commas. So, I kind of need \l_qchoice_csv_choices_tl to be such a csv list. But, I'm also open to seeing how someone else might approach this too. – A.Ellett Nov 26 '14 at 21:26
  • After playing around with this a bit more, the problem seems to be around the doubly nested brackets in the line where I'm trying to expand the token once and reassign to itself. – A.Ellett Nov 26 '14 at 21:27
  • 1
    I wouldn't write \tl_set:No \l_tmpa_tl \l_tmpb_tl, I would put braces around the second argument (o is not one token). By the way, why \tl_set:No and not \tl_set_eq:NN or \tl_set:NV? (I didn't read the whole question yet, jsut suggesting) ADD: “But the syntax seems fine to me since \l_qchoice_tmp_tl should be expanded once leaving its content to be assigned back to \l_qchoice_tmp_tl” It's not expanded, only the first {. – Manuel Nov 26 '14 at 21:38
  • 2
    @A.Ellett Just the opposite. It's not one token, so it should have { .. } around the second argument. Expanding once {\tmp} (braces included in the expansion) is done by \expandafter{{\tmp}} so \expandafter only affects the first two tokens (saves one and expands the second) hence \expandafter{{ does nothing. (I'm not a pro, just my thoughts :P) – Manuel Nov 26 '14 at 21:41
7

While you could construct the result here one part at a time I would arrange to do the entire job inside an x-type expansion using a mapping. As the input is a comma list it's probably easiest to do this directly

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}

\ExplSyntaxOn
\tl_new:N \l_qchoice_choices_tl
\cs_new:Npn \__qhoice_tmp:n #1 { }
\NewDocumentCommand \qchoice { m o m }
  {
    \tl_clear:N \l_qchoice_csv_tl
    \cs_set:Npn \__qhoice_tmp:n ##1
      {
        \exp_not:N #1
          {
            \IfNoValueF {#2} { \exp_not:n {#2} }
            \exp_not:n {##1}
          }
        \exp_not:N \par
      }
    \tl_set:Nx \l_qchoice_csv_tl
      {
        \clist_map_function:nN {#3}\__qhoice_tmp:n
      }
    \tl_show:N \l_qchoice_csv_tl
  }
\ExplSyntaxOff

\newcommand\aebf[1]{$\rightarrow$ \textbf{#1} DONE}
\pagestyle{empty}
\begin{document}

\qchoice\aebf[front matter::]{a,b,c,d}

\end{document}

For the case where you don't want the separator to appear after the last item there are a few approaches. Perhaps the conceptually easiest is to use an inline mapping where there is a test to see if the list is empty: in that case don't add the separator.

\documentclass{article}
\usepackage{amsmath}
\usepackage{xparse}

\ExplSyntaxOn
\tl_new:N \l_qchoice_choices_tl
\cs_new:Npn \__qhoice_tmp:n #1 { }
\NewDocumentCommand \qchoice { m o m }
  {
    \tl_clear:N \l_qchoice_csv_tl
    \clist_map_inline:nn {#3}
      {
        \tl_set:Nx \l_qchoice_csv_tl
          {
            \exp_not:V \l_qchoice_csv_tl
            \tl_if_empty:NF \l_qchoice_csv_tl
              { \exp_not:N \par }
            \exp_not:N #1
              {
                \IfNoValueF {#2} { \exp_not:n {#2} }
                \exp_not:n {##1}
              }
          }
      }
    \tl_show:N \l_qchoice_csv_tl
  }
\ExplSyntaxOff

\newcommand\aebf[1]{$\rightarrow$ \textbf{#1} DONE}
\pagestyle{empty}
\begin{document}

\qchoice\aebf[front matter::]{a,b,c,d}

\end{document}

This approach needs one assignment per item: if more efficiency is needed, there are alternatives (the question suggests the list is short).

As noted in comments, \tl_set:No \l_tmpa_tl { { \l_tmpa_tl } } tries to expand { not \l_tmpa_tl (\tl_set:No is low-ish level and equivalent to \exp_after:wN \tl_set:Nn \exp_after:wN). Usually it's best to avoid building up complex token lists a bit at a time if you can arrange to do the job in one go using an x-type expansion: only one assignment is needed and this is a performance gain.

  • Nice job, but this will place \par after the last item d which I don't want to have happen. – A.Ellett Nov 26 '14 at 21:50
  • @A.Ellett That was not clear from the question: I'll revise. – Joseph Wright Nov 26 '14 at 21:53
  • I've updated the question to better reflect that idea too. :) – A.Ellett Nov 26 '14 at 21:58
  • Note that in both approaches I've avoided adding too many things stepwise: that gets slow (ongoing work on siunitx involves exactly this point). – Joseph Wright Nov 26 '14 at 22:05
  • I hadn't wanted to build things step by step as I did in the Expl3 code. Thank you for the solution: seeing the use of \exp_not:* greatly helped as did the discussion with @manuel about how :o works. – A.Ellett Nov 26 '14 at 22:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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