编译原理实验二:LL(1)语法分析器

一、实验要求

  

  不得不想吐槽一下编译原理的实验代码量实在是太大了,是编译原理撑起了我大学四年的代码量...

  这次实验比上次要复杂得多,涵盖的功能也更多了,我觉得这次实验主要的难点有两个(其实都是难点...):

  1. 提取左公因子或消除左递归(实现了消除左递归)

  2. 递归求First集和Follow集

  其它的只要按照课本上的步骤顺序写下来就好(但是代码量超多...),下面我贴出实验的一些关键代码和算法思想。

二、基于预测分析表法的语法分析

  2.1 代码结构

    2.1.1  Grammar类

       功能:主要用来处理输入的文法,包括将文法中的终结符和非终结符分别存储,检测直接左递归和左公因子,消除直接左递归,获得所有非终结符的First集,Follow集以及产生式的Select集。

#ifndef GRAMMAR_H
#define GRAMMAR_H
#include <string>
#include <cstring>
#include <iostream>
#include <vector>
#include <set>
#include <iomanip>
#include <algorithm>
using namespace std;
const int maxn = 110;

//产生式结构体
struct EXP{
    char left;      //左部
    string right;   //右部
};

class Grammar
{
    public:
        Grammar();               //构造函数
        bool isNotTer(char x);   //判断是否是终结符
        int  getTer(char x);     //获取终结符下标
        int  getNonTer(char x);  //获取非终结符下标
        void getFirst(char x);   //获取某个非终结符的First集
        void getFollow(char x);  //获取某个非终结符的Follow集
        void getSelect(char x);  //获取产生式的Select集
        void input();            //输入文法
        void scanExp();          //扫描输入的产生式,检测是否有左递归和左公因子
        void remove();           //消除左递归
        void solve();            //处理文法,获得所有First集,Follow集以及Select集
        void display();          //打印First集,Follow集,Select集
        void debug();            //用于debug的函数
        ~Grammar();               //析构函数
    protected:
        int cnt;                 //产生式数目
        EXP exp[maxn];           //产生式集合
        set<char> First[maxn];   //First集
        set<char> Follow[maxn];  //Follow集
        set<char> Select[maxn];  //select集
        vector<char> ter_copy;   //去掉$的终结符
        vector<char> ter;        //终结符
        vector<char> not_ter;    //非终结符
};
#endif

       2.1.2  AnalyzTable类

    功能:得到预测分析表,判断输入的文法是否是LL(1)文法,用预测分析表法判断输入的符号串是否符合刚才输入的文法,并打印出分析过程。

#ifndef ANALYZTABLE_H
#define ANALYZTABLE_H
#include "Gramma.h"
class AnalyzTable:public Gramma
{
    public:
        AnalyzTable();
        void getTable();          //得到分析表
        void judge();             //判断是否是LL(1)文法
        void analyExp(string s);  //分析输入串
        void displayTable();      //打印表
        void inputString();       //输入符号串
        ~AnalyzTable();
    protected:
        string s;                 //符号串
        vector<char> stack;       //分析栈
        vector<char> left;        //剩余输入串
        int detect[maxn][maxn];   //检测表
        int table[maxn][maxn];    //预测分析表
};
#endif

  2.2 记号和规定

    • 非终结符:大写字母‘A‘~‘Z‘
    • 终结符:除大写字母之外的所有字符
    • 空串:$
    • 符号栈终止符:#
    • 规定第一个产生式的左边那个非终结符就是开始符号
    • 输入的产生式需要分开写(比如A->a|b, 要输入A->a和A->b才能处理)

  2.3 算法思想

    2.3.1 求First集的算法思想      

  • 遍历每一个左部为x的产生式
  • 如果产生式右部第一个字符为终结符,则将其计入左部非终结符的First集
  • 如果产生式右部第一个字符为非终结符
  • 求该非终结符的First集
  • 将该非终结符的去掉$的First集计入左部的First集
  • 若存在$,继续往后遍历右部
  • 若不存在$,则停止遍历该产生式,进入下一个产生式
  • 若已经到达产生式的最右部的非终结符(即右部的First集都含有空串),则将$加入左部的First集
  • 处理数组中重复的First集中的终结符
