文章目錄
introduction
- Deference between Compilers with Interpreters
Compiler is off-line, and it’s imput is “program” then compiler it to exec witch can coculate Data to Output.
Interpreters is on-line, and it’s input is Program and Data, it interpreter the Program line by line, and excute the code. - History
1954 IBM develops the 704. In this time, software is more expensive than hardware. - Speedcoding
1954 John Backus.today’s interperters. very slow.
than he invented Fortran1(1954-1957). It’s the first compiler.
(好吧,英語記筆記有點慢)並且編譯的核心由此確定爲“理論(Theory)+練習(Pratice)” - 編譯原理分爲:lexical analysis, parsing, sematic analysis, optimization, code generation
lexical analysis和parsing注重句法分析,sematic注重語義,
1.1 編譯器的結構
人類是如何理解一段英語的
理解語句 | 對應編譯過程 |
---|---|
This is a sentence. | if x == y then z = 1; else z = 2; |
理解單詞 將句子分割成單詞集 {‘This’, ‘is’, ‘a’, ‘sentence’} |
詞法分析 將獨立的程序文本分割成單詞或tokens(標記) 可以識別出關鍵字{‘if’, ‘and’, ‘else’}、變量名{‘x’, ‘y’, ‘z’}、常量{‘1’, ‘2’}、操作符{’==’, ‘’=’, ‘=’} |
理解句子結構 |
語法分析(語法樹) |
理解句子意思 這是會有歧義的 |
語義分析 強制規定,避免模糊語言 |
簡化語言表示 | 自動代碼優化 但當Y==NaN時不能這麼做 |
翻譯成其他語言 | code生成 |
1.2 經濟型程序語言
問1:爲什麼有這麼多程序語言?
如,科學計算→Fortran,商業程序→SQL,系統程序→C/C++
答:不同程序所解決的領域(application domains)是不同的
問2:爲什麼有新的程序語言出現?
答:對編程語言來說,需要投入的前期編程教育佔據了支出的主要部分。
且現在主流語言之間的差距並不大。
創建一個新編程語言很容易。當新語言帶來的生產力大於培訓成本時,選擇創建新語言。
編程語言嘗試填補空缺
問3:好的程序語言是什麼?
3. 詞法分析Lexical Analysis
3.1 詞法分析的目的
將源碼分解爲<Identifier, token>對和詞彙表
3.1.1 名詞釋義
名詞 | 詞義 |
---|---|
Identifier | 字符串或由字符開始的數字串 |
Integer | 非空數字組成的字符串 |
Keyword | “else” or “if” or “begin” or … |
Witespace | 非空字符串,這個字符串由空格、換行符、製表符構成 |
Operator | 運算符 |
3.1.2 詞法分析概要
- 將源碼的字串歸類成Tocken class
- 通過Tocken與語法分析溝通
3.1.3 詞素例子
3.2 詞法分析例子
3.2.1 FORTRAN
FORTRAN規則:空格是無影響的(“var1” == “va r1”)
向前看規則
這說明了詞法分析的任務:
- 分割文本。從左向右讀源程序,生成Tocken,一次個狀態識別一個Tocken
- “向前看”用來解決一個Tocken的終止和另一個Tocken的開始
3.2.2 PL/1
(PL/1是一個IBM設計的編程語言)
特點:
- 關鍵字不保留
- DECLARE二義性
3.2.3 C++
在<>和>>、<<之間的問題
如:Foo<Bar> ,這裏出現了>>,會和流>>混淆。所以需要將這裏的>>改成> >(加了個空格)
3.3 正規語言
Lexical structure = token classes
3.3.1 正規表達式
3.3.1.1 正規表達式Regular Expressions
正規表達式由{單個字符,空字符}構成
空字符用""表示
3.3.1.2 正規表達式的操作
操作名 | 方法示意 |
---|---|
Union | |
Concatenation | |
Iteration |
定義: 是一個正規表達式中各正規式的組成元素集合
上圖中,都是在給定的(即正規表達式構成元素)組成的語法(grammer)
舉例說明:
3.3.2 正規語言Formal Languages
定義:設是一個字符集。一個在上產生的語法是,從上產生的字符串集。(主要部分:語法是字符串集,其他定語自己看懂)
就像英文字母表是英語字符構成的,而英文語言是由英文句子構成的
Alphabet = ASCII
Language = C programs
3.3.2.? Meaning Function L
Meaning Function L將語法(Syntax)映射到語義(Semantics)上去
(上圖exp爲expression縮寫)
使用Meaning Function的意義:
- 分清語法和語義
- 有利於將符號(notation)看做成一個獨立的問題
- 表達式和語義並不是一一對應的
上圖展示了,不同語法通過Meaning Function可能映射到相同語義上去。這有助於我們將相同功能、不同語法寫成的程序,用高效的程序代替低效的程序。
並且,語法不會映射到多個語義上去。(無二義性)
3.3.3 正規表達式如何說明編程語言中的不同方向
注:
要描述的類型 | 描述方法 |
---|---|
Keyword | ‘if’+‘else’+then’… |
Interger | digit = ‘0’+‘1’+…+‘9’ |
Identifier | letter=[a-zA-Z] ([a-z](a到z求並)) |
Whitespace | ’ ‘+’\n’+’\t’ |
例子:識別email地址的正規表達式
[email protected]
正規式表達:
PASCAL語言中的正規表達式例子:
(這裏的+表示連接,在往上的文章中有用(1+0)這樣表示的正規式中的+表示或,因爲課件中用的是+表示或,十分有歧義,並且龍書中用的也是|。下面我儘量使用|,用以區分+,請注意)如下圖所示。。。這老師用的跟我校用的那本清華的編譯原理講的也不一樣,真討厭
3.4 詞法規範(lexical specification)
詞法檢測過程:
- 根據詞素寫出Tocken類
- 構建一個符合所有詞素和Token的R
- 輸入
CTMD垃圾CSDN,Ctrl+S不能保存,寫了1000多字全沒了。。。。Scheiße!
4 語法分析
4.1 上下文無關文法
4.1.1 結構
左優先和右優先
規範推導爲右優先
4.1.2 二義文法
解決方法:將二義文法改寫成非二義文法
改寫成:(消除左遞歸)
注意:
4.2 處理問題
4.2.1 問題的種類
4.2.2 處理問題需要做的事
- 準確且清晰地報告錯誤
- 快速地從錯誤中恢復過來(狀態)
4.2.3 處理問題的方法
4.2.3.1 恐慌模式Panic mode
處理方式:一直接着喫,直到找到了一個正確的role
尋找同步token
z.B.
可以使用一個特殊的終結符"error"來描述有多少輸入需要略過
4.2.3.2 錯誤產生式 Error Productions
只能知道語法中簡單的錯誤
z.B.
4.2.3.3 自動的局部或全局矯正 Automatic local or global correction
現在並不常用這種
4.2.3.4 過去和現在比較
4.3 語法樹
編譯器剩下一部分需要一個能代替程序的結構。
抽象語法樹:近似語法樹但忽略一些細節、簡單地描述成AST(Abstract Syntax Tree)
改寫成這樣
4.4遞歸下降的語法分析(自頂向下的語法分析)
語法樹構建方法:自頂向下,從左到右
先序遍歷地生成Terminals
z.B.
生成過程,注意有迴歸
accept
4.4.2 舉一個RD algorithm例子
對於語法E:{
E → T | T+E
T → int | int*T | (E)
}
Token有:INT, OPEN, CLOSE, PLUS, TIMES
global next指向下一輸入的token
- 定義一個返回值是bool值的檢測函數,它檢測將要輸入的token是不是一個token。
// 返回當前token是否符合選擇的token,並將指針移到下一個輸入上去
bool term(Token tok){
return *next++==tok;
}
定義一系列的代表產生式的函數S " bool Sn() { … } ",只有在相同時才返回爲真。
定義一個包含所有產生式的函數S " bool S() { … } "
// z.B. 對於產生式 "E→T"有函數
bool E1() { return T(); }
// z.B. 2: "E→T+E":
bool E2() { return T() && term(PLUS) && E(); }
bool E(){
Token *save = next; // 在嘗試任何匹配前,先把**接下來**要從哪去token的位置記錄下來。
return (next = save, E1())
|| (next = save, E2());
}
- 定義接下來的T的函數
// 對於 T→int
bool T1() { return term(INT); }
// 對於 T→int * T
bool T2() { return term(INT) && term(TIMES) && T(); }
// 對於 T→(E)
bool T3() { return term(OPEN) && E() && term(CLOSE); }
// T → int | int*T | (E)
bool T(){
TOKEN *save = next;
return (next = save, T1()) || (next = save, T2()) || (next = save, T3());
}
- 開始語法分析
初始化next指向輸入字符串的第一個字符,調用函數E()
問題: 這對於輸入"int * int "會reject,因爲第一次使用的是E→int進行推導,如果使用E→int*T就不會出錯。所以有問題”如果一個產生非終結符的產生式被使用了,則不再有回溯回來檢測此時使用別的產生式的可能“
通常上,自頂向下遞歸分析需要支持全回溯,纔可以進行完整的語法檢測。
雖然正常情況下不使用這種算法,但這算法容易手工實現。在一個非終結符只能推導出一個終結符情況下是可用的。消除例子中的公共前綴left factoring就可以用了:)。
4.4.3 消除左遞歸(left recursion)
舉個例子:
// S → Sa
bool S1() { return S() && term(a); }
bool S() {return S1();}
這裏的S會產生無窮的遞歸。一個左遞歸語法要求沒有這樣的S,這樣的非終結符S使得S可以加步推導出S
考慮這樣的語法:
它會產生這樣的語言:
這導致最後生成了,它從右向左依次生成,因此可以右遞歸地生成。
上式也可寫成右遞歸式:(從左向右生成)
更多的消除左遞歸式的例子: