一個編譯器的實現0


前一陣做了個編譯器(僅詞法分析、語法分析、部分語義分析,所以說是前端),拿來分享一下,如有錯誤,歡迎批評指教!

整個代碼庫具有如下功能:

提供編譯器所需基礎數據結構、計算流程框架類,可供繼承使用;
提供基礎數據結構的可視化控件;
提供類似YACC的詞法分析器、語法分析器自動生成功能;
提供Winform程序,集成和擴展上述功能,方便研究和應用。

本文及其后續系列將逐步給出所有工程源代碼(visual studio 2010版本)。

上圖展示一下先。

圖1 詞法、語法分析和結點匹配

圖1 詞法、語法分析和結點匹配

圖2 自動生成詞法分析器、語法分析器

圖2 自動生成詞法分析器、語法分析器

圖3 自動生成詞法分析器、語法分析器

圖3 自動生成詞法分析器、語法分析器

圖4 自動打印語法樹

圖4 自動打印語法樹

為了說清楚編譯器這種東西,我想最好還是舉例。

比如我們要為數學計算的表達式(Expression)設計一個編譯器。(當然有很多方法可以實現讀取數學表達式並計算結果的算法,未必使用編譯原理)

來看一些數學表達式的例子:

37

19 * 19 - 18 * 18

(19 + 18) * (19 - 18)

18 +19 / (18 / 18)

a  + (a + 1) + (a + 2) + (a + 3)

好了夠了,大家能夠了解本文所討論的Expression的范圍了。那么我們引入“文法”(Grammar)的概念。Expression的文法就是這樣的:

<Expression> ::= <Multiply> <PlusOpt>;
<PlusOpt> ::= "+" <Multiply> | "-" <Multiply> | null;
<Multiply> ::= <Unit> <MultiplyOpt>;
<MultiplyOpt> ::= "*" <Unit> | "/" <Unit> | null;
<Unit> ::= identifier | "(" <Expression> ")" | number;

我們分別展示出上述幾個例子用文法展開的過程。

37: <Expression>

=> <Multiply> <PlusOpt>

=> <Unit> <MultiplyOpt>

=> number

19 * 19 - 18 * 18: <Expression>

=> <Multiply> <PlusOpt>

=> <Unit> <MultiplyOPt> "-" <Multiply>

=> number "*" <Unit> "-" <Unit> <MultiplyOpt>

=> number "*" number "-" number "*" <Unit>

=> number "*" number "-" number "*" number

(19 + 18) * (19 - 18): <Expression>

=> <Multiply> <PlusOpt>

=> <Unit> <MultiplyOpt>

=> "(" <Expression> ")" "*" <Unit>

=> "(" <Multiply> <PlusOpt> ")" "*" "(" <Expression> ")"

=> "(" <Unit> <MultiplyOpt> "+" <Multiply> ")" "*" "(" <Multiply> <PlusOpt> ")"

=> "(" number "+" <Unit> <MultiplyOpt> ")" "*" "(" <Unit> <MultiplyOpt> "-" <Multiply> ")"

=> "(" number "+" number ")" "*" "(" number "-" number <MultiplyOpt> ")"

=> "(" number "+" number ")" "*" "(" number "-" number ")"

寫到這里就,其余例子大家自己試試~如果寫不出來,后面的部分可能就不太容易看了。(試試寫寫,很快就寫的比較熟練了)

 

總結一下“文法”(Grammar)。文法就是描述Expression的構成的,和英語的語法類似吧。 有了文法,我們就可以寫編譯器了。

Expression的文法有5個式子,這5個式子就叫做“產生式”(Production),因為他們能從左邊的結構產生(推導)出右邊的結構來。一個文法至少有一個產生式,第一個產生式的左邊的結點是初始結點,所有的推導都必須從初始結點(即第一個產生式)開始。

產生式(Production)左邊叫做左部(左部只有始終一個結點),右邊叫做右部(廢話),中間用【::=】這個符號隔開。

