Lexer的设计--上(3)
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
, 这么做的原因有如下几个 :
- 考虑到读取过程可能会使用前瞰来进行准确判断, 使用缓存
buffer
可以让我们方便地获取上一个读取的字符. - 一次读取一个字符效率太低, 一次多个读取有助于提高效率.
这里的row
和column
用来记录当前分析到的位置, 在编译器发现用户的源代码错误的时候可以给用户一个相对准确的定位. 所以昨天的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 theunion
and it must be manually defined.
大概就是说, 在C++03的时候, 联合体如果有任何成员的构造函数或者析构函数是显式的, 那么就不行. 而在C++11中放宽了要求, 如果出现上述情况也可以, 但是对于那个显式出现的函数, 编译器将不再帮助你自动生成对应在联合体中的函数, 你必须自己定义它.
所以这个地方的错误根源在于标准库中的string有它自己的构造函数而且析构函数 这里稍微有点过于隐晦了, 所以我选择的改动方式是 : 对于所有的Token
都只留下string
, 将string
解析为long
或double
等到词法分析的时候再执行. 这样可以避开这种比较隐晦的问题, 因为说实话, 这一块我也不是很了解.
改动之后的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
再次运行, 发现已经可以了, 但是我突然想到了另外一个问题. 我们每次读取下一个字符的时候都会尝试着跟新我们的缓存区, 但是如果文件中内容读取完毕之后是不是应该考虑不再跟新缓存区呢? 对此我做了两个改变 :
- 添加了一个flag
EndOfFile
, 并在更新缓存的fill()
函数中进行了相应的改变. - 将
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)的更多相关文章
- 设计上如何避免EMC问题
最近经常被问到EMC相关的问题,比如怎么设计才能避免EMC的问题,我想经常关注高速先生的同鞋们有机会肯定也会问到这个问题.首先这是一个系统 性的问题,不是那么好回答,尤其是对于聚焦在高速信号这个领域而 ...
- C#进阶系列——MEF实现设计上的“松耦合”(二)
前言:前篇 C#进阶系列——MEF实现设计上的“松耦合”(一) 介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能不限于此,比如MEF的目录服务.目录筛选.重组部件等高级 ...
- C#进阶系列——MEF实现设计上的“松耦合”(四):构造函数注入
前言:今天十一长假的第一天,本因出去走走,奈何博主最大的乐趣是假期坐在电脑前看各处堵车,顺便写写博客,有点收获也是好的.关于MEF的知识,之前已经分享过三篇,为什么有今天这篇?是因为昨天分享领域服务的 ...
- Type-C设计上的防护
Type C设计上各家芯片公司都提供了很多方案,但在防护方面很多留给了客户自己选择,这方面我可以重点聊聊,说起防护,无非就是过压过流防护. 过压防护,Type C的信号线有很多,都需要做静电防护,US ...
- MEF实现设计上的“松耦合”
C#进阶系列——MEF实现设计上的“松耦合”(二) 前言:前篇 C#进阶系列——MEF实现设计上的“松耦合”(一) 介绍了下MEF的基础用法,让我们对MEF有了一个抽象的认识.当然MEF的用法可能 ...
- 面试挂在了 LRU 缓存算法设计上
好吧,有人可能觉得我标题党了,但我想告诉你们的是,前阵子面试确实挂在了 RLU 缓存算法的设计上了.当时做题的时候,自己想的太多了,感觉设计一个 LRU(Least recently used) 缓存 ...
- JavaScript 与 Java 是两种完全不同的语言,无论在概念还是设计上。
JavaScript 与 Java 是两种完全不同的语言,无论在概念还是设计上. Java(由 Sun 发明)是更复杂的编程语言. ECMA-262 是 JavaScript 标准的官方名称. Jav ...
- 全面提价2499元起小米6发布:四曲陶瓷机身+骁龙835+变焦双摄(小米在设计上也多次获得红点最佳、iF金奖等72项工业设计大奖)
集微网 4月19日报道 今日,小米公司在北京召开正式推出了新一代旗舰手机“小米手机6”.在试玩过真机后,第一感觉就是这款手机做工与颜值相比此前小米手机提升巨大:有四曲面玻璃或陶瓷机身.不锈钢高亮边框 ...
- java架构-一些设计上的基本常识
最近给团队新人讲了一些设计上的常识,可能会对其它的新人也有些帮助, 把暂时想到的几条,先记在这里. 1.API与SPI分离 框架或组件通常有两类客户,一个是使用者,一个是扩展者. API(Applic ...
随机推荐
- NET WinForm 开发所见即所得的 IDE 开发环境
Github 开源:使用 .NET WinForm 开发所见即所得的 IDE 开发环境(Sheng.Winform.IDE)[2.源代码简要说明] GitHub:https://github.co ...
- crx
https://www.crx4chrome.com/down/770/crx/ Downloading crx file for Linkclump The Linkclump crx file y ...
- [GraphQL] Write a GraphQL Mutation
In order to change the data that we can query for in a GraphQL Schema, we have to define what is cal ...
- Hook技术简介(有用SDK写的例子)
钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的.当消息到达后,在目标窗口处理函数之前处理它.钩子机 ...
- Django表单上传
任务描述:实现表单提交(上传文件) 1.项目目录: 2.源代码: regist.html <!DOCTYPE html> <html lang="en"> ...
- 【u009】瑞瑞的木板
Time Limit: 1 second Memory Limit: 128 MB [问题描述] 瑞瑞想要亲自修复在他的一个小牧场周围的围栏.他测量栅栏并发现他需要N(1≤N≤20,000)根木板,每 ...
- [Angular] Scrolling the Message List To the Bottom Automatically Using OnChanges
Let's say the message list can Input (messages) from parent component, and what we want to do is whe ...
- Android JNI -基础篇
JNI(Java Native Interface,JAVA本地接口) 可以使Java代码和其他语言写的代码(如C/C++代码)进行交互.为什么要进行交互? 首先,Java语言提供的类库无法满足要求, ...
- 你的薪水增速跑赢GDP了没
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvZm9ydW9r/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/d ...
- oracle中imp导入数据中文乱码问题(转)
(转自 http://blog.chinaunix.net/uid-186064-id-2823338.html) oracle中imp导入数据中文乱码问题 用imp命令向oracle中导入数据后, ...