围绕编程语言的词法分析、语法解析和解释执行,有以下几个概念
CST:
AST:
假设有如下简单的表达式:
1 + 2 * 3;
其 AST 可能表示如下:
+
/ \
1 *
/ \
2 3
在这个树中:
根节点是 + 运算符。左子节点是操作数 1。
右子节点是 * 运算符,它的左子节点是操作数 2,右子节点是操作数 3。
AST 提供了一种高效的方式来表示和操作程序的结构,抽象出源代码的语法信息。通过使用 AST,编译器和解释器能够更容易地进行代码分析、优化和生成,从而提高代码处理的效率和灵活性。
我们从一个简单的加减法 Parser 开始,逐步实现一个简单的加法解析器,然后我们再一步一步添加更多的语法。
首先需要安装Chevrotain
大部分人使用 pnpm 也就是
pnpm install chevrotain
我个人更喜欢使用 bun
bun i chevrotain
bun add v1.1.20 (ae194892)
installed [email protected]
[271.00ms] done
安装完毕之后,创建一个 index.ts
文件
import {
createToken,
Lexer,
CstParser,
tokenMatcher,
CstNode,
} from "chevrotain";
导入 Chevrotain 的相关方法和类,用于创建词法分析器和语法解析器。
// AdditionOperator: 创建一个表示加法运算符的 Token,但使用 Lexer.NA,表示这个 Token 不会被直接匹配(它是个类别)。
const AdditionOperator = createToken({
name: "AdditionOperator",
pattern: Lexer.NA,
});
// Plus 和 Minus: 分别创建表示加号(+)和减号(-)的 Token,它们都是 AdditionOperator 的子类。
const Plus = createToken({
name: "Plus",
pattern: /\+/,
categories: AdditionOperator,
});
const Minus = createToken({
name: "Minus",
pattern: /-/,
categories: AdditionOperator,
});
// NumberLiteral: 创建表示数字的 Token,正则表达式 /[1-9]\d*/ 匹配 1 到 9 开头的整数(即不以 0 开头)。
const NumberLiteral = createToken({
name: "NumberLiteral",
pattern: /[1-9]\d*/,
});
// WhiteSpace: 创建一个表示空格的 Token,使用 Lexer.SKIPPED 表示在词法分析中跳过它(即不产生 Token)。
const WhiteSpace = createToken({
name: "WhiteSpace",
pattern: /\s+/,
group: Lexer.SKIPPED,
});
const allTokens = [WhiteSpace, Plus, Minus, NumberLiteral, AdditionOperator];
// 将所有定义的 Token 放入数组 allTokens 中,并创建一个新的词法分析器 CalculatorLexer。
const CalculatorLexer = new Lexer(allTokens);
class CalculatorPure extends CstParser {
constructor() {
super(allTokens);
const $ = this;
// expression 规则: 这个规则对应一个表达式,包含一个加法表达式,后面我们再添加更多的表达式:例如乘法。
$.RULE("expression", () => {
$.SUBRULE($.additionExpression);
});
// additionExpression 规则: 这个规则定义了加法表达式的结构,首先消费一个 NumberLiteral 作为左操作数(lhs),然后消费零个或多个加法运算符和右操作数(rhs)。
$.RULE("additionExpression", () => {
$.CONSUME(NumberLiteral, { LABEL: "lhs" });
$.MANY(() => {
$.CONSUME(AdditionOperator);
$.CONSUME2(NumberLiteral, { LABEL: "rhs" });
});
});
this.performSelfAnalysis();
}
}
const parser = new CalculatorPure();
const BaseCstVisitor = parser.getBaseCstVisitorConstructor();
class CalculatorInterpreter extends BaseCstVisitor {
constructor() {
super();
this.validateVisitor();
}
expression(ctx) {
return this.visit(ctx.additionExpression);
}
// additionExpression 方法: 处理加法表达式的逻辑。首先获取左操作数的值,然后遍历右操作数并根据运算符进行加法或减法。
additionExpression(ctx) {
let result = Number.parseInt(ctx.lhs[0].image, 10);
if (ctx.rhs) {
ctx.rhs.forEach((rhsOperand, idx) => {
let rhsValue = Number.parseInt(rhsOperand.image, 10);
let operator = ctx.AdditionOperator[idx];
if (tokenMatcher(operator, Plus)) {
result += rhsValue;
} else {
result -= rhsValue;
}
});
}
return result;
}
}
这段代码定义了一个简单的加法和减法计算器,能够解析表达式并计算结果。它使用了 Chevrotain 的功能来创建 Token,构建语法规则,并实现了解释器逻辑。
function eval(input: string) {
const lexResult = CalculatorLexer.tokenize(input);
parser.input = lexResult.tokens;
const cst = parser.row();
return new CalculatorInterpreter().visit(cst);
}
console.log(eval("1 + 2 + 3")); // 6
console.log(eval("1 + 2 - 3")); // 0
本文简单实现了一个加法解析器,后续我们将添加更多的语法规则。