語法分析之 LL1分析法實現
一、設計目的
根據某一文法編制調試LL(1)分析程序,以便對任意輸入的符號串進行分析。本次實驗的目的主要是加深對預測分析LL(1)分析法的理解。
二、設計要求
程序輸入/輸出示例:
對下列文法,用LL(1)分析法對任意輸入的符號串進行分析:
原文法:
E->E+T|E-T|T
T->T*F|T/F|F
F->id|(E)|num
其中: id: a-f, A-F,num:0-9
消左遞歸:
E->TA A->+TA A->-TA A->e
T->FB B->*FB B->/FB B->e
F->i F->(E) F->n
其中:i:id, n:num, e:epsilonE->TG
FIRST集和FOLLOW集:
TA | +TA | -TA | e | FB | *FB | /FB | e | i | (E) | n | |
---|---|---|---|---|---|---|---|---|---|---|---|
FIRST | i,(,n | + | - | e | i,(,n | * | / | e | i | ( | n |
E | A | T | B | F | |
---|---|---|---|---|---|
FOLLOW | $,) | $,) | +,-,\(,) | +,-,\),) | *,/,+,-,$,) |
輸出的格式如下:
(1)輸入一以#結束的符號串(包括+—*/()i#):
(2)輸出過程如下:
棧 | 輸入 | 輸出 | ||
---|---|---|---|---|
\(E | (a-1)*(3+4/2)+((8*2))\) | E->TA | |||
(3)輸入符號串為非法符號串(或者為合法符號串)
注意:
1.表達式中允許使用運算符(+-*/)、分割符(括號)、字符i,結束符#;
2.如果遇到錯誤的表達式,應輸出錯誤提示信息(該信息越詳細越好);
3.測試用的表達式可以事先放在文本文件中,一行存放一個表達式,同時以分號分割。同時將預期的輸出結果寫在另一個文本文件中,以便和輸出進行對照;
三、設計說明
1. 需求分析:
a****) 輸入及其范圍
輸入為文法,表達式中允許使用運算符(+-*/)、分割符(括號)、字符a。
b****) 輸出形式
棧 | 輸入 | 輸出 | ||
---|---|---|---|---|
\(E | (a-1)*(3+4/2)+((8*2))\) | E->TA | |||
c) 程序功能
根據輸入的文法進行分析,利用LL(1)控制程序根據顯示棧棧頂內容、向前看符號以及LL(1)分析表,對輸入符號串自上而下的分析過程
d) 測試數據
輸入:文件“fin.txt”輸入待分析串
輸出:命令行界面輸出預測分析表,LL(1)分析過程輸出至“fout.txt”
2. 概要設計
a****)數據類型的定義
vector<vector
vector
map<char, int> index //文法符號到下標的轉換字典
string terminal("in+-*/()$") //終結符
string nonTerminal("EATBF") //非終結符
vector
vector
b****)主程序流程

