用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 ::= " " | " "
newline ::= " " | " "
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 ::= " " | " "
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"	"
local newline = P"
" + "
"
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