6

An answer to one of my previous questions provided this code for referencing a range of lines in which a line labels were placed at the beginning and end of the line range.

\makeatletter
\newcommand{\reflines}[1]{%
    \begingroup
    \def\temp@a{\getrefnumber{start:#1}}%
    \def\temp@b{\getrefnumber{end:#1}}%
    \ifnum\temp@a = \temp@b
    line \temp@a%
    \else
    lines \temp@a--\temp@b%
    \fi
    \endgroup
}
\makeatother

In the MWE, if we change one instance of \reflines to occur within \edef, there is a compilation error:

\documentclass{memoir}


\usepackage{refcount}
\usepackage{lineno}

\newcommand{\labellines}[2]{%
    \linelabel{start:#1}%
    #2%
    \linelabel{end:#1}%
}

\makeatletter
\newcommand{\reflines}[1]{%
    \begingroup
    \edef\temp@a{\getrefnumber{start:#1}}%
    \edef\temp@b{\getrefnumber{end:#1}}%
    \ifnum\temp@a = \temp@b
    line \temp@a%
    \else
    lines \temp@a\ and \temp@b%
    \fi
    \endgroup
}
\makeatother

\begin{document}
    \runninglinenumbers*
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam lacinia posuere
    magna ut imperdiet. \labellines{nunc-pellentesque}{Nunc pellentesque} velit quis
    leo interdum ullamcorper. Ut fringilla sapien ut sem viverra, et rhoncus enim
    lobortis. Morbi diam eros, tristique nec feugiat eu, consequat ac dui. Duis
    molestie, sem id efficitur dignissim, velit metus auctor orci, sit amet mattis
    metus purus in nibh. Etiam mattis, magna ut imperdiet molestie, ex leo viverra
    velit, ac posuere augue est maximus risus. Etiam ac pellentesque nulla. Morbi
    quis pharetra purus. Proin porta, turpis et scelerisque hendrerit, elit eros
    malesuada mi, a imperdiet massa est id nibh. Class aptent taciti sociosqu ad
    litora torquent per conubia nostra, per inceptos himenaeos.
    \labellines{nunc-pharetra}{Nunc pharetra vehicula metus, eu tincidunt arcu
        aliquet et. Aenean ac odio nunc. Etiam ultricies purus non dolor tincidunt, et
        ultricies turpis consequat. Integer vitae ullamcorper eros. Nullam pulvinar
        dictum nunc. Morbi ac justo mi.}

    % \reflines{nunc-pellentesque} should print "line 2"
    ``Nunc pellentesque\ldots'' was on \reflines{nunc-pellentesque}.

    \edef\nuncref{\reflines{nunc-pharetra}}
    % \reflines{nunc-pharetra} should print "lines 10--13"
    ``Nunc pharetra\ldots'' was on \nuncref.
\end{document}

Why is this?

6
  • The simple answer is that \def, by definition, is unexpandable because it is evaluated at time of execution. But I'm sure a fuller explanation will occur. Aug 4, 2015 at 16:14
  • @StevenB.Segletes, so does every command containing \def have to be defined using \DeclareRobustCommand? Aug 4, 2015 at 16:15
  • 1
    I don't know what you want to achieve here actually, but nice that you change my code ;-)
    – user31729
    Aug 4, 2015 at 16:16
  • 1
    I will let the Texperts provide a bulletproof explanation, rather than babble myself. Aug 4, 2015 at 16:16
  • 1
    @ChristianHupfer As Clint Eastwood so famously said, "a man has to know his own limitations." Aug 4, 2015 at 16:23

3 Answers 3

5

Let's see what happens when you have

\makeatletter
\newcommand{\reflines}[1]{%
    \begingroup
    \edef\temp@a{\getrefnumber{start:#1}}%
    \edef\temp@b{\getrefnumber{end:#1}}%
    \ifnum\temp@a = \temp@b
    line \temp@a%
    \else
    lines \temp@a\ and \temp@b%
    \fi
    \endgroup
}
\makeatother

and try to do

\edef\nuncref{\reflines{nunc-pharetra}}

First \reflines is expanded, which means finding its argument, absorbing it and replace the whole thing with the replacement text:

    \begingroup
    \edef\temp@a{\getrefnumber{start:nunc-pharetra}}%
    \edef\temp@b{\getrefnumber{end:nunc-pharetra}}%
    \ifnum\temp@a = \temp@b
    line \temp@a
    \else
    lines \temp@a\ and \temp@b
    \fi
    \endgroup

However, \edef goes all the way through expansion, so it continues scanning the token list for expandable tokens; the first two are \begingroup and \edef, which are not expandable, so they pass untouched. Next comes \temp@a, which is undefined; indeed, the error message is

! Undefined control sequence.
\reflines #1->\begingroup \edef \temp@a 
                                        {\getrefnumber {start:#1}}\edef \tem...
l.47     \edef\nuncref{\reflines{nunc-pharetra}
                                               }

It's a common misunderstanding that \edef also executes commands, which it actually doesn't. If you try

\edef\foo{\edef\foobar{A}}

assuming you have \def\foobar{B} somewhere before this, the replacement text of \foo would be

\edef B{A}

which will raise an error upon usage of \foo. It will not raise errors when \edef\foo{...} is executed. This has nothing to do with fragile commands at this level.

Trying \protected@edef\nuncref{\protect\reflines{nunc-pharetra}} is completely useless, because the replacement text of \nuncref would simply be

\protect\reflines{nunc-pharetra}

as doing \show\nuncref would show:

> \nuncref=macro:
->\protect \reflines {nunc-pharetra}.

It's not clear why you want to \edef your macro \nuncref, because there would be no advantage over simply doing

\newcommand\nuncref{\reflines{nunc-pharetra}
5
  • 1
    "It's a common misunderstanding that \edef also executes commands" --- I don't think any (mis-) understanding of \edef can be called common. Aug 4, 2015 at 20:54
  • @SimonKuang \edef is a bit complicated. ;-)
    – egreg
    Aug 4, 2015 at 20:55
  • .... definitely. Aug 4, 2015 at 20:56
  • 1
    So \edef tries totally expanding its argument, not just once? Aug 4, 2015 at 20:59
  • 1
    @SimonKuang Yes, all the way. On the contrary, \expandafter just performs one step.
    – egreg
    Aug 4, 2015 at 21:00
4

Your problem is caused by the fact \reflines is not expandable due to the presence of a group and assignments (\def). Those are not needed, so we can just drop them

\newcommand{\reflines}[1]{%
    \ifnum\getrefnumber{start:#1}=\getrefnumber{end:#1} %
      line \getrefnumber{start:#1}%
    \else
      lines \getrefnumber{start:#1}\ and \getrefnumber{end:#1}%
    \fi
}

at which point you can \edef \reflines. It is possible to force evaluation of the two look-ups only once each, but here the effort does not seem to be needed.

3
  • The reason why I used the \edef statements in the original answer as that it wasn't clear what the real output format should be, so I stored the values in between -- I had the direct \ifnum\getrefnumber in mind, however
    – user31729
    Aug 4, 2015 at 16:31
  • @ChristianHupfer Understood: for the case we have stated here I've just gone for fixing the immediate issue. As I've noted, we could force expansion of the two references and then pass them on the input stack if required.
    – Joseph Wright
    Aug 4, 2015 at 16:32
  • No worries... (+1)
    – user31729
    Aug 4, 2015 at 16:33
0

It's the old problem with fragile commands.

\documentclass{memoir}


\usepackage{refcount}
\usepackage{lineno}

\newcommand{\labellines}[2]{%
    \linelabel{start:#1}%
    #2%
    \linelabel{end:#1}%
}

\makeatletter
\newcommand{\reflines}[1]{%
    \begingroup
    \edef\temp@a{\getrefnumber{start:#1}}%
    \edef\temp@b{\getrefnumber{end:#1}}%
    \ifnum\temp@a = \temp@b
    line \temp@a%
    \else
    lines \temp@a\ and \temp@b%
    \fi
    \endgroup
}
\makeatother

\begin{document}
    \runninglinenumbers*
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam lacinia posuere
    magna ut imperdiet. \labellines{nunc-pellentesque}{Nunc pellentesque} velit quis
    leo interdum ullamcorper. Ut fringilla sapien ut sem viverra, et rhoncus enim
    lobortis. Morbi diam eros, tristique nec feugiat eu, consequat ac dui. Duis
    molestie, sem id efficitur dignissim, velit metus auctor orci, sit amet mattis
    metus purus in nibh. Etiam mattis, magna ut imperdiet molestie, ex leo viverra
    velit, ac posuere augue est maximus risus. Etiam ac pellentesque nulla. Morbi
    quis pharetra purus. Proin porta, turpis et scelerisque hendrerit, elit eros
    malesuada mi, a imperdiet massa est id nibh. Class aptent taciti sociosqu ad
    litora torquent per conubia nostra, per inceptos himenaeos.
    \labellines{nunc-pharetra}{Nunc pharetra vehicula metus, eu tincidunt arcu
        aliquet et. Aenean ac odio nunc. Etiam ultricies purus non dolor tincidunt, et
        ultricies turpis consequat. Integer vitae ullamcorper eros. Nullam pulvinar
        dictum nunc. Morbi ac justo mi.}

    % \reflines{nunc-pellentesque} should print "line 2"
    ``Nunc pellentesque\ldots'' was on \reflines{nunc-pellentesque}.

    \makeatletter
    \protected@edef\nuncref{\protect\reflines{nunc-pharetra}}
    \makeatother
    % \reflines{nunc-pharetra} should print "lines 10--13"
    ``Nunc pharetra\ldots'' was on \nuncref.
\end{document}
2
  • Instead of \protecting \reflines, we could also have it defined using \DeclareRobustCommand. Aug 4, 2015 at 16:12
  • This defeats the whole point of applying \edef (I assume): try \show\nuncref after your definition.
    – Joseph Wright
    Aug 4, 2015 at 16:29

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.