上一篇:时间管理

将 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_arrayConTeXt 开发者实现的 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

garfileo
6k 声望1.9k 粉丝

这里可能不会再更新了。