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 ...
随机推荐
- Vue学习之--------全局事件总线(2022/8/22)
文章目录 1.全局事件总线基础知识(GlobalEventBus) 2.图解过程 3.代码实例 3.1 main.js 3.1 App.vue 3.2 School.vue 3.3 Student.v ...
- 齐博x1服务器性能太差,调整系统升级每次校验的文件数
系统升级需要校验本地的文件是否被修改过,系统默认每次检验1千个文件,一般来说需要分四到五页来处理,如下图所示. 如果你的服务器性能太差的话,就需要手工把数值调小.把下面的代码复制出来.进入后台数据库管 ...
- go-zero docker-compose 搭建课件服务(九):http统一返回和集成日志服务
0.索引 go-zero docker-compose 搭建课件服务(九):http统一返回和集成日志服务 0.1源码地址 https://github.com/liuyuede123/go-zero ...
- go-zero docker-compose 搭建课件服务(五):完善user服务
0.转载 go-zero docker-compose 搭建课件服务(五):完善user服务 0.1源码地址 https://github.com/liuyuede123/go-zero-course ...
- C语言表白窗口程序
#include<windows.h> #include<stdio.h> #include<string.h> int main() { char modeCom ...
- 11.-ORM-基本操作-创建数据
一.ORM-操作 基本操作包括增删改查,即(CRUD)操作 CRUD是指在做计算处理时增加(create).读取查询(read).更新(update).删除(delete) ORM CRUD 核心 - ...
- C++算法之旅、02 从木棒切割问题领悟二分法精髓
172.木棒切割问题 https://sunnywhy.com/problem/172 题目描述 给出n根木棒的长度,现在希望通过切割它们来得到至少k段长度相等的木棒(长度必须是整数),问这些长度相等 ...
- Python基础之函数:1、函数的介绍及名称空间
目录 一.函数 1.什么是函数 2.函数的语法结构 3.函数的定义与调用 4.函数的分类 5.函数的返回值 6.函数的参数 二.函数参数 1.位置参数 2.默认参数 3.可变长参数 1.一个*号 2. ...
- C++实现真值表
这一片文章主要是关于真值表,在完成之前我也遇到了许多问题.比如怎么去求解表达式的值,怎么去将每个变量进行赋值,也就是如何 将n个字符进行01全排列. 01全排列真的神奇,01全排列其实就是2^n.他可 ...
- Guess Next Session
打开又是一个输入框的界面,点一下下面的看源码 很简短的一个源码 大概意思是如果password等于session[password]就输出flag 直接搜了下session函数的漏洞,发现sessio ...