2

I am trying to write verbatim text into a file in Plain TeX. In LaTeX I could use the verbatim package. My goal is using Asymptote in Plain TeX. So I need something that works like \verbatim@start from verbatim.sty.

MWE:

% Plain LuaTeX
\catcode`@=11
% from latex.ltx
\long\def\@ifnextchar#1#2#3{%
    \let\reserved@d=#1%
    \def\reserved@a{#2}%
    \def\reserved@b{#3}%
    \futurelet\@let@token\@ifnch}
\def\@ifnch{%
    \ifx\@let@token\@sptoken
    \let\reserved@c\@xifnch
    \else
    \ifx\@let@token\reserved@d
    \let\reserved@c\reserved@a
    \else
    \let\reserved@c\reserved@b
    \fi
    \fi
    \reserved@c}
\def\:{\let\@sptoken= } \:  % this makes \@sptoken a space token
\def\:{\@xifnch} \expandafter\def\: {\futurelet\@let@token\@ifnch}

\def\@makeother#1{\catcode`#1=12\relax}

Let's try to write.

\newwrite\file
\immediate\openout\file=temp.tmp\relax

\def\verbatimwrite{\begingroup\catcode`\^^M=12\relax
    %\let\do\@makeother\dospecials%%%%%%% PROBLEM! (A)
    \@verbatimwrite}

\begingroup
\catcode`\^^M=12 %
\gdef\@verbatimwrite#1^^M{% Every line must be written separately to save the line breaks.
    \immediate\write\file{\unexpanded{#1}}%
    \@ifnextchar\endverbatimwrite\endgroup\@verbatimwrite}% detect the end of verbatim (B)
\endgroup

\let\endverbatimwrite\relax

\verbatimwrite
123#;^{t}
äöüß

4567\undefined
\endverbatimwrite

\immediate\closeout\file

\bye

There are 2 problems (also as comments in the code):

  • (A): Where do I have to change all the other catcodes? It should be possible for example to write unbalanced braces. If this is done correctly I think the \unexpanded command is not needed any more.
  • (B): Do you have an idea how to improve the detection of \endverbatimwrite? I think my method is really poor.

Thanks.

1
  • For future users who might come across this question -- if you want to define a new environment that captures the argument verbatim and does something else with it in LaTeX, there are already packages to do that -- see tex.stackexchange.com/a/130298/250119
    – user202729
    Sep 12, 2021 at 0:33

1 Answer 1

2

You were changing the catcodes at the right moment. The problem was that after \let\do\@makeother \dospecials the guard \endverbatimwrite is not a control sequence anymore, but the backslash character plus a sequence of 16 letters. This made your code fail because your \@ifnextchar test looks for the control sequence \endverbatimwrite, which is never found.

As for detecting \endverbatimwrite, you don't need to search for it after every line (you can, but you don't have to). The biggest problem is, again, due to the catcode changes: you don't have \endverbatimwrite anymore, you have \ e n d v e r b a t i m w r i t e, which is completely different (and much harder to find with \@ifnextchar; you'd need a loop of some sort, more or less like listings detects the end of the environment). It's much easier to just define a delimited macro that looks for the string \endverbatimwrite with the correct catcodes.

Usually the algorithm for a verbatim environment (the simple ones, of course) is: 1) Grab arguments (optional arguments, file name to write or, in this case, the write stream); 2) \begingroup; 3) \catcodes; 4) Grab the contents of the command/environment; and 5) \endgroup and process the grabbed characters (the order of these last two may vary).

Here's a modified version of your code (based some of it form here):

\catcode`@=11

\def\@makeother#1{\catcode`#112\relax}
\def\verbatimwrite#1{%
  \begingroup
    \catcode`\^^M=13
    \newlinechar`\^^M
    \let\do\@makeother \dospecials
    \verbwrite@cleanup#1}
\begingroup
  \lccode`\?=`\\
  \lowercase{\endgroup% \
\def\verbwrite@other#1#2?endverbatimwrite{%
  \immediate\write#1{\unexpanded\expandafter
    {\verbwrite@strip@newline#2\relax}}%
  \endgroup}}
\begingroup
  \catcode`\^^M=13
  \gdef\verbwrite@cleanup#1#2^^M{%
    \if\relax\detokenize{#2}\relax
    \else \errmessage{Wrong input `#2' discarded}%
    \fi
    \verbwrite@other#1}
  \gdef\verbwrite@strip@newline#1^^M\relax{#1}
\endgroup

Let's try to write.

\newwrite\file
\immediate\openout\file=temp.tmp\relax

\verbatimwrite\file
123#;^{t}
äöüß}

4567\undefined{
\endverbatimwrite

\immediate\closeout\file

\bye

The output file contains:

123#;^{t}
äöüß}

4567\undefined{

11
  • Thank you. But I don't understand why \verbwrite@cleanup is needed. What does it prevent? Jan 12, 2020 at 20:44
  • @user125730 When \verbatimwrite does its thing, the end of that line is tokenized as ^^M, so you get a leading empty line in the output file. \verbwrite@cleanup removes that first ^^M and ensures that you didn't write any code in that same line (like \verbatimwrite\file abc<newline>). To see the difference in the output, just replace the \verbwrite@cleanup in the definition of \verbatimwrite by \verbwrite@other. Jan 12, 2020 at 20:48
  • Another question: Is \unexpanded still needed? When I delete it nothing changes. (...\immediate\write#1{\verbwrite@strip@newline#2\relax}...) Jan 12, 2020 at 21:10
  • @user125730 I added the \unexpanded for the code here because (pdf)LaTeX makes characters like äöüß active to cope with Unicode in 8-bit engines, so without \unexpanded the \write will expand them and BOOM! :-) For LuaTeX (Plain TeX in general, for that matter, except for Knuth's TeX) you probably don't need the \unexpanded. Though it doesn't harm either... Jan 12, 2020 at 21:22
  • So how does PDFLaTeX avoid the expansion of UTF8 characters? :D When you write something like \chapter{äöüß} there is no problem. äöüß will appear correctly in the toc file. Jan 12, 2020 at 21:50

You must log in to answer this question.

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