編譯原理實踐:計算器

概述

  本博客主要講述如何利用編譯原理的知識實現一個控制檯計算器.若是以前利用棧(在學數據結構的時候)實現過計算器,必定會有所印象,寫一個計算器程序最重要的就是把握運算優先級了.而本文換一個角度,利用文法的知識來實現一個功能齊全的計算器.雖然用編譯原理的理論來作計算器實在有點殺雞焉用宰牛刀的味道,但如此實踐確實是有必要的."合抱之木,生於毫末;九層之臺,起於累土;千里之行,始於足下."git

  須要說明的是,本文對於不懂編譯原理的人來講基本算是天書了,因此,請仔細閱讀預備知識.github

預備知識

  須要說明的是,越到後面會愈加現,寫一個程序並非你會了幾種語言的問題,語言只是一個工具,沒有紮實的理論,永遠寫不出好的程序.正則表達式

  1. 編譯原理的知識.好比:若是你不知道怎麼消除左遞歸,那麼徹底不必往下看了.
  2. C語言(用於手工構造),lex/yacc(不懂這一部分能夠只看手工構造)

原理分析

  相信這一部分對於熟悉編譯原理的人沒有難度.如下是一個計算器輸入語句的文法定義express

  expression : term | expression + term | expression -  term;編程

  term : primary_expression | term * primary_expression | term / primary_expression;數據結構

  primary_expression : DOUBLE_LITERAL | ( expression ) | - primary_expression;app

  以上文法直接運用到yacc是沒問題的,由於yacc生成的是一個LALR(1)解析器,但手工構造就沒那麼幸運了,老司機一眼就能看出來,這是一個左遞歸的BNF,因此須要消除左遞歸.編程語言

  expression : term half_expression;ide

  half_expression : + term half_expression | - term half_expression | ε;函數

  term :  primary_expression half_term;

  half_term : * primary_expression | / primary_expression | ε;

  primary_expression : DOUBLE_LITERAL | ( expression ) | - primary_expression;

   求一下這個文法的FIRST和FOLLOW集:

   

  構造一下預測分析表就知道是一個LL(1)文法.   

  以上是簡單的原理分析.

手工構造

  所謂手工構造就是本身編寫程序,這裏使用的是C語言,固然了,其餘語言也能夠.不過這裏多說一句話,若是完整的實現一個編譯器,必然要涉及將源程序轉化爲機器碼,這是個面向底層的工做,十分適合C語言來處理.並且lex/yacc生成的也是C程序,雖然也有了像JavaCC等其餘語言的詞法/語法分析生成器,但仍是建議使用C語言.

  趙裕-GitHub-calculator存放了全部代碼,llparser_version目錄下就是手工構造的源代碼.

  手工構造通常採用遞歸降低法(也稱遞歸子程序法),每當遇到一個終結符就會調用一個對應的子函數進行解析,這裏不對源代碼作原理性詳細的分析,由於我假設你已經熟悉了理論層面的知識,只是缺少一個實踐的參照,學習理解這個程序最好的辦法就是clone下來,本身進行單步調試,這是最有效的學習辦法.

  如下是歸納性的程序說明:

  1. token.h存放基本的聲明,如+,-,*,/,(,)的標記
  2. lexical_analyzer.c存放詞法解析程序,起中包含的一個主要函數get_token()用於返回token
  3. parser.c存放語法分析程序,包含主函數,以及每一個非終結符對應的函數.
  4. 語法分析程序對於超前讀取的字符,若是不須要則退回,也能夠保持始終預讀一個字符.本代碼採用前一種.

lex/yacc構造

關於lex/yacc

  關於lex/yacc這裏不作過多的介紹,學過編譯原理或多或少都知道一點,O'Reilly出版社的Lex&Yacc是爲數很少系統講解這兩個工具的書籍,須要深刻了解的能夠閱讀閱讀.

構造計算器

  一樣,相關代碼放在了lex-yacc_version目錄下面,這裏也不直接給出代碼,相對於手工構造的代碼,lex和yacc的代碼都十分易讀(前提是你十分熟悉正則表達式文法),只是你在使用這兩個工具的時候必須熟悉他們的一套規則,這兩個工具都有些年頭了,因此有些地方或者說有些設計理念可能不是那麼優雅,但他們確實十分強大!

  最後,利用這兩個工具生成的C代碼頗有必要打開看看,通常來講詞法分析手工構造尚可,但語法分析利用工具確實省時省力又高效,因此,十分有必要看看到底生成的了怎樣的代碼.

  即便不懂lex/yacc,只要按照以下步驟編譯,應該就能獲得C語言的目標代碼:

  

  使用-dv參數是爲了生成一個輔助文件y.output,這個文件包含不少有用信息,並且若是出現了衝突能夠給出詳細的說明.能夠本身打開看看,如下是該文件的一部分:

 1 Grammar
 2 
 3     0 $accept: line_list $end
 4 
 5     1 line_list: line
 6     2          | line_list line
 7 
 8     3 line: expression CR
 9     4     | error CR
10 
11     5 expression: term
12     6           | expression ADD term
13     7           | expression SUB term
14 
15     8 term: primary_expression
16     9     | term MUL primary_expression
17    10     | term DIV primary_expression
18 
19    11 primary_expression: DOUBLE_LITERAL
20    12                   | LP expression RP
21    13                   | SUB primary_expression
22 
23 
24 Terminals, with rules where they appear
25 
26 $end (0) 0
27 error (256) 4
28 DOUBLE_LITERAL (258) 11
29 ADD (259) 6
30 SUB (260) 7 13
31 MUL (261) 9
32 DIV (262) 10
33 CR (263) 3 4
34 LP (264) 12
35 RP (265) 12
36 
37 
38 Nonterminals, with rules where they appear
39 
40 $accept (11)
41     on left: 0
42 line_list (12)
43     on left: 1 2, on right: 0 2
44 line (13)
45     on left: 3 4, on right: 1 2
46 expression (14)
47     on left: 5 6 7, on right: 3 6 7 12
48 term (15)
49     on left: 8 9 10, on right: 5 6 7 9 10
50 primary_expression (16)
51     on left: 11 12 13, on right: 8 9 10 13
52 
53 
54 State 0
55 
56     0 $accept: . line_list $end
57 
58     error           shift, and go to state 1
59     DOUBLE_LITERAL  shift, and go to state 2
60     SUB             shift, and go to state 3
61     LP              shift, and go to state 4
62 
63     line_list           go to state 5
64     line                go to state 6
65     expression          go to state 7
66     term                go to state 8
67     primary_expression  go to state 9
68 
69 
70 State 1
71 
72     4 line: error . CR
73 
74     CR  shift, and go to state 10
75 
76 
77 State 2
78 
79    11 primary_expression: DOUBLE_LITERAL .
80 
81     $default  reduce using rule 11 (primary_expression)
82 ......
View Code

 

小結

  本文介紹了編譯原理在編寫計算器上的實踐,講的很簡略(由於我沒有講解代碼,也沒有一步一步分析原理,畢竟這不是一兩句話的事),充分理解這些在信息的最好方法就是本身對着代碼敲一遍.

  很久沒寫博客了,感受以前寫的好多質量都不夠高,但願從本篇開始本身可以以一個更務實的心態寫一些有水平的東西,作一些有深度的總結.

參考

  <<自制編程語言>>,前橋和彌.

相關文章
相關標籤/搜索