用lpeg解析文本语法

所谓语法解析,就是将文本中符合既定规则的子串提取出来。欲解析,先要找出文本的既定规则;欲写出代码,先要将规则从抽象域转为自然语言的形式域,就像人们把数学概念用符号固化下来。这种符号,先辈们早已给出了方案,最常用的是BNF。lpeg便是自然地契合BNF的,这也是它与正则表达式等模式匹配库最大的不同,也是它最大的优势,它把每个模式对象作为lua的第一类对象,也就是可以存储于变量中,模式对象间可以相互运算,用BNF来看,每个模式对象便是非终结符,其定义便是终结符。如匹配一个email:

  • 用正则表达式可以为:
^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$
  • 用lpeg可以为:
local lpeg = require "lpeg"
local R = lpeg.R
local C = lpeg.C
local alpha = R"az" + R"AZ" +  R"_"
local num = R"09"
local world = alpha + num
local email = world^1 * "@" * world^1 * "." * world^1

lpeg看起来不更清晰,更接近自然语言些吗?

下面用一个复杂些的例子来说明lpeg的运用,来解析C语言的结构体定义,然后将提取的类型定义保存在一张表中。这张表为一个数组,每个元素的结构如下:

--[[
struct student{
    char *name;
    int age;
};

--]]
{
    name = student
    fields = {
        [1] = {"char","*","name"},
        [2] = {"int","age"}
    }
}

我们用的文本如下:

//学生
struct student{
    char *name;
    int age;
};

//账号
struct account{
    char *user;
    char pwd[32];//md5
};

第一步,把文本的规则形式化,用EBNF描述出来。
struct ::= banks name "{" banks {field} banks"};" banks
space ::= " " | "\t"
newline ::= "\r\n" | "\n"
name ::= alpha | "" {world}
world = alpha | num | "
"
alpha ::= lower | upper
lower ::= "a"|"b"|"c"|"d"|"e"|"f"|"g"|"h"|"i"|"j"|"k"|"l"|"m"|"n"|"o"|"p"|"q"|"r"|"s"|"t"|"u"|"v"|"w"|"x"|"y"|"z"
num ::= "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"
upper ::= 同lower的大写字母形式
bank ::= newline | comment | space
banks ::= {bank}
comment ::= "//" {.}
field ::= type space | {space} ptr {space} name [array] ; banks"
space ::= " " | "\t"
type ::= name
ptr ::= {"*"}
array ::= "[" {num} "]"

第二步,把EBNF转为lpeg模式

local lpeg = require "lpeg"
local P = lpeg.P
local S = lpeg.S
local R = lpeg.R
local C = lpeg.C
local Ct = lpeg.Ct
local Cg = lpeg.Cg
local Cc = lpeg.Cc
local V = lpeg.V
local Carg = lpeg.Carg
local Cmt = lpeg.Cmt

local alpha = R"az" + R"AZ" + "_"
local num = R"09"
local space = P" " + P"\t"
local newline = P"\r\n" + "\n"
local comment = P"//" * (1 - newline)^0   
local bank = space + newline + comment
local banks = bank^0
local world = alpha + num + "_"
local name = C(alpha * world^1)
local dtype = name 
local array = C(P"[" * num^0 * "]")
local ptr = C(P"*")

local function multipat(...)
    local pat = P" "^0
    local pp = {...}
    for _,v in ipairs(pp) do
        pat = pat * v
    end
    return Ct(pat)
end

local field = multipat(banks * dtype * space^0 * ptr^0 * space^0 * name * array^0 * ";" * banks)
local struct = multipat(banks * "struct" * space^0 * Cg(name,"name") * "{" * banks * Cg(Ct(field^0),"fields") * banks * "};" * banks)
local typedef = Ct(struct^0)

--解析
local r = typedef:match(text)

用lpeg,要以BNF为指导,然后根据保存形式选择合适的捕获。它虽然很容易提取符合语法的子串,但无法检测子串的合法性,如字段的数据类型是否有效,其合法性就需要另外检测了。

转载于:https://www.cnblogs.com/watercoldyi/p/6799958.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值