本节我们先从一个简易的可以识别四则运算和整数值的词法分析扫描器开始。它实现的功能也很简单,就是读取我们给定的文件,并识别出文件中的token将其输出。

这个简易的扫描器支持的词法元素只有五个:

  • 四个基本的算术运算符:+-*/
  • 十进制整数

我们需要事先定义好每一个token,使用枚举类型来表示:

//defs.h

// Tokens
enum {
T_PLUS, T_MINUS, T_STAR, T_SLASH, T_INTLIT
};

在扫描到token后将其存储在一个如下的结构体中,当标记是 T_INTLIT(即整数文字)时,该intvalue 字段将保存我们扫描的整数值:

//defs.h

// Token structure
struct token {
int token;
int intvalue;
};

我们现在假定有一个文件,其内部的的代码就是一个四则运算表达式:

2 + 34 * 5 - 8 / 3

我们要实现的是读取他的每一个有效字符并输出,就像这样:

Token intlit, value 2
Token +
Token intlit, value 34
Token *
Token intlit, value 5
Token -
Token intlit, value 8
Token /
Token intlit, value 3

我们看到了最终要实现的目标,让我们来一步步分析需要的功能。

  1. 首先我们需要一个逐字符的读出文件中的内容并返回的函数。当我们在输入流中读的太远时,需要将读取到的字符放回(如上例当读到数字时,因无法直接获取数字是否结束,只能循环读取,当读到第一个非数字字符时则判定该十进制数读取结束,需将该十进制数返回并将读取的非数字字符放回),记录行号的的功能也是在这里实现。
// 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;
}
  1. 我们只需要有效字符,所以需要去除空白字符的功能
// 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);
}
  1. 当读到的是数字的时候,怎么确定数字有多少位呢?所以我们需要一个专门处理数字的函数。
// 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 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;
}

所以现在我们可以在跳过空格的同时读取字符;如果我们读到一个字符太远,我们也可以放回一个字符。我们现在可以编写我们的第一个词法扫描器:

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: // 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);
}
// We found a token
return (1);
}

现在我们可以读取token并将其返回。

main() 函数打开一个文件,然后扫描它的令牌:

void main(int argc, char *argv[]) {
...
init();
...
Infile = fopen(argv[1], "r");
...
scanfile();
exit(0);
}

scanfile()在有新token时循环并打印出token的详细信息:

// 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");
}
}

我们本节的内容就到此为止。下一部分中,我们将构建一个解析器来解释我们输入文件的语法,并计算并打印出每个文件的最终值。

本文Github地址https://github.com/Shaw9379/acwj/tree/master/01_Scanner

