這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
原文 http://www.oschina.net/translate/build-an-interpreted-language-in-go
英文原文:Part 1: Let’s build an interpreted language in Go!
我目前正參與我們的一個大項目,Alloy。Alloy 是一種編譯型的程式設計語言。我目前在電腦及編程領域最喜歡的一個愛好就是語言。事實上,我認為每個程式員都應該對程式設計語言是如何工作的有個基本的瞭解,這就是我寫這個系列的原因。 這是系列文章中的第一篇文章。該系列將描述我已經寫過的代碼,來向你展示如何製作自己的程式設計語言。這裡注意一下,本文假設你對編譯器/解譯器的理論/實踐有已有很少或沒有過往經驗。還有要注意的是,這一系列的文章不是介紹編程或Go編程的。 |
Garfielt 翻譯於 1周前1人頂 頂 翻譯的不錯哦! |
什麼是解譯器(interpreter)?解譯器會直接執行或表現寫在某特定指令碼語言中的指令。這可以是一種已存在的指令碼語言,像 Python或者 Ruby。它也可以是一種你自己創造的指令碼語言,這將是我們在這裡要做的。這一系列將從Go的基礎開始指導你實現自己的指令碼語言/解譯器“玩具”。 為什麼是“玩具”指令碼語言/解譯器?解譯器可以是極其複雜的。現代解譯器(比如Ruby或Python)十分龐大,包括成百上千行,甚至多達百萬的代碼量。這對一個新手來說不太容易理解。玩具語言是個更為簡化的版本,它們常常跳過或者省去一些短語(在這裡我們將不考慮最佳化)。製造一種玩具語言是一個理解它們如何工作的有效方法,當開始使用它們時,它們將實實在在地協助你理解,即使你不是在一個已經存在的解譯器(如Rust)上工作。 |
Ann_mf 翻譯於 7天前1人頂 頂 翻譯的不錯哦! |
程式設計語言你可以用任意一種你喜歡的語言構建一個解譯器。在這個案例中,我將使用Go。這之前我還沒寫過許多Go,所以對我來說這也是一個學習的經曆!然而如果你不習慣用Go寫,你可以用如下任一種語言製作你的解譯器,可以是 C,Java,或者甚至是 JavaScript。 小結由於在當今世界有如此多的解譯器和編譯器,因此有許多工具可以來協助你製作它們。你需要決定是否考慮偷偷使用一個外部工具,或者你想要自己寫所有的代碼。我更喜歡後者,因為我覺得如果我使用某個外部工具來代勞,我就學不會它如何工作。不過這完全取決於你自己。在解譯器環境中,你是否使用這些工具會在編譯器/解譯器社區引起非常強烈的爭論。一些人會告訴你如果你不用ANTLR,BISON或者其它一些工具那麼你會出錯。另一些人會說完成它的唯一方法就是親手寫你自己的詞彙分析器(lexer)和文法分析器(parser)。最後,這是你的選擇,但在這一系列文章中,我會至少會涵蓋如何構建詞彙分析器(Lexer)和文法分析器(Parser)。 |
Ann_mf 翻譯於 7天前1人頂 頂 翻譯的不錯哦! |
理論在深入之前,我們需要講解一下理論。 什麼是詞彙分析器和文法分析器如果你看到這一段落,並困惑於我所指的詞彙分析器和文法分析器,那麼不用擔心。典型做法是把這個分析過程分成不同的階段。有些階段是可選的,換句話叫做最佳化階段。但是大部分現代解析器幾乎處理所有階段。讓我們深入去看看這些階段吧。 詞法分析第一階段是文法分析,基本上就是一個分詞器。詞法分析、解析器或者文法分析把字元或者輸入資料流分割成標記。這些標記以列表或者容器等資料結構存成標記流。解析器通過歸類這些詞(輸入資料流中的符號字串),給於特定的標記某種含義。例如,*,=,+等詞可以歸為操作符,tost 和bacon可以歸為字串常亮,而’a‘和’b‘則是字元。 |
開心613 翻譯於 7天前2人頂 頂 翻譯的不錯哦! |
解析解析器是一個翻譯組件,它用來接收資料的輸入,一個詞法分析器產生令牌列表,併產生一個運算式,通常是一種抽象的文法樹,或其他結構。解譯器遵循的規則被叫做文法,它是你定義的一種語言的方式,這些文法諸如Extended Backus- Naur Form (EBNF)和 BNF (Backus-Naur Form),它們被用來描述一種語言的文法。下面是一個被寫成EBNF文法的例子: letter = "A" | "a" | ... "Z" | "z" | "_";digit = { "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" };identifier = letter { letter | digit }; 這可能對你沒有任何意義。你可能認識這些在程式設計語言中的符號,例如管道|,花括弧{}。所有的符號都有特殊的含義: { } - denotes repetition | - denotes an option, similar to OR[...] - optional terminal/nonterminal; - termination = - definition ... - sequence"..." - terminal string |
無若 翻譯於 7天前1人頂 頂 翻譯的不錯哦! |
我們在後面將著眼於更多的一些符號. 上面的樣本定義了一個“生產規則”. 一個生產規則可能包含兩個詞彙元素: 非終端和終端. 終端是不能使用文法規則不能被改變的文字. 非終端則是可以被替換的符號, 可以把它看做是一個預留位置或者一個變數. 它們有時會被稱為“文法變數”. 在上面的樣本中, 標識符,字母和數字都是非終端符號. 而 "Z", "0", "1", 都是終端符號的例子,它們是常量字元,也就是說它們不能被改變. 現在來看,上面的文法中所有的符號都意味著什麼呢? 一個字母的定義是: letter = "A" | "a" | ... "Z" | "z" | "_"; 為了能夠理解,要像讀英語一樣閱讀它,例如,上面的文法被讀作 “A” 或者 “a” 到 “Z” 或者 “z” 或者 “_”. 因此一個字母可以是任何從 a-Z 或者一個底線的東西. |
LeoXu 翻譯於 6天前1人頂 頂 翻譯的不錯哦! |
我們如此定義一個數字: digit = { "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" }; 這意味著一個數字可以是 “0” 或者 “1” 或者 “2” … 你理解到點子上了. 不過, 要注意這裡的大括弧. 如果你還記得我們在上面提供的列表, 括弧代表了重複, 指定重複 0 到 n 次, 這裡的n可以是任何一個數字. 這就意味著一個數字可以是 0 - 9 總共重複 n 多次, 因此 123 是對的, 5123 也是對的. 最後是標識符: identifier = letter { letter | digit }; 目前我們理解了字母(letter)和數字(digit)的意思, 我們現在就能夠理解這個小的生產規則. 基本上,一個標識符必須以一個字元開頭,其後可以是0個或者更多個不同字母或者數位重複. 例如,a_, a_a, a_1, a__, 等等都是正確的標識符. |
LeoXu 翻譯於 6天前1人頂 頂 翻譯的不錯哦! |
這兩個階段的詞法和文法解析通常指的是作為前端的編譯器和解譯器。現在,讓我們開始寫一些代碼,我將使用GO來編寫。所有的原始碼將公布在我的 Github頁面上。如果你接著使用GO來編寫,首先為你的項目建立一個新的目錄並且設定好你的main go檔案。剛好,我編寫了一個簡單的hello world檔案來進行測試。GO擁有一個神奇的工作空間系統,因此一開始,你就需要建立你的工作空間,我一直使用Linux來作為我的工作空間,因此我使用GO設定$HOME/go 的環境變數 。為方便起見,GO推薦我們增加這個設定到達我們的路徑: mkdir $HOME/go export PATH=$PATH:$GOPATH/bin 我的項目的基本路徑是在 github.com/felixangell。 你可以找到你想要的,或者你的 github 使用者名稱: mkdir -p $GOPATH/src/github.com/yourusername |
何傳友 翻譯於 7天前1人頂 頂 翻譯的不錯哦! |
現在開始設定我們的解譯器程式,我們在個人目錄下建立一個檔案夾,名字可以是你給這個解譯器起的任何名字,我叫它 vident。我們進入這個目錄。 ?
12 |
mkdir $GOPATH /src/github .com /felixangell/vident cd $GOPATH /src/github .com /felixangell/vident |
然後我們建立一個簡單的檔案作為測試用,可以直接拷貝這一部分: ?
1234567 |
package main import "fmt" func main() { fmt.Printf( "hello, world\n" ); } |
把他儲存到我們剛剛建立的檔案夾 vident 中,名字為 main.go。現在我們編譯並運行它: ?
因為我們正在用工程目錄結構系統,我們需要添加 bin 目錄到我們的目錄,然後簡單的運行上面的代碼。當你運行時,你應該可以看到輸出了“hello, world”。 |
澀狼 翻譯於 6天前1人頂 頂 翻譯的不錯哦! |
那麼接下來我們要定義我們的語言。Vident 是一門簡單的語言,我們從一些小的特性入手,然後我們再轉移到複雜的樣本。下面是 Vident 的一個代碼執行個體: let x = 5 + 5print: x, "hello", x 我需要把->改為:,否則熟悉 Tumblr 格式的人對它很多抱怨,抱歉!我們語言的 EBNF 文法: letter = "A" | "a" | ... "Z" | "z" | "_";digit = { "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" };identifier = letter { letter | digit }; number_literal = digit | [ "." digit ];string_literal = """ letter { letter } """;char_literal = "'" letter "'";literal = number_literal | string_literal | char_literal;binaryOp = "+" | "-" | "/" | "*";binary_expr = expression binaryOp expression;expression = binary_expr | function_call | identifier | literal;let_stat = "let" identifier [ "=" expression ];arguments = { expression "," };function_call = identifier [ ":" arguments ];statement = let_stat | function_call; 目前我們已經為這門語言引入了一些東西,最明顯的是方括弧。方括弧表示一個可選值,例如: let_stat = "let" identifier [ "=" expression ]; 這代表 let x 和 let x = 5 + 5 都是有效,第一個是一個定義,比如定義變數,第二是顯示的變數聲明,即定義變數並聲明值。 |
Garfielt 翻譯於 5天前1人頂 頂 翻譯的不錯哦! |
現在看上面的文法可能會有點複雜,但如果你一點點的靠近去理解它,它就會比你想象的更加簡單. 注意,我們不會一下就全部實現它,而是按階段分部分去著重於文法的每一個部分並進行實現!
不管怎麼樣,如上就是第一部分! 敬請關注接下來的章節,我們將會編寫詞法分析器,而我們也會討論更多有關解譯器後端的內容.