词法解析器 | 从零实现一门语言
从这一节开始,进行词法解析器的原理分析和代码实现,我们以C的语法为模板,用C++来进行编译器的代码开发,开发语言任意选择自己熟悉的一种都行。
观察以下代码块,分析其结构。
int main(){
int a = 10;
string b = "hello world";
return a;
}
在上一节中我们知道,词法解析就是将源代码中的字符识别成高级语言中定义的各种标识符,如变量和函数。很显然,a、b即为变量,main为函数,int、return为关键字。
常见的词法记号为:标识符(变量/方法)、常量、关键字、运算符、逗号、分号等。
我们的目标是构建一个词法解析器,并输出词法标记的序列,流程如下。
词法解析器可以用状态转换图或者有限自动机的方式实现,我们采用后者中的确定有限自动机,具体定义可以参见编译原理等相关教材。此外也使用正则表达式等工具来构建。
一 定义词法标记
/**
* 标记类型
*/
enum Tag {
ID,//标识符
INT, CHAR, STR, VOID, BOL,//变量类型
NUM,//数字类型
ADD, SUB, MUL, DIV, AND, OR,//运算符
ASSIGN,//赋值符
LPR, RPR, //()
LBK, RBK, //[]
LBE, RBE, //{}
SEM, //;
COMMA, //,
COL, //:
RETURN,
END,
ERO, //错误标记
UKN//未知
};
定义词法标记基类。
/**
* 词法标记基类
*/
class Token {
public:
Tag tag;
Token(Tag t);
virtual string toString();
virtual ~ Token();
};
定义词法标记的各种类型,暂只包括标识符、数字类型、字符串和关键字。
class Id : public Token{
public:
string name;
Id(string name);
virtual string toString();
};
class Num : public Token{
public:
int value;
Num(int v);
virtual string toString();
};
class Str : public Token{
public:
string str;
Str(string str);
virtual string toString();
};
class Keyword {
public:
//初始化所有关键字
std::tr1::unordered_map<string, Tag> keywords;
Keyword();
Tag getTag(string name);
}
二 构造字符扫描器
class Scanner {
public:
FILE *file;//源文件
char scan();//扫描并返回一个字符
Scanner(FILE *file);
~Scanner();
};
可以一次性读取多个,减少操作文件的次数。
//scan()伪码
int readPos = 0;//上次读取的位置
char line[100];//一次读取100个
int lineLen = fread(line, 1, 100, file);//如果line中存在就不用再操作文件
char ch = line[readPos];
readPos ++;
三 构造词法解析器
class Lexer {
public:
FILE *file; //源文件
Scanner &scanner; //扫描器
char ch; //读取的字符
Token* fetch();//有限自动机读取一个词法标记
Lexer(Scanner &scanner);
~ Lexer();
};
有限自动机的实现。
Token *Lexer::fetch() {
//循环读取字符直至结束
for (;ch != -1;) {
Keyword keyword;
while (ch == ' ' || ch == '\n' || ch == '\t') {//空格、换行符、制表符继续读取下一个
ch = scanner.scan();
}
if(ch == -1){//读取完毕,返回结束标记
return new Token(END);
}
if (ch >= 'a' && ch <= 'z') {//识别标识符: 以小写字符开头
return fetchId();
} else if (ch >= '0' && ch <= '9') {//识别数字类型: 以数字开头
return fetchNum();
} else if(ch == '"'){//识别字符串: 以"开头
return fetchStr();
}else {//识别其他符号
return fetchMark();
}
}
return new Token(END);
}
识别标识符。
Token *Lexer::fetchId() {
Keyword keyword;
string id = "";
id.push_back(ch);
while (ch != -1) {
ch = scanner.scan();
//如果读到数字或者下划线,则说明是标识符
if ((ch >= '0' && ch <= '9') || '_' == ch || (ch >= 'a' && ch <= 'z')) {
id.push_back(ch);
} else if(ch == '\n'){//换行符,继续读取下一个
continue;
}else{//否则结束
break;
}
}
Tag tag = keyword.keywords[id];//判断是否为关键字
return tag ? new Token(tag) : new Id(id);
}
我们依次实现ferchNum()、fetchStr()和fetchMark()等函数。注意,数字类型需要先判断其进制。
十进制:以0~9的任意数字组合;八进制:以0开头,0~7的任意数字组合;二进制:以0b开头,0和1的任意组合;十六进制:以0x开头,数字0-9及字母a~z的任意组合。
识别数字类型。
Token* Lexer::fetchNum() {
int num = 0;
int hex = 2; // 0, 二进制;1,八进制;2,十进制;3,十六进制;
char old = ch;
ch = scanner.scan();
char next = ch;
if (old == '0') {
//第一个非零为十进制,通过第二个字符判断具体数值进制
if (ch == 'b') {
hex = 0;
} else if (ch == 'x') {
hex = 3;
} else if (ch = '0' && ch <= '7') {
hex = 1;
num = convertNum(hex, num, ch);
} else if (ch == ' ') {
return new Num(num);
} else if (ch == '\n') {//继续读
} else {
return new Token(ERO);
}
} else {
hex = 2;
//如果第二个为终止符,直接返回;如果为换行符,继续往下读,否则校验值的类型并追加到num中
if (ch == ';') {
return new Num(convertNum(hex, num, old));
}
if (ch != '\n') {
//如果是其他类型且不属于数值,返回错误
Token *t = validNum(hex, ch);
if (t != NULL) {
return t;
}
}
}
//明确数值类型进行初始值计算
num = convertNum(hex, num, old);
while (ch != -1) {
num = convertNum(hex, num, ch);
ch = scanner.scan();
if (ch == ';') {
return new Num(num);
}
Token *t = validNum(hex, ch);
if (t) {
return t;
}
if (ch == '\n') {
continue;
}
if (ch == ' ') {
return new Num(convertNum(hex, num, next));
}
}
return nullptr;
}
将单个字符数字转换成相应进制的具体数值。
int convertNum(int type, int origin, int current) {
if (current == '\n') {
return origin;
}
if (type == 0) {
return origin * 2 + current - '0';
} else if (type == 1) {
return origin * 8 + current - '0';
} else if (type == 2) {
return origin * 10 + current - '0';
} else if (type == 3) {
return origin * 16 + current - '0';
}
return origin;
}
至此,我们完成词法解析器的构造,输出其解析的结果,如下图。
欢迎关注公众号:零点码起。
1.一个hello world的诞生
2.词法解析器
3.从自然语言认识文法
4.构造文法
5.语义分析
6.生成中间代码
7.函数的帧栈调用过程
8.汇编
9.编译和链接
10.终于跑起来了
11.多文件编译
12.丰富数据类型
13.流程控制语句
14.编译优化算法
15.文件读取
16.一个线程的实现
17.什么是锁
18.网络编程
19.面向对象
20.其他规划
欢迎关注公众号:零点码起。
词法解析器 | 从零实现一门语言相关推荐
- PHP的词法解析器:re2c
http://www.phppan.com/2011/09/php-lexical-re2c/ PHP的词法解析器:re2c 胖胖 PHP 2011/09/26 1 条留言 147 views re2 ...
- re2c php,PHP的词法解析器:re2c
出处:http://www.phppan.com/2011/09/php-lexical-re2c/ 作者: 胖胖 re2c是一个扫描器制作工具,可以创建非常快速灵活的扫描器.它可以产生高效代码,基于 ...
- 用 C 语言开发一门编程语言 — 语法解析器
目录 文章目录 目录 前文列表 编程语言的本质 词法分析 语法分析 使用 MPC 解析器组合库 安装 快速入门 实现波兰表达式的语法解析 波兰表达式 正则表达式 代码实现 前文列表 <用 C 语 ...
- 25Interpreter(解析器)模式
技术交流QQ群:1027579432,欢迎你的加入! 1.领域规则模式 在特定领域中,某些变化虽然频繁,但可以抽象为某种规则.这时候,结合特定领域,将问题抽象为语法规则,从而给出在该领域下的一般性解决 ...
- 自己动手实现一个简单的JSON解析器
1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等.在 ...
- 手把手教你实现一个 JSON 解析器!
1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等. 在 ...
- MySQL内核源码解读-SQL解析之解析器浅析
MYSQL服务器接收SQL格式的查询,首先要对sql进行解析,内部将文本格式转换为二进制结构,这个转换就是解析器,解析的目的是为了让优化器更好的处理指令,以便以最优的路径,最少的耗时返回我们想要的结果 ...
- mysql ddl 语法解析工具_sharding-sphere之语法解析器
语法解析器,根据不同类型的语句有不同的语法解析器去解析成成SQLStatement,SQL解析器的类图我用脑图画出来如下: SQLParser.png 可以看到,不同的sql有不同的处理解析器去解析, ...
- json string 格式_自己动手实现一个简单的JSON解析器
作者:田小波 原文:http://cnblogs.com/nullllun/p/8358146.html 1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数 ...
最新文章
- [转]WinForm下Splash(启动画面)制作
- 请求中文乱码_【1】执行Http请求访问网页
- 无服务器计算将会取代容器?
- python 复制、移动文件到指定目录并修改名字
- 2015盘点最佳5笔小投资引援:骑士得枪勇士收肉盾
- 《一》php多进程编程:第一次fork
- NSOperationQueue线程队列完毕finished状态检测
- python数据抓取课程_Python爬虫入门教程 21-100 网易云课堂课程数据抓取
- *【2019牛客暑期多校训练营(第三场)- G】Removing Stones(分治)
- linux 远程赋值,linux 简单远程复制命令scp参数
- bzoj 1565 [NOI2009]植物大战僵尸【tarjan+最大权闭合子图】
- 深度学习推荐模型-DeepCrossing
- 从 VI 编辑器谈操作系统起源及编辑器本质
- python库中文手册_Python3.8.1标准库参考中文手册(The Python Library Reference) 高清pdf版...
- android自定义通知栏_推送图片
- Go语言核心之美 3.3-Map
- Linux遇到Aborted (core dumped)
- 出入库与库存系统的模型问题
- 5110. 近义词句子
- Webdriver - webdriver hangs when get or click
热门文章
- 几十万台 Exchange 服务器已被入侵,修复补丁来了
- 个性化品牌开始繁荣?为设计师和代工厂牵线的平台Maker's Row获得100万美元融资 | 36氪...
- 三步实现自动注册工厂替代switch语句(c++)
- Chrome的一些快捷键
- Java简单的对外接口验签
- 非常值得收藏的书签栏,程序员学习与设计相关的网站(有附件下载)
- 1036:镂空三角形
- 爬虫之爬取易班推文信息
- SuperPoint学习训练纪录 无训练版与带训练版本(一)
- 对数组做交换(swap)会发生什么?