lexer的构造函数

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

private:
static const size_t BUFFERSIZE = 256;
static const size_t LIMITSIZE = 250;
static const size_t COPYLENGTH = BUFFERSIZE - LIMITSIZE; std::ifstream& ifs;
char buffer[BUFFERSIZE];
size_t idx; size_t row;
size_t column;
public:
Lexer(std::ifstream& ifs):ifs(ifs), idx(0), row(1), column(0){
ifs.read(buffer, BUFFERSIZE);
lex();
}

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

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

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

// part of the Token class, not complete!
private:
Token_Type type;
union Value{
long l;
double d;
std::string s;
}; Value value;
size_t row;
size_t column; public:
Token(Token_Type type, const std::string string, size_t row, size_t column): type(type), row(row), column(column) {
switch (type){
case INT:{
value.l = parseInt(string);
break;
}
case FLOAT:{
value.d = parseFloat(string);
break;
}

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

    void fill(){
if(idx <= LIMITSIZE){
return;
}
idx -= LIMITSIZE;
strncpy(buffer, buffer + LIMITSIZE, COPYLENGTH);
ifs.read(buffer + COPYLENGTH, LIMITSIZE);
} char getNextChar(){
fill();
return buffer[idx++];
} void eatSpace(){
char ch = 0;
while(ch = buffer[idx++]){
fill();
switch (ch){
case '\n':{
++row;
column = 0;
break;
}
case ' ':{ }
case '\t':{
++column;
break;
}
default:{
--idx;
return;
}
}
}
}

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

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

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

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

In file included from /Users/zhangzhimin/ClionProjects/Fred/Font/Lexer/Token.cpp:1:
/Users/zhangzhimin/ClionProjects/Fred/Font/Lexer/Token.h:31:5: error: call to implicitly-deleted default constructor of 'Token::Value'
Token(Token_Type type, const std::string string, size_t row, size_t column): type(type), row(row), column(column) {
^
/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
std::string s;
^
/Users/zhangzhimin/ClionProjects/Fred/Font/Lexer/Token.h:31:5: error: attempt to use a deleted function
Token(Token_Type type, const std::string string, size_t row, size_t column): type(type), row(row), column(column) {
^
/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
std::string s;
^
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代码如下.

#ifndef FRED_TOKEN_H
#define FRED_TOKEN_H #include <string> class Token{
public:
enum Token_Type{
INT, // 0|([1-9][0-9]*)
FLOAT, // (0|([1-9][0-9]*)\.?[0-9]+)
STRING,
IDENTIFIER, // [a-zA-Z_][0-9a-zA-Z_]*
KEYWORD,
OPERATOR, // + - * / += -= *= /= = == ! - && ||
BRACKET, // () {} []
}; private:
Token_Type type;
std::string value;
size_t row;
size_t column; public:
Token(Token_Type type, const std::string string, size_t row, size_t column): type(type), value(string), row(row), column(column) {
if(type == IDENTIFIER && isKeyword(string)){
this->type = KEYWORD;
}
} Token(const Token&) = delete;
Token(const Token&&) = delete;
Token& operator=(const Token& rhs) = delete;
Token& operator=(const Token&& rhs) = delete;
virtual ~Token() = default; Token_Type getType() const { return type; } const std::string& getValue() const { return value; } private:
long parseHelper(const std::string&, size_t&);
long parseInt(const std::string&);
double parseFloat(const std::string&);
bool isKeyword(const std::string&);
}; #endif //FRED_TOKEN_H

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

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

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

#ifndef FRED_LEXER_H
#define FRED_LEXER_H #include <fstream> class Lexer{
private:
static const size_t BUFFERSIZE = 256;
static const size_t LIMITSIZE = 250;
static const size_t COPYLENGTH = BUFFERSIZE - LIMITSIZE; std::ifstream& ifs;
bool EndOfFile;
char buffer[BUFFERSIZE];
size_t idx; size_t row;
size_t column;
public:
Lexer(std::ifstream& ifs):ifs(ifs), EndOfFile(false), idx(0), row(1), column(0){
ifs.read(buffer, BUFFERSIZE);
lex();
} void lex(); private:
void updateBuffer(){
if(idx <= LIMITSIZE || EndOfFile){
return;
}
idx -= LIMITSIZE;
strncpy(buffer, buffer + LIMITSIZE, COPYLENGTH);
ifs.read(buffer + COPYLENGTH, LIMITSIZE);
if(ifs.eof()){
EndOfFile = true;
}
} char getNextChar(){
updateBuffer();
++column;
return buffer[idx++];
} void eatSpace();
}; #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. ENVI显示GDAL创建GeoTiff文件的一个问题及其思考

    作者:朱金灿 来源:http://blog.csdn.net/clever101 使用gdal创建一个100*100的红色的geotiff图像,代码如下: #include <assert.h& ...

  2. [Compose] 16. Apply multiple functors as arguments to a function (Applicatives)

    We find a couple of DOM nodes that may or may not exist and run a calculation on the page height usi ...

  3. 基于bootstrap的富文本框——wangEditor【欢迎增加开发】

    先来一张效果图: 01. 引言 老早就開始研究富文本框的东西,在写完<深入理解javascript原型与闭包>之后,就想着要去做一个富文本框的插件的样例. 如今网络上开源的富文本框插件许多 ...

  4. AndroidStudio实现JNI的示例详解

    1. NDK简介 Android NDK 是在SDK前面又加上了“原生”二字,即Native Development Kit,因此又被Google称为“NDK” 1.1 NDK产生的背景 Androi ...

  5. 【t047】网络

    Time Limit: 1 second Memory Limit: 128 MB [问题描述] Bessie受雇来到John的农场帮他们建立internet网络.农场有 N (2<= N &l ...

  6. java 替换json字符串中间的引号保留两边的引号,避免json校验失败

    一.json概要 JSON(JavaScript Object Notation, JS 对象标记)-一种轻量级的数据交换标准(相对xml),独立于编程语言.具体以逗号分隔的key:value键值对的 ...

  7. 分布式ID解决方案

    开发十年,就只剩下这套Java开发体系了 >>>   在游戏开发中,我们使用分布式ID.有很多优点 便于合服 便于ID管理 等等 一.单服各自ID系统的弊端 1. 列如合服 在游戏上 ...

  8. Erlang中频繁发送远程消息要注意的问题

    http://avindev.iteye.com/blog/76373 注:这篇文章可能会有争议,欢迎提出意见 在Erlang中,如果要实现两个远程节点之间的通信,就需要通过网络来实现,对于消息发送, ...

  9. 【BZOJ 1024】 [SCOI2009]生日快乐

    [题目链接]:http://www.lydsy.com/JudgeOnline/problem.php?id=1024 [题意] [题解] 要求恰好分成n个部分;每个部分的面积都一样; 则dfs的时候 ...

  10. Diffie-Hellman Key Exchange – A Non-Mathematician’s Explanation

    The Complete Diffie-Hellman Key Exchange Diagram The process begins when each side of the communicat ...