打造自己的編譯器

2021-04-22 19:54:13 字數 4261 閱讀 1263

為了更深入的了解程式設計的底層,就想研究下編譯。

找了幾本編譯原理看,什麼正規表示式、自動機把我都搞糊塗了。

把複雜的問題弄得更複雜,這不是我希望的。

還是自己做個東西模擬下編譯過程。

任務:讀入原始碼,輸出彙編。

為什麼不直接生成機器碼?彙編可以直接看出編譯是否錯誤。

先設定乙個語言模型:

只有運算子、識別符號、數字、分號四種語法元素。

識別符號都定為全域性變數。

運算子只有= + - * /代表賦值、加、減、乘、整除五種運算。

數字僅僅是32位整數。

分號表示一條語句結束。

如:aa02=15/kl+kh*kl/51;

問題一:建立乙個符號表,按順序寫入必要的語法元素,

可以查詢或修改符號的型別等資訊。

把符號表做成乙個指標陣列,指向什麼呢?

pp=^tx

tx=record

next:pp    //指向下個符號

lx:integer;//型別

wz:integer;//在該型別中的位置

end;

fhb:array of pp;//符號表

js:integer;      //符號表記數

原來是指向乙個記錄,它可以構建鍊錶(為什麼用鍊錶?可以方便去掉

已經處理的符號),乙個符號乙個pp。

lx(型別)預定義四種:

1:運算子  2:識別符號 3:數值 4:暫存器(儲存中間結果)

運算子的wz(位置):

1:=  3:+  4:-  6:*  7:/

暫存器的位置:與sjcq對應

sjcq:array[0..5]of string=('eax','ebx','ecx','edi','esi','edx');

edx怎麼放到後面去了?除法要用這個暫存器啊。

是不是還要記錄暫存器被哪個符號使用?

bjcq:array [0..5]of pp; //對應的暫存器使用者

至於識別符號和數值我把他放到bsf中,wz就是它們在bsf中的index。

bsf:tstringlist;

(若是fhb:array of tx 一次性申請記憶體,可以免去頻繁的new(pp),原始碼裡忘了釋放了)

好象還缺點什麼,讀入原始碼是為了輸出**服務的,編譯是一條一條語句

編譯的,所以還要記錄一條語句是從符號表**開始**結束。

yj=record     //語句

qd,zd:integer;//起點,終點

end;

yjz:array[0..100]of yj; //語句組

yjzjs:integer;          //語句組記數

這樣讀取原始碼建立符號表、語句組應該沒有難度了,詳見procedure dq;

問題二、判斷運算優先順序

現在嘗試處理一條語句,也就是fhb[qd]到fhb[zd]的一段鍊錶。

我們要做的就是遍歷這段鍊錶,找出最先運算的運算子,

實際上運算子的位置就是優先順序;

varxp,xq,last,qm,hm:pp;

x,y,z1,z2:integer;

xp:=fhb[qd]; x:=-1;xq:=nil;

while xq<>fhb[zd].next do //不能超過一條語句終點

begin

if xp^.lx=1 then  //如果是運算子

begin

if xp^.wz<=x+1 then break;  //判斷運算優先

qm:=last;      //運算子前面乙個符號

hm:=xp^.next;   //運算子後面乙個符號

x:=xp^.wz;       //x是具體哪個運算子

end;//endif

last:=xp;

xq:=xp^.next;

xp:=xq;

end;//endwhile

x+1是為了同級別運算按順序來;

應該看明白了吧,測試下 aa=55-hk*jj/51;

x=6(乘法),qm(前面)指向hk,hm(後面)指向jj,

這樣二元運算的三個關鍵符號都到手了。

問題三、前後都是數值編譯器應該先計算

這個簡單,看原始碼。

問題四、處理qm(前面的符號)

先看一些彙編:

sub eax,10

add eax,[s]

mul eax,ecx,7

idiv ecx

如果你對彙編有點了解的話就該知道,qm必須轉成暫存器型別。

如果qm^.lx=4(暫存器)那不用處理.

如果qm^.lx=2(識別符號)就要轉成暫存器型別.

qm^.wz:=newjcq(qm);//申請暫存器

qm^.lx:=4;     //改為暫存器型別

當然x=1(賦值運算)是不用轉的

如果qm^.lx=3(數值)最麻煩,要分兩種情況:

如果x是乘,加運算,qm和hm的內容要交換;(為了優化**)

如果x是除,減運算,qm要轉成暫存器型別;(必須這樣啦)

問題五、乘法的處理

要按hm的型別分三種情況。

hm是暫存器: 生成類似** imul ebx

hm是數值:   生成類似** imul eax,ecx,5

可以優化成 lea eax,[ecx+ecx*4](麻煩)

hm是識別符號:這也分兩種情況

qm是eax,  生成類似** imul dword ptr [b]

qm不是eax,生成類似** imul ebx,[b]

問題六、除法最麻煩(俺還是在用整除呢)

除法要求,被除數在eax中,edx不能用。(所以在sjcq中俺把edx弄到最後)

如果qm不是eax怎麼辦?交換。

假如qm是ecx,生成類似** xchg eax,ecx

eax和qm的暫存器相關也要交換,不然會出錯的。

//原eax的與qm 交換

tp:=bjcq[0];   //取得原來使用eax的符號 

bjcq[qm^.wz]:=tp;

tp^.wz:=qm^.wz;

qm^.wz:=0;

bjcq[0]:=qm;

現在可以生成**了。

首先 cdq (用xor edx,edx也可以,但是指令多乙個位元組)

hm是數值:   首先要把hm轉成暫存器型別

hm是暫存器: 生成類似** idiv ebx

hm是識別符號: 生成類似** idiv dword ptr [b]

問題七、賦值運算

hm是數值:   生成類似** mov [a],18

hm是暫存器: 生成類似** mov [a],eax

hm是識別符號: 生成類似** mov eax,[b]

mov [a],eax

問題八、收尾工作

if hm^.lx=4 then bjcq[hm^.wz]:=nil ; // 不要的暫存器要歸還

if qm=fhb[qd] then exit;    //一條語句弄完了

qm^.next:=hm^.next;      //把已經運算的從鍊錶中刪除

yx(qd,zd); //再來

用鍊錶刪除資料就是簡單

測試:與delphi生成的**對比

設 a:=8;

b:=4;

d:=10;

編譯a:=b*5-d div 5;

當然在我的編譯器中寫做a=b*5-d/5;

delphi**:

mov eax,[d]

mov ecx,$00000005

cdqidiv ecx

mov edx,[b]

lea edx,[edx+edx*4]

sub edx,eax

mov [a],edx

我生成的**:

mov eax ,[b]

imul eax,eax,5

mov ebx ,[d]

xchg eax,ebx

mov ecx ,5

cdqidiv  ecx

sub ebx,eax

mov [a],ebx

經過1千萬次迴圈delphi的用時250毫秒,我的200毫秒。

但是delphi的**佔30位元組,我的佔35位元組

如果你在delphi中用for迴圈測試要小心,

for迴圈的迴圈變數會占用 ebx。

怎麼處理,改下sjcq中成員的次序。

最後的問題:擴充套件

括號是最容易擴充套件的,你可以把括號當成語句。

比較運算最後要test或cmp。

……

自己動手寫編譯器 鏈結器致謝

自己動手寫編譯器 鏈結器 本書投稿後,有幸請csdn暨 程式設計師 雜誌總編 劉江老師閱讀了本書的初稿,並為本書作序,在此向劉老師表示最衷心的感謝。本書臨近出版之際,承蒙清華大學王生原老師閱讀了本書終稿,並對書稿做了中肯評價 本書特色鮮明,內容有深度,文筆也很不錯,很值得出版。本書最大的特色是所選的...

自己動手寫編譯器 鏈結器致謝

本書投稿後,有幸請csdn暨 程式設計師 雜誌總編 劉江老師閱讀了本書的初稿,並為本書作序,在此向劉老師表示最衷心的感謝。本書臨近出版之際,承蒙清華大學王生原老師閱讀了本書終稿,並對書稿做了中肯評價 本書特色鮮明,內容有深度,文筆也很不錯,很值得出版。本書最大的特色是所選的目標平台,即x86處理器以...

手工打造編譯器之語義分析1

語義分析階段的任務是 將變數的定義與它們的各個使用聯絡起來,檢查每乙個表示式是否有正確的型別,並將抽象語法變為更簡單的更適合生成機器 的表示。符號表是繫結的集合。這些繫結是識別符號與其含義的一種對映關係。因為作用域的改變,符號表也隨之改變。如下面的c語言 struct m 上面 的符號表可能為 t1...