热门标签 | 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);}
}


推荐阅读
  • 如何优化Webpack打包后的代码分割
    本文介绍了如何通过优化Webpack的代码分割来减小打包后的文件大小。主要包括拆分业务逻辑代码和引入第三方包的代码、配置Webpack插件、异步代码的处理、代码分割重命名、配置vendors和cacheGroups等方面的内容。通过合理配置和优化,可以有效减小打包后的文件大小,提高应用的加载速度。 ... [详细]
  • 深入理解Java虚拟机的并发编程与性能优化
    本文主要介绍了Java内存模型与线程的相关概念,探讨了并发编程在服务端应用中的重要性。同时,介绍了Java语言和虚拟机提供的工具,帮助开发人员处理并发方面的问题,提高程序的并发能力和性能优化。文章指出,充分利用计算机处理器的能力和协调线程之间的并发操作是提高服务端程序性能的关键。 ... [详细]
  • 本文介绍了绕过WAF的XSS检测机制的方法,包括确定payload结构、测试和混淆。同时提出了一种构建XSS payload的方法,该payload与安全机制使用的正则表达式不匹配。通过清理用户输入、转义输出、使用文档对象模型(DOM)接收器和源、实施适当的跨域资源共享(CORS)策略和其他安全策略,可以有效阻止XSS漏洞。但是,WAF或自定义过滤器仍然被广泛使用来增加安全性。本文的方法可以绕过这种安全机制,构建与正则表达式不匹配的XSS payload。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 如何搭建Java开发环境并开发WinCE项目
    本文介绍了如何搭建Java开发环境并开发WinCE项目,包括搭建开发环境的步骤和获取SDK的几种方式。同时还解答了一些关于WinCE开发的常见问题。通过阅读本文,您将了解如何使用Java进行嵌入式开发,并能够顺利开发WinCE应用程序。 ... [详细]
  • Ihavethefollowingonhtml我在html上有以下内容<html><head><scriptsrc..3003_Tes ... [详细]
  • Java中包装类的设计原因以及操作方法
    本文主要介绍了Java中设计包装类的原因以及操作方法。在Java中,除了对象类型,还有八大基本类型,为了将基本类型转换成对象,Java引入了包装类。文章通过介绍包装类的定义和实现,解答了为什么需要包装类的问题,并提供了简单易用的操作方法。通过本文的学习,读者可以更好地理解和应用Java中的包装类。 ... [详细]
  • Python爬虫中使用正则表达式的方法和注意事项
    本文介绍了在Python爬虫中使用正则表达式的方法和注意事项。首先解释了爬虫的四个主要步骤,并强调了正则表达式在数据处理中的重要性。然后详细介绍了正则表达式的概念和用法,包括检索、替换和过滤文本的功能。同时提到了re模块是Python内置的用于处理正则表达式的模块,并给出了使用正则表达式时需要注意的特殊字符转义和原始字符串的用法。通过本文的学习,读者可以掌握在Python爬虫中使用正则表达式的技巧和方法。 ... [详细]
  • Android实战——jsoup实现网络爬虫,糗事百科项目的起步
    本文介绍了Android实战中使用jsoup实现网络爬虫的方法,以糗事百科项目为例。对于初学者来说,数据源的缺乏是做项目的最大烦恼之一。本文讲述了如何使用网络爬虫获取数据,并以糗事百科作为练手项目。同时,提到了使用jsoup需要结合前端基础知识,以及如果学过JS的话可以更轻松地使用该框架。 ... [详细]
  • java drools5_Java Drools5.1 规则流基础【示例】(中)
    五、规则文件及规则流EduInfoRule.drl:packagemyrules;importsample.Employ;ruleBachelorruleflow-group ... [详细]
  • 正则表达式及其范例
    为什么80%的码农都做不了架构师?一、前言部分控制台输入的字符串,编译成java字符串之后才送进内存,比如控制台打\, ... [详细]
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社区 版权所有