20

I'm not getting the output I want from my LaTeX code, and I think the problem is that I'm not expanding things at the right times.

Code

\documentclass{article}

\usepackage{tikz}
\usepackage{etextools}

\makeatletter

% The result of calling
%   \@defineLine{foo}{35}
% is that the command \wickerson@foo@pos gets defined as 35.
\newcommand*\@defineLine[2]{%
  \typeout{Defining wickerson@#1@pos = #2}
  \expandafter\xdef\csname wickerson@##1@pos\endcsname{#2}
}

% Project the pos-field from the line with the given
% identifier. For instance, if \wickerson@foo@pos=35 then
%   \@getLinePos{foo}
% will give 35.
\xdef\@getLinePos#1{\csname wickerson@#1@pos\endcsname}

% A comma-separated list of "active" lines. Each element is
% a line identifier. 
\newcommand*\@activeLines{}

% Add the given identifier to the list of active lines.
\newcommand*\@addToActiveLines[1]{%
  \ifx\@activeLines\@empty\else\g@addto@macro\@activeLines{,}\fi
  \g@addto@macro\@activeLines{#1}
}

% Remove the given identifier from the list of active lines.             
\newcommand*\@removeFromActiveLines[1]{%                                 
  \@expandtwoargs\@removeelement{#1}\@activeLines\@activeLines           
}                                                                        

% Print each active line identifier, associated with its
% corresponding pos field. Typical output:
%   {a ↦ 10, b ↦ 30, c ↦ 50, d ↦ 70,}
\newcommand*\@printState{%
  Current state: $\{
  \foreach\i in \@activeLines {%
    \i \mapsto \@getLinePos\i,
  }
  \}$ \par
}

\begin{document}

\foreach \x/\xvalue in {a/10, b/30, c/50, d/70} {
  \ExpandNextTwo\@defineLine{\x}{\xvalue}
  \ExpandNext\@addToActiveLines{\x}
  \@printState
} 
\foreach \x in {a, c} {                                                  
  \ExpandNext\@removeFromActiveLines{\x}                                 
  \@printState                                                           
}                                                                        

\end{document}

Explanation

The first foreach-loop cycles over a list of pairs, building up a function from identifiers (e.g. a,b,etc.) to numbers. The values of this function are stored in a series of macros (e.g. \wickerson@a@pos stores the value of the function for identifier a), and the domain of this function is the comma-separated list \@activeLines. Initially \@activeLines is empty, and by the end it is a,b,c,d.

I print my function after each iteration of the loop. By the end I expect to print

{a↦10, b↦30, c↦50, d↦70}

but instead it prints

{a↦70, b↦70, c↦70, d↦70}

I suspect the problem is when I add a mapping to my function, I'm not expanding the value enough, and so it changes when I add another mapping. I thought using ExpandNextTwo from etextools would expand everything nicely, but it doesn't seem to have helped. Any ideas?

The second foreach-loop tries to remove some of the mappings from my function, with the help of the \@removeelement command. Unfortunately, it doesn't work. The mapping is only removed within the scope of the current loop iteration, and by the next iteration, the mapping has returned. I suspect the problem is that the \@removeelement is not redefining \@activeLines globally, but I don't know how to fix this. Any ideas? (I hope I don't need to switch to the expl3 syntax to achieve this, because that would be require quite a drastic change to my code!)

Current output

enter image description here

Desired output

enter image description here

2 Answers 2

20

The following two macros need fixing:

\newcommand*\@defineLine[2]{%
  \typeout{Defining wickerson@#1@pos = #2}
  \expandafter\xdef\csname wickerson@##1@pos\endcsname{#2}
}

That will define for \@defineLine{a}{10} the macro \wickerson@#1@pos instead of \wickerson@a@pos. Fix:

\newcommand*\@defineLine[2]{%
  \typeout{Defining wickerson@#1@pos = #2}
  \expandafter\xdef\csname wickerson@#1@pos\endcsname{#2}
}

The next macro uses it:

% Project the pos-field from the line with the given
% identifier. For instance, if \wickerson@foo@pos=35 then
%   \@getLinePos{foo}
% will give 35.
\xdef\@getLinePos#1{\csname wickerson@#1@pos\endcsname}

Since \xdef expands, \csname is executed to \wickerson@#1@pos with #1 part of the macro name, not an argument. Fix:

\gdef\@getLinePos#1{\csname wickerson@#1@pos\endcsname}

Getting rid of the last comma

A feature of \foreach can be used to get rid of the latest comma in the output:

\newcommand*\@printState{%
  Current state: $\{
  \foreach\i [count=\ii] in \@activeLines {%
    \ifnum\ii>1 ,\fi
    \i \mapsto \@getLinePos\i
  }
  \}$ \par
}

Result

Removing

The question uses the following definition for removing elements:

\newcommand*\@removeFromActiveLines[1]{%                                 %
  \@expandtwoargs\@removeelement{#1}\@activeLines\@activeLines           %
}                                                                        %

Indeed, the effect of LaTeX's \@removeelement is limited to the current group. If you want to make the changes of \@activeLines global, then \global\let helps:

\newcommand*\@removeFromActiveLines[1]{%                                 %
  \@expandtwoargs\@removeelement{#1}\@activeLines\@activeLines           %
  \global\let\@activeLines=\@activeLines
}                                                                        %
6
  • Thank you so much Heiko. Your explanations are extremely clear to me. Commented May 16, 2013 at 19:37
  • So I wasn't actually defining \wickerson@a@pos, \wickerson@b@pos, etc, but instead defining \wickerson@#1@pos over and over again. Heh. Commented May 16, 2013 at 19:53
  • Transparent PNG background... I like it.
    – Werner
    Commented May 16, 2013 at 20:19
  • 2
    @Werner Thanks. The transparent background is the result of ImageMagick's convert from PDF to PNG. It uses Ghostscript with device pngalpha. Commented May 16, 2013 at 20:26
  • 1
    @JohnWickerson Answer updated (using \global\let\@activeLines=\@activeLines). Commented May 17, 2013 at 12:46
13

I believe that with expl3 it's more straightforward, since it doesn't rely on knowing what's needed to expand and when.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn
\seq_new:N \l_wickerson_list_seq

\NewDocumentCommand{\printState}{>{\SplitList{,}}m}
 {
  \seq_clear:N \l_wickerson_list_seq
  \ProcessList{#1}{\splitatslash}
  Current~state\tl_to_str:n { : } ~ %
  $\seq_use:Nnnn \l_wickerson_list_seq { , } { , } { , }$
 }
\NewDocumentCommand{\splitatslash}{>{\SplitArgument{1}{/}}m}
 {
  \wickerson_split_element:nn #1
 }
\cs_new_protected:Npn \wickerson_split_element:nn #1 #2
 {
  \seq_put_right:Nn \l_wickerson_list_seq { #1 \mapsto #2 }
 }
\ExplSyntaxOff

\begin{document}
\printState{a/10}

\printState{a/10, b/30}

\printState{a/10, b/30, c/50}

\printState{a/10, b/30, c/50, d/70}
\end{document}

enter image description here


A different implementation that makes it possible to remove states; I've used both a sequence for the "before slash" parts and a property list that holds the vaule ("after slash").

The command \defineStates reinitializes the variables, while \addStates put new states in the variables; similarly, \removeStates removes states. Finally, \printStates just gives a visual representation of the states.

\documentclass{article}
\usepackage{xparse}

\ExplSyntaxOn

%%% variables
\seq_new:N \g_wickerson_list_seq
\prop_new:N \g_wickerson_states_prop

%%% document commands
\NewDocumentCommand{\defineStates}{m}
 {
  \wickerson_definestates:n { #1 }
 }
\NewDocumentCommand{\addStates}{m}
 {
  \wickerson_addstates:n { #1 }
 }
\NewDocumentCommand{\removeStates}{m}
 {
  \clist_map_inline:nn { #1 }
   {
    \seq_gremove_all:Nn \g_wickerson_list_seq { ##1 }
    % The following also removes the state from the property list
    % You can choose whether keep it or comment it out
    \prop_gremove:Nn \g_wickerson_states_prop { ##1 }
   }
 }
\NewDocumentCommand{\printStates}{}
 {
  Current~state\tl_to_str:n { : } ~ %
  $
   \seq_map_inline:Nn \g_wickerson_list_seq
    { ##1 \mapsto \prop_get:Nn \g_wickerson_states_prop { ##1 }, }
  $
 }

%%% internal commands
\cs_new_protected:Npn \wickerson_addstates:n #1
 {
  \clist_map_inline:nn { #1 }
   {
    \wickerson_split_element:ww ##1 \q_stop
   }
 }
\cs_new_protected:Npn \wickerson_definestates:n #1
 {
  \seq_gclear:N \g_wickerson_list_seq
  \prop_gclear:N \g_wickerson_states_prop
  \wickerson_addstates:n { #1 }
 }
\cs_new_protected:Npn \wickerson_split_element:ww #1 / #2 \q_stop
 {
  \seq_gput_right:Nn \g_wickerson_list_seq { #1 }
  \prop_gput:Nnn \g_wickerson_states_prop { #1 } { #2}
 }

\ExplSyntaxOff

\begin{document}
%% Initialize
\defineStates{a/10,b/30}\printStates

%% Add new states
\addStates{c/50}\printStates

\addStates{d/70,e/90}\printStates

%% remove some states
\removeStates{a,c}\printStates

\end{document}

enter image description here

5
  • I think it would be more natural to use a delimited function at the expl3 level, not xparse. Namely, \cs_new_protected:Npn \wickerson_split_element:ww #1 / #2 \q_stop {...}. Commented May 17, 2013 at 12:48
  • 1
    @BrunoLeFloch I agree, but I wanted to use less expl3 as possible. I'll provide a "full" `expl3 version later.
    – egreg
    Commented May 17, 2013 at 12:53
  • I'm trying to learn more about this expl3 stuff, but so far have only found a short introduction (here). Where can I learn about things like what {>{\SplitList{,}}m} means? Commented May 18, 2013 at 15:46
  • 1
    @JohnWickerson That's in the documentation of xparse: texdoc xparse
    – egreg
    Commented May 18, 2013 at 15:47
  • Note that as of September 2014, \prop_get:Nn has been renamed to \prop_item:Nn
    – egreg
    Commented Sep 17, 2014 at 17:58

You must log in to answer this question.

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