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

哈工大编译原理实验语法分析_前端要以正确的姿势学习编译原理(上篇)

正在找工作,有没有大佬带带我的?brambles:坐标深圳,贵司还缺前端吗?​zhuanlan.zhihu.c
3f01cd34bacc713409ace0c42060fa3f.png

正在找工作,有没有大佬带带我的?

brambles:坐标深圳,贵司还缺前端吗?​zhuanlan.zhihu.com
f39fa0fcfe2b3167896639e5fe4d80c7.png

前言

最近在我的 timline 上面出现了很多类似《前端为什么要学编译原理》这类文章以及《前端怎么学AST》这类的问题,但是却发现并没有人给大家介绍前端要如何以系统并且正确地学习编译原理,所以我就结合自己的经验以及走过的弯路来给大家分享点心得和经验,希望能让大家少走点弯路。

最后我并不是前端,只是恰好会写点 Javascript 而已。


目录

上篇:

  • 编译原理为什么难
  • 怎么学好编程语言
  • 代码到底是什么
  • 正则与上下文无关文法
  • 编程语言从 AST 才正式开始

下篇:

  • 静态分析
  • 类型推导
  • AST 的转换
  • Conitnuation
  • 字节码虚拟机

编译原理为什么难

大家提起编译原理第一反应都是很难,难到无从下手,但是为什么难呢?说白了,编译原理不就是研究把一门语言解析并且转换成另一门语言的技术吗?这项技术到底有哪些地方成为了阻碍呢?我认为这个最大的阻碍其实就是“编程语言”本身。

我相信在看这篇文章的朋友至少已经学会了 Javascript 了吧,但是我想多嘴问一句,大家真的懂 Javascript 吗?能描述出 Javascript 的语法规则吗?能理解语法所代指的逻辑结构吗?知道 Javascript 是如何在被解释和执行的吗?所以,大家真的懂 Javascript 吗?反正我是至今没有底气说自己”精通“ Javascript ,原因是我还不懂如何实现一个 JIT。

我们多数时候称自己“精通”某编程语言的时候,仅仅指会熟练使用某编程语言,但是编译原理这门学科折腾的核心恰恰是编程语言,它要求我们对编程语言有深入的了解,了解它是如何构造和解释的。我们如果没有这项基础其实是很难学好这门学科的。

推荐阅读:

  • 《七周七语言》——看看不同的编程语言都长什么样子
  • 编程语言 —— 维基百科
  • 编程范式 —— 维基百科

怎么学好编程语言

部分国外高校的计算机专业喜欢用 Lisp 系的 Scheme 入门,一开始我并不明白其中缘由,直到我发现他们的课程作业中最后总会要求实现一个简易的 Lisp 解释器时我才恍然大悟。外国学校安排课程的水平真是高明,学校教 Scheme 可不是为了让学生拿来写工程代码,而是让学生学习编程以及编程语言本身到底是一个什么东西。

Lisp 是一门具备现代编程语言特性的几乎最简的实现,所有编程语言都是 Lisp 方言真的不仅仅是一句玩笑话。简易的 Lisp 的解释难度很低,Lisp 语法的解析只有解析 JSON 同等的难度,我们会经常看到很多新手用百来行代码就能实现一个 Lisp 解释器。虽然实现一个 Lisp 解释器不难,但是他对学生来说的意义非常重大,它能让学生们对编程语言和程序的构造和执行有一个非常非常基础但又非常全面的认识。而这种对编程语言全面的认识,也正是我们这些拿着 C/C艹 亦或者 Javascript 入门的大家所缺失的。

所以如何学好编程语言?正途当然是啃我们的经典神书 《SICP》了,不过考虑到 《SICP》严重的教科书属性,讲地并不生动有趣,所以还是给大家推荐一个科普性质更强的书,叫做《计算的本质》大家可以用这本书先入门,如果学有余力或者非常有兴趣再去啃《SICP》。

推荐阅读:

  • 《计算的本质:深入剖析程序和计算机》—— 科普版 SICP
  • 《计算机程序的构造和解释》—— 经典神书,学有余力或者感兴趣再啃
  • 《微信小程序也要强行热更代码,鹅厂不服你来肛我呀》 —— 我自己写的文章,用 Javascript 实现一个 Javascript 解释器

代码到底是什么

上一节我们提过,Lisp 的解析难度和 JSON 是一样的,那我们能不能干脆用 JSON 代替代码呢?当然可以,Javascript 的解析后的语法树就是用 JSON 表示的。所以就表达能力来说,Javascript 的代码和 JSON 是没有差别的。那么问题来了,代码到底是什么?

