草庐IT

ACWJ_00扫描器

Janos 2023-03-28 原文

第一部分:词法扫描介绍

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

​ 我们将定义一个只有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. Ruby 扫描/获取直到 EOF - 2

    我想扫描未知数量的行,直到扫描完所有行。我如何在ruby中做到这一点?例如:putreturnsbetweenparagraphsforlinebreakadd2spacesatend_italic_or**bold**输入不是来自"file",而是通过STDIN。 最佳答案 在ruby​​中有很多方法可以做到这一点。大多数情况下,您希望一次处理一行,例如,您可以使用whileline=getsend或STDIN.each_linedo|line|end或者通过使用-n开关运行ruby​​,例如,这意味着上述循环之一(在每次迭代中将

  2. ruby-on-rails - `method_missing':#<Rails::Application::Configuration:0x00> 的未定义方法 `action_mailer' - 2

    我正在构建一个Rails应用程序并且使用的是Rails4.0.1。我有一个错误,并注意到它在3个月前被称为rails上的一个错误,所以我决定:捆绑更新并获得rails4.0.3这样做之后,测试和服务器都不会启动,并且会抛出错误:gems/railties-4.0.3/lib/rails/railtie/configuration.rb:95:in`method_missing':undefinedmethod`action_mailer'for#(NoMethodError)目前我在config/environments/*中注释掉了action_mailer行,但最好能找到一个真正的

  3. ruby-on-rails - Rails/Heroku - 如何对上传的文件进行反病毒扫描? - 2

    如何扫描上传文件中的病毒、木马等?只是想阻止一些用户上传一些讨厌的东西。我正在使用Heroku和AmazonS3。 最佳答案 Checkoutthis它支持REST/JSON防病毒网络服务。 关于ruby-on-rails-Rails/Heroku-如何对上传的文件进行反病毒扫描?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/9640516/

  4. ruby 正则表达式扫描与 =~ - 2

    Ruby(1.9.3)文档似乎暗示scan等同于=~除了scan返回多个匹配项,而=~仅返回第一个匹配项,并且scan返回匹配数据,而=~返回索引。但是,在下面的示例中,这两种方法似乎对相同的字符串和表达式返回不同的结果。这是为什么?1.9.3p0:002>str="PerlandPython-thetwolanguages"=>"PerlandPython-thetwolanguages"1.9.3p0:008>exp=/P(erl|ython)/=>/P(erl|ython)/1.9.3p0:009>str=~exp=>01.9.3p0:010>str.scanexp=>[["er

  5. 信息收集(Web目录扫描) - 2

    一、扫描原因        (1)寻找到网站后台管理        (2)寻找未授权界面        (3)寻找网站更多隐藏信息        (4)通过使用目录扫描可以让我们发现这个网站存在多少个目录,多少个页面,探索出网站的整体结构。通过目录扫描我们还能扫描敏感文件,后台文件,数据库文件,和信息泄漏文件等等。二、方法1、robots.txt        (1)Robots协议(RobotsExclusionProtocol)“网络爬虫排除标准”,网站通过Robots协议告诉搜索引擎哪些页面可以抓取,哪些页面不能抓取。        (2)同时也记录网站所具有基本的目录。        

  6. ruby - 使用\d 扫描字符串中的 Unicode 数字 - 2

    根据theOnigurumadocumentation,\d字符类型匹配:decimaldigitcharUnicode:General_Category--Decimal_Number但是,在包含所有Decimal_Number字符的字符串中扫描\d会导致仅匹配拉丁文0-9数字:#encoding:utf-8require'open-uri'html=open("http://www.fileformat.info/info/unicode/category/Nd/list.htm").readdigits=html.scan(/U\+([\da-f]{4})/i).flatten.

  7. ruby - 00.0 和 0.0 不一样吗? - 2

    我刚刚在编程语言ruby中发现了一个奇怪的问题,这不是什么大问题,但我就是不明白为什么会这样。如果有人知道我的问题的问题,我会很感兴趣。在ruby中你可以写成0或者00,没关系,结果是一样的。如果您运行0===00,您还会得到true,这意味着两个输入完全相同。0.0也等于0,所以逻辑上00.0也应该等于0.0但问题是,如果你尝试使用数字00.0那么你只会得到一个错误。例如,如果您运行:a=00.0你得到这个错误:syntaxerror,unexpectedtINTEGER当然我知道这是个小问题,但如前所述,我想了解为什么计算机不将00.0视为与0.0相同?

  8. ruby - 在 ruby​​ 中获取字符串扫描结果的索引 - 2

    我想获取索引以及扫描结果"abab".scan(/a/)我不仅想拥有=>["a","a"]还有那些比赛的索引[1,3]有什么建议吗? 最佳答案 试试这个:res=[]"abab".scan(/a/)do|c|res[["a",0],["a",2]] 关于ruby-在ruby​​中获取字符串扫描结果的索引,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/3520208/

  9. ruby - 如果 float 不是 .00 则只显示小数点 sprintf/printf - 2

    我很喜欢格式化一个float,但如果没有相关的float,我希望它显示为一个整数。即1.20->1.2x1.78->1.78x0.80->0.8x2.00->2x我可以通过一些正则表达式来实现这一点,但想知道是否有一个sprintf-only方法可以做到这一点?我在ruby​​中懒洋洋地这样做:("%0.2fx"%(factor/100.0)).gsub(/\.?0+x$/,'x') 最佳答案 您想使用%g而不是%f:"%gx"%(factor/100.00) 关于ruby-如果floa

  10. ruby - Ruby 字符串上的扫描和匹配有什么区别 - 2

    我是Ruby的新手,一直使用String.scan来搜索数字的第一次出现。返回值在嵌套数组中有点奇怪,但我只是去[0][0]获取我想要的值。(我确定它有它的用途,只是我还没有使用它。)我刚刚发现有一个String.match方法。而且似乎更方便,因为返回的数组不是嵌套的。这是两者的一个例子,第一个是扫描:>>'a1-nightstay'.scan(/(a)?(\d*)[-]night/i).to_a=>[["a","1"]]然后是匹配>>'a1-nightstay'.match(/(a)?(\d*)[-]night/i).to_a=>["a1-night","a","1"]我已经检查了

随机推荐