解释器(一) 词法分析器

辣鸡的我终于在一个已经保研的小哥哥(萌似泰迪)的帮助下完成了解释器!!(VS2013)

分为3步:词法分析器、语法分析器、语义分析器

代码大部分出自《编译原理基础-习题与上机解答》(西安电子科技大学出版社)中的附录

下面会上所有代码附带(超级)大量详细注释和理解,以及很多处理细节。因为在这些在高手看来顺理成章的过程才是新手很大的障碍。

step 1

安装Virsual Stidio 2013

经过我的实践和另一个小哥哥的经验:windos7只能安装vs2013版的,否则就会出现下面这种2015版装了3个小时进度条还没前进的情况

  

不要装在C盘,这样如果系统崩了,C盘会全丢失,而且放进C盘电脑会运行变慢。

step 2

上代码之前,先来说说词法分析器。

我们需要

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

时间: 2024-08-16 20:48:08

解释器(一) 词法分析器的相关文章

实现脚本解释器 - 词法分析器

本系列介绍 笔者最近正学习编译原理,为了将理论变为实践,所以创作了本系列来记录学习过程中的思考与问题,注意文章中为了理论上描述方便增加了自创的术语. 本系列使用 Java 语言来实现一个脚本解释器,该脚本语言命名为 Foo,其语法参考 JavaScript 语言,本系列代码地址 Github . 词法分析器介绍 词法分析器的作用是将输入的字符串转变为一个个的记号(token),记号是由记号名(name)和属性值(value)构成的二元组(unit doublet). 通过构造有限自动机(fini

&lt;编译原理 - 函数绘图语言解释器(1)词法分析器 - python&gt;

<编译原理 - 函数绘图语言解释器(1)词法分析器 - python> 背景 编译原理上机实现一个对函数绘图语言的解释器 - 用除C外的不同种语言实现 解释器分为三个实现块: 词法分析器:用于识别一条语句中的关键词是否符合预先定义的规则. 语法分析器:用来确定一条语句是否满足语法规则. 解释器:用来确定满足语法规则的句子,在意思上是否符合要求. 设计思路: 设计记号:词法分析器读取一个序列并根据构词规则把序列转化为记号流 定义一个字典:把所有符合一个模式的保留字.常量名.参数名.函数名等放进字

atitit.自己动手开发编译器and解释器(1) ------词法分析--attilax总结

atitit.自己动手开发编译器and解释器(1) ------词法分析--attilax总结 1.   应用场景:::DSL 大大提升开发效率 1 2. 2. 流程如下::: 词法分析(生成token流) >>>>语法分析(生成ast) >>解释执行... 2 3. 如何进行词法分析?Fsm状态机(自动机) 2 4. 使用状态模式构建FSM  (简单,易用..推荐首选) 2 5. ---代码( 状态模式构建FSM ) 3 6. 词法分析概念 3 6.1. 词法分析(英

如何写一个解释器(1):编译原理

最近在看DSL的东西,对于外部DSL,写一个解释器是必不可少的.我试图归纳一下我学到的,以写一个解释器为目标,讲一下如果来实现一个可用的解释器.一个解释器通常可以分为一下几个阶段: 词法分析(Lexer) 语法分析(Parser, BNF, CFG, AST) 语义分析(AST的处理, annotated AST) 目标语言生成(stack-based) 这里的解释器不包括目标语言的执行和运行时环境,如果需要类似于python/ruby的解析执行器的话,还需要bytecode-compiler,

[编译原理]用BDD方式开发lisp解释器(编译器)|开发语言java|Groovy|Spock

lisp是一门简单又强大的语言,其语法极其简单: (+ 1 2 ) 上面的意思 是:+是方法或函数,1 ,2 是参数,fn=1+2,即对1,2进行相加求值,结果是:3 双括号用来提醒解释器开始和结束. 之前在iteye写过一篇文章来简单介绍怎么写lisp的解释器: http://gyc567.iteye.com/blog/2242960 同时也画了一张草图来说明: 因为lexer(词法分析器)主要工作就是把程序的字符串表达式转化为tokens.(Pair),以下是百科对词法分析的说明: 词法分析

jcSQL词法分析器对字符串token的解析

上星期写完词法分析器的时候,曾遇上一个无关紧要却X疼的问题.毕竟是第一次完整地写整个语言的编译器(暂且这么叫着吧,解释器更靠谱),由于经验不足,在字符串解析这一块驻足了两天才解决掉,这里记录下来供以后参考.哦对了,之所以想自己手写词法分析器,并不是我不知道有自动工具可以自动生成,而是我不会用,嗯,果然高冷. 词法分析器的作用简而言之就是将语言分割成一个一个独立的词法单元(单词),并赋予一定的类型.(如果不了解其作用,建议参考词法分析) 例如: a = 3 ; 我们就可以将其分课程一个个有意义的单

用_Python_实现_Python_解释器

摘要: Allison 是 Dropbox 的工程师,在那里她维护着这个世界上最大的 Python 客户端网络之一.在去 Dropbox 之前,她是 Recurse Center 的协调人, 是这个位于纽约的程序员深造机构的作者. Allison 是 Dropbox 的工程师,在那里她维护着这个世界上最大的 Python 客户端网络之一.在去 Dropbox 之前,她是 Recurse Center 的协调人, 是这个位于纽约的程序员深造机构的作者.她在北美的 PyCon 做过关于 Python

atitit.java解析sql语言解析器解释器的实现

1. 解析sql的本质:实现一个4gl dsl编程语言的编译器 1 2. 解析sql的基本的流程,词法分析,而后进行语法分析,语义分析,构建sql的AST 1 3. 词法分析器 2 4. 语法分析器--ANTLR 2 5. Eclipse插件,,ANTLR Studio 3 6. 一个基于javacc实现的解析器JSqlParser0.7(yr2011), 3 7. 样例代码-----解析sql表格列的名称and类型 3 8. }Sql的历史 4 9. 解析select语句 4 10. zql,

用 Python 编写的 Python 解释器

Allison是Dropbox的工程师,在那里她维护着世界上最大的由Python客户组成的网络.在Dropbox之前,她是Recurse Center的引导师, - 她在北美的PyCon做过关于Python内部机制的演讲,并且她喜欢奇怪的bugs.她的博客地址是akaptur.com. Introduction Byterun是一个用Python实现的Python解释器.随着我在Byterun上的工作,我惊讶并很高兴地的发现,这个Python解释器的基础结构可以满足500行的限制.在这一章我们会