oboard

oboard

https://oboard.eu.org/
github
email
tg_channel
medium
twitter
bilibili
steam_profiles
follow

從零開始實現一個 MoonBit 解釋器(二)

圍繞程式語言的詞法分析、語法解析和解釋執行,有以下幾個概念

  1. Token(標記):Token 是程式語言中的基本單位,它代表了語法中的一種語義元素。比如在算術表達式中,加號 (+)、減號 (-)、數字 (123) 都可以是 Token。 每個 Token 有一個類型(如 Plus, Minus, NumberLiteral),以及一個模式(用正則表達式定義),用於在源代碼中識別這些 Token。
  2. Lexer(詞法分析器):Lexer 是將源代碼字符串轉換為一系列 Token 的工具。它負責識別語言的基本構成單位(Token)。 Lexer 會跳過不必要的字符(如空格),並生成有用的 Token 給後續的語法分析器處理。
  3. CST(Concrete Syntax Tree,具體語法樹):CST 是一種數據結構,用於表示源代碼的具體語法結構。它包含了所有語法元素及其關係。 CST 允許解析器理解源代碼的結構,提供對語法的完整視圖,便於後續處理(如語義分析和代碼生成)。
  4. AST(Abstract Syntax Tree,抽象語法樹) 是一種表示程序結構的樹形數據結構,廣泛應用於編譯器和解釋器中。與 CST(Concrete Syntax Tree,具體語法樹)不同,AST 只關注程序的邏輯結構,而忽略了語法的具體細節(如空格、括號等)。
  5. Parser(解析器):Parser 是處理 Token 序列並生成 ST 的組件。它會根據語言的語法規則,判斷 Token 的組合是否符合預期的結構。
  6. Visitor(訪問者模式):訪問者模式是一種設計模式,允許在不修改對象結構的情況下對其進行操作。在此上下文中,它被用來遍歷 CST,並執行相應的操作(如計算表達式的值)。

AST 與 CST 的比較#

CST:

  • 表示代碼的具體結構,包括所有的語法細節。
  • 包含所有的語法元素,例如括號、分號、空格等。

AST:

  • 提供更簡潔的視圖,只關注語法的語義部分。
  • 省略不必要的細節,比如多餘的空白符和分隔符。

假設有如下簡單的表達式:

1 + 2 * 3;

其 AST 可能表示如下:

     +
    / \
   1   *
      / \
     2   3

在這棵樹中:
根節點是 + 運算符。左子節點是操作數 1。
右子節點是 * 運算符,它的左子節點是操作數 2,右子節點是操作數 3。

AST 提供了一種高效的方式來表示和操作程序的結構,抽象出源代碼的語法信息。通過使用 AST,編譯器和解釋器能夠更容易地進行代碼分析、優化和生成,從而提高代碼處理的效率和靈活性。

代碼#

我們從一個簡單的加減法 Parser 開始,逐步實現一個簡單的加法解析器,然後我們再一步一步添加更多的語法。

Addition 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 的相關方法和類,用於創建詞法分析器和語法解析器。

創建 Token#

// 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

本文簡單實現了一個加法解析器,後續我們將添加更多的語法規則。

項目地址: https://github.com/oboard/moonrepl

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。