LuaTeX: Mimicking File System Input
07 Jul 2020In LaTeX, verbatim environments are extremely tricky. Different verbatim environments are based on distinct LaTeX magic, which makes their behavior inconsistent. The only realiable way to use a verbatim environment is to write them in the TeX source as-is, for any attempt to construct such environments programmatically usually fails. In existing packages, such problems are avoided by saving the constructed environments into files first and then use \input
command to read them as TeX source files. I really dislike this solution as it induces significant I/O overhead (despite the fact that other I/O bottleneck may be more dominant). In this post, I provide a LuaTeX-based method that allows \input
from Lua strings.
(This post corresponds to my question on TeX.SE.)
Using LuaTeX with verbatim
At the beginning of my investigation, I found this post, which suggestes constructing verbatim environments with LuaTeX.
\directlua{tex.print([[\unexpanded{\begin{verbatim}]]..'one\rtwo'..[[\end{verbatim}}]])}
Unfortunately, this only works with verbatim
environment. When I try this on minted
, which is based on fancyvrb
, errors will occur.
% this won't work
\directlua{tex.print([[\unexpanded{\begin{minted}{python}]]..'one\rtwo'..[[\end{minted}}]])}
This just shows that LuaTeX’s tex.print
is still subject to LaTeX’s standard parsing routine. We need a way to decouple our constructed verbatim environments from this routine. The most common solution is to use an external file. But I dislike this idea because disks are slow (maybe not anymore recently…). The file system approach uses file as an outlet to escape from TeX’s parsing routine. Since LuaTeX introduces Lua as an scripting engine apart from TeX, I think there must be a way to use Lua as the outlet.
The approach
According to LuaTeX reference, we are able to customize find_read_file
and open_read_file
callbacks. When we call \input{filename}
, the find_read_file
callback is first invoked to search for filename
(potentially in working directory and $PATH
). If filename
is found, find_read_file
callback returns the actual filename of the file; otherwise, it returns nil
. If filename
is found, then open_read_file
callback is invoked to read the file. This callback returns a Lua table object, which contains a reader
key. Here, reader
is a function that returns a line from the file for each invocation. When EOF is reached, reader
returns nil
. Therefore, we can establish the following procedure to mimic file system input with a Lua string.
- Construct and save contents in a Lua string.
- Define a TeX macro as follows:
\newcommand{\TFBInputAsFile}{ % register custom callbacks \directlua{tex_file_buffer:register_callback()} % input some random filename, which will be processed by our own callbacks \input randomfile }
- In the
reader
function, return the Lua string and immediately remove our own callback so that it doesn’t affect the read of other files.
The source code can be seen in the source code section.
Example usage
\documentclass{article}
\usepackage[T1]{fontenc}
\usepackage{minted}
\usepackage{tex-file-buffer} % import source code
\usepackage{datetime2}
\usepackage{expl3}
\usepackage{minted}
\begin{document}
% construct buffer
\ExplSyntaxOn
\str_clear:N \l_tmpa_str
\exp_args:Nx \TFBAppend {\c_backslash_str begin{minted}{python}} \TFBAppendCR
\exp_args:Nx \TFBAppend {print("abc")} \TFBAppendCR
\exp_args:Nx \TFBAppend {\c_backslash_str end{minted}}
\ExplSyntaxOff
\TFBUse % show buffer as plain text
\TFBInputAsFile % read buffer as file
\TFBClear % clear buffer
\par\DTMNow
\end{document}