词法扫描
虽然 Lua 的字符串模式匹配是非常强大,但需要更强大的东西。pl.lexer.scan可以提供标记字符串,按标记机分类数字、字符串等。
> lua -lpl
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
> tok = lexer.scan 'alpha = sin(1.5)'
> = tok()
iden alpha
> = tok()
= =
> = tok()
iden sin
> = tok()
( (
> = tok()
number 1.5
> = tok()
) )
> = tok()
(nil)
scanner是一个函数,它会反复被调用并返回标记的类型和值。基本类型有'iden'、 'string'、 'number' 和 'space' ,一切都由基本类型识别。请注意默认情况下扫描程序将跳过任何’space’(空白)标记。
'comment'(注释) 和 'keyword' (关键字)并不是适用于简单的scanner,对所有语言一样,但了解 Lua 的scanner可以。它承认了 Lua 的关键字,并了解短和长注释和字符串。
> for t,v in lexer.lua 'for i=1,n do' do print(t,v) end
keyword for
iden i
= =
number 1
, ,
iden n
keyword do
词法扫描器会很有用,当没有高度结构化的数据。例如,这里是我维护一个内部文件格式的片段:
points
(818344.1,-20389.7,-0.1),(818337.9,-20389.3,-0.1),(818332.5,-20387.8,-0.1)
,(818327.4,–20388,–0.1),(818322,–20387.7,–0.1),(818316.3,–20388.6,–0.1)
,(818309.7,–20389.4,–0.1),(818303.5,–20390.6,–0.1),(818295.8,–20388.3,–0.1)
,(818290.5,–20386.9,–0.1),(818285.2,–20386.1,–0.1),(818279.3,–20383.6,–0.1)
,(818274,–20381.2,–0.1),(818274,–20380.7,–0.1);
这是用pl.lexer提取点 :
-- assume 's' contains the text above...
local lexer = require 'pl.lexer'
local expecting = lexer.expecting
local append = table.insert
local tok = lexer.scan(s)
local points = {}
local t,v = tok() -- should be 'iden','points'
while t ~= ';' do
c = {}
expecting(tok,'(')
c.x = expecting(tok,'number')
expecting(tok,',')
c.y = expecting(tok,'number')
expecting(tok,',')
c.z = expecting(tok,'number')
expecting(tok,')')
t,v = tok() -- either ',' or ';'
append(points,c)
end
expecting函数抓取下一个标记,如果类型不匹配,则会引发一个错误。(pl.lexer ,不同于其他 PL库,如果出了什么差错将引发错误,所以你应该代码中用pcall优雅地捕获错误。)
所有scanner都有第二个可选参数,这是一个表,控制是否您想要排除空白和评论。Lexer.lua的默认值是{space=true,comments=true}。有第三个可选的参数如何处理字符串和数字标记。
高度结构化的数据当然是程序源代码。 ' text-lexer.lua'的一点片段:
require 'pl'
lines = [[
for k,v in pairs(t) do
if type(k) == 'number' then
print(v) -- array-like case
else
print(k,v)
end
end
]]
ls = List()
for tp,val in lexer.lua(lines,{space=true,comments=true}) do
assert(tp ~= 'space' and tp ~= 'comment')
if tp == 'keyword' then ls:append(val) end
end
test.asserteq(ls,List{'for','in','do','if','then','else','end','end'})
这里是一个有用的小工具,用于标识在 lua 模块 (暂时忽略那些局部声明) 中找到的所有常见全局变量:
-- testglobal.lua
require 'pl'
local txt,err = utils.readfile(arg[1])
if not txt then return print(err) end
local globals = List()
for t,v in lexer.lua(txt) do
if t == 'iden' and _G[v] then
globals:append(v)
end
end
pretty.dump(seq.count_map(globals))
与其dump整个列表,我们把它传入seq.count_map转换为键值对,并且关联的值的表示这些值会出现在序列中的次数。典型输出如下所示:
{
type = 2,
pairs = 2,
table = 2,
print = 3,
tostring = 2,
require = 1,
ipairs = 4
}
您可以进一步通过这tablex.keys以获取唯一的符号列表。当编写 '严格'的 Lua 模块时,所有全局符号都必须在文件的顶部定义为local。
lexer.scan的更多详细使用,请看示例目录中testxml.lua。
XML
新的 0.9.7 版本是 支持一些 XML的。这是一个大的话题,和Penlight不提供一个完整的 XML 堆栈,这是更专门库的任务。
解析和好的打印
Lua 中的半标准 XML 分析器是lua-expat。尤其是,它有lxp.lom.parse函数可以解析 XML成 Lua 对象模型 (LOM)。然而,它不提供将此数据转换回为 XML 文本的方法。如果 lua-expat可用xml.parse将使用这个函数,否则为切换回原来 Roberto Ierusalimschy 写的纯 Lua 分析器。
生成的文档对象知道如何以字符串呈现,这对于调试非常有用:
> d = xml.parse "
> = d
> pretty.dump (d)
{
{
"alice",
attr = {
"id",
id = "1"
},
tag = "node"
},
attr = {
},
tag = "nodes"
}
数据的实际形状揭示了 LOM 的结构:
每个元素都具有其名称的tag字段
attr字段包含属性字段,并是数组。
元素的子级在元素数组里,所以d[1]是d的第一个孩子。
可以认为,具有属性作为数组的attr部分不是必不可少的 (你不能依赖 XML 中的属性顺序),但这与标准一致。
lua-expat是Penlight 的另一个软依赖项;一般来说,回退分析器对于简单 XML配置文件是是好足够的。doc.basic_parse不打算成为强大的符合分析器 (它只有六十行),但它可以处理简单没有的注释或 DTD 的指令的文档。它有足够的智力,忽略指令,仅此而已。
你可以得到好的打印效果,通过显式调用xml.tostring并传递给它的初始缩进和每个元素缩进:
> = xml.tostring(d,'',' ')
第四个参数是属性缩进:
> a = xml.parse "
> = xml.tostring(a,'',' ',' ')
type&#61;&#39;hobbit&#39; name&#61;&#39;baggins&#39; age&#61;&#39;50&#39; /> 分析和使用配置文件 现在用XML作配置很常见。处理LOM数据和按你的想要的格式提取数据&#xff0c;非常简单 &#xff1a; require &#39;pl&#39; local config &#61; [[ ]] local d,err &#61; xml.parse(config) local t &#61; {} for item in d:childtags() do t[item.tag] &#61; item[1] end pretty.dump(t) ---> { beta &#61; "10", alpha &#61; "1.3", name &#61; "bozo" } 唯一的地方是&#xff0c;在这里我们必须使用的Doc:childtags方法&#xff0c;它可以跳过任何文本元素。 本文摘录自serviceproviders.xml&#xff0c;它通常被发现位于Debian/Ubuntu Linux 系统/usr/share/mobile-broadband-provider-info/serviceproviders.xml。 d &#61; xml.parse [[ ... .... ]] 得到每个国家的供应商的名称非常简单&#xff1a; local t &#61; {} for country in d:childtags() do local providers &#61; {} t[country.attr.code] &#61; providers for provider in country:childtags() do table.insert(providers,provider:child_with_name(&#39;name&#39;):get_text()) end end pretty.dump(t) --> { za &#61; { "Cell-c", "MTN", "Vodacom", "Virgin Mobile" } .... } 使用&#39;Xmlification&#39; 生成 XML 此功能的灵感来自Oribt的htmlify简化 HTML 生成&#xff0c;只是没有环境函数 &#xff1b;tags函数返回一组给定标记名称的元素构造器。 > nodes, node &#61; xml.tags &#39;nodes, node&#39; > &#61; node &#39;alice&#39; > &#61; nodes { node {id&#61;&#39;1&#39;,&#39;alice&#39;}} Lua 表的灵活性会非常有用&#xff0c;因此可以自然地编码的属性和元素的子级。这些标记的构造函数的参数是单个值 &#xff08;如字符串&#xff09; 或表&#xff0c;这个表的属性是命名的键和孩子们的数组值。 使用模板生成 XML 模板是一个小的XML 文档&#xff0c;其中包含”$”变量。subst方法可以产生包含这些变量值的数组。请。注意指定父标记名称的方式&#xff1a; > templ &#61; xml.parse " > &#61; templ:subst {tag&#61;&#39;nodes&#39;, {id&#61;1,name&#61;&#39;alice&#39;},{id&#61;2,name&#61;&#39;john&#39;}} 替代是和过滤文件相关。有关 XML 的令人讨厌的事情有&#xff0c;一是它是一种文档标记语言&#xff0c;二是它是数据语言。标准的解析器将假定你真的关心所有这些额外的文本元素。请考虑此片段&#xff0c;已经一个五岁孩子改变了&#xff1a; T &#61; [[ boops! ]] 标准解析器会在 local function parse (str) return xml.parse(str,false,true) end 第二个参数是指 字符串&#xff0c;而非文件&#xff0c;第三个参数表示使用 Lua 内置解析器 &#xff08;而不是 LuaExpat &#xff0c;如果可用&#xff09;&#xff0c;默认情况下保持字符串不感兴趣。 如何删除字符串boops!? clone&#xff08;作为一种方法调用时也称为filter&#xff09; 可以复制LOM 的文档。它可以传入一个filter函数&#xff0c;作用到每个找到的字符串。这个函数的强大之处在于接收结构信息 — 父节点&#xff0c;无论是否是一个标记名称&#xff0c;一个文本元素或属性的名称&#xff1a; d &#61; parse (T) c &#61; d:filter(function(s,kind,parent) print(stringx.strip(s),kind,parent and parent.tag or &#39;?&#39;) if kind &#61;&#61; &#39;*TEXT&#39; and #parent > 1 then return nil end return s end) ---> weather *TAG ? boops! *TEXT weather current_conditions *TAG weather condition *TAG current_conditions $condition data condition temp_c *TAG current_conditions $temp data temp_c bo *TAG current_conditions whoops! *TEXT bo 通过丢弃不是单一元素子元素的文本元素&#xff0c;我们可以把拉出来 &#39;boops&#39; 而不是 &#39;whoops&#39; 。 使用模板提取数据 匹配在相反的方向。我们有一份文件&#xff0c;并想使用模式从中提取值。 这样的一个常见用途是分析 API 查询的 XML 结果。谷歌天气 API是一个很好的例子。使用pretty-print打印抓取的结果http://www.google.com/ig/api?weather&#61;Johannesburg,ZA"如下&#xff1a; mobile_row &#61; &#39;0&#39; > …. ml_api_reply> 假设上述 XML 已从google被读取。这个想法写一个模板&#xff0c;并使用它来提取一些感兴趣的值&#xff1a; t &#61; [[ ]] local res, ret &#61; google:match(t) pretty.dump(res) 的输出是&#xff1a; { condition &#61; "Clear", temp &#61; "24" } match方法可以传入一个LOM文档或一些文本&#xff0c;可以进行文本分析。 但是如果我们需要从重复元素中提取值呢&#xff1f;匹配模板可能包含 &#39;数组匹配&#39; &#xff0c;用&#39;{{}.}&#39; 括住&#xff1a; {{ }} 匹配结果是&#xff1a; { { low &#61; "60", high &#61; "89", day &#61; "Sat", condition &#61; "Clear", }, { low &#61; "53", high &#61; "86", day &#61; "Sun", condition &#61; "Clear", }, { low &#61; "57", high &#61; "87", day &#61; "Mon", condition &#61; "Clear", }, { low &#61; "60", high &#61; "84", day &#61; "Tue", condition &#61; "Clear", } } 这一系列的表&#xff0c;您可以使用tablex或List重新塑造成所需的格式。和读取 Unix 密码文件与配置类似&#xff0c;您可以进行数组到的天气图到使用条件&#xff1a; tablex.pairmap (&#39;|k,v| v,v.day&#39;,conditions) &#xff08;在这里使用替代字符串 lambda&#xff09; 但是&#xff0c;xml 匹配可以塑造输出结构。通过替换模板的day_of_week行 请注意$NUMBER是指一个数值索引&#xff0c;这样&#xff0c; $1是生成的数组&#xff0c;等等的第一个元素。您可以混合使用编号和命名捕获&#xff0c;但它已强烈建议使编号的捕获形成适当的数组序列 &#xff08;从1到n包容一切)。$0有特殊的含义 &#xff1b;如果它是唯一的捕获 ({[0]&#61;‘foo’}&#xff09; 表可以折叠为 foo。 {{ }} 现在的结果是&#xff1a; { Tue &#61; { "60", "84", "Clear" }, Sun &#61; { "53", "86", "Clear" }, Sat &#61; { "60", "89", "Clear" }, Mon &#61; { "57", "87", "Clear" } } 将匹配应用到此配置文件会带来另一个问题&#xff0c;因为实际标记匹配本身有意义。 标记 &#39;通配符&#39; 的元素名称用连字符结束。 {{ 你就会找到{{alpha&#61;‘1.3’},…}。将由这返回的最方便的格式 &#xff08;请注意&#xff0c; –的行为就像$): {{<_->$0}} 这会返回{alpha&#61;‘1.3’,beta&#61;‘10’,name&#61;‘bozo’}. 我们可以无止境地&#xff0c;玩这种游戏和编码方式的转换的捕获。但该模式足够复杂&#xff0c;不过很容易地进行转换。 local numbers &#61; {alpha&#61;true,beta&#61;true} for k,v in pairs(res) do if numbers[v] then res[k] &#61; tonumber(v) end end HTML 分析 HTML 是一种异常地退化形式的 XML&#xff0c;Dennis Schridde 贡献的一个功能&#xff0c;可以更轻松解析它。例如&#xff1a; doc &#61; xml.parsehtml [[ Hello dolly HTML is slack ]] asserteq(xml.tostring(doc),[[ Hello dolly HTML is slack 也就是说&#xff0c;所有的标记都转换为小写字母&#xff0c;空的 HTML 元素如br已正确关闭 &#xff1b;不需要被引用属性。 此外&#xff0c;DOCTYPE 指令和注释被跳过。对于真正格式不好的 HTML&#xff0c;这不是你的工具 &#xff01;
]])