编译原理实验--利用LL(1)文法设计一个简单的c++语言编译器

词法分析

符号表

符号符种符号符种
int1void2
float3long4
double5iostream6
string7algorithm8
map9stack10
using11namespace12
std13if14
else15while16
for17return18
include19cin20
cout21--22
->23-24
标识符NAME#26
(27)28
[29]30
{31}32
;33*34
:35,36
%37^38
+39?40
=41|42
&43!44
<45>46
>=47==48
>>49!=50
<<51&&52
<=53||54
++55?=56
-57--58
->5960
数字NUM

状态转换图

在这里插入图片描述

程序

//该程序的输入是一个名字未 “测试程序.cpp” 的文件
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <string>
#include <map>
#include <bitset>

using namespace std;

int transform(char *p , int n);
bool exists(char *p);
void ci_fa_fen_xi();
int guanjz( char ch1[] );

map<string,int> NAME;
int location = 0;

int main(){
	ci_fa_fen_xi(); 
}


int transform(char *p , int n)
{
	int rnum = 0;
	for(int i=0 ; i<n ; i++)
	{
		rnum = rnum*10 + (p[i]-'0');
	}
	return rnum;
}

bool exists(char *p)
{
	map<string,int>::iterator iter;
	iter = NAME.begin();
	while(iter!=NAME.end())
	{
//		printf("%s,",iter->first.data());
		if(strcmp(p,iter->first.data())==0){
			return true;
		}
		iter++;
	}
//	printf("\n");
	return false;
}
 
