Parboiled概述
Parboiled是一个混合的Java/ Scala库,提供了基于解析表达文法(PEGs)的轻量级、易用、功能强大的任意输入文本解析。
图形数据库Neo4j使用Parboiled解析查询语言Cypher。
特点
- 用户可以以某种方式指定解析语法,并使它快速,轻松地工作。
- 解析表达式语法的强大表达能力
- 支持强大而灵活的解析器操作
- 出色的解析错误报告和恢复
- 良好的性能
- 易于集成
- 轻量级,易于使用
Parboiled提供了递归下降PEG解析器的实现,该实现可对用户指定的PEG规则进行操作。
解析表达文法(PEG)
以纯公式的形式展现递归下降解析器的基础语法,对这个具体的解析器采用的实现方法没有限定。
与上下文无关文法(CFG)很像,但存在区别:
- PEG不存在二义性,只产生一个确定的语法分析树。
- PEG的选择操作符是有序的。如果第一个可能成功了,那么第二个可能就忽略。
文法的组成部分:
- 一个有限的非终结符的集合
- 一个有限的终结符的集合
,和
没有交集
- 一个有限的解析规则的集合
- 一个被称为起点表达式的解析表达式
中每一个解析规则以
的形式出现,这里
是一个非终结符,
是一个解析表达式。解析表达式是类似正则表达式的层次表达式。
原子解析表达式
- 任何的非终结符
- 任何的终结符
- 空字符串
解析表达式操作符
优点
- PEG更加严格更加强大,可以很好地成为正则表达式的替代品。
- PEG不存在二义性。
缺点
- PEG不能表达左递归的解析规则。
- 未能被广泛应用。
使用Parboiled的两个阶段
-
规则构建
以Parboiled的方式构建解析器规则的树 / 有向图。该阶段与实际的输入无关,且构建的规则树可重用。
从BaseParser派生一个自定义的类,并定义返回Rule实例的方法。这些方法从其他规则、终端、预定义原语和动作表达式构建规则实例。对于Java,Parboiled采用了一种“解析器扩展”的过程来更加简洁地构造代码。
-
规则执行
为了使解析器不仅仅是一个“识别器”(确定给定的输入是否符合语法定义的程序)。解析器需要包含解析器操作,即在规则执行期间的特定执行点的自定义代码片段。除了检查解析器状态以外,解析器操作通常还会构造解析器“值”,并且可以作为语义谓词影响解析过程。
规则针对特定的输入文本运行。该阶段的最终结果将得到以下信息:
- 确定输入是否匹配根规则的布尔标志
- 可能遇到的错误信息列表
- 由解析器操作构造一个或多个值的对象
值栈(The Value Stack)
在规则执行阶段,解析器操作可以利用值栈来组织AST(抽象语法树)节点等自定义对象的构造。值栈通常用作自定义对象的临时存储。
解析树(The Parse Tree)
在规则执行阶段,Parboiled可以选择构造一个解析树,其节点与识别的规则相对应。每个解析树的节点都包含一个对它的构造规则的匹配器的引用、匹配的输入文本位置和位于值栈顶部的当前元素。解析树可用来查看哪些规则已匹配给定输入,在调试期间很有用。
ParseRunner
负责监督解析运行并可选地应用额外的逻辑,最重要的是可以根据语法处理非法输入字符,即解析错误。
以下是ParserRunner的五个预定义的解析器:
- BasicParseRunner:不执行错误处理。(速度最快)
- ReportingParseRunner:为输入中的第一个分析错误创建一个适当的InvalidInputError对象
- RecoveringParseRunner:报告输入中所有的错误信息,并尝试恢复。(最复杂)
- TracingParseRunner:有选择地为每个匹配或不匹配的规则打印跟踪语句
- ProfilingParseRunner:产生关于解析器如何处理一个或多个输入
Parboiled for Java
安装
jdk 1.8
parboiled-core-1.3.1.jar
asm-all-5.2.jar
Maven配置
<dependency>
<groupId>org.parboiled</groupId>
<artifactId>parboiled-java</artifactId>
<version>1.1.8</version>
</dependency>
具体步骤
1. 决定使用哪种类型V来参数化解析器value栈,并创建一个从BaseParser派生的自定义解析器类使用返回类型规则添加一个或多个规则方法。
2. 通过调用Parboiled.createParser来创建解析器的实例。
3. 调用语法分析器的规则方法,该方法构建语法的根规则来创建规则树。
4. 选择一个标准的ParseRunner实现,并调用它的“run”方法来传递根规则和要解析的输入文本。
5. 检查返回的ParsingResult对象的不同成员。
示例:AbcParser
AbcParser算法能够描述经典的非上下文无关文法
。
AbcParser.java
package com.parboiledlearn.demo;
import org.parboiled.BaseParser;
import org.parboiled.Rule;
import org.parboiled.annotations.BuildParseTree;
/**
* S <- &(A c) a+ B !(a|b|c)
* A <- a A? b
* B <- b B? c
*/
@SuppressWarnings({"InfiniteRecursion"})
@BuildParseTree
public class AbcParser extends BaseParser<Object> {
public Rule S() {
return Sequence(
Test(A(), 'c'),
OneOrMore('a'),
B(),
TestNot(AnyOf("abc"))
);
}
public Rule A() {
return Sequence('a', Optional(A()), 'b');
}
public Rule B() {
return Sequence('b', Optional(B()), 'c');
}
}
AbcMain.java
package com.parboiledlearn.demo;
import org.parboiled.Parboiled;
import org.parboiled.common.StringUtils;
import org.parboiled.errors.ErrorUtils;
import static org.parboiled.support.ParseTreeUtils.printNodeTree;
import org.parboiled.parserunners.ReportingParseRunner;
import org.parboiled.support.ParsingResult;
import java.util.Scanner;
public class AbcMain {
public static void main(String[] args) {
AbcParser parser = Parboiled.createParser(AbcParser.class);
while (true) {
System.out.print("Enter an a^n b^n c^n expression (single RETURN to exit)!\n");
String input = new Scanner(System.in).nextLine();
if (StringUtils.isEmpty(input)) break;
ParsingResult<?> result = new ReportingParseRunner(parser.S()).run(input);
if (!result.parseErrors.isEmpty())
System.out.println(ErrorUtils.printParseError(result.parseErrors.get(0)));
else
System.out.println(printNodeTree(result) + '\n');
}
}
}
运行结果
对于无效输入,ReportingParseRunner会报告第一个错误位置。
参考资料:
https://github.com/sirthias/parboiled/wiki
https://zh.wikipedia.org/wiki/%E8%A7%A3%E6%9E%90%E8%A1%A8%E8%BE%BE%E6%96%87%E6%B3%95