在开发前端BFF框架的时候,需要将团队后台使用的JCE协议(类似协议)转换成对应的语法,这里参考@/-cli的实现,使用PEG.js解析生成AST,下面就来介绍一下PEG.js是如何进行解析的?

我们在对文本进行解析的时候,通常可以使用正则表达式从目标文本中提取所需信息。但是仅使用正则表达式来解析,会发现非常难以阅读,可维护性比较差,而PegJs 则是一种更加简便可维护的 工具。

PEG.js是一个的词法解析器,可以用来处理复杂的数据或计算机语言,并轻松构建转换器、解释器、编译器和其他工具。它的语法对前端工程师很友好,只需要掌握基本的正则语法即可,并提供在线体验网址。下面是基于PegJS语法的一个官方示例,它的语法有这样两个特点:

// additive.pegjs
start
= additive

additive
= left:multiplicative "+" right:additive { return left + right; }
/
multiplicative

multiplicative
= left:primary "*" right:multiplicative { return left * right; }
/
primary

primary
= integer
/
"(" additive:additive ")" { return additive; }

integer "integer"
= digits:[0-9]+ { return parseInt(digits.join(""), 10); }

上面的语法定义了加法和乘法的混合运算规则,可以将文本中符合规则的字符,比如 (2+7)*8,进行解析和运算。我们首先从简单的表达式开始分析:

interger = [0-9] // 只匹配一个数字
// "1" -> "1"
// "12" -> Line 1, column 2: Expected end of input but "2" found.

integer = [0-9]+ // 至少匹配一个数字
// "12" -> ["1", "2"]
// "" -> Line 1, column 1: Expected [0-9] but end of input found.

integer = [0-9]* // 匹配0个或者多个数字
// "124" -> ["1", "2", "4"]
// "" -> []

符号+表示至少匹配1个,符号*表示匹配0个或者多个。默认情况下,使用了+和* 匹配出的结果会返回一个数组,PegJS 提供在表达式中通过变量名和一个函数来自定义返回值。

integer = digits:[0-9] { return digits.join() }
// "124" -> "124"

我们再来看上面的””规则,显然,这条规则匹配多个数字并返回类型的返回值。

integer "integer"
= digits:[0-9]+ { return parseInt(digits.join(""), 10); }
// "124" -> 124

这里来做一个小练习,匹配一个浮点数:

float = number:interger "." decimal:interger {return parseFloat(number + '.' + decimal);}
interger = digits:[0-9]+ { return digits.join(''); }
// "124.35" -> 124.35

再看一下符号/,它表示or 的含义。

number = float / integer

为避免歧义,如果定义规则start = a / b,当输入即可以匹配a也可以匹配b,那么PegJS则优先使用a来进行解析。

下面介绍一个重要的概念:递归,这在描述嵌套或者树状结构的时候非常有用。先来看一个简单的例子:

commaSeparatedIntegerList
= integer ',' commaSeparatedIntegerList
/ integer
integer = [0-9]

当解析输入”1,2″的时候,首先匹配了”1,”,接下来”2″去递归匹配rList规则,发现符合表达式,最终的返回值是:

[
"1",
",",
"2"
]

这里对匹配到的结果返回的value做一个说明:

最后我们再来看一下下面两条规则decimal.js,它们的功能完全相同,第一条规则中定义了一个规则名称”数字”,我们可以通过这种方式为规则起一个可读性高的名称:

interger "数字" 
= [0-9]

interger = [0-9]

我们还可以在规则定义的最开始用”{“和”}”在大括号内部定义一些代码:

{
function makeInteger(digits) {
return parseInt(digits.join(""), 10)
}
}
interger = digits:[0-9]+ { return makeInteger(digits); }

以上就是 PegJS 语法的基本使用方法,我们可以用它来定义各种复杂的解析规则。

现在我们开始尝试去解析一个简单的JCE文件吧,它主要由两部分组成:和。定义了数据类型, 中则声明了服务的方法。

module MTT {
struct HelloReq {
0 require int id;
};

struct HelloRsp {
0 require int iCode;
1 require string sMessage;
};

interface Hello {
int hello (HelloReq req, out HelloRsp rsp);
};
};

首先定义的规则,它里面包含多个成员,每个成员由序号、关键字、类型、变量名和分号组成:

StructDefinition "struct"
= "struct" _+ id: Identifier _* "{" _* members: MemberDeclaration+ _* "}" _* ";" _* {
return {
id,
type: "struct",
members
}
}

MemberDeclaration "member"
= i: IntegerLiteral _+
key: ("require" / "optional") _+
type: TypeSpecifier _+
id: Identifier _* ";" _* {
return {
index: i,
isRequired: key === "required",
id,
type,
id
};
}

IntegerLiteral
= digits: [0] { return parseInt(digits); }
/ head: [1-9] tail:[0-9]* { return parseInt([head, ...tail].join('')); }

Identifier
= head: [_a-zA-Z] tail: [_a-zA-Z0-9]* {
return [head, ...tail].join('
');
}

// 这里TypeSpecifier的定义仅为示例,没有罗列出所有的类型定义
TypeSpecifier
= "void" / "bool" / "string" / "int" / "short" / type: "unsigned" blank "int" { return type.join("") }

blank
= [ ]+ {
return "";
}

_ "whitespace"
= ([ tnr]) {
return "";
}

通过上面的规则,我们就可以得到结构化的了。

Decimaljs保留小数_decimal.js_Decimaljs属性

.png

接下来用类似的思路来定义的规则:

InterfaceDefinition "interface"
= "interface" _+ id: Identifier _* "{" _* methods: MethodDeclaration+ _* "}" _* ";" _* {
return {
id,
type: "interface",
methods
}
}

MethodDeclaration "method"
= returnType: TypeSpecifier _+ id: Identifier _* "(" _* params: ParameterDefinition _* ")" _* ";" _* {
return {
id,
type: "method",
returnType,
params
}
}

ParameterDefinition
= first: SingleParameterDefinition _* "," _* left: ParameterDefinition {
return [first, ...left]
} /
param: SingleParameterDefinition { return [param]; }


SingleParameterDefinition
= "out" _+ type: (Identifier / TypeSpecifier) _+ id: Identifier {
return {
id,
io: "out",
type
}
}
/
_* type: (Identifier / TypeSpecifier) _+ id: Identifier {
return {
id,
io: "",
type
}
}

那么就得到了解析后的接口方法定义:

decimal.js_Decimaljs属性_Decimaljs保留小数

.png

最后,将规则和规则进行整合后,就可以得到一个简单的Pegjs语法的JCE解析器了。

jce
= module: ModuleDefinition { return module; }

ModuleDefinition
= _* "module" _+ id: Identifier _* "{" _* value: ValueDefinition+ _* "}" _* ";" _* {
return {
type: "module",
id,
value,
}
}

ValueDefinition = StructDefinition / InterfaceDefinition

最终整个JCE文件的解析结果如下:

decimal.js_Decimaljs属性_Decimaljs保留小数

jce.png

参考文献:

Intro to Peg.js

#–and-

会员全站资源免费获取,点击查看会员权益

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注