热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

重学前端28#通过四则运算的解释器快速理解编译原理

说明每天10分钟,重构你的前端知识体系专栏笔记。一、分析按照编译原理相关的知识,将其分成几个步骤。定义四则运算:产出四则运算的词法定义和语

说明


每天10分钟,重构你的前端知识体系专栏笔记。


一、分析


按照编译原理相关的知识,将其分成几个步骤。


  • 定义四则运算:产出四则运算的词法定义和语法定义。
  • 词法分析:把输入的字符串流变成 token。
  • 语法分析:把 token 变成抽象语法树 AST。
  • 解释执行:后序遍历 AST,执行得出结果。

二、定义四则运算


2.1、定义词法


  • Token
    • Number: 1 2 3 4 5 6 7 8 9 0 的组合
    • Operator: +、-、*、/ 之一
  • Whitespace:
  • LineTerminator:

2.2、定义语法


语法定义多数采用 BNF。巴科斯范式(BNF: Backus-Naur Form 的缩写)是由 John Backus 和 Peter Naur 首次引入一种形式化符号来描述给定语言的语法(最早用于描述ALGOL 60 编程语言)。Javascript 标准里面就是一种跟 BNF 类似的自创语法。

1、加法是由若干个乘法再由加号或者减号连接成的:

<Expression> ::&#61;<AdditiveExpression><EOF><AdditiveExpression> ::&#61;<MultiplicativeExpression>|<AdditiveExpression><&#43;><MultiplicativeExpression>|<AdditiveExpression><-><MultiplicativeExpression>

2、可把普通数字当成乘法的一种特例&#xff1a;

<MultiplicativeExpression> ::&#61;<Number>|<MultiplicativeExpression><*><Number>|<MultiplicativeExpression></><Number>

上面就是四则运算的定义。

三、词法分析&#xff1a;状态机


词法分析&#xff1a;把字符流变成 token 流&#xff0c;有两种方案&#xff0c;一种是状态机&#xff0c;一种是正则表达式&#xff0c;它们是等效的。


3.1、实现状态机

// 可能产生四种输入元素&#xff0c;其中只有两种 token&#xff0c;状态机的第一个状态就是根据第一个输入字符来判断进入了哪种状态var token &#61; [];
function start(char) {if(char &#61;&#61;&#61; &#39;1&#39; || char &#61;&#61;&#61; &#39;2&#39;|| char &#61;&#61;&#61; &#39;3&#39;|| char &#61;&#61;&#61; &#39;4&#39;|| char &#61;&#61;&#61; &#39;5&#39;|| char &#61;&#61;&#61; &#39;6&#39;|| char &#61;&#61;&#61; &#39;7&#39;|| char &#61;&#61;&#61; &#39;8&#39;|| char &#61;&#61;&#61; &#39;9&#39;|| char &#61;&#61;&#61; &#39;0&#39;) {token.push(char);return inNumber;}if(char &#61;&#61;&#61; &#39;&#43;&#39; || char &#61;&#61;&#61; &#39;-&#39; || char &#61;&#61;&#61; &#39;*&#39; || char &#61;&#61;&#61; &#39;/&#39;) {emmitToken(char, char);return start}if(char &#61;&#61;&#61; &#39; &#39;) {return start;}if(char &#61;&#61;&#61; &#39;\r&#39; || char &#61;&#61;&#61; &#39;\n&#39;) {return start;}
}
function inNumber(char) {if ( char &#61;&#61;&#61; &#39;1&#39; || char &#61;&#61;&#61; &#39;2&#39; || char &#61;&#61;&#61; &#39;3&#39;|| char &#61;&#61;&#61; &#39;4&#39;|| char &#61;&#61;&#61; &#39;5&#39; || char &#61;&#61;&#61; &#39;6&#39; || char &#61;&#61;&#61; &#39;7&#39;|| char &#61;&#61;&#61; &#39;8&#39; || char &#61;&#61;&#61; &#39;9&#39; || char &#61;&#61;&#61; &#39;0&#39;) {token.push(char);return inNumber;} else {emmitToken("Number", token.join(""));token &#61; [];// put back charreturn start(char);}
}// 用函数表示状态&#xff0c;用 if 表示状态的迁移关系&#xff0c;用 return 值表示下一个状态。

3.2、运行状态机

function emmitToken(type, value) {console.log(value);
}var input &#61; "1024 &#43; 2 * 256"var state &#61; start;for(var c of input.split(&#39;&#39;))state &#61; state(c);state(Symbol(&#39;EOF&#39;))// 输出结果
1024
&#43;
2
*
256

四、语法分析&#xff1a;LL


LL 语法分析根据每一个产生式来写一个函数。


4.1、写好函数名

function AdditiveExpression( ){}
function MultiplicativeExpression(){}

4.2、假设已经拿到 token

var tokens &#61; [{type:"Number",value: "1024"
}, {type:"&#43;",value: "&#43;"
}, {type:"Number",value: "2"
}, {type:"*",value: "*"
}, {type:"Number",value: "256"
}, {type:"EOF"
}];