其实代码跟 JSON 一样,是一种结构化的文本数据格式。在这里我们要仅仅抓着两个特点——“文本”和“结构化”。

代码的第一个特点是文本,那意味着我们所有对字符串的拼接、截取或者替换等所有操作,都可以应用在代码上面。很多程序员虽然都能对各类文本的读写了如指掌,但大家好像都没有意识到代码文件,也可以是那个可以读写、修改的文件之一。

对代码文件的读写和操作是进入编译世界的第一个重要门槛,有的时候并不需要太复杂的算法就能够对代码做一些有意义的转换,比如我们可以直接通过正则分析 import / export / require 来实现一个简易的 webpack,比如在我之前一篇文章也是通过简单的正则优化尾递归代码。真正有意识地把代码文件当成文本文件以后,我们就能把代码从此拉下“神坛”,可以让大家能够像思考文本一样思考代码。

代码的第二个特点则是结构化。不知道大家能不能理解,代码里面除了字面量意外,其他部分都只是标识结构而并不具有实际意义,赋予这些结构意义是解释器如何和执行这段代码。这个特点就是要求我们在看待代码的时候,要在脑中形成一种结构,而不再是一行一行的字符串。

var a = 123 // 除了字面量 123 外其他所有字符都是标识结构

比如上面这串简单的 Javascript 代码,var 这是一个抽象符号,他是 var 也好是 val 也好,就算是 #%$ 都没有任何问题,唯一的目的就是标识了这个结构(语句)是一个声明赋值。变量名 a 标识的是一种联系,这个 a 具体是什么也是无关紧要的,只要它所标识的联系不变,a 也是可以替换成任何字符。这里面唯一有实际意义的就是那个 123,我不能把它换成 456。

知乎之前有一个问题问为什么一些大佬能够在两个星期内学会一门编程语言,我的回答是两个星期都够我们造一门编程语言了,就像 Javascript 也就是 布兰登·艾克 大佬花了一个星期设计的。我虽然肯定不及这些大佬们,但是让我两个星期内拿 C艹 造一个 Javascript 1.0 还是没什么太大问题的。所以只要把文章到这里之前推荐的书好好看了,基础补上了,那么其实大家每个人都能轻松在两个星期内学好一门编程语言。

最后还是要提一下,能够用两个星期学好一门编程语言并不代表能用两个星期学好一个领域。就像你不能说你学会了 Javascript 就等于学会了前端,也不能说学会了 Python 就等于学会了人工智能(虽然现在很多坑爹培训班打着人工智能旗号教 Python 基础),编程语言仅仅是编程语言,仅仅是一个工具。

推荐阅读:

  • 《尾递归为啥能优化?》 —— 我写的文章,使用简单的字符处理来优化尾递归函数成循环

推荐工具:

  • AST Explorer —— 看看自己常写的 Javascript 长什么样子

正则与上下文无关文法

这篇文章到这里已经是第四个小节了,但直到这里才算能够正式抱起我们的经典教材——龙书、虎书或者鲸鱼书进行学习了。这一节简单介绍一下编译器前端技术 —— Parser。

编译器前段就在干一件事,把代码这个结构化的文本文件解析成我们计算机可以理解的数据结构 —— 抽象语法树(AST)。解析代码是一个比较无聊、复杂而又繁琐的过程。这种复杂和繁琐是来由于编程语言本身语法设计的繁琐和复杂导致的。比如我们前文讨论过的 Lisp 由于语法设计的非常简单、一致而又无歧义,所以解析起来非常轻松,但是作为代价的就是 Lisp 那个被吐槽很多的括号括号括号。

解析代码一般分成两个步骤,第一个步骤是词法分析,将文本的代码转化成一个个 Token。看到这里的大家应该都有一些正则表达式的基础吧,在解析代码的过程中,我们需要用正则来分词做词法分析。在编译原理面我们学习正则的时候就不仅仅是学习正则表达式了,也会学习正则的内核 DFA,不过这部分难度不大就是了。

解析代码的第二个步骤是语法分析,语法分析是将我们上面词法分析出的 Token 转化成 AST。语法分析我们要学习上下文无关文法(CFG),并且可以用 BNF 这个表示。CFG 比正则表达能力更强,强在 CFG 能表达递归结构,常见的递归结构有表达式和代码块。在语法分析这个部分,会基本的 LL(1) 算法,能够对自顶向下的分析有足够的了解,就已经足够了。

