新聞中心
現(xiàn)在前端開(kāi)發(fā)中,我們常常會(huì)用到babel來(lái)編譯例如react、vue框架的代碼,以支持更多的(更古老的)瀏覽器,babel編譯代碼的過(guò)程就是編譯原理的應(yīng)用之一。

專注于為中小企業(yè)提供成都網(wǎng)站設(shè)計(jì)、成都做網(wǎng)站服務(wù),電腦端+手機(jī)端+微信端的三站合一,更高效的管理,為中小企業(yè)西固免費(fèi)做網(wǎng)站提供優(yōu)質(zhì)的服務(wù)。我們立足成都,凝聚了一批互聯(lián)網(wǎng)行業(yè)人才,有力地推動(dòng)了數(shù)千家企業(yè)的穩(wěn)健成長(zhǎng),幫助中小企業(yè)通過(guò)網(wǎng)站建設(shè)實(shí)現(xiàn)規(guī)模擴(kuò)充和轉(zhuǎn)變。
Babel is a JavaScript compiler!這是Babel官方對(duì)于babel的定義。身為前端工程師,因此有必要了解編譯原理,幸運(yùn)的是,“The Super Tiny Compiler”開(kāi)源項(xiàng)目利用JavaScript寫(xiě)了一個(gè)簡(jiǎn)單的編譯器。
麻雀雖小,五臟俱全,通過(guò)對(duì)該項(xiàng)目的學(xué)習(xí),一起加深對(duì)編譯過(guò)程的理解,以提升我們寫(xiě)出更高質(zhì)量的程序!
一、收益
通過(guò)掌握(了解)編譯原理,將有如下收益:
加深對(duì)編程語(yǔ)言的認(rèn)識(shí),無(wú)論何種編程語(yǔ)言,萬(wàn)變不離其宗
殊途同歸,有利于理解babel等轉(zhuǎn)譯器、eslint、prettier、less等工具的工作原理,可開(kāi)發(fā)相關(guān)插件
可以造更多輪子了
二、編譯過(guò)程概述
編譯過(guò)程的具體實(shí)現(xiàn)主要分為三步驟:
- 代碼解析(parse)
- 代碼轉(zhuǎn)換(Transformation)
- 代碼生成(Code Generation)
通過(guò)上述三步驟,可以將我們的原代碼,轉(zhuǎn)換(編譯)到目標(biāo)代碼,例如把javascript代碼轉(zhuǎn)換到python一樣。
編譯過(guò)程
“The Super Tiny Compiler”項(xiàng)目中是將LISP語(yǔ)言編譯為C語(yǔ)言,如下案例:
- * LISP(source) C(target)
- *
- * 2 + 2 (add 2 2) add(2, 2)
- * 4 - 2 (subtract 4 2) subtract(4, 2)
- * 2 + (4 - 2) (add 2 (subtract 4 2)) add(2, subtract(4, 2))
二、轉(zhuǎn)換過(guò)程
基于“The Super Tiny Compiler”項(xiàng)目,實(shí)現(xiàn)一個(gè)將LISP函數(shù)轉(zhuǎn)換到C語(yǔ)言函數(shù)編寫(xiě)形式!
- (源代碼)LISP CODE: (add 2 (subtract 4 2))
- (目標(biāo)代碼)C CODE: add(2, subtract(4, 2))
2.1 解析(Parse)
具象到具象的轉(zhuǎn)換是很難的,但抽象到具象的轉(zhuǎn)換往往是容易的。
解析的過(guò)程中包含了兩個(gè)關(guān)鍵步驟詞法分析(Lexical Analysis)和語(yǔ)法分析(Syntactic Analysis),解析就是一個(gè)具象到抽象的過(guò)程。
2.1.1 詞法分析
詞法分析的過(guò)程,主要是將原代碼(字符串),通過(guò)分詞的方式生成一個(gè)具有描述程序語(yǔ)義的token列表。
分詞的原理:逐個(gè)讀取源代碼中的字符,與預(yù)設(shè)的關(guān)鍵詞、字符串、數(shù)字、操作符等LISP語(yǔ)言定義的語(yǔ)法相關(guān)規(guī)則,轉(zhuǎn)換成 {type: 'xx', value: 'xx'} 的具有描述意義的形式
例如LISP:(add 2 (subtract 4 2)),經(jīng)過(guò)詞法分析會(huì)得到如下結(jié)果:
- [
- { type: 'paren', value: '(' },
- { type: 'name', value: 'add' },
- { type: 'number', value: '2' },
- { type: 'paren', value: '(' },
- { type: 'name', value: 'subtract' },
- { type: 'number', value: '4' },
- { type: 'number', value: '2' },
- { type: 'paren', value: ')' },
- { type: 'paren', value: ')' },
- ]
這樣一個(gè)列表(暫稱作:tokens列表)按照順序下來(lái)很好的描述了源代碼中的字符串和編程語(yǔ)義。
2.1.2 語(yǔ)法分析
詞法分析后得到的tokens列表已經(jīng)可以描述LISP的語(yǔ)法,但是還并不抽象,因?yàn)橹庇^看來(lái),我們無(wú)法解讀這個(gè)程序的意思,這就需要將其轉(zhuǎn)換為AST(Abstract Syntax Tree,抽象語(yǔ)法樹(shù))。
為什么要將其轉(zhuǎn)換到AST,AST能更好的描述源代碼的語(yǔ)義、描述結(jié)構(gòu)更加通用,tokens列表只是描述了“符號(hào)”的意義,可以將詞法分析過(guò)程看作是分類過(guò)程,而語(yǔ)法分析的過(guò)程,則是將符號(hào)組合,使其具有了執(zhí)行順序以及執(zhí)行規(guī)則的語(yǔ)法,抽象語(yǔ)法樹(shù),因?yàn)楦橄?,因而能更高效率轉(zhuǎn)換到其他形式。
操作LISP得到的AST能更好轉(zhuǎn)換到C語(yǔ)言的AST,因?yàn)樗麄兊腁ST結(jié)構(gòu)都是類似的,操作AST比tokens更容易。
語(yǔ)法分析的過(guò)程,將tokens列表轉(zhuǎn)化成為一個(gè)樹(shù)結(jié)構(gòu):
- {
- type: 'Program',
- body: [
- {
- type: 'CallExpression',
- name: 'add',
- params: [
- {
- type: 'NumberLiteral',
- value: '2',
- },
- {
- type: 'CallExpression',
- name: 'subtract',
- params: [
- {
- type: 'NumberLiteral',
- value: '4',
- },
- {
- type: 'NumberLiteral',
- value: '2',
- }
- ]
- }
- ]
- }
- ]
- }
2.2 代碼轉(zhuǎn)換(Transform)
代碼轉(zhuǎn)換的過(guò)程是將傳入的AST結(jié)構(gòu),通過(guò)在AST上例如增、刪、改屬性,將傳入AST轉(zhuǎn)換為C語(yǔ)言需要的標(biāo)準(zhǔn)AST結(jié)構(gòu)。
為了實(shí)現(xiàn)轉(zhuǎn)換,我們?cè)黾恿艘粋€(gè) traverser(ast, visitor) 函數(shù),這個(gè)函數(shù)接收parse過(guò)程得到的AST和visitor規(guī)則轉(zhuǎn)換對(duì)象。
visitor對(duì)象實(shí)際可理解為轉(zhuǎn)換規(guī)則,traverser函數(shù)在遍歷AST結(jié)構(gòu)時(shí),會(huì)根據(jù)visitor中定義的規(guī)則執(zhí)行轉(zhuǎn)換,用于生成新的符合C語(yǔ)言描述標(biāo)準(zhǔn)的AST結(jié)構(gòu)。
最后通過(guò) transform(ast)(預(yù)處理,定義visitor對(duì)象) -> traverser(ast, visitor) 過(guò)程,得到一個(gè)新的AST結(jié)構(gòu):
- {
- type: 'Program',
- body: [
- {
- type: 'ExpressionStatement',
- expression: {
- type: 'CallExpression',
- callee: {
- type: 'Identifier',
- name: 'add'
- },
- arguments: [
- {
- type: 'NumberLiteral',
- value: '2'
- },
- {
- type: 'CallExpression',
- callee: {
- type: 'Identifier',
- name: 'subtract'
- },
- arguments: [
- {
- type: 'NumberLiteral',
- value: '4'
- },
- {
- type: 'NumberLiteral',
- value: '2'
- }
- ]
- }
- }
- }
- ]
- }
2.3 生成目標(biāo)代碼(Code Generate)
最后通過(guò)解析符合C語(yǔ)言標(biāo)準(zhǔn)的AST,根據(jù)C語(yǔ)言的語(yǔ)法規(guī)則,轉(zhuǎn)換成為C語(yǔ)言格式
codeGenerator(newAst) 函數(shù)如下,接收新生成的AST結(jié)構(gòu):
- function codeGenerator(newAst) {
- switch (newAst.type) {
- case "Program":
- return newAst.body.map(codeGenerator).join("\n");
- case "ExpressionStatement":
- return codeGenerator(newAst.expression) + ";";
- case "CallExpression":
- return (
- codeGenerator(newAst.callee) +
- "(" +
- newAst.arguments.map(codeGenerator).join(", ") +
- ")"
- );
- case "Identifier":
- return newAst.name;
- case "NumberLiteral":
- return newAst.value;
- case "StringLiteral":
- return '"' + newAst.value + '"';
- default:
- throw new TypeError(newAst.type);
- }
- }
同時(shí)還做了代碼的部分格式化,比如空格、換行符添加等。
此時(shí)自然會(huì)思考下,VScode編輯器中的Prettier代碼格式化插件是不是也是這么做的?
四、總結(jié)
推薦大家完整閱讀一下“The Super Tiny Compiler”開(kāi)源項(xiàng)目,作者寫(xiě)的代碼注釋也非常詳細(xì)。編譯(轉(zhuǎn)譯)的過(guò)程原理基本類似,還有很多優(yōu)秀的項(xiàng)目,比如codeMirror、babel、esprima、acorn、recast都是值得閱讀的源碼,喜歡的小伙伴一定要去瞅瞅。
Reference
- 《Babel Docs》- https://babeljs.io/docs/en/
- 《the super tiny complier》- https://github.com/jamiebuilds/the-super-tiny-compiler/
網(wǎng)站題目:用JavaScript實(shí)現(xiàn)一個(gè)編譯器
轉(zhuǎn)載來(lái)源:http://m.fisionsoft.com.cn/article/dpsgdjj.html


咨詢
建站咨詢
