1. 程式人生 > >JavaScript實現ZLOGO: 用語法樹實現多層迴圈

JavaScript實現ZLOGO: 用語法樹實現多層迴圈

照例先上演示弱效果圖. 演示地址照舊:
2018_01_02_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())}; };

上面的原始碼生成語法樹大致如下所示. 實現上還有很多需要改進的, 比如’前進’和’轉向’現在是兩種’型別’, 但應該是一種; 根節點型別不應為空; 等等:
2018_01_02_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.