3. 詳細設計
\1. int main()
{
for(文法G每個產生式itG,itFirst為其右部符號串的first集){
x = itG左部非終結符號的下標;
for(itFirst中的每個終結符號first){
y = 終結符號first的下標;
把itG加入分析表表G[x][y];
}
if(終結符號first == epsilon)
for(Follow集中的每個符號follow){
y = follow的下標;
把itG加入分析表G[x][y];
}
}
for(所有非終結符號的Follow集)
if(對應表項為空)
寫入synch;
將分析表輸出到命令行界面;
return analysis();
}
\2. int analysis(void)
{
從文件fin.txt讀取待分析串到s;
s末尾加‘$’;
分析棧vector
向棧壓入‘$’、‘E’;
ip指向s的第一個字符;
do{
top是棧頂符號;
cur是ip所指向的輸入符號;
if(cur是字母) cur = ‘i’;
if(cur是數字) cur = ‘n’;
if(top是終結符號或‘$’){
if(top == cur){從棧頂彈出cur;ip前移一個位置;}
else error;
}
else{
x = top對應下標; y = cur對應下標;
產生式production = table[x][y];
if(production非空){
棧頂彈出cur;
把production右部逆序壓棧;
輸出production;
}
else error;
}
while(top != ‘$’);
}
四、運行結果及分析
1.測試數據
fin.txt文件入字符串:(a-1)(3+4/2)+((82))
2.測試輸出的結果
)
輸出文件:
3.設計和思考
主要的難點在於對LL(1)的理解部分,消除二義性、消除左遞歸、提取左因子,判斷是否為LL(1)文法,然后開始整理思路進行編碼階段。開始要對錯誤的文法進行分析,並提示詳細的錯誤信息。思考之后實現了表達式中允許使用運算符(+-*/)、分割符(括號)、字符a。
五、總結
本次課程設計是本周實驗來難點最大的一次作業,首先需要溫習LL(1)的知識,如何消除左遞歸,區別二義性文法,以及對文法的分析。在實驗的過程中,最重要的還是要理順思路,想好解決辦法,這也是我經過不斷實驗總結出的自我思考的方法。然后就進入了編碼階段,此次編碼也有一定的難度,在代碼量以及代碼的整體設計上都有了提升,也是最值得思考的地方。最后,通過實驗報告的書寫,以及參考資料的查找,對今后的學習和工作都有很大的幫助。
代碼
#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>
#include <string>
#include <map>
#include <stdexcept>
using namespace std;
// 預測分析表
vector<vector<string> > table(5, vector<string>(9));
// 文法的產生式
vector<string> G = {"E->TA", "A->+TA", "A->-TA", "A->e", "T->FB", "B->*FB", "B->/FB", "B->e", "F->i", "F->(E)", "F->n"};
// 文法符號到下標轉換
map<char, int> index = {{'E', 0}, {'A', 1}, {'T', 2}, {'B', 3}, {'F', 4}, {'i', 0}, {'n', 1}, {'+', 2}, {'-', 3}, {'*', 4}, {'/', 5}, {'(', 6}, {')', 7}, {'$', 8}, {'e', 9}};
// 終結符
string terminal("in+-*/()$");
// 非終結符
string nonTerminal("EATBF");
// 產生式右部的first集
vector<string> First = {"i(n", "+", "-", "e", "i(n", "*", "/", "e", "i", "(", "n"};
// 非終結符的follow集
vector<string> Follow = {"$)", "$)", "+-$)", "+-$)", "*/+-$)"};
int analysis(void);
// 預測分析過程
int analysis(void) {
ifstream fin("fin.txt");
if (!fin.is_open()) {
cout << "輸入文件不存在 fin.txt." << endl;
return 1;
}
ofstream fout("fout.txt");
if (!fout.is_open()) {
cout << "無法打開輸出文件 fout.txt." << endl;
return 1;
}
//輸入緩沖區
string s;
fin >> s;
cout << "成功讀取待分析串:" << endl << s << endl;
int wid = s.length() + 1;
s.push_back('$');
//分析棧
vector<char> analyStack;
analyStack.push_back('$');
analyStack.push_back('E');
// 棧頂和當前輸入
char top = '\0', cur = '\0';
auto ip = s.begin();
// 輸出頭
fout << left << setw(wid + 10) << "棧" << right << setw(wid) << "輸入" << " " << "輸出" << endl;
do {
// 輸出當前棧和當前剩余輸入
string str1(analyStack.begin(), analyStack.end());
string str2(ip, s.end());
fout << left << setw(wid + 10) << str1 << right << setw(wid) << str2 << " ";
// 棧頂和當前輸入符號
top = analyStack.back();
cur = *ip;
// 標識符及數字變換
if (isalpha(cur))
cur = 'i';
else if (isdigit(cur))
cur = 'n';
// 棧頂是終結符號或$
if (terminal.find(top) != terminal.npos || top == '$') {
if (top == cur) {
analyStack.pop_back();
++ip;
fout << endl;
} else {
fout << "出錯! 不匹配,彈出" << top << endl;
analyStack.pop_back();
}
}
// 棧頂非終結符
else {
//坐標轉換
int x = index.at(top);
int y;
try {
y = index.at(cur);
} catch (out_of_range) {
fout << "輸入字符非法!" << endl;
break;
}
// 產生式
string production = table.at(x).at(y);
// 產生式非空
if (!production.empty()) {
if (production == "synch") { //同步
fout << "出錯!synch,彈出" << top << endl;
analyStack.pop_back();
} else { //正常分析
analyStack.pop_back();
string expr(production.begin() + 3, production.end());
if (expr == "e") //epsilon產生式
expr = "";
// 逆序壓棧
for (auto iter = expr.rbegin(); iter != expr.rend(); ++iter)
analyStack.push_back(*iter);
// 輸出產生式
fout << production << endl;
}
} else { //表項空白
fout << "出錯!空白,跳過" << *ip << endl;
++ip;
}
}
} while (top != '$');
cout << endl << "分析結果已輸出至 fout.txt." << endl;
return 0;
}
int main() {
//算法4.2 構造預測分析表
// 遍歷G的每個產生式
for (auto itG = G.begin(), itFirst = First.begin(); itG != G.end() && itFirst != First.end(); ++itG, ++itFirst) {
// 非終結符下標轉換
int x = index.at(*(itG->begin()));
for (auto first = itFirst->begin(); first != itFirst->end(); ++first) {
if (*first != 'e') {
int y = index.at(*first);
table.at(x).at(y) = *itG;
} else {
for (auto follow = Follow.at(x).begin(); follow != Follow.at(x).end(); ++follow) {
int y = index.at(*follow);
table.at(x).at(y) = *itG;
}
}
}
}
// 寫入同步信息
for (string::size_type i = 0; i < nonTerminal.length(); ++i) {
int x = index.at(nonTerminal.at(i));
for (vector<string>::size_type j = 0; j < Follow.at(i).length(); ++j) {
int y = index.at(Follow.at(i).at(j));
if (table.at(x).at(y).empty())
table[x][y] = "synch";
}
}
// 輸出預測分析表
cout << "預測分析表:" << endl;
// 輸出終結符
for (string::size_type i = 0; i < terminal.size(); ++i)
cout << '\t' << terminal[i];
cout << endl;
// 輸出非終結符
for (string::size_type x = 0; x < nonTerminal.size(); ++x) {
cout << nonTerminal[x];
// 輸出產生式
for (string::size_type y = 0; y < table.at(x).size(); ++y)
cout << '\t' << table.at(x).at(y);
cout << endl;
}
cout << endl;
return analysis();
}