语法分析实现
1. 目标及代码
- 目标: 实现表达式的语法树构建(加减乘除的表达式解析)
如: 将1+2*5等表达式解析为语法树的形式
-
代码地址: git代码仓库
-
注意: 本文章主要目的是记录个人学习历程,如有不对的地方欢迎大家评论指出
2. 语法分析阶段做什么?
- 语法分析阶段主要的功能就是把词法分析的结果构建一颗语法树, 此法阶段就相当于找到了一个一个的单词而语法阶段要做的就是按照文法规则将这些单词组织起来
- 此处采用的方法为递归下降法, 文法部分内容太偏数学如果有兴趣可自行搜索
3. 表达式解析理论简介
3.1 表达式相关产生式
- Eper代表表达式,如果我们想用Eper来表示加法运算
// Eper代表开始符(类似完整表达式 1 + 1)
// Eper可以产生出Eper - Eper | Eper + Eper | digital 中间的竖线代表或者
// digital代表数字
// 完整的看就类似一个递归表达式,递归终结条件在于当前的Eper是否为一个digital
// 但是目前这样的表达是直接写代码有问题,因为每一个Exper如果我们先处理左边那么又是一个Exper,这样就是一个无限递归, 所以我们需要消除左递归(注意:此处的Exper最终就是一个函数,用于解析一个表达式)
Eper -> Eper - Eper | Eper + Eper | digital
// digital 表示数字
digital -> [0-9]+
- 消除左递归
//也就是说上述存在左递归的形式直接被以下两个推导式改为了右递归
Exper -> digital Exper_
Exper_ -> -Exper Exper_ | +Exper Exper_ | null
// digital 表示数字
digital -> [0-9]+
- 伪代码实现
- 注意一下代码没有做任何校验仅仅描述主逻辑
func ExperTree Exper() {
// 构造返回节点
ExperTree node = new ExperTree();
// Exper -> digital Exper_
// digital部分直接从符号流获取
node.addChildNode(fetchDigitalToken());
// Exper_
node.addChildNode(Exper_());
return node;
}
func ExperTree Exper_() {
// 构造返回节点
ExperTree node = new ExperTree();
// Exper -> digital Exper_
// digital部分直接从符号流获取
node.addChildNode(fetchDigitalToken());
// Exper_
exper_Node = Exper_();
// 因为-Exper Exper_ | +Exper Exper_ | null, 中第一位是符号第二位才是一个表达式节点
node.addChildNode(exper_Node.get(1));
//当前表达式的符号(此处也就是+-)
node.setValue(exper_Node.get(0).value())
return node;
}
4. 多优先级表达式
- 如果出现乘除法该怎么处理
- 首先以上表达式如果出现乘除法是不能够正确的构建一个语法树的此处只写出±/*法的表达式
// E表达式只表示加减法
E -> E + Iterm | E - Iterm | Iterm
// Iterm表示乘除法
Iterm -> Iterm * digital | Iterm / digital | digital
// digital 表示数字
digital -> [0-9]+
// 同样由于左递归的问题
E -> Iterm E_
E_ -> +Iterm E_ | -Iterm E_ | null
Iterm -> digital Iterm_
Iterm_ -> *digital Iterm_ | /digital Iterm_ | null
// 以上推导式中除了digital所有的都是函数
5. 通用表达式
- 在计算机中不仅仅只有上节中所说的两种优先级(加减和乘除), 我们常见的优先级如下
1. <、<=、>、>=
2. +、-
3. *、/
4. << >>
5. ()、++、--
- 遇到多优先级改如何表达?一级一级的写表达式? 在这里实际上我们可以将表达式定义为一个函数但是不同优先级可以传入数字来表示当前是第几优先级
- 表达式如下
// 注意Op(k) 代表当前优先级的所有符号集合
// E(k) 实际上在加减乘除中代表的就是E + Iterm | E - Iterm
// E(k + 1)实际上在加减乘除中代表的就是Iterm
E(K) -> E(k) Op(k) E(k + 1) | E(k + 1)
// 同样因为表达式存在左递归,消除左递归如下
E(k) -> E(k + 1) E_(k)
E_(k) -> Op(k) E(k + 1) E_(k) | null
// 目前还有一个问题E(k) 中K到何时结束, 实际上就是到最高优先级就结束这里也就是5, terminal
// E(t) 类似于之前Term
E(t) -> E(t) Op(t) digital | digital
E(t) -> digital E_(t)
E_(t) -> Op(t) digital E_(t) | null
6.具体代码实现
6.1 基础语法树节点
- 基础结构类如下(具体代码请查看代码仓库)
- GrammerNode为抽象语法节点(包含token以及节点类型)
- Factor是因子也是抽象类其主要有Variable和Scalar两个子类
- Exper代表表达式的意思
- GrammerNode代码
@Data
public abstract class GrammerNode {
protected Token token; // 词法单元
protected String label; // 备注(标签)
protected GrammerNodeTypes type;
//子节点
protected List<GrammerNode> children = new ArrayList<>();
//父节点
protected GrammerNode parent;
public GrammerNode() {
}
public GrammerNode(GrammerNodeTypes type, String label) {
this.type = type;
this.label = label;
}
public GrammerNode getChild(int index) {
if(index >= this.children.size()) {
return null;
}
return this.children.get(index);
}
public void addChild(GrammerNode node) {
node.parent = this;
children.add(node);
}
}
6.2 添加PeekTokenIterator
- 目的:用于将Token作为流进行操作
- 具体代码如下:
public class PeekTokenIterator extends PeekIterator<Token> {
public PeekTokenIterator(Stream<Token> stream) {
super(stream);
}
/**
* 判断下一个token的值是否等于value并返回token
* @param value
* @return
* @throws ParseException
*/
public Token nextMatch(String value) throws ParseException {
Token token = this.next();
if(!token.getValue().equals(value)) {
throw new ParseException(token);
}
return token;
}
/**
* 判断下一个token的类型是否等于type并返回token
* @param type
* @return
* @throws ParseException
*/
public Token nextMatch(TokenType type) throws ParseException {
Token token = this.next();
if(!token.getType().equals(type)) {
throw new ParseException(token);
}
return token;
}
}
6.3 保存优先级符号
- 保存所有的操作符到集合
- 具体代码
/**
* 操作符优先级存储
*/
public class PriorityTable {
private List<List<String>> table = new ArrayList<>();
public PriorityTable() {
table.add(Arrays.asList("&", "|", "^"));
table.add(Arrays.asList("==", "!=", ">", "<", ">=", "<="));
table.add(Arrays.asList("+", "-"));
table.add(Arrays.asList("*", "/"));
table.add(Arrays.asList("<<", ">>"));
}
public int size(){
return table.size();
}
public List<String> get(int level) {
return table.get(level);
}
}
6.4 Exper中实现语法分析
- 表达式如下
E(K) -> E(k) Op(k) E(k + 1) | E(k + 1)
// 同样因为表达式存在左递归,消除左递归如下
E(k) -> E(k + 1) E_(k)
E_(k) -> Op(k) E(k + 1) E_(k) | null
// 目前还有一个问题E(k) 中K到何时结束, 实际上就是到最高优先级就结束这里也就是5, terminal
// E(t) 类似于之前Term
E(t) -> E(t) Op(t) digital | digital
E(t) -> digital E_(t)
E_(t) -> Op(t) digital E_(t) | null
-
目标: 输入一个Token流,将Token流解析为语法树
-
实现从字符流中解析actor(相当于解析Digital)
public class Factor extends GrammerNode {
public Factor(Token token) {
super();
this.token = token;
this.label = token.getValue();
}
public static GrammerNode parse(PeekTokenIterator it) {
Token token = it.peek();
TokenType type = token.getType();
if(type == TokenType.VARIABLE) {
it.next();
return new Variable(token);
} else if(token.isScalar()){
it.next();
return new Scalar(token);
}
return null;
}
}
- 在Expr中实现完整表达式推导,整体表达式解析入口为pase函数
/**
* 表达式
* 一元表达式
* 二元表达式
*/
public class Expr extends GrammerNode {
//优先级存储对象
private static PriorityTable priorityTable = new PriorityTable();
public Expr(GrammerNodeTypes type, Token token) {
super();
this.type = type;
this.label = token.getValue();
this.token = token;
}
/**
* 传入符号流
* @param it 符号流迭代器
* @return 语法树根节点
* @throws ParseException 语法异常
*/
public static GrammerNode parse(PeekTokenIterator it) throws ParseException {
// 起始符E E(K) -> E(k) Op(k) E(k + 1) | E(k + 1)
return E(0, it);
}
/**
* E(K)函数,其右递归表达式如下
* E(k) -> E(k + 1) E_(k) 注意当 k 为最高优先级时为结束条件
* 注意:此处默认表达式一定是一元或二元表达式
* @param k 当前第几优先级
* @param it 符号流
* @return 语法根节点
*/
private static GrammerNode E(int k, PeekTokenIterator it) throws ParseException {
//当还未达到最高优先级时E(k) -> E(k + 1) E_(k)
// 当前情况Expr.left = E(k + 1) Exper.right = E_(k)中的表达式部分 Expr.label为E_(k)的符号部分
if (k < priorityTable.size() - 1) {
GrammerNode expr = combine(it, () -> E(k + 1, it), () -> E_(k , it));
return expr;
} else {
// 达到最高优先级
//E(t) -> digital E_(t)
//E(t) -> F E_(t) | U E_(t) 此处F相当于digital,但还有一种特俗情况一元表达式
return race(
it,
() -> combine( it, () -> F(it), () -> E_( k, it)),
() -> combine( it, () -> U(it), () -> E_( k, it))
);
}
}
/**
* E(t) -> U E_(t)
* @param it
* @return
*/
private static GrammerNode U(PeekTokenIterator it) throws ParseException {
Token token = it.peek();
String value = token.getValue();
if(value.equals("(")) {
it.nextMatch("(");
GrammerNode expr = E(0, it);
it.nextMatch(")");
return expr;
}
else if (value.equals("++") || value.equals("--") || value.equals("!")) {
Token t = it.peek();
it.nextMatch(value);
Expr unaryExpr = new Expr(GrammerNodeTypes.UNARY_EXPR, t);
unaryExpr.addChild(E(0, it));
return unaryExpr;
}
return null;
}
/**
* E(t) -> F E_(t)
* @param it
* @return
*/
private static GrammerNode F(PeekTokenIterator it) {
GrammerNode node = Factor.parse(it);
if (null == node) {
return null;
}
return node;
}
/**
* E_(k) -> Op(k) E(k + 1) E_(k) | null
* @param k 当前级
* @param it 迭代器
* @return 节点
*/
private static GrammerNode E_(int k, PeekTokenIterator it) throws ParseException {
// lookhead查看当前第一位是否为操作符
Token token = it.peek();
String value = token.getValue();
//存在当前操作符不存在直接返回空
if(priorityTable.get(k).indexOf(value) != -1) {
Expr expr = new Expr(GrammerNodeTypes.BINARY_EXPR, it.nextMatch(value));
expr.addChild(combine(it,
() -> E(k+1, it),
() -> E_(k, it)
));
return expr;
}
return null;
}
/**
* 结合时只看结果
* @param a E(k + 1)
* E(t) -> digital E_(t)
* @param b E_(k) E_(k) -> Op(k) E(k + 1) E_(k) | null
* @return 结合后的节点
*/
private static GrammerNode combine(PeekTokenIterator it, ExprFunc a, ExprFunc b) throws ParseException {
//如果a执行结果为空则时一元表达式
GrammerNode aNode = a.exec();
if(aNode == null) {
return it.hasNext() ? b.exec() : null;
}
GrammerNode bNode = it.hasNext() ? b.exec() : null;
if(bNode == null) {
return aNode;
}
//E_(k) -> Op(k) E(k + 1) E_(k) 此处相当于Op(k) 是根节点
Expr expr = new Expr(GrammerNodeTypes.BINARY_EXPR, bNode.token);
expr.addChild(aNode);
//E(k + 1)
expr.addChild(bNode.getChild(0));
//指定父节点
return expr;
}
private static GrammerNode race(PeekTokenIterator it, ExprFunc aFunc, ExprFunc bFunc) throws ParseException {
if(!it.hasNext()) {
return null;
}
GrammerNode a = aFunc.exec();
if(a != null) {
return a;
}
return bFunc.exec();
}
}