前端中的AST抽象语法树问题

  • 四则运算
  • 正则表达式
  • 词法分析
  • 语法分析
  • 完整代码

github地址: https://github.com/feddiyao/Frontend-05-Template/tree/master/Week%2003

四则运算

首先明确,此次的代码都是基于LL的语法分析来实现的,实现的是四则混合运算的功能,先看下定义:
TokenNumber:
· 1 2 3 4 5 6 7 8 9 0 的组合
Operator:
+ - * / 之一
WhiteSpace:
<SP>
LineTerminator:
<LF> <CR>

看下产生式:

正则表达式

我们首先实现正则表达式的匹配原则:

<script>var regexp = /([0-9\.]+)|([ \t]+)|([\r\n]+)|(\*)|(\/)|(\+)|(\-)/gvar dictionary = ["Number", "Whitespace", "LineTerminator", "*", "/", "+", "-"];function tokenize(source) {var result = null;while(true) {result = regexp.exec(source);if(!result) break;for(var i = 1; i <= dictionary.length; i ++) {if(result[i])console.log(dictionary[i - 1]);}console.log(result);}}tokenize("1024 + 10 * 25");
</script>

此时我们看一下页面的运行打印结果:

值得一提的是这里用到了exec方法,exec() 方法用于检索字符串中的正则表达式的匹配。
我们看一下它的语法:
RegExpObject.exec(string)

如果 exec() 找到了匹配的文本,则返回一个结果数组。否则,返回 null。此数组的第 0 个元素是与正则表达式相匹配的文本,第 1 个元素是与 RegExpObject 的第 1 个子表达式相匹配的文本(如果有的话),第 2 个元素是与 RegExpObject 的第 2 个子表达式相匹配的文本(如果有的话),以此类推。除了数组元素和 length 属性之外,exec() 方法还返回两个属性。index 属性声明的是匹配文本的第一个字符的位置。input 属性则存放的是被检索的字符串 string。我们可以看得出,在调用非全局的 RegExp 对象的 exec() 方法时,返回的数组与调用方法 String.match() 返回的数组是相同的。

但是,当 RegExpObject 是一个全局正则表达式时,exec() 的行为就稍微复杂一些。它会在 RegExpObject 的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。这就是说,您可以通过反复调用 exec() 方法来遍历字符串中的所有匹配文本。当 exec() 再也找不到匹配的文本时,它将返回 null,并把 lastIndex 属性重置为 0。

词法分析

我们在这一部分对上面的代码做优化。
首先是刚才提到的:
当 RegExpObject 是一个全局正则表达式时,exec() 的行为就稍微复杂一些。它会在 RegExpObject 的 lastIndex 属性指定的字符处开始检索字符串 string。当 exec() 找到了与表达式相匹配的文本时,在匹配后,它将把 RegExpObject 的 lastIndex 属性设置为匹配文本的最后一个字符的下一个位置。
那么我们就要考虑到没有匹配上字符的情况,做一个判断处理:

<script>var regexp = /([0-9\.]+)|([ \t]+)|([\r\n]+)|(\*)|(\/)|(\+)|(\-)/gvar dictionary = ["Number", "Whitespace", "LineTerminator", "*", "/", "+", "-"];function* tokenize(source) {var result = null;var lastIndex = 0;while(true) {lastIndex = regexp.lastIndex;result = regexp.exec(source);if(!result) break;if(regexp.lastIndex - lastIndex > result[0].length)break;let token = {type: null,value: null}for(var i = 1; i <= dictionary.length; i ++) {if(result[i])token.type = dictionary[i - 1];}token.value = result[0];yield token}yield {type: 'EOF'}}for (let token of tokenize("1024 + 10 * 25")) {console.log(token)}
</script>

如上,我们对regexp.lastIndex - lastIndexresult[0] 的长度进行比较,判断是否有字符串没有匹配上。
将整个函数改成generator函数的形式,我们看下运行的结果:

语法分析

首先编写分块的产生式,我们看一下总的代码结构:

