--shell-escape
启用后,pdfTeX
允许运行外部程序并使用 扩展输入结果\input|"..."
。与 Lua 解释器 一起使用texlua
,这可用于提供 的简化版本\directlua
。但是,texlua
需要文件名作为其参数。
在我的设置中(Ubuntu 10.04,安装了最新的 TeXLive 2012),可以pdftex --shell-escape
运行
\everyeof{\noexpand}
\message{\input|"echo print\string\(3.4+5.7\string\) > tmp.lua ; texlua tmp.lua" a}
\bye
得到\message{9.1}
。请注意,\everyeof{\noexpand}
部分是为了方便,可以在没有这个不可扩展的步骤的情况下使构造工作(不是完全稳健地)。
这种方法不可移植,因为它依赖于echo
,而 Windows 环境中可能不存在 。解决此问题的一种方法是使用一个简单的 Lua 脚本将代码print\string\(3.4+5.7\string\)
作为其参数(请参阅最近有一个关于 texlua 参数的问题),然后以某种方式执行它。这可能吗?否则,我们需要一种依赖于操作系统的方法。
第二个问题是特殊字符(例如括号)需要转义。哪些字符需要转义?我特别担心引号"
,它用于括住参数\input|
。给定要执行的转义列表,我有一些代码可以扩展地执行此操作(请参阅l3kernel-extras
LaTeX3 svn 存储库中的包)。
基本上,我的问题可以归结为:“如何尽可能方便地使用任意 Lua 代码texlua
进行调用?”pdfTeX
答案1
内置功能echo
在 Unix 和 Windows 上均可用,因此只要平台检测可用,那么问题主要在于决定要转义哪些字符。对于 LaTeX,可以使用ifplatform
提供\ifwindows
开关的包进行平台检测。
第一个需要担心的字符是“链式进程”。在 Windows 上,这是&
,而在 Unix 上;
是正确的。它使用类似
\RequirePackage{ifplatform}
\edef\luachainprocess{\ifwindows\string&\else;\fi }
然后是部分中特殊字符的问题echo
。对于 Windows,所需的替换是
>
取而代之^>
<
取而代之^<
|
取而代之^|
&
取而代之^&
^
取而代之^^
其余一切都被留下了echo
。
在 Unix 上,适当的列表似乎是
>
取而代之\>
<
取而代之\<
(
取而代之\(
)
取而代之\)
&
取而代之\&
|
取而代之\|
`
取而代之\`
\
取而代之\\
(注意:这是使用 bash shell,但其他的可能有所不同。)
^(
由于Windows 允许使用(
,因此最简单的方法可能是使用一组通用字符进行替换,而仅转义字符本身会有所不同。(对于 Unix 也是如此,例如\^
。)
有一件事完全不清楚,那就是在这两种情况下是否有办法处理"
,因为管道语法\input
没有提供这种转义,所以无论你做什么,它似乎都会被误解。这对于 Unix 的情况尤其令人沮丧,因为生成有效的
echo "<code>"
可以避免需要转义"
代码之外的任何内容。
答案2
我发现了loadstring
Lua 中的函数,它将字符串转换为 Lua 代码。为了避免 TeX → Lua 转换中的任何转义问题,TeX 代码将 的参数转换为\texlua
以空格分隔的字符代码列表,然后将其作为 的额外参数提供texlua
。Lua 代码使用 对其进行解码str.char
。
剩下的薄弱环节是\input|...
,就像任何其他的\input
或一样\scantokens
,会插入文件结束标记,而该文件结束标记可能不会出现在任何命令的参数中。我们需要删除它,一种方法是在文件结束之前和之后添加一些内容,即此处\romannumeral\numexpr0\noexpand
和\relax
(\noexpand
is 键)。不幸的是,如果出现 Lua 错误,则不会插入代码\romannumeral\numexpr0\noexpand
;在这种情况下,文件结束标记不会被删除,一切都会变得一团糟。
\begingroup
\catcode `@ = 11
%
% Get \texlua@input = primitive \input
\expandafter\ifx\csname @@input\endcsname\relax
\global\let\texlua@input\input
\else
\global\let\texlua@input\@@input
\fi
%
% Write very simple Lua file.
\newwrite \texlua@write
\xdef \texlua@file {\jobname.texlua}
\immediate \openout \texlua@write \texlua@file \relax
\immediate \write \texlua@write
{%
assert(loadstring(string.char(unpack(arg))))()%
\detokenize{print("\\romannumeral\\numexpr0\\noexpand")}%
}
\immediate \closeout \texlua@write
%
% Detokenize the argument and convert spaces to category other.
\gdef\texlua@str#1%
{%
\expandafter\texlua@str@i
\detokenize{#1}\\ \\\texlua@str@ii/ \relax
}
\lccode `* = 32
\lowercase{\gdef\texlua@str@i#1 #2\\#3 {#3\texlua@str@i#1*#2\\{#3} }}
\gdef\texlua@str@ii/#1#2\\#3\relax{#2}
%
% Convert each character to its character code, space-separated.
% This is not "f-expandable", it is "restricted expandable" in
% expl3 terms.
\gdef\texlua@num#1%
{\expandafter\texlua@num@i\romannumeral-`q\texlua@str{#1}\relax}
\gdef\texlua@num@i#1%
{%
\ifx #1\relax
\else
\number `#1 \space
\expandafter\texlua@num@i
\fi
}
%
% Calling `\input|`.
\gdef\texlua#1%
{%
\texlua@input|"texlua \texlua@file \space \texlua@num{#1}"%
\relax
}
\endgroup
\message{a\texlua{io.write(3+4)}b}
答案3
在筛选ConTeXt 中的模块提供了通过外部命令运行环境或宏内容所需的样板(坦白说,我是该模块的作者)。它不用于\input|
读取输出,而是假设外部程序将文件写入外部文件,然后用户可以指定如何读取文件(默认为\ReadFile
,类似于\input
)。
下面的例子说明了如何使用进行处理texlua
。
% engine=pdftex
\usemodule[filter]
\defineexternalfilter
[luatex]
[filtercommand={texlua \externalfilterinputfile > \externalfilteroutputfile}]
\starttext
\inlineluatex{print((3+5)*3)}
\startluatex
local t = {1,2,3}
print("Number of entries " .. #t)
\stopluatex
\stoptext
笔记:该\inlineluatex
宏不可扩展。