//求出非终结符的First集
void Gramma::getFirst(char x){
    cout<<x<<endl;
    bool flag = 0;  //记录非终结符的First集是否有空串
    int tot = 0;    //记录一个非终结符产生式含有空串的产生式
    for(int i=0;i<cnt;i++){
        if(exp[i].left==x){
            //如果右部的第一个字符是终结符
            if(!isNotTer(exp[i].right[0])){
                First[getNonTer(x)].insert(exp[i].right[0]);
            }
            //如果是非终结符
            else{
                //从左到右遍历右部
                for(int j=0;j<exp[i].right.length();j++){
                    //如果遇到终结符,结束
                    if(!isNotTer(exp[i].right[j])){
                        First[getNonTer(x)].insert(exp[i].right[j]);
                        break;
                    }
                    //不是终结符,求该非终结符的First集
                    getFirst(exp[i].right[j]);
                    set<char>::iterator it;
                    int ind = getNonTer(exp[i].right[j]);
                    for(it=First[ind].begin();it!=First[ind].end();it++){
                        if(*it==‘$‘){
                            flag = 1;
                        }else{
                            First[getNonTer(x)].insert(*it);
                        }
                    }
                    //没有空串就不必再找下去了
                    if(flag==0){
                        break;
                    }else{
                        flag = 0;
                        tot++;
                    }
                }
                //如果右部所有符号的First集都有空串,则符号x的First集也有空串
                if(tot==exp[i].right.length()){
                    First[getNonTer(x)].insert(‘$‘);
                }
            }
        }
    }
}

2.3.2 求Follow集的算法思想

  • 遍历每一个右部包含非终结符x的产生式
  • 如果x的下一个字符是终结符,添加进x的Follow集
  • 如果x的下一个字符是非终结符,把该字符的First集加入x的Follow集(不能加入空串)
  • 如果下一字符的First集有空串并且该产生式的左部不是x,则把左部的Follow集加入x的Follow集
  • 如果x已经是产生式的末尾,则把左部的Follow集添加到x的Follow集里
//求出非终结符的Follow集
void Gramma::getFollow(char x){
    //找到非终结符x出现的位置
    for(int i=0;i<cnt;i++){
        int index = -1;
        int len = exp[i].right.length();
        for(int j=0;j<len;j++){
            if(exp[i].right[j]==x){
                index = j;
                break;
            }
        }
        //如果找到了x,并且它不是最后一个字符
        if(index!=-1&&index<len-1){
            //如果下一个字符是终结符,添加进x的Follow集
            char next = exp[i].right[index+1];
            if(!isNotTer(next)){
                Follow[getNonTer(x)].insert(next);
            }else{
            //如果下一个字符是非终结符
                bool flag = 0;
                set<char>::iterator it;
                //遍历下一个字符的First集
                for(it = First[getNonTer(next)].begin();it!=First[getNonTer(next)].end();it++){
                    if(*it==‘$‘){
                        flag = 1;
                    }else{
                        Follow[getNonTer(x)].insert(*it);
                    }
                }
                //如果有空串并且左部不是它本身(防止陷入死循环),当前非终结符的Follow集是x的Follow集
                char tmp = exp[i].left;
                if(flag&&tmp!=x){
                    getFollow(tmp);
                    set<char>::iterator it;
                    for(it = Follow[getNonTer(tmp)].begin();it!=Follow[getNonTer(tmp)].end();it++){
                        Follow[getNonTer(x)].insert(*it);
                    }
                }
            }
        }else if(index!=-1&&index==len-1&&x!=exp[i].left){
            //如果x在产生式的末尾,则产生式左部的Follow集应该添加到x的Follow集里
            char tmp = exp[i].left;
            getFollow(tmp);
            set<char>::iterator it;
            for(it = Follow[getNonTer(tmp)].begin();it!=Follow[getNonTer(tmp)].end();it++){
                Follow[getNonTer(x)].insert(*it);
            }
        }
    }
} 

    2.3.3 求预测分析表的算法思想

  • 遍历每一个产生式
  • 如果右部的第一个字符tmp是终结符且不是空串,更新预测分析表,即table[left][tmp] = i(i为产生式编号)
  • 如果右部的第一个字符是空串,遍历左部的Follow集,更新预测分析表,即table[left][x] = i(i为产生式编号,x为Follow集字符编号)
  • 如果右部的第一个字符是非终结符,遍历它的First集,更新预测分析表,即table[left][x] = i(i为产生式编号,x为First集字符编号)
