《Lua-in-ConTeXt》08:引數列表解析

garfileo發表於2023-02-08
上一篇:時間管理

將 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

相關文章