簡易編譯器流程圖:
一個典型的編譯器,可以包含為一個前端,一個后端。前端接收源程序產生一個中間表示,后端接收中間表示繼續生成一個目標程序。所以,前端處理的是跟源語言有關的屬性,后端處理跟目標機器有關的屬性。
復雜的編譯器:
詞法分析器:
1.詞法分析器讀入源代碼,然后對字符流(源代碼)做切分成記號流。舉個例子:
這是一個程序員看到的字符流(源代碼)
2.詞法分析器將字符流讀入,根據關鍵字、標識符、標點、字符串、整形數等進行劃分,形成記號流(單詞):
?舉個例子: 假如源語句if(x>5),則詞法分析器返回token{k=IF,lexeme=0};token{k=LPAREN,lexeme=0};token{k=ID,lexeme="X"};……
詞法分析器的任務:字符流到記號流。
字符流:和被編譯語言密切相關(ASCII,Unicode,or……)
記號流:編譯器內部定義的數據結構,編碼所識別出的詞法單元
語法分析器:
?語法分析器(Parser)通常是作為編譯器或解釋器的組件出現的,它的作用是進行語法檢查(檢查語法是否符合這個語言的規則)、并構建由輸入的單詞組成的數據結構(一般是語法分析樹、抽象語法樹等層次化的數據結構)。語法分析器通常使用一個獨立的詞法分析器從輸入字符流中分離出一個個的“單詞”,并將單詞流作為其輸入。實際開發中,語法分析器可以手工編寫,也可以使用工具(半)自動生成。
抽象語法樹是對程序語法的抽象表示
例如,當在開發語言時,可能在開始的時候,選擇LL(1)文法來描述語言的語法規則,編譯器前端生成LL(1)語法樹,編譯器后端對LL(1)語法樹進行處理,生成字節碼或者是匯編代碼。但是隨著工程的開發,在語言中加入了更多的特性,用LL(1)文法描述時,感覺限制很大,并且編寫文法時很吃力,所以這個時候決定采用LR(1)文法來描述語言的語法規則,把編譯器前端改生成LR(1)語法樹,但在這個時候,你會發現很糟糕,因為以前編譯器后端是對LL(1)語樹進行處理,不得不同時也修改后端的代碼。
1.抽象語法樹的第一個特點為:不依賴于具體的文法。無論是LL(1)文法,還是LR(1),或者還是其它的方法,都要求在語法分析時候,構造出相同的語法樹,這樣可以給編譯器后端提供了清晰,統一的接口。即使是前端采用了不同的文法,都只需要改變前端代碼,而不用連累到后端。即減少了工作量,也提高的編譯器的可維護性。
2.抽象語法樹的第二個特點為:不依賴于語言的細節。在編譯器家族中,大名鼎鼎的gcc算得上是一個老大哥了,它可以編譯多種語言,例如c,c++,java,ADA,Object C,?FORTRAN,?PASCAL,COBOL等等。在前端gcc對不同的語言進行詞法,語法分析和語義分析后,產生抽象語法樹形成中間代碼作為輸出,供后端處理。要做到這一點,就必須在構造語法樹時,不依賴于語言的細節,例如在不同的語言中,類似于if-condition-then這樣的語句有不同的表示方法
語義分析器:
對語法樹的合法性進行處理(例如:一個變量在使用之前是否先定義聲明,所調用的函數是否有對應的定義),產生相應的中間代碼或目標代碼.
語義分析是編譯過程的一個邏輯階段, 語義分析的任務是對結構上正確的源程序進行上下文有關性質的審查,進行類型審查。語義分析是審查源程序有無語義錯誤,為代碼生成階段收集類型信息。比如語義分析的一個工作是進行類型審查,審查每個算符是否具有語言規范允許的運算對象,當不符合語言規范時,編譯程序應報告錯誤。如有的編譯程序要對實數用作數組下標的情況報告錯誤。又比如某些程序規定運算對象可被強制,那么當二目運算施于一整型和一實型對象時,編譯程序應將整型轉換為實型而不能認為是源程序的錯誤。
經過上面的處理,程序中就沒有在包括語言和語義的錯誤(除非編譯器本身就有bug)
中間代碼
??中間代碼也叫中間語言
(Intermediate code /language)是:源程序的一種內部表示,不依賴目標機的結構,復雜性介于源語言和機器語言之間。
中間代碼的優點
1、邏輯結構清楚;
2、利于不同目標機上實現同一種語言;
3、利于進行與機器無關的優化;
中間代碼可以生成例如 三地址代碼 SSA 控制流圖 等 (中間碼的生成也是取決于編譯器設計中的考慮,例如需不需要優化,或者追求速度,性能等).
關于語法分析器和中間代碼:
LINK:?https://blog.csdn.net/yongchaocsdn/article/details/79056504
代碼生成:
中間代碼可以被最終的一個代碼生成的階段處理為最后的目標代碼(例如機器碼,JVM字節碼)
符號表
符號表是存儲程序編譯過程中重要信息,可以給每個階段提供支持
在計算機科學中,符號表是一種用于語言翻譯器(例如編譯器和解釋器)中的數據結構。在符號表中,程序源代碼中的每個標識符都和它的聲明或使用信息綁定在一起,比如其數據類型、作用域以及內存地址。
符號表在編譯程序工作的過程中需要不斷收集、記錄和使用源程序中一些語法符號的類型和特征等相關信息。這些信息一般以表格形式存儲于系統中。如常數表、變量名表、數組名表、過程名表、標號表等等,統稱為符號表。對于符號表組織、構造和管理方法的好壞會直接影響編譯系統的運行效率。
舉個例子:
一個加法表達式(sum)在編譯中的過程:
例如sum的語法規則是:
1.整數數字 n
2.加法表達式式 n1+n2
根據上面的規則
3
1+2
1+2+3? (加法表達式遵循左結合也就是 1+2 的結果 3+3 這也屬于加法表達式的一種)
這上面的三種都遵守了加法表達式的規則? 所以都是加法表示式的一種
目標機器: 棧式計算機(stack)
是一個LIFO的一個先入后出存儲器,跟它向對應的是FIFO存儲器先入先出傳統順序
有兩條指令:
1.push n (將指定的參數壓入棧中)
2.add (將棧頂的兩個索引的數據彈出并進行加法運算,再將運算后的結果壓入棧中)
//x和y表示棧頂的數據 pop[]表示彈出棧頂的數據
x = pop[]y = pop[]//加法運算
n = x+y//壓棧
push n
例如1+2+3加法運算經過語法分析器翻譯抽象語法樹(AST):
1+2+3 =語法分析器: 語法樹(+)(+) (3)(1) (2)
先從左結合開始:
1+2 = 3
3+3 = 6
生成棧式計算機(stack)的代碼:
代碼生成使用樹的后續遍歷(從樹的左邊子節點開始遍歷,然后遍歷右邊子節點最后遍歷根節點)
代碼生成規則:
1. 遍歷樹中時如果遇到整數 n 生成代碼: push n
2.遍歷樹中時如果遇到 + 生成代碼: add
最后生成的棧式計算機的代碼:
push 1push 2addpush 3add
?