ACWJ_00扫描器
第一部分:词法扫描介绍
我们从一个简单的词汇扫描器开始我们的编译器编写之旅。正如我在之前部分所提到的,扫描器的任务是从输入语言中(用来编译的语句)识别词法元素或者是符号。
我们将定义一个只有5种词法元素的输入语言:
- 四个基本的数学符号:
*,/,+和- - 有1个或者多个数字的十进制数字
0..9
我们所要扫描的每一个符号将会被存放于以下的结构中(来自defs.h)
// Token structure
struct token {
int token;
int intvalue;
};
其中的token域可以是下列枚举结构中的任一个(来自defs.h)
// Tokens
enum {
T_PLUS, T_MINUS, T_STAR, T_SLASH, T_INTLIT
};
当符号是一个T_INTLIT(一个整数符), 那intvalue变量将会存放我们所扫到的这个整数值
scan.c中的函数
scan.c文件中有着词法扫描器的函数代码。我们将从输入文件中一次读入一个字符。然而,在实际情况中会多次遇到我们从输入流里多读了一个字符需要将他“放回去”的情况(这里作者用的一个Putback全局变量时刻保存着多读出的那个字符)。我们同样也希望跟踪我们目前读到了输入文件的第几行,如此我们可以在我们的调试信息中打印出来具体的行号。所有上述的这些都在函数next()中得以完成。
// Get the next character from the input file.
static int next(void) {
int c;
if (Putback) { // Use the character put
c = Putback; // back if there is one
Putback = 0;
return c;
}
c = fgetc(Infile); // Read from input file
if ('\n' == c)
Line++; // Increment line count
return c;
}
Putback、Line和全局输入文件指针变量都被定义在 data.h头文件中。
extern_ int Line;
extern_ int Putback;
extern_ FILE *Infile;
所有声明了extern_宏的C文件都将能够使用上面的这些变量
最后,我们如何把一个多读出来的字符放回到输入流中呢?像这样:
// Put back an unwanted character
static void putback(int c) {
Putback = c;
}
忽略空白字符
我们需要一个函数去读取并且悄悄地跳过所有空格字符直到读到了一个非空格字符并且将其返回,像下面这样:
// Skip past input that we don't need to deal with,
// i.e. whitespace, newlines. Return the first
// character we do need to deal with.
static int skip(void) {
int c;
c = next();
while (' ' == c || '\t' == c || '\n' == c || '\r' == c || '\f' == c) {
c = next();
}
return (c);
}
扫描符号:scan()
那现在我们可以读取字符并且同时跳过输入流里的空格字符。当我们超读了一个字符的时候,也可以将其放回去。现在我们可以编写我们的第一个词法扫描器如下:
// Scan and return the next token found in the input.
// Return 1 if token valid, 0 if no tokens left.
int scan(struct token *t) {
int c;
// Skip whitespace
c = skip();
// Determine the token based on
// the input character
switch (c) {
case EOF:
return (0);
case '+':
t->token = T_PLUS;
break;
case '-':
t->token = T_MINUS;
break;
case '*':
t->token = T_STAR;
break;
case '/':
t->token = T_SLASH;
break;
default:
// More here soon
}
// We found a token
return (1);
}
这就是简单的单字符处理:对于每一个所识别到的字符,将其转化为token结构体变量的token对应成员。你可能会问:为什么不直接把识别到的字符放入struct token中当作成员呢?答案是之后我们会需要去识别多字符符号比如==和if 以及while关键字。所以说用枚举列表去列出符号值会比较省力一些。
整数数值
事实上,我们不得不面对这样的情况:去识别诸如3827 和87731这样的整数数值。下面是上述代码块switch里default处缺失的代码处理:
default:
// If it's a digit, scan the
// literal integer value in
if (isdigit(c)) {
t->intvalue = scanint(c);
t->token = T_INTLIT;
break;
}
printf("Unrecognised character %c on line %d\n", c, Line);
exit(1);
当我们击中一个整数字符的时候,我们调用辅助函数 scanint()处理。它将会返回被扫描的整数数值。要做到这一点,他需要依次读取从这个数字开始后面的每一个字符,检查它们是否是合法的数字,并且组建好最终的数值返回,下面是实现:
// Scan and return an integer literal
// value from the input file. Store
// the value as a string in Text.
static int scanint(int c) {
int k, val = 0;
// Convert each character into an int value
while ((k = chrpos("0123456789", c)) >= 0) {
val = val * 10 + k;
c = next();
}
// We hit a non-integer character, put it back.
putback(c);
return val;
}
我们把val 值初始化为0。每次我们获取到一个09的数字字符,我们用函数`chrpos()`将它转换为`int`值。我们把`val`值乘以10然后再加上它在09序列中的位置,也就是它自己实际值。
比如说,如果我们有这三个连续的字符读取3, 2, 8,我们这样做:
val= 0 * 10 + 3, i.e. 3val= 3 * 10 + 2, i.e. 32val= 32 * 10 + 8, i.e. 328
在上述代码的最后部分,你有没有发现putback(c)的调用?程序走到这里的时候我们发现一个字符并不是十进制数子。我们不能简单地将它直接抛弃,幸运的是,我们可以将它放回源输入中供以后使用。
你可能在这个时候也会问:为什么不简单地把每一个输入字符减去对应的'0'的ASCII码值来得到他的整数值呢?答案是,之后我们可能也会使用chrpos("0123456789abcdef") 这样的调用去转换十六进制数字。(09的ASCII码和af的可差得远呢)
下面是函数chrpos()的实现:
// Return the position of character c
// in string s, or -1 if c not found
static int chrpos(char *s, int c) {
char *p;
p = strchr(s, c);
return (p ? p - s : -1);
}
这是目前针对词法扫描器章节的scan.c中的实现。
让扫描器工作起来
main.c 中的代码让上述的扫描器开始工作起来。main.() 函数会打开一个文件并且扫描其中的符号。
void main(int argc, char *argv[]) {
...
init();
...
Infile = fopen(argv[1], "r");
...
scanfile();
exit(0);
}
并且scanfile() 函数中有个循环不停地读取新符号,并将他的详细信息打印出来。
// List of printable tokens
char *tokstr[] = { "+", "-", "*", "/", "intlit" };
// Loop scanning in all the tokens in the input file.
// Print out details of each token found.
static void scanfile() {
struct token T;
while (scan(&T)) {
printf("Token %s", tokstr[T.token]);
if (T.token == T_INTLIT)
printf(", value %d", T.intvalue);
printf("\n");
}
}
一些输入例子文件
我提供了一些输入文件的例子便于你们去观察发现扫描器在每个文件中获取到了哪些符号,并且观察扫描器具体拒绝了哪些输入格式的文件。
$ make
cc -o scanner -g main.c scan.c
$ cat input01
2 + 3 * 5 - 8 / 3
$ ./scanner input01
Token intlit, value 2
Token +
Token intlit, value 3
Token *
Token intlit, value 5
Token -
Token intlit, value 8
Token /
Token intlit, value 3
$ cat input04
23 +
18 -
45.6 * 2
/ 18
$ ./scanner input04
Token intlit, value 23
Token +
Token intlit, value 18
Token -
Token intlit, value 45
Unrecognised character . on line 3
总结和展望
我们向前迈进了一小步,并且我们有了一个简单的词法扫描器,可以识别四个主要的数学符号和整数数字。我们注意到了我们需要跳过输入流里的空白字符和将超读的字符放回输入流。
单字符符号很容易扫描,但是多字符连在一起的符号就有一点难度了。但是在最后, scan()函数返回了输入流中的下一个字符存储于一个传入的struct token参数变量中。
struct token {
int token;
int intvalue;
};
在编译器编写旅程中的下一章节,我们会编写一个递归下降分析器去翻译我们输入文件里的语法,并且计算和打印每个文件里的表达式的最终的值。
ACWJ_00扫描器的更多相关文章
- SNMP高速扫描器braa
SNMP高速扫描器braa SNMP(Simple Network Monitoring Protocol,简单网络管理协议)是网络设备管理标准协议.为了便于设备管理,现在联入网络的智能设备都支持 ...
- Python3实现TCP端口扫描器
本文来自 高海峰对 玄魂工作室 的投稿 作者:高海峰 QQ:543589796 在渗透测试的初步阶段通常我们都需要对攻击目标进行信息搜集,而端口扫描就是信息搜集中至关重要的一个步骤.通过端口扫描我们可 ...
- Atitit 图像扫描器---基于扫描线
Atitit 图像扫描器---基于扫描线 调用范例 * @throws FileExistEx */ public static void main(String[] args) throws Fil ...
- Atitit 图像处理 公共模块 矩阵扫描器
Atitit 图像处理 公共模块 矩阵扫描器 1.1. 调用说明对矩阵像素遍历处理调用1 2. 矩阵扫描器主题结构1 2.1. 主要说明 从像素点开始填充矩阵1 2.2. 得到模板中心点所对应的图像坐 ...
- qqzoneQQ空间漏洞扫描器的设计attilax总结
qqzoneQQ空间漏洞扫描器的设计attilax总结 1.1. 获取对方qq(第三方,以及其他机制)1 1.2. QQ空间的html流程1 1.3. 判断是否有权限1 1.4. 2015年度Web服 ...
- Python与Hack之window下运行带参数的Python脚本,实现一个简单的端口扫描器
1.前提是:windows已经配置好Python的环境变量: 2.进入cmd命令行模式: **输入python命令,检测是否环境配置好:显示这样说明配置环境变量没问题 **用cd命令进入Python脚 ...
- Spring利器之包扫描器
在学习Spring这门技术中为了大大减少applicationContext.xml配置的代码量于是有了包扫描器. 闲话不多说我们马上来实现一下吧 示例架构如下: 第一步我们先来修改我们的配置appl ...
- 端口扫描器——ZenmapKail Linux渗透测
3.3 端口扫描器——ZenmapKail Linux渗透测 Zenmap(端口扫描器)是一个开放源代码的网络探测和安全审核的工具.它是Nmap安全扫描工具的图形界面前端,它可以支持跨平台.使用Z ...
- 达内培训:php在线端口扫描器
达内培训:php在线端口扫描器 [来源] 达内 [编辑] 达内 [时间]2012-12-21 这个扫描器很简单.就是用了一个数组来定义端口的相关信息,原理就是用fsockopen函数连接,如 ...
- 互联网扫描器 ZMap 完全手册
初识 ZMap ZMap被设计用来针对整个IPv4地址空间或其中的大部分实施综合扫描的工具.ZMap是研究者手中的利器,但在运行ZMap时,请注意,您很有 可能正在以每秒140万个包的速度扫描整个IP ...
随机推荐
- HTML基础知识(1)常用标签的使用 h、p、img、meta、a、iframe...
文章目录 1.html简介 2.html注释 3.标签的属性 3.1 代码 3.2 测试结果 4.常用的标签 4.1 代码 4.2 测试结果 5.实体 5.1 代码 5.2 测试结果 6.图片引入 6 ...
- 网络工程知识(二)VLAN的基础和配置:802.1q帧;Access、Trunk、Hybrid接口工作模式过程与配置;VLANIF的小实验
介绍-VLAN VLAN(Virtual Local Area Network)即虚拟局域网,工作在数据链路层. 交换机将通过:接口.MAC.基于子网.协议划分(IPv4和IPv6).基于策略的方式划 ...
- 45.限流Throttling及源码解析
什么是限流? 限流类似于权限机制,它也决定是否接受当前请求,用于控制客户端在某段时间内允许向API发出请求的次数,也就是频率 假设有客户端(比如爬虫程序)短时间发起大量请求,超过了服务器能够处理的能力 ...
- csharp 基础
1.C#基础 1.1 C#简介 C#是一个面向对象的.由微软开发的高级编程语言,它专门为公共语言基础结构(CLI)设计的,CLI是由可执行代码和运行时环境组成的. C#语言在编写时有点像JAVA,在运 ...
- 【深入浅出 Yarn 架构与实现】2-3 Yarn 基础库 - 服务库与事件库
一个庞大的分布式系统,各个组件间是如何协调工作的?组件是如何解耦的?线程运行如何更高效,减少阻塞带来的低效问题?本节将对 Yarn 的服务库和事件库进行介绍,看看 Yarn 是如何解决这些问题的. 一 ...
- 2022春每日一题:Day 22
题目:[HAOI2008]糖果传递 光看题几乎没有思路,但是显然到最后每个人手中一定有 d=s/n个糖果(s为所有人糖果总和),不妨设2号给1号x2个糖果,3号给2号x3个.....1号给n号x1个, ...
- 2022春每日一题:Day 19
题目:吃奶酪 状压dp实现,dp[i][j]表示走过状态i,停到了j的位置的最小价值.枚举状态,起点终点,转移dp[i][j]=min{dp[i-(1<<s)][k]+dis(s,k)} ...
- c#对接每人计平台获取数据
使用c#对接到晓舟科技的客流统计设备.那么需要先注册一个平台的账号 地址:http://mrd.meirenji.cn/login;JSESSIONID=323cbd18-29ed-4232-8c04 ...
- C温故补缺(十六):未定义行为
未定义行为 在计算机程序设计中,未定义行为是指执行某种计算机代码 所产生的结果,这种代码在当前程序状态下的行为在其所使用的语言标准中没有规定. 以C语言为例,未定义行为指C语言标准未作规定的行为,同时 ...
- C温故补缺(十五):栈帧
栈帧 概念 栈帧:也叫过程活动记录,是编译器用来实现过程/函数调用的一种数据结构,每次函数的调用,都会在调用栈(call stack)上维护一个独立的栈帧(stack frame) 栈帧的内容 函数的 ...