JavaScript实现ZLOGO: 用语法树实现多层循环

简介: 基于JavaScript和Antlr4, 通过语法树实现多层循环. Use AST based on JavaScript and Antlr4 to achieve multi-level loops.

照例先上演示弱效果图. 演示地址照旧:
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.

2018-01-02

相关文章
|
4月前
|
JavaScript 前端开发 开发者
JavaScript中的箭头函数:简洁的语法与this绑定
JavaScript中的箭头函数:简洁的语法与this绑定
439 184
|
7月前
|
JSON 前端开发 Serverless
Mock.js 语法结构全解析
Mock.js 的语法规范介绍,从数据模板定义规范和数据占位符定义规范俩部分介绍, 让你更好的使用 Mock.js 来模拟数据并提高开发效率。
|
JavaScript 前端开发
js循环有几种
js循环有几种
191 0
|
9月前
|
消息中间件 JavaScript 前端开发
最细最有条理解析:事件循环(消息循环)是什么?为什么JS需要异步
度一教育的袁进老师谈到他的理解:单线程是异步产生的原因,事件循环是异步的实现方式。 本质是因为渲染进程因为计算机图形学的限制,只能是单线程。所以需要“异步”这个技术思想来解决页面阻塞的问题,而“事件循环”是实现“异步”这个技术思想的最主要的技术手段。 但事件循环并不是全部的技术手段,比如Promise,虽然受事件循环管理,但是如果没有事件循环,单一Promise依然能实现异步不是吗? 博客不应该只有代码和解决方案,重点应该在于给出解决方案的同时分享思维模式,只有思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您
|
前端开发 JavaScript
前端基础(八)_JavaScript循环(for循环、for-in循环、for-of循环、while、do-while 循环、break 与 continue)
本文介绍了JavaScript中的循环语句,包括for循环、for-in循环、for-of循环、while循环、do-while循环以及break和continue的使用。
901 1
前端基础(八)_JavaScript循环(for循环、for-in循环、for-of循环、while、do-while 循环、break 与 continue)
|
JavaScript
js动画循环播放特效源码(上班族的一天)
js动画循环播放特效是一段实现了包含形象的卡通小人吃、睡、电脑工作的网页动画,js循环动画,简单的画面设计。非常丝滑有意思,欢迎对此代码感兴趣的朋友前来下载参考。
137 2
|
JavaScript 前端开发
JavaScript 函数语法
JavaScript 函数是使用 `function` 关键词定义的代码块,可在调用时执行特定任务。函数可以无参或带参,参数用于传递值并在函数内部使用。函数调用可在事件触发时进行,如用户点击按钮。JavaScript 对大小写敏感,函数名和关键词必须严格匹配。示例中展示了如何通过不同参数调用函数以生成不同的输出。
|
JavaScript 前端开发 索引
|
JavaScript 前端开发 大数据
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
在JavaScript中,Object.assign()方法或展开语法(...)来合并对象,Object.freeze()方法来冻结对象,防止对象被修改
211 0
|
JavaScript
自动循环提交js
自动循环提交js
75 0