编译原理 实验三 LL(1)分析法(LL1分析表的自动生成)

简介: 编译原理 实验三 LL(1)分析法(LL1分析表的自动生成)

一、实验内容

1.实现LL(1)分析算法

2.输入:教材中的算术表达式文法;待分析的语句(如i+i*i)

3.输出:语句的分析过程(参见ppt例题)

4.要求:LL(1)分析表程序自动生成。如果使用已知的分析表,实验分数会降低。

二、实验目的

通过设计、编制、调试一个具体的文法分析程序,深入理解LL(1)预测分析法的基本分析原理,理解FIRST集、FOLLOW集的构造方法并对其加以实现,构造LL(1)预测分析表并利用分析表对语句、文法进行分析。

三、实验分析

书中算术表达式文法示例如下:

image.png

image.png

image.png

由于算术表达式文法不存在左公因子,所以无需消除回溯。

在程序实现时,可以消除文法中的或符号,即对其进行处理:

image.png


image.png

image.png


image.png

四、实验流程

4.1 整体流程

首先从文件中读入文法,识别出文法的终结符与非终结符,对文法进行消除左递归、消除回溯、消除或。再次识别出文法的终结符与非终结符,对每个符号与短语求出其FIRST集合与FOLLOW集合,自动分析生成建立分析表。输入待分析的文法,对其进行分析。整体流程如下图所示。

4.2 求FIRST

对字符或短语求FIRST集合时,往往需要用到递归。若在递归时发现c字符的FIRST集合已经求解完毕,则直接返回。若当前字符c是终结符,则FIRST集合为自己,返回。若不是终结符,则对其产生式进行遍历。若产生式中就是空字,则将空字加入FIRST集合。否则,遍历产生式的字符。递归的求解当前字符的FIRST集合,将该字符FIRST集合除了空字以外的元素加入。若该字符的FIRST集合存在空字,则遍历下一字符,如此循环。求解短语的FIRST集合的过程与求解某一非终结符的产生式的FIRST集合的流程是类似的。求FIRST集合的流程如下图。

4.3 求FOLLOW


image.png

4.4 求分析表并分析输入串

image.png

五、实验代码

5.1 数据结构

利用S表示文法的开始符,利用VNVT分别表示非终结符与终结符。

字符的FIRST集合用map存储。map的键为字符,值为FIRST集合,用set存储。字符的FIRST集表被定义为:map> firstSet

短语的FIRST集合也用map存储。map的键为字符串,即短语。值为FIRST集合,用set存储。其被定义为:map> firstSetS

字符的FOLLOW集合用map存储。map的键为字符,值为FOLLOW集合,用set存储。字符的FOLLOW集表被定义为:map> followSet;

利用MAP表示产生式表,利用map存储。map的键为产生式的左部,值为产生式右部。因为一个非终结符可能有多个右部,所以值用vector存储,可以存储多个右部字符串。map> expressionSet;

预测分析表本体利用二维数组存储。vector> table;

文件读取到的文法、消左递归的文法、消除回溯的文法、增广的文法,可以用于求解FIRST与FOLLOW的文法存储在不同的变量名中。

vector<string> filebuffer;//文件读取的缓冲区
vector<string> expression_withoutrecursion;//消左递归
vector<string> expression_withoutback;//消完左递归和回溯
vector<string> inputExpression;//最后增广完成,用于进行分析的文法

5.2 部分核心算法设计

5.2.1 消除左递归与回溯并消除或


image.png

消除“或”时,同样需要找到每处”|”,并对含有”|”符号的字符串进行重新拼接处理。

