# 语法开发
本文档主要介绍了如何基于OpenEX创建新的语法结构
提示
OpenEX的语法解析为有限自动机架构,每次流程将解析出一条语句
在OpenEX
中,一条语句的头部必须为关键字,如果为标识符,OpenEX则会自动判断该条字段为表达式并送入ExpressionParser
进行进一步解析
以下例子中,我们创建一个 定义if语句 的分析器
# 语句分割
在什么类型的Parser
中创建对您的语句相关的解析流程,代表您的语句可以被定义在什么位置
# 脚本根
您需要在io.github.mczzcs.compiler.parser.Parser
类中getParser
方法的switch
语句中创建case "if" -> {}
一条判断分支,否则OpenEX将直接抛出编译型异常: 不是语句
然后根据您的语句结构,规定解析流程
- if语句的结构为
if ( 布尔表达式 ) { 代码块 }
此时我们新建一个对象Token t = getToken();
用于获取下一个词素
按照我们的语法结构, "不出意外" 的话获取到的 t
应该是左括号 (
所以为了防止意外发生,我们应该加上一条if语句去判断
if (!(t.getType() == Token.LP
&& t.getData().equals("(")))
throw new CompileException("'(' expected.", t, filename,this);
// 如果不为 '(' 则抛出编译型异常
2
3
4
左括号对应的词素类型为
LP
,对应id是7
注意
因为OpenEX词法分析机制,"值"也被算作一个词素,所以诸如"("
这样的字符串也被看做一个值,单纯的通过getData
方法获取词素的数据会发生语法的错误判断,所以OpenEX词法分析器给每个词素都打上了其对应的类型,防止此类事情发生
然后我们可以根据括号组合获取表达式的整体字段
List<Token> vars = new LinkedList<>();
t = getToken();
int index = 1;
do {
if (t.getType() == Token.LP && t.getData().equals("(")) {
vars.add(t);
index += 1;
}
if (t.getType() == Token.LR && t.getData().equals(")") && index > 0) {
index -= 1;
vars.add(t);
}
if (t.getType() == Token.LR && t.getData().equals(")") && index <= 0) break;
vars.add(t);
t = getToken();
} while (true);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
这个时候,列表vars
存储的就是表达式的整体字段了,然后我们丢入ExpressionParser
类对表达式进行解析
ExpressionParsing e = new ExpressionParsing(vars, this, c);
// 其中,因为我们是直接在Parser类中编写,所以构造函数第二个形参填写'this'即可
// 'c' 成员实例对象代表一个`Compiler` OpenEX标准编译器
2
3
然后我们该解析由{}
大括号括起来的代码块了
我们再次调用getToken()
获取一次词素,并判断是否为 {
左括号
t = getToken();
if (!(t.getType() == Token.LP && t.getData().equals("{")))
throw new CompileException("Missing statement body.", t, getFilename(),this);
2
3
接着我们分割出代码块的内容
List<Tokens> groups = new LinkedList<>();
int i = 1;
do {
t = getToken();
if (t.getType() == Token.LP && t.getData().equals("{")) i += 1;
if (t.getType() == Token.LR && t.getData().equals("}")) i -= 1;
if (i == 0) break;
groups.add(t);
} while (true);
2
3
4
5
6
7
8
9
此时得到的groups
就是代码块中的所有词素了,然后我们丢入SubParser
类中解析语句块的内容即可
SubParser sp = new SubParser(groups, this, c, false, false, null);
//初始化一个子解析器,其中传入的groups代表我们分割好的语句块
// this 为Parser类
// c 为 Compiler类
// 第一个false为判断是否是在function语句块内,但是我们是在脚本根解析,所以填上false
// 第二个false为判断是否是在while语句块内
// 第三个null只有在function语句块内才能获取到,我们因为是在脚步根,直接填null即可
2
3
4
5
6
7
至此,我们的if语句前端语句分割部分就完成了
然后我们在Parser同级包下创建一个叫IfParser
的类,代表我们的if语句的语法/语义混合解析器
首先我们继承BaseParser
接口,并实现其中的方法
package io.github.mczzcs.compile.parser;
import io.github.mczzcs.compile.Compiler;
import io.github.mczzcs.util.CompileException;
import io.github.mczzcs.exe.code.ASTNode;
import io.github.mczzcs.exe.code.struct.decide.IfNode;
import java.util.List;
public class IfParser implements BaseParser{
@Override
public ASTNode eval(Parser parser, Compiler compiler,FunctionParser functionParser) throws CompileException {
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
然后我们使其能接受Parser
传来的参数,我们添加一个构造方法和一些成员对象
List<ASTNode> bool;
List<BaseParser> group;
public IfParser(List<ASTNode> bool, List<BaseParser> group){
this.bool = bool;
this.group = group;
}
2
3
4
5
6
然后我们在实现BaseParser
的方法内详细编写混合解析器需要做的事,话不多说直接上代码
List<ASTNode> groups = new LinkedList<>();
for(BaseParser bp:group){
groups.add(bp.eval(parser, compiler,functionParser));
}
return new IfNode(bool,groups);
2
3
4
5
6
最重要的一点,你需要在eval方法中返回继承于ASTNode
接口的执行节点,每个执行节点实现其中的executor
方法
执行节点的功能并不是由执行引擎实现的,而是每个执行节点类去实现的,执行引擎只是调用该方法并执行
# 文档编写中
详细的实现参考源码 io.github.mczzcs.exe.code.struct.IfNode
.
← 项目结构