右部由符號【|】分為若干部分,每一部分都是產生式可能推導出的一個結果,且每次只能選擇其中一個進行推導。【null】表示什么也不推導出來。(這是個霸氣的符號,不要覺得什么都不推導出來就不重要,恰恰相反,這個符號很重要)

為簡化后文的說明,繼續舉例:<PlusOpt> ::= "+" <Multiply> | "-" <Multiply> | null;

對於這個產生式,其實是由三部分<PlusOpt> ::= "+" <Multiply>;和<PlusOpt> ::= "-" <Multiply>和<PlusOpt> ::= null;組成的,每一部分都稱為一個“推導式”(Derivation)。

像【(19 + 18) * (19 - 18)】這樣一個具體的“東西”,我們稱之為一個“句子”(Sentence)。

明了了上述關於文法的東西,就可以進行編譯器的設計了。

 

我們先搞搞清楚,編譯器能做什么?以Expression的【19 * 19 - 18 * 18】為例,Expression的編譯器首先要讀取字符串格式的源代碼,即:

1 var sentence = “19 * 19 - 18 * 18”;
2 var expLexicalAnalyzer = new LexicalAnalyzerExpression();
3 expLexicalAnalyzer.SetSourceCode(sentence);

 

然后,編譯器進行詞法分析,得到單詞流(TokenList)。“流”這個東西,其實就是數組。

1 var tokens = expLexicalAnalyzer.Analyze();

在此例中,得到的單詞流是這樣的:

[19]$[Number]$[0,0]$[False]$[]
[*]$[Multiply]$[0,3]$[False]$[]
[19]$[Number]$[0,5]$[False]$[]
[-]$[Minus_]$[0,8]$[False]$[]
[18]$[Number]$[0,10]$[False]$[]
[*]$[Multiply_]$[0,13]$[False]$[]
[18]$[Number]$[0,15]$[False]$[]

第一個單詞的意思是:這個單詞是【19】,類別是【Number】,在源代碼中第一個字符的位置是【行0, 列0】,是否錯誤的單詞【False】,其它描述信息為【】(空,即木有描述信息))

然后是根據這個單詞流分析出語法樹:

1 var expSyntaxParser = new SyntaxParserExpression();
2 expSyntaxParser.SetTokenList(tokens);
3 var syntaxTree = expSyntaxParser.Parse();

得到的語法樹是一個樹的結構,可以表示如下:

<Expression>
  ├─<Multiply>
  │  ├─<Unit>
  │  │  └─number(19)
  │  └─<MultiplyOpt>
  │     ├─*
  │     └─<Unit>
  │        └─number(19)
  └─<PlusOpt> 
     ├─- 
     └─<Multiply> 
        ├─<Unit> 
        │  └─number(18) 
        └─<MultiplyOpt> 
           ├─* 
           └─<Unit> 
              └─number(18)

從此樹中可以看到,樹的結構和上文的文法展開過程是對應的,並且樹的葉結點從上到下組成了我們的例子【19 * 19 - 18 * 18】

然后就是語義分析了。到目前為止(據我所學到的),人類還沒有完善的自動生成語義分析代碼的能力。我們在此處就把”計算結果“作為語義分析的任務。仍以上例進行說明。各個葉結點的含義我們是知道的,【+】【-】【*】【/】代表運算,【number】代表數值,【identifier】代表變量名。那么在沒有【identifier】的時候,數和數就直接算出結果來,有【identifier】就保留着不動。我們分別為Expression文法的各類結點都賦予語義:

<Expression>:將它的兩個子結點進行運算或保留。

<Multiply>:將它的兩個子結點進行運算或保留。

<PlusOpt>:去掉自己,用自己的子結點代替自己的位置。

<Unit>:去掉自己,用自己的子結點代替自己的位置。

<MultiplyOpt>:去掉自己,用自己的子結點代替自己的位置。

“+”:對自己的左右結點進行加法運算。

“-”:對自己的左右結點進行減法運算。

“*”:對自己的左右結點進行乘法運算。

