2

I want to use a macro to typeset programming code with the usual characteristics (particularly lines should be obeyed - this is the most important issue; highlighting and such stuff is - for now - not intended). This sounds simple enough, but I was not able to succeed so far. For some reason, something like this does not work:

\newcommand{\script}[1]{
\obeylines
#1
}

On the other hand, verbatim environments also don't work - at least not like this (obviously):

\newcommand{\script}[1]{
\begin{lstlisting}
#1
\end{lstlisting}
}

Some time ago I found this solution (which I don't really understand):

\makeatletter
\newcommand{\@skript}[1]{
    \def\skript{%
        {\bigbreak
         \begin{minipage}{\textwidth}
                \scriptsize\ttfamily{#1}
         \end{minipage}
}
    \par\noindent}
    \skript
\egroup}
\newcommand\skript{\par\bgroup\obeylines\@skript}
\makeatother

It works in principle, but you cannot build anything around it such as another macro or an ifthenelse statement.

Is there a good way to solve my problem?

  • 2
    Macros that change category codes (including \obeylines and \begin{lstlisting}) cannot be used in the argument to another command. – egreg Jun 24 '15 at 19:48
  • \obeylines actually does compile it just doesn't obey lines :-) – lukas.coenig Jun 24 '15 at 19:52
  • If you feed #1 to \obeylines, the lines of #1 are already read (tokenized). – Heiko Oberdiek Jun 24 '15 at 20:12
  • 4
    Packages listings, verbatim, fancyvrb, ... provide means to define own personalized environments. – Heiko Oberdiek Jun 24 '15 at 20:14
  • 1
    Your \skript example will not work with special characters. – Steven B. Segletes Jun 24 '15 at 20:15
1

As Heiko Oberdiek noted in the comments, the issue here is that when TeX is scanning the input to read macro parameters, it tokenizes the input. How this is carried out is described in great detail in the TeXbook, in TeX by Topic, and on this site.

Roughly speaking, this process is controlled by the category codes of the input characters that are in effect at the time the input is read. TeX reads each line of input at a time, removes trailing white space and adds an end of line character. When tokenizing, the end-of-line character is either

  • ignored (e.g., if the line ends with a comment or after a control word—meaning a control sequence consisting of letters like \relax or \item);
  • converted to a space token which happens if there is some nonspace input on the line; or
  • converted to a \par token if the line only contains whitespace.

Again, full details are elsewhere.

So given a macro like \newcommand\foo[1]{#1}, when TeX encounters

\foo{%
    asdf

    123
}

it needs to determine what the parameter to \foo is, so it reads all of the input between the braces and produces a sequence of tokens.

The line containing \foo{% ends with the end-of-line character (as all lines do) but the comment causes it to be ignored so no tokens are produced.

In the next line, \asdf, leading spaces are ignored (as they always are by default) so TeX produces the four letter tokens a, s, d, and f. Here, the end-of-line character is on a line that contains something other than white space, so a space token is produced.

The next line is blank so the end-of-line character causes a \par token to be produced. The next line, 123 is similar to the asdf line. It produces 3 other tokens 1, 2, and 3 and the end-of-line character causes a space token to be produced.

Putting that all together, TeX treats this identically to if you had written

\foo{asdf \par123 }

By the time \foo gets around to getting expanded, there are no "lines" of text left at all. Just a sequence of 10 tokens.

This takes us to the example you don't really understand. Let me try a simpler one that will hopefully make more sense.

\documentclass{article}

\newcommand*\skript{%
        \par
        \begingroup
        \parindent=0pt
        \obeylines
        \obeyspaces
        \skriptx
}
\newcommand\skriptx[1]{%
        #1%
        \par
        \endgroup
}

\begin{document}

\skript{
Here's some text
that obeys lines      and spaces
and it's part of the argument to a macro.
As you note, this cannot be used inside
another macro.
}

\end{document}

I've defined two macros, \skript and \skriptx. The first does not take any parameters. This is crucial because as noted above, parameters are tokenized before the macro is expended. As a result, \skript expands to it's replacement text immediately. The replacement text ends any current paragraph, starts a new group, sets paragraph indentation to 0pt, instructs TeX to change end-of-line characters and spaces, and finally expands \skriptx.

At the point \skriptx is encountered, its parameter text will be tokenized according to the current category codes in effect. Since \obeylines and \obeyspaces were executed before \skriptx's parameters were tokenized, you get the newlines you want in the parameter to \skriptx.

The result looks like this.

enter image description here

But there seriously is no way of telling latex to simply obey lines in a macro parameter??

Not really. You can tell TeX to change how it handles the end-of-line character before it has tokenized the character, but once it has tokenized it, it's too late.

The same is true of other "special" characters like _, ^, %, and # (and several others).

Hopefully it's now clear why you can't put something like \begin{lstlisting} or \verb inside another macro and expect it to handle special characters (including end-of-line characters) correctly.

In closely, let me say that this is usually not an issue for typesetting code. I recommend putting your code in a separate file and using \lstinputlisting to typeset it.

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.