侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

深入探究Lua中的解析表达式

技术  /  管理员 发布于 5年前   266

 使用一个模式

这个例子显示了一个建立和使用模式的程序,它非常简单但很完整:

复制代码 代码如下:local lpeg = require "lpeg"

-- matches a word followed by end-of-string
p = lpeg.R"az"^1 * -1

print(p:match("hello"))        --> 6
print(lpeg.match(p, "hello"))  --> 6
print(p:match("1 hello"))      --> nil

模式是简单的一个或多个小写字符并在尾端以(-1)结束的序列。该程序调用match来当作一个方法和函数。在以上成功案例,匹配函数返回成功 匹配的第一个字符的索引,为其字符串长度加1。

复制代码 代码如下:Name-value lists

这个例子解析一个名称 - 值配对的列表,并返回那些配对的表:

复制代码 代码如下:lpeg.locale(lpeg)   -- adds locale entries into 'lpeg' table

local space = lpeg.space^0
local name = lpeg.C(lpeg.alpha^1) * space
local sep = lpeg.S(",;") * space
local pair = lpeg.Cg(name * "=" * space * name) * sep^-1
local list = lpeg.Cf(lpeg.Ct("") * pair^0, rawset)
t = list:match("a=b, c = hi; next = pi")  --> { a = "b", c = "hi", next = "pi" }

