C++編譯原理之求解First集合
1、上機(jī)要求
目的:熟練掌握自上而下的語(yǔ)法分析方法,并能用程序?qū)崿F(xiàn)。
要求:
例如,使用的文法如下:
編寫First函數(shù),實(shí)現(xiàn)其求解過(guò)程。
E -> TE'
E' -> +TE' | #
T -> FT'
T' -> *FT' | #
F -> (E) | id
end
提示:
- 非終結(jié)符為 大寫字母;或 后面帶'的大寫字母
- 終結(jié)符為 小寫字母和符號(hào)(+、*)
- 推導(dǎo)符號(hào)→為或->
- 用end結(jié)束文法。
不針對(duì)特定文法,編寫求first函數(shù)。
2、原理
A -> a, 則將 a 加入 First(A)中
A -> Y1Y2···Yn
將 First(Y1) 除空串外的字符加入到First(A)中,若 1 =< i < n - 1,Y1,Y2, Yi中均含有空串,則將First(Yi + 1)加入到First(A)中,若Y1,Y2,···,Yn都有空串,則將空串加入到First(A)中
First(a) = {a}
3、一點(diǎn)思路及優(yōu)化
將輸入格式化(掃描輸入)
將產(chǎn)生式轉(zhuǎn)換為哈希map:
- 對(duì)任一產(chǎn)生式:
A -> body_1 | body_2 | ··· | body_n, - 將 A 作為
map的key, - map的
value為一個(gè)string類的向量(vector<string>), - 將
body_1,body_2,···,body_n都加入value中。
- 求解First(str)
- 特殊情況處理,str為空或str不在產(chǎn)生式的
key中,返回空;str的首個(gè)字符是終結(jié)符,返回首個(gè)字符構(gòu)成的集合。 - 一般情況,獲取str推導(dǎo)產(chǎn)生的產(chǎn)生體集
bodys(其中的每個(gè)產(chǎn)生體為body),遍歷產(chǎn)生體集合求解First集 - 針對(duì)空串,我們加入標(biāo)記
hasBlank = true,往下遍歷body的字符 body的首個(gè)字符為終結(jié)符,直接將該字符加入first集,記hasBlank = false以便遍歷下一body(如果有的話)。body的首個(gè)字符為非終結(jié)符,遞歸求解該非終結(jié)符first集,記為temp,同時(shí)將空串標(biāo)記記為false,將temp的中除空串外的字符加入first集;若temp中有空串,記空串標(biāo)記為true,繼續(xù)遍歷當(dāng)前body的字符,理解上可以將body后面的字符串視為一個(gè)新的body繼續(xù)進(jìn)行求解步驟。- body的字符遍歷結(jié)束后若空串標(biāo)記
hasBlank仍然為true,則將空串加入first集。 - 優(yōu)化:遞歸求解的中間結(jié)果可以放在全局哈希First(或者換個(gè)名字避免沖突)中,避免重復(fù)的迭代(本代碼沒(méi)實(shí)現(xiàn),下次一定)。
4、代碼
/**
* @brief Function for generating set of First(a)
* @author 立秋小豬
* @time: 2021/10/13
* @notice: 要求產(chǎn)生體句型不得有空格
* 左遞歸的產(chǎn)生體中必須有空串(必須能夠終結(jié))
* char '#' act as varepsilon
* **/
#include <iostream>
#include <unordered_map>
#include <vector>
#include <string>
#include <fstream>
#include <unordered_set>
using namespace std;
unordered_map<string, vector<string>> P; //產(chǎn)生式P的集合
void scan(){
//scan函數(shù)實(shí)現(xiàn)從文件掃描文法,將對(duì)應(yīng)的產(chǎn)生式加入到映射P中
fstream fs;
string input;
fs.open("lan.txt");
if(!fs.is_open()){ // 文件打開(kāi)失敗
cout << "Error: Could not open the file" << endl;
exit(-1);
}
fs >> input;
while(input != "end"){
string VN = input; // 產(chǎn)生式的非終結(jié)符
fs >> input; //跳過(guò)推導(dǎo)符號(hào)
if (input != "->" && input != "→"){
cout << "Error: undefined symbol [" << input << "]" << endl;
exit(-2);
}
fs >> input; //產(chǎn)生體拆開(kāi)后加入到set集合中,默認(rèn)推導(dǎo)符號(hào)后必有一個(gè)產(chǎn)生體
P[VN].emplace_back(input);
while( fs >> input && input == "|"){
fs >> input;
P[VN].emplace_back(input);
}
}
}
// void generate(){
// }
unordered_set<char> First(const string& str){
// 終結(jié)符以及空串情況下, whether has the VN or not
if(str == "" || str == "#" || P.find(str) == P.end())
return {};
if(!(str[0] >= 'A' && str[0] <= 'Z'))
return {str[0]};
vector<string> bodys = P[str]; // str -> bodys
unordered_set<char> res = {};
for(auto &s: bodys){
bool hasBlank = true;//是否含有空串,是否繼續(xù)讀產(chǎn)生體
for (int i = 0; i < s.size() && hasBlank; ++i){
if(s[i] >= 'A' && s[i] <= 'Z'){//是否為終結(jié)符
unordered_set<char> temp = {};//遞歸的臨時(shí)集
string next;
if(i < s.size() - 1 && s[i + 1] == '\''){ // 大寫字母 + ' 的非終結(jié)符
next = s.substr(i, 2);
++i;
}else{ //僅僅是大寫字母的終結(jié)符
next = s[i];
}
if(next != str){ //避免無(wú)限遞歸,默認(rèn)自身是含有空串(hasBlank為True)
temp = First(next); //遞歸求解
hasBlank = false; //先默認(rèn)temp中沒(méi)有空串
for(auto &c : temp)
if(c == '#')
hasBlank = true;//temp中發(fā)現(xiàn)了空串
else
res.emplace(c);
}
}else{
res.emplace(s[i]);
hasBlank = false;//默認(rèn)連接的終結(jié)符不為空,故此終結(jié)符后不會(huì)再有新元素加入First集
}
}
if(hasBlank) //產(chǎn)生體中所有非終結(jié)符都包含空串,則將空串加入first集中
res.emplace('#');
}
return res;
}
int main(){
// unordered_map<string, vector<char>> First; //First集合
scan();
cout << "輸入的產(chǎn)生式如下:\n"
<< "********************************\n";
for(auto &[vn, bodys]: P){
cout << vn << " -> " << bodys[0];
for (int i = 1; i < bodys.size(); ++i)
cout << " | " << bodys[i];
cout << endl;
}
cout << "********************************\n";
for(auto &[vn,_]: P){
unordered_set<char> f = First(vn);
cout << "First(" << vn << ") : ";
auto iter = f.begin();
if(iter != f.end()){
cout << *iter;
while(++iter != f.end()){
cout << " , " << *iter;
}
}
cout << endl;
}
return 0;
}
4.1 lan.txt文件內(nèi)容
E -> TE' E' -> +TE' | # T -> FT' T' -> *FT' | # F -> (E) | id end
運(yùn)行結(jié)果

