上一篇:参数列表解析
这份文档的题目是 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)
其中,my
,ctx
,dim
,textwidth
,w1
,w2
, 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 短字符串语法,而 b
,c
和 d
的值用的是 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)
变量 a
,b
和 c
的值分别为 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
第二类迭代过程应用最为广泛,无论是遍历字符串,还是遍历表,皆依赖它。有连个函数,pairs
和 ipairs
可在这种迭代过程中使用。
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》。
下一篇:缓冲区魔法
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。