void leftRecursion(){
  for (string str : filebuffer){
    if (str[0] == str[str.find('>') + 1]){
      char newch = 'A';
      for (newch = 'A'; newch <= 'Z'; newch++)
        if (VN.count(newch) == 0)
          break;
      VN.insert(newch);
      string newstr = "";
      string alpha = str.substr(4, str.find('|') - 4);
      //  cout << "\nalpha" << alpha << endl;
      //A->betaA'
      newstr = newstr + str[0] + (string)"->" + str.substr(str.find('|') + 1) + newch;
      expression_withoutrecursion.push_back(newstr);
      newstr = "";
      //A'->alphaA'|e
      newstr = newstr + newch + "->" + alpha + newch + "|" + 'e';
      //e表示空字符episilon
      VT.insert('e');
      expression_withoutrecursion.push_back(newstr);
    }
    else
      expression_withoutrecursion.push_back(str);
  }
  cout << "\n消除左递归:\n";
  for (string str : expression_withoutrecursion)
    cout << str << endl;
}
//消除回溯
void leftTraceback(){
  for (string str : expression_withoutrecursion){
    //发现回溯
    if (str[3] == str[str.find("|") + 1]){
      char newch = 'A';
      for (newch = 'A'; newch <= 'Z'; newch++)
        if (VN.count(newch) == 0)
          break;
      VN.insert(newch);
      string delta = "" + str[3];
      string newstr = "";
      //A->alphaA'
      newstr += str[0] + "->" + delta + newch;
      expression_withoutback.push_back(newstr);
      newstr = "";
      //A'->beta1|beta2
      string beta1 = str.substr(4, str.find('|') - 4);
      string beta2 = str.substr(str.find('|') + 1);
      newstr += newch + "->" + beta1 + '|' + beta2;
      expression_withoutback.push_back(newstr);
    }
    else
      expression_withoutback.push_back(str);
  }
  cout << "\n消除回溯:\n";
  for (string str : expression_withoutback)
    cout << str << endl;
}
//增广,删除竖线|
void deleteor(){
  for (string str : expression_withoutback){
    if (str.find('|') != string::npos){
      //将A->beta1|beta2拆成A->beta1和A->beta2
      string right1 = str.substr(3, str.find('|') - 3);
      //  cout << "right1" << right1 << endl;
      string newstr1 = "";
      newstr1 = str[0] + (string)"->" + right1;
      //  cout << "newstr1" << newstr1 << endl;
      inputExpression.push_back(newstr1);
      string right2 = str.substr(str.find('|') + 1);
      //  cout << "right2" << right2 << endl;
      string newstr2 = "";
      newstr2 += str[0] + (string)"->" + right2;
      //  cout << "newstr2" << newstr2 << endl;
      inputExpression.push_back(newstr2);
    }
    else
      inputExpression.push_back(str);
  }
  cout << "\n增广\n";
  for (string str : inputExpression)
    cout << str << endl;
}

5.2.2 求解FIRST

求字符与短语的FIRST集合的过程与4.2章节中的流程图是一致的。这是一个递归的过程。因为涉及到了求单个字符与短语,所以需要对函数进行重载。

//字符的求first集
void getFirst(char c){
  //已经求完了当前字符的first集
  if (firstSet.count(c))
    return;
  set<char> newset;
  //若c为终结符,则first为自己,结束
  if (VT.count(c)){
    newset.insert(c);
    firstSet[c] = newset;
    return;
  }
  for (string s : expressionSet[c]){
    //若存在X->e,将e加入
    if ('e' == c)
      newset.insert('e');
    else{
      for (char cur : s){
        if (!firstSet.count(cur))
          getFirst(cur);
        set<char> curFirst = firstSet[cur];
        for (auto curf : curFirst)
          newset.insert(curf);
        //将Y的first集合中的元素中不是e的加入
        if (!curFirst.count('e'))
          break;
        //如果加到了e,则循环回去,即X->Y1Y2..Yk时,Y1产生e,就需要加入Y2的first集合
      }
    }
  }
  firstSet[c] = newset;
}
//多个字符(字符串、短语)first集求法
void getFirst(string s){
  if (firstSetS.count(s))
    return;
  set<char> newset;
  int i = 0;
  while (i < s.length()){
    char cur = s[i];
    if (!firstSet.count(cur))
      getFirst(cur);
    //将当前字符的first集作为短语的first集
    set<char> rightSet = firstSet[cur];
    for (auto rs : rightSet)
      newset.insert(rs);
  //类似的,如果加到了e,则循环回去,即X->Y1Y2..Yk时,Y1产生e,就需要加入Y2的first集合
    if (rightSet.count('e'))
      i++;
    else
      break;
    //遍历完了当前语句后,将空字加入当前语句first集
    if (i == s.length())
      newset.insert('e');
  }
  firstSetS[s] = newset;
}

5.2.3 求解FOLLOW

求解FOLLOW集合的流程是迭代的。在调用初始化函数时,需要针对每一个字符反复的对其求解FOLLOW集合。

//求每个非终结符的follow
  for (char c : VN)
    getFollow(c);

求解某一字符FOLLOW集合的流程与章节4.3的流程图是一致的。

//某个字符的follow集合
void getFollow(char c){
  vector<string> list = expressionSet[c];
  set<char> leftFollowSet;
  if (followSet.count(c))
    leftFollowSet = followSet[c];
  //对文法开始符号,将#加入follow集合
  if (c == S)
    leftFollowSet.insert('#');
  for (char ch : VN) {
    for (string s : expressionSet[ch]) {
      for (int i = 0; i < s.length(); i++) {
        //若有A->aBb(a可为空),则将b加入B的follow集合
        if (c == s[i] && i + 1 < s.length() && VT.count(s[i + 1])) 
          leftFollowSet.insert(s[i + 1]);
      }
    }
  }
  followSet[c] = leftFollowSet;
  for (string s : list){
    int i = s.length() - 1;
    //对每个产生式从右往左遍历
    while (i >= 0)
     {
      char cur = s[i];
      //对于当前标识符B
  //对于当前标识符之后的字符,若是非终结符,则将这个非终结符的first集加入当前字符的follow集
      if (VN.count(cur)){
        string right = s.substr(i + 1);
        set<char> rightFirstSet;
        if (!followSet.count(cur)) 
          getFollow(cur);
        set<char> curFollowSet = followSet[cur];
        //若A->aB或A->aB\beta且\beta->e
        //则把follow(A)加到follow(B)
        if (right.length() == 0)
          for (auto lfs : leftFollowSet) 
            curFollowSet.insert(lfs);
        else
          //将若A->Bbeta,将B后的字符的first(除e)加入
          if (right.length() == 1)
            if (!firstSet.count(right[0])) 
              getFirst(right[0]);
            rightFirstSet = firstSet[right[0]];
          else
            if (!firstSetS.count(right))
              getFirst(right);
            rightFirstSet = firstSetS[right];
          for (char var : rightFirstSet)
            if (var != 'e')
              curFollowSet.insert(var);
          //若A->Bbeta且e属于first(beta),将follow(A)加入follow(B)
          if (rightFirstSet.count('e'))
            for (auto lfs : leftFollowSet)
              curFollowSet.insert(lfs);
        }
        followSet[cur] = curFollowSet;
      }
      i--;
     }
  }
}

5.2.4 文法分析

image.png

void analyzeLL(){
  cout << "\nLL分析过程\n";
  cout << setw(20) << "stack" << setw(10) << "input" << setw(20) << "expression";
  cout << endl;
  analyzeStack.push('#');
  //#入栈
  analyzeStack.push(S);
  //开始符入栈
  displayLL();
  char X = analyzeStack.top();
  while (X != '#'){
    char a = strInput[index];
    if (X == a){
      action = "";
      analyzeStack.pop();
      index++;
    }
    else if (VT.count(X))
      return;//找到终结符,出错
    else if (find(X, a) == "")
      return;
    else if (find(X, a) == "e"){
      analyzeStack.pop();
      action = "";
      action += X;
      action += "->e";
    }//空字,出栈
    else{//其余情况,需要按分析表对栈顶元素进行推导
      string str = find(X, a);
      if (str != ""){
        action = "";
        action += X;
        action += "->";
        action += str;
        analyzeStack.pop();
        int len = str.length();
        for (int i = len - 1; i >= 0; i--)
        analyzeStack.push(str[i]);//栈顶元素出栈,按分析表和下一字符将分析表中元素入栈
      }
      else
      {
        cout << "\nERROR!\n";//出错,直接返回
        return;
      }
    }
    X = analyzeStack.top();
    displayLL();
  }
}