“/”:對自己的左右結點進行除法運算。

identifier:保持不變。

number:保持不變。

“(“:若自己右部的<Expression>成為數字或單一的【identifier】,則去掉自己,去掉<Expression>右部的”)”;否則不變。

“)”:保持不變。

上例經過語義分析(對語法樹自頂向下進行遞歸分析其語義),就得到一個數值”37“。

語義分析的偽代碼如下:

語義分析偽代碼
 1 SyntaxTreeExpression SemanticAnalyze(SyntaxTree root)
 2 
 3 {
 4 
 5     switch(root.NodeType)
 6 
 7     {
 8 
 9     case EnumTreeNodeType.Expression:
10 
11           return Cacul(SemanticAnalyze(root.Children[0]),SemanticAnalyze(root.Children[1]));
12 
13           break;
14 
15     case EnumTreeNodeType.Multiply:
16 
17           return Cacul(SemanticAnalyze(root.Children[0]),SemanticAnalyze(root.Children[1]));
18 
19           break;
20 
21     case EnumTreeNodeType.PlusOpt:
22 
23           var child = SemanticAnalyze(root.Children[0]);
24 
25           var child2 = SemanticAnalyze(root.Children[1]);
26 
27           root.parent.Children[1] = child; root.parent.Children[2] = child2;
28 
29           break;
30 
31     case EnumTreeNodeType.Unit:
32 
33           root.parent.Children[0] = root.Children[0];
34 
35           break;
36 
37     //
38 
39     case EnumTreeNodeType.Plus:// “+”
40 
41           return Calcu(SemanticAnalyze(root.parent.Children[0]), SemanticAnalyze(root.parent.Children[2]));
42 
43           break;
44 
45     //
46 
47 }

語義分析完成,我們這個編譯器前端也就大功告成了。

所以這個編譯器要實現的東西大體感覺就是這樣的。雖然單單對Expression進行編譯分析是沒多大意思的,但是這個例子在足夠簡單的同時,又足夠典型,等我們把這個例子實現了,再復雜的編譯器也都能做出來了。編譯器制作步驟比較多,工作量也大,如果一上來就抱着完整的C語言文法來做,等於把自己埋在深不見底的BUG海洋中活活淹死。

以后實現了編譯器的語法分析后,就可以自動生成示例中的語法樹了,其實這也算是一種語義分析。

后面系列文章將給出具體的設計和實現過程,以及完整的工程代碼。敬請關注!

關於本系列有什么好的建議,也請提出來,O(∩_∩)O謝謝!

PS:下面給出【(19 + 18) * (19 - 18)】的語法樹,供大家學習參考,也方便后續文章講解。

<Expression>
  ├─<Multiply>
  │  ├─<Unit>
  │  │  ├─(
  │  │  ├─<Expression>
  │  │  │  ├─<Multiply>
  │  │  │  │  ├─<Unit>
  │  │  │  │  │  └─number(19)
  │  │  │  │  └─<MultiplyOpt>
  │  │  │  │     └─null
  │  │  │  └─<PlusOpt>
  │  │  │     ├─+ 
  │  │  │     └─<Multiply>
  │  │  │        ├─<Unit>
  │  │  │        │  └─number(18)
  │  │  │        └─<MultiplyOpt>
  │  │  │           └─null
  │  │  └─)
  │  └─<MultiplyOpt>
  │     ├─*
  │     └─<Unit>
  │        ├─(
  │        ├─<Expression>
  │        │  ├─<Multiply>
  │        │  │  ├─<Unit>
  │        │  │  │  └─number(19)
  │        │  │  └─<MultiplyOpt>
  │        │  │     └─null
  │        │  └─<PlusOpt>
  │        │     ├─-
  │        │     └─<Multiply>
  │        │        ├─<Unit>
  │        │        │  └─number(18)
  │        │        └─<MultiplyOpt>
  │        │           └─null
  │        └─)
  └─<PlusOpt> 
     └─null

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM