《Lua in ConTeXt》12:参数表解析

将 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", "!"}

bar 函数可生成以下排版结果:

之所以是这个结果,是因为 bar 函数在 ConTeXt 源文件里输出了以下代码的缘故:

\startitemize[n,broad]
\item Hello
\item world
\item !
\stopitemize

上述的 TeX 宏向 Lua 函数传递参数的方法蕴含的技艺是移花接木。虽然巧妙,但是 \foo 的调用语句里已经有了 Lua 代码的痕迹。\foo 接受的参数里含有 3 个 Lua 字符串常量,亦即三段文本,然而在 TeX 源文件里,一切皆文本,无需引号。换言之,为了向 Lua 函数传递数据,TeX 源文件不再是纯粹的 TeX 语法了。

倘若想在 TeX 源文件里消除宏参数里的引号,同时又能够向 Lua 函数传递正确的参数,有一个直拙的办法。这个办法,人类用了几千年了。例如,我将我所知道的有关 ConTeXt 的一切传授给一个人,那么我能想到的最好的办法是,写一份文档送给他。他通过阅读这份文档,从而在他的思想里构造出对 ConTeXt 的认识。

现在,重新定义 \foo

\def\foo#1{\ctxlua{bar("#1")}}

再重新定义 bar

function bar(x)
    context(x)
end

然后,像下面这样调用 \foo

\foo{x1::x2::x3, y1::y2::y3, z1::z2}

bar 函数能输出以下结果:

我传给 bar 函数的参数虽然是字符串 "x1::x2::x3, y1::y2::y3, z1::z2",但是我希望 bar 函数能够想办法理解,我传给它的是一个表,只是迫于维护 TeX 世界的清净无为,我用字符串表示了这个表。

ConTeXt 说,可用 utilities.parsers.settings_to_array……

试试看:

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

现在,bar 函数能够给出的输出如下:

接下来,我希望 bar 函数能够进一步理解,x1::x2::x3 这样的字符串也是表,即 {"x1", "x2", "x3"}

ConTeXt 觉得,此事也不难,用他实现的 string.split 函数就能解决。例如

local a = "x1::y1::z1"
local b = string.split(a, "::")

b 的值便是 {"x1", "y1", "z1"}。好的……于是我将 bar 函数重新定义为

function bar(x)
    local y = utilities.parsers.settings_to_array(x)
    context.startitemize{"n", "broad"}
    for _, v in ipairs(y) do
        local z = string.split(v, lpeg.P("::"))
        context.item(v)
        context.startitemize{"n", "broad"}
        for _, w in ipairs(z) do
            context.item(w)
        end
        context.stopitemize()
    end
    context.stopitemize()
end

对于宏调用

\foo{x1::x2::x3, y1::y2::y3, z1::z2}

现在的 bar 函数给出的输出为

基于上述参数解析原理,我想定义的 \keywords 宏就有着落了。

知道了原理的副作用是,懒得再去实践了……

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章