lexer的构造函数

有了上一节Token做铺垫, 可以开始设计lexer, 首先应该想到的是, 源代码是以文件流的格式传到编译器中的, 所以作为编译器的前段的第一个阶段, lexer必须负责处理输入的文件流.

  1. private:
  2. static const size_t BUFFERSIZE = 256;
  3. static const size_t LIMITSIZE = 250;
  4. static const size_t COPYLENGTH = BUFFERSIZE - LIMITSIZE;
  5. std::ifstream& ifs;
  6. char buffer[BUFFERSIZE];
  7. size_t idx;
  8. size_t row;
  9. size_t column;
  10. public:
  11. Lexer(std::ifstream& ifs):ifs(ifs), idx(0), row(1), column(0){
  12. ifs.read(buffer, BUFFERSIZE);
  13. lex();
  14. }

然后这里有一点要注意的是, 我设计了一个buffer专门用来缓存读取的char, 这么做的原因有如下几个 :

  1. 考虑到读取过程可能会使用前瞰来进行准确判断, 使用缓存buffer可以让我们方便地获取上一个读取的字符.
  2. 一次读取一个字符效率太低, 一次多个读取有助于提高效率.

这里的rowcolumn用来记录当前分析到的位置, 在编译器发现用户的源代码错误的时候可以给用户一个相对准确的定位. 所以昨天的Token也需要做相应的修改.

  1. // part of the Token class, not complete!
  2. private:
  3. Token_Type type;
  4. union Value{
  5. long l;
  6. double d;
  7. std::string s;
  8. };
  9. Value value;
  10. size_t row;
  11. size_t column;
  12. public:
  13. Token(Token_Type type, const std::string string, size_t row, size_t column): type(type), row(row), column(column) {
  14. switch (type){
  15. case INT:{
  16. value.l = parseInt(string);
  17. break;
  18. }
  19. case FLOAT:{
  20. value.d = parseFloat(string);
  21. break;
  22. }

然后我们再来看下面几个工具函数.

  1. void fill(){
  2. if(idx <= LIMITSIZE){
  3. return;
  4. }
  5. idx -= LIMITSIZE;
  6. strncpy(buffer, buffer + LIMITSIZE, COPYLENGTH);
  7. ifs.read(buffer + COPYLENGTH, LIMITSIZE);
  8. }
  9. char getNextChar(){
  10. fill();
  11. return buffer[idx++];
  12. }
  13. void eatSpace(){
  14. char ch = 0;
  15. while(ch = buffer[idx++]){
  16. fill();
  17. switch (ch){
  18. case '\n':{
  19. ++row;
  20. column = 0;
  21. break;
  22. }
  23. case ' ':{
  24. }
  25. case '\t':{
  26. ++column;
  27. break;
  28. }
  29. default:{
  30. --idx;
  31. return;
  32. }
  33. }
  34. }
  35. }

第一个函数的作用是当缓存中的字符串几乎要分析完毕的时候, 将最后几个字符移动到开头并重新刷新缓存, 这里我这是的刷新界限是250. 后面两个很简单, 就不解释了.

在进行词法分析之前, 我准备测试一下, 粗略地一下前面的代码, 我个人认为在代码规模相对较小的时候确保代码的正确性和可靠性是一种珍惜生命的方式...

  1. void Lexer::lex() {
  2. eatSpace();
  3. std::cout << getNextChar() << std::endl;
  4. std::cout << row << std::endl;
  5. std::cout << column << std::endl;
  6. }
  1. int main() {
  2. std::ifstream ifs("/Users/zhangzhimin/x.txt");
  3. if(ifs){
  4. Lexer x(ifs);
  5. }
  6. }

很幸运的是, 报错了...

  1. In file included from /Users/zhangzhimin/ClionProjects/Fred/Font/Lexer/Token.cpp:1:
  2. /Users/zhangzhimin/ClionProjects/Fred/Font/Lexer/Token.h:31:5: error: call to implicitly-deleted default constructor of 'Token::Value'
  3. Token(Token_Type type, const std::string string, size_t row, size_t column): type(type), row(row), column(column) {
  4. ^
  5. /Users/zhangzhimin/ClionProjects/Fred/Font/Lexer/Token.h:23:21: note: default constructor of 'Value' is implicitly deleted because variant field 's' has a non-trivial default constructor
  6. std::string s;
  7. ^
  8. /Users/zhangzhimin/ClionProjects/Fred/Font/Lexer/Token.h:31:5: error: attempt to use a deleted function
  9. Token(Token_Type type, const std::string string, size_t row, size_t column): type(type), row(row), column(column) {
  10. ^
  11. /Users/zhangzhimin/ClionProjects/Fred/Font/Lexer/Token.h:23:21: note: destructor of 'Value' is implicitly deleted because variant field 's' has a non-trivial destructor
  12. std::string s;
  13. ^
  14. 2 errors generated.

log看着一大堆, 其实都在说一个错误, 就是我在Token中使用了一种联合体的方式, 也就是说将字符串放在union里面会导致编译器无法为联合体自动合成构造函数和析构函数, 关于这一块我也是第一次接触到, so上搜索了一下 :

Unrestricted unions[edit]

In C++03, there are restrictions on what types of objects can be members of a union. For example, unions cannot contain any objects that define a non-trivial constructor or destructor. C++11 lifts some of these restrictions.[3]

If a union member has a non trivial special member function, the compiler will not generate the equivalent member function for the union and it must be manually defined.

大概就是说, 在C++03的时候, 联合体如果有任何成员的构造函数或者析构函数是显式的, 那么就不行. 而在C++11中放宽了要求, 如果出现上述情况也可以, 但是对于那个显式出现的函数, 编译器将不再帮助你自动生成对应在联合体中的函数, 你必须自己定义它.

所以这个地方的错误根源在于标准库中的string有它自己的构造函数而且析构函数 这里稍微有点过于隐晦了, 所以我选择的改动方式是 : 对于所有的Token都只留下string, 将string解析为longdouble等到词法分析的时候再执行. 这样可以避开这种比较隐晦的问题, 因为说实话, 这一块我也不是很了解.

改动之后的Token代码如下.

  1. #ifndef FRED_TOKEN_H
  2. #define FRED_TOKEN_H
  3. #include <string>
  4. class Token{
  5. public:
  6. enum Token_Type{
  7. INT, // 0|([1-9][0-9]*)
  8. FLOAT, // (0|([1-9][0-9]*)\.?[0-9]+)
  9. STRING,
  10. IDENTIFIER, // [a-zA-Z_][0-9a-zA-Z_]*
  11. KEYWORD,
  12. OPERATOR, // + - * / += -= *= /= = == ! - && ||
  13. BRACKET, // () {} []
  14. };
  15. private:
  16. Token_Type type;
  17. std::string value;
  18. size_t row;
  19. size_t column;
  20. public:
  21. Token(Token_Type type, const std::string string, size_t row, size_t column): type(type), value(string), row(row), column(column) {
  22. if(type == IDENTIFIER && isKeyword(string)){
  23. this->type = KEYWORD;
  24. }
  25. }
  26. Token(const Token&) = delete;
  27. Token(const Token&&) = delete;
  28. Token& operator=(const Token& rhs) = delete;
  29. Token& operator=(const Token&& rhs) = delete;
  30. virtual ~Token() = default;
  31. Token_Type getType() const { return type; }
  32. const std::string& getValue() const { return value; }
  33. private:
  34. long parseHelper(const std::string&, size_t&);
  35. long parseInt(const std::string&);
  36. double parseFloat(const std::string&);
  37. bool isKeyword(const std::string&);
  38. };
  39. #endif //FRED_TOKEN_H

再次运行, 发现已经可以了, 但是我突然想到了另外一个问题. 我们每次读取下一个字符的时候都会尝试着跟新我们的缓存区, 但是如果文件中内容读取完毕之后是不是应该考虑不再跟新缓存区呢? 对此我做了两个改变 :

  1. 添加了一个flagEndOfFile, 并在更新缓存的fill()函数中进行了相应的改变.
  2. fill()改名为updateBuffer()因为之前这个名字是在是太不贴切了...

所以经过一系列的更改, 目前源代码如下 :

  1. #ifndef FRED_LEXER_H
  2. #define FRED_LEXER_H
  3. #include <fstream>
  4. class Lexer{
  5. private:
  6. static const size_t BUFFERSIZE = 256;
  7. static const size_t LIMITSIZE = 250;
  8. static const size_t COPYLENGTH = BUFFERSIZE - LIMITSIZE;
  9. std::ifstream& ifs;
  10. bool EndOfFile;
  11. char buffer[BUFFERSIZE];
  12. size_t idx;
  13. size_t row;
  14. size_t column;
  15. public:
  16. Lexer(std::ifstream& ifs):ifs(ifs), EndOfFile(false), idx(0), row(1), column(0){
  17. ifs.read(buffer, BUFFERSIZE);
  18. lex();
  19. }
  20. void lex();
  21. private:
  22. void updateBuffer(){
  23. if(idx <= LIMITSIZE || EndOfFile){
  24. return;
  25. }
  26. idx -= LIMITSIZE;
  27. strncpy(buffer, buffer + LIMITSIZE, COPYLENGTH);
  28. ifs.read(buffer + COPYLENGTH, LIMITSIZE);
  29. if(ifs.eof()){
  30. EndOfFile = true;
  31. }
  32. }
  33. char getNextChar(){
  34. updateBuffer();
  35. ++column;
  36. return buffer[idx++];
  37. }
  38. void eatSpace();
  39. };
  40. #endif //FRED_LEXER_H

Lexer的设计--上(3)的更多相关文章

  1. 设计上如何避免EMC问题

    最近经常被问到EMC相关的问题,比如怎么设计才能避免EMC的问题,我想经常关注高速先生的同鞋们有机会肯定也会问到这个问题.首先这是一个系统 性的问题,不是那么好回答,尤其是对于聚焦在高速信号这个领域而 ...

  2. C#进阶系列——MEF实现设计上的“松耦合”(二)

    前言:前篇 C#进阶系列——MEF实现设计上的“松耦合”(一) 介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能不限于此,比如MEF的目录服务.目录筛选.重组部件等高级 ...

  3. C#进阶系列——MEF实现设计上的“松耦合”(四):构造函数注入

    前言:今天十一长假的第一天,本因出去走走,奈何博主最大的乐趣是假期坐在电脑前看各处堵车,顺便写写博客,有点收获也是好的.关于MEF的知识,之前已经分享过三篇,为什么有今天这篇?是因为昨天分享领域服务的 ...

  4. Type-C设计上的防护

    Type C设计上各家芯片公司都提供了很多方案,但在防护方面很多留给了客户自己选择,这方面我可以重点聊聊,说起防护,无非就是过压过流防护. 过压防护,Type C的信号线有很多,都需要做静电防护,US ...

  5. MEF实现设计上的“松耦合”

    C#进阶系列——MEF实现设计上的“松耦合”(二)   前言:前篇 C#进阶系列——MEF实现设计上的“松耦合”(一) 介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能 ...

  6. 面试挂在了 LRU 缓存算法设计上

    好吧,有人可能觉得我标题党了,但我想告诉你们的是,前阵子面试确实挂在了 RLU 缓存算法的设计上了.当时做题的时候,自己想的太多了,感觉设计一个 LRU(Least recently used) 缓存 ...

  7. JavaScript 与 Java 是两种完全不同的语言,无论在概念还是设计上。

    JavaScript 与 Java 是两种完全不同的语言,无论在概念还是设计上. Java(由 Sun 发明)是更复杂的编程语言. ECMA-262 是 JavaScript 标准的官方名称. Jav ...

  8. 全面提价2499元起小米6发布:四曲陶瓷机身+骁龙835+变焦双摄(小米在设计上也多次获得红点最佳、iF金奖等72项工业设计大奖)

    集微网  4月19日报道 今日,小米公司在北京召开正式推出了新一代旗舰手机“小米手机6”.在试玩过真机后,第一感觉就是这款手机做工与颜值相比此前小米手机提升巨大:有四曲面玻璃或陶瓷机身.不锈钢高亮边框 ...

  9. java架构-一些设计上的基本常识

    最近给团队新人讲了一些设计上的常识,可能会对其它的新人也有些帮助, 把暂时想到的几条,先记在这里. 1.API与SPI分离 框架或组件通常有两类客户,一个是使用者,一个是扩展者. API(Applic ...

随机推荐

  1. jquery zoom jquery放大镜特效

    这是一款非常不错的给图片添加放大镜效果,可以应用在诸如zen cart,magento电子商场之类的开源项目上.如果想看它的效果,你可以直接访问: http://www.mind-projects.i ...

  2. html5 背景音乐 js控制播放 暂停

    <html> <head> <title> 测试页面 </title> <script src="jquery.min.js" ...

  3. 【codeforces 742C】Arpa's loud Owf and Mehrdad's evil plan

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  4. ArcEngine 数据导入经验(转载)

    转自原文ArcEngine 数据导入经验(转载) (一) GIS系统数据是基础,想必大家在做ArcEngine二次开发的过程中都会遇到向MDB和SDE写入数据的工作,我们将会通过几个篇幅,从大数据量导 ...

  5. [TypeScript] Using ES6 and ESNext with TypeScript

    TypeScript is very particular about what is and isn't allowed in a TS file to protect you from commo ...

  6. HTML中DOM对象的属性和方法的层级关系是怎样的?(目录即层次)

    HTML中DOM对象的属性和方法的层级关系是怎样的?(目录即层次) 一.总结 一句话总结:目录就是测试题 1.document取得元素(get element)的方式有哪几种? 解答:四种,分别是id ...

  7. js进阶 9-6 js如何通过name访问指定指定表单控件

    js进阶 9-6 js如何通过name访问指定指定表单控件 一.总结 一句话总结:form中控件的三种访问方式:2formElement 1document 1.form中控件的三种访问方式? 1.f ...

  8. Oracle数据库零散知识06 -- Package的定义与简单触发器

    CREATE OR REPLACE PACKAGE pak_02 IS--包头 --这里可定义公共参数 FUNCTION fun_01 RETURN NUMBER; PROCEDURE pro_01 ...

  9. sparksql parquet 分区推断Partition Discovery

    网上找的大部分资料都很旧,最后翻了下文档只找到了说明 大概意思是1.6之后如果想要使用分区推断就要设置数据源的basePath,因此代码如下 java public class ParitionInf ...

  10. 选iphone5可以正常编译运行 , 但是5s和6和6s都会编译报错

    选iphone5可以正常编译运行   但是5s和6和6s都会编译报错 iphone6编译报错iphone5s编译报错 解决办法是,Build settings里面把Architectures里面的$( ...