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
unionmember has a non trivial special member function, the compiler will not generate the equivalent member function for theunionand 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 ...
随机推荐
- JavaScript实现form表单的多文件上传
form表单的多文件上传,具体内容如下 formData对象可以使用一系列的键值对来模拟一个完整的表单,然后使用Ajax来发送这个表单 使用<form>表单初始化FormData对象的方式 ...
- 历届图灵奖 (Turing award)得奖名单
历届图灵奖 (Turing award)得奖名单 一.总结 一句话总结:各个方面都有. 二.历届图灵奖 (Turing award)得奖名单 Turing奖最早设立于1966年,是美国计算机协会在计算 ...
- idea中建立一个OSGI项目
参考网址:http://chenjingbo.iteye.com/blog/1893597 首先我使用的是equinox作为我的osgi framework 直接在官网上解压下载即可,第一步creta ...
- C# Tuple VS ValueTuple
C# Tuple VS ValueTuple(元组类 VS 值元组) C# 7.0已经出来一段时间了,大家都知道新特性里面有个对元组的优化:ValueTuple.这里利用详尽的例子详解Tuple VS ...
- WPF 针对数据源某个属性进行排序
原文:WPF 针对数据源某个属性进行排序 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/wanlong360599336/article/detai ...
- 【BZOJ 1016】[JSOI2008]最小生成树计数(搜索+克鲁斯卡尔)
[题目链接]:http://www.lydsy.com/JudgeOnline/problem.php?id=1016 [题意] [题解] /* 两个最小生成树T和T'; 它们各个边权的边的数目肯定是 ...
- Swift语言实现代理传值
需求:利用代理实现反响传值(下面样例採用点击第二个视图控制器中的button来改变第一个视图控制器中的Label的内容) 一.创建RootViewController import Foundatio ...
- 微信小程序的轮播图swiper问题
微信小程序的轮播图swiper,调用后,怎样覆盖系统的 点,达到自己想要的效果 不多说,先上一图望大家多给意见: 这个是效果图: 微信小程序效果图就成这样子: <view class=" ...
- View的绘制顺序
1.写在 super.onDraw() 的下面 把绘制代码写在 super.onDraw() 的下面,由于绘制代码会在原有内容绘制结束之后才执行,所以绘制内容就会盖住控件原来的内容. 2.写在 sup ...
- CentOS下Apache的停止和卸载
昨晚搞到一台全球性价比最高的服务器,折腾一晚上,好不容易把node服务开启了,结果访问不了我的网站!!! 访问我的网站,显示的是一个Apache欢迎页面.我想,是不是像之前那样,80端口没有开放,然后 ...