上一篇:时间管理
将 TeX 宏接到的参数传递于 Lua 函数,略含机巧。例如,将 \foo
接受的 Lua 表数据传递给 bar
函数,
\environment card-env
\startluacode
function bar(x)
context.startitemize{"n", "broad"}
for _, v in ipairs(x) do
context.item(v)
end
context.stopitemize()
end
\stopluacode
\def\foo#1{\ctxlua{bar({#1})}}
\starttext
\foo{"Hello", "world", "!"}
\stoptext
\foo
接到的参数,并非真正的 Lua 表,而是一段文本 "Hello", "world", "!"
。
宏调用语句
\foo{"Hello", "world", "!"}
里的这对花括号 {}
,它是 TeX 的编组(Group)符号,用于囊括一段文本并将其作为 \foo
的参数 #1
。换言之,对于上述宏调用语句而言,\foo
的定义里的参数 #1
是 "Hello", "world", "!"
,而非 {"Hello", "world", "!"}
。
在 \foo
的定义里,将 #1
的值传递给 Lua 函数 bar
时,我又给 #1
穿上了 {}
,此时,对于 Lua 解释器而言,bar
函数的参数是一个表 {#1}
。由于在上例里,#1
的值是 "Hello", "world", "!"
,所以 Lua 解释器便认为 bar
函数的参数是 {"Hello", "world", "!"}
。
上述的 TeX 宏向 Lua 函数传递参数的方法蕴含的技艺是移花接木。虽然巧妙,但是 \foo
的调用语句里已经有了 Lua 代码的痕迹。\foo
接受的参数里含有 3 个 Lua 字符串常量,亦即三段文本,然而在 TeX 源文件里,一切皆文本,无需引号。换言之,为了向 Lua 函数传递数据,TeX 源文件不再是纯粹的 TeX 语法了。从后者角度看,\foo
应当像下面这样调用:
\foo{Hello, world, !}
该如何实现这样的宏呢?
首先,将 \foo
重新定义为
\def\foo#1{\ctxlua{bar([[#1]])}}
亦即,将 \foo
所接受的参数以长字串的形式作为 Lua 函数 bar
的参数。
然后重新定义 bar
函数:
function bar(x)
context(x)
end
此时,
\foo{Hello, world, !}
的排版结果变为
该结果表明,bar
函数接到的参数的确是一个字符串。接下来,只需要对该字符串予以解析,将解析结果存为 Lua 表结构。继续重新定义 bar
函数:
function bar(x)
local y = utilities.parsers.settings_to_array(x)
context.startitemize{"n", "broad"}
for _, v in ipairs(y) do
context.item(v)
end
context.stopitemize()
end
utilities.parsers.settings_to_array
是 ConTeXt 开发者实现的 Lua 库里的函数,其作用是以逗号作为分隔符对字符串进行分割,结果存 Lua 表,于是便解决了上面提出的问题。
将上述思路应用于上一篇定义的 \task
宏,便可将其两个参数变为 1 个:
% 待办事项
\definextable[todolist]
\setupxtable[todolist][frame=off]
\startluacode
my = my or {}
local ctx = context
local dim = number.todimen
local textwidth = tex.dimen.textwidth
function my.task(task)
local x = utilities.parsers.settings_to_array(task)
ctx.startxrow()
-- 第一列
ctx.startxcell{width=dim(tex.sp("1.5em"))};
context([[$\circ$]]);
ctx.stopxcell()
-- 第二列
ctx.startxcell{width=dim(0.9 * textwidth)};
context([[%s]], x[1]);
ctx.stopxcell()
-- 第三列
ctx.startxcell{width=dim(0.1 * textwidth),align="{middle,lohi}"};
if x[2] then
context(x[2])
else
context([[\strut]])
end
ctx.stopxcell()
ctx.stopxrow()
end
\stopluacode
\def\task#1{\ctxlua{my.task([[#1]])}}
\task
的用法如下:
\environment card-env
\starttext
\timestamp{2023 年 01 月 31 日}
\startxtable[todolist]
\task{晒太阳, $\checkmark$}
\task{包饺子, $\checkmark$}
\task{拖地板}
\stopxtable
\stoptext
下一篇:学一点 Lua