说明:
为实践《编译原理》中的相关知识,认真完成了课程设计,实现了C-语言的词法分析器与语法分析器
C-语言是C语言的一个子集,语法包括:
整型变量与函数的声明
if else 分支语句
while 循环语句
本篇介绍语法分析器的实现
将利用将文法改写为LL(1)文法的技术实现语法分析器
流程:
- 去除左递归
- 提取左公因子
- 获取FIRST, FOLLOW集合
- 构造预测分析表
- 匹配词法分析器产生的token构造语法树
数据结构:
因为要对文法中的产生式频繁的删改,最开始用了std::list<std::list<std::string>>,内层的每个list存产生式,因为在判断产生式是几个终结符、非终结符并列时,要逐字符判断空格,后面将数据结构换成std::vector<std::vector<std::vector<std::string>>>储存文法,最内层是符号,外面一层是一个产生式,再外面一层是一条文法,最外层是所有文法
去除左递归:
先将文法左端的前面文法的非终结符替换为该非终结符的产生式
再去除左递归
将A -> Aα| β改为
A -> βA1
A1 -> αA1
提取左公因子:
因为LL(1)文法每次的产生式是确定的,而两个产生式会导致选择是产生分歧,故要去除公因子
将A -> αB| αC
改为A -> αA'
A' -> B | C
在最开始,我以为难点主要在去左递归,提取左公因子看起来很直观很好操作,后面踩了坑发现提取左公因子是最难的一步,因为一个可能两个产生式虽然不一样,但他们产生的非终结符是相同的,而提取左公因子就要把左公终结符全部提取出来,但是直接把一个非终结符充分展开显然非常麻烦。
在判断是否是LL(1)文法是会求FIRST集合,而通过FIRST集合判断LL(1)文法就是在找是否有公因子
我的做法是两两比较一个非终结符的所有产生式,如果有明显的左公因子,即字符相同的Vn&Vt,先将明显的左公因子提取出来,将新的文法加入所有文法,回到算法开头重新开始。如果没有明显的左公因子,算一次FIRST集合,判断有没有隐藏在产生式中的左公因子,有的话就展开当前比较的两条产生式,然后再回到算法开头重新开始。直到遍历完所有文法找不到左共因子,就提取出了所有左公因子
获取FIRST, FOLLOW集合
FIRST(α)被定义为可从α推导得到的串的首符号的集合,其中α是任意的文法符号串。
对于产生式 α -> βγ
在计算FIRST(α)时,要先知道FIRST(β),故想到采用递归解决,根据规则,如果FIRST(β)中含有ε,再继续求FIRST(γ),若FIRST(γ)中也含有ε,则将ε加入FIRST(α)
对于非终结符A,FOLLOW(A)被定义为可能在某些句型中紧跟在A右边的终结符号的集合
规则是
(1)将$放到FOLLOW(S)中,其中S是开始符号,而$是输入右端的结束标记
(2)如果存在一个产生式A -> αBβ,那么FIRST(β)中除ε之外的所有符号都在FOLLOW(B)中
(3)如果存在一个产生式A -> αB,或存在产生式A -> αBβ且FIRST(β)包含ε,那么FOLLOW(A)中的所有符号都在FOLLOW(B)中
根据规则,在计算完FIRST集合后,扫一遍所有产生式即可计算出FOLLOW集合
FIRST集合和FOLLOW集合正好可以用std::set存储
构造预测分析表
对于文法G的每个产生式A -> α,进行如下处理:
(1)对于FIRST(α)中的每个终结符号a, 将A -> α加入到M [A, a]中。
(2)如果ε在FIRST(α)中,那么对于FOLLOW(A)中的每个终结服符号b,将A -> α加入到M [A, b]中。如果ε在FIRST(α)中,且$在FOLLOW(A)中,也将A -> α加入到M [A, $]中。
在求出FIRST集合和FOLLOW集合后,根据规则很容易求出预测分析表
因为这一步将一个非终结符到一个终结符的推导映射到一个产生式上,可以用std::map存储
匹配词法分析器产生的token构造语法树
预测分析表构造好后,就可以拿来处理词法分析器得出的token,如果文法是处理正确的LL(1)文法,且源代码也没有语法错误,那么这一步将会把所有的token构造出一棵语法树。
详细代码:
//Parser.h
//作者:IuSpet
//作用:将一般文法转变为LL(1)文法并构建语法分析树
#ifndef Parser_h
#define Parser_h
#include"utlib.h"
//语法树的节点
struct node
{
node *Parent;
std::string type;
std::string value;
std::list<node*> sons;
};
class Parser
{
public:
Parser();
void get_LL1_grammar(); //得到LL(1)文法
void Parse(); //解析,匹配文法与token,建树
~Parser()
{
tokenfile.close();
}
private:
std::list<std::list<std::string>> grammar; //文法
std::vector<std::vector<std::vector<std::string>>> final_grammar; //处理后的LL(1)文法
std::map<std::string, bool> can_produce_empty;
std::map<std::string, bool> is_Vn;
std::map<std::string, std::set<std::string>> FIRST, FOLLOW;
//预测分析表,结构为 predictive_table[非终结符号,终结符号] = 产生式
std::map<std::pair<std::string, std::string>, std::vector<std::string>> predictive_table;
const char *grammar_file;
const char *token_file;
FILE* f;
int filepos;
node root; //语法树根
std::ifstream tokenfile;
void get_grammar(); //获取初始文法
void Eliminate_left_recursion(); //消除左递归
void get_FIRST(); //获取FIRST集合
void get_FOLLOW(); //获取FOLLOW集合
bool judge_LL1_grammar(); //判断是不是LL(1)文法
bool cmp_set(const std::set<std::string> s1, const std::set<std::string> s2); //判断两个set是否有交集
void get_predict_table(); //计算预测分析表
void get_left_common_factor(); //提取左公因子
void get_all_Vn(); //找出所有非终结符
void reconsitution(); //换个方便的数据结构。。。
std::string get_next_token(); //不断获得下一个token
void print_grammar0(); //打印初始文法
void print_grammar1(); //打印去除左递归后的文法
void print_grammar2(); //打印提取左公因子后的文法
void print_final_grammar(); //打印重构后的文法,测试
std::set<std::string> cal_first(std::string Vn);//用于递归计算FIRST集
void print_FIRST(); //打印FIRST集合
void print_FOLLOW(); //打印FOLLOW集合
void print_predictive_table(); //打印预测分析表
void print_tree(); //打印语法树
void string_to_vector(std::string &s, std::vector<std::string> &v); //将string表示的产生式转为vector
void vector_to_string(std::string &s, std::vector<std::string> &v); //将vector表示的产生式转为string
bool has_common_prefix(std::vector<std::string> &gm1, std::vector<std::string> &gm2); //判断两个产生式有无左公因子
std::set<std::string> get_left(std::vector<std::string> & tmp); //返回左端,即first集,用于查看是否有左公因子
void get_token_value(std::string &token, std::string &value, std::string &type); //获取token属性
void deep_print(std::ofstream &out, int r, node *t); //递归打印树
};
#endif // !Parser_h
#pragma once
//Parser.cpp
//作者:IuSpet
//作用:将一般文法转变为LL(1)文法并构建语法分析树
#include "Parser.h"
const int BUFFERLENGTH = 4096;
//构造函数指定文法文件和token文件
Parser::Parser()
{
grammar_file = "D://cminus//grammar.txt";
token_file = "D://cminus//token.txt";
tokenfile.open(token_file);
filepos = 0;
//语法树根
root.Parent = NULL;
root.type = "non-terminal";
root.value = "program";
}
void Parser::get_LL1_grammar()
{
get_grammar(); //读文法
print_grammar0();
Eliminate_left_recursion(); //去左递归
print_grammar1();
get_all_Vn(); //标记所有非终结符
get_left_common_factor(); //消除左公因子
print_grammar2();
reconsitution(); //重构存储文法的数据结构
print_final_grammar();
get_FIRST(); //计算FIRST集合
print_FIRST();
get_FOLLOW(); //计算FOLLOW集合
print_FOLLOW();
if (!judge_LL1_grammar())
{
std::cout << "不是LL(1)文法" << std::endl;
//system("pause");
}
else
{
std::cout << "是LL(1)文法" << std::endl;
//system("pause");
}
get_predict_table(); //构造预测分析表
print_predictive_table();
}
//解析token,构造语法树
void Parser::Parse()
{
std::ofstream outfile("D://cminus//matching_process.txt");
outfile << std::setiosflags(std::ios::left);
outfile << std::setw(30) << "Stack" << std::setw(20) << "Input" << "Action" << std::endl;
std::stack<node*> match;
//std::stack<std::string> match;
node end;
end.Parent = NULL;
end.type = "";
end.value = "$";
match.push(&end);
match.push(&root);
std::string type;
std::string value;
std::string out;
std::string token = get_next_token();
get_token_value(token, value, type);
while (true)
{
node &top = *match.top();
match.pop();
//token读取完了
if (token == "$")
{
//匹配成功
if (match.size() == 1)
{
outfile << std::setw(30) << "$" << std::setw(20) << "$" << "accept" << std::endl;
match.pop();
break;
}
else
{
//栈顶是非终结符,符号或保留字,匹配值存在value中
bool choose = true;
if (top.type == "RESERVED WORD" || top.type == "SYMBOL" || top.type == "non-terminal")
{
out = top.value + " ... " + "$";
outfile << std::setw(30) << out << std::setw(20) << "$" << "output" << top.value;
}
//栈顶是标识符或运算符,匹配值存在type中
else
{
out = top.type + " ... " + "$";
outfile << std::setw(30) << out << std::setw(20) << "$" << "output" << top.value;
choose = false;
}
//查表获取操作
std::vector<std::string> &pro = choose ? predictive_table[std::make_pair(top.value, value)] : predictive_table[std::make_pair(top.type, value)];
out.clear();
vector_to_string(out, pro);
outfile << " -> " << out << std::endl;
//空的情况
if (out == "empty")
{
continue;
}
//往栈中压入新值,并连接节点建树
for (int i = pro.size() - 1; i >= 1; i--)
{
node *son;
son = new node;
//新节点是原栈顶的子节点
top.sons.push_back(son);
son->Parent = ⊤
//根据新压入符号不同构造节点
if (is_Vn[pro[i]])
{
son->type = "non-terminal";
son->value = pro[i];
}
else
{
if (pro[i] == "ID" || pro[i] == "NUM")
{
son->type = pro[i];
son->value = "";
}
else
{
if (pro[i][0] >= 'a' && pro[i][0] <= 'z')
{
son->type = "RESERVED WORD";
son->value = pro[i];
}
else
{
son->type = "SYMBOL";
son->value = pro[i];
}
}
}
match.push(son);
}
}
}
else
{
bool choose = true;
if (top.type == "RESERVED WORD" || top.type == "SYMBOL" || top.type == "non-terminal")
{
out = top.value + " ... " + "$";
outfile << std::setw(30) << out;
}
//栈顶是标识符或运算符,匹配值存在type中
else
{
out = top.type + " ... " + "$";
outfile << std::setw(30) << out;
choose = false;
}
//token是关键字或符号
if (type == "RESERVED WORD" || type == "SYMBOL")
{
out = value + " ... " + "$";
outfile << std::setw(20) << out;
//匹配,读取下一个token
if (top.value == value)
{
outfile << "match" << std::endl;
token.clear();
token = get_next_token();
get_token_value(token, value, type);
continue;
}
//不匹配,查表
else
{
std::vector<std::string> &pro = choose ? predictive_table[std::make_pair(top.value, value)] : predictive_table[std::make_pair(top.type, value)];
if (pro.size() == 0)
{
outfile << "error" << std::endl;
exit(1);
}
if (choose)
{
outfile << "output: " << top.value << " -> ";
}
else
{
outfile << "output: " << top.type << " -> ";
}
out.clear();
vector_to_string(out, pro);
outfile << out << std::endl;
//空的情况
if (out == "empty")
{
continue;
}
//往栈中压入新值,并连接节点建树
for (int i = pro.size() - 1; i >= 0; i--)
{
node *son;
son = new node;
//新节点是原栈顶的子节点
top.sons.push_back(son);
son->Parent = ⊤
//根据新压入符号不同构造节点
if (is_Vn[pro[i]])
{
son->type = "non-terminal";
son->value = pro[i];
}
else
{
if (pro[i] == "ID" || pro[i] == "NUM")
{
son->type = pro[i];
son->value = "";
}
else
{
//保留字
if (pro[i][0] >= 'a' && pro[i][0] <= 'z')
{
son->type = "RESERVED WORD";
son->value = pro[i];
}
//运算符
else
{
son->type = "SYMBOL";
son->value = pro[i];
}
}
}
match.push(son);
}
}
}
//token是标识符或数字
else
{
out = type + " ... " + "$";
outfile << std::setw(20) << out;
//匹配,节点加入值,读取下一个token
if (top.type == type)
{
outfile << "match" << std::endl;
top.value = value;
token.clear();
token = get_next_token();
get_token_value(token, value, type);
continue;
}
else
{
std::vector<std::string> &pro = choose ? predictive_table[std::make_pair(top.value, type)] : predictive_table[std::make_pair(top.type, type)];
if (choose)
{
outfile << "output: " << top.value << " -> ";
}
else
{
outfile << "output: " << top.type << " -> ";
}
out.clear();
vector_to_string(out, pro