前言:本實驗采用自上而下的方法實現算術表達式的語法分析器。只是實現了對加減乘數和帶括號的語法分析,判斷語法的正確性。
一 實驗要求:
(1)程序通過標准輸入按行讀取用戶輸入,表達式在1行內讀完。
(2)程序對用戶輸入的內容首先進行詞法分析處理(可以復用實驗一的部分代碼,由於詞法規則更簡單,可以大大簡化),詞法分析得到的詞法單位對應文法中的終結符。//代碼太長直接就用詞法分析的結果進行語法分析。
(3)對於用戶輸入的表達式,如果經過分析后語法正確,給出相應提示。如果分析過程中遇到錯誤不需要嘗試恢復分析,停止該次分析過程即可,但應盡量給出說明性較強的錯誤提示。
下面給出一些驗證語法分析結果正確性的測試用例:
正確:a+3*( b + c/10) -4 +5 錯誤:a+3 ( b + c/10) 錯誤:(1+2)(3-4) 錯誤:(1+2
二:假設詞法分析已經正確的完成,只進行語法分析
比如以 a+3*( b + c/10) -4 +5為例,進行詞法分析后得到的結果為
1 (29,a) 2 (12,+) 3 (28,3) 4 (14,*) 5 (21,() 6 (29,b) 7 (12,+) 8 (29,c) 9 (15,/) 10 (28,10) 11 (22,)) 12 (13,-) 13 (28,4) 14 (12,+) 15 (28,5)
注意,上面的結果第一個參數是種別碼,種別碼的聲明如下(關於詞法分析的過程可具體參考我之前寫的詞法分析 https://www.cnblogs.com/henuliulei/p/10597281.html),當然種別碼可以自己聲明。
1 begin 1 2 end 2 3 if 3 4 then 4 5 while 5 6 do 6 7 const 7 8 var 8 9 call 9 10 procedure 10 11 odd 11 12 + 12 13 - 13 14 * 14 15 / 15 16 = 16 17 # 17 18 < 18 19 > 19 20 := 20 21 ( 21 22 ) 22 23 , 23 24 . 24 25 ; 25 26 (*多行注釋*) 26 27 //單行注釋 27 28 常數 28 29 標識符 29
輸入:
以上面的結果作為輸入
輸出:
對於語法正確的表達式,報告“正確:“+”表達式”;
對於語法錯誤的表達式,報告“語法錯誤”, 指出錯誤原因
設計思想
遞歸下降分析法的原理是利用函數之間的遞歸調用來模擬語法樹自上而下的構建過程。從根節點出發,自頂向下為輸入串中尋找一個最左匹配序列,建立一棵語法樹。在不含左遞歸和每個非終結符的所有候選終結首字符集都兩兩不相交條件下,我們就可能構造出一個不帶回溯的自頂向下的分析程 序,這個分析程序是由一組遞歸過程(或函數)組成的,每個過程(或函數)對應文法的而一個非終結符。
代碼:
1 #include<bits/stdc++.h> 2 using namespace std; 3 ifstream infile("D:/Test/Test1.txt"); 4 string str;//string變量進行字符識別 5 string sym; //指針 6 7 void expressionAnalysis();//表達式分析 8 void termAnaysis();//項分析 9 void factorAnalysis();//因子分析 10 int advance(); 11 12 int conterr=0;//記錄錯誤 13 int lpnum=0;//記錄左括號 14 int found;//提取字符串中指針的位置 15 int flag=0;//記錄往后移動一個指針SYM是否正確 16 17 int advance(){//SYM的移動 18 if(!getline(infile,str)){//從文件中提取字符 19 return 0; 20 } 21 found=str.find(',',0); 22 if(found==-1){//當為error的時候,沒有‘,’ 23 conterr++; 24 cout<<"語法錯誤 識別字符錯誤"<<endl; 25 return -1; 26 } 27 sym=str.substr(1,found-1); 28 //cout<<sym<<endl; 29 return 1; 30 } 31 32 void factorAnalysis(){//識別分析標識符 33 if(sym=="29"||sym=="28"){//如果是標識符和無符號整數,指針就往后移動 34 flag=advance(); 35 if(conterr){ 36 return; 37 } 38 if(lpnum==0&&sym=="22"){// 39 conterr++; 40 cout<<"語法錯誤 ')'不匹配"<<endl; 41 return; 42 } 43 } 44 else if(sym=="21"){//如果是左括號,就要接下來判斷是否為表達式,指針往后移動 45 lpnum++; 46 // cout<<lpnum<<endl; 47 flag=advance(); 48 if(conterr){ 49 return; 50 } 51 if(flag==0){//當為最后一個標志的時候,若沒有右括號匹配就錯誤 52 conterr++; 53 cout<<"語法錯誤 '('后缺少表達式"<<endl; 54 return; 55 } 56 expressionAnalysis(); 57 if(conterr){ 58 return; 59 } 60 if(flag==0||sym!="22"){ 61 conterr++; 62 cout<<"語法錯誤 表達式后面缺少')'"<<endl; 63 return; 64 }else{ 65 lpnum--; 66 flag=advance(); 67 if(conterr){ 68 return; 69 } 70 if(flag==0){ 71 return; 72 } 73 } 74 }else{ 75 cout<<"語法錯誤 因子首部不為<標識符>|| <無符號整數> ||'('"<<endl; 76 conterr++; 77 return; 78 } 79 return; 80 } 81 82 void termAnalysis(){//識別分析乘除符號 83 factorAnalysis(); 84 if(conterr){ 85 return; 86 } 87 while((sym=="14")||(sym=="15")){//當為'*'或'/'的時候,一直往后識別因子並循環 88 flag=advance(); 89 if(conterr){ 90 return; 91 } 92 if(flag==0){ 93 conterr++; 94 cout<<"語法錯誤 <乘除法運算符>后缺因子"<<endl; 95 return; 96 } 97 if(conterr){ 98 return; 99 } 100 factorAnalysis(); 101 if(conterr){ 102 return; 103 } 104 } 105 return; 106 }; 107 108 void expressionAnalysis(){//識別分析加減符號 109 if(conterr){ 110 return; 111 } 112 if((sym=="12")||(sym=="13")){//當為'+'或'-'的時候 113 flag=advance(); 114 if(!conterr){ 115 return; 116 } 117 if(flag==0){ 118 cout<<"語法錯誤 <加減法運算符>后缺項"<<endl; 119 conterr++; 120 return; 121 } 122 } 123 termAnalysis(); 124 if(conterr){ 125 return; 126 } 127 while((sym=="12")||(sym=="13")){//當為'+'或'-'的時候,一直往后識別項並循環 128 flag=advance(); 129 if(conterr){ 130 return; 131 } 132 if(flag==0){ 133 cout<<"語法錯誤 <加法運算符>后缺項"<<endl; 134 conterr++; 135 return; 136 } 137 termAnalysis(); 138 if(conterr){ 139 return; 140 } 141 } 142 return; 143 } 144 145 int main(){ 146 flag=advance(); 147 if(flag){ 148 expressionAnalysis(); 149 } 150 if(flag!=-1&&!conterr){ 151 cout<<"語法正確"<<endl; 152 } 153 return 0; 154 }
3:代碼中先進行詞法分析,再把詞法分析的結果作為輸入進行語法分析
本代碼里的路徑D:/Test/b.txt對應文件里放的是要詞法分析的表達式,D:/Test/a.txt放的是種別碼,程序會把詞法分析的結果放到D:/Test/c.txt里面,再讀到程序里面進行語法分析,把結果顯示在屏幕上,代碼里是上面的語法分析和之前寫的詞法分析的結合。
1 // pL/0語言詞法分析器 2 #include<bits/stdc++.h> 3 using namespace std; 4 5 ifstream infile("D:/Test/c.txt");//詞法分析的結果或語法分析的輸入 6 string str;//string變量進行字符識別 7 string sym; //指針 8 9 void expressionAnalysis();//表達式分析 10 void termAnaysis();//項分析 11 void factorAnalysis();//因子分析 12 int advance(); 13 14 int conterr=0;//記錄錯誤 15 int lpnum=0;//記錄左括號 16 int found;//提取字符串中指針的位置 17 int flag=0;//記錄往后移動一個指針SYM是否正確 18 string s;//用來保存要分析的字符串 19 struct _2tup 20 { 21 string token; 22 int id; 23 }; 24 25 int advance(){//SYM的移動 26 if(!getline(infile,str)){//從文件中提取字符 27 return 0; 28 } 29 found=str.find(',',0); 30 if(found==-1){//當為error的時候,沒有‘,’ 31 conterr++; 32 cout<<"語法錯誤 識別字符錯誤"<<endl; 33 return -1; 34 } 35 sym=str.substr(1,found-1); 36 //cout<<sym<<endl; 37 return 1; 38 } 39 40 void factorAnalysis(){//識別分析標識符 41 if(sym=="29"||sym=="28"){//如果是標識符和無符號整數,指針就往后移動 42 flag=advance(); 43 if(conterr){ 44 return; 45 } 46 if(lpnum==0&&sym=="22"){// 47 conterr++; 48 cout<<"語法錯誤 ')'不匹配"<<endl; 49 return; 50 } 51 } 52 else if(sym=="21"){//如果是左括號,就要接下來判斷是否為表達式,指針往后移動 53 lpnum++; 54 // cout<<lpnum<<endl; 55 flag=advance(); 56 if(conterr){ 57 return; 58 } 59 if(flag==0){//當為最后一個標志的時候,若沒有右括號匹配就錯誤 60 conterr++; 61 cout<<"語法錯誤 '('后缺少表達式"<<endl; 62 return; 63 } 64 expressionAnalysis(); 65 if(conterr){ 66 return; 67 } 68 if(flag==0||sym!="22"){ 69 conterr++; 70 cout<<"語法錯誤 表達式后面缺少')'"<<endl; 71 return; 72 }else{ 73 lpnum--; 74 flag=advance(); 75 if(conterr){ 76 return; 77 } 78 if(flag==0){ 79 return; 80 } 81 } 82 }else{ 83 cout<<"語法錯誤 因子首部不為<標識符>|| <無符號整數> ||'('"<<endl; 84 conterr++; 85 return; 86 } 87 return; 88 } 89 90 void termAnalysis(){//識別分析乘除符號 91 factorAnalysis(); 92 if(conterr){ 93 return; 94 } 95 while((sym=="14")||(sym=="15")){//當為'*'或'/'的時候,一直往后識別因子並循環 96 flag=advance(); 97 if(conterr){ 98 return; 99 } 100 if(flag==0){ 101 conterr++; 102 cout<<"語法錯誤 <乘除法運算符>后缺因子"<<endl; 103 return; 104 } 105 if(conterr){ 106 return; 107 } 108 factorAnalysis(); 109 if(conterr){ 110 return; 111 } 112 } 113 return; 114 }; 115 116 void expressionAnalysis(){//識別分析加減符號 117 if(conterr){ 118 return; 119 } 120 if((sym=="12")||(sym=="13")){//當為'+'或'-'的時候 121 flag=advance(); 122 if(!conterr){ 123 return; 124 } 125 if(flag==0){ 126 cout<<"語法錯誤 <加減法運算符>后缺項"<<endl; 127 conterr++; 128 return; 129 } 130 } 131 termAnalysis(); 132 if(conterr){ 133 return; 134 } 135 while((sym=="12")||(sym=="13")){//當為'+'或'-'的時候,一直往后識別項並循環 136 flag=advance(); 137 if(conterr){ 138 return; 139 } 140 if(flag==0){ 141 cout<<"語法錯誤 <加法運算符>后缺項"<<endl; 142 conterr++; 143 return; 144 } 145 termAnalysis(); 146 if(conterr){ 147 return; 148 } 149 } 150 return; 151 } 152 153 bool is_blank(char ch) 154 { 155 return ch == ' ' || ch == ' ';//空格或控制字符 156 } 157 bool gofor(char& ch, string::size_type& pos, const string& prog)//返回指定位置的字符 158 { 159 ++pos; 160 if (pos >= prog.size()) 161 { 162 return false; 163 } 164 else 165 { 166 ch = prog[pos]; 167 return true; 168 } 169 } 170 171 _2tup scanner(const string& prog, string::size_type& pos, const map<string, int>& keys, int& row) 172 { 173 /* 174 if 175 標示符 176 else if 177 數字 178 else 179 符號 180 */ 181 _2tup ret; 182 string token; 183 int id = 0; 184 185 char ch; 186 ch = prog[pos]; 187 188 while(is_blank(ch)) 189 { 190 ++pos; 191 ch = prog[pos]; 192 } 193 // 判斷標示符、關鍵字 194 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_') 195 { 196 //保證讀取一個單詞 197 while((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '_') 198 { 199 token += ch;//追加標示符、關鍵字 200 if (!gofor(ch, pos, prog)) 201 { 202 break; 203 } 204 } 205 // 這里先看做都是其他標示符 206 id = keys.size(); 207 208 // 驗證是否是關鍵字 209 map<string, int>::const_iterator cit = keys.find(token);//根據string類型的token返回int類型的id賦值給cit 210 if (cit != keys.end()) 211 { 212 id = cit->second;//此時是關鍵字,記錄他的id 213 } 214 } 215 // 識別常數 216 else if ((ch >= '0' && ch <= '9') || ch == '.') 217 { 218 while (ch >= '0' && ch <= '9' || ch == '.') 219 { 220 token += ch; 221 if (!gofor(ch, pos, prog)) 222 { 223 break; 224 } 225 } 226 id = keys.size() - 1; 227 int dot_num = 0; 228 for (string::size_type i = 0; i != token.size(); ++i) 229 { 230 if (token[i] == '.') 231 { 232 ++dot_num; 233 } 234 } 235 if (dot_num > 1) 236 { 237 id = -1; 238 } 239 } 240 else 241 { 242 map<string, int>::const_iterator cit; 243 switch (ch) 244 { 245 case '-': // - 操作符 246 token += ch; 247 if (gofor(ch, pos, prog)) 248 { 249 if (ch == '-' || ch == '=' || ch == '>') // -- 操作符 250 { 251 token += ch; 252 gofor(ch, pos, prog); 253 } 254 } 255 cit = keys.find(token); 256 if (cit != keys.end()) 257 { 258 id = cit->second; 259 } 260 break; 261 case ':': 262 token += ch; 263 if (gofor(ch, pos, prog)) 264 { 265 if (ch == '=') // -- 操作符 266 { 267 token += ch; 268 gofor(ch, pos, prog); 269 } 270 } 271 cit = keys.find(token); 272 if (cit != keys.end()) 273 { 274 id = cit->second; 275 } 276 break; 277 278 case '=': 279 token += ch; 280 if (gofor(ch, pos, prog)) 281 { 282 if (ch == '=') // !% %= 操作符 283 { 284 token += ch; 285 gofor(ch, pos, prog); 286 } 287 } 288 cit = keys.find(token); 289 if (cit != keys.end()) 290 { 291 id = cit->second; 292 } 293 break; 294 295 case '/': // / 操作符 296 token += ch; 297 if (gofor(ch, pos, prog)) 298 { 299 if (ch == '=') // /= 操作符 300 { 301 token += ch; 302 gofor(ch, pos, prog); 303 } 304 else if (ch == '/') // 單行注釋 305 { 306 token += ch; 307 ++pos; 308 while (pos < prog.size()) 309 { 310 ch = prog[pos]; 311 if (ch == '\n') 312 { 313 break; 314 } 315 token += ch; 316 ++pos; 317 } 318 if (pos >= prog.size()) 319 { 320 ; 321 } 322 else 323 { 324 ; 325 } 326 id = keys.size() - 2; 327 break; 328 } 329 else if (ch == '*') // 注釋 330 { 331 token += ch; 332 if (!gofor(ch, pos, prog)) 333 { 334 token += "\n!!!注釋錯誤!!!"; 335 id = -10; 336 break; 337 } 338 if (pos + 1 >= prog.size()) 339 { 340 token += ch; 341 token += "\n!!!注釋錯誤!!!"; 342 id = -10; 343 break; 344 } 345 char xh = prog[pos + 1]; 346 while (ch != '*' || xh != '/') 347 { 348 token += ch; 349 if (ch == '\n') 350 { 351 ++row; 352 } 353 //++pos; 354 if (!gofor(ch, pos, prog)) 355 { 356 token += "\n!!!注釋錯誤!!!"; 357 id = -10; 358 ret.token = token; 359 ret.id = id; 360 return ret; 361 } 362 //ch = prog[pos]; 363 if (pos + 1 >= prog.size()) 364 { 365 token += ch; 366 token += "\n!!!注釋錯誤!!!"; 367 id = -10; 368 ret.token = token; 369 ret.id = id; 370 return ret; 371 } 372 xh = prog[pos + 1]; 373 } 374 token += ch; 375 token += xh; 376 pos += 2; 377 ch = prog[pos]; 378 id = keys.size() - 2; 379 break; 380 } 381 } 382 cit = keys.find(token); 383 if (cit != keys.end()) 384 { 385 id = cit->second; 386 } 387 break; 388 case '+': 389 token += ch; 390 cit = keys.find(token); 391 if (cit != keys.end()) 392 { 393 id = cit->second; 394 } 395 gofor(ch, pos, prog); 396 break; 397 398 case '<': 399 token += ch; 400 if (gofor(ch, pos, prog)) 401 { 402 if (ch == '<') 403 { 404 token += ch; 405 if (gofor(ch, pos, prog)) 406 { 407 if (ch == '=') 408 { 409 token += ch; 410 gofor(ch, pos, prog); 411 } 412 } 413 } 414 else if (ch == '=') 415 { 416 token += ch; 417 gofor(ch, pos, prog); 418 } 419 } 420 cit = keys.find(token); 421 if (cit != keys.end()) 422 { 423 id = cit->second; 424 } 425 break; 426 427 case '>': 428 token += ch; 429 if (gofor(ch, pos, prog)) 430 { 431 if (ch == '>') 432 { 433 token += ch; 434 if (gofor(ch, pos, prog)) 435 { 436 if (ch == '=') 437 { 438 token += ch; 439 gofor(ch, pos, prog); 440 } 441 } 442 } 443 else if (ch == '=') 444 { 445 token += ch; 446 gofor(ch, pos, prog); 447 } 448 } 449 cit = keys.find(token); 450 if (cit != keys.end()) 451 { 452 id = cit->second; 453 } 454 break; 455 case '(': // / 操作符 456 token += ch; 457 if (gofor(ch, pos, prog)) 458 459 { 460 if (ch == '*') // 注釋 461 { 462 token += ch; 463 if (!gofor(ch, pos, prog)) 464 { 465 token += "\n!!!注釋錯誤!!!"; 466 id = -10; 467 break; 468 } 469 if (pos + 1 >= prog.size()) 470 { 471 token += ch; 472 token += "\n!!!注釋錯誤!!!"; 473 id = -10; 474 break; 475 } 476 char xh = prog[pos + 1]; 477 while (ch != '*' || xh != ')') 478 { 479 token += ch; 480 if (ch == '\n') 481 { 482 ++row; 483 } 484 //++pos; 485 if (!gofor(ch, pos, prog)) 486 { 487 token += "\n!!!注釋錯誤!!!"; 488 id = -10; 489 ret.token = token; 490 ret.id = id; 491 return ret; 492 } 493 //ch = prog[pos]; 494 if (pos + 1 >= prog.size()) 495 { 496 token += ch; 497 token += "\n!!!注釋錯誤!!!"; 498 id = -10; 499 ret.token = token; 500 ret.id = id; 501 return ret; 502 } 503 xh = prog[pos + 1]; 504 } 505 token += ch; 506 token += xh; 507 pos += 2; 508 ch = prog[pos]; 509 id = keys.size() - 2; 510 break; 511 } 512 } 513 cit = keys.find(token); 514 if (cit != keys.end()) 515 { 516 id = cit->second; 517 } 518 break; 519 520 case '*': 521 token += ch; 522 cit = keys.find(token); 523 if (cit != keys.end()) 524 { 525 id = cit->second; 526 } 527 gofor(ch, pos, prog); 528 break; 529 530 case ',': 531 case ')': 532 case '#': 533 case '.': 534 case ';': 535 token += ch; 536 gofor(ch, pos, prog); 537 //++pos; 538 //ch = prog[pos]; 539 cit = keys.find(token); 540 if (cit != keys.end()) 541 { 542 id = cit->second; 543 } 544 break; 545 546 case '\n': 547 token += "換行"; 548 ++pos; 549 ch = prog[pos]; 550 id = -2; 551 break; 552 default: 553 token += "錯誤"; 554 ++pos; 555 ch = prog[pos]; 556 id = -1; 557 break; 558 } 559 } 560 ret.token = token; 561 ret.id = id; 562 563 return ret; 564 } 565 566 void init_keys(const string& file, map<string, int>& keys)//讀取單詞符號和種別碼 567 { 568 ifstream fin(file.c_str());//.c_str返回的是當前字符串的首地址 569 if (!fin) 570 { 571 cerr << file << " doesn't exist!" << endl;//cerr不經過緩沖而直接輸出,一般用於迅速輸出出錯信息 572 // exit(1); 573 } 574 keys.clear();//清空map對象里面的內容 575 string line; 576 string key; 577 int id; 578 while (getline(fin, line))//這個函數接收兩個參數:一個輸入流對象和一個string對象,getline函數從輸入流的下一行讀取,並保存讀取的內容到string中 579 { 580 istringstream sin(line);//istringstream sin(s);定義一個字符串輸入流的對象sin,並調用sin的復制構造函數,將line中所包含的字符串放入sin 對象中! 581 sin >> key >> id;//讀取里面的字符串每一行一個key id 582 keys[key] = id; 583 } 584 } 585 586 void read_prog(const string& file, string& prog){//讀取代碼,並追加到prog上 587 ifstream fin(file.c_str()); 588 if (!fin) 589 { 590 cerr << file << " error!" << endl; 591 // exit(2); 592 } 593 prog.clear(); 594 string line; 595 while (getline(fin, line)) 596 { 597 prog += line + '\n'; 598 } 599 } 600 601 void cifafenxi() 602 { 603 map<string, int> keys; 604 init_keys("D:/Test/a.txt", keys); 605 606 string prog; 607 read_prog("D:/Test/b.txt", prog); 608 609 vector< _2tup > tups; 610 string token, id; 611 612 string::size_type pos = 0;//size_type屬於string標准庫,作用可看做相當於unsigned·int 613 int row = 1; 614 615 _2tup tu; 616 int no = 0; 617 freopen("D:/Test/c.txt","w",stdout); 618 do 619 { 620 tu = scanner(prog, pos, keys, row); 621 622 switch (tu.id) 623 { 624 case -1://返回的是錯誤 625 ++no; 626 cout << no << ": "; 627 cout << "Error in row" << row << "!" << '<' << tu.token<< "," << tu.id << '>' << endl; 628 tups.push_back(tu); 629 break; 630 case -2: 631 ++row; 632 // cout << '<' << tu.token<< "," << tu.id << '>' << endl; 633 break; 634 default: 635 636 s=prog; 637 cout << '(' << tu.id<< "," << tu.token << ')' << endl; 638 639 tups.push_back(tu); 640 break; 641 } 642 } while (pos < prog.size()); 643 644 } 645 646 yufafenxi()//語法分析 647 { 648 freopen("CON", "w", stdout);//結果在控制台上輸出 649 flag=advance(); 650 if(flag){ 651 expressionAnalysis(); 652 } 653 if(flag!=-1&&!conterr){ 654 cout<<"正確:"<<s<<endl; 655 } 656 657 } 658 int main() 659 { 660 cifafenxi(); 661 yufafenxi(); 662 return 0; 663 }
效果如下:

reference:https://blog.csdn.net/Flamewaker/article/details/82899466
注:本文里用到了freopen函數的句柄,該句柄作用是當不想輸入或輸出到文件了,要恢復句柄,可以重新打開標准控制台設備文件,這個設備文件的名字是與操作系統相關:
DOS/Win: freopen("CON", "r", stdin);
freopen("CON", "w", stdout);
在linux中,控制台設備是 /dev/console:freopen("/dev/console", "r", stdin)
