編譯原理是軟件工程的一項基礎的課程,是研究軟件是什麼,爲何能夠運行,以及怎麼運行的學科,編譯系統的改進將會直接對其上層的應用程序的執行效率,執行原理產生深入的影響。編譯原理的目的是將源語言翻譯成目標語言。與翻譯的區別就是,編譯將高級語言編譯成低級語言。至於達到什麼樣的低級語言,在不一樣的系統中是不一樣的,對於不一樣的機器都要用相應的指令系統,編譯的目的就是將編譯出來的語言用目標機的指令系統執行,通常而言是翻譯到彙編語言的層次,但也有特例,好比JVM,Java虛擬機是將高級語言編譯到中間語言環節,對於任何的高級語言,都翻譯成相同的本身能夠識別的中間語言,這樣就能夠在不一樣的機型上運行了,這種獨特的創意造就了與平臺無關的語言識別器——虛擬機的出現,從本質上來講也是用到了編譯原理。編譯原理的內容很是豐富,技術很是成熟,有着幾十年的研究歷史,筆者在大學學習《編譯原理》的時候,僥倖在最後的期末考試中得到了滿分的優異成績,這樣一門內容豐富、邏輯嚴謹、極度抽象和形式化的課程,筆者是怎麼學的呢,無外乎兩個字——興趣。只要有興趣,任何困難都會顯得微不足道!!!轉瞬之間,大學已接近尾聲,在即將踏出校門的一刻,忽然有種什麼事情沒有完成的感受,仔細想一想,本身在大學學了不少的知識,本身究竟掌握的怎麼樣了,是否是很好的收集和整理了?想到這裏,不覺出了冷汗,所以,在之後的空閒時間就把本身學到的知識,以爲有意思的東西拿出來給你們欣賞,一是爲了本身之後記憶,二是爲了交流和共享。我一直相信這個時代最偉大的一種變革就是交流和共享,由於有溝通,有彼此的相互瞭解,相互學習才能打破人類歷史幾千年來的閉門造車、敝帚自珍,人人都獻出一點有用的、精華的信息,隨着時代的發展,幾十年,上百年以後,文明將會變得更加的璀璨和瑰麗,這個世界將變得更加美好!在這個系列中,我將拿出兩個貫穿編譯原理的例子來讓你們可以比較輕鬆的理解編譯上的問題,一個是詞法分析器,另外一個是在詞法分析的基礎上,進行算符優先分析文法的語法分析,而且生成中間代碼,執行代碼的例子,這兩個例子都是本人通過了十來天的編程調試,用C/C++實現的,便於理解,有着很好的學習指導意義!ios
2.一、設計題目:手工設計c語言的詞法分析器 (能夠是c語言的子集)git
2.二、設計內容: 處理c語言源程序,過濾掉無用符號,判斷源程序中單詞的合法性,並分解出正確的單詞,以二元組形式存放在文件中。算法
2.三、設計目的: 瞭解高級語言單詞的分類,瞭解狀態圖以及如何表示並識別單詞規則,掌握狀態圖到識別程序的編程。編程
2.四、分析與設計
數組
要想手工設計詞法分析器,實現C語言子集的識別,就要明白什麼是詞法分析器,它的功能是什麼。詞法分析是編譯程序進行編譯時第一個要進行的任務,主要是對源程序進行編譯預處理(去除註釋、無用的回車換行找到包含的文件等)以後,對整個源程序進行分解,分解成一個個單詞,這些單詞有且只有五類,分別是標識符、保留字、常數、運算符、界符。以便爲下面的語法分析和語義分析作準備。能夠說詞法分析面向的對象是單個的字符,目的是把它們組成有效的單詞(字符串);而語法的分析則是利用詞法分析的結果做爲輸入來分析是否符合語法規則而且進行語法制導下的語義分析,最後產生四元組(中間代碼),進行優化(無關緊要)以後最終生成目標代碼。可見詞法分析是全部後續工做的基礎,若是這一步出錯,好比明明是‘<=’卻被拆分紅‘<’和‘=’就會對下文形成不可挽回的影響。所以,在進行詞法分析的時候必定要定義好這五種符號的集合。下面是我構造的一個C語言子集。函數
第一類:標識符 letter(letter | digit)* 無窮集
第二類:常數 (digit)+ 無窮集
第三類:保留字(32)
auto break case char const continue
default do double else enum extern
float for goto if int long
register return short signed sizeof static
struct switch typedef union unsigned void
volatile while
第四類:界符 ‘/*’、‘//’、 () { } [ ] " " ' 等
第五類:運算符 <、<=、>、>=、=、+、-、*、/、^、等
對全部可數符號進行編碼:
<$,0>
<auto,1>
...
<while,32>
<+,33>
<-,34>
<*,35>
</,36>
<<,37>
<<=,38>
<>,39>
<>=,40>
<=,41>
<==,42>
<!=,43>
<;,44>
<(,45>
<),46>
<^,47>
<,,48>
<",49>
<',50>
<#,51>
<&,52>
<&&,53>
<|,54>
<||,55>
<%,56>
<~,57>
<<<,58>左移
<>>,59>右移
<[,60>
<],61>
<{,62>
<},63>
<\,64>
<.,65>
<?,66>
<:,67>
<!,68>
"[","]","{","}"
<常數99 ,數值>
<標識符100 ,標識符指針>
上述二元組中左邊是單詞的符號,右邊爲其種別碼,其中常數和標識符有點特別,由於是無窮集合,所以常數用自身來表示,種別碼爲99,標識符用標識符符號表的指針表示(固然也可用自身顯示,比較容易觀察),種別碼100。根據上述約定,一旦見到了種別碼syn=63,就惟一肯定了‘}’這個單詞。
下面是一些變量的約定:
//全局變量,保留字表
static char reserveWord[32][20] = {
"auto", "break", "case", "char", "const", "continue",
"default", "do", "double", "else", "enum", "extern",
"float", "for", "goto", "if", "int", "long",
"register", "return", "short", "signed", "sizeof", "static",
"struct", "switch", "typedef", "union", "unsigned", "void",
"volatile", "while"
};
//界符運算符表,根據須要能夠自行增長
static char operatorOrDelimiter[36][10]={
"+","-","*","/","<","<=",">",">=","=","==",
"!=",";","(",")","^",",","\"","\'","#","&",
"&&","|","||","%","~","<<",">>","[","]","{",
"}","\\",".","\?",":","!"
};
static char IDentifierTbl[1000][50]={""};//標識符表
char resourceProject[10000];//輸入的源程序存放處,最大能夠存放10000個字符。
char token[20]={0};//每次掃描的時候存儲已經掃描的結果。
int syn=-1;//syn即爲種別碼,約定‘$’的種別碼爲0,爲整個源程序的結束符號一旦掃描到這個字符表明掃描結束
int pProject = 0;//源程序指針,始終指向當前源程序待掃描位置。
幾個重要函數:
//查找保留字,若成功查找,則返回種別碼
//不然返回-1,表明查找不成功,即爲標識符
int searchReserve(char reserveWord[ ][20], char s[])
/*********************判斷是否爲字母********************/
bool IsLetter(char letter)
/*****************判斷是否爲數字************************/
bool IsDigit(char digit)
/********************編譯預處理,取出無用的字符和註釋**********************/
void filterResource(char r[],int pProject)
/****************************分析子程序,算法核心***********************/
void Scanner(int &syn,char resourceProject[],char token[],int &pProject)
學習
下面說一下整個程序的流程:
1.詞法分析程序打開源文件,讀取文件內容,直至趕上’$’文件結束符,而後讀取結束。
2.對讀取的文件進行預處理,從頭至尾進行掃描,去除//和/* */的內容,以及一些無用的、影響程序執行的符號如換行符、回車符、製表符等。可是千萬注意不要在這個時候去除空格,由於空格在詞法分析中有用,好比說int i=3;這個語句,若是去除空格就變成了「inti=3」,這樣就失去了程序的本意,所以不能在這個時候去除空格。
3.選下面就要對源文件從頭至尾進行掃描了,從頭開始掃描,這個時候掃描程序首先要詢問當前的字符是否是空格,如果空格,則繼續掃描下一個字符,直至不是空格,而後詢問這個字符是否是字母,如果則進行標識符和保留字的識別;若這個字符爲數字,則進行數字的判斷。不然,依次對這個字符可能的狀況進行判斷,如果將全部可能都走了一遍仍是沒有知道它是誰,則認定爲錯誤符號,輸出該錯誤符號,而後結束。每次成功識別了一個單詞後,單詞都會存在token[ ]中。而後肯定這個單詞的種別碼,最後進行下一個單詞的識別。這就是掃描程序進行的工做,能夠說這個程序完全實現了肯定有限自動機的某些功能,好比說識別標識符,識別數字等。爲了簡單起見,這裏的數字只是整數。
4.主控程序主要負責對每次識別的種別碼syn進行判斷,對於不一樣的單詞種別作出不一樣的反應,如對於標識符則將其插入標識符表中。對於保留字則輸出該保留字的種別碼和助記符,等等吧。直至遇到syn=0;程序結束。
測試
2.五、流程圖
下面是程序的流程圖:
優化
2.六、運行與測試
好比說,就拿這個源程序的一部分進行測試:
this
運行程序後結果爲:
一樣單詞也寫入了文件以下:
。。。
綜上分析,達到了預期的結果。
2.七、實驗體會
每作一次比較大的實驗,都應該寫一下實驗體會,來加深本身對知識的認識。其實此次的實驗,算法部分並不難,只要知道了DFA,這個模塊很好寫,比較麻煩的就是五種類型的字符個數越多程序就越長。但爲了能識別大部分程序,我仍是用了比較大的子集,結果花了一下午的功夫才寫完,雖然很累吧,但看着這個詞法分析器的處理能力,以爲仍是值得的。同時也加深了對字符的認識。程序的可讀性還算不錯。程序沒有實現的是對全部複合運算的分離,但原理是相同的,好比「+=「,只需在」+「的邏輯以後向前掃描就好了,所以就沒有再加上了。感覺最深的是學習編譯原理必需要作實驗,寫程序,這樣纔會提升本身的動手能力,加深本身對難點的理解,對於之後的求first{},follow{},fisrtVT{},lastVT{}更是應該如此。
2.八、源程序
1 // Lexical_Analysis.cpp : 定義控制檯應用程序的入口點。 2 // 3 #include "stdio.h" 4 #include "stdlib.h" 5 #include "string.h" 6 #include "iostream" 7 using namespace std; 8 //詞法分析程序 9 //首先定義種別碼 10 /* 11 第一類:標識符 letter(letter | digit)* 無窮集 12 第二類:常數 (digit)+ 無窮集 13 第三類:保留字(32) 14 auto break case char const continue 15 default do double else enum extern 16 float for goto if int long 17 register return short signed sizeof static 18 struct switch typedef union unsigned void 19 volatile while 20 21 第四類:界符 ‘/*’、‘//’、 () { } [ ] " " ' 22 第五類:運算符 <、<=、>、>=、=、+、-、*、/、^、 23 24 對全部可數符號進行編碼: 25 <$,0> 26 <auto,1> 27 ... 28 <while,32> 29 <+,33> 30 <-,34> 31 <*,35> 32 </,36> 33 <<,37> 34 <<=,38> 35 <>,39> 36 <>=,40> 37 <=,41> 38 <==,42> 39 <!=,43> 40 <;,44> 41 <(,45> 42 <),46> 43 <^,47> 44 <,,48> 45 <",49> 46 <',50> 47 <#,51> 48 <&,52> 49 <&&,53> 50 <|,54> 51 <||,55> 52 <%,56> 53 <~,57> 54 <<<,58>左移 55 <>>,59>右移 56 <[,60> 57 <],61> 58 <{,62> 59 <},63> 60 <\,64> 61 <.,65> 62 <?,66> 63 <:,67> 64 <!,68> 65 "[","]","{","}" 66 <常數99 ,數值> 67 <標識符100 ,標識符指針> 68 69 70 */ 71 72 /****************************************************************************************/ 73 //全局變量,保留字表 74 static char reserveWord[32][20] = { 75 "auto", "break", "case", "char", "const", "continue", 76 "default", "do", "double", "else", "enum", "extern", 77 "float", "for", "goto", "if", "int", "long", 78 "register", "return", "short", "signed", "sizeof", "static", 79 "struct", "switch", "typedef", "union", "unsigned", "void", 80 "volatile", "while" 81 }; 82 //界符運算符表,根據須要能夠自行增長 83 static char operatorOrDelimiter[36][10] = { 84 "+", "-", "*", "/", "<", "<=", ">", ">=", "=", "==", 85 "!=", ";", "(", ")", "^", ",", "\"", "\'", "#", "&", 86 "&&", "|", "||", "%", "~", "<<", ">>", "[", "]", "{", 87 "}", "\\", ".", "\?", ":", "!" 88 }; 89 90 static char IDentifierTbl[1000][50] = { "" };//標識符表 91 /****************************************************************************************/ 92 93 /********查找保留字*****************/ 94 int searchReserve(char reserveWord[][20], char s[]) 95 { 96 for (int i = 0; i < 32; i++) 97 { 98 if (strcmp(reserveWord[i], s) == 0) 99 {//若成功查找,則返回種別碼 100 return i + 1;//返回種別碼 101 } 102 } 103 return -1;//不然返回-1,表明查找不成功,即爲標識符 104 } 105 /********查找保留字*****************/ 106 107 /*********************判斷是否爲字母********************/ 108 bool IsLetter(char letter) 109 {//注意C語言容許下劃線也爲標識符的一部分能夠放在首部或其餘地方 110 if (letter >= 'a'&&letter <= 'z' || letter >= 'A'&&letter <= 'Z'|| letter=='_') 111 { 112 return true; 113 } 114 else 115 { 116 return false; 117 } 118 } 119 /*********************判斷是否爲字母********************/ 120 121 122 /*****************判斷是否爲數字************************/ 123 bool IsDigit(char digit) 124 { 125 if (digit >= '0'&&digit <= '9') 126 { 127 return true; 128 } 129 else 130 { 131 return false; 132 } 133 } 134 /*****************判斷是否爲數字************************/ 135 136 137 /********************編譯預處理,取出無用的字符和註釋**********************/ 138 void filterResource(char r[], int pProject) 139 { 140 char tempString[10000]; 141 int count = 0; 142 for (int i = 0; i <= pProject; i++) 143 { 144 if (r[i] == '/'&&r[i + 1] == '/') 145 {//若爲單行註釋「//」,則去除註釋後面的東西,直至遇到回車換行 146 while (r[i] != '\n') 147 { 148 i++;//向後掃描 149 } 150 } 151 if (r[i] == '/'&&r[i + 1] == '*') 152 {//若爲多行註釋「/* 。。。*/」則去除該內容 153 i += 2; 154 while (r[i] != '*' || r[i + 1] != '/') 155 { 156 i++;//繼續掃描 157 if (r[i] == '$') 158 { 159 printf("註釋出錯,沒有找到 */,程序結束!!!\n"); 160 exit(0); 161 } 162 } 163 i += 2;//跨過「*/」 164 } 165 if (r[i] != '\n'&&r[i] != '\t'&&r[i] != '\v'&&r[i] != '\r') 166 {//若出現無用字符,則過濾;不然加載 167 tempString[count++] = r[i]; 168 } 169 } 170 tempString[count] = '\0'; 171 strcpy(r, tempString);//產生淨化以後的源程序 172 } 173 /********************編譯預處理,取出無用的字符和註釋**********************/ 174 175 176 /****************************分析子程序,算法核心***********************/ 177 void Scanner(int &syn, char resourceProject[], char token[], int &pProject) 178 {//根據DFA的狀態轉換圖設計 179 int i, count = 0;//count用來作token[]的指示器,收集有用字符 180 char ch;//做爲判斷使用 181 ch = resourceProject[pProject]; 182 while (ch == ' ') 183 {//過濾空格,防止程序因識別不了空格而結束 184 pProject++; 185 ch = resourceProject[pProject]; 186 } 187 for (i = 0; i<20; i++) 188 {//每次收集前先清零 189 token[i] = '\0'; 190 } 191 if (IsLetter(resourceProject[pProject])) 192 {//開頭爲字母 193 token[count++] = resourceProject[pProject];//收集 194 pProject++;//下移 195 while (IsLetter(resourceProject[pProject]) || IsDigit(resourceProject[pProject])) 196 {//後跟字母或數字 197 token[count++] = resourceProject[pProject];//收集 198 pProject++;//下移 199 }//多讀了一個字符既是下次將要開始的指針位置 200 token[count] = '\0'; 201 syn = searchReserve(reserveWord, token);//查表找到種別碼 202 if (syn == -1) 203 {//若不是保留字則是標識符 204 syn = 100;//標識符種別碼 205 } 206 return; 207 } 208 else if (IsDigit(resourceProject[pProject])) 209 {//首字符爲數字 210 while (IsDigit(resourceProject[pProject])) 211 {//後跟數字 212 token[count++] = resourceProject[pProject];//收集 213 pProject++; 214 }//多讀了一個字符既是下次將要開始的指針位置 215 token[count] = '\0'; 216 syn = 99;//常數種別碼 217 } 218 else if (ch == '+' || ch == '-' || ch == '*' || ch == '/' || ch == ';' || ch == '(' || ch == ')' || ch == '^' 219 || ch == ',' || ch == '\"' || ch == '\'' || ch == '~' || ch == '#' || ch == '%' || ch == '[' 220 || ch == ']' || ch == '{' || ch == '}' || ch == '\\' || ch == '.' || ch == '\?' || ch == ':') 221 {//若爲運算符或者界符,查表獲得結果 222 token[0] = resourceProject[pProject]; 223 token[1] = '\0';//造成單字符串 224 for (i = 0; i<36; i++) 225 {//查運算符界符表 226 if (strcmp(token, operatorOrDelimiter[i]) == 0) 227 { 228 syn = 33 + i;//得到種別碼,使用了一點技巧,使之呈線性映射 229 break;//查到即推出 230 } 231 } 232 pProject++;//指針下移,爲下一掃描作準備 233 return; 234 } 235 else if (resourceProject[pProject] == '<') 236 {//<,<=,<< 237 pProject++;//後移,超前搜索 238 if (resourceProject[pProject] == '=') 239 { 240 syn = 38; 241 } 242 else if (resourceProject[pProject] == '<') 243 {//左移 244 pProject--; 245 syn = 58; 246 } 247 else 248 { 249 pProject--; 250 syn = 37; 251 } 252 pProject++;//指針下移 253 return; 254 } 255 else if (resourceProject[pProject] == '>') 256 {//>,>=,>> 257 pProject++; 258 if (resourceProject[pProject] == '=') 259 { 260 syn = 40; 261 } 262 else if (resourceProject[pProject] == '>') 263 { 264 syn = 59; 265 } 266 else 267 { 268 pProject--; 269 syn = 39; 270 } 271 pProject++; 272 return; 273 } 274 else if (resourceProject[pProject] == '=') 275 {//=.== 276 pProject++; 277 if (resourceProject[pProject] == '=') 278 { 279 syn = 42; 280 } 281 else 282 { 283 pProject--; 284 syn = 41; 285 } 286 pProject++; 287 return; 288 } 289 else if (resourceProject[pProject] == '!') 290 {//!,!= 291 pProject++; 292 if (resourceProject[pProject] == '=') 293 { 294 syn = 43; 295 } 296 else 297 { 298 syn = 68; 299 pProject--; 300 } 301 pProject++; 302 return; 303 } 304 else if (resourceProject[pProject] == '&') 305 {//&,&& 306 pProject++; 307 if (resourceProject[pProject] == '&') 308 { 309 syn = 53; 310 } 311 else 312 { 313 pProject--; 314 syn = 52; 315 } 316 pProject++; 317 return; 318 } 319 else if (resourceProject[pProject] == '|') 320 {//|,|| 321 pProject++; 322 if (resourceProject[pProject] == '|') 323 { 324 syn = 55; 325 } 326 else 327 { 328 pProject--; 329 syn = 54; 330 } 331 pProject++; 332 return; 333 } 334 else if (resourceProject[pProject] == '$') 335 {//結束符 336 syn = 0;//種別碼爲0 337 } 338 else 339 {//不能被以上詞法分析識別,則出錯。 340 printf("error:there is no exist %c \n", ch); 341 exit(0); 342 } 343 } 344 345 346 int main() 347 { 348 //打開一個文件,讀取其中的源程序 349 char resourceProject[10000]; 350 char token[20] = { 0 }; 351 int syn = -1, i;//初始化 352 int pProject = 0;//源程序指針 353 FILE *fp, *fp1; 354 if ((fp = fopen("D:\\zyr_rc.txt", "r")) == NULL) 355 {//打開源程序 356 cout << "can't open this file"; 357 exit(0); 358 } 359 resourceProject[pProject] = fgetc(fp); 360 while (resourceProject[pProject] != '$') 361 {//將源程序讀入resourceProject[]數組 362 pProject++; 363 resourceProject[pProject] = fgetc(fp); 364 } 365 resourceProject[++pProject] = '\0'; 366 fclose(fp); 367 cout << endl << "源程序爲:" << endl; 368 cout << resourceProject << endl; 369 //對源程序進行過濾 370 filterResource(resourceProject, pProject); 371 cout << endl << "過濾以後的程序:" << endl; 372 cout << resourceProject << endl; 373 pProject = 0;//從頭開始讀 374 375 if ((fp1 = fopen("D:\\zyr_compile.txt", "w+")) == NULL) 376 {//打開源程序 377 cout << "can't open this file"; 378 exit(0); 379 } 380 while (syn != 0) 381 { 382 //啓動掃描 383 Scanner(syn, resourceProject, token, pProject); 384 if (syn == 100) 385 {//標識符 386 for (i = 0; i<1000; i++) 387 {//插入標識符表中 388 if (strcmp(IDentifierTbl[i], token) == 0) 389 {//已在表中 390 break; 391 } 392 if (strcmp(IDentifierTbl[i], "") == 0) 393 {//查找空間 394 strcpy(IDentifierTbl[i], token); 395 break; 396 } 397 } 398 printf("(標識符 ,%s)\n", token); 399 fprintf(fp1, "(標識符 ,%s)\n", token); 400 } 401 else if (syn >= 1 && syn <= 32) 402 {//保留字 403 printf("(%s , --)\n", reserveWord[syn - 1]); 404 fprintf(fp1, "(%s , --)\n", reserveWord[syn - 1]); 405 } 406 else if (syn == 99) 407 {//const 常數 408 printf("(常數 , %s)\n", token); 409 fprintf(fp1, "(常數 , %s)\n", token); 410 } 411 else if (syn >= 33 && syn <= 68) 412 { 413 printf("(%s , --)\n", operatorOrDelimiter[syn - 33]); 414 fprintf(fp1, "(%s , --)\n", operatorOrDelimiter[syn - 33]); 415 } 416 } 417 for (i = 0; i<100; i++) 418 {//插入標識符表中 419 printf("第%d個標識符: %s\n", i + 1, IDentifierTbl[i]); 420 fprintf(fp1, "第%d個標識符: %s\n", i + 1, IDentifierTbl[i]); 421 } 422 fclose(fp1); 423 return 0; 424 }