5.3 完整程序

https://blog.csdn.net/qq_46640863/article/details/125705891

六、运行结果

从文件中读取输入文法为算术表达式文法如下,这一文法存在左递归,且没有增广。

image.png

对于消除了”|”完成的文法,再次识别终结符与非终结符,并对每个非终结符输出其产生式右部。

在预处理工作完成后,即可对每个终结符与非终结符求解FIRST集合,求解非终结符的FOLLOW集合。其结果与第三章节的结果是完全一致的。

求解FIRST集合和FOLLOW集合完成后,构建分析表,其结果与第三章节的分析是一致的。


image.png

在对正确地文法分析完毕后,分析过程正确地停止,程序退出。

七、实验感悟


目录
相关文章
|
6月前
|
网络安全
编译原理复习二:Top-Down分析LL(1)文法的判断与LL(1)分析表的构造(附题目与答案 超详细)
编译原理复习二:Top-Down分析LL(1)文法的判断与LL(1)分析表的构造(附题目与答案 超详细)
433 1
|
6月前
编译原理复习三:Bottom-Up LR(0)自动机构造 SLR(1)分析表与分析器的构造(附题目与答案 超详细)
编译原理复习三:Bottom-Up LR(0)自动机构造 SLR(1)分析表与分析器的构造(附题目与答案 超详细)
124 0
|
5月前
|
数据格式 Windows
必知的技术知识:Excel公式SWITCH函数你用过吗?多种查找函数介绍
必知的技术知识:Excel公式SWITCH函数你用过吗?多种查找函数介绍
87 0
|
6月前
|
存储 编译器 测试技术
【编译原理】LL(1)分析法:C/C++实现
【编译原理】LL(1)分析法:C/C++实现
626 0
|
6月前
|
存储 缓存 安全
learn_C_deep_9 (汇编角度理解return的含义、const 的各种应用场景、volatile 的基本理解与实验证明)
learn_C_deep_9 (汇编角度理解return的含义、const 的各种应用场景、volatile 的基本理解与实验证明)
成信大ENVI_IDL第一周实验测试:数组的简单运算+详细解析
成信大ENVI_IDL第一周实验测试:数组的简单运算+详细解析
90 0
|
SQL 存储 关系型数据库
SQL优化万能公式:5 大步骤 + 10 个案例
SQL优化万能公式:5 大步骤 + 10 个案例
4231 7
SQL优化万能公式:5 大步骤 + 10 个案例
|
存储 算法
编译原理 实验四 LR(0)分析法(LR0分析表的自动生成)
编译原理 实验四 LR(0)分析法(LR0分析表的自动生成)
1468 0
编译原理 实验四 LR(0)分析法(LR0分析表的自动生成)
|
存储 Go C语言
编译原理,C语言实现LR(0)分析(扩展文法的生成、项目集规范簇的生成、ACTION GOTO表的生成、句子的分析)
注:代码生成的项目集规范簇、ACTION GOTO表的顺序可能和课本、教材、参考答案的顺序不同,但这并不影响分析过程的正确性,毕竟机器是按规律办事的😄
499 0
编译原理,C语言实现LR(0)分析(扩展文法的生成、项目集规范簇的生成、ACTION GOTO表的生成、句子的分析)
编译原理 实验三 LL(1)分析法(LL1分析表的自动生成) 完整代码
编译原理 实验三 LL(1)分析法(LL1分析表的自动生成) 完整代码
218 0