无论是正则还是 CFG,他们都是在用一种形式语言(我们的编程语言也是一种形式语言),来描述一种抽象结构,所以在学习的过程中,脑子里面一定要这种从抽象结构的概念,能够事半功倍。

Parser 在编译原理里面是难点但却不是重点,所以在这一部分大家觉得复杂的算法完全可以跳过,不建议浪费太多时间。Parser 都是可以根据正则和 CFG 自动生成的,并不需要自己手写。所以这部分主要目的是学好的是正则和 CFG,那些复杂的算法学起来意义很小。

最后还有一个非常有趣的现象,正则表达式是上下文无关文法,而 BNF 却又是正则文法,大家可以想想为什么?

推荐阅读:

  • 对 Parser 的误解 —— 我自己的真正入门的文章
  • 龙书、虎书或者鲸鱼书任选 —— 经典编译原理经典教材
  • 当然我在扯淡 —— 垠神的博客,看的时候请自动屏蔽垠神的主观自嗨

推荐工具:

  • Acorn —— Javascript Parser
  • REGEXPER —— 将正则表达式以图形的形式展示
  • Ohm —— 可以可视化的 BNF 编辑器
  • Jison —— 用正则和 BNF 构建通用的 Parser

编程语言从 AST 才正式开始

其实在大多数眼里的编译原理,都停留在 Parser 这个阶段,因为大部分人都在学习的时候卡在了个这个阶段。但是事实上 Parser 不过是这个领域最表面的一层技术而已。编程语言从 AST 才算正是开始,只有到了 AST 的阶段,我们的计算机才可以对我们的编程语言进行包括分析、解释或者翻译,而我们前面我们所辛辛苦苦写的代码只不过是给我们这些愚蠢的人类看的罢了。

对编程语言 AST 的分析、转换、解释以及翻译理应是编译原理中最重要的一个部分,但由于我们经典编译原理书出版时间都比较早(1985年),并且也只着眼于当时流行的以 C 为主的编译型语言,所以它的重点都放在了解析代码和生成汇编两个部分。但是以现在的编程语言角度来看的话,前端有 Parser Generator,后端有 LLVM 那么我们更多的重点其实应该跟多地放在中端上来。

不过到这里为止,我们介绍的内容其实已经足够大部分小伙伴给自己写个 DSL,给自己写一个编译到 Javascript 的小语言玩了。 但是这足够了吗?我们到底可以对 AST 做些什么呢?让我们下篇再见吧。

参考项目:

  • bramblex/blx-fsm —— 自己造的用于描述 FSM 的玩具 DSL
  • bramblex/Smooth —— 自己造的玩具小语言

Hello World!




推荐阅读
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • 如何查询zone下的表的信息
    本文介绍了如何通过TcaplusDB知识库查询zone下的表的信息。包括请求地址、GET请求参数说明、返回参数说明等内容。通过curl方法发起请求,并提供了请求示例。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • 本文介绍了如何使用php限制数据库插入的条数并显示每次插入数据库之间的数据数目,以及避免重复提交的方法。同时还介绍了如何限制某一个数据库用户的并发连接数,以及设置数据库的连接数和连接超时时间的方法。最后提供了一些关于浏览器在线用户数和数据库连接数量比例的参考值。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 本文介绍了如何使用JSONObiect和Gson相关方法实现json数据与kotlin对象的相互转换。首先解释了JSON的概念和数据格式,然后详细介绍了相关API,包括JSONObject和Gson的使用方法。接着讲解了如何将json格式的字符串转换为kotlin对象或List,以及如何将kotlin对象转换为json字符串。最后提到了使用Map封装json对象的特殊情况。文章还对JSON和XML进行了比较,指出了JSON的优势和缺点。 ... [详细]
  • python3 nmap函数简介及使用方法
    本文介绍了python3 nmap函数的简介及使用方法,python-nmap是一个使用nmap进行端口扫描的python库,它可以生成nmap扫描报告,并帮助系统管理员进行自动化扫描任务和生成报告。同时,它也支持nmap脚本输出。文章详细介绍了python-nmap的几个py文件的功能和用途,包括__init__.py、nmap.py和test.py。__init__.py主要导入基本信息,nmap.py用于调用nmap的功能进行扫描,test.py用于测试是否可以利用nmap的扫描功能。 ... [详细]
author-avatar
Chinaexpoinfo
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有