6

I've tried pairing this down, but each time I do that I'm either correcting my mistake or something. So here's a slightly unwieldy MWE.

In a nutshell, what I'm trying to do is....

I'm writing a quiz that has multiple version. I've got some clunky LaTeX code that already handles versioning, but I decided I could probably get cleaner looking document code if I tried writing something using LaTeX3.

Each quiz has a version attached to it. I'm writing a \choice macro that's given a ; separated list of possible output which will change based upon the version of the quiz.

For example if I write \choice{x;y;z;w} then on version A of the quiz \choice will provide x, on version B of the quiz \choice will provide y, etc.

I'm not anywhere near getting this up and running. I'm actually not interested in someone telling me how to achieve my ultimate goal: I think I'll learn a fair amount if I try it on my own. Also, I know I'm not following notational conventions quite correctly, but it's giving me a headache trying to balance notation (which I still find difficult to read) and mastering syntax (which I find slightly obscure, but beginning to get the hang of).

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
%-@-(1)---------------------------------------------------------------------
%% --- VERSIONING --- %%
%% store what the possible version are
\tl_new:N  \g__version_types
\tl_set:Nn \g__version_types { a;b;c;d }
\tl_show:N \g__version_types
%% make a sequence of the possible versions
\seq_new:N \g__all_possible_versions_seq
\seq_gset_split:Nnn \g__all_possible_versions_seq { ; } {\g__version_types}
\seq_show:N \g__all_possible_versions_seq
%% allow the user to define what the versions are
\NewDocumentCommand{\defineversions}{ O{;} m }{
    \tl_set:Nn \g__version_types { #2 }
    \seq_set_split:Nnn \g__all_possible_versions_seq { #1 } \g__version_types
}
%-@-(2)---------------------------------------------------------------------
%% --- GETTING/SETTING VERSIONS ---%%
\tl_new:N   \g__tl_current_doc_version 

\cs_new:Npn \mv_set_version:n #1 
    {
        \tl_set:Nn \g__tl_current_doc_version { #1 }
    }

\cs_new:Npn \mv_get_version 
    {
        \tl_use:N \g__tl_current_doc_version
    }

\newcommand{\setversion}[1]{\mv_set_version:n {#1}}
\newcommand{\getversion}{ \mv_get_version }

\cs_new:Npn \mv_test_version:n #1 {
    \str_if_eq:VnTF \g__tl_current_doc_version { #1 } { HELLO } { BYE}
}
\newcommand{\testversion}[1]{\mv_test_version:n {#1}}

%-@-(3)---------------------------------------------------------------------
%% --- CREATING THE USER INTERFACE --- %%
%% I'm going to destructive examine the sequence, so make 
%% a copy of it and work with copy
\seq_new:N     \g__copy_all_possible_versions_seq
\seq_set_eq:NN \g__copy_all_possible_versions_seq \g__all_possible_versions_seq
\tl_new:N      \g__current_possible_version_tl
%% Information that the user passes to us
\seq_new:N \g__user_defined_choice_seq
\tl_new:N  \g__current_possible_choice_tl

%% This "cs" assumes that the user choices have been translated
%% into a sequence
\cs_new:Npn \__test_current_choice_against_version:n #1 { 
    \seq_pop_left:NN \g__user_defined_choice_seq        
                     \g__current_possible_choice_tl

    \seq_pop_left:NN \g__copy_all_possible_versions_seq 
                     \g__current_possible_version_tl

    \tl_use:N        \g__current_possible_choice_tl --
    \tl_use:N        \g__current_possible_version_tl \par

    \str_if_eq:VVTF  \g__current_possible_choice_tl 
                     \g__current_possible_version_tl
                     { \tl_use:N \g__current_possible_choice_tl  }
                     { #1 }
}

\cs_new:Npn \mv_make_choice:n #1 {
    \__test_current_choice_against_version:n
        { \__test_current_choice_against_version:n 
            { \__test_current_choice_against_version:n 
                { FAIL }}}
}

\NewDocumentCommand{\choices}{ O{;} m }{
    \texttt{#2}\par
    \seq_gset_split:Nnn \g__user_defined_choice_seq  {#1}  {#2}
    \mv_make_choice:n {#1}
}

\ExplSyntaxOff
\begin{document}

Hello:  I'm setting the version \setversion{b}

I'm getting the version \textbf{\getversion}!

Choosing \choices{x;y;z;w}
\end{document}

I'm getting an error about an empty sequence. I've tried showing the sequence, and tokens used to create it, but nothing seems to be right.

13
  • 1
    typo: \str_if_equ:VVTF should be \str_if_eq:VVTF
    – Scott H.
    Jan 22, 2013 at 0:10
  • 1
    \str_if... should be \tl_if.... There are many more glitches.
    – egreg
    Jan 22, 2013 at 0:21
  • @egreg. Really? That was the first bit of code I wrote and it worked (or I thought it did). :(
    – A.Ellett
    Jan 22, 2013 at 0:27
  • 1
    This seems hopeless. I fix one error and another pops up. Why is this so difficult????
    – A.Ellett
    Jan 22, 2013 at 0:28
  • 2
    Doesn't any of the several exam packages do what you want (or near)? This way you'll only go prematurely bald.
    – vonbrand
    Jan 22, 2013 at 1:50

2 Answers 2

5

I've tried to correct it, but I don't know if it does exactly what you want:

\documentclass{article}
\usepackage{xparse}
\ExplSyntaxOn
%-@-(1)---------------------------------------------------------------------
%% --- VERSIONING --- %%
%% store what the possible version are
\tl_new:N  \g__version_types_tl
\tl_set:Nn \g__version_types_tl { a;b;c;d }
%% make a sequence of the possible versions
\seq_new:N \g__all_possible_versions_seq
\seq_new:N \g__copy_all_possible_versions_seq
\cs_generate_variant:Nn \seq_gset_split:Nnn { NnV }
\seq_gset_split:NnV \g__all_possible_versions_seq { ; } \g__version_types_tl
\seq_gset_eq:NN \g__copy_all_possible_versions_seq \g__all_possible_versions_seq
%% allow the user to define what the versions are
\NewDocumentCommand{\defineversions}{ O{;} m }
 {
  \tl_gset:Nn \g__version_types_tl { #2 }
  \seq_gset_split:NnV \g__all_possible_versions_seq { #1 } \g__version_types_tl
  \seq_gset_eq:NN \g__copy_all_possible_versions_seq \g__all_possible_versions_seq
 }

%-@-(2)---------------------------------------------------------------------
%% --- GETTING/SETTING VERSIONS ---%%

\tl_new:N   \g__current_doc_version_tl

\cs_new_protected:Npn \mv_set_version:n #1 
 {
  \tl_set:Nn \g__current_doc_version_tl { #1 }
 }

\cs_new:Npn \mv_get_version:
 {
  \tl_use:N \g__current_doc_version_tl
 }

\NewDocumentCommand{\setversion}{m}
 {
  \mv_set_version:n {#1}
 }
\NewDocumentCommand{\getversion} {}
 {
  \mv_get_version:
 }

\cs_new:Npn \mv_test_version:n #1
 {
  \tl_if_eq:VnTF \g__tl_current_doc_version { #1 } { HELLO } { BYE}
 }
\NewDocumentCommand{\testversion}{m}
 {
  \mv_test_version:n {#1}
 }

%-@-(3)---------------------------------------------------------------------
%% --- CREATING THE USER INTERFACE --- %%
%% I'm going to destructively examine the sequence, so make 
%% a copy of it and work with copy
\tl_new:N  \g__current_possible_version_tl
%% Information that the user passes to us
\seq_new:N \g__user_defined_choice_seq
\tl_new:N  \g__current_possible_choice_tl

%% This "cs" assumes that the user choices have been translated
%% into a sequence
\cs_new_protected:Npn \__test_current_choice_against_version: 
 {
  %I'm working 
  \seq_gpop_left:NN \g__user_defined_choice_seq        
                    \g__current_possible_choice_tl

  \seq_gpop_left:NN \g__copy_all_possible_versions_seq 
                    \g__current_possible_version_tl

  \tl_if_eq:NNTF    \g__current_possible_choice_tl 
                    \g__current_possible_version_tl
                    { \tl_use:N \g__current_possible_choice_tl  }
                    { FAIL }
 }

\cs_new:Npn \mv_make_choice:n #1
 {
  \__test_current_choice_against_version:
 }

\NewDocumentCommand{\choices}{ O{;} m }
 {
  \texttt{#2}\par
  \seq_gset_split:Nnn \g__user_defined_choice_seq  {#1}  {#2}
  \mv_make_choice:n {#1}
 }

\ExplSyntaxOff
\begin{document}

Hello:  I'm setting the version \setversion{b}

I'm getting the version \textbf{\getversion}!

Choosing \choices{x;y;z;w}
\end{document}

I fixed the names and some programming glitches. For instance you're comparing token lists with \str_if_eq:... but \tl_if_eq:... should be used. In particular \tl_if_eq:NNTF and not \str_if_eq:VVTF which does more work for nothing. It's quite obscure why you define \mv_make_choice:n with an argument that you don't use. Just use \__test_current_choice_against_version: as the final instruction in \choices.

Other points.

  1. When a variable is declared global, use only global assignments to it.
  2. Don't forget that all functions should have a colon in their name.
  3. Use \cs_new_protected:Npn when the function does unexpandable jobs (such as setting token lists or sequences).
2
  • This whole notion of unexpandable jobs is murky to me. I tried your version, but it's not working for me, I get an Undefined Control Sequence error with \seq_gset_split:NnV
    – A.Ellett
    Jan 22, 2013 at 0:50
  • Sorry about being obscure with \mv_make_choice:n. Originally I meant it to take an argument. But things weren't working correctly and so I wound up not using the argument. :(
    – A.Ellett
    Jan 22, 2013 at 1:01
4

I greatly appreciate all the feed back folks gave me. I'm not sure I could have come up with a working example without your suggestions. Because of the time and effort you all put in I thought I'd post what I came up with.

At this point, if you see things you really don't like stylistically, I would love feedback. I think I understand much better what's happening. @egreg. I tried to follow your advise about \tl_if_eq and \str_if_eq, I think I really do need that later in the following code. On account of your comments and suggestions, I'll mark your answer as the correct answer.

Edit

Given that I've presented as much of my solution as I have, I figured I'd update it to a version that does some simple error checking. It could still use some improvement, but I think this will be my final edit.

\documentclass{article}
\pagestyle{empty}
\usepackage{amsmath,amssymb,xcolor}
\usepackage{xparse}
\ExplSyntaxOn
%% ----------------- PARAMETERS ------------------ %%
%% Creating versions and their default values      %%
\seq_new:N          \g__possible_version_types_seq
\seq_gset_split:Nnn \g__possible_version_types_seq {;} { a;b;c;d }
\tl_new:N           \g__current_version_tl
\tl_set:Nn          \g__current_version_tl {a}
%% Creating choices and a means saving choice for  %%
%% later use: even if defined within a group       %%
\seq_new:N  \g__user_provided_choices_seq
\tl_new:N   \g__selected_choice_tl
%% this next token is to allow `\selectedchoice`   %%
%% to be definable from within a grouping.         %%
\tl_new:N   \g__callable_selected_choice_tl
%% a function to select the choice corresponding   %%
%% to the current version                          %%
\cs_new:Npn \fnc_match_version_to_choices:nn #1#2 {
    \str_if_eq:nVT {#1} \g__current_version_tl {\tl_gset:Nn \g__selected_choice_tl {#2}} 
}
%% --------------- ERROR CHECKING ---------------  %%
%% Make sure you don't try to pass a version that  %%
%% has not yet been defined.                       %%
\msg_new:nnnn {mymodule}{invalid version call}{You've\ called\ for\ a\ non-existant\ version.}{}
\cs_new:Nn \err_am_i_defining_valid_version: {
        \seq_if_in:NVF \g__possible_version_types_seq \g__current_version_tl
                { \msg_error:nn{mymodule}{invalid version call}}
}
%% Make sure you provide choices to correspond     %%
%% with the number of versions.  Having more       %%
%% choices than versions will not signal an error. %%
\msg_new:nnnn {mymodule}{unequalchoices}{There\ are\ more\ versions\ than\ choices}{}
\cs_new:Nn \err_fewer_choices_than_versions:   {
      \int_compare:nT  {
                           \seq_length:N \g__possible_version_types_seq 
                           >
                           \seq_length:N \g__user_provided_choices_seq  
                       }
                       {\msg_error:nn{mymodule}{unequalchoices}}
    }
%% ----------------  USER INTERFACE ---------------%%
%% allow the user to set the version types         %%
\NewDocumentCommand{\setpossibleversions}{O{;} m}{ 
      \seq_gset_split:Nnn \g__possible_version_types_seq {#1} {#2}
    }
%% allow the user to set the version manually      %%
\NewDocumentCommand{\setversion}{m}{
        \tl_gset:Nn \g__current_version_tl {#1}
        \err_am_i_defining_valid_version:
    }
%% user interface to map choices to version of quiz   %%
%%                                                    %%
%% make sure that you're not expecting more versions  %%
%% than you've provided choices for.                  %%
%%                                                    %%
%% (1) pair "versions" with "choices"                 %%
%% (2) match "version" against "current version" to   %%
%%     select the desired choice                      %%
%% (3) if the same, return "selected choice"          %%
%%                                                    %%
%% NOTE:  the starred version of `\choice` saves the  %%
%% value of the choice to `\selectedchoice` so you    %%
%% can access it later                                %%
\NewDocumentCommand{\choices}{ s O{;} m }{
    \seq_gset_split:Nnn \g__user_provided_choices_seq {#2} {#3}
    \err_fewer_choices_than_versions:
    \seq_mapthread_function:NNN \g__possible_version_types_seq 
                                \g__user_provided_choices_seq 
                                \fnc_match_version_to_choices:nn
    \tl_use:N \g__selected_choice_tl
    \IfBooleanT #1 {\tl_gset:NV \g__callable_selected_choice_tl \g__selected_choice_tl}
    \tl_gset:Nn \g_selected_choice_tl {}
}
\newcommand{\selectedchoice}{\tl_use:N \g__callable_selected_choice_tl}
%% you might want access to the current version to    %%
%% set page numbers                                   %%
\NewDocumentCommand{\getversion}{}{\tl_use:N \g__current_version_tl}
\ExplSyntaxOff
\begin{document}
\setpossibleversions[,]{a,b,c,d,e}

\setversion{a}

If $m\angle\choices{1;2;3;4;5}=\choices[,]{40,42,38,45,25}^\circ$, then the
\choices*{supplement;complement;complement;supplement;supplement} of the angle has measure\ldots

The measure of the \selectedchoice\ is...

\setversion{d}

If $m\angle\choices*{1;2;3;4;5}=\choices[,]{40,42,38,45,25}^\circ$, then the
\choices{supplement;complement;complement;supplement;supplement} of the angle has measure\ldots

Notice how the measure of $\angle{\selectedchoice}$ is smaller than 90.
This is a clue.

\end{document}

Regardless of style though, thanks to LaTeX3 I am able to write cleaner looking documents than the loopier solutions I've been having to put up with.

enter image description here

16
  • One thing that is mostly visual, you can, if you prefer, use \str_if_eq:nVT {#1} \g__current_version {#2} and eliminate the empty false branch. I'm quite certain that \seq_gset_split:NnV should be defined without generating a variant...if it's not, then either your packages aren't up to date, or it's been removed very recently.
    – Scott H.
    Jan 22, 2013 at 2:12
  • @scottH. Thanks for your input. I'm working on a Mac and though I updated LaTeX about 3 weeks ago, it's from the version as of June or July or sometime around then. I've still got to figure out how to get more up-to-date versions. (Someone made a comment about how to do that a while ago, I'll have to look it up again---I always find updating scary).
    – A.Ellett
    Jan 22, 2013 at 2:15
  • Rather than storing the user input in \g__possible_version_types_tl then splitting it with \seq_gset_split:NnV you can split it directly with \seq_gset_split:Nnn (with second and third arguments {#1} {#2}). Jan 22, 2013 at 14:01
  • Rather than \seq_map_function:NN with a function defined separately, you can use \seq_map_inline:Nn \g__possible_version_types_seq { \str_if_eq:nVT {#1} \g__current_version {\bool_gset_true:N ...}}. In fact, you can do simply \seq_if_in:NVT \g__possible_version_types_seq \g_current_version {\msg_error:nnn ... } and get rid of the boolean altogether. Slight subtlety: \seq_if_in takes category codes into account. Jan 22, 2013 at 14:06
  • \int_value:w has been removed from more recent LaTeX3. In fact, it is not needed in your setting, since \seq_length:N (now renamed \seq_count:N) directly expands to an integer. Jan 22, 2013 at 14:07

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .