利用LL1文法设计一个简单的c++语言编译器
词法分析
符号表
符号 | 符种 | 符号 | 符种 |
int | 1 | void | 2 |
float | 3 | long | 4 |
double | 5 | iostream | 6 |
string | 7 | algorithm | 8 |
map | 9 | stack | 10 |
using | 11 | namespace | 12 |
std | 13 | if | 14 |
else | 15 | while | 16 |
for | 17 | return | 18 |
include | 19 | cin | 20 |
cout | 21 | -- | 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 |
-> | 59 | “ | 60 |
数字 | 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
- 若 a ∈ F I R S T ( α i ) a \in FIRST(\alpha_i) a∈FIRST(αi) , 则在预测分析表中 [ A , a ] [A,a] [A,a] 处填上产生式 α \alpha α
- 若
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) a∈FOLLOW(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;
}