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?

  • 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. – Steven B. Segletes Aug 4 '15 at 16:14
  • @StevenB.Segletes, so does every command containing \def have to be defined using \DeclareRobustCommand? – Simon Kuang Aug 4 '15 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 '15 at 16:16
  • 1
    I will let the Texperts provide a bulletproof explanation, rather than babble myself. – Steven B. Segletes Aug 4 '15 at 16:16
  • 1
    @ChristianHupfer As Clint Eastwood so famously said, "a man has to know his own limitations." – Steven B. Segletes Aug 4 '15 at 16:23
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}
  • 1
    "It's a common misunderstanding that \edef also executes commands" --- I don't think any (mis-) understanding of \edef can be called common. – Simon Kuang Aug 4 '15 at 20:54
  • @SimonKuang \edef is a bit complicated. ;-) – egreg Aug 4 '15 at 20:55
  • .... definitely. – Simon Kuang Aug 4 '15 at 20:56
  • 1
    So \edef tries totally expanding its argument, not just once? – Simon Kuang Aug 4 '15 at 20:59
  • 1
    @SimonKuang Yes, all the way. On the contrary, \expandafter just performs one step. – egreg Aug 4 '15 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.

  • 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 '15 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 '15 at 16:32
  • No worries... (+1) – user31729 Aug 4 '15 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}
  • Instead of \protecting \reflines, we could also have it defined using \DeclareRobustCommand. – Simon Kuang Aug 4 '15 at 16:12
  • This defeats the whole point of applying \edef (I assume): try \show\nuncref after your definition. – Joseph Wright Aug 4 '15 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.