4.3、AdditiveExpression处理

1、三种情况

<AdditiveExpression> ::&#61;<MultiplicativeExpression>|<AdditiveExpression><&#43;><MultiplicativeExpression>|<AdditiveExpression><-><MultiplicativeExpression>

2、AdditiveExpression 的写法

AdditiveExpression 的写法是根传入的节点&#xff0c;利用产生式合成新的节点。

function AdditiveExpression(source){if(source[0].type &#61;&#61;&#61; "MultiplicativeExpression") {let node &#61; {type:"AdditiveExpression",children:[source[0]]}source[0] &#61; node;return node;}if(source[0].type &#61;&#61;&#61; "AdditiveExpression" && source[1].type &#61;&#61;&#61; "&#43;") {let node &#61; {type:"AdditiveExpression",operator:"&#43;",children:[source.shift(), source.shift(), MultiplicativeExpression(source)]}source.unshift(node);}if(source[0].type &#61;&#61;&#61; "AdditiveExpression" && source[1].type &#61;&#61;&#61; "-") {let node &#61; {type:"AdditiveExpression",operator:"-",children:[source.shift(), source.shift(), MultiplicativeExpression(source)]}source.unshift(node);}
}

4.4、函数 Expression 处理

1、把解析好的 token 传给的顶层处理函数 Expression。

Expression(tokens);

2、如果 Expression 收到第一个 token&#xff0c;是个 Number&#xff0c;就需要对产生式的首项层层展开&#xff0c;根据所有可能性调用相应的处理函数&#xff0c;这个过程在编译原理中称为求 closure

function Expression(source){if(source[0].type &#61;&#61;&#61; "AdditiveExpression" && source[1] && source[1].type &#61;&#61;&#61; "EOF" ) {let node &#61; {type:"Expression",children:[source.shift(), source.shift()]}source.unshift(node);return node;}AdditiveExpression(source);return Expression(source);
}
function AdditiveExpression(source){if(source[0].type &#61;&#61;&#61; "MultiplicativeExpression") {let node &#61; {type:"AdditiveExpression",children:[source[0]]}source[0] &#61; node;return AdditiveExpression(source);}if(source[0].type &#61;&#61;&#61; "AdditiveExpression" && source[1] && source[1].type &#61;&#61;&#61; "&#43;") {let node &#61; {type:"AdditiveExpression",operator:"&#43;",children:[]}node.children.push(source.shift());node.children.push(source.shift());MultiplicativeExpression(source);node.children.push(source.shift());source.unshift(node);return AdditiveExpression(source);}if(source[0].type &#61;&#61;&#61; "AdditiveExpression" && source[1] && source[1].type &#61;&#61;&#61; "-") {let node &#61; {type:"AdditiveExpression",operator:"-",children:[]}node.children.push(source.shift());node.children.push(source.shift());MultiplicativeExpression(source);node.children.push(source.shift());source.unshift(node);return AdditiveExpression(source);}if(source[0].type &#61;&#61;&#61; "AdditiveExpression")return source[0];MultiplicativeExpression(source);return AdditiveExpression(source);
}
function MultiplicativeExpression(source){if(source[0].type &#61;&#61;&#61; "Number") {let node &#61; {type:"MultiplicativeExpression",children:[source[0]]}source[0] &#61; node;return MultiplicativeExpression(source);}if(source[0].type &#61;&#61;&#61; "MultiplicativeExpression" && source[1] && source[1].type &#61;&#61;&#61; "*") {let node &#61; {type:"MultiplicativeExpression",operator:"*",children:[]}node.children.push(source.shift());node.children.push(source.shift());node.children.push(source.shift());source.unshift(node);return MultiplicativeExpression(source);}if(source[0].type &#61;&#61;&#61; "MultiplicativeExpression"&& source[1] && source[1].type &#61;&#61;&#61; "/") {let node &#61; {type:"MultiplicativeExpression",operator:"/",children:[]}node.children.push(source.shift());node.children.push(source.shift());node.children.push(source.shift());source.unshift(node);return MultiplicativeExpression(source);}if(source[0].type &#61;&#61;&#61; "MultiplicativeExpression")return source[0];return MultiplicativeExpression(source);
};var source &#61; [{type:"Number",value: "3"
}, {type:"*",value: "*"
}, {type:"Number",value: "300"
}, {type:"&#43;",value: "&#43;"
}, {type:"Number",value: "2"
}, {type:"*",value: "*"
}, {type:"Number",value: "256"
}, {type:"EOF"
}];
var ast &#61; Expression(source);console.log(ast);
// 输出结果 children: Array(1) children: Array(3)还可以继续展开。。。
{type: "Expression",children: [{type: "AdditiveExpression",operator: "&#43;",children: [{type: "AdditiveExpression",children: Array(1)},{type: "&#43;",value: "&#43;"},{type: "MultiplicativeExpression",operator: "*",children: Array(3)}]},{type: "EOF"}]
}

五、解释执行


得到了 AST 之后&#xff0c;只需要对这个树做遍历操作执行即可。

// 根据不同的节点类型和其它信息&#xff0c;用 if 分别处理即可&#xff1a;
function evaluate(node) {if(node.type &#61;&#61;&#61; "Expression") {return evaluate(node.children[0])}if(node.type &#61;&#61;&#61; "AdditiveExpression") {if(node.operator &#61;&#61;&#61; &#39;-&#39;) {return evaluate(node.children[0]) - evaluate(node.children[2]);}if(node.operator &#61;&#61;&#61; &#39;&#43;&#39;) {return evaluate(node.children[0]) &#43; evaluate(node.children[2]);}return evaluate(node.children[0])}if(node.type &#61;&#61;&#61; "MultiplicativeExpression") {if(node.operator &#61;&#61;&#61; &#39;*&#39;) {return evaluate(node.children[0]) * evaluate(node.children[2]);}if(node.operator &#61;&#61;&#61; &#39;/&#39;) {return evaluate(node.children[0]) / evaluate(node.children[2]);}return evaluate(node.children[0])}if(node.type &#61;&#61;&#61; "Number") {return Number(node.value);}
}


推荐阅读
  • 实用正则表达式有哪些
    小编给大家分享一下实用正则表达式有哪些,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下 ... [详细]
  • Ihaveastringwithquotesaroundthepathasfollows:我在路径周围有一个带引号的字符串,如下所示:C:\ProgramFiles(x ... [详细]
  • 本文详细介绍了中央电视台电影频道的节目预告,并通过专业工具分析了其加载方式,确保用户能够获取最准确的电视节目信息。 ... [详细]
  • 2018-2019学年第六周《Java数据结构与算法》学习总结
    本文总结了2018-2019学年第六周在《Java数据结构与算法》课程中的学习内容,重点介绍了非线性数据结构——树的相关知识及其应用。 ... [详细]
  • 本文介绍如何使用 Angular 6 的 HttpClient 模块来获取 HTTP 响应头,包括代码示例和常见问题的解决方案。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 并发编程:深入理解设计原理与优化
    本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ... [详细]
  • 最近团队在部署DLP,作为一个技术人员对于黑盒看不到的地方还是充满了好奇心。多次咨询乙方人员DLP的算法原理是什么,他们都以商业秘密为由避而不谈,不得已只能自己查资料学习,于是有了下面的浅见。身为甲方,虽然不需要开发DLP产品,但是也有必要弄明白DLP基本的原理。俗话说工欲善其事必先利其器,只有在懂这个工具的原理之后才能更加灵活地使用这个工具,即使出现意外情况也能快速排错,越接近底层,越接近真相。根据DLP的实际用途,本文将DLP检测分为2部分,泄露关键字检测和近似重复文档检测。 ... [详细]
  • 本文介绍如何使用Perl编写一个简单的爬虫,从丁香园网站获取意大利的新冠病毒感染情况。通过LWP::UserAgent模块模拟浏览器访问并解析网页内容,最终提取所需数据。 ... [详细]
  • 本文详细介绍了如何在 Windows 环境下使用 node-gyp 工具进行 Node.js 本地扩展的编译和配置,涵盖从环境搭建到代码实现的全过程。 ... [详细]
  • 本文介绍了多个关于JavaScript的书籍资源、实用工具和编程实例,涵盖从入门到进阶的各个阶段,帮助读者全面提升JavaScript编程能力。 ... [详细]
  • 创建项目:Visual Studio Online 入门指南
    本文介绍如何使用微软的 Visual Studio Online(VSO)创建和管理开发项目。作为一款基于云计算的开发平台,VSO 提供了丰富的工具和服务,简化了项目的配置和部署流程。 ... [详细]
  • 解决Python中 'NoneType' 对象无属性 'find_all' 错误
    本文详细探讨了在Python编程中遇到的常见错误——'NoneType'对象没有属性'find_all',并深入分析其原因及解决方案。通过理解find_all函数的工作原理和常见用法,帮助读者避免类似问题。 ... [详细]
  • 本文介绍了如何利用 Spring Boot 和 Groovy 构建一个灵活且可扩展的动态计算引擎,以满足钱包应用中类似余额宝功能的推广需求。我们将探讨不同的设计方案,并最终选择最适合的技术栈来实现这一目标。 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
author-avatar
手机用户2502907057
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有