编译原理

大作业

题    目:  LL(1)文法预测分析表的实现

班   级:

计算机科学与技术学院

2019年

、、

目录

1目的12要求13分工14设计15代码46程序运行截图14

目的

通过本次大作业,使学生对自顶向下语法分析的基本概念、基本原理、基本方法等充分理解并掌握预测分析方法。

要求

  1. 给定无左递归和无回溯的文法,计算FIRST集合和FOLLOW集合,并构造出预测分析表。
    文法如下:(在这里用e来表示空串ε)
    E->TA A->+TA A->e T->FB
    B->*FB B->e F->(E) F->i
  2. 根据预测分析表,判断该文法是否为LL(1)文法。

分工

本次大作业以5-6人为一组,各组成员按照实际完成情况填写下表

求出能推出ε的非终结符

计算FIRST集

计算FOLLOW集

计算SELECT集

构造预测分析表

判断LL(1)文法

负责人

设计

对给定的无左递归和无回溯的上下文无关文法,按以下步骤执行(每个步骤写出具体算法):

  1. 初始化文法
    从文档中读取文法,然后用结构体把文法存储,非终结符通常用大写字母表示,所以通常有26个,为了后期的遍历方便,所以选用哈希表进行非终结符的存储:
    Item** pGram = (Item**)malloc(sizeof(Item*)*26);//用于存储文法
    typedef struct item //用于记录非终结符推出的项
    {
    char val[20];//非终结符推出的内容
    item* pItemNext;
    }Item;
    如图:

编译原理大作业完整代码 + 报告_初始化

  1. 求出能推出ε的非终结符
    如果A->B,B->e 时,则A->e;
    所以求推出ε的非终结符需要依赖其他非终结符,所以采用递归算法。
  2. 构造FOLLOW集合算法
    3.1FOLLOW求法
    ①S为开始符号,则加入FOLLOW(S)
    ②则 的非空元素加入FOLLOW(B)中
    若则将加入FOLLOW(B)中。
    3.2FOLLOW集结构
    FOLLOW集结构是一个包含26个大写英文字母的指针数组,每一个数组元素为每个非终结符的头指针。FOLLOW集由链表构成。即邻接表形式。由于用到开始符号,所以将文法产生式结构的第26个元素用于求开始符号。

编译原理大作业完整代码 + 报告_i++_02

3.3构造FOLLOW集的算法。
构造FOLLOW用到了两个函数GetFollow,和Follow以及ischangefollow数组。其中GetFollow为总入口创建Follow集以及初始化,调用Follow求每一个非终结符的Follow集。ischangfollow判断是否此次求Follow是否有新的添加项,有则再求一次。Follow经过一轮无更新后,求取完毕。
GetFollow
Node** GetFollow(Item**pGramCon ,char *pNULLKey,Node** pFirst)
这个函数是获得所有非终结符的FOLLOW集,要用到文法pGramCon,非终结符能否走到空pPULLKEY,以及First集pFirst。
此函数的流程图如下所示:

编译原理大作业完整代码 + 报告_预测分析_03

Follow
Node* Follow(char key, Item**pGramCon,char *pNULLKey,Node** pFirst,Node** pFollow)
这个是得到单个非终结符的Follow集,并维护ischangefollow数组。

  1. 构造SELECT集合算法
    4.1Select求法
    ①若经过零步或多步推不出则产生式的select集为产生式的右部的first集合。
    ②能推出则select集为产生式右部非空并上follow产生式左部非终结符集。
    4.2Select集结构
    由于是对每个产生式求Select集,所以之前构造First集和Follow集的结构不适用。所以我在文法处添加了valnum标记每个产生式的位置。用全局变量GenCount记录产生式的个数。依旧为邻接表形式结构,但每个元素头代表产生式的valnum标记。即第0号元素为第0号产生式的Select集开头。
    4.3Select求法
    用到了两个函数,GetSelect为总入口,Select为求每个产生式的Select集。GetSelect函数就是遍历文法对每个产生式调用Select函数。Select函数的流程图如下所示:

编译原理大作业完整代码 + 报告_初始化_04

  1. 构造预测分析表M
    5.1预测分析表的结构
    由于不知是否文法是LL1文法,故无法确定预测分析表每一个元素仅为一个产生式。故将预测分析表构造成二维指针数组形式。每一个元素为此点符合要求的产生式的链表头部指针。
    5.2预测分析表的构造
    大概方法就是遍历Select集,对预测分析表进行赋值。
    5.3预测分析表的输出
    其中产生式前的零代表,此处符合要求的产生式为一。假若产生式不唯一,避免显示效果的不美观,仅显示第一个产生式。

编译原理大作业完整代码 + 报告_预测分析_05

  1. 判断是否为LL(1)文法

6.1判断方法

判断左部相同产生式的select集合是否有交集不为空,有则不是LL(1)文法。

6.2判断方法实现

select集合形象的表示在了预测分析表中,通过第五步够造的预测分析表即可判断是否为LL(1)文法。判断方法为如果预测分析表节点所表示的链表有两个产生式的结点。即交集不为空,就不是LL(1)文法。

代码

  1. 初始化文法的主要代码
    Item** Init()//初始化文法--------从文档中读取
    {
    FILE* file = fopen("Grammer_Content.txt","r");
    if(!file)
    {
    printf("文件打开失败!!!\n");
    exit(-1);
    }
    Item** pGram = (Item**)malloc(sizeof(Item*)*26);//用于存储文法
    memset(pGram,0,sizeof(Item*)*26);
    char key;
    char val[20] = {0};
    while((fscanf(file,"%c %s",&key,val)) != EOF)
    {
    Item*pTemp = pGram[key-'A'];
    Item* pMark = (Item*)malloc(sizeof(Item));
    Node* pNode = NULL;
    Node* p = NULL;
    memset(pMark,0,sizeof(Item));
    //找到项的放置位置
    if(pTemp == NULL)
    {
    pGram[key-'A'] = pMark;
    }
    else
    {
    while(pTemp->pItemNext)
    {
    pTemp = pTemp->pItemNext;
    }
    pTemp->pItemNext = pMark;
    }
    sprintf(pMark->val,"%s",val);
    fgetc(file);
    }
    return pGram;
    }
  2. 求出能推出ε的非终结符
    int IsToNULL(char key,Item** pGramCon)//判断非终结符能否直接推出空(‘e’)
    {
    if(key > 'Z' || key < 'A' ||pGramCon[key-'A'] == NULL) return 0;
    Item* pItem = pGramCon[key-'A'];
    while(pItem)
    {
    if(pItem->val[0] == 'e')
    return 1;
    pItem = pItem->pItemNext;
    }
    return 0;
    }
    int IsStrToNULL(char c,char*pNullArr,Item** pGramCon)//判断非终结符能否经多次推到-》空(‘e’)
    {
    if(pGramCon[c-'A'] == NULL)return 0;
    if(pNullArr[c-'A'] != 0) return 1;
    Item *pItem = pGramCon[c-'A'];
    int i;
    while(pItem)
    {
    for(i = 0; i<strlen(pItem->val); i++ )
    //终结符
    if(!(pItem->val[i] >= 'A'&& pItem->val[i] <= 'Z') && pItem->val[i]!= 'e')
    break;
    //str[i]为非终结符,是否能推出e;
    else if(IsStrToNULL(pItem->val[i],pNullArr,pGramCon) == 0)
    {
    break;
    }

    if(i == strlen(pItem->val))
    return 1;
    pItem = pItem->pItemNext;
    }
    return 0;
    }
    3.求解FIRST(X)代码:
    void InsertFirst(char key,Node** pFirst) //把终结符插入first集中
    {
    Node *pTemp = (Node*)malloc(sizeof(pTemp));
    pTemp->m_key = key;
    pTemp->pNext = NULL;
    if(*pFirst == NULL)
    {
    *pFirst = pTemp;
    return;
    }
    Node* pMark = *pFirst;
    while( pMark->m_key != key && pMark->pNext)
    {
    pMark = pMark->pNext;
    }
    if(pMark->m_key == key)
    {
    free(pTemp);
    pTemp == NULL;
    return;
    }
    pMark->pNext = pTemp;
    }
    Node* First(char key, Item**pGramCon,char *pNULLKey,Node** pFirst) //递归得到非终结符的FIRST集
    {
    if(pFirst[key-'A'] != NULL) return pFirst[key-'A'];
    Item* pItem = pGramCon[key-'A'];
    Node* pMark = NULL;
    while(pItem)
    {
    for(int i =0 ; i<strlen(pItem->val); i++)
    {
    pMark = NULL;
    //如果为非终结符
    if(pItem->val[i] >= 'A' && pItem->val[i] <= 'Z')
    {
    pMark = First(pItem->val[i],pGramCon,pNULLKey,pFirst);
    if(pMark != NULL)
    {
    while(pMark)
    {
    InsertFirst(pMark->m_key,&pFirst[key-'A']);
    pMark = pMark->pNext;
    }
    }
    //如果能推出e(空)
    if(pNULLKey[pItem->val[i]-'A']!=0)
    continue;
    else
    break;
    }
    //为终结符且非空
    else if(!(pItem->val[i] >= 'A' && pItem->val[i] <= 'Z') && pItem->val[i] != 'e')
    {
    InsertFirst(pItem->val[i],&pFirst[key-'A']);
    break;
    }
    }
    pItem = pItem->pItemNext;
    }
    if(pNULLKey[key-'A'] != 0)
    {
    InsertFirst('e',&pFirst[key-'A']);
    }
    return pFirst[key-'A'];
    }
    Node** GetFirst(Item**pGramCon ,char *pNULLKey) //得到所有非终结符的FIRST集
    {
    Node** pFirst = (Node**)malloc(sizeof(Node*)*26); //用于存储非终结符的FIRST集
    memset(pFirst,0,sizeof(Node*)*26);
    Node* pTemp = NULL;
    Node* pMark = NULL;
    char c;
    for(int i = 0; i<26; i++)
    {
    if(pGramCon[i] == NULL)
    continue;
    if(pFirst[i] == NULL)
    First(i+'A',pGramCon,pNULLKey,pFirst);
    }
    return pFirst;
    }
    void PrintFirst(Node** pFirst) //打印FIRST集
    {
    Node* pNode;
    for(int i= 0; i< 26; i++)
    {
    if(pFirst[i] != NULL)
    {
    printf("First(%c)={",i+'A');
    pNode = pFirst[i];
    while(pNode->pNext)
    {
    printf("%c,",pNode->m_key);
    pNode = pNode->pNext;
    }
    printf("%c",pNode->m_key);
    printf("}\n");
    }
    }
  3. 求Follow集主要代码
    bool ischangefollow[26]={0};
    Node* Follow(char key, Item**pGramCon,char *pNULLKey,Node** pFirst,Node** pFollow) //得到非终结符的Follow集
    {
    Item* pItem= NULL;
    for(int i=0;i<26;i++){
    if(pGramCon[i]!=NULL){
    pItem = pGramCon[i];
    while (pItem!=NULL)
    {
    for(int j=strlen(pItem->val)-1;j>=0;j--){
    if(pItem->val[j]==key){
    bool isexec = false;
    if(isupper(pItem->val[j+1])){
    if(insertFirst_To_Follow(pFirst[pItem->val[j+1]-'A'],pFollow[key-'A'])){
    ischangefollow[key-'A']=true;
    }
    if(pNULLKey[pItem->val[j+1]-'A']!=0){
    isexec=true;
    }
    }else if(pItem->val[j+1]=='\0'){
    isexec=true;
    }else{
    if(InsertNode(pItem->val[j+1],&pFollow[key-'A'])){
    ischangefollow[key-'A']=true;
    }
    }
    if(isexec){
    if(i+'A'==key)continue;
    Node *pNode =pFollow[i];
    while(pNode!=NULL){
    if(InsertNode(pNode->m_key,&pFollow[key-'A'])){
    ischangefollow[key-'A']=true;
    }
    pNode=pNode->pNext;
    }
    }
    }
    }
    pItem=pItem->pItemNext;
    }
    }
    }
    return pFollow[key-'A'];
    }
    Node** GetFollow(Item**pGramCon ,char *pNULLKey,Node** pFirst) //获得所有非终结符的FOLLOW
    {
    Node** pFollow = (Node**)malloc(sizeof(Node*)*26); //用于存储非终结符的FOLLOW集
    memset(pFollow,0,sizeof(Node*)*26);
    InsertNode('#',&pFollow[pGramCon[26]->val[0]-'A']);//#加入开始符号的Follow集中
    bool ischange = true;
    while (ischange)
    {
    for(int i=0;i<26;i++){
    if(pGramCon[i]!=NULL)
    Follow(i+'A',pGramCon,pNULLKey,pFirst,pFollow);
    }
    ischange=false;
    for(int i=0;i<26;i++){
    if(ischangefollow[i]){
    ischangefollow[i]=false;
    ischange=true;
    }
    }
    }
    return pFollow;
    }
  4. 求Select集主要代码
    void Select(char key,Item* pKey /*产生式索引*/, Item**pGramCon,char *pNULLKey,Node** pFirst,Node** pFollow,Node** pSelect)
    {
    bool can_To_e=true;
    int len =strlen(pKey->val) ;
    for(int i=0;i<len && can_To_e;i++){
    if(pKey->val[i]!='e'){
    if(isupper(pKey->val[i])&&pNULLKey[pKey->val[i]-'A']==0)can_To_e=false;
    if(!isupper(pKey->val[i]))can_To_e=false;
    }
    }
    for(int i=0;i<len;i++){
    if(pKey->val[i]!='e' && !isupper(pKey->val[i])){//是终结符
    InsertNode(pKey->val[i],&pSelect[pKey->valnum]);
    break;
    }else if(isupper(pKey->val[i])){//是非终结符
    Node *pNode = pFirst[pKey->val[i]-'A'];
    while (pNode)
    {
    if(pNode->m_key!='e')
    InsertNode(pNode->m_key,&pSelect[pKey->valnum]);
    pNode=pNode->pNext;
    }
    break;
    }
    }
    if(can_To_e){//产生式右部能推出空,加入产生式左部
    Node* pNode=pFollow[key-'A'];
    while (pNode)
    {
    InsertNode(pNode->m_key,&pSelect[pKey->valnum]);
    pNode=pNode->pNext;
    }
    }
    }
    Node** GetSelect(Item**pGramCon ,char *pNULLKey,Node** pFirst,Node** pFollow) //获得所有非终结符的Select
    {
    Node** pSelect = (Node**)malloc(sizeof(Node*)*GenCount); //用于存储非终结符的Select集
    memset(pSelect,0,sizeof(Node*)*GenCount);
    for(int i=0;i<26;i++){
    if(pGramCon[i]!=NULL){
    Item * pItem = pGramCon[i];
    while (pItem)
    {
    Select(i+'A',pItem,pGramCon,pNULLKey,pFirst,pFollow,pSelect);
    pItem=pItem->pItemNext;
    }
    }
    }
    return pSelect;
    }
  5. 求预测分析表主要代码
    char findLeft(int num,Item**pGramCon,Item**pWenFa)//根据产生式的索引标记找到产生式左部非终结符
    {
    Item* pItem=NULL;
    for(int i=0;i<26;i++){
    if(pGramCon[i]!=NULL){
    pItem=pGramCon[i];
    while (pItem)
    {
    if(pItem->valnum==num){
    sprintf((*pWenFa)->val,"%s",pItem->val);
    (*pWenFa)->valnum=num;
    return i+'A';
    }
    pItem=pItem->pItemNext;
    }
    }
    }
    }
    bool InsertGramCon(Item* pT,Item**pItem) //把终结符插入预测分析表中
    {
    if(*pItem == NULL)
    {
    *pItem = pT;
    return true;
    }
    Item* pMark = *pItem;
    while( strcmp(pMark->val,pT->val) && pMark->pItemNext)
    {
    pMark = pMark->pItemNext;
    }
    if(strcmp(pMark->val,pT->val))
    {
    free(pT);
    pT = NULL;
    return false;
    }
    pMark->pItemNext = pT;
    return true;
    }
    Item*(*GetPredictTable(Item**pGramCon,Node**pSelect))[128]//表:26行为非终结符,128列为终结符,返回预测分析表指针
    {
    Node*pNode=NULL;
    Item*(*pPredictTable)[128] = (Item* (*)[128])malloc(sizeof(Item*)*26*128);
    memset(pPredictTable,0,sizeof(Item*)*26*128);
    for(int i=0;i<GenCount;i++){//对Select集合遍历找出终结符
    pNode = pSelect[i];
    while (pNode)
    {
    Item*pItem = (Item*)malloc(sizeof(Item));
    pItem->pItemNext=NULL;
    char ch = findLeft(i,pGramCon,&pItem);//返回左部非终结符,以及将pItem赋值初始化
    //printf("%c %c ->%s\n",ch,pNode->m_key,pItem->val);
    InsertGramCon(pItem,&pPredictTable[ch-'A'][pNode->m_key]);
    Item*ppp = pPredictTable[ch-'A'][pNode->m_key];
    pNode=pNode->pNext;
    }
    }
    return pPredictTable;
    }
  6. 判断文法是否为LL(1)文法

bool JudgePredictTable(Item*(*pPredictTable)[128])

{

bool isLL1=true;

for(int i=0;i<26;i++){//遍历预测分析表

for(int j=0;j<128;j++){

if(pPredictTable[i][j]!=NULL){

Item*pItem=pPredictTable[i][j];

if(pItem->pItemNext!=NULL){//如果预测分析表某一位置包含两个文法产生式可以抵达

//则不是LL(1)文法

isLL1=false;

break;

}

}

}

}

return isLL1;

}

程序运行截图

编译原理大作业完整代码 + 报告_i++_06