天天看點

JavaScript實作ZLOGO: 用文法樹實作多層循環

照例先上示範弱效果圖. 示範位址 照舊 :

JavaScript實作ZLOGO: 用文法樹實作多層循環
代碼如下:

開始
  循環4次
    循環4次
      前進50
      左轉90度
    到此為止
  右轉90度
  到此為止
結束           

如上文《JavaScript實作ZLOGO子集: 測試用例》末尾所言, 此文用Antlr進行代碼分析生成文法樹. 再通過文法樹生成p5js繪制代碼.

Antlr支援兩種代碼分析方法, Visitor(監聽者)和Visitor(通路者). SO上的問答

Antlr4 Listeners and Visitors - which to implement?

大緻說明了差別. 基于有限的實踐, 用Visitor方法生成文法樹似乎在實作上更加友善. 尤其相比

Creating a simple parser with ANTLR

一文中使用監聽者+棧來建構文法樹.

Antlr生成工具預設不生成Visitor, 添加-visitor參數後可以生成:

java -cp "antlr-4.7-complete.jar:$CLASSPATH" org.antlr.v4.Tool -Dlanguage=JavaScript -visitor 圈3.g4           

下面是"定制通路器.js"中建構文法樹的部分, 看起來比實作前想的簡單. 預設生成的'圈3Visitor'中, visitXX方法實作都是"this.visitChildren(ctx)", 但那樣會把所有的子節點傳回值放進數組, 形成(至少這裡是)多餘的層次:

定制通路器.prototype.visit程式 = function(上下文) {
  文法樹 = {子節點: this.visit(上下文.聲明())};
  return 文法樹;
};

定制通路器.prototype.visit循環 = function(上下文) {
  return {
    類型: '循環',
    次數: parseInt(上下文.T數().getText()),
    子節點: this.visit(上下文.聲明())};
};

定制通路器.prototype.visit聲明 = function(上下文) {
  return this.visit(上下文.getChild(0));
};

定制通路器.prototype.visit轉向 = function(上下文) {
  var 方向 = 上下文.T轉向().getText();
  var 角度 = parseInt(上下文.T數().getText()) * (方向 === "左" ? 1 : -1);
  return {類型: '轉向', 參數: 角度};
};

定制通路器.prototype.visit前進 = function(上下文) {
  return {類型: '前進', 參數: parseInt(上下文.T數().getText())};
};           

上面的源碼生成文法樹大緻如下所示. 實作上還有很多需要改進的, 比如'前進'和'轉向'現在是兩種'類型', 但應該是一種; 根節點類型不應為空; 等等:

JavaScript實作ZLOGO: 用文法樹實作多層循環

下面是"編譯.js"中基于文法樹生成指令清單的方法, 之後就與之前一樣根據指令清單生成p5js繪制函數(代碼也不用修改).

function 生成指令序列(節點) {
  var 指令序列 = [];
  // TODO: 根節點類型不應為空
  if (!節點.類型) {
    var 聲明節點 = 節點.子節點;
    for (var i = 0; i < 聲明節點.length; i++) {
      Array.prototype.push.apply(指令序列, 生成指令序列(聲明節點[i]));
    }
  } else if (節點.類型 == "循環") {
    var 指令序列 = [];
    for (var i = 0; i < 節點.次數; i++) {
      Array.prototype.push.apply(指令序列, 生成指令序列({子節點: 節點.子節點}));
    }
  } // TODO: 修改類型統一為'指令'
  else if (節點.類型 == "前進" || 節點.類型 == "轉向") {
    return [{名稱: (節點.類型 == "前進" ? 常量_指令名_前進 : 常量_指令名_轉向), 參數: 節點.參數}];
  }
  return 指令序列;
}           

修改相應測試用例, 以及清理不再使用的監聽器代碼後. 代碼已從visitor分支(

program-in-chinese/quan3

)合并到master.

2018-01-02