編譯器是將一種語言翻譯爲另外一種語言的計算機程序。前端
過程描述以下:正則表達式
源程序→ 編譯器→ 目標程序。算法
基礎知識:express
自動機原理編程
數據結構小程序
離散數學後端
編譯器的發展:數組
馮諾依曼體系結構計算機 --> 機器語言程序 --> 彙編語言程序 --> FORTRAN語言及其編譯器/ Noam Chomsky天然語言研究 --> 優化技術(生成有效目標代碼)--> 編譯器的自動構造(分析程序生成器,如Yacc)/有窮自動機的研究 --> IDE整合數據結構
註釋:編輯器
彙編語言不易編寫,依賴特定的機器。所以須要一種數學定義或天然語言的簡潔形式編寫程序操做。
首先解決的是分析問題,用於限定上下文無關語言的識別的有效算法。對應喬姆斯基2型結構。具體解決方案有有窮自動機和正則表達式。
而後深化了生成有效的目標代碼方法。到此以構成最初的編譯器。
接下來解決的問題是編譯器的自動構造,分析程序生成器。
參考:https://mjbin888.iteye.com/blog/1511207
https://blog.csdn.net/Dongle_74/article/details/52745859
Noam Chomsky的天然語言結構的研究,使得編譯器結構異常簡單,甚至還帶有了一些自動化。Chomsky的
研究致使了根據語言文法( grammar,指定其結構的規則)的難易程度以及識別它們所需的算法來爲語言分類。
喬姆斯基分類結構( Chomsky hierarchy)包括了文法的4個層次:0型、1型、2型和3型文法,且其中的每個都是其前者的專門化。2型(或上下文無關文法( context-free grammar))被證實是程序設計語言中最有用的,並且今天它已表明着程序設計語言結構的標準方式。
終結符:語言的組成成分,是最後的內容
非終結符:不是語言的組成成分,而是在推到過程當中的佔位符,最終要替換終結符。
產生式:用終結符替代非終結符的規則。
終結符,通俗的說就是不能單獨出如今推導式左邊的符號,也就是說終結符不能再進行推導。不是終結符的都是非終結符。非終結符可理解爲一個可拆分元素,而終結符是不可拆分的最小元素。如:有α → β ,則α 必然是個非終結符。通常書上把非終結符用大寫字母表示,而終結符用小寫字母表示。識別符號就是開始符。由文法產生語言句子的基本思想是:從識別符號開始,把當前產生的符號串中的非終結符號替換爲相應規則右部的符號串,直到最終全由終結符號組成。這種替換過程稱爲推導或產生句子的過程,每一步成爲直接推導或直接產生。
(非終結符:A,B,C,D,終結符:a,b,c,d)
0型文法,產生式左右部可使用"非終結符"和"終結符"隨意組合,但左部不能爲空,如DAaBb->CcdD;
1型文法,在0型文法的基礎上,要求右部的符號長度大於左部(空除外),如AaBb->CcddDd
1<=|AaBb|<=|CcddDd|
2型文法,在1型文法的基礎上,要求左部必須由非終結符號組成,如AB->CcdddDd
3型文法,在2型文法的基礎上,產生式必須型如:A->Aa|a或A->aA|a,好比:A->AA,A->aa,這些都不是
3型文法也許是你們最難理解的,下面爲你們舉幾個例子來講明:
3型文法也叫正規文法,它對應於有限狀態自動機。它是在2型文法的基礎上知足:A→α|αB(右線性)或 A→α|Bα(左線性)。
若有:A->a,A->aB,B->a,B->cB,則符合3型文法的要求。但若是推導爲:A->ab,A->aB,B->a,B->cB或推導 爲:A->a,A->Ba,B->a,B->cB則不符合3型方法的要求了。具體的說,例子A->ab,A->aB,B->a,B->cB中的A->ab 不符合3型文法的定義,若是把後面的ab,改爲「一個非終結符+一個終結符」的形式(即爲aB)就對了。例子 A->a,A->Ba,B->a,B->cB中若是把B->cB改成B->Bc的形式就對了,由於A→α|αB(右線性)和A→α|Bα(左線 性)兩套規則不能同時出如今一個語法中,只能徹底知足其中的一個,才能算3型文法。
注意:上面例子中的大寫字母表示的是非終結符,而小寫字母表示的是終結符
有窮自動機( finite automata)和正則表達式(regular expression)同上下文無關文法緊密
相關,它們與喬姆斯基的3型文法相對應。
(1) 掃描程序(scanner)
在這個階段編譯器實際閱讀源程序(一般以字符流的形式表示)。掃描程序執行詞法分析(Lexical analysis):它將字符序列收集到稱做記號(token)的有意義單元中,記號同天然語言,如英語中的字詞類似。所以能夠認爲掃描程序執行與拼寫類似的任務。
(2) 語法分析程序(parser)
語法分析程序從掃描程序中獲取記號形式的源代碼,並完成定義程序結構的語法分析(syntax analysis),這與天然語言中句子的語法分析相似。語法分析定義了程序的結構元素及其關係。一般將語法分析的結果表示爲分析樹( parse tree)或語法樹(syntax tree)。
(3) 語義分析程序(semantic analyzer)
程序的語義就是它的「意思」,它與語法或結構不一樣。程序的語義肯定程序的運行,可是大多數的程序設計語言都具備在執行以前被肯定而不易由語法表示和由分析程序分析的特徵。這些特徵被稱做靜態語義( static semantic),而語義分析程序的任務就是分析這樣的語義(程序的「動態」語義具備只有在程序執行時才能肯定的特性,因爲編譯器不能執行程序,因此它不能由編譯器來肯定)。通常的程序設計語言的典型靜態語義包括聲明和類型檢查。由語義分析程序計算的額外信息(諸如數據類型)被稱爲屬性( attribute),它們一般是做爲註釋或「裝飾」增長到樹中(還可將屬性添加到符號表中)。
(4) 源代碼優化程序(source code optimizer)
編譯器一般包括許多代碼改進或優化步驟。絕大多數最先的優化步驟是在語義分析以後完成的,而此時代碼改進可能只依賴於源代碼。這種可能性是經過將這一操做提供爲編譯過程當中的單獨階段指出的。每一個編譯器不論在已完成的優化種類方面仍是在優化階段的定位中都有很大的差別。
(5) 代碼生成器(code generator)
代碼生成器獲得中間代碼( I R),並生成目標機器的代碼。儘管大多數編譯器直接生成目標代碼,可是爲了便於理解,本書用匯編語言來編寫目標代碼。正是在編譯的這個階段中,目標機器的特性成爲了主要因素。當它存在於目標機器時,使用指令不只是必須的並且數據的形式表示也起着重要的做用。例如,整型數據類型的變量和浮點數據類型的變量在存儲器中所佔的字節數或字數也很重要。
(6) 目標代碼優化程序(target code optimizer)
在這個階段中,編譯器嘗試着改進由代碼生成器生成的目標代碼。這種改進包括選擇編址模式以提升性能、將速度慢的指令更換成速度快的,以及刪除多餘的操做。
(1)記號
當掃描程序將字符收集到一個記號中時,它一般是以符號表示這個記號;這也就是說,做爲一個枚舉數據類型的值來表示源程序的記號集。
(2)語法樹
若是分析程序確實生成了語法樹,它的構造一般爲基於指針的標準結構,在進行分析時動態分配該結構,則整棵樹可做爲一個指向根節點的單個變量保存。結構中的每個節點都是一個記錄,它的域表示由分析程序和以後的語義分析程序收集的信息。
(3)符號表
這個數據結構中的信息與標識符有關:函數、變量、常量以及數據類型。符號表幾乎與編譯器的全部階段交互:掃描程序、分析程序或將標識符輸入到表格中的語義分析程序;語義分析程序將增長數據類型和其餘信息;優化階段和代碼生成階段也將利用由符號表提供的信息選出恰當的代碼。由於對符號表的訪問如此頻繁,因此插入、刪除和訪問操做都必須比常規操做更有效。儘管可使用各類樹的結構,但雜湊表倒是達到這一要求的標準數據結構。有時在一個列表或棧中可以使用若干個表格。
(4)常數表
常數表的功能是存放在程序中用到的常量和字符串,所以快速插入和查找在常數表中也十分重要。可是,在其中卻無需刪除,這是由於它的數據全程應用於程序並且常量或字符串在該表中只出現一次。經過容許重複使用常量和字符串,常數表對於縮小程序在存儲器中的大小顯得很是重要。在代碼生成器中也須要常數表來構造用於常數和在目標代碼文件中輸入數據定義的符號地址。
(5)中間代碼
根據中間代碼的類型(例如三元式代碼和P -代碼)和優化的類型,該代碼能夠是文本串
的數組、臨時文本文件或是結構的鏈接列表。對於進行復雜優化的編譯器,應特別注意選擇允
許簡單重組的表示。
(6)臨時文件
計算機過去一直未能在編譯器時將整個程序保留在存儲器中。這一問題已經經過使用臨時文件來保存翻譯時中間步驟的結果或經過「匆忙地」編譯(也就是隻保留源程序早期部分的足夠信息用以處理翻譯)解決了。存儲器的限制如今也只是一個小問題了,如今能夠將整個編譯單元放在存儲器之中,特別是在能夠分別編譯的語言中時。可是偶爾仍是會發現須要在某些運行步驟中生成中間文件。
(1)分析和綜合
將分析源程序以計算其特性的編譯器操做歸爲編譯器的分析部分,而將生成翻譯代碼時所涉及到的操做稱做編譯器的綜合部分。固然,詞法分析、語法分析和語義分析均屬於分析部分,而代碼生成倒是綜合部分。在優化步驟中,分析和綜合都有。分析正趨向於易懂和更具備數學性,而綜合則要求更深的專業技術。所以,將分析步驟和綜合步驟二者區分開來以便發生變化時互不影響是頗有用的。
(2)前端和後端
掃描程序、分析程序和語義分析程序是前端,代碼生成器是後端。可是一些優化分析能夠依賴於目標語言,這樣就是屬於後端了,然而中間代碼的綜合卻常常與目標語言無關,所以也就屬於前端了。在理想狀況下,編譯器被嚴格地分紅這兩部分,而中間表示則做爲其間的交流媒介。
這一結構對於編譯器的可移植性十分重要,此時設計的編譯器既能改變源代碼(它涉及到重寫前端),又能改變目標代碼(它還涉及到重寫後端)。在實際中,這是很難作到的,並且稱做可移植的編譯器仍舊依賴於源語言和目標語言。
(3)遍
編譯器發現,在生成代碼以前屢次處理整個源程序很方便。這些重複就是遍( pass)。
(4)語言定義和編譯器
(5)編譯器的選項和界面
(6)出錯處理