上一篇:時間管理
將 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