<script>var regexp = /([0-9\.]+)|([ \t]+)|([\r\n]+)|(\*)|(\/)|(\+)|(\-)/gvar dictionary = ["Number", "Whitespace", "LineTerminator", "*", "/", "+", "-"];function* tokenize(source) {var result = null;var lastIndex = 0;while(true) {lastIndex = regexp.lastIndex;result = regexp.exec(source);if(!result) break;if(regexp.lastIndex - lastIndex > result[0].length)break;let token = {type: null,value: null}for(var i = 1; i <= dictionary.length; i ++) {if(result[i])token.type = dictionary[i - 1];}token.value = result[0];yield token}yield {type: 'EOF'}}let source = [];for(let token of tokenize("10 * 25")) {if (token.type !== "Whitespace" && token.type !== "LineTerminator")source.push(token);}function Expression(tokens) {}function AdditiveExpression(source){}function MultiplicativeExpresson(source) {console.log(source);}MultiplicativeExpresson("10 * 25")
</script>

我们先从MultiplicativeExpresson来进行研究,它分为四种情况:

function MultiplicativeExpresson(source) {//如果是数字则进行封装if(source[0].type === "Number") {let node = {type: "MultiplicativeExpresson",children:[source[0]]}source[0] = node;return MultiplicativeExpresson(source)}//如果是乘号或者除号,则将三项出栈,进行重组if(source[0].type === "MultiplicativeExpresson" && source[1] && source[1].type === "*") {let node = {type: "MultiplicativeExpresson",operator: "*",children: []}node.children.push(source.shift());node.children.push(source.shift());node.children.push(source.shift());source.unshift(node);return MultiplicativeExpresson(source)}if(source[0].type === "MultiplicativeExpresson" && source[1] && source[1].type === "/") {let node = {type: "MultiplicativeExpresson",operator: "*",children: []}node.children.push(source.shift());node.children.push(source.shift());node.children.push(source.shift());source.unshift(node);return MultiplicativeExpresson(source)}//递归结束的条件if(source[0].type === "MultiplicativeExpresson")return source[0];return MultiplicativeExpresson(source);}

我们看一下当source为"10 * 25 / 2"时调用console.log(MultiplicativeExpresson(source))最后运行的结果:

接下来看AdditiveExpression 本质上和MultiplicativeExpresson没有什么不同,差异点已经标注在代码当中了:

    function AdditiveExpression(source){if(source[0].type === "MultiplicativeExpresson") {let node = {type: "AdditiveExpression",children:[source[0]]}source[0] = node;return AdditiveExpression(source)}//如果是乘号或者除号,则将三项出栈,进行重组if(source[0].type === "AdditiveExpression" && source[1] && source[1].type === "+") {let node = {type: "AdditiveExpression",operator: "+",children: []}node.children.push(source.shift());node.children.push(source.shift());//考虑到第三个数可能时Number 需要在这里再次调用一下 MultiplicativeExpresson 做处理MultiplicativeExpresson(source);node.children.push(source.shift());source.unshift(node);return AdditiveExpression(source)}if(source[0].type === "AdditiveExpression" && source[1] && source[1].type === "-") {let node = {type: "AdditiveExpression",operator: "-",children: []}node.children.push(source.shift());node.children.push(source.shift());MultiplicativeExpresson(source);node.children.push(source.shift());source.unshift(node);return AdditiveExpression(source)}//递归结束的条件if(source[0].type === "AdditiveExpression")return source[0];//第一次进循环 调用MultiplicativeExpresson(source);return AdditiveExpression(source);}

我们看一下当source为"10 * 25 / 2"时调用console.log(AdditiveExpression(source))最后运行的结果:

那么Expression的代码逻辑就很好表达了:

function Expression(tokens) {if(source[0].type === "AdditiveExpression" && source[1] && source[1].type === "EOF") {let node = {type: "Expression",children: [source.shift(), source.shift()]}source.unshift(node);return node;}AdditiveExpression(source);return Expression(source);}

看下运行后的结果:

以上就是所有的js解析抽象语法树的代码。

完整代码

<script>var regexp = /([0-9\.]+)|([ \t]+)|([\r\n]+)|(\*)|(\/)|(\+)|(\-)/gvar dictionary = ["Number", "Whitespace", "LineTerminator", "*", "/", "+", "-"];function* tokenize(source) {var result = null;var lastIndex = 0;while(true) {lastIndex = regexp.lastIndex;result = regexp.exec(source);if(!result) break;if(regexp.lastIndex - lastIndex > result[0].length)break;let token = {type: null,value: null}for(var i = 1; i <= dictionary.length; i ++) {if(result[i])token.type = dictionary[i - 1];}token.value = result[0];yield token}yield {type: 'EOF'}}let source = [];for(let token of tokenize("10 * 25 / 2")) {if (token.type !== "Whitespace" && token.type !== "LineTerminator")source.push(token);}function Expression(tokens) {if(source[0].type === "AdditiveExpression" && source[1] && source[1].type === "EOF") {let node = {type: "Expression",children: [source.shift(), source.shift()]}source.unshift(node);return node;}AdditiveExpression(source);return Expression(source);}function AdditiveExpression(source){if(source[0].type === "MultiplicativeExpresson") {let node = {type: "AdditiveExpression",children:[source[0]]}source[0] = node;return AdditiveExpression(source)}//如果是乘号或者除号,则将三项出栈,进行重组if(source[0].type === "AdditiveExpression" && source[1] && source[1].type === "+") {let node = {type: "AdditiveExpression",operator: "+",children: []}node.children.push(source.shift());node.children.push(source.shift());//考虑到第三个数可能时Number 需要在这里再次调用一下 MultiplicativeExpresson 做处理MultiplicativeExpresson(source);node.children.push(source.shift());source.unshift(node);return AdditiveExpression(source)}if(source[0].type === "AdditiveExpression" && source[1] && source[1].type === "-") {let node = {type: "AdditiveExpression",operator: "-",children: []}node.children.push(source.shift());node.children.push(source.shift());MultiplicativeExpresson(source);node.children.push(source.shift());source.unshift(node);return AdditiveExpression(source)}//递归结束的条件if(source[0].type === "AdditiveExpression")return source[0];//第一次进循环 调用MultiplicativeExpresson(source);return AdditiveExpression(source);}function MultiplicativeExpresson(source) {if(source[0].type === "Number") {let node = {type: "MultiplicativeExpresson",children:[source[0]]}source[0] = node;return MultiplicativeExpresson(source)}//如果是乘号或者除号,则将三项出栈,进行重组if(source[0].type === "MultiplicativeExpresson" && source[1] && source[1].type === "*") {let node = {type: "MultiplicativeExpresson",operator: "*",children: []}node.children.push(source.shift());node.children.push(source.shift());node.children.push(source.shift());source.unshift(node);return MultiplicativeExpresson(source)}if(source[0].type === "MultiplicativeExpresson" && source[1] && source[1].type === "/") {let node = {type: "MultiplicativeExpresson",operator: "*",children: []}node.children.push(source.shift());node.children.push(source.shift());node.children.push(source.shift());source.unshift(node);return MultiplicativeExpresson(source)}//递归结束的条件if(source[0].type === "MultiplicativeExpresson")return source[0];return MultiplicativeExpresson(source);}console.log(Expression(source))
</script>

JS实现AST抽象语法树问题相关推荐

  1. AST(抽象语法树)实战入门:js逆向中滑块加密if语句转化

    概述:AST 抽象语法树 实战 入门 案例 js逆向 js滑块 js加密 极验 瑞数 阿里滑块 5秒盾 ​引言: AST算得上是高端技能.如果把爬虫技能分为初中高三个阶段的话.常规的JS逆向找找参数, ...

  2. 应用ast抽象语法树修改js函数

    原理:AST抽象语法树 目标:在每一个函数里面插入一个console.log()把函数传入的全部参数输出出来 关于:本文章是在基于我的个人理解且怕忘记知识所记录下来的给自己看并且分享自己的一个心得,文 ...

  3. AST(抽象语法树)超详细

    自己研究的东西会用到AST,就自己通过查阅资料,整理一下. 本文目录 第一部分:AST的作用 第二部分:AST的流程 第三部分: Eclipse AST的获取与访问 第一部分:AST的作用 首先来一个 ...

  4. AST抽象语法树的基本思想

    AST抽象语法树的基本思想 前言 AST概述 AST结构 AST解析 转换 生成 前言 在阅读java ORM框架spring data jpa的源码时,发现Hibernate(spring data ...

  5. php ast 抽象语法树,AST抽象语法树的基本思想

    AST抽象语法树的基本思想 前言 AST概述 AST结构 AST解析 转换 生成 前言 在阅读java ORM框架spring data jpa的源码时,发现Hibernate(spring data ...

  6. ast抽象语法树_新抽象语法树(AST)给 PHP7 带来的变化

    本文大部分内容参照 AST 的 RFC 文档而成:https://wiki.php.net/rfc/abstract_syntax_tree,为了易于理解从源文档中节选部分进行介绍. 我的官方群点击此 ...

  7. java AST 表达式_java 编译时注解-AST 抽象语法树简介

    AST 语法入门 以前使用 Lombok 一直觉得是一个很棒的设计,可以同时兼顾注解的遍历和运行的性能. 运行时注解一直因为性能问题被人诟病. 自己尝试写过一些框架,但是耗费了比较多的精力,因为 AS ...

  8. python compiler.ast_ast --- 抽象语法树 — Python 3.7.9 文档

    ast --- 抽象语法树¶ ast 模块帮助 Python 程序处理 Python 语法的抽象语法树.抽象语法或许会随着 Python 的更新发布而改变:该模块能够帮助理解当前语法在编程层面的样貌. ...

  9. Java解析SQL生成语法树_Atitit.sql ast 表达式 语法树 语法 解析原理与实现 java php c#.net js python...

    Atitit.sql ast 表达式 语法树 语法 解析原理与实现java php c#.net js python 1.1.Sql语法树ast如下图锁死 2.SQL语句解析的思路和过程 2.1.le ...

最新文章

  1. 网络安全等级保护测评高风险判定指引_等保知识|测评高风险项详解:安全管理中心...
  2. 人工智能技术结合制造业,是学术研究还是落地方案?!
  3. MySql,Sql Server分区技术浅析
  4. mysql中最常用的存储引擎有_mysql常用的存储引擎有哪些
  5. 【自用】docker命令记录
  6. 20200421:周赛练习题(leetcode148周周赛上)
  7. javaCV开发详解之7:让音频转换更加简单,实现通用音频编码格式转换、重采样等音频参数的转换功能(以pcm16le编码的wav转mp3为例)...
  8. Win7如何修复开机画面
  9. 储备池计算(Reservoir Computing)综述
  10. 引流复盘:从知乎引流20万粉,我只用了1个月
  11. 利用opencv带你玩转人脸识别-上篇(读取图片,灰度转换,尺寸修改,绘制矩形快速入门)
  12. ESB项目培训样例说明
  13. php直播源码,图片亮度
  14. 按Enter键起到Tab键的效果
  15. 2021年计算机应用基础统考题库,2021年7月计算机应用基础统考题库-网络教育统考计算机应用基础真题...
  16. 【CSS】你真的了解font-weight吗?
  17. 《TP基础篇——MTK平台》
  18. vue更改vue-admin-telement模板登录
  19. 用Tensorflow实现AlexNet识别猫狗数据集(猫狗大战)【附代码】
  20. JetsonNano学习(一)SDKManager系统烧录

热门文章

  1. 树莓派3代发布支持蓝牙WiFi:售35美元
  2. 南邮 OJ 1972 炒股票的女巫璐璐
  3. iOS--无法加载dae文件
  4. 东大19春计算机基础,东大19春学期《计算机基础》在线作业3标准答案.doc
  5. 女士适合学数据分析吗
  6. 怎么获取apk的包名??
  7. 8.Python之文件
  8. 扬州大学计算机控制技术课设,计算机控制技术的课设.doc
  9. 大数据平台开发需要掌握什么语言
  10. qq公众平台出错了609_腾讯广告投放平台,腾讯广告投放推广平台有哪些?