void ci_fa_fen_xi(){
	FILE	*fp, *fp1,*fp2;
	int	hanjsq = 1;                             /* 行计数器,保存行号 */
	int	guanjz( char ch1[] );                   /* 声明函数 */
	char	ch, infile[15], outfile[15];            /* 定义输入和输出文件名 */
	if ( (fp = fopen( "测试程序.cpp", "r" ) ) == NULL )     /* 打开需要扫描的文件 */
	{
		printf( "cannot open file\n" );
		exit( 0 );
	}
	if ( (fp1 = fopen( "词法分析输出.txt", "w" ) ) == NULL )   /* 打开需要存入的文件 */
	{
		printf( "cannot open file\n" );
		exit( 0 );
	}
	if ( (fp2 = fopen( "中间文件.txt", "w" ) ) == NULL )   /* 打开需要存入的文件 */
	{
		printf( "cannot open file\n" );
		exit( 0 );
	}
	while ( !feof( fp ) )
	{
		ch = fgetc( fp );                       /* 读一个字符 */
		if ( ch == 10 )
			hanjsq++;                       /* 换行,ASCII码10 */
		/**********************扫描头文件单词及保留字***********************/
		if ( isalpha( ch ) || ch == '_' )       /* 如果第一个字符为字母或下划线则判断为标识符 */
		/*
		 * 表示符分为 普通标识符,关键字
		 * isalpha是自带的库函数
		 */
		{
			
			int	i = 0;
			char	ch1[30];                /* 假定每个标识符最长为30 */
			ch1[i++] = ch;                  /* 将ch保存到ch1[0]中并使i自加1 */
			while ( !feof( fp ) )           /* 继续往下读 */
			{
				ch = fgetc( fp );       /* 读一个字符 */
				if ( ch == 10 )
					hanjsq++;       /* 如果ch为换行符,则行计数器自加1 */
				if ( isalpha( ch ) || isdigit( ch ) || ch == '_' )
				{
					/* 如果ch为字母、数字或下划线就把ch放到ch1[i]中并使i自加1 */
					ch1[i++] = ch;
				}
				if ( !isalpha( ch ) && !isdigit( ch ) && ch != '_' && ch != '.' )
				{                       /* 如果ch不为字母、数字、下划线和点时判断其为标识符 */
					ch1[i] = '\0';
					if(guanjz( ch1 )==25)
					{
						
						if(!exists(ch1)){
							NAME[ch1] = location;
							location++;
						}	
						printf("<%d,NAME[%d]>\n",25,NAME[ch1]);
						fprintf(fp1,"<%d,NAME[%d]>\n",25,NAME[ch1]);
						fprintf(fp2,"NAME[%d]%s\n",NAME[ch1],ch1);
					}
					else
					{
						printf("<%d,>\n",guanjz( ch1 ));
						fprintf(fp1,"<%d,>\n",guanjz( ch1 ));
						fprintf(fp2,"%s\n",ch1);
					}
					break;  //打破循环且不用回退 
				}
			}
		}
		/************************扫描数字*************************/
		if(isdigit(ch) || ch=='-')//如果ch为数字或'-',负数 
		{
			if(isdigit(ch))//如果ch为数字
			{
				int cur = 0;
				char num[30];
				num[cur++] = ch;
				while(!feof(fp)) //继续读 
				{
					ch=fgetc(fp);//如果ch为数字则循环输出
					if(isdigit(ch))
					{
						num[cur++] = ch;
						fprintf(fp1,"%c",ch);
					}
					else//否则视为数字结束
					{
						int value = transform(num,cur);
						bitset<8> b(value);
						printf("<%d,%s>\n",value,b.to_string().data());
						fprintf(fp1,"<%d,%s>\n",value,b.to_string().data());
						fprintf(fp2,"NUM\n");
						fseek(fp,-1,1);//回退一位
						ch='0';//置ch为0,以免影响下面误判并顺利退出扫描数字
						break;
					} 
				} 
			}
			if(ch=='-')//如果ch为'-', 有可能为负数,也可能是减法 ,还可能是自减 , 还可能是 -> 
			{
				ch=fgetc(fp); //接着读
				if(ch=='-')//如果ch还是为'-'则判断为自减符'--'
				{
					printf("<%d,>\n",22);
					fprintf(fp1,"<%d,>\n",22);
					fprintf(fp2,"--\n");
				}
				if(ch=='>')//如果ch为'>',则判断为结构体运算符'->'
				{
					printf("<%d>\n",23);
					fprintf(fp1,"<%d>\n",23);
					fprintf(fp2,"->\n");
				}
				if(isdigit(ch))//如果ch为数字则可能为减号或负号
				{
					fseek(fp,-3,1);//回退3为判断
					ch=fgetc(fp);
					if(isdigit(ch))//如果ch为数字则判断'-'为减号
					{
						ch=fgetc(fp);
						printf("<%d>\n",24);
						fprintf(fp1,"<%d>\n",24);
						fprintf(fp2,"-\n");
					}
					else //否则判断'-'为负号
					{
						int cur = 0;
						char num[30];
						ch=fgetc(fp);
						num[cur++] = ch;
						while(!feof(fp))
						{
							ch=fgetc(fp);//预读一位如果ch为数字则循环输出
							if(isdigit(ch) || ch=='.')
							{
								num[cur++] = ch;
							}
							else//否则视为数字结束
							{
								int value = transform(num,cur);
								value = 0-value;
								bitset<8> b(value);
								printf("<%d,%s>\n",value,b.to_string().data());
								fprintf(fp1,"<%d,%s>\n",value,b.to_string().data());
								fprintf(fp2,"NUM\n");
								fseek(fp,-1,1);//回退1
								break;
							}					
						}
					}
				}
			} 
		} 
		
		/*********************扫描其他符号********************/
		if(!isdigit(ch) && !isalpha(ch) && ch!='_' && ch!='"' && ch!='/')
		{
			char ch2[14]={"#()[]{};*:,%^"};//定义部分单符号集
			char ch3[9]={"+?=|&!<>"};//定义部分单符号或双符号(前半部分)集
			char ch4[9]={"+==|&==="};//定义部分双符号(后半部分)
			for(int i=0;i<13;i++)
			{//判断单个符号
				if(ch==ch2[i])
				{
					printf("<%d,>\n",i+26);
					fprintf(fp1,"<%d,>\n",i+26);
					fprintf(fp2,"%c\n",ch);
				}
			}
			for(int j=0;j<8;j++)
			{//判断双符号
				if(ch==ch3[j])
				{//如果ch与ch3中第j个字符匹配
					ch=fgetc(fp);//预读一位
					if(ch==ch4[j])
					{//且ch与ch4第j个匹配,则表示ch3[j]与ch4[j]连起来为一个双符号
						printf("<%d,>\n",j+47);
						fprintf(fp1,"<%d,>\n",j+47);
						fprintf(fp2,"%c%c\n",ch3[j],ch4[j]);
						continue;
					}
					if(ch=='<' && ch3[j]=='<')//判断'<<'符
					{
						printf("<%d,>\n",51);
						fprintf(fp1,"<%d,>\n",51);
						fprintf(fp2,"<<\n");
						continue;
					}
					if(ch=='>' && ch3[j]=='>')//判断'>>'符
					{
						printf("<%d,>\n",49);
						fprintf(fp1,"<%d,>\n",49);
						fprintf(fp2,">>\n");
						continue;
					}
					else//否则表示ch3[j]为单符号,不是双符号的一部分
					{
						printf("<%d,>\n",j+39);
						fprintf(fp1,"<%d,>\n",j+39);
						fprintf(fp2,"%c\n",ch3[j]);
						fseek(fp,-1,1);
					}
				}
			}
		}
		
	}
	fprintf(fp2,"#");
	fclose(fp);
	fclose(fp1);
	fclose(fp2);
}


int guanjz( char ch1[] )                                        /* 关键字和标识符判断 */
{
	char ch2[21][10] = { "int",    "void",	  "float", "long",	"double", "iostream", "string", "algorithm",
			         "map","stack",	  "using", "namespace", "std",	  "if",	      "else",	"while",    "for",
			     "return", "include", "cin","cout" }; /* 定义关键字集 */
	for ( int i = 0; i < 21; i++ )
	{                                                       /* 逐个比对如果为关键字则返回类别i+1 */
		if ( !strcmp( ch1, ch2[i] ) )
			return(i + 1);
	}
	return(25);                                             /* 否则返回一般标识符类 */
}


输出结果

在这里插入图片描述

语法分析

c++语言对应的LL(1)文法如下:

1、<>表示非终结符

2、为了不与非终结符的表示冲突:

  • 大于符号转义为 ~
  • 小于符号转义为@

3、$表示空

<程序>-><头文件列表> <定义列表>
<头文件列表>-># include  <文件名>  <文件名结尾>
<文件名>->iostream|string|algorithm
<文件名结尾>-><头文件列表>|using namespace std ;|$
<定义列表>-><定义> <定义列表>|$
<定义>-><数据类型> NAME <名字类型>
<数据类型>->int|void|float|double
<名字类型>-><变量类型>|<函数类型>
<变量类型>->;|[ NUM ] ;|= NUM ;
<函数类型>->( <形参列表> ) <代码块>
<形参列表>-><参数语句>|$
<参数语句>-><参数> <参数结尾>
<参数>-><数据类型> NAME <数组类型>
<数组类型>->[ ]|$
<参数结尾>->, <参数> <参数结尾>|$
<代码块>->{ <局部变量定义列表> <代码列表> }
<局部变量定义列表>-><局部变量定义> <局部变量定义列表>|$
<局部变量定义>-><数据类型> NAME <变量类型>
<代码列表>-><代码> <代码列表>|; <代码列表>|$
<代码>-><普通语句>|<条件语句>|<迭代语句>|<返回语句>|<循环语句>|<输出语句>|<输入语句>|<局部变量定义列表>
<普通语句>->NAME <普通语句结尾>
<普通语句结尾>-><表达式> ;|= <表达式> ;|<变量结尾> = <表达式> ;|<调用函数结尾> ;|<比较运算符> <表达式> ;
<变量结尾>->[ <表达式> ]|<单目运算符>|$
<表达式>-><加法表达式> <表达式结尾>|<单目运算符> <表达式结尾>
<单目运算符>->++|--|+=|-=
<表达式结尾>-><比较运算符> <加法表达式>|$
<比较运算符>->@=|@|~|~=|==|!=|=|++
<加法表达式>-><> <加法表达式结尾>
<加法表达式结尾>-><加法运算符> <> <加法表达式结尾>|$
<加法运算符>->+|-
<>-><因子> <项结尾>
<项结尾>-><乘法运算符> <因子> <项结尾>|$
<乘法运算符>->*|/
<因子>->( <表达式> )|NAME <因子结尾>|NUM
<因子结尾>-><变量结尾>|( <因子参数> )
<因子结尾>-><变量结尾>|( <因子参数> )
<因子参数>-><因子参数列表>|$
<因子参数列表>-><表达式> <因子参数列表结尾>
<因子参数列表结尾>->, <表达式> <因子参数列表结尾>|$
<调用函数结尾>->( <调用参数> )
<调用参数>-><调用参数列表>|$
<调用参数列表>-><表达式> <调用参数结尾>
<调用参数结尾>->, <表达式> <调用函数结尾>|$
<条件语句>->if ( <表达式> ) { <代码列表> } <条件语句结尾>
<条件语句结尾>->else { <代码列表> }|$
<迭代语句>->while ( <表达式> ) <迭代语句结尾>
<迭代语句结尾>->{ <代码列表> }|<代码>
<循环语句>->for ( <表达式> ; <表达式> ; <表达式> ) <循环语句结尾>
<循环语句结尾>->{ <代码列表> }|<代码>
<输入语句>->cin <输入语句结尾> ;
<输入语句结尾>->~~ <表达式> <输入语句结尾>|$
<输出语句>->cout <输出语句结尾> ;
<输出语句结尾>->@@ <表达式> <输出语句结尾>|$
<返回语句>->return <返回语句结尾>
<返回语句结尾>->;|<表达式> ;

设计程序求文法的预测分析表:

求预测分析表是独立在文法外完成的,应为python语言处理字符串比较简单,故笔者采用的python求预测分析表

求FIRST集

算法描述:
1.若X->a…,则将终结符a放入First(X)中
2.若X->ε,则将ε放入First(X)中
3.若有X->Y1Y2Y3…Yk,则
(1)把First(Y1)去掉 ε后加入First(X)
(2)如果First(Y1)包含ε,则把First(Y2)也加入到First(X)中,以此类推,直到某一个非终结符Yn的First集中不包含ε
(3)如果First(Yk)中包含ε,即Y1~Yn都有ε产生式,就把ε加入到First(X)中
4.重复上述过程,直到first集合中的元素不在更新为止
程序:
文件名 “getFirst.py”

def first_once(wenfa, first):
    # 遍历文法, 文法每一行是一个产生式
    for line in wenfa:
        null_flag = False
        # "->" 将产生式分成左右两个部分
        produce = line.split('->')
        # LL(1)文法左部一定是非终结符,且LL(1)文法左部的非终结符求并集是整个
        # 文法的非终结符,expressions[0]即表示取出产生式左部
        head = produce[0][1:len(produce[0]) - 1]
        # 如果head还没有出现过
        if head not in first.keys():
            # 定义一个first[head]
            first[head] = []

        # 左部处理完后,处理右部,右部是以 "|" 为分割的各部分 候选式
        expressions = produce[1].split('|')

        # 遍历候选式
        for expression in expressions:
            # 对于每一个候选式,每个符号(非终结符和终结符)相互之间都是用空格隔开的
            items = expression.split(' ')
            # items[0][0]表示候选式的第一个符号的第一个字符
            if items[0][0] != '<':  # 说明items[0]一定是一个终结符
                if items[0] not in first[head]:
                    # 根据求first的算法,候选式第一个为终结符,直接将其加入 first集合
                    first[head].append(items[0])
            else:  # 若items[0]是一个非终结符则
                # 遍历 items这个候选式
                for i in range(0, len(items)):
                    # 取出<>里面的符号
                    NT = items[i][1:len(items[i]) - 1]
                    # 如过存在这个非终结符
                    if NT in first.keys():
                        # 将这个非终结符的first集的符号存入 first[head]
                        for element in first[NT]:
                            if element == "$":  # 如果first[NT]中有空,单独处理
                                null_flag = True
                                if (i == len(items) - 1):  # 判断这个非终结符是不是最后一个
                                    # 若是最后一个,且first[head]里无空,则,将空存入
                                    if "$" not in first[head]:
                                        first[head].append("$")
                                continue
                            if element not in first[head]:  # 其他情况正常存入
                                first[head].append(element)
                        if null_flag:
                            null_flag = False
                            continue
                        else:
                            break
    # 没遍历一遍文法,就返回当前 first集合所有字符的总长度
    # 作为判断循环跳出的条件
    sum_len = 0
    for key in first:
        sum_len += len(first[key])
    return sum_len, first


# 求解first集合
def get_first(wenfa, first):
    # first_once只能遍历一次文法,要想求得正确的结果必须
    # 多次遍历文法
    pre_len = 0
    while True:
        now_len, first = first_once(wenfa, first)
        if pre_len == now_len:  # 如果长度不在更新,即跳出循环
            break
        pre_len = now_len
    return first


# 获取文法
def create_wenfa():
    wenfa = []  # 定义一个list,存储需要分析的文法
    # 获取文法
    n = int(input("请输入文法行数: "))
    for i in range(n):
        wenfa.append(input())
    return wenfa


# 初始化first集合
def init_first():
    first = {}  # 定义一个字典,存储每个非终结符对应first集合
    return first


# 输出first集合
def output_first(first):
    # 输出first集合
    print("*" * 100)
    i = 0
    for key in first:
        print("first{", key, "}:", first[key])
    print("*" * 100)


if __name__ == '__main__':
    wenfa = create_wenfa()
    first = init_first()

    # 生成first集合
    first = get_first(wenfa, first)

    # 输出first集合
    output_first(first)


测试书上第四章,4-2的文法:

<E>-><T> <E`>
<E`>->+ <T> <E`>|$
<T>-><F> <T`>
<T`>->* <F> <T`>|$
<F>->( <E> )|i

输出:
在这里插入图片描述
测试结果正确
在这里插入图片描述

求FLLOW集

算法描述:
1.对于文法开始符号S,把$加入到Follow(S)中
2.若有A->aBC,则将First(C)中除了ε之外的元素加入到Follow(B)(此处a可以为空)
3.若有A->aB或者A->aBC且ε属于first(C),则将Follow(A)加入到follow(B)中(此处a可以为空)
4.若有A->Bc,则直接将c加入follow(B)中
follow集中不含有空串ε
程序:

# 求follow集合需要借助first,故将其引入
import getFirst


def init_follow():
    follow = {}  # 定义一个字典,存储每个非终结符对应follow集合
    return follow


def follow_once(wenfa, first, follow):
    # 遍历文法, 文法每一行是一个产生式
    for line in wenfa:
        # "->" 将产生式分成左右两个部分
        produce = line.split('->')
        # LL(1)文法左部一定是非终结符,且LL(1)文法左部的非终结符求并集是整个
        # 文法的非终结符,expressions[0]即表示取出产生式左部
        head = produce[0][1:len(produce[0]) - 1]
        if head not in follow.keys():
            # 定义一个first[head]
            follow[head] = []
            # 如果这个head是整个文法的开始符号,就要放入一个”#“号
            # 取出整个文法的开始符号 start
            start = wenfa[0].split("->")[0]
            start = start[1:len(start) - 1]
            if (head == start):
                follow[head].append("#")
        # 左部处理完后,处理右部,右部是以 "|" 为分割的各部分 候选式
        expressions = produce[1].split('|')
        # 遍历候选式
        for expression in expressions:
            # 对于每一个候选式,每个符号(非终结符和终结符)相互之间都是用空格隔开的
            items = expression.split(' ')
            # 对于一个候选式,获取候选式的中符号的个数
            k = len(items)
            # 遍历一个候选式的符号,除了最后一个符号,最后一个符合单独讨论
            for i in range(k - 1):
                if (items[i][0] == "<"):  # 如果当前符号为非终结符
                    NT = items[i][1:len(items[i]) - 1]
                    if (items[i + 1][0] != "<"):  # 查看下一个符号,如果下一个符号为终极符
                        if NT not in follow.keys(): #如果NT不存在,就创建一个
                            follow[NT] = []
                        # 则将终极符存入前面那个非终结符的follow集合中
                        if items[i + 1] not in follow[NT] and items[i + 1] != "$":
                            follow[NT].append(items[i + 1])
                    else:  # 如果下一个符号为非终极符
                        # 到这里说明是两个非终结符连着的情况
                        # 把后面那个非终结符的first集合加入到前面那个非终结符的follow集合中,除了空之外。
                        NEXT = items[i + 1][1:len(items[i + 1]) - 1]
                        for element in first[NEXT]:
                            if NT not in follow.keys():  # 如果NT不存在,就创建一个
                                follow[NT] = []
                            if element not in follow[NT] and element != "$":
                                follow[NT].append(element)
                        if "$" in first[NEXT]:  # 如果后面那个终极符有空
                            # 把后面那个非终结符的follow集合加入到前面那个非终结符的follow集合中
                            if NEXT not in follow.keys():  # 如果NEXT不存在,就创建一个
                                follow[NEXT] = []
                            for element in follow[NEXT]:
                                if element not in follow[NT] and element != "$":
                                    follow[NT].append(element)
            # 最后一个符号单独讨论
            if (items[k - 1][0] == "<"):  # 若为非终结符,则将head的follow放到最后一个符号的follow中
                # 取出中一个符号 放入 NT
                NT = items[k - 1][1:len(items[k - 1]) - 1]
                for element in follow[head]:
                    if element not in follow[NT] and element != "$":
                        follow[NT].append(element)
    sum_len = 0
    for key in first:
        sum_len += len(follow[key])
    return sum_len,follow


# 输出follow集合
def output_follow(follow):
    # 输出follow集合
    print("*" * 100)
    i = 0
    for key in follow:
        print("first{", key, "}:", follow[key])
    print("*" * 100)


def get_follow(wenfa,first,follow):
    #与求first一样
    # follow_once只能遍历一次文法,要想求得正确的结果必须
    # 多次遍历文法
    pre_len = 0
    while True:
        now_len, follow = follow_once(wenfa,first,follow)
        if pre_len == now_len:  # 如果长度不在更新,即跳出循环
            break
        pre_len = now_len
    return follow



if __name__ == '__main__':
    # 借助getFirst文件,得到文法和文法的first集合
    wenfa = getFirst.create_wenfa()
    first = getFirst.init_first()
    first = getFirst.get_first(wenfa, first)

    # 初始化follow集合
    follow = init_follow()

    #求解follow集合
    follow = get_follow(wenfa,first,follow)

    #打印follow集合
    output_follow(follow)


输出:
在这里插入图片描述

测试结果正确
在这里插入图片描述

求预测分析表

算法描述:

假设要用终结符 A A A 进行匹配,面临的输入符号为 a a a ,且 A A A 的所有产生式为:

A → α 1 ∣ α 2 ∣ α 3 ⋯ ∣ α n A\rightarrow \alpha_1|\alpha_2|\alpha_3\cdots|\alpha_n Aα1α2α3αn

  1. a ∈ F I R S T ( α i ) a \in FIRST(\alpha_i) aFIRST(αi) , 则在预测分析表中 [ A , a ] [A,a] [A,a] 处填上产生式 α \alpha α
  2. a a a 不属于任何候选式首符号集,则:
    (一).若 ϵ \epsilon ϵ 属于某个 F I R S T ( α i ) FIRST(\alpha_i) FIRST(αi),且 a ∈ F O L L O W ( A ) a \in FOLLOW(A) aFOLLOW(A) ,则在预测分析表中 [ A , a ] [A,a] [A,a] 处填上空跳
    (二).否则,什么都不填,当语法分析器运行到什么都没有的空格时,就产生报错

程序:

import getFirst  # 引入求first集合的文件
import getFollow  # 引入求follow集合的文件
import xlwt  # 引入处理excel表格头文件


# 函数,将字符串中的<,>去掉
def replace_str(string):
    rstring = ""
    for i in string:
        if (i != "<" and i != ">"):
            if i == "@":
                i = "<"
            elif i == "~":
                i = ">"
            rstring += i
    return rstring


# 函数,获取文法中所有的终结符和非终结符
def get_char_of_wenfa(wenfa, first):
    zhong_jie_fu = []  # 创建一个列表,存放整个文法中所有终极符
    fei_zhong_jie_fu = []  # 创建一个列表,存放整个文法中所有非终极符

    # 非终结符好求,直接把first集合的keys遍历一遍放入list
    for t in first.keys():
        fei_zhong_jie_fu.append(t)

    # 求文法中所有终结符,遍历文法
    for line in wenfa:
        # "->" 将产生式分成左右两个部分
        produce = line.split('->')
        # 只处理产生式右部
        # 先把产生式右部所有的<,>替换掉,因为要提取里面的符号
        right = replace_str(produce[1])
        # 对产生式右部进行处理,右部以是|分割的
        expressions = right.split('|')

        # 遍历产生式右部,单独处理每个候选式
        for expression in expressions:
            # 对于一个候选式,符号以空格分割
            items = expression.split(' ')
            # 遍历符号
            for item in items:
                # 判断是不是非终结符
                if item not in fei_zhong_jie_fu:
                    # 则将其加入终结符列表
                    zhong_jie_fu.append(item)
    return zhong_jie_fu, fei_zhong_jie_fu


# 选取候选式函数
def get_expression(expressions, t, first):
    # 遍历产生式的的所有候选式
    for expression in expressions:
        # 如果该候选式的第一个符号与要匹配的符号一致
        # 则返回该候选式
        items = expression.split(" ")
        if items[0] == t:
            return expression
        elif items[0] in first.keys():
            # 如果 t 在该候选式的first集合,即使t与首个符号不同
            # 也可以将其返回
            if t in first[items[0]]:
                return expression


def get_predict_table(wenfa, first, follow):
    table = {}  # 创建一个字典,存放预测分析表
    # 遍历文法
    for line in wenfa:
        # "->" 将产生式分成左右两个部分
        produce = line.split('->')
        # 提取产生式左部的符号
        head = produce[0][1:len(produce[0]) - 1]

        # 处理产生式右部
        expressions = replace_str(produce[1]).split('|')
        # 遍历head的first集合
        for t in first[head]:
            if t == "$":
                continue
            # 从若干个候选式中选出符号要求的
            expression = get_expression(expressions, t, first)
            # 预测分析表生成一条记录
            table[(head, t)] = expression
        # 遍历head的follow集
        for t in follow[head]:
            # 如果head的first集合里面有空才处理follow
            if "$" in first[head]:
                if (head, t) not in table.keys():
                    table[(head, t)] = "$"
    return table


def generate_table(table, wenfa, first):
    # 获取文法中所有终结符和非终结符
    zhong_jie_fu, fei_zhong_jie_fu = get_char_of_wenfa(wenfa, first)

    # 处理excel

    # 工作空间
    workbook = xlwt.Workbook(encoding="utf-8")

    # 工作表
    worksheet = workbook.add_sheet("sheet 1")

    current_line = 0  # 记录当前行

    for i in range(len(zhong_jie_fu)):
        worksheet.write(current_line, i + 1, zhong_jie_fu[i])
    current_line += 1

    for head in fei_zhong_jie_fu:
        worksheet.write(current_line, 0, head)
        for i in range(len(zhong_jie_fu)):
            key = (head, zhong_jie_fu[i])
            if key in table.keys():
                worksheet.write(current_line, i + 1, table[key])
        current_line += 1

    workbook.save("预测分析表.xls")


if __name__ == '__main__':
    # 初始化
    wenfa = getFirst.create_wenfa()  # 创建文法
    first = getFirst.init_first()  # 初始化first集合
    follow = getFollow.init_follow()  # 初始化follow集合

    # 得到first和follow
    first = getFirst.get_first(wenfa, first)  # 求first集合
    follow = getFollow.get_follow(wenfa, first, follow)  # 求follow集合

    # 得到预测分析表
    table = get_predict_table(wenfa, first, follow)

    # 生成预测分析表
    generate_table(table, wenfa, first)

输出结果

在这里插入图片描述

测试结果正确
在这里插入图片描述

求任意C++ll对应的(1)文法的预测分析表

将上面的文法输入到程序即可,有图可见,预测分析表还是比较大的,这里只截取了部分,详情见附录
在这里插入图片描述
为了后续好导入,保留一份txt文件版本
在这里插入图片描述
注意要另存为ANSI编码格式,不然c++处理会乱码

在这里插入图片描述

语法分析器实现

现在有了预测分析表,语法分析就很好实现了,就是一个栈不断push和pop的过程,如果发现预测分析表上没有对应的值,就报错,这里直接上程序:

#include "词法分析.cpp"
#include <iostream>
#include <map>
#include <string>
#include <fstream>
#include <stack>
using namespace std;

void split(string str,char c,string result[]); //分割字符串
void split(string str,char c,string result[],int &len);//重载 分割字符串
void get_fenfa();//从预测分析表获取文法带 wenfa 
string slice(string str,int start,int end);//切片函数 
bool is_guan_ian_zi(string t);
bool exits(string t);

#define size 50

map <pair<string,string>,string> wenfa;

string define_NAME[size];
int NAME_len = 0;

int main(){
	ci_fa_fen_xi();
	get_fenfa();
	fstream file("中间文件.txt");
	stack<string> s;
	s.push("#");
	s.push("程序");
	string t;
	getline(file,t);
	string pre_t = t; 
	while(1){
		string top = s.top();
		s.pop();
		if(top==t){
			if(t=="#"&&s.empty()){
				cout<<endl;
				cout<<"***************************************************"<<endl;
				cout<<"语法分析结束,程序语法正确!"<<endl;
				cout<<"***************************************************"<<endl;
				break;
			}
			getline(file,t);
			if(slice(t,0,4)=="NAME"){
				if(is_guan_ian_zi(pre_t)){
					define_NAME[NAME_len++] = t;
					t = slice(t,0,4);
				}else{
					if(exits(t)){
						t = slice(t,0,4);
					}else{
						cout<<endl;
						cout<<"***************************************************"<<endl;
						cout<<"语法错误!"<<slice(t,7,t.length())<<" "<<"未定义!"<<endl;
						cout<<"***************************************************"<<endl;
						return 0; 
					}
				}
			}
			pre_t = t;
			continue;
		}
		pair<string,string> key(top,t);
		if(wenfa[key].length()!=0){
			cout<<top<<" -> "<<wenfa[key]<<endl;
			if(wenfa[key]=="$"){
				continue;
			}
			string ready_to_push[size];
			int len = 0; 
			split(wenfa[key],' ',ready_to_push,len);
			for(int i = len-1 ; i>=0 ; i--){
				s.push(ready_to_push[i]);
			}
		}else{
			cout<<endl;
			cout<<"***************************************************"<<endl;
			cout<<top<<","<<t<<endl; 
			cout<<"语法错误!"<<endl;
			cout<<"***************************************************"<<endl;
			break;
		}	 
	}
	return 0;
} 

void split(string str,char c,string result[]){
	string temp = "";
	int len = 0;
	for(auto i : str){
		if(i!=c){
			temp += i;
		}else{
			result[len++] = temp;
			temp = "";
		}	
	}
	result[len++] = temp;
}

void get_fenfa(){
	ifstream file("预测分析表.txt");
	string line;
	string wenfa_line[size];
	while(getline(file,line)){
		split(line,'|',wenfa_line);
		pair<string,string> key(wenfa_line[0],wenfa_line[1]);
		wenfa[key] = wenfa_line[2];
	}
}

bool is_guan_ian_zi(string t){
	string guanjianzi[5] = {"int","float","double","char","bool"};
	for(auto i : guanjianzi){
		if(i==t){
			return true;
		}
	}
	return false;
}


void split(string str,char c,string result[],int &len){
	string temp = "";
	len = 0;
	for(auto i : str){
		if(i!=c){
			temp += i;
		}else{
			result[len++] = temp;
			temp = "";
		}	
	}
	result[len++] = temp;
}

string slice(string str,int start,int end){
	string rstr;
	for(int i=start ; i<end&&i<str.length(); i++){
		rstr += str[i];
	}
	return rstr;
}

bool exits(string t){
	for(auto i:define_NAME){
		if(i==t){
			return true;
		}
	}
	return false;
}

输出结果:
这是我的测试程序

#include<iostream>

using namespace std;

int main(){
	a;
	if(a<1){
		a = a+1;
	}
	int i = 1;
	for(i=0 ; i<10 ; i++){
		cout<<a+i;
	}
	int b;
	return 0;
}

在这里插入图片描述

附录

实验报告和代码下载地址

  • 3
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值