本节我们先从一个简易的可以识别四则运算和整数值的词法分析扫描器开始。它实现的功能也很简单,就是读取我们给定的文件,并识别出文件中的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. 基于MATLAB的手写公式识别(8)

    从一个无知角落里开始,蹒跚学步,一个未知到另一个未知,在跌跌撞撞中越走越快,越走越远,最后宇宙也为之开源.对于探索者来说,最后他们的思想总是变得和自己的足迹一样伟大.   1.图像的预处理 1.1图像 ...

  2. 1.7.1- HTML表格table

    存在即是合理的,表格的是一种常用的标签,不是用来布局,常见是用处理 适合用表格table的地方: 创建表格:

  3. 功能:SpringBoot日志配置详情

    SpringBoot日志配置详情 一.介绍 在所有的项目中,日志是必不可少的,为了高效清晰的查找日志,可以配置日志输出的等级和格式. 在配置后,可以自定义输出日志到指定目录,可以按照天数来分割日志,可 ...

  4. hdu1530 最大团简单题目

    题意:       给你一个无向图,让你找到这个图里面的最大团是多少. 思路:       最大图案是NP问题,直接暴力搜索,如果当前的这个点可以加入当前最大团,那么就选择加入或者舍去,如果不能加入, ...

  5. POJ1325二分匹配或者DINIC(最小路径覆盖)

    题意:        有k个任务,两个机器,第一个机器有n个模式,第二个机器有m个模式,每个任务要么在第一个机器的一个模式下工作,要么在第二个机器的一个模式下工作,机器每切换一个模式需要重启一次,两个 ...

  6. SSH后门万能密码

    当我们在获得一台Linux服务器的 root 权限后,我们第一想做的就是如何维持这个权限,维持权限肯定想到的就是在目标服务器留下一个后门.但是留普通后门,肯定很容易被发现.我们今天要讲的就是留一个SS ...

  7. windows同时安装jdk7和jdk8

    windows同时安装jdk7和jdk8 我本地的情况是本地安装了jdk8,但是因为项目的需要,将tomcat9换成tomcat8,即jdk8换成jdk7(但是好像也可以不用换,因为 7 and la ...

  8. 学javaweb 先学Servlet 应用理论很重要

    package cn.Reapsun.servlet; import java.io.IOException; import java.io.PrintWriter; import javax.ser ...

  9. 射线与空间内三角形的相交检测算法(Möller-Trumbore)的推导与实践

    背景介绍(学习算法之前需要先了解) 射线与空间内三角形的相交检测是游戏程序设计中一个常见的问题,最典型的应用就是拾取(Picking),本文介绍一个最常见的方法,这个方法也是DirectX中采用的方法 ...

  10. C++ primer plus读书笔记——第10章 对象和类

    第10章 对象和类 1. 基本类型完成了三项工作: 决定数据对象需要的内存数量: 决定如何解释内存中的位: 决定可使用数据对象执行的操作或方法. 2. 不必在类声明中使用关键字private,因为这是 ...