2

I am a beginner in Latex programming. I'm working on a small package based on arrayjobx. I have created structures to handle associative arrays as described in arrayjobx.pdf. My intention is to add some features of high level, but I've encountered the first problem by creating a macro that you exchange the values of two elements of the same array. The error message "Undefined control sequence" is showed. Any help is welcome.

The code of the package, called arrayassoc.sty, is the following:

\NeedsTeXFormat{LaTeX2e}[1994/06/01]
\ProvidesPackage{arrayassoc}
  [2016/08/11 v1.0 of arrayassoc package]

\RequirePackage{arrayjobx,multido}

\makeatletter
% Internally, we use three "standard" arrays to define one associative array
\newarray\AssociativeArray@Names 
\newarray\AssociativeArray@Values
\newarray\AssociativeArray@Indexes

\newcount\AssociativeArrayNbValues
\AssociativeArrayNbValues=\z@

\newif\ifAssociativeArray@ElementFound

% To store one element
\def\AssociativeArray(#1)=#2{%
\expandarrayelementtrue
\AssociativeArray@ElementFoundfalse
\edef\@tempa{#1}%
\edef\@tempb{#2}%
\Multido{\iValue=\@ne+\@ne}{\AssociativeArrayNbValues}{%
  \checkAssociativeArray@Names(\iValue)%
  \ifx\@tempa\cachedata
    % This element already exist: we replace its current value
    % \checkAssociativeArray@Values(\iValue)% Debug
    % \typeout{In #1, replace ‘\cachedata’\space by ‘#2’}% Debug
    \AssociativeArray@Values(\iValue)={\@tempb}%
    \AssociativeArray@ElementFoundtrue
    \multidostop
  \fi}
\ifAssociativeArray@ElementFound
\else
  % New element:
  \advance\AssociativeArrayNbValues\@ne
  \AssociativeArray@Names(\AssociativeArrayNbValues)={\@tempa}%
  \AssociativeArray@Values(\AssociativeArrayNbValues)={\@tempb}%
  \AssociativeArray@Indexes(\AssociativeArrayNbValues)={\the\AssociativeArrayNbValues}%
\fi}

% To get one element
\def\checkAssociativeArray(#1){%
\edef\@tempa{#1}%
\edef\@tempb{999999}%
\Multido{\iValue=\@ne+\@ne}{\AssociativeArrayNbValues}{%
  \checkAssociativeArray@Names(\iValue)%
  \ifx\@tempa\cachedata
    % We have found it by name
    \edef\@tempb{\iValue}%
    \multidostop
  \fi}
% We have now to get its value
\checkAssociativeArray@Values(\@tempb)}

% To get one index
\def\checkiAssociativeArray(#1){%
\edef\@tempa{#1}%
\edef\@tempb{999999}%
\Multido{\iValue=\@ne+\@ne}{\AssociativeArrayNbValues}{%
  \checkAssociativeArray@Names(\iValue)%
  \ifx\@tempa\cachedata
    % We have found it by name
    \edef\@tempb{\iValue}%
    \multidostop
  \fi}
% We have now to get it index
\checkAssociativeArray@Indexes(\@tempb)%
%same result if replace above line by: \@tempb
}

% Simple macro to print all the associative array
\def\printAssociativeArray{%
\par\noindent%
Array (\newline
\multido{\iValue=\@ne+\@ne}{\AssociativeArrayNbValues}{%
  \checkAssociativeArray@Names(\iValue)%
  \hphantom{Ar}\iValue: [‘\cachedata’] $=>$ ‘\AssociativeArray@Values(\iValue)’\newline}%
)\par%
}

% A simple macro to print the array of indexes (for debugging)
\def\printIndexes{%
\par\noindent%
Array (\newline
\multido{\iIndex=\@ne+\@ne}{\AssociativeArrayNbValues}{%
  \checkAssociativeArray@Indexes(\iIndex)%
  \hphantom{Ar}index(\iIndex) $=$ ‘\cachedata’\newline}%
)\par%
}

\def\echoAssociativeArray(#1){%
  \checkAssociativeArray(#1)%
  \ifemptydata%
    \null\relax%
  \else%
    \cachedata%
  \fi%
}

\def\echoiAssociativeArray(#1){%
  \checkiAssociativeArray(#1)%
  \ifemptydata%
    0% index not found
  \else%
    \cachedata%
  \fi%
}

\def\firstValueAssociativeArray{%
  \AssociativeArray@Values(1)
}
\def\firstKeyAssociativeArray{%
  \AssociativeArray@Names(1)
}
\def\lastValueAssociativeArray{%
  \AssociativeArray@Values(\AssociativeArrayNbValues)
}
\def\lastKeyAssociativeArray{%
  \AssociativeArray@Names(\AssociativeArrayNbValues)
}

\def\lengthAssociativeArray{%
  \the\AssociativeArrayNbValues
}

\newif\ifAssociativeArray@FirstElementFound
\newif\ifAssociativeArray@SecondElementFound

\def\swapAssociativeArray(#1)(#2){%
\expandarrayelementtrue%
\def\@tempi{\echoAssociativeArray(#1)}%
\def\@tempj{\echoAssociativeArray(#2)}%
\@tempi, \@tempj% for debugging
\AssociativeArray(#1)={\@tempj}% this doesn't work
\AssociativeArray(#2)={\@tempi}% this doesn't work

%I've replaced the two lines of code above with the following code, but it does not work.
%\AssociativeArray@FirstElementFoundfalse%
%\AssociativeArray@SecondElementFoundfalse%
%\edef\@tempa{#1}%
%\edef\@tempb{#2}%
%\Multido{\iValue=\@ne+\@ne}{\AssociativeArrayNbValues}{%
%  \checkAssociativeArray@Names(\iValue)%
%  \ifx\@tempa\cachedata
%    % First element exists: we replace its current value
%    \AssociativeArray@Values(\iValue)={\@tempj}% this command fails
%    \AssociativeArray@FirstElementFoundtrue
%  \else
%    \ifx\@tempb\cachedata
%      % Second element exists: we replace its current value
%      \AssociativeArray@Values(\iValue)={\@tempi}% this command fails
%      \AssociativeArray@SecondElementFoundtrue
%    \fi
%  \fi
%  \ifAssociativeArray@FirstElementFound
%   \ifAssociativeArray@SecondElementFound
%      \multidostop
%    \fi
%  \fi}
}
\makeatother

\newcommand{\newAssociativeArray}[1]{%
  \global\expandafter\let\csname#1\endcsname\AssociativeArray%
  \global\expandafter\let\csname#1Check\endcsname\checkAssociativeArray%
  \global\expandafter\let\csname#1Checki\endcsname\checkiAssociativeArray%
  \global\expandafter\let\csname#1Print\endcsname\printAssociativeArray%
  \global\expandafter\let\csname#1Printi\endcsname\printIndexes%
  \global\expandafter\let\csname#1Echo\endcsname\echoAssociativeArray%
  \global\expandafter\let\csname#1Echoi\endcsname\echoiAssociativeArray%
  \global\expandafter\let\csname#1First\endcsname\firstValueAssociativeArray%
  \global\expandafter\let\csname#1Last\endcsname\lastValueAssociativeArray%
  \global\expandafter\let\csname#1FirstKey\endcsname\firstKeyAssociativeArray%
  \global\expandafter\let\csname#1LastKey\endcsname\lastKeyAssociativeArray%
  \global\expandafter\let\csname#1Length\endcsname\lengthAssociativeArray%
  \global\expandafter\let\csname#1Swap\endcsname\swapAssociativeArray%
}

\endinput
%%
%% End of file `arrayassoc.sty'.

The MWE is:

\documentclass{article}
\usepackage{arrayassoc}

\begin{document}
\newAssociativeArray{word}

\word(a)={a}
\word(b)={b}

\wordSwap(a)(b)
\wordPrint
\end{document}
  • Why is this a MINIMAL WE? Doesn't the document \documentclass{article}\usepackage{arrayassoc}\begin{document}\newAssociativeArray{word}\word(a)={love}\word(c)={air}\wordSwap(a)(c)\end{document} exhibit the same problem, while being considerably shorter? – gernot Aug 17 '16 at 16:09
  • Sorry, mwe corrected. – delrj Aug 17 '16 at 17:06
  • I'd never use arrayjobx; you're better to have a look at expl3. – egreg Aug 17 '16 at 17:49
1

Let me address your problem on several levels, the most abstract (and most important) one first.

You are new to TeX programming, as you say. Even though some packages on top of TeX may give the impression that it is an ordinary programming language, it is quite different. This means you will not get very far with a project like yours before you haven't understood some key concepts of TeX, and haven't learned some key techniques. The best source in my opinion is still The TeXbook; you don't need everything, but dive into it. After that you will learn from looking at the code of others.

To analyze your problem, insert

\tracingmacros=2

before the line causing the problem, i.e. \wordSwap(a)(b). Run TeX, abort it when the error occurs, and check the log file. There you will see that \AssociativeArray is expanded with \@tempj as second argument. The fourth line of the definition of \AssociativeArray hence reads

\edef\@tempb{\@tempj}

Now it is important to understand what \edef does, and what it doesn't do. It expands \@tempj until only non-expandable tokens are left (governed by rules described in The TeXbook). This way tokens might get expanded that wouldn't during ordinary execution, or might have been set to a different value. \edef does not 'run' the argument and then stuff the result into the macro \@tempb.

In your case \@tempj expands to \echoAssociativeArray, which expands to \checkAssociativeArray (among other stuff), which expands to \Multido, which expands to \multido@, which expands to \multido@@, which expands to \multido@initvar. Now \multido@temp is expanded (which does not happen during normal execution where it is only checked by \ifx) which accidentally happens to be \@nil. Further expansion fails, since \@nil is undefined.

One way to define the swap (at least for simple cases) is as follows:

\def\swapAssociativeArray(#1)(#2)
 {\checkAssociativeArray(#1)%
  \let\@tmpa\cachedata
  \checkAssociativeArray(#2)%
  \let\@tmpb\cachedata
  \AssociativeArray(#1)={\@tmpb}%
  \AssociativeArray(#2)={\@tmpa}%
 }

Last note: Is it intended that all associative arrays in fact refer to the same array? If you create a 'new' array by \newAssociativeArray{anotherword}, \anotherwordEcho(a) will do the same as \wordEcho(a), since both are \let to \echoAssociativeArray. The name of the array at creation time is remembered nowhere.

  • Hello gernot. First of all thank you for so detailed explanation you've provided about the problem I have. I didn't know the command \tracingmacros, which is very interesting, so from now on I will use it always to debug my Latex code. I admit that I haven't read "The Texbook", so my first task in hand will be to read it before continuing with my project. Your explanation about the command \edef has helped me very much to understand how it works in reality. Regarding your last question, yes I intended to refer all associative arrays to the same array. Is that a problem? Would you do it oth – delrj Aug 18 '16 at 13:54
  • Well, if you want to address the same associations under different names then your \lets are perfectly ok. I just thought that, judged by its name, the purpose of \newAssociativeArray would be to initialize a new set of associations, where all labels are again empty at the beginning. – gernot Aug 18 '16 at 14:22
  • You have reason, \newAssociativeArray should initialize a new set of associations. I'm going to try to recode this command to work as you have mentioned. – delrj Aug 18 '16 at 16:10

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.