第一部分:词法扫描介绍

​ 我们从一个简单的词汇扫描器开始我们的编译器编写之旅。正如我在之前部分所提到的,扫描器的任务是从输入语言中(用来编译的语句)识别词法元素或者是符号。

​ 我们将定义一个只有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;
}

PutbackLine和全局输入文件指针变量都被定义在 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关键字。所以说用枚举列表去列出符号值会比较省力一些。

整数数值

​ 事实上,我们不得不面对这样的情况:去识别诸如382787731这样的整数数值。下面是上述代码块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. 3
  • val= 3 * 10 + 2, i.e. 32
  • val= 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扫描器的更多相关文章

  1. SNMP高速扫描器braa

    SNMP高速扫描器braa   SNMP(Simple Network Monitoring Protocol,简单网络管理协议)是网络设备管理标准协议.为了便于设备管理,现在联入网络的智能设备都支持 ...

  2. Python3实现TCP端口扫描器

    本文来自 高海峰对 玄魂工作室 的投稿 作者:高海峰 QQ:543589796 在渗透测试的初步阶段通常我们都需要对攻击目标进行信息搜集,而端口扫描就是信息搜集中至关重要的一个步骤.通过端口扫描我们可 ...

  3. Atitit 图像扫描器---基于扫描线

    Atitit 图像扫描器---基于扫描线 调用范例 * @throws FileExistEx */ public static void main(String[] args) throws Fil ...

  4. Atitit 图像处理 公共模块 矩阵扫描器

    Atitit 图像处理 公共模块 矩阵扫描器 1.1. 调用说明对矩阵像素遍历处理调用1 2. 矩阵扫描器主题结构1 2.1. 主要说明 从像素点开始填充矩阵1 2.2. 得到模板中心点所对应的图像坐 ...

  5. qqzoneQQ空间漏洞扫描器的设计attilax总结

    qqzoneQQ空间漏洞扫描器的设计attilax总结 1.1. 获取对方qq(第三方,以及其他机制)1 1.2. QQ空间的html流程1 1.3. 判断是否有权限1 1.4. 2015年度Web服 ...

  6. Python与Hack之window下运行带参数的Python脚本,实现一个简单的端口扫描器

    1.前提是:windows已经配置好Python的环境变量: 2.进入cmd命令行模式: **输入python命令,检测是否环境配置好:显示这样说明配置环境变量没问题 **用cd命令进入Python脚 ...

  7. Spring利器之包扫描器

    在学习Spring这门技术中为了大大减少applicationContext.xml配置的代码量于是有了包扫描器. 闲话不多说我们马上来实现一下吧 示例架构如下: 第一步我们先来修改我们的配置appl ...

  8. 端口扫描器——ZenmapKail Linux渗透测

    3.3  端口扫描器——ZenmapKail Linux渗透测​ Zenmap(端口扫描器)是一个开放源代码的网络探测和安全审核的工具.它是Nmap安全扫描工具的图形界面前端,它可以支持跨平台.使用Z ...

  9. 达内培训:php在线端口扫描器

    达内培训:php在线端口扫描器 [来源] 达内    [编辑] 达内   [时间]2012-12-21 这个扫描器很简单.就是用了一个数组来定义端口的相关信息,原理就是用fsockopen函数连接,如 ...

  10. 互联网扫描器 ZMap 完全手册

    初识 ZMap ZMap被设计用来针对整个IPv4地址空间或其中的大部分实施综合扫描的工具.ZMap是研究者手中的利器,但在运行ZMap时,请注意,您很有 可能正在以每秒140万个包的速度扫描整个IP ...

随机推荐

  1. 【Firefox浏览器】关闭触摸板双指滑动进行前进后退的功能

    痛点 本以为只是Chrome浏览器存在这一奇葩功能,没成想Firefox也沦陷了!有好一阵子在使用Firefox的时候,并未发现其存在这个功能.直到有一天,打开自己的博客,翻阅上篇< [Chro ...

  2. JUC(1)线程和进程、并发和并行、线程的状态、lock锁、生产者和消费者问题

    1.线程和进程 进程:一个程序,微信.qq...程序的集合.(一个进程包含多个线程,至少包含一个线程.java默认有两个线程:主线程(main).垃圾回收线程(GC) 线程:runnable.thre ...

  3. Codeforces 1684 E. MEX vs DIFF

    题意 给你n个非负整数的数列a,你可以进行K次操作,每次操作可以将任意位置的数数更改成任意一个非负整数,求操作以后,DIFF(a)-MEX(a)的最小值:DIFF代表数组中数的种类.MEX代表数组中未 ...

  4. app自动化测试环境安装

    一.环境依赖 app自动化测试环境如下: appium服务 第三方库appium-python-client 手机或模拟器 java环境jdk1.8 android环境 二.appium服务安装 1. ...

  5. 16.-admin管理后台

    一.admin管理后台 Django提供给了比较完善的后台管理数据库接口,可供开发过程中调用和测试使用 Django会搜集所有已注册的模型类,为这些模型类提供数据管理界面,供开发者使用   命令:py ...

  6. String基础: String两种创建对象方式的比较

    字符串常量 在一般的语言中常量一旦声明则不可改变,在java中的字符串常量是以匿名对象来表示的 javaz中字符串两种定义方法: String strA= new String("hello ...

  7. 聊聊GPU与CPU的区别

    目录 前言 CPU是什么? GPU是什么? GPU与CPU的区别 GPU的由来 并行计算 GPU架构优化 GPU和CPU的应用场景 作者:小牛呼噜噜 | https://xiaoniuhululu.c ...

  8. <五>关于类的各类成员

    类的各种成员-> 成员方法 & 成员变量 普通的成员方法=>编译器会添加一个this形参变量 1:属于类的作用域 2:调用该方法时,需要依赖一个对象,而且常对象不能调 3:可以任意 ...

  9. springboot使用jira-rest-java-client-api集成jira,自定义对查询board和sprint的支持

    公司内部使用jira作项目管理,我接到新的需求,要在测试报告上获取jira的所有项目,再根据项目获取board看板,再根据看板获取Sprint,最后获取未完成的bug信息.效果如下: 第一次接入jir ...

  10. K8S节点选择器案例

    #给节点打上标签 [root@lecode-k8s-master deployment]# kubectl label no lecode-dev-001 hostname=lecode-dev-00 ...