C语言编译器开发之旅(一):词法分析扫描器的更多相关文章

  1. C语言编译器开发之旅(开篇)

    编译器写作之旅   最近在Github上看到一个十分有趣的项目acwj(A Compiler Writing Journey),一个用C语言编写编译器的项目.身为一个程序员,这在我看来是一件十分酷的事 ...

  2. C语言编译器开发之旅(二):解析器

    本节是我们这个编译器系列的第二节,进入语法分析与语义分析的部分解.在本节我们会编写一个简单的解析器. 解析器的主要功能分为两个部分: 识别输入的语法元素生成AST(Abstract Syntax Tr ...

  3. JVM 平台上的各种语言的开发指南

    JVM 平台上的各种语言的开发指南 为什么我们需要如此多的JVM语言? 在2013年你可以有50中JVM语言的选择来用于你的下一个项目.尽管你可以说出一大打的名字,你会准备为你的下一个项目选择一种新的 ...

  4. 第一个C语言编译器是怎样编写的?

    首先向C语言之父Dennis MacAlistair Ritchie致敬! 当今几乎所有的实用的编译器/解释器(以下统称编译器)都是用C语言编写的,有一些语言比如Clojure,Jython等是基于J ...

  5. 【转】自己动手写SC语言编译器

    自序 编译原理与技术的一整套理论在整个计算机科学领域占有相当重要的地位,学习它对程序设计人员有很大的帮助.我们考究历史会发现那些人人称颂的程序设 计大师都是编译领域的高手,像写出BASIC语言的BIL ...

  6. 嵌入式C语言编译器

    GCC与gcc: 初识编译器: 扩展问题: 如何理解“多语言混合开发”? 参考: 狄泰软件学院唐佐林视频教程

  7. 跨平台、跨语言应用开发,Elements 介绍

    目录 1,Elements 介绍 2,Elements 版本 3,Elements 能干嘛 4,Elements  IDES 5,Elements 工具 1,Elements 介绍 RemObject ...

  8. C语言编译器和IDE的选择

    什么是编译器: CPU只认识几百个二进制形式的指令,C语言对CPU而言简直就是天书.C语言是用固定的词汇与格式组织起来,简单直观,程序员容易识别和理解. 这时候就需要一个工具,将C语言代码转换成CPU ...

  9. 李洪强漫谈iOS开发[C语言-003]-开发概述程序设计语言

    李洪强iOS开发之程序设计语言 printf 是打印的意思- 格式化输出 f: format 格式化 C语言编译器 编译器的功能就是将高级语言的源代码,翻译成机器可以识别的二进制文件就是可执 行文件- ...

随机推荐

  1. 前端进阶(2)使用fetch/axios时, 如何取消http请求

    前端进阶(2)使用fetch/axios时, 如何取消http请求 1. 需求 现在前端都是SPA,我们什么时候需要取消HTTP请求呢? 当我们从一个页面跳转到另外一个页面时,如果前一个页面的请求还没 ...

  2. 【Nginx(三)】Nginx配置集群 负载均衡策略

    Nginx配置集群 负载均衡策略 一.安装环境 1.安装JDK8的环境,配置JDK8的环境变量 2.上传jar包demo-1.jar 和 demo-2.jar demo-1.jar 监听8080端口; ...

  3. 9.PHP文件处理

    PHP文件系统 (当成是扩展C++来看就行了,几乎一样): 读取整个文件readfile() .file(). file_get_contents() <?php    readfile('fi ...

  4. Win64 驱动内核编程-21.DKOM隐藏和保护进程

    DKOM隐藏和保护进程 主要就是操作链表,以及修改节点内容. DKOM 隐藏进程和保护进程的本质是操作 EPROCESS 结构体,不同的系统用的时候注意查下相关定义,确定下偏移,下面的数据是以win7 ...

  5. @RequestParam、@PathVariable、 @RequestBody用法

    Get和Post请求 get方式的请求是没有请求体的.但是get有query string parameter .比如url?name=zhangsan post请求发现了两种请求体.一种是FromD ...

  6. CAS指令

    原文链接:https://www.jianshu.com/p/00edb3d74a33   CAS是CPU的一条指令,其具有原子性,原子性是由CPU硬件层面保证的.   CAS原语有三个操作数--内存 ...

  7. Codeforces Round #712 (Div. 2)

    A. Déjà Vu 题意:就是问能否加上字母a,使得字符串不中心对称 思路:只有一种情况不能加入,就是全部是a,剩下的都可以满足,找a的位置就找哪个字母不是a,然后让它的对称位置是新加的这个a 代码 ...

  8. Educational Codeforces Round 92 (Rated for Div. 2)

    A.LCM Problem 题意:最小公倍数LCM(x,y),处于[l,r]之间,并且x,y也处于[l,r]之间,给出l,r找出x,y; 思路:里面最小的最小公倍数就是基于l左端点的,而那个最小公倍数 ...

  9. JS实现单例模式的多种方案

    JS实现单例模式的多种方案 今天在复习设计模式中的-创建型模式,发现JS实现单例模式的方案有很多种,稍加总结了一下,列出了如下的6种方式与大家分享 大体上将内容分为了ES5(Function)与ES6 ...

  10. ALPHA任务拆解

    项目 内容 这个作业属于哪个课程 BUAA2020软件工程 这个作业的要求在哪里 作业要求 我们在这个课程的目标是 学会团队合作,共同开发一个完整的项目 这个作业在哪个具体方面帮助我们实现目标 团队任 ...