├── .editorconfig
├── .gitignore
├── Makefile
├── docs
├── 1.md
├── 2-1.md
├── 2-2.md
├── 3-1.md
├── 3-2.md
├── 3-3.md
├── 4.md
├── 5.md
├── 6-1.md
├── 6-2.md
├── 7.md
├── README.md
├── SUMMARY.md
├── book.json
└── images
│ ├── 2-1-1.png
│ ├── 2-1-2.png
│ ├── 2-1-3.png
│ ├── 3-1-1.png
│ ├── 3-1-2.png
│ ├── 3-1-3.png
│ ├── 3-1-4.png
│ ├── 3-1-5.gif
│ ├── 3-2-1.png
│ ├── 3-2-2.png
│ ├── 3-3-1.png
│ ├── 3-3-10.png
│ ├── 3-3-11.png
│ ├── 3-3-2.png
│ ├── 3-3-3.png
│ ├── 3-3-4.png
│ ├── 3-3-5.png
│ ├── 3-3-6.png
│ ├── 3-3-7.png
│ ├── 3-3-8.png
│ ├── 3-3-9.png
│ └── 5-1.png
├── examples
├── 2-1-1.html
├── 2-1-2.html
├── 2-2-1.html
├── 2-2-2.html
├── 3-1-1.html
├── 3-1-2.html
├── 3-1-3.html
├── 3-2-1.html
├── 3-2-2.html
├── 4-1.html
├── 4-2.html
├── 4-3.html
├── 4-4.html
├── 4-5.html
├── 7-1.html
├── 7-2.html
├── 7-3.html
└── step.html
├── package.json
├── src
├── Errors.js
├── Nodes
│ ├── ExpressionBlockNode.js
│ ├── IntNode.js
│ ├── Node.js
│ ├── PrintNode.js
│ └── VariableNode.js
├── Parser.js
├── Reader.js
├── Scanner.js
├── Token.js
├── example.ws
└── index.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*.js]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | trim_trailing_whitespace = true
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | _book/
2 | build/
3 | public/
4 | node_modules/
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | .PHONY: serve
2 |
3 | serve:
4 | docker run -d --rm -v ${PWD}:/gitbook -p 4000:4000 jaceju/gitbook serve docs
5 |
6 | build:
7 | docker run --rm -v ${PWD}:/gitbook jaceju/gitbook install docs
8 | docker run --rm -v ${PWD}:/gitbook jaceju/gitbook build docs ./build
9 | cp -R build/* public/
10 | rm -rf build
11 | cd public/ && git add . && git commit -m "Update" && git push && cd ../
12 |
--------------------------------------------------------------------------------
/docs/1.md:
--------------------------------------------------------------------------------
1 | # 一、簡介
2 |
3 | 相信每個 programmer 都跟西杰一樣想過設計一種自己的編程語言,最近西杰就有機會要寫一個編譯器了。雖然在大學時已經讀過如何編寫一個編譯器,但要認真寫起上來還真的不容易,而且網上教寫編譯器的教材不多(尤其中文的),所以就把這次經驗記下來,疏理一下自己在開發過程中所學到的東西,也同時為互聯網增加一些有關編譯器這方面的中文資源吧。
4 |
5 | 西杰在開發過程中經常參考 Actionscript 編譯器的 source code(用 Java 寫的),大家有興趣可以看看這裡(在 `/trunk/modules/asc` 裡), 是 open source 的。
6 |
7 | 在這個教程中,西杰將會使用 JavaScript 來開發,原因有二。第一,JS 是我最喜愛的編程語言之一,語法簡潔易明,亦較多人認識。第二,可以讓大家在瀏覽器直接運行 Demo,大家不用浪費時間下載本文所舉的例子再執行。
8 |
9 | 整個教程將會分為七個主要單元,除了這篇簡介外,還包括以下六個單元。
10 |
11 | 二、詞法分析(Lexical analysis):把字元合併成為詞語
12 |
13 | 三、語法分析(Syntactic analysis):把詞語組合成一句有意思的句子
14 |
15 | 四、語意分析(Semantic analysis):把句子組成有上文下理的段落,成為有意思的故事。西杰認為這個單元和第三個單元最難,大家要有心理準備
16 |
17 | 五、虛擬機(Virtual Machine):用來運行編譯好的程式
18 |
19 | 六、生成代碼(Code Generation):把你閱讀完的故事寫出來給虛擬機看
20 |
21 | 七、優化器(Optimizer):可以把故事說得簡單一點
22 |
23 | 好了,那麼我們開始吧,先來看看我們即將開發的語言 ﹣ Wescript (音類似 Westkit,不過要翹舌)的特徵:
24 |
25 | * 兩種變數類型(variable type): `bool`, `int`
26 | * 兩種控制結構(Control structure): `if/else`, `while`
27 | * 註釋(Comment): `// 單行`, `/* 多行 */`
28 | * 運算符(Operator): `+`, `-`, `*`, `/`, `%`, `(`, `)`, `&&`, `||`, `!`, `==`, `!=`, `=`, `+=`, `++`, `-=`, `–`
29 | * Static scoping , `bool` 不能與 `int` 比較,忽略空白符號。
30 |
31 | 例子:
32 |
33 | ```js
34 | /*
35 | Wescript
36 | */
37 | var a:int = 1;
38 | var b:int = 2;
39 | var c:bool = true;
40 | if (c){
41 | print a;
42 | }else{
43 | print b;
44 | }
45 | var i:int = 0;
46 | while (i < 10){
47 | print i;
48 | i++;
49 | }
50 | //WoW
51 | ```
52 |
53 | 就是這樣了,下一章就會開始做 Scanner 。
54 |
--------------------------------------------------------------------------------
/docs/2-1.md:
--------------------------------------------------------------------------------
1 | # 二、掃瞄器(Scanner)﹣詞法分析(Lexical analysis)(上)
2 |
3 | 寫 Compiler 第一步通常都是先寫 Scanner,什麼是 Scanner 呢?這裡只給你初步概念,詳細解釋在維基看吧。試想像有一句英文句子(例子: `”The quick brown fox jumps over the lazy dog” is an English-language pangram.`),人類看英文的方法就是逐個逐個詞語地看,電腦怎樣才能知道要跳過 `”` 雙引號才能讀取第一個詞語呢?那就是要靠 Scanner 來分析了, Scanner 會逐個逐個字元讀進來並且在 “適當時候” 把字元合成一組詞語供後邊的 Parser 做其他處理工作。
4 |
5 | 
6 | 
7 |
8 | 單字元的 Token
9 |
10 | 先來處理比較簡單的單字元 Token 吧,在這裡要先界定一下什麼是單字元 Token(這只是西傑的定義),單字元 Token 的意思是這個 Token 只有一個字元而且不會因後面的字元而有任何歧義,例如 `“:”` 或者 `“;”` 就是了。 `”+”` 是不是單字元 Token 呢?不是,因為 `“+”` 是會有歧義的,它可能是代表 `1 + 1` 中的相加意思,亦可能代表 `i ++` 中加 1 的意思,所以它不是單字元 Token ,而多字元 Token 會在下一節才處理。
11 |
12 | 現在我們要列出所有單字元 Token 。
13 |
14 | ```
15 | : COLON_TOKEN
16 |
17 | ; SEMICOLON_TOKEN
18 |
19 | ( LEFTPAREN_TOKEN
20 |
21 | ) RIGHTPAREN_TOKEN
22 |
23 | { LEFTBRACE_TOKEN
24 |
25 | } RIGHTBRACE_TOKEN
26 |
27 | % MOD_TOKEN
28 | ```
29 |
30 | 就這七款了嗎?其實還有一個是 `EOS_TOKEN` ,代表 `end of stream` ,即已經沒有東西可以讀了,用來終止 Scanner 再讀。
31 |
32 | ## Reader
33 |
34 | 現在要開始寫一個 Reader , Reader 的工作主要是用來逐個逐個字元讀進來,但亦可以退回一個字元下次再讀(這個功能在讀取多字元 Token 會有用),看看 code 吧。
35 |
36 | ```js
37 | //Reader class
38 | //str is the data to be read
39 |
40 | function Reader(str){
41 | this.data = str;
42 | this.currPos = 0;
43 | this.dataLength = str.length;
44 |
45 | }
46 |
47 | Reader.prototype.nextChar = function (){
48 | if (this.currPos >= this.dataLength){
49 | return -1; //end of stream
50 | }
51 |
52 | return this.data[this.currPos++];
53 | }
54 |
55 | //n is the number of characters to be retracted
56 | Reader.prototype.retract = function (n){
57 |
58 | if (n == undefined){
59 | n = 1;
60 | }
61 |
62 | this.currPos -= n;
63 |
64 | if (this.currPos < 0){
65 | this.currPos = 0;
66 | }
67 | }
68 | ```
69 |
70 | 就三個 function , 一個 constructor ,把要 compile 的字串傳入去,用 `nextChar ()` 來讀取下一個字元,用 `retract ()` 來退回。現在運行一下我們的 tester ,看看 Reader 是否運作正常。
71 |
72 | ```js
73 | function log(str){
74 | $("#log").append(str + "
");
75 | }
76 | $(function (){
77 | //we stored our wescript in
23 |
24 |
25 |
28 |
29 |
30 |
58 |
59 |
60 |
88 |
89 |
90 |