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


推荐阅读
  • 前言--页数多了以后需要指定到某一页(只做了功能,样式没有细调)html ... [详细]
  • 本文将介绍如何编写一些有趣的VBScript脚本,这些脚本可以在朋友之间进行无害的恶作剧。通过简单的代码示例,帮助您了解VBScript的基本语法和功能。 ... [详细]
  • 技术分享:从动态网站提取站点密钥的解决方案
    本文探讨了如何从动态网站中提取站点密钥,特别是针对验证码(reCAPTCHA)的处理方法。通过结合Selenium和requests库,提供了详细的代码示例和优化建议。 ... [详细]
  • 本文详细介绍了如何在Linux系统上安装和配置Smokeping,以实现对网络链路质量的实时监控。通过详细的步骤和必要的依赖包安装,确保用户能够顺利完成部署并优化其网络性能监控。 ... [详细]
  • 非公版RTX 3080显卡的革新与亮点
    本文深入探讨了图形显卡的进化历程,重点介绍了非公版RTX 3080显卡的技术特点和创新设计。 ... [详细]
  • PHP 编程疑难解析与知识点汇总
    本文详细解答了 PHP 编程中的常见问题,并提供了丰富的代码示例和解决方案,帮助开发者更好地理解和应用 PHP 知识。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • 资源推荐 | TensorFlow官方中文教程助力英语非母语者学习
    来源:机器之心。本文详细介绍了TensorFlow官方提供的中文版教程和指南,帮助开发者更好地理解和应用这一强大的开源机器学习平台。 ... [详细]
  • 1.如何在运行状态查看源代码?查看函数的源代码,我们通常会使用IDE来完成。比如在PyCharm中,你可以Ctrl+鼠标点击进入函数的源代码。那如果没有IDE呢?当我们想使用一个函 ... [详细]
  • 深入理解 SQL 视图、存储过程与事务
    本文详细介绍了SQL中的视图、存储过程和事务的概念及应用。视图为用户提供了一种灵活的数据查询方式,存储过程则封装了复杂的SQL逻辑,而事务确保了数据库操作的完整性和一致性。 ... [详细]
  • 本文详细介绍了 Dockerfile 的编写方法及其在网络配置中的应用,涵盖基础指令、镜像构建与发布流程,并深入探讨了 Docker 的默认网络、容器互联及自定义网络的实现。 ... [详细]
  • 数据库内核开发入门 | 搭建研发环境的初步指南
    本课程将带你从零开始,逐步掌握数据库内核开发的基础知识和实践技能,重点介绍如何搭建OceanBase的开发环境。 ... [详细]
  • 本文详细介绍了如何使用 Yii2 的 GridView 组件在列表页面实现数据的直接编辑功能。通过具体的代码示例和步骤,帮助开发者快速掌握这一实用技巧。 ... [详细]
  • 解决PHP与MySQL连接时出现500错误的方法
    本文详细探讨了当使用PHP连接MySQL数据库时遇到500内部服务器错误的多种解决方案,提供了详尽的操作步骤和专业建议。无论是初学者还是有经验的开发者,都能从中受益。 ... [详细]
  • 使用C#开发SQL Server存储过程的指南
    本文介绍如何利用C#在SQL Server中创建存储过程,涵盖背景、步骤和应用场景,旨在帮助开发者更好地理解和应用这一技术。 ... [详细]
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社区 版权所有