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

      The figure shown on the left is left-right symmetric as it is possible to fold the sheet of paper ...

  2. 【ShardingSphere】ShardingSphere学习(三)-数据分片-分片

    分片键 分片算法 分片策略 SQL Hint 分片键 用于分片的数据库字段,是将数据库(表)水平拆分的关键字段.例:将订单表中的订单主键的尾数取模分片,则订单主键为分片字段. SQL中如果无分片字段, ...

  3. 【CPU100%排查】CPU100%问题排查方案

    1.使用top -c 查看CPU 占用情况 ,按P(大写)可以倒序查看占CPU占用率  2.找到占用率高的进程以后,再定位到具体线程 比如 此时进程ID 14724 CPU占用高,进一步使用top - ...

  4. Git 常用命令总结,将会持续更新

    平常在windows电脑上使用Git Extensions 工具比较多,大部分的常用指令都可以通过点点点就可以完成.在mac电脑上的话使用sourcetree工具.但有时候也会直接通过git命令,很多 ...

  5. drozer浅析三:命令实现与交互

    前面走马观花的看了几个模块的源码,看到是用python(会加载自定义的java类)写的.产生2个问题:在命令行中输入command,drozer是如何去执行的:python是如何与java交互的. d ...

  6. UVA10827球面上的最大和

    题意:      最大子矩阵的加强版,就是给你一个n*n的矩阵,每个格子里面都有数字,然后我们在里面选择一个矩阵,使得矩阵中所有数字的和最大,而且这个题目说这个n*n的矩阵的最右边和最左边是相邻的,最 ...

  7. 迪杰斯特拉(Dijkstra) 最短路算法

    直接看B站视频吧: https://www.bilibili.com/video/BV1QK411V7V4/

  8. 老师不讲的C语言知识

    老师不讲的C语言知识 导语: 对于工科生,C语言是一门必修课.标准C(ANSI C)这个看似简单的语言在硬件底层编程.嵌入式开发领域还是稳坐头把交椅.在20年5月份,C语言就凭借其在医疗设备上的广泛应 ...

  9. Jenkins+Git的搭建和自动部署

    前言 Jenkins在工作中都使用过,之前都是运维去搭建部署,弄好了之后给我一个网址去构建项目就可以了,所以也都是一直没了解过安装过程. 今天在自己的服务器上搭建了一遍,中间有遇到很多坑,特在此归纳总 ...

  10. 『居善地』接口测试 — 5、使用Requests库发送POST请求

    目录 1.请求正文是application/x-www-form-urlencoded 2.请求正文是raw (1)json格式文本(application/json) (2)xml格式文本(text ...