解释器 词法分析器
Posted olive_gyr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了解释器 词法分析器相关的知识,希望对你有一定的参考价值。
辣鸡的我终于在一个已经保研的小哥哥(萌似泰迪)的帮助下完成了解释器!!(VS2013)
分为3步:词法分析器、语法分析器、语义分析器
代码大部分出自《编译原理基础-习题与上机解答》(西安电子科技大学出版社)中的附录
下面会上所有代码附带(超级)大量详细注释和理解,以及很多处理细节。因为在这些在高手看来顺理成章的过程才是新手很大的障碍。
step 1
安装Virsual Stidio 2013
经过我的实践和另一个小哥哥的经验:windos7只能安装vs2013版的,否则就会出现下面这种2015版装了3个小时进度条还没前进的情况
不要装在C盘,这样如果系统崩了,C盘会全丢失,而且放进C盘电脑会运行变慢。
step 2
上代码之前,先来说说词法分析器。
“sacanner”是词法分析器,输入流是序列,输出流是“字典”。
我们需要
1、设计记号:词法分析器读取一个序列并根据构词规则把序列转化为记号流
2、定义一个字典:把所有符合一个模式的保留字、常量名、参数名、函数名等放进字典。字典是个数组,其元素的类型和记号的类型相同
3、设计程序的结构,具体见下面的代码
step3(重头戏)
新建一个词法分析器的项目
把已经编写好的代码扔进去:scanner.h scanner.c scannermain.c
1 //-----------------------scanner.h-------------------- 2 #pragma once 3 //#ifndef SCANNER_H 4 //#define SCANNER_H 5 #define _CRT_SECURE_NO_WARNINGS 6 7 #include<stdio.h> 8 #include<string.h> 9 #include<stdlib.h> 10 #include<ctype.h> 11 #include<stdarg.h> 12 #include<math.h> 13 14 enum Token_Type//枚举记号的类别 15 { 16 ORIGIN,SCALE,ROT,IS,TO,STEP,DRAW,FOR,FROM,//保留字 17 T,//参数 18 SEMICO,L_BRACKET, R_BRACKET,COMMA,//分隔符 19 PLUS,MINUS,MUL,DIV,POWER,//运算符 20 FUNC,//函数 21 CONST_ID,//常数 22 NONTOKEN,//空记号 23 ERRTOKEN//出错记号 24 }; 25 26 typedef double(*MathFuncPtr) (double); 27 28 struct Token//记号的数据结构 记号由类别和属性组成 29 { 30 Token_Type type;//记号的类别 31 char *lexeme;//属性,原始输入的字符串 是个字符指针,当需要记号的字符串时,就会引用这个指针,但是字符串保留在TokenBuffer中,所以要指向TokenBuffer 32 double value; //为常数设置,是常数的值 33 double(*FuncPtr)(double); //为函数设置,是函数的指针 34 }; 35 //正规式个数越少越利于程序编写,所以把相同模式的记号共用一个正规式描述,要设计出一个预定义的符号表(就是一个数组),进行区分~ 36 static Token TokenTab[]=//符号表(字典):数组元素的类型于记号的类型相同 37 {//当识别出一个ID时,通过此表来确认具体是哪个记号 38 { CONST_ID, "PI", 3.1415926, NULL }, 39 { CONST_ID, "E", 2.71828, NULL }, 40 { T, "T", 0.0, NULL }, 41 { FUNC, "SIN", 0.0, sin }, 42 { FUNC, "COS", 0.0, cos }, 43 { FUNC, "TAN", 0.0, tan }, 44 { FUNC, "LN", 0.0, log }, 45 { FUNC, "EXP", 0.0, exp }, 46 { FUNC, "SQRT", 0.0, sqrt }, 47 { ORIGIN, "ORIGIN", 0.0, NULL }, 48 { SCALE, "SCALE", 0.0, NULL }, 49 { ROT, "ROT", 0.0, NULL }, 50 { IS, "IS", 0.0, NULL }, 51 { FOR, "FOR", 0.0, NULL }, 52 { FROM, "FROM", 0.0, NULL }, 53 { TO, "TO", 0.0, NULL }, 54 { STEP, "STEP", 0.0, NULL }, 55 { DRAW, "DRAW" , 0.0, NULL } 56 }; 57 58 extern unsigned int LineNo; //跟踪记好所在源文件行号 59 extern int InitScanner(const char*); //初始化词法分析器 60 extern Token GetToken(void); //获取记号函数 61 extern void CloseScanner(void); //关闭词法分析器 62 63 //#endif
1 #include"scanner.h" 2 #ifndef MSCANNER_H 3 #define MSCANNER_H 4 #define TOKEN_LEN 100//设置一个字符缓冲区,这是他的大小用来保留记号的字符串 5 unsigned int LineNo;//记录字符所在行的行号-》词法分析器对每个记号的字符串进行分析时必须记住该字符串在源程序的位置 6 static FILE *InFile;//打开绘图语言源程序时,指向该源程序的指针 7 static char TokenBuffer[TOKEN_LEN];//设置一个字符缓冲区,用来保留记号的字符串,当需要记号的字符串时,char*lexeme指针会指向TokenBuffer 8 9 //--------------------初始化词法分析器 10 extern int InitScanner(const char *FileName)//输入要分析的源程序文件名 11 { 12 LineNo = 1; 13 InFile = fopen(FileName, "r"); 14 if (InFile != NULL) 15 return 1; //如果存在,打开文件,并初始化lineNO的值为1,返回true 16 else 17 return 0;//不存在返回0 18 } 19 20 //---------------------关闭词法分析器 21 extern void CloseScanner(void) 22 { 23 if (InFile != NULL) 24 fclose(InFile); 25 } 26 27 //--------------------从输入源程序中读入一个字符 28 static char GetChar(void) 29 { 30 int Char = getc(InFile); 31 return toupper(Char);//输出源程序的一个字符,没有输入 32 } 33 34 //--------------------把预读的字符退回到输入源程序中,分析的过程中需要预读1、2...个字符,预读的字符必须回退,以此保证下次读时不会丢掉字符 35 static void BackChar(char Char)//输入:回退一个字符, 没有输出 36 { 37 if (Char != EOF) 38 ungetc(Char, InFile); 39 } 40 41 //--------------------加入字符到TokenBuffer-----把已经识别的字符加到TokenBuffer 42 static void AddCharTokenString(char Char)//输入源程序的一个字符 没有输出 43 { 44 int TokenLength = strlen(TokenBuffer);//设定好长度 45 if (TokenLength + 1 >= sizeof(TokenBuffer)) 46 return;//此时字符串的长度超过最大值,返回错误 47 TokenBuffer[TokenLength] = Char;//添加一个字符 48 TokenBuffer[TokenLength + 1] = \'\\0\'; 49 } 50 51 //--------------------请空记号缓冲区 52 static void EmptyTokenString() 53 { 54 memset(TokenBuffer, 0, TOKEN_LEN); 55 } 56 57 //--------------------根据识别的字符串在符号表中查找相应的记号 58 static Token JudgeKeyToken(const char *IDString)//输入:识别出的字符串;输出:记号 59 { 60 int loop; 61 for (loop = 0;loop < sizeof(TokenTab) / sizeof(TokenTab[0]);loop++) 62 if (strcmp(TokenTab[loop].lexeme, IDString) == 0) 63 return TokenTab[loop];//查找成功,返回该记号 64 Token errortoken; 65 memset(&errortoken, 0, sizeof(Token)); 66 //void *memset(void *s, int ch, size_t n); 67 // 函数解释:将s中前n个字节替换为ch并返回s; 68 // memset : 作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法。 69 errortoken.type = ERRTOKEN; 70 return errortoken;//查找失败,返回错误记号 71 } 72 73 //--------------------获取一个记号 74 extern Token GetToken(void)//次函数由DFA转化而来。此函数输出一个记号。每调用该函数一次,仅仅获得一个记号。 75 //因此,要获得源程序的所有记号,就要重复调用这个函数。下面声明的函数都被此函数调用过! 76 //输出一个记号,没有输入 77 { 78 Token token; 79 int Char; 80 81 memset(&token, 0, sizeof(Token)); 82 EmptyTokenString();//清空缓冲区 83 token.lexeme = TokenBuffer;//记号的字符指针指向字符缓冲区 84 for (;;) 85 { 86 Char = GetChar();//从源程序中读出一个字符 87 if(Char==EOF) 88 { 89 token.type = NONTOKEN; 90 return token; 91 } 92 if (Char == \'\\n\') 93 LineNo++; 94 if (!isspace(Char)) 95 break; 96 }//end of for 97 AddCharTokenString(Char); 98 //若不是空格、TAB、回车、文件结束符等,则先加入到记号的字符缓冲区中 99 if (isalpha(Char))//判断是英文字母 //若char是A-Za-z,则一定是函数,关键字、PI、E等 100 { 101 for (;;) 102 { 103 Char = GetChar(); 104 if (isalnum(Char)) 105 AddCharTokenString(Char); 106 else 107 break; 108 } 109 BackChar(Char); 110 token = JudgeKeyToken(TokenBuffer); 111 token.lexeme = TokenBuffer; 112 return token; 113 } 114 else if (isdigit(Char))//判断是数字 //若是一个数字,则一定是常量 115 { 116 for (;;) 117 { 118 Char = GetChar(); 119 if (isdigit(Char)) 120 AddCharTokenString(Char); 121 else 122 break; 123 } 124 if (Char == \'.\') 125 { 126 AddCharTokenString(Char); 127 for (;;) 128 { 129 Char = GetChar(); 130 if (isdigit(Char)) 131 AddCharTokenString(Char); 132 else 133 break; 134 } 135 } 136 BackChar(Char); 137 token.type = CONST_ID; 138 token.value = atof(TokenBuffer); 139 return token; 140 } 141 else //不是字母和数字,则一定是运算符或者分隔符 142 { 143 switch (Char) 144 { 145 case \';\':token.type = SEMICO;break; 146 case \'(\':token.type = L_BRACKET;break; 147 case \')\':token.type = R_BRACKET;break; 148 case \',\':token.type = COMMA;break; 149 case \'+\':token.type = PLUS;break; 150 case \'-\': 151 Char = GetChar(); 152 if (Char == \'-\') 153 { 154 while (Char != \'\\n\'&&HUGE != EOF) 155 Char = GetChar(); 156 BackChar(Char); 157 return GetToken(); 158 } 159 else 160 { 161 BackChar(Char); 162 token.type = MINUS; 163 break; 164 } 165 case \'/\': 166 Char = GetChar(); 167 if (Char == \'/\') 168 { 169 while (Char != \'\\n\'&&Char != EOF) 170 Char = GetChar(); 171 BackChar(Char); 172 return GetToken(); 173 } 174 else 175 { 176 BackChar(Char); 177 token.type = DIV; 178 break; 179 } 180 case \'*\': 181 Char = GetChar(); 182 if (Char == \'*\') 183 { 184 token.type = POWER; 185 break; 186 } 187 else 188 { 189 BackChar(Char); 190 token.type = MUL; 191 break; 192 } 193 default:token.type = ERRTOKEN;break; 194 }//end of switch 195 }//end of else 196 return token; 197 }//end of GetToken 198 #endif
1 #include"scanner.h" 2 using namespace std; 3 void main() 4 { 5 Token token; 6 char file[] = "test0.txt"; 7 if (!InitScanner(file)) //初始化词法分析器 8 { 9 printf("Open Sorce File Error !\\n"); 10 return; 11 } 12 printf("记号类别 字符串 常数值 函数指针\\n"); 13 printf("--------------------------------------------\\n"); 14 while (true) 15 { 16 token = GetToken();//输出一个记号 17 if (token.type != NONTOKEN)//记号的类别不是错误,就打印出他的内容 18 printf("%4d,%12s,%12f,%12x\\n", token.type, token.lexeme, token.value, token.FuncPtr); 19 else 20 break; 21 } 22 printf("-------------------------------------------\\n"); 23 getchar(); 24 //.当程序调用getchar时.程序就等着用户按键.........没有这个,黑框会闪一下 25 CloseScanner(); 26 system("pause"); 27 }
有了对词法分析器的分析和详细大量的注解,应该不难看懂。下面说说这个工程的细节。
step 4
1、这是我遇到最多的一个问题,也就是这个问题反复请教小哥哥的。
我们来看看报错
错误1error C4996: \'fopen\': This function or variable may be unsafe. Consider using fopen_s instead. To disable depreca
解决方案:1.项目 ->属性 -> c/c++ -> 预处理器 -> 点击预处理器定义,编辑,加入_CRT_SECURE_NO_WARNINGS
2.在scanner.h中定义_CRT_SECURE_NO_WARNINGS
2、测试程序:通过更改scannermain.cpp中的file字符数组来改变要读取的文件
eg:test0
FOR t FROM 0 TO 2*PI STEP PI/50 DRAW(COS(t),sin(t));
运行结果如下:
----the end----
再次感谢萌萌小哥哥对我的帮助 o(* ̄▽ ̄*)o
以上是关于解释器 词法分析器的主要内容,如果未能解决你的问题,请参考以下文章