《Lua-in-ConTeXt》09:學一點 Lua

garfileo發表於2023-02-08
上一篇:引數列表解析

這份文件的題目是 ConTeXt 裡的 Lua。主角應該是 Lua,ConTeXt 只是定語。但是,這個定語很長。這份文件迄今引入的 ConTeXt 知識尚不及 ConTeXt 全部知識的 1%。Lua 語言比 TeX 語言容易得多。倘若覺得學習 ConTeXt 毫無用處,學點 Lua 總是有益的,至少在這個資訊化時代的某些時候跟別人說,我也懂點計算機程式設計。

變數

在 card-env.tex 檔案裡,有以下 Lua 程式碼片段:

my = my or {}
local ctx = context
local dim = number.todimen
local textwidth = tex.dimen.textwidth
local w1, w2, w3 = tex.sp("1.5em"), nil, nil
w3 = w1; w2 = textwidth - (w1 + w3)

其中,myctxdimtextwidthw1w2, w3,皆為變數。my 是全域性變數,其他皆為區域性變數。上述程式碼的每一行皆為變數賦值語句。凡用 = 賦值的物件即變數。凡用 local 修飾的變數為區域性變數,否則為全域性變數。

定義變數時,可不賦值,例如:

foo
local bar

定義了全域性變數 foo 和區域性變數 bar,因為未賦值於它們,它們的值是 nil

變數的賦值語法支援多變數賦值。例如

local w1, w2, w3 = tex.sp("1.5em"), nil, nil

= 右側的三個值分別賦於 = 左側的三個區域性變數。

在 Lua 語言裡,分號 ; 僅表示一條語句的終結,與換行符等效。例如

w3 = w1; w2 = textwidth - (w1 + w3)

w3 = w1
w2 = textwidth - (w1 + w3)

等效。

變數的型別只有 nil,布林值,數字、字串,函式,表,執行緒和使用者資料(userdata)。若是在 ConTeXt 原始檔裡用 Lua 語言編寫程式,只需要將注意力放在前 6 種型別即可。

在上述示例裡,已經見識了型別為 nil,數字、函式和表的變數:

my = {}    -- 表
local ctx = context   -- 表
local dim = number.todimen    -- 函式
local textwidth = tex.dimen.textwidth    -- 數字

-- 是 Lua 語言的程式碼註釋符號,Lua 直譯器會忽略 -- 及其之後直到行尾的內容。

數字型別的變數,支援所有的小學數學課本里的算數。Lua 的 math 庫裡支援中學數學課本里常用的函式。大學數學裡的函式、微分、積分、級數……可能需要用 Lua 語言自行編寫。

字串型別的變數,在 card-env.tex 裡也大量出現了,只不過它們是以函式的引數的形式出現。下面給出字串變數賦值示例:

local a = "\\starttext ... \\stoptext"
local b = [[\starttext ... \stoptext]]
local c = [=[\starttext ... \stoptext]=]
local d = [==[\starttext ... \stoptext]==]

這 4 個字串變數的值等價。a 的值用的是 Lua 短字串語法,而 bcd 的值用的是 Lua 長字串語法。

在短字串語法裡,一些特殊字元,需要使用 \ 轉義方能將其視為普通字元,例如 \ 本身,又例如換行符 \n——將字元 n 轉義為換行符。

在長字串語法裡,要表示換行,需要換行……例如:

local a = "\\starttext\n ... \n\\stoptext"

要用與上述短字串等價的長字串作為 a 的值,需要寫成

local a = [[\starttext
 ... 
\stoptext]]

函式

在 Lua 語言裡,函式可以作為變數的值,亦即函式並不比變數更特殊。

定義一個函式 f(x) = x,語法是

function (x)
    return x
end

寫成

function (x) return x end

亦可。

倘若像中學數學裡那樣說 y = f(x),只需寫為

y = function (x) return x end

以上是 Lua 語言裡的匿名函式的寫法和用法。通常不需要如此行為藝術,只需將 y 直接定義為函式,例如

function y (x)
    return x
end

要定義多元函式,例如數學課本里的 z = f(x, y) = x + y 函式,只需

function z (y, x)
    return x + y
end

由於 Lua 語言支援多變數賦值,因此函式可以返回多個變數。例如

function f (x, y)
    return x * x, y * y, x + y
end

local a, b, c = f(2, 4)

變數 abc 的值分別為 4,16 和 6。

表,是 Lua 語言的精華。下面的程式碼定義了一個空表:

local my = {}

若令 my 的索引(或下標)為 1,2,3 的元素為三個字串,只需

my[1] = [[\starttext]]
my[2] = " ... ... ... "
my[3] = [[\stoptext]]

倘若預先知道表中各個元素的值,可在定義變數時可直接將元素的值放在表裡,例如:

local my = {[[\starttext]], " ... ... ... ", [[\stoptext]]

表可以是同構的,例如上述元素皆為字串的表,也可以是異構的,例如:

local foo = {1, "two", function (x) return x end, {3, 4, "hello"}}

函式和表也可以作為表的元素。foo[3] 是函式,可以像下面這樣呼叫它:

foo[3](foo[2])

Lua 直譯器對上述語句的求值結果為字串 "two"

foo[4] 是表。foo[4][1] 的值為 3。foo[4][3] 的值為 "hello"

Lua 語言將表的索引定義為從 1 開始,而不是 0。請記住這一點。

表的元素也可以是鍵值對。例如:

local color = {red = 0.001, green = 0.803, blue = 0.222}

有兩種訪問表 color 裡的元素的方法。例如,

color.green

color["gree"]

等價。

對於上述 color 表的定義,可繼續追加鍵值對,例如:

color.alpha = 0.5

結果,color 就有了 4 個元素。

由於 Lua 表有上述特性,因此 Lua 的程式模組也可基於表予以構造。例如,以下程式碼建立了一個叫做 my 的模組:

my = {}
my.foo = "Hello world!"

my.y  = function (x) return x end

function my.test (x, y)
    return x + y
end

return my

假設上述程式碼儲存在 my-module.lua 檔案裡,那麼在與該檔案位於同一目錄下的另一個 Lua 程式原始檔或 ConTeXt 原始檔裡……以後者為例,在 foo.tex 檔案裡載入 mingyi 模組:

% 這是 foo.tex 檔案
\environment card-env
\starttext
\startluacode
local mingyi = require("my-module")
context.title(my.foo)
context("1 + 2 = %d\n", my.test(1, 2))
\stopluacode
\stoptext

執行 context 命令解釋 foo.tex 檔案,可將其編譯為 foo.pdf:

$ context foo

條件

若一段程式需要符合某個條件方能執行,需要使用以下語法:

if 條件成立 then
    一段程式
end

如果條件不成立也要執行一段程式,可使用以下語法

if 條件成立 then
    一段程式
else
    另一段程式
end

例如 card-env.tex 裡的 is_cjk 函式:

function my.is_cjk_char(c)
    if c >= 0x3400 and c <= 0x4db5
        ... ... ...
        or c >= 0x31c0 and c <= 0x31ef then
        return true;
    else
        return false;
    end
end

如果要根據多個條件選擇某段程式予以執行,可使用以下語法

if 條件 1 成立 then
    程式段 1
elseif 條件 2 成立 then
    程式段 2
else if ... then
    ... ... ...
else
    以上條件都不成立時會被執行的程式段
end

迭代

在 ConTeXt 原始檔裡,Lua 程式碼的最大的用武之地有二,數值運算和迭代(亦稱迴圈)。

迭代主要用於做兩類事情。一類是需要對一段程式反覆執行有限次。另一類是,遍歷表裡的每個元素。

第一類事情,本質上是對整數集的遍歷,例如計算 1 + 2 + ... + 10,

local sum = 0
for i = 1, 10 do
    sum = sum + i
end

上述迭代過程,i 的步進值為 1,即每次迭代過程結束後,i 的值增 1。

再例如,計算 1 + 3 + 5 + 7 + 9,可將上述迭代過程的變數 i 的步進值變為 2,即

local sum = 0
for i = 1, 10, 2 do
    sum = sum + i
end

第二類迭代過程應用最為廣泛,無論是遍歷字串,還是遍歷表,皆依賴它。有連個函式,pairsipairs 可在這種迭代過程中使用。

pairs 可用於遍歷由鍵值對構成的表。例如

\environment card-env
\starttext
\startluacode
local color = {red = 0.001, green = 0.122, blue = 1.000}
context.startitemize({"inmargin", "broad"})
for k, v in pairs(color) do
    context.item(string.format([[\color[%s]{%s} = %f]], k, k, v))
end
context.stopitemize()
\stopluacode
\stoptext

由鍵值對構成的表,遍歷它時,鍵值對的順序未必是在表在構造時鍵值對的出現順序。

ipairs 可用於在迭代過程裡遍歷索引表,即元素索引為 1, 2, ... 的表。如果希望表中元素依序遍歷,應該考慮用索引表而非鍵值對錶。ipairs 的用法與 pairs 相似,例如,

\environment card-env
\starttext
\startluacode
local function vector(v, s)
    local n = #v  -- 索引表 v 裡元素的個數
    context("\\[")
    for i, e in ipairs(v) do
        if i == n then
            context(s, e)
        else
            context(s .. ", ", e)
        end
    end
    context("\\]")
end

x = {0.1, 0.2, 0.3}
y = {0.4, 0.6, 0.1}
z = {}
for i, v in ipairs(x) do
    z[i] = x[i] + y[i]
end

local s = "%.1f"
context.startformula()
vector(x, s); context(" + "); vector(y, s); context(" = "); vector(z, s)
context.stopformula()
\stopluacode
\stoptext

如果在遍歷表的過程中,不需要獲得鍵或索引,可使用 _ 變數,避免為它們費心取一個名字,例如,

for _, v in pairs(foo) do
    ... ... ...
end

結語

在 ConTeXt 裡使用 Lua,我樂觀地覺得,我所掌握的這點 Lua 知識應該是夠用的。倘若需要更深入的學習 Lua,自然是閱讀《Programming in Lua》。

下一篇:緩衝區魔法

相關文章