转自:https://zhuanlan.zhihu.com/p/36301857前端
最近在个人 timline 上面出现了不少相似《前端为何要学编译原理》这类文章以及《前端怎么学AST》这类的问题,可是却发现并无人给你们介绍前端要如何以系统而且正确地学习编译原理,因此我就结合本身的经验以及走过的弯路来给你们分享点心得和经验,但愿能让你们少走点弯路。webpack
最后我并非前端,只是刚好会写点 JavaScript 而已。git
上篇:程序员
下篇:github
你们提起编译原理第一反应都是很难,难到无从下手,可是为何难呢?说白了,编译原理不就是研究把一门语言解析而且转换成另外一门语言的技术吗?这项技术到底有哪些地方成为了阻碍呢?我认为这个最大的阻碍其实就是“编程语言”自己。web
我相信在看这篇文章的朋友至少已经学会了 JavaScript 了吧,可是我想多嘴问一句,你们真的懂 JavaScript 吗?能描述出 JavaScript 的语法规则吗?能理解语法所代指的逻辑结构吗?知道 JavaScript 是如何在被解释和执行的吗?因此,你们真的懂 JavaScript 吗?反正我是至今没有底气说本身”精通“ JavaScript ,缘由是我还不懂如何实现一个 JIT。正则表达式
咱们多数时候称本身“精通”某编程语言的时候,仅仅指会熟练使用某编程语言,可是编译原理这门学科折腾的核心偏偏是编程语言,它要求咱们对编程语言有深刻的了解,了解它是如何构造和解释的。咱们若是没有这项基础实际上是很难学好这门学科的。算法
推荐阅读:编程
部分国外高校的计算机专业喜欢用 Lisp 系的 Scheme 入门,一开始我并不明白其中原因,直到我发现他们的课程做业中最后总会要求实现一个简易的 Lisp 解释器时我才恍然大悟。外国学校安排课程的水平真是高明,学校教 Scheme 可不是为了让学生拿来写工程代码,而是让学生学习编程以及编程语言自己究竟是一个什么东西。小程序
Lisp 是一门具有现代编程语言特性的几乎最简的实现,全部编程语言都是 Lisp 方言真的不只仅是一句玩笑话。简易的 Lisp 的解释难度很低,Lisp 语法的解析只有解析 JSON 同等的难度,咱们会常常看到不少新手用百来行代码就能实现一个 Lisp 解释器。虽然实现一个 Lisp 解释器不难,可是他对学生来讲的意义很是重大,它能让学生们对编程语言和程序的构造和执行有一个很是很是基础但又很是全面的认识。而这种对编程语言全面的认识,也正是咱们这些拿着 C/C艹 亦或者 JavaScript 入门的你们所缺失的。
因此如何学好编程语言?正途固然是啃咱们的经典神书 《SICP》了,不过考虑到 《SICP》严重的教科书属性,讲地并不生动有趣,因此仍是给你们推荐一个科普性质更强的书,叫作《计算的本质》你们能够用这本书先入门,若是学有余力或者很是有兴趣再去啃《SICP》。
推荐阅读:
上一节咱们提过,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 基础),编程语言仅仅是编程语言,仅仅是一个工具。
推荐阅读:
推荐工具:
这篇文章到这里已是第四个小节了,但直到这里才算可以正式抱起咱们的经典教材——龙书、虎书或者鲸鱼书进行学习了。这一节简单介绍一下编译器前端技术 —— Parser。
编译器前段就在干一件事,把代码这个结构化的文本文件解析成咱们计算机能够理解的数据结构 —— 抽象语法树(AST)。解析代码是一个比较无聊、复杂而又繁琐的过程。这种复杂和繁琐是来因为编程语言自己语法设计的繁琐和复杂致使的。好比咱们前文讨论过的 Lisp 因为语法设计的很是简单、一致而又无歧义,因此解析起来很是轻松,可是做为代价的就是 Lisp 那个被吐槽不少的括号括号括号。
解析代码通常分红两个步骤,第一个步骤是词法分析,将文本的代码转化成一个个 Token。看到这里的你们应该都有一些正则表达式的基础吧,在解析代码的过程当中,咱们须要用正则来分词作词法分析。在编译原理面咱们学习正则的时候就不只仅是学习正则表达式了,也会学习正则的内核 DFA,不过这部分难度不大就是了。
解析代码的第二个步骤是语法分析,语法分析是将咱们上面词法分析出的 Token 转化成 AST。语法分析咱们要学习上下文无关文法(CFG),而且能够用 BNF 这个表示。CFG 比正则表达能力更强,强在 CFG 能表达递归结构,常见的递归结构有表达式和代码块。在语法分析这个部分,会基本的 LL(1) 算法,可以对自顶向下的分析有足够的了解,就已经足够了。
不管是正则仍是 CFG,他们都是在用一种形式语言(咱们的编程语言也是一种形式语言),来描述一种抽象结构,因此在学习的过程当中,脑子里面必定要这种从抽象结构的概念,可以事半功倍。
Parser 在编译原理里面是难点但却不是重点,因此在这一部分你们以为复杂的算法彻底能够跳过,不建议浪费太多时间。Parser 都是能够根据正则和 CFG 自动生成的,并不须要本身手写。因此这部分主要目的是学好的是正则和 CFG,那些复杂的算法学起来意义很小。
最后还有一个很是有趣的现象,正则表达式是上下文无关文法,而 BNF 却又是正则文法,你们能够想一想为何?
推荐阅读:
推荐工具:
其实在大多数眼里的编译原理,都停留在 Parser 这个阶段,由于大部分人都在学习的时候卡在了个这个阶段。可是事实上 Parser 不过是这个领域最表面的一层技术而已。编程语言从 AST 才算正是开始,只有到了 AST 的阶段,咱们的计算机才能够对咱们的编程语言进行包括分析、解释或者翻译,而咱们前面咱们所辛辛苦苦写的代码只不过是给咱们这些愚蠢的人类看的罢了。
对编程语言 AST 的分析、转换、解释以及翻译理应是编译原理中最重要的一个部分,但因为咱们经典编译原理书出版时间都比较早(1985年),而且也只着眼于当时流行的以 C 为主的编译型语言,因此它的重点都放在了解析代码和生成汇编两个部分。可是以如今的编程语言角度来看的话,前端有 Parser Generator,后端有 LLVM 那么咱们更多的重点其实应该跟多地放在中端上来。
不过到这里为止,咱们介绍的内容其实已经足够大部分小伙伴给本身写个 DSL,给本身写一个编译到 JavaScript 的小语言玩了。 可是这足够了吗?咱们到底能够对 AST 作些什么呢?让咱们下篇再见吧。
参考项目: