词法分析
Token的几个种类
前端的第一步就是词法分析, 这个过程通俗来讲就是将源代码转化为一串Tokens
. 所以首先应该想到的是, 到底该有哪几种类型的Token
? 关于这个问题我已经想过了, 该语言将会有如下几种Token
.
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, // () {} []
};
你可以看到, 该语言其实只有三种基本类型, 我个人不打算支持bool
因为我感觉bool
底层实现就是整形, 所以并没有支持的必要. 另一方面这里面的int
和float
如果过长的话可能会导致编译出错, 我个人只打算使用C++中的long
和double
来解析这两个类型, 所以其实他们的长度也是有限制的. 另外KEYWORD
在分析阶段会归入IDENTIFIER
中, 等到真正构造Token的时候才会划分开来, 这样可以降低词法分析的难度.暂时的话我就想到这几种, 如果不够的话再临时加上吧.
Token的属性以及构造函数
接下来我们可以整体看一下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;
union Value{
long l;
double d;
std::string s;
};
Value value;
public:
Token(Token_Type type, const std::string string): type(type) {
switch (type){
case INT:{
value.l = parseInt(string);
break;
}
case FLOAT:{
value.d = parseFloat(string);
break;
}
case IDENTIFIER:{
if(isKeyword(string)){
type = IDENTIFIER;
}
//no break here, auto jump into default
}
default:{
value.s = string;
}
}
}
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 Value& getValue() const { return value; }
private:
long parseInt(const std::string&);
long parseFloat(const std::string&);
bool isKeyword(const std::string&);
};
#endif //FRED_TOKEN_H
- 存
Token
的值之所以使用了union
类型, 是因为对于int
和float
, 这里实际上只需要关注他们的真实值
这里使用的是long
和double
来表示, 但是对于其他类型则仍然选择用字符串表示. 我也考虑过在Token
上衍生出两个子类的做法, 但是考虑到子类getValue
返回值不统一的情况, 所以这里只能选择用union
, 这里我 自己也感觉有点别扭, 所以如果有更好的设计方式或者想法欢迎留言. - 另外你可以看到, 正如我前面所说的我是在构造函数中将
KEYWORD
从IDENTIFIER
中分离出来的思路, 这样可以简化分析.
完成了这些之后另外三个工具函数很简单 :
#include "Token.h"
#include <cmath>
long Token::parseHelper(const std::string& str, size_t& idx){
long sum = 0;
for(size_t i = 0; i != idx; ++i){
if(str[i] == '.'){
idx = i;
return sum;
}
sum = sum * 10 + (str[i] - '0');
}
return sum;
}
long Token::parseInt(const std::string& str){
size_t len = str.length();
return parseHelper(str, len);
}
double Token::parseFloat(const std::string& str){
double sum = 0;
size_t len = str.length(), idx = len;
sum += parseHelper(str, idx);
len -= ++idx;
sum += parseHelper(str.substr(idx), len) / pow(10, len);
return sum;
}
inline bool Token::isKeyword(const std::string& str) {
return str == "int" ||
str == "float" ||
str == "string" ||
str == "print" ||
str == "while" ||
str == "for" ||
str == "class" ||
str == "return";
}