4.2 lan.txt文件內(nèi)容
S -> SaRb | # R -> RSQ | # Q -> e end
運(yùn)行結(jié)果

到此這篇關(guān)于C++/編譯原理之求解First集合的文章就介紹到這了,更多相關(guān)C++ 求解First集合內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)猜數(shù)字小游戲
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)猜數(shù)字小游戲,附有詳細(xì)代碼,需要的小伙伴可以參考一下,希望對(duì)你的遼西有所幫助2021-10-10
C/C++函數(shù)參數(shù)傳遞機(jī)制詳解及實(shí)例
這篇文章主要介紹了C/C++函數(shù)參數(shù)傳遞機(jī)制詳解及實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-02-02
c語(yǔ)言如何實(shí)現(xiàn)兩數(shù)之和
這篇文章主要介紹了c語(yǔ)言如何實(shí)現(xiàn)兩數(shù)之和,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
VSCode遠(yuǎn)程開(kāi)發(fā)調(diào)試服務(wù)器c/c++代碼
語(yǔ)音相關(guān)的好多項(xiàng)目要在linux上跑,但代碼開(kāi)發(fā)大多是在PC機(jī)上,本篇簡(jiǎn)單介紹一下怎么在個(gè)人電腦上用VSCode遠(yuǎn)程開(kāi)發(fā)調(diào)試服務(wù)器上的c/c++代碼。感興趣的朋友跟隨小編一起看看吧2020-04-04
深入解析設(shè)計(jì)模式中的適配器模式在C++中的運(yùn)用
這篇文章主要介紹了設(shè)計(jì)模式中的適配器模式在C++中的運(yùn)用,通常適配器模式可以細(xì)分為類適配器和對(duì)象適配器兩種情況,需要的朋友可以參考下2016-03-03
C語(yǔ)言程序設(shè)計(jì)第五版譚浩強(qiáng)課后答案(第二章答案)
這篇文章主要介紹了C語(yǔ)言程序設(shè)計(jì)第五版譚浩強(qiáng)課后答案(第二章答案),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
C++11實(shí)現(xiàn)簡(jiǎn)易定時(shí)器的示例代碼
這篇文章主要介紹了C++11實(shí)現(xiàn)簡(jiǎn)易定時(shí)器的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04

