# 语法开发

本文档主要介绍了如何基于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);
// 如果不为 '(' 则抛出编译型异常             
1
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);
1
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标准编译器
1
2
3

然后我们该解析由{}大括号括起来的代码块了
我们再次调用getToken()获取一次词素,并判断是否为 { 左括号

t = getToken();
if (!(t.getType() == Token.LP && t.getData().equals("{")))
    throw new CompileException("Missing statement body.", t, getFilename(),this);
1
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);
1
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即可
1
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 {
    }
}
1
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;
}
1
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);
1
2
3
4
5
6

最重要的一点,你需要在eval方法中返回继承于ASTNode接口的执行节点,每个执行节点实现其中的executor方法

执行节点的功能并不是由执行引擎实现的,而是每个执行节点类去实现的,执行引擎只是调用该方法并执行


# 文档编写中

详细的实现参考源码 io.github.mczzcs.exe.code.struct.IfNode.