手写编译器之词法分析器一




   讲道理现在应该先定义一下token了,不过我还不知道token怎么定义,就先不这么搞了,先解决读取文件的问题,



定义一个string变量(filepath)存储文件地址,定义文件流(in)用于读取文件内容,然后从流中每次读取一行,存入



字符串变量line\_str,这样思路就很清晰了,我们从line\_str中逐个取出字符,然后分析。



1 ifstream in;//文件流 2 string filepath;//文件路径 3 string line_str;//存储每次读出的行 4 int line_len;//读出行的长度 5 int line_pos;//逐字符处理时,用于记录位置 6 int line_num;//记录当前源文件第几行,可能以后报语法错误的时候有用 7 char ch;//逐字符处理时,记录当前字符 8 string token_str;//记录token字符串




  为防止出错,我们定义初始化函数init(),将所有变量的初始化放在该函数中,如下:



1 void init() 2 { 3 cout<<"func enter: init"<<endl; 4 line_pos=0; 5 line_num=0; 6 token_str=""; 7 ch='\0'; 8 //end_of_file=false; 9 //end_of_lex=false; 10 //tokenlist=new TokenNode_tag; 11 //tokenlist->token_str="#"; 12 //tokenlist->next=NULL; 13 //listtail=tokenlist; 14 in.open(filepath.c_str()); 15 cout<<"func end: init"<<endl; 16 }




  在此函数执行之前,filepath就已经在函数外赋值了,我懒得传参数,就在该函数调用前加了一句: 



filepath=sourcefilepath;  //sourcefilepath是传进来的参数。



  下面,我们就开始定义token数据结构,目前我能想到的数据结构属性只有两个,一个token\_str,



一个token\_type。token\_str用来保存token字符串,而token\_type记录该token是什么类型,比如标识符,



关键字,数字等等。在定义token前先定义TokenType(枚举类型),如下:



1 enum TokenType 2 { 3 //顺序不可改变 4 KEYWORDS_INT, 5 KEYWORDS_DOUBLE, 6 KEYWORDS_FLOAT, 7 KEYWORDS_IF, 8 KEYWORDS_ELSE, 9 KEYWORDS_ELSIF, 10 KEYWORDS_WHILE, 11 KEYWORDS_FOR, 12 13 14 IDENTIFY, 15 OP_EQUAL,//== 16 OP_ASSIGN,//= 17 OP_LP,//( 18 OP_RP,//) 19 OP_ADD,//+ 20 OP_SUB,//- 21 OP_MUL,//* 22 OP_DIV,// / 23 OP_SEMI,//; 24 NUM_FLOAT, 25 NUM_INT 26 };




  TokenType里面的注释肯定就很清晰了,就不多介绍,下面就是TokenNode的定义:



1 typedef struct TokenNode_tag 2 { 3 string token_str; 4 TokenType token_type; 5 struct TokenNode_tag *next;//用于制作链表 6 }*TokenList_tag,TokenNode_tag;




  可能会有人疑惑,命名为什么要加一个tag呢?首先,名字是什么都无所谓(关键字除外),其次



加一个tag是为了区别内部使用和外部调用,所谓内部就是lex.cpp中的函数使用,外部就是为了以后的



其他模块调用(到时候会给这些结构重新起名为TokenNode)。



  好了,万事俱备,只欠最关键的函数了:



1 TokenList_tag LexExcute(string sourcefilepath) 2 { 3 cout<<"func enter: main"<<endl; 4 filepath=sourcefilepath;//; 5 init();     //这里将写关键代码,一个大while循环 6 cout<<"func end: main"<<endl; 7 }




  这个函数作为主函数,他的工作原理就是循环从line\_str中取出字符,然后根据字符判断目前是不是一个单词



或符号,比如出现空格就说明字符串结束等(说法不准确)。好吧,我知道这里有个自动机什么的,我也讲不清楚,



下面直接说我的实现方法:



  我将词法分析运作过程分为几个阶段,每个阶段都用一种状态记录,比如说,当while大循环第一次运作的时候,



此时为初始状态(STATUS\_NON),在这个状态下若取出字符ch为数字(0-9),那么词法分析状态就转变为数字



态(STATUS\_NUM),在该状态下若取出字符ch为数字则状态不变,若ch为 ‘.’ (小数点)说明数字为浮点型,状态转为浮点



态(STATUS\_FLOAT),若ch为空或者其他字符,说明数字结束,状态转为初始态,开始下一个循环。



  可能我自己觉得讲的挺清晰,看到的人反而有困难,不如我就画个图:



![](https://img-blog.csdnimg.cn/img_convert/d21a15bb4a9d436a9ed893fd4cab09f3.png)



  好吧,真是不画不知道自己画多丑,不过抛开这些还是挺清晰的吧。。。额,这不重要,这应该就有传说



中的自动机的影子了吧,好吧,不强求能不能看懂了,最下面会有函数完整代码,看懂代码肯定就没问题了。



  在写主函数(LexExcute)之前,我们还要做一些必要工作:



1 bool end_of_file;//初始为false,当读取文件结束的时候置为true 2 bool end_of_lex;//当读取文件结束,而其当前行也分析完毕时置为true(词法分析结束标志) 3 TokenList_tag tokenlist;//词法分析器执行结束后产生的 token 串(主函数返回链表的头指针) 4 TokenNode_tag*listtail;//链表尾指针,用于尾部插入节点 5 void getline();//文件中读取一行 6 void getch();//获取一个字符 7 bool ischar();//是否为a-z或者A-Z 8 bool isnum();//是否为0-9 9 void concat();//连接到token_str 10 // 11 void backwords();//回退一个字符 12 void tokenlist_insert(TokenNode_tag*);//链表插入函数 13 void tokenlist_visit();//链表遍历函数,用于检查正误,对功能没用用 14 int iskeywords(string );//判断是否是关键字




  关键字数组,自动机状态定义具体函数实现就看下面的完整代码吧:



1 string keywords[]= 2 { 3 //顺序不可改变 4 "int", 5 "double", 6 "float", 7 "if", 8 "else", 9 "elsif", 10 "while", 11 "for" 12 }; 13 14 enum LexStatus 15 { 16 STATUS_NON, 17 STATUS_NUM, 18 STATUS_STR, 19 STATUS_FLOAT, 20 STATUS_ASSIGN, 21 22 };

最后

正值招聘旺季,很多小伙伴都询问我有没有前端方面的面试题!

enum LexStatus 15 { 16 STATUS_NON, 17 STATUS_NUM, 18 STATUS_STR, 19 STATUS_FLOAT, 20 STATUS_ASSIGN, 21 22 };`

最后

正值招聘旺季,很多小伙伴都询问我有没有前端方面的面试题!

前端资料图.PNG

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值