每一配对都有  formatname =namefollowed 的一个可选的分离器(用逗号或分号)。  配对模式(Thepairpattern)在一个组模式里形成闭包,那么那些名称就可以成为单个捕获的值。 列表模式 (Thelistpattern)然后折叠这些捕获。 它以空列表开始,通过创建列表捕获匹配一个空字符串,然后为每个捕获(一名称对)appliesrawsetover累加器(表)和捕捉值(对名称)。rawsetreturns((未初始化的集合)返回表本身,所以累加器总是表中执行。


以下代码创建了一个模式,该模式使用给定的分隔模式sep作为分隔器来来拆分字符串:

复制代码 代码如下:function split (s, sep)
  sep = lpeg.P(sep)
  local elem = lpeg.C((1 - sep)^0)
  local p = elem * (sep * elem)^0
  return lpeg.match(p, s)
end

首先,该函数确保sep一个合适的模式。只要没有匹配分隔器,该模式的elem 是重复的零个或多个任意字符。它还捕捉其匹配值。模式p匹配由sep拆分的一组元素.

如果拆分产生的结果值太多,可能会溢出由一个Lua函数返回的最大数目的值。在这种情况下,我们可以将这些值放到一个表中:

复制代码 代码如下:function split (s, sep)
  sep = lpeg.P(sep)
  local elem = lpeg.C((1 - sep)^0)
  local p = lpeg.Ct(elem * (sep * elem)^0)   -- make a table capture
  return lpeg.match(p, s)
end

模式搜索

基本的匹配仅仅工作在锚定模式下。如果我们打算查找匹配字符串中任何地方的模式,那么我们必须写一个匹配任何地方的模式。

因为模式是可以编写的,所以我们可以编写一个函数,它给定一个任意的模式p,返回一个搜索p的新模式,以匹配字符串的任何位置。执行这种搜索有几种方法。一种方法如下:

复制代码 代码如下:function anywhere (p)
  return lpeg.P{ p + 1 * lpeg.V(1) }
end

这个语法的直接解读:匹配p或者跳过一个字符,然后试图再次匹配。

如果我们想知道这个模式在字符串的所有匹配位置(而不仅仅知道它在字符串的某个位置),那么我们可以给这个模式添加位置捕捉:

复制代码 代码如下:local I = lpeg.Cp()
function anywhere (p)
  return lpeg.P{ I * p * I + 1 * lpeg.V(1) }
end

print(anywhere("world"):match("hello world!"))   -> 7   12

这种搜索的另一个方法如下:

复制代码 代码如下:local I = lpeg.Cp()
function anywhere (p)
  return (1 - lpeg.P(p))^0 * I * p * I
end

再次说明,这个模式的直接解读:当不匹配p时,它跳过尽可能多的字符,然后对p进行匹配(外加正确的位置捕捉)。

如果我们打算查找仅仅匹配单词边界的模式的话,那么我们可以使用以下转换:

复制代码 代码如下:local t = lpeg.locale()

function atwordboundary (p)
  return lpeg.P{
    [1] = p + t.alpha^0 * (1 - t.alpha)^1 * lpeg.V(1)
  }
end

平衡的括号

以下模式只匹配带有平衡括号的字符串::

复制代码 代码如下:b = lpeg.P{ "(" * ((1 - lpeg.S"()") + lpeg.V(1))^0 * ")" }

阅读第一个(也是唯一的)所给语法规则,所谓平衡字符串,就是一个开括号,后跟零个或多个非括号字符或者平衡字符串(LPFG.V(1)),最后跟着与开括号能够闭合的结束括号。
全局替换

下面的例子和tostring.gsub所做工作类似。它接收一个母串和一个模式以及一个替换值,然后替代所传入的母串中所有与指定模式匹配的子串为指定的替换值::

复制代码 代码如下:function gsub (s, patt, repl)
  patt = lpeg.P(patt)
  patt = lpeg.Cs((patt / repl + 1)^0)
  return lpeg.match(patt, s)
end

作为instring.gsub,替换值可以是一个字串、函数,或者一个表.

逗号分隔值(CSV)

下面的例子将字符串转换成逗号分隔的值,并返回所有的字段:

复制代码 代码如下:local field = '"' * lpeg.Cs(((lpeg.P(1) - '"') + lpeg.P'""' / '"')^0) * '"' +
                    lpeg.C((1 - lpeg.S',\n"')^0)

local record = field * (',' * field)^0 * (lpeg.P'\n' + -1)

function csv (s)
  return lpeg.match(record, s)
end

一个字段或是一个引用的字段(一族可能包含任何字符除单引号,或双引号)或是一个未被引用的字段(不包含逗号,换行符或引号)。一个记录就是一个用逗号分隔的字段列表(以换行符或以字符串结尾)。

就像这样,前面的匹配返回的每个字段都是独立返回的。若我们添加一个列表截取定义的记录。返回的将不再是一个独立的包含所有字段的列表。

复制代码 代码如下:local record = lpeg.Ct(field * (',' * field)^0) * (lpeg.P'\n' + -1)


UTF-8 和 Latin 1

使用LPeg来将一字符串从UTF-8编码转换成Latin 1(ISO 88590-1),这并不困难:

复制代码 代码如下:-- convert a two-byte UTF-8 sequence to a Latin 1 character
local function f2 (s)
  local c1, c2 = string.byte(s, 1, 2)
  return string.char(c1 * 64 + c2 - 12416)
end

local utf8 = lpeg.R("\0\127")
           + lpeg.R("\194\195") * lpeg.R("\128\191") / f2

local decode_pattern = lpeg.Cs(utf8^0) * -1

这些代码中,UTF-8定义已经为Latin 1的编码范围(从0到255)。所有不在该范围内的编码(以及任何无效的编码)将不匹配该模式。

正如decode_pattern所要求的,这个模式匹配所有的输入(因为-1在它的结尾处),任何无效字符串会匹配失败,而无任何关于此问题的有用信息。我们可以通过重定义如下decode_pattern来改善这种情况:

复制代码 代码如下:local function er (_, i) error("invalid encoding at position " .. i) end

local decode_pattern = lpeg.Cs(utf8^0) * (-1 + lpeg.P(er))

现在,如果模式utf8^0  在字符串尾部之前停止,会调用一个适用的出错函数。

UTF-8 和 Unicode

我们可以扩展以前的模式来处理所有的Unicdoe代码片段,当然, 我们不能把它们翻译阿拉伯数字1或其他任何一个字节编码。相反,我们的翻译序列结果中的数字表示的代码片段。这里完整的代码:
 
复制代码 代码如下:-- decode a two-byte UTF-8 sequence
local function f2 (s)
  local c1, c2 = string.byte(s, 1, 2)
  return c1 * 64 + c2 - 12416
end
 
-- decode a three-byte UTF-8 sequence
local function f3 (s)
  local c1, c2, c3 = string.byte(s, 1, 3)
  return (c1 * 64 + c2) * 64 + c3 - 925824
end
 
-- decode a four-byte UTF-8 sequence
local function f4 (s)
  local c1, c2, c3, c4 = string.byte(s, 1, 4)
  return ((c1 * 64 + c2) * 64 + c3) * 64 + c4 - 63447168
end
 
local cont = lpeg.R("\128\191")   -- continuation byte
 
local utf8 = lpeg.R("\0\127") / string.byte
           + lpeg.R("\194\223") * cont / f2

 
-- decode a two-byte UTF-8 sequence
local function f2 (s)
  local c1, c2 = string.byte(s, 1, 2)
  return c1 * 64 + c2 - 12416
end
 
-- decode a three-byte UTF-8 sequence
local function f3 (s)
  local c1, c2, c3 = string.byte(s, 1, 3)
  return (c1 * 64 + c2) * 64 + c3 - 925824
end
 
-- decode a four-byte UTF-8 sequence
local function f4 (s)
  local c1, c2, c3, c4 = string.byte(s, 1, 4)
  return ((c1 * 64 + c2) * 64 + c3) * 64 + c4 - 63447168
end
 
local cont = lpeg.R("\128\191")   -- continuation byte
 
local utf8 = lpeg.R("\0\127") / string.byte
           + lpeg.R("\194\223") * cont / f2

Lua的长字符串

Lua中的长字符串由模式 [= *[ 开始,到第一次出现的带有完全相同数量的等号的 ] =*] 结束。如果开括号后跟一个换行符,换行符将被丢弃(即,不会把它当作字符串的一部分)。

在Lua中想要匹配一个长字符串,模式必须捕捉第一次重复的等号,然后,只需找到有关闭字符串的候选项,检查其是否具有相同数量的等号。

复制代码 代码如下:equals = lpeg.P"="^0
open = "[" * lpeg.Cg(equals, "init") * "[" * lpeg.P"\n"^-1
close = "]" * lpeg.C(equals) * "]"
closeeq = lpeg.Cmt(close * lpeg.Cb("init"), function (s, i, a, b) return a == b end)
string = open * lpeg.C((lpeg.P(1) - closeeq)^0) * close / 1

open 模式匹配 [=*[,在一个名为 init 的组中捕获重复的等号;它也会丢弃一个可选的换行符(如果它存在的话)。close 模式匹配 ]= *],也是捕捉重复的等号。closeeq模式首先匹配 close,然后它采用逆向捕捉来恢复先前由 open 捕捉并命名为 init 的内容,最后,用 match-time 捕捉来检查两个捕获是否相同。字符串模式从 open 开始之后,它会一直包含到匹配了 closeeq 为止,然后匹配最终的 close。最后的数字捕获简单地丢弃由 close 产生的捕获。

算术表达式

本例对简单的算术表达式进行完整的解析和求值。而且我们用两种风格来书写。

第一种途径首先建立一个语法树,然后遍历这棵树来计算表达式的值:

复制代码 代码如下:-- 词典元素
[code]local Space = lpeg.S(" \n\t")^0
local Number = lpeg.C(lpeg.P"-"^-1 * lpeg.R("09")^1) * Space
local TermOp = lpeg.C(lpeg.S("+-")) * Space
local FactorOp = lpeg.C(lpeg.S("*/")) * Space
local Open = "(" * Space
local Close = ")" * Space

-- 语法
local Exp, Term, Factor = lpeg.V"Exp", lpeg.V"Term", lpeg.V"Factor"
G = lpeg.P{ Exp,
  Exp = lpeg.Ct(Term * (TermOp * Term)^0);
  Term = lpeg.Ct(Factor * (FactorOp * Factor)^0);
  Factor = Number + Open * Exp * Close;
}

G = Space * G * -1

-- 求值器
function eval (x)
  if type(x) == "string" then
    return tonumber(x)
  else
    local op1 = eval(x[1])
    for i = 2, #x, 2 do
      local op = x[i]
      local op2 = eval(x[i + 1])
      if (op == "+") then op1 = op1 + op2
      elseif (op == "-") then op1 = op1 - op2
      elseif (op == "*") then op1 = op1 * op2
      elseif (op == "/") then op1 = op1 / op2
      end
    end
    return op1
  end
end

-- 解析/求值
function evalExp (s)
  local t = lpeg.match(G, s)
  if not t then error("syntax error", 2) end
  return eval(t)
end

-- 使用例子
print(evalExp"3 + 5*9 / (1+1) - 12")   --> 13.5

第二种风格不用建立语法树,直接求值。下面代码就按此种途径(假定和上面有相同的词典元素):

复制代码 代码如下:-- 辅助函数
function eval (v1, op, v2)
  if (op == "+") then return v1 + v2
  elseif (op == "-") then return v1 - v2
  elseif (op == "*") then return v1 * v2
  elseif (op == "/") then return v1 / v2
  end
end

-- 语法
local V = lpeg.V
G = lpeg.P{ "Exp",
  Exp = lpeg.Cf(V"Term" * lpeg.Cg(TermOp * V"Term")^0, eval);
  Term = lpeg.Cf(V"Factor" * lpeg.Cg(FactorOp * V"Factor")^0, eval);
  Factor = Number / tonumber + Open * V"Exp" * Close;
}

-- 使用例子
print(lpeg.match(G, "3 + 5*9 / (1+1) - 12"))   --> 13.5

注意 fold (收集器) 捕获的用法。 要计算一个表达式的值,收集器从第一个术语的值开始,为每个副本应用进化收集器,操作符,和新术语。


  • 上一条:
    怎么使 Mysql 数据同步
    下一条:
    用IE远程创建Mysql数据库的简易程序
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 2024.07.09日OpenAI将终止对中国等国家和地区API服务(0个评论)
    • 近期文章
    • 智能合约Solidity学习CryptoZombie第三课:组建僵尸军队(高级Solidity理论)(0个评论)
    • 智能合约Solidity学习CryptoZombie第二课:让你的僵尸猎食(0个评论)
    • 智能合约Solidity学习CryptoZombie第一课:生成一只你的僵尸(0个评论)
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-10
    • 2016-11
    • 2017-07
    • 2017-08
    • 2017-09
    • 2018-01
    • 2018-07
    • 2018-08
    • 2018-09
    • 2018-12
    • 2019-01
    • 2019-02
    • 2019-03
    • 2019-04
    • 2019-05
    • 2019-06
    • 2019-07
    • 2019-08
    • 2019-09
    • 2019-10
    • 2019-11
    • 2019-12
    • 2020-01
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2020-10
    • 2020-11
    • 2021-04
    • 2021-05
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-12
    • 2022-01
    • 2022-02
    • 2022-03
    • 2022-04
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-06
    • 2023-07
    • 2023-08
    • 2023-09
    • 2023-10
    • 2023-12
    • 2024-02
    • 2024-04
    • 2024-05
    • 2024-06
    • 2025-02
    • 2025-07
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客