上一篇:工夫治理

将 TeX 宏接到的参数传递于 Lua 函数,略含机巧。例如,将 \foo 承受的 Lua 表数据传递给 bar 函数,

\environment card-env\startluacodefunction 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]\startluacodemy = my or {}local ctx = contextlocal dim = number.todimenlocal textwidth = tex.dimen.textwidthfunction 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