3

I'm trying to define a new environment to format some user scenarios for a project I'm working on. The MWE below shows what's happening. It appears that the first use of \\ empties the token lists.

If these two lines are commented out:

\the\scgiventoks \\
When & \@when \\

then the "Then" phrases appear, so I know the token lists are getting properly populated. Any ideas on what would be causing the token lists to be emptied, or how to work around this problem? I'm processing the file with pdflatex.

\documentclass[letterpaper,12pt]{article}
\usepackage[utf8]{inputenc}

\title{Test Tokens in Environment}

% Counter for scenarios in this story
\newcounter{scenariocnt}
\newtoks{\scgiventoks} \scgiventoks={}
\newtoks{\scthentoks} \scthentoks={}

\makeatletter
\newenvironment{scenario}[1][no title provided]{%
  % Token registers for givens and thens
  \scgiventoks={}
  \scthentoks={}
  \def\given##1{\scgiventoks=\expandafter{\the\scgiventoks \@formatgiven{##1}}\ignorespaces}%
  \def\when##1{\gdef\@when{##1}\ignorespaces}%
  \def\@when{\@latex@error{No \noexpand\when provided.}\@ehc}%
  \def\then##1{\scthentoks=\expandafter{\the\scthentoks \@formatthen{##1}}\ignorespaces}%

  \stepcounter{scenariocnt}%
  \subsection{Scenario \thescenariocnt : #1}%
  \begin{tabular}{rp{5.5in}}%
  \ignorespaces
}{%
  \gdef\@given{Given}
  \gdef\@then{Then}
  \let\argrowsep=\empty
  % if the next two lines are commented out, then the Then tokens get rendered
  % it seems that the presence of a \\ empties the token lists.
  \the\scgiventoks \\
  When & \@when \\
  \let\argrowsep=\empty
  \the\scthentoks \\
  \end{tabular}%
}%

\newcommand{\@formatgiven}[1]{%
  \argrowsep % first time empty, second time a \\
  \@given & #1
  \gdef\argrowsep{\\}% next time
  \gdef\@given{And}% next time
}

\newcommand{\@formatthen}[1]{%
  \argrowsep % first time empty, second time a \\
  \@then & #1
  \gdef\argrowsep{\\}% next time
  \gdef\@then{And}% next time
}
\makeatother

\begin{document}

\section{Minimum Working Example}
I'm trying to create a new environment to allow a scenario to be defined.

The environment can contain exactly one \verb+\when+ macro, and any number of \verb+\given+ or
\verb+\then+ macros. Since the macros can be given in any order, the environment uses token lists
to collect the \emph{given} and \emph{then} invocations, and the closing block of the environment
outputs them in the desired format.

The output I want from the environment should look like this:
\vspace{1em}

\begin{tabular}{rp{5.5in}}
  Given & the account balance is \$100.00 \\
    And & the card is valid \\
    And & the machine contains enough money \\
  When  & the Account Holder requests \$20.00 \\
  Then  & the ATM should dispense \$20.00 \\
   And  & the account balance should be \$80.00 \\
   And  & the card should be returned \\
\end{tabular}

\begin{scenario}[Account has sufficient funds]
  \when{the Account Holder requests \$20.00}

  \given{the account balance is \$100.00}
  \then{the ATM should dispense \$20.00}
  \given{the card is valid}
  \then{the account balance should be \$80.00}
  \given{the machine contains enough money}
  \then{the card should be returned}
\end{scenario}
\end{document}
  • Two better ways to test: (1) comment out the tabular and change the &s to \&s; (2) use \showthe at appropriate points. – cfr Aug 28 '16 at 2:54
0

Since your assignments to token list registers happen in a table cell, they should be global:

\documentclass[letterpaper,12pt]{article}
\usepackage[utf8]{inputenc}

% Counter for scenarios in this story
\newcounter{scenariocnt}
\newtoks{\scgiventoks}
\newtoks{\scthentoks}

\makeatletter
\newenvironment{scenario}[1][no title provided]{%
  % Token registers for givens and thens
  \global\scgiventoks={}%
  \global\scthentoks={}%
  \def\given##1{%
    \global\scgiventoks=\expandafter{\the\scgiventoks \@formatgiven{##1}}\ignorespaces
  }%
  \def\when##1{\gdef\@when{##1}\ignorespaces}%
  \def\@when{\@latex@error{No \noexpand\when provided.}\@ehc}%
  \def\then##1{%
    \global\scthentoks=\expandafter{\the\scthentoks \@formatthen{##1}}\ignorespaces
  }%
  \stepcounter{scenariocnt}%
  \subsection{Scenario \thescenariocnt : #1}%
  \begin{tabular}{rp{4in}}%
  \ignorespaces
}{%
  \gdef\@given{Given}%
  \gdef\@then{Then}%
  \let\argrowsep=\empty
  % if the next two lines are commented out, then the Then tokens get rendered
  % it seems that the presence of a \\ empties the token lists.
  \the\scgiventoks \\%
  When & \@when \\%
  \let\argrowsep=\empty
  \the\scthentoks \\%
  \end{tabular}%
}%

\newcommand{\@formatgiven}[1]{%
  \argrowsep % first time empty, second time a \\
  \@given & #1%
  \gdef\argrowsep{\\}% next time
  \gdef\@given{And}% next time
}

\newcommand{\@formatthen}[1]{%
  \argrowsep % first time empty, second time a \\
  \@then & #1%
  \gdef\argrowsep{\\}% next time
  \gdef\@then{And}% next time
}
\makeatother

\begin{document}

\title{Test Tokens in Environment}

\section{Minimum Working Example}
I'm trying to create a new environment to allow a scenario to be defined.

The environment can contain exactly one \verb+\when+ macro, and any number of \verb+\given+ or
\verb+\then+ macros. Since the macros can be given in any order, the environment uses token lists
to collect the \emph{given} and \emph{then} invocations, and the closing block of the environment
outputs them in the desired format.

The output I want from the environment should look like this:
\vspace{1em}

\noindent
\begin{tabular}{rp{4in}}
  Given & the account balance is \$100.00 \\
    And & the card is valid \\
    And & the machine contains enough money \\
  When  & the Account Holder requests \$20.00 \\
  Then  & the ATM should dispense \$20.00 \\
   And  & the account balance should be \$80.00 \\
   And  & the card should be returned \\
\end{tabular}

\begin{scenario}[Account has sufficient funds]
  \when{the Account Holder requests \$20.00}
  \given{the account balance is \$100.00}
  \then{the ATM should dispense \$20.00}
  \given{the card is valid}
  \then{the account balance should be \$80.00}
  \given{the machine contains enough money}
  \then{the card should be returned}
\end{scenario}
\end{document}

enter image description here

You may enjoy an expl3 implementation of the same. No global assignment required, because the table body is built before starting the tabular.

\documentclass[letterpaper,12pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{xparse}

% Counter for scenarios in this story
\newcounter{scenariocnt}

\ExplSyntaxOn
\NewDocumentEnvironment{scenario}{O{no title provided}}
 {
  \stepcounter{scenariocnt}
  \subsection{Scenario~\thescenariocnt :~#1}
  \cs_set_eq:NN \given \scenario_given:n
  \cs_set_eq:NN \then \scenario_then:n
  \cs_set_eq:NN \when \scenario_when:n
 }
 {
  \bool_if:NF \l_scenario_when_bool
   {
    \msg_error:nn { scenario } { missing-when }
   }
  \begin{tabular}{rp{4in}}
  \l_scenario_given_tl
  \l_scenario_when_tl
  \l_scenario_then_tl
  \end{tabular}
 }

\tl_const:Nn \c_scenario_given_tl { Given }
\tl_const:Nn \c_scenario_and_tl   { And }
\tl_const:Nn \c_scenario_then_tl  { Then }
\tl_const:Nn \c_scenario_when_tl  { When }

\bool_new:N \l_scenario_given_bool
\bool_new:N \l_scenario_then_bool

\tl_new:N \l_scenario_given_tl
\tl_new:N \l_scenario_then_tl
\tl_new:N \l_scenario_when_tl

\msg_new:nnnn { scenario } { missing-when }
 {
  Missing~\exp_not:N\when
 }
 {
  You~need~one~\exp_not:N\when command
 }

\cs_new_protected:Nn \scenario_given:n
 {
  \bool_if:NTF \l_scenario_given_bool
   {
    \tl_put_right:Nn \l_scenario_given_tl { \c_scenario_and_tl & #1 \\ }
   }
   {
    \tl_put_right:Nn \l_scenario_given_tl { \c_scenario_given_tl & #1 \\ }
    \bool_set_true:N \l_scenario_given_bool
   }
 }

\cs_new_protected:Nn \scenario_then:n
 {
  \bool_if:NTF \l_scenario_then_bool
   {
    \tl_put_right:Nn \l_scenario_then_tl { \c_scenario_and_tl & #1 \\ }
   }
   {
    \tl_put_right:Nn \l_scenario_then_tl { \c_scenario_then_tl & #1 \\ }
    \bool_set_true:N \l_scenario_then_bool
   }
 }

\cs_new_protected:Nn \scenario_when:n
 {
  \tl_put_right:Nn \l_scenario_when_tl { \c_scenario_when_tl & #1 \\ }
  \bool_set_true:N \l_scenario_when_bool
 }
\ExplSyntaxOff

\begin{document}

\title{Test Tokens in Environment}

\section{Minimum Working Example}
I'm trying to create a new environment to allow a scenario to be defined.

The environment can contain exactly one \verb+\when+ macro, and any number of \verb+\given+ or
\verb+\then+ macros. Since the macros can be given in any order, the environment uses token lists
to collect the \emph{given} and \emph{then} invocations, and the closing block of the environment
outputs them in the desired format.

The output I want from the environment should look like this:
\vspace{1em}

\noindent
\begin{tabular}{rp{4in}}
  Given & the account balance is \$100.00 \\
    And & the card is valid \\
    And & the machine contains enough money \\
  When  & the Account Holder requests \$20.00 \\
  Then  & the ATM should dispense \$20.00 \\
   And  & the account balance should be \$80.00 \\
   And  & the card should be returned \\
\end{tabular}

\begin{scenario}[Account has sufficient funds]
  \when{the Account Holder requests \$20.00}
  \given{the account balance is \$100.00}
  \then{the ATM should dispense \$20.00}
  \given{the card is valid}
  \then{the account balance should be \$80.00}
  \given{the machine contains enough money}
  \then{the card should be returned}
\end{scenario}
\end{document}
1

You can save the tokens into a macro before the list is emptied and then use the macro afterwards.

For example,

  \xdef\tempa{\the\scthentoks}%
  \the\scgiventoks \\
  When & \@when \\
  \let\argrowsep=\empty
  \tempa\\

saved toks

Complete code:

\documentclass[letterpaper,12pt]{article}
\usepackage[utf8]{inputenc}

\title{Test Tokens in Environment}

% Counter for scenarios in this story
\newcounter{scenariocnt}
\newtoks{\scgiventoks} \scgiventoks={}
\newtoks{\scthentoks} \scthentoks={}

\makeatletter
\newenvironment{scenario}[1][no title provided]{%
  % Token registers for givens and thens
  \scgiventoks={}%
  \scthentoks={}%
  \def\given##1{\scgiventoks=\expandafter{\the\scgiventoks \@formatgiven{##1}}\ignorespaces}%
  \def\when##1{\gdef\@when{##1}\ignorespaces}%
  \def\@when{\@latex@error{No \noexpand\when provided.}\@ehc}%
  \def\then##1{\scthentoks=\expandafter{\the\scthentoks \@formatthen{##1}}\ignorespaces}%
  \stepcounter{scenariocnt}%
  \subsection{Scenario \thescenariocnt : #1}%
  \begin{tabular}{rp{5.5in}}%
  \ignorespaces
}{%
  \gdef\@given{Given}
  \gdef\@then{Then}
  \let\argrowsep=\empty
  % if the next two lines are commented out, then the Then tokens get rendered
  % it seems that the presence of a \\ empties the token lists.
  \xdef\tempa{\the\scthentoks}%
  \the\scgiventoks \\
  When & \@when \\
  \let\argrowsep=\empty
  \tempa\\
  \end{tabular}%
}%

\newcommand{\@formatgiven}[1]{%
  \argrowsep % first time empty, second time a \\
  \@given & #1
  \gdef\argrowsep{\\}% next time
  \gdef\@given{And}% next time
}

\newcommand{\@formatthen}[1]{%
  \argrowsep % first time empty, second time a \\
  \@then & #1
  \gdef\argrowsep{\\}% next time
  \gdef\@then{And}% next time
}
\makeatother

\begin{document}

\section{Minimum Working Example}

\begin{scenario}[Account has sufficient funds]
  \when{the Account Holder requests \$20.00}

  \given{the account balance is \$100.00}
  \then{the ATM should dispense \$20.00}
  \given{the card is valid}
  \then{the account balance should be \$80.00}
  \given{the machine contains enough money}
  \then{the card should be returned}
\end{scenario}
\end{document}

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.