6

I'm trying to write some macros in plain TeX for cross-references, analogous to LaTeX's \label, \ref and to Eplain's \xrdef, \xref. My definition of \label, called as \label {foo}, defines a control sequence \foo in another file. My version of \ref, called as \ref {foo}, should expand to the page number on which \label {foo} was called; if \foo is undefined, \ref informs me thereof by writing to the console. My \label works as intended. But my \ref invariably informs me that a label that I have defined is undefined.

Example:

\input ref-label
foo\label {foo}\vfill \eject
bar (\ref {foo})%
\immediate \closeout \refs
\bye

Here's ref-label.tex.

\def \reffile {refs}%
\newwrite \refs
\immediate \openout \refs = \reffile
\def \label #1{\def \text {#1}\makelabel}%
\def \makelabel {\edef \writeit
    {\write \refs
        {\string \def \space \csname \text \endcsname
            {\noexpand \number \pageno}}}%
    \writeit
}%
\def \ref #1{\expandafter \ifx \csname #1\endcsname \relax
    \immediate \write 16 {Undefined label ``#1''.}%
    \else \csname #1\endcsname \fi
}%
\input \reffile\ 

After one run of luatex on my example file, the file refs.tex contains \def \foo {1}; and "Undefined label ``foo''." is written to the console, as expected. But, even after a second run of luatex, \ref {foo} writes "Undefined label ``foo''." to the console. Hence, \foo appears to still be undefined though its definition is in fact present in refs.tex.

Note 1: My \label and \ref are cobbled together from the TeXbook's \xref [1] and Eplain's \xrdef, \xrefn [2]; they aren't really "mine".

Note 2: I'm writing the described macros in plain TeX only. I'd be grateful to not receive suggestions to use other formats, engines or packages.

[1]: The TeXbook, pp. 419--420.

[2]: TeX for the Impatient, pp. 302--304.

6

You need to input \reffile -at the start- to collect the values from the previous run. as is you do

\immediate \openout \refs = \reffile

which empties the file just before you do

\input \reffile\ 

incidentally that line should be

\input \reffile

otherwise you will force white space into the document.


\def \reffile {refs}%
\newwrite \refs

\newread\refcheck
\openin\refcheck=\reffile


\ifeof\refcheck\else
\input \reffile 
\fi

\immediate \openout \refs = \reffile
\def \label #1{\def \text {#1}\makelabel}%
\def \makelabel {\edef \writeit
    {\write \refs
        {\def \expandafter\string\csname \text \endcsname
            {\noexpand \number \pageno}}}%
    \writeit
}%
\def \ref #1{\expandafter \ifx \csname #1\endcsname \relax
    \immediate \write 16 {Undefined label ``#1''.}%
    \else \csname #1\endcsname \fi
}%
| improve this answer | |
  • Would you kindly explain why you've written \def \expandafter\string\csname \text \endcsname in \makelabel instead of my original \string \def \space \csname \text \endcsname? In particular, why the change from \string \def to \def \expandafter \string? – user3143179 May 10 '16 at 4:25
  • I see now that your \def \expandafter \string avoids an error that my \string \def doesn't: for the example file, my \string \def results in \def 1{1} being written to \reffile on the third call of tex and every second call thereafter. – user3143179 May 10 '16 at 4:47
  • @user3143179 yes:-) – David Carlisle May 10 '16 at 8:42
4

The main problem is, as already remarked by David Carlisle, that you're inputting \reffile after having opened it for writing, but this operation clears the file.

You also have assorted slips; for instance, closing the file should not be immediate, or you might lose material. Second, you are defining macros in the wild, which could overwrite existing ones. If you do \label{box}, you'll end up in very deep trouble. So it's better to decorate the macros with some prefix.

You also have a level of indirection that doesn't seem needed: it's useful if you have some part of the token list to be written that should be expanded immediately, which is not your case. There's no point in defining \text: the less macros you define, the least is the risk of overwriting needed ones.

I added a check for duplicate labels. Instead of writing directly the macros to the auxiliary file, I think it's better to have a \newlabel macro: it makes writing much easier.

\edef\reffile{\jobname refs.tex}

% read the labels
\def\newlabel#1#2{%
\expandafter\show\csname ref@#1\endcsname
  \expandafter\def\csname ref@#1\endcsname{#2}%
}

\newread\refcheck
\openin\refcheck=\reffile\relax % <--- important
\ifeof\refcheck % still no refs file
\else
  \input\reffile
\fi
\closein\refcheck

\newwrite\refs
\immediate\openout\refs=\reffile % this clears the file

\def\label#1{%
  \expandafter\ifx\csname label@#1\endcsname\relax
    \write\refs{\string\newlabel{#1}{\the\pageno}}%
    \global\expandafter\let\csname label@#1\endcsname\empty
  \else
    \immediate\write16{Duplicate label ``#1''.}%
  \fi
}
\def\ref#1{%
  \expandafter\ifx\csname ref@#1\endcsname\relax
     \immediate\write16{Undefined label ``#1''.}%
  \else
    \csname ref@#1\endcsname
  \fi
}

foo\label{foo}

\vfill\eject

bar (\ref{foo})%

\closeout\refs % not \immediate
\bye
| improve this answer | |
  • Would you kindly explain why \relax is necessary on the line on which you allocate a new input stream; or direct me to a resource that explains why? – user3143179 May 10 '16 at 4:21
  • And why \relax isn't necessary on the line on which you allocate a new output stream? – user3143179 May 10 '16 at 4:52
  • 1
    @user3143179 Without the \relax the conditional would be expanded too early; there's no such risk in the other case. In the \openin case also a blank line would be good. – egreg May 10 '16 at 6: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.