//获得预测分析表
void AnalyzTable::getTable(){
    for(int i=0;i<cnt;i++){
        char tmp = exp[i].right[0];
        //如果产生式右部的第一个字符是终结符
        if(!isNotTer(tmp)){
            //该终结符不是空串,更新table
            if(tmp!=‘$‘){
                detect[getNonTer(exp[i].left)][getTer(tmp)]++;
                table[getNonTer(exp[i].left)][getTer(tmp)] = i;
            }
            if(tmp==‘$‘){
                //该终结符是空串,遍历左部的Follow集,更新table
                set<char>::iterator it;
                for(it = Follow[getNonTer(exp[i].left)].begin();it!=Follow[getNonTer(exp[i].left)].end();it++){
                    table[getNonTer(exp[i].left)][getTer(*it)] = i;
                    detect[getNonTer(exp[i].left)][getTer(*it)]++;
                }
            }
        }else{
            //如果产生式右部的第一个字符是非终结符,遍历它的First集,更新table
            set<char>::iterator it;
            for(it = First[getNonTer(tmp)].begin();it!=First[getNonTer(tmp)].end();it++){
                table[getNonTer(exp[i].left)][getTer(*it)] = i;
                detect[getNonTer(exp[i].left)][getTer(*it)]++;
            }
            //如果有空串,遍历左部的Follow集,更新table
            if(First[getNonTer(tmp)].count(‘$‘)!=0){
                set<char>::iterator it;
                for(it = Follow[getNonTer(exp[i].left)].begin();it!=Follow[getNonTer(exp[i].left)].end();it++){
                    table[getNonTer(exp[i].left)][getTer(*it)] = i;
                    detect[getNonTer(exp[i].left)][getTer(*it)]++;
                }
            }
        }
    }
}

    2.3.4 符号串的分析过程

  • 规定f1为符号栈栈顶字符,f2为剩余输入串的第一个字符
  • 若f1=f2=“#”,则分析成功,停止分析
  • 若f1=f2≠”#”,则把f1从栈顶弹出,让f2指向下一个输入符号.
  • 若f1是一个非终结符,则查看分析表,若table[f1][f2]中有值,则f1出栈,并且产生式的右部反序进栈
  • 再把产生式的右部符号推进栈的同时应做这个产生式相应得语义动作,若M[A,a]中存放着”出错标志”,则调用出错诊察程序error.

      

                        分析栈

                    流程图

//分析符号串
void AnalyzTable::analyExp(string s){
    cout<<setw(15)<<"分析栈"<<setw(15)<<"剩余输入串"<<setw(20)<<"推导式"<<endl;
    //把整个串倒序push进剩余符号vector
    left.push_back(‘#‘);
    for(int i=s.length()-1;i>=0;i--){
        left.push_back(s[i]);
    }
    //把#和开始符push进分析栈
    stack.push_back(‘#‘);
    stack.push_back(not_ter[0]);
    //如果剩余输入串长度不为0,就一直循环
    while(left.size()>0){
        //输出分析栈内容
        string outputs = "";
        for(int i=0;i<stack.size();i++){
            outputs+=stack[i];
        }
        cout<<setw(15)<<outputs;
        outputs = "";
        //输出剩余输入串内容
        for(int i=left.size()-1;i>=0;i--){
            outputs+=left[i];
        }
        cout<<setw(15)<<outputs;
        char f1 = stack[stack.size()-1];
        char f2 = left[left.size()-1];
        //如果可以匹配,并且都为#
        if(f1==f2&&f1==‘#‘){
            cout<<setw(21)<<"Accept!"<<endl;
            return;
        }
        //如果可以匹配,并且都为终结符
        if(f1==f2){
            stack.pop_back();
            left.pop_back();
            cout<<setw(15)<<f1<<" 匹配"<<endl;
        }else if(table[getNonTer(f1)][getTer(f2)]!=-1){
            //如果在预测分析表中有值
            int index = table[getNonTer(f1)][getTer(f2)];
            stack.pop_back();
            if(exp[index].right!="$"){
                for(int i=exp[index].right.length()-1;i>=0;i--){
                    stack.push_back(exp[index].right[i]);
                }
            }
            cout<<setw(15)<<exp[index].left<<"->"<<exp[index].right<<endl;
        }else{
            cout<<setw(15)<<"error"<<endl;
            return;
        }
    }
}

原文地址:https://www.cnblogs.com/jiaqi666/p/9879777.html

时间: 2024-08-26 18:31:10

编译原理实验二:LL(1)语法分析器的相关文章

编译原理 - 实验三 - 递归下降语法分析器的调试及扩展

一. 语法分析介绍 语法分析是编译过程的核心部分,它的主要任务是按照程序语言的语法规则,从由词法分析输出的源程序符号串中识别出各类语法成分,同时进行语法检查,为语义分析和代码生成做准备.执行语法分析任务的程序叫语法分析程序或语法分析器. 二. 所实现的语义分析和代码生成程序能处理什么语句 (1)简单变量的声明语句 (2)表达式语句 (3)if语句. (4)while语句 (5)for语句 (6)write语句 (7)read语句 (8)do语句. (9)处理过程调用和返回 三.实验过程 ①用VC

编译原理实验:java实现语法分析器

实验方法:递归下降分析法基本思想是,对文法中的每个非终结符编写一个函数,每个函数的功能是识别由该非终结符所表示的语法成分.因此需要分别构造 E,E’,T,T’,F 函数来执行自己的识别功能,根据文法的内容顺序决定函数的识别功能. java程序实现: import java.util.Scanner; public class GrammarAnalysis { static char[] s = new char[100]; static int sing; static int i; //用来

编译原理 - 实验二 - FLEX词法分析器

FLEX词法分析器 一.Lex和Yacc介绍 Lex 是一种生成扫描器的工具.扫描器是一种识别文本中的词汇模式的程序. 一种匹配的常规表达式可能会包含相关的动作.这一动作可能还包括返回一个标记. 当 Lex 接收到文件或文本形式的输入时,它试图将文本与常规表达式进行匹配. 它一次读入一个输入字符,直到找到一个匹配的模式. 如果能够找到一个匹配的模式,Lex 就执行相关的动作(可能包括返回一个标记). 另一方面,如果没有可以匹配的常规表达式,将会停止进一步的处理,Lex 将显示一个错误消息. Ya

编译原理:LL(1)文法 语法分析器(预测分析表法)

设计要求:对于任意输入的一个LL(1)文法,构造其预测分析表,并对指定输入串分析其是否为该文法的句子. 思路:首先实现集合FIRST(X)构造算法和集合FOLLOW(A)构造算法,再根据FIRST和FOLLOW集合构造出预测分析表,并对指定的句子打印出分析栈的分析过程,判断是否为该文法的句子. 指定文法: //文法 E->TK K->+TK K->$ T->FM M->*FM M->$ F->i F->(E) 对于输入串i+i*i# ,这里我们先给出实验结果

CSUFT 编译原理实验二LL(1)文法分析

1 #include <cstdio> 2 #include <cstring> 3 #include <iostream> 4 #include <stack> 5 #include <queue> 6 #include <map> 7 #include <algorithm> 8 #include <vector> 9 10 using namespace std; 11 12 char A[20];/*分

哈工大软件学院编译原理实验1——词法分析

这次实验被"过来人"们定位非常easy,实验内容例如以下: ----------------------------------------------------------------------------------- 对例如以下工作进行展开描写叙述 (1) 给出语言的词法规则描写叙述 · 标识符.keyword.整常数.字符常数.浮点常数 · 单界符:+,-,×,;,- · 双界符:/*,:=,>=,<=,!=,- · 凝视 (2) 针对这样的单词的状态转换图和程

编译原理实验代码(词法分析,语法分析,中间代码生成)

花了一天写出的程序没有顾及很多层面,但对于理解基本的实验道理和交上实验还是有点帮助的.代码实现了基于有限自动机的词法分析,采用递归下降分析法和EBNF文法实现语法分析并生成中间代码. lexAnalysis.h /* * lexAnalysis.h * * Created on: 2014-12-2 * Author: liuqiushan */ #ifndef LEXANALYSIS_H_ #define LEXANALYSIS_H_ #include <stdio.h> #include

实验二 PHP基本语法实验

实验二 PHP基本语法实验 0 实验准备 0.1实验环境和相关工具软件 具体到的机房环境,请在Windowsxp环境下做本实验: l  操作系统:Windowsxp l  Web服务器:Apache 2.0.63  默认发布文档路径:c:\Apache\htdocs l  PHP:PHP5.2.11  安装路径:c:\php l  数据库:MySQL 5.1.39  使用默认路径 l  脚本编辑器: 记事本 或 EditPlus-v3.11.463H 或 Macromedia Dreamweav

[Vue源码]一起来学Vue模板编译原理(二)-AST生成Render字符串

本文我们一起通过学习Vue模板编译原理(二)-AST生成Render字符串来分析Vue源码.预计接下来会围绕Vue源码来整理一些文章,如下. 一起来学Vue双向绑定原理-数据劫持和发布订阅 一起来学Vue模板编译原理(一)-Template生成AST 一起来学Vue模板编译原理(二)-AST生成Render字符串 一起来学Vue虚拟DOM解析-Virtual Dom实现和Dom-diff算法 这些文章统一放在我的git仓库:https://github.com/yzsunlei/javascri