8

I am having some problems when I define a macro whose content has the lstlisting environment. For example, (assume ! is the escape char inside the lstlisting environment)

\def\mycode#1{
    \begin{lstlisting}
    class Person {
        private int !{\bf #1}!;
        int getAge(){
            return !#1!;
        }
    }
    \end{lstlisting}
}

When I use \mycode{age} I get an error. Is there any way to fix this (note that I've tried lrbox environment but without success)?

What I really want is some macro definition stuff that simply replaces the arguments in the text and copies the resulting content to where the macro is called. That is, where the following line appeared

\mycode{age}

LaTeX would replace it by

   \begin{lstlisting}
    class Person {
        private int !{\bf age}!;
        int getAge(){
            return age;
        }
    }
    \end{lstlisting}

and start processing of \begin{lstlisting} ... \end{lstlisting}.

This should be simple, but unfortunately it seems too complicated (at least for me).

2
  • 1
    I would like to ask you to include the Minimal Working Example, and copy the exact text of the LaTeX Error you get. Then your problem can be much more easily solved.
    – yo'
    Jan 24, 2012 at 13:12
  • 2
    You can't; lstlistings is like verbatim, so it cannot go in the argument to another command.
    – egreg
    Jan 24, 2012 at 13:21

3 Answers 3

8

the environment lstlisting cannot be an argument of another macro.

\documentclass{article}
\usepackage[T1]{fontenc}    
\usepackage{listings}
\lstnewenvironment{mycode}[1]
  {\lstset{escapechar=!}\gdef\myPara{#1}}
  {}

\begin{document}

\begin{mycode}{age}
    class Person {
        private int !\textbf{\myPara}!;
        int getAge(){
            return !\myPara!;
        }
    }
\end{mycode}
\end{document}

enter image description here

3
  • Since I am using frames with beamer, I had to put your code inside a \begin{frame}[fragile]\begin{mycode}{age}...\end{mycode}\end{frame} for it to work: and it worked brilliantly!!! Thanks!
    – Mário
    Jan 24, 2012 at 13:43
  • SORRY!!! MY MISTAKE!!! THAT IS NO SOLUTION, because it requires me to copy the code over and over, and what I want is too write a macro having the code once, and reuse it with different parameters in other places.
    – Mário
    Jan 24, 2012 at 13:54
  • 3
    @Mário: In this case you need to store it in an external file an use \lstinputlisting to load it every time you need it. You can write the text into the external file using the filecontents environment. Jan 24, 2012 at 14:20
3

With the newer engines that support \scantokens, you can directly use that one so you don't need to use external files. (*)

(*): it's "equivalent" to using external files.


Method 1: custom macro \cprotDef to make defining such macros easy

In the code below, \cprotDef is defined, which is similar to \cprotect, but for macro definitions.

It seems to be "do only argument substitution", while making the user-side macro definition as simple as possible (although in this case the catcode information of the inputs are discarded)

Note that the commands defined using this command will use the catcodes at the point of execution, not the point of definition (so, for example \ExplSyntaxOff before calling the macro itself).

%! TEX program = lualatex
\documentclass{article}
\usepackage{listings}
\begin{document}


% ======== Define the helper command \cprotDef to make the main macro definition (see below) easy. ========

\errorcontextlines=10000
\ExplSyntaxOn

% format: \cprotDef \cs_set_protected:Npn \control_sequence (parameter text) {(replacement text)}
% or \cprotDef {\cs_set_protected:Npn \control_sequence (parameter text)} {(replacement text)}
\cs_set_protected:Npn \cprotDef #1 # {
    \tl_if_empty:nTF {#1} {
        \cprotDef_auxi:n
    } {
        \cprotDef_auxi:n {#1}
    }
}

\cs_set_protected:Npn \cprotDef_auxi:n #1 {
    \begingroup

    % after reading the control sequence and the parameter text, set catcode

    % (list of all special characters can be found in \dospecials macro)

    % special characters that keeps their meaning
    %\{
    %\}
    %\#

    % extra not listed in dospecials
    \char_set_catcode_other:N \^^I
    \char_set_catcode_other:N \^^J

    % the rest are set to (other).
    \char_set_catcode_other:N \  %space
    \char_set_catcode_other:N \\
    \char_set_catcode_other:N \$
    \char_set_catcode_other:N \&
    \char_set_catcode_other:N \^
    \char_set_catcode_other:N \_
    \char_set_catcode_other:N \%
    \char_set_catcode_other:N \~

    \newlinechar=`\^^J % which character is considered a newline when printed / by \scantokens
    \endlinechar=`\^^J % which character is put after each line
    \relax

    % then read in the {(replacement text)}
    \cprotDef_aux:nn {#1}
}

\cs_set_protected:Npn \cprotDef_aux:nn #1 #2 {
    % #1 = (stored)  approximately `\def \control_sequence (parameter_text)'
    % #2 = (newly read)  (replacement_text), with catcode of most special characters set to (other)

    \endgroup  % restore the catcode values

    #1 {\scantokens{#2}}
}
\ExplSyntaxOff

% ======== Define the main command. ========

\cprotDef \protected \def \mycode #1 {
\begin{lstlisting}[escapechar=!]
class person {
    private int !{\bf #1}!;
    int getAge(){
        return #1;
    }
}
\end{lstlisting}
}

% ======== Use that command. ========
\mycode{age}


\end{document}

Method 2. Use scontents.

In this particular case you don't really need to substitute the value of the argument into the code itself (can assign a macro outside and use it inside).

%! TEX program = xelatex
\documentclass{article}
\usepackage{listings}
\usepackage{scontents}
\begin{document}

\begin{scontents}[store-env=myStorageListing]
\begin{lstlisting}[escapechar=!]
class person {
    private int !{\bf \myAge}!;
    int getAge(){
        return age;
    }
}
\end{lstlisting}
\end{scontents}

\ExplSyntaxOn
% ======== Define the main command. ========
\NewDocumentCommand \myCommand {m} {
    \tl_set:Nn \myAge {#1}
    \getstored{myStorageListing}
}
\ExplSyntaxOff

% ======== Use that command. ========
\myCommand {age}

\end{document}

Method 3: raw \scantokens

Build the code one part at a time and finally \scantokens it. This is the strongest method, but may be somewhat hard to read.

More explanation: https://tex.stackexchange.com/a/622505/250119

%! TEX program = xelatex
\documentclass{article}
\usepackage{listings}
\begin{document}
\ExplSyntaxOn
% ======== Define \myTLSetVerbatim : similar to \tl_set:Nn, but second argument is of xparse's +v type. ========
\NewDocumentCommand \myTLSetVerbatim {m +v} {
    \tl_set:Nn #1 {#2}
}
\ExplSyntaxOff
% ======== Define some auxiliary helper TLs. ========
\myTLSetVerbatim \myTextBefore @\begin{lstlisting}[escapechar=!]
class person {
    private int !{\bf @
\myTLSetVerbatim \myTextAfter   @}!;
    int getAge(){
        return age;
    }
}
\end{lstlisting}@
% ======== Define the main command. ========
\ExplSyntaxOn
\NewDocumentCommand \myCommand {m} {
    \exp_args:Nx \scantokens {
        \myTextBefore #1 \myTextAfter
    }
}
\ExplSyntaxOff
% ======== Use that command. ========
\myCommand {age}
\end{document}

Because you're "only replace text", you can't put verbatim content into it even with +v argument unless you put it in verbatim yourself (!{\bf <verbatim content>}! also doesn't work in the environment.)

Method 4. \cprotect

\cprotect can be used like this: (internally it uses external file)

This solution is less neat than scontents and have the same restrictions.

%! TEX program = xelatex
\documentclass{article}
\usepackage{listings}
\usepackage{cprotect}
\begin{document}

% ======== Define the helper commands.
\cprotect {\newcommand \myCommandAux} {
\begin{lstlisting}[escapechar=!]
class person {
    private int !{\bf \myAge}!;
    int getAge(){
        return age;
    }
}
\end{lstlisting}
}
\ExplSyntaxOn
% ======== Define the main command. ========
\NewDocumentCommand \myCommand {m} {
    \tl_set:Nn \myAge {#1}
    \myCommandAux
}
\ExplSyntaxOff

% ======== Use that command. ========
\myCommand {age}

\end{document}
0

The problem with the environment lstlisting is the same as with environments like verbatim:

It expects its "body" and the phrase \end{lstlisting} to be tokenized under some non-standard-catcode-régime, yielding a set of tokens differing from the set of tokens one would obtain by tokenizing under standard-catcode-régime.

If coming from the replacement-text of a macro \mycode whose definition-text was tokenized under standard-catcode-régime, then the phrase \end{lstlisting} won't be recognized as the end of the environment lstlisting.

As a workaround I can offer a routine

\DefineVerbatimToScantokens{⟨control-word-token⟩}{⟨xparse-argument-specifiers⟩}{%
  ⟨verbatim-material to be passed to \scantokens⟩
}%

⟨verbatim-material to be passed to \scantokens⟩ is read and tokenized under verbatim-catcode-régime.

Then within ⟨verbatim-material to be passed to \scantokens⟩ every # is replaced by a character-token of catcode 6(parameter).

Then ⟨control-word-token⟩ is defined to process arguments according to ⟨xparse-argument-specifiers⟩ and to pass ⟨verbatim-material to be passed to \scantokens⟩ to \scantokens.

\documentclass{article}
\usepackage[T1]{fontenc}    
\usepackage{listings}

%=== Code of \DefineVerbatimToScantokens ========================
% With older LaTeX-releases uncomment the following line:
%\usepackage{xparse}   
\NewDocumentCommand\DefineVerbatimToScantokens{mm}{%
  \begingroup
  \catcode`\^^I=12\relax
  \InnerDefineVerbatimToScantokens{#1}{#2}%
}%
\begingroup
\makeatletter
\def\InnerDefineVerbatimToScantokens#1#2{%
  \endgroup
  \NewDocumentCommand\InnerDefineVerbatimToScantokens{mm+v}{%
    \endgroup\ReplaceHashloop{##3}{##1}{##2}%
  }%
  \newcommand\ReplaceHashloop[3]{%
    \ifcat$\detokenize\expandafter{\Hashcheck##1#1}$%
    \expandafter\@firstoftwo\else\expandafter\@secondoftwo\fi
    {%
      \NewDocumentCommand{##2}{##3}{%
         \begingroup\newlinechar=\endlinechar
         \scantokens{\endgroup##1#2}%
      }%
    }{%
      \expandafter\ReplaceHashloop\expandafter{\Hashreplace##1}{##2}{##3}%
    }%
  }%
  \@ifdefinable\Hashcheck{\long\def\Hashcheck##1#1{}}%
  \@ifdefinable\Hashreplace{\long\def\Hashreplace##1#1{##1####}}%
}%
\catcode`\%=12\relax
\catcode`\#=12\relax
\InnerDefineVerbatimToScantokens{#}{%}%
%=== End of code of \DefineVerbatimToScantokens =================


% Be aware that indenting does matter within \DefineVerbatimToScantokens's
% <verbatim-material to be passed to \scantokens>-argument:

\DefineVerbatimToScantokens\mycode{m}{%
\begin{lstlisting}[escapechar=!]
class Person {
    private int !\textbf{#1}!;
    int getAge(){
        return !#1!;
    }
}
\end{lstlisting}
}%



\begin{document}

\noindent aaa

\noindent \mycode{age}

\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .