要实现一个解释器,主要要经过三个步骤,分别是构造:
1、词法分析器:用于识别一条语句中的关键词是否符合预先定义的规则。
2、语法分析器:用来确定一条语句是否满足语法规则。
3、解释器:用来确定满足语法规则的句子,在意思上是否符合要求。
程序的主要架构是:
1、词法分析器:scanner.h scanner.cpp
2、语法分析器:parser.h parser.cpp
3、解释器:semantic.h semantic.cpp main.cpp
首先介绍一下函数绘图语言:
函数绘图源程序举例:
画出的图形举例:
下面给出词法分析器的源码:
scanner.h:
1 #ifndef SCANNER_H 2 #define SCANNER_H 3 4 #include <string.h> 5 #include <stdio.h> 6 #include <stdlib.h> 7 #include <ctype.h> 8 #include <stdarg.h> 9 #include <math.h> 10 11 enum Token_Type //记号种类 12 { 13 ORIGIN, SCALE, ROT, IS, TO, //保留字 14 STEP, DRAW, FOR, FROM, //保留字 15 T, //参数 16 SEMICO, L_BRACKET, R_BRACKET, COMMA, //分隔符号 17 PLUS, MINUS, MUL, DIV, POWER, //运算符 18 FUNC, //函数 19 CONST_ID, //常数 20 NONTOKEN, //空记号 21 ERRTOKEN //出错记号 22 }; 23 24 typedef double(*MathFuncPtr)(double); 25 26 struct Token //记号与符号表结构 27 { 28 Token_Type type; //记号的类型 29 char *lexeme; //构成记号的字符串 30 double value; //若为常数,则是常数的值 31 double(*FuncPtr)(double); //若为函数,则是函数的指针 32 }; 33 34 static Token TokenTab[] = //符号表内容 35 { 36 { CONST_ID,"PI",3.1415926,NULL }, 37 { CONST_ID,"E",2.71828,NULL }, 38 { T,"T",0.0,NULL }, 39 { FUNC,"SIN",0.0,sin }, 40 { FUNC,"COS",0.0,cos }, 41 { FUNC,"TAN",0.0,tan }, 42 { FUNC,"LN",0.0,log }, 43 { FUNC,"EXP",0.0,exp }, 44 { FUNC,"SQRT",0.0,sqrt }, 45 { ORIGIN,"ORIGIN",0.0,NULL }, 46 { SCALE,"SCALE",0.0,NULL }, 47 { ROT,"ROT",0.0,NULL }, 48 { IS,"IS",0.0,NULL }, 49 { FOR,"FOR",0.0,NULL }, 50 { FROM,"FROM",0.0,NULL }, 51 { TO,"TO",0.0,NULL }, 52 { STEP,"STEP",0.0,NULL }, 53 { DRAW,"DRAW",0.0,NULL }, 54 }; 55 //extern 表示变量或者函数的定义在别的函数中 56 extern unsigned int LineNo; //跟踪记号所在源文件行号 57 extern int InitScanner(const char*); //初始化词法分析器 58 extern Token GetToken(void); //获取记号函数 59 extern void CloseScanner(void); //关闭词法分析器 60 61 #endif // SCANNER_H
scanner.cpp:
1 #include "scanner.h" 2 3 #define TOKEN_LEN 100 //记号最大长度 4 5 unsigned int LineNo; //跟踪源文件行号 6 //static FILE *InFile; //输入文件流 7 static char TokenBuffer[TOKEN_LEN]; //记号字符缓冲 8 FILE* pFile; 9 errno_t err = 0; //用来记录文件是否打开成功 10 //初始化词法分析器 11 extern int InitScanner(const char *FileName)//如果成功打开文件,则返回值1 12 { 13 LineNo = 1; 14 err = fopen_s(&pFile, FileName, "r"); 15 if (err == 0) return 1; 16 else return 0; 17 } 18 19 //关闭词法分析器 20 extern void CloseScanner(void) 21 { 22 if (err == 0) fclose(pFile); 23 } 24 25 //从输入源程序中读入一个字符 26 static char GetChar(void) 27 { 28 int Char = getc(pFile); 29 return toupper(Char); //将Char变成大写字母 30 } 31 32 //把预读的字符退回到输入源程序中 33 static void BackChar(char Char) 34 { 35 if (Char != EOF) ungetc(Char, pFile); 36 } 37 38 //加入字符到记号缓冲区 39 static void AddCharTokenString(char Char) 40 { 41 int TokenLength = strlen(TokenBuffer); 42 if (TokenLength + 1 >= sizeof(TokenBuffer)) return;//发生数组越界 43 TokenBuffer[TokenLength] = Char; 44 TokenBuffer[TokenLength + 1] = '\0';//字符串以'\0'结尾,c语言的字符串都是以/0来结尾的 45 } 46 47 //清空记号缓冲区 48 static void EmptyTokenString() 49 { 50 memset(TokenBuffer, 0, TOKEN_LEN); 51 } 52 53 //判断所给的字符串是否在符号表中 54 static Token JudgeKeyToken(const char *IDString) 55 { 56 int loop; //sizeof(TokenTab)L:表示这个数组一共占了多少字节数;sizeof(TokenTab[0]):表示一个元素所占的字节数, 57 //两者相除,表述数组中一共有多少个元素 58 for (loop = 0; loop<sizeof(TokenTab) / sizeof(TokenTab[0]); loop++) //TokenTab是一个数组,里面存储的是常量、函数、关键字的信息 59 { 60 //遍历TokenTab表 61 if (strcmp(TokenTab[loop].lexeme, IDString) == 0) return TokenTab[loop]; //判断所给的字符与TokenTab中的所有项,是否存在匹配 62 } 63 Token errortoken; 64 memset(&errortoken, 0, sizeof(Token)); 65 errortoken.type = ERRTOKEN; 66 return errortoken; 67 } 68 69 //获取一个记号 70 extern Token GetToken(void) 71 { 72 Token token; 73 int Char; 74 75 memset(&token, 0, sizeof(Token)); 76 EmptyTokenString(); 77 token.lexeme = TokenBuffer; 78 for (;;) //过滤源程序中的空格、TAB、回车等,遇到文件结束符返回空记号 79 { 80 Char = GetChar(); 81 if (Char == EOF) 82 { 83 token.type = NONTOKEN; 84 return token; 85 } 86 if (Char == '\n') LineNo++; 87 if (!isspace(Char)) break;// 是空白字符,返回非0值 如果Char不是空白字符,跳出for循环 88 } 89 AddCharTokenString(Char); 90 91 //若不是空格、TAB、回车、文件结束符等,则先加入到记号的字符缓冲区中 92 if (isalpha(Char)) //判断Char是不是英文字母 //若char是A-Za-z,则一定是函数、关键字、PI、E等。 93 { 94 for (;;) 95 { 96 Char = GetChar(); 97 if (isalnum(Char)) //判断字符变量Char是否为字母或者数字,是则返回非0,否则返回0; 98 AddCharTokenString(Char); //Char是字母或者数字 99 else break; 100 } 101 BackChar(Char); 102 token = JudgeKeyToken(TokenBuffer); 103 token.lexeme = TokenBuffer; 104 return token; 105 } 106 else if (isdigit(Char)) //若是一个数字,则一定是常量 107 { 108 for (;;) 109 { 110 Char = GetChar(); 111 if (isdigit(Char)) AddCharTokenString(Char); 112 else break; 113 } 114 if (Char == '.') 115 { 116 AddCharTokenString(Char); 117 for (;;) 118 { 119 Char = GetChar(); 120 if (isdigit(Char)) AddCharTokenString(Char); 121 else break; 122 } 123 } 124 BackChar(Char); //把预读的字符退回到输入源程序中 125 token.type = CONST_ID; 126 token.value = atof(TokenBuffer); //把字符串转换成浮点数 127 return token; 128 } 129 else //不是字母和数字,则一定是运算符或分隔符 130 { 131 switch (Char) 132 { 133 case ';':token.type = SEMICO; break; 134 case '(':token.type = L_BRACKET; break; 135 case ')':token.type = R_BRACKET; break; 136 case ',':token.type = COMMA; break; 137 case '+':token.type = PLUS; break; 138 case '-': 139 Char = GetChar(); 140 if (Char == '-') 141 { 142 while (Char != '\n' && Char != EOF) Char = GetChar(); 143 BackChar(Char); 144 return GetToken(); 145 } 146 else 147 { 148 BackChar(Char); 149 token.type = MINUS; 150 break; 151 } 152 case '/': 153 Char = GetChar(); 154 if (Char == '/') 155 { 156 while (Char != '\n' && Char != EOF) Char = GetChar(); 157 BackChar(Char); 158 return GetToken(); 159 } 160 else 161 { 162 BackChar(Char); 163 token.type = DIV; 164 break; 165 } 166 case '*': 167 Char = GetChar(); 168 if (Char == '*') 169 { 170 token.type = POWER; 171 break; 172 } 173 else 174 { 175 BackChar(Char); 176 token.type = MUL; 177 break; 178 } 179 default: 180 token.type = ERRTOKEN; 181 break; 182 }// end of switch 183 }//end of else(不是字母和数字,则一定是符号) 184 return token; 185 }//end of GetToken
scannermain.cpp 测试主程序:(在最后构造解释器的时候,不需要,主要作用是用来测试词法分析器的正常运行)
#include "scanner.h" int main(int argc, char *argv[]) { Token token; if(argc < 2) { printf("please input Source File !\n"); return 0; } if(!InitScanner(argv[1])) { printf("Open Source File Error!\n"); return 0; } printf("记号类型 字符串 常数值 函数指针\n"); printf("------------------------------------------\n"); while(1) { token = GetToken(); if(token.type != NONTOKEN) printf("%4d,%12s,%12f,%12x\n",token.type, token.lexeme, token.value, token.FuncPtr); else break; } printf("------------------------------------------\n"); CloseScanner(); }
环境说明:在单独测试词法分析器的时候,要进入到scanner生成的可执行文件的目录下,通过windows的控制台,执行scanner的可执行文件,加上函数绘图语言的源程序文件来测试。
如: scanner.exe test.txt
测试结果:
至此,成功构造该解释器的词法分析器。