Sample語言編譯與執行及簡單虛擬機器器的設計實現
記錄一下編譯原理課程設計的內容(Java )。這是一次課程設計,程式碼中有很多漏洞,歡迎來噴。
Sample語言的編譯包括,詞法分析,語法分析,語義分析及中間程式碼生成,中間程式碼轉化為彙編程式碼。詞法分析採用有窮自動機實現,語法分析和語義分析均為自上而下進行。中間程式碼轉化為彙編的過程中統一採用變址定址方式,簡單粗暴,未做更多優化。
Sample語言具有一般高階語言的共同特徵:字符集包括所有的大小寫字母、數字和一些分界符;有多種內部資料型別:整型、實型、布林型和字元型;語句說明包括說明語句和可執行語句;語句控制包括順序、條件和迴圈3種結構。具體來說主要包括如下一些語法成分。
(1) 由字母、數字以及下劃線構成的字符集。
(2) 由關鍵字、識別符號、常數、界符和運算子構成的單詞集。
(3) 有整形、布林型、實型和字元型4中資料結構。
(4) 包括算術表示式、邏輯表示式和關係表示式3種類型的表示式;語句由說明語句和可執行語句兩種型別構成;說明語句包括常量說明(用const定義)和變數說明(用var定義);可執行語句有賦值語句、if語句、while語句、do-while語句等。
(5) 程式由關鍵字program開頭。
語法分析階段涉及的句法如下:
算術表示式的語法定義如下。
(1)<算術表示式>::=<項> <加法運算子> <項> | <項>
(2)<項>::=<因子> <乘法運算子> <因子> | <因子>
(3)<因子>::=<識別符號> | <無符號整數> |(<算術表示式>)
(4)<加法運算子>::= + | -
(5)<乘法運算子>::=* | /
布林表示式的語法定義如下。
(1)<布林表示式>::=<布林項> or <布林表示式> | <布林項>
(2)<布林項>::=<布林因子> or <布林項> | <布林因子>
(3)<布林因子>::=<布林量> | !<布林因子>
(4)<布林量>::=<布林表示式> | <算術表示式> <關係符> <算術表示式>
(5)<關係符>::= < | > | < > | <=| >= | == |=
if,while、for、賦值等語句的定義如下。文字表達可能不太清楚,所以用圖的形式描述。
目的碼生成是將語義分析所產生的中間程式碼變換成目的碼,從而實現了源程式的最後翻譯,此階段的工作也是最複雜的。它的複雜性在於,翻譯工作有賴於目標機器的系統結構,涉及記憶體的使用、指令的選擇以及暫存器的排程。因此我們將四元式到機器碼的翻譯分為兩步,第一步是四元式到組合語言的翻譯,第二步是組合語言到機器碼的翻譯。
接下來就是簡單虛擬機器器的設計的實現了。該虛擬機器將完成從彙編程式碼到二進位制程式碼的轉換,並將二進位制程式碼載入到記憶體、執行。
我們首先需要設計一臺抽象的計算機——虛擬裸機,包括暫存器的設計與使用、記憶體的設計與使用、指令格式的設計、定址方式的設計等。
抽象計算機的虛擬硬體配置。
1、記憶體為256×256個單元(64K),一個單元成為一個字,一個字有兩個位元組,地址為0~65535。單元可以存放置零,也可以存放資料。記憶體單元若存放資料,則資料範圍為-32768~+32767或0~65535。一條機器指令佔用一個單元,若不考慮資料佔用的記憶體單元,程式最大長度為256×256。
2、虛擬機器具有四個通用暫存器(2位元組)、一個標誌暫存器FlagReg和一個堆疊暫存器TopReg。4個通用暫存器分別標記為R0、R1、R2、R3,除可用於存放運算元和計算結果外,還可用於變址定址。標誌暫存器FlagReg用於儲存cmp指令比較結果。堆疊暫存器TopReg用作系統棧頂指標。
定址方式及暫存器功能說明。
1、記憶體單元若存放指令,高四位(第15、14、13、12位)為操作碼、低12位(11~0)
用於描述地址。第11、10位表徵第一地址,第一地址只能是暫存器。
00:表示第一地址為R0;
01:表示第一地址為R1;
02:表示第一地址為R2;
03:表示第一地址為R3;
第9、8位表徵第二地址的定址方式,第二地址可以是記憶體直接地址、暫存器或變
址定址,還可以是0~255範圍內的直接數。
00:表示直接地址定址(M),第二運算元在記憶體低地址區域,地址範圍為0~255,7~0位表示記憶體地址。
01:表示暫存器定址,3~0位的值可為0~3,分別表示R0~R3(實際上是用第一位和第0位)。7~4位或為0,或為1(實際上使用第5位)。0表示暫存器直接定址(Ri),
第二運算元在暫存器Ri中;1表示暫存器間址定址(@Ri),在暫存器Ri中存放的是第二運算元地址。
10:表示直接數訪問立即定址(D),7~0位表示直接數,直接數範圍為0~255。
11:表示以C*255為基址,以R3為位移的變址定址(C[R3],0≤C≤FFh),7~0位表示C。C相當於段號或頁號,R3相當於段內位移或頁內位移。當R3用於變址定址時,R3僅低8位有效,地址計算公式為:C*256+(R3&0x00ff)。因二進位制位有限,這裡約定只有R3可以作為變址暫存器。
2、堆疊暫存器TopReg(2位元組)的初始值為0,系統堆疊從記憶體地址65535開始(0-1=65535)。執行call指令時,TopReg減1,系統將斷點存入TopReg所指的記憶體單元,入口由call指令的第二地址確定;當執行ret指令時,從TopReg所指的單元獲取斷點,系統將TopReg增1。使用者程式從地址0開始存放,CPU模擬器從該地址開始執行程式指令。在編制使用者程式時,應注意和堆疊儲存區域的衝突。
3、輸入輸出指令無第二地址,由於第一地址只能是暫存器,故輸入輸出指令只能對暫存器進行操作。jmp、jmpneg、jmppos、jmpzero和call指令無第一地址。因斷點在堆疊中,故ret指令無第一、第二地址。
4、對於某些指令,某些二進位制位可能不使用,在編寫機器語言程式時,統一將其置位0。
機器語言指令描述。
1、read(0h):從鍵盤讀一個字到第一地址。等待使用者從鍵盤輸入一個十進位制資料,數值範圍為-32768~+32767。資料可以空格、Tab和回車結束。在輸入過程中,可利用Backspace鍵刪除已輸入的字元,也可鍵入Esc鍵終止程式執行。若輸入資料中存在非法字元,則虛擬裸機拒絕接受,要求使用者重新輸入。
2、write(1h):從第一地址寫一個字到螢幕。
3、load(2h):從第二地址將字裝入第一地址。
4、store(3h):將第一地址中的字存放到第二地址。
5、call(4h):轉移到第二地址指定的記憶體單元,執行子程式,斷點保留在系統堆疊中。
6、ret(5h):由系統堆疊獲得斷點,返回。
7、add(6h):將第一地址中的字加上第二地址中的字,結果保留在第一地址中。
8、sub(7h):將第一地址中的字減去第二地址中的字,結果保留在第一地址中。
9、mul(8h):將第一地址中的字乘以第二地址中的字,結果保留在第一地址中。
10、div(9h):將第一地址中的字除以第二地址中的字,結果保留在第一地址中。
11、cmp(Ah):將第一地址中的字和第二地址中的字比較,由系統置位標誌暫存器FlagReg。
標誌暫存器FlagReg=-1,表示第一地址中的字小於第二地址中的字。
標誌暫存器FlagReg=1,表示第一地址中的字大於第二地址中的字。
標誌暫存器FlagReg=0,表示第一地址中的字等於第二地址中的字。
12、jmp(Bh):無條件轉移到第二地址指定的記憶體單元。
13、jmpneg(Ch):若標誌暫存器FlagReg中的值為-1,轉移到第二地址指定的記憶體單元。
14、jmppos(Dh):若標誌暫存器FlagReg中的值為1,轉移到第二地址指定的記憶體單元。
15、jmpzero(Eh):若標誌暫存器FlagReg中的值為0,轉移到第二地址指定的記憶體單元。
16、halt(Fh):終止程式執行。
指令系統。
指令格式如下(16Bit):
15-12 11-10 9-8 7-0
操作碼 |
第一地址 |
定址方式 |
第二地址 |
① 操作碼
② 第一地址為暫存器(R0-R3)
③ 定址方式和第二地址
④ 指令分類
到此為止一個簡單的編譯器已經完成了。測試程式中有一個輾轉相減法求最大公約數和最小公倍數的例程。
執行結果如下。
上圖中12和30為輸入資料,輸出6為最大公約數,60為最小公倍數。
程式碼地址:https://github.com/fhlt/Compile.git