Parsing 分隔符、比较、运算符标记类型

Parsing 分隔符、比较、运算符标记类型,parsing,compiler-construction,lexical-analysis,Parsing,Compiler Construction,Lexical Analysis,摘自Aho、Lam、Sethi和Ullman的“编译器原理、技术和工具,第二版”(“紫书”): 图3.2:第112页令牌示例 [Token] [Informal Description] [Sample Lexemes] if characters i, f if else characters e, l, s, e else

摘自Aho、Lam、Sethi和Ullman的“编译器原理、技术和工具,第二版”(“紫书”):

图3.2:第112页令牌示例

[Token]       [Informal Description]                  [Sample Lexemes]
if            characters i, f                         if
else          characters e, l, s, e                   else
comparison    < or > or <= or >= or == or !=          <=, !=
id            letter followed by letters and digits   pi, score, D2
number        any numeric constant                    3.14159, 0, 6.02e23
literal       anything but ", surrounded by "'s       "core dumped"

当操作符在语法上相同时,对于如何表示单个操作符的观点各不相同。许多人会为不同的操作符编写单独的结果,即使没有真正的语法差异,语义差异也是有限的

话虽如此,
=
=
为什么要对关键字使用不同的标记类型
在编写解析器时,通常要切换令牌类型。如果令牌类型没有足够的信息来做出决定,这意味着您还必须检查
案例中的令牌值。如果令牌的值表示为字符串,那么进行比较的成本也会更高(即使字符串已被插入,If-else-If序列的效率仍低于开关)。在许多解析器生成器中,基于令牌的值进行决策是不可能的,或者比仅使用令牌的类型更复杂

为了说明这一点,这里摘录了一个手写解析器,其中不同的关键字具有不同的令牌类型:

parse_statement() {
  switch(current_token.type) {
    case IF:
      parse_if_statement(); break;
    case WHILE:
      parse_while_statement(); break;
    //...
    case ID: case NUMBER: case LITERAL:
      parse_expression_statement(); break;
    default:
      syntax_error(); break;
  }
}
statement
  : IF condition body (ELSE body)?
  | WHILE condition body
  | ... | expression ';' ;
如果不是这样,则使用相同的代码:

parse_statement() {
  switch(current_token.type) {
    case KEYWORD:
      if (current_token.value == "if") {
        parse_if_statement();
      } else if (current_token.value == "while") {
        parse_while_statement();
      // '}  else if(...) {'s for other valid keywords go here
      } else {
        syntax_error();
      }
    // Other statement types that don't start with a keyword go here
    case ID: case NUMBER: case LITERAL:
      parse_expression_statement(); break;
    default:
      syntax_error(); break;
  }
}
注意额外的嵌套,现在有两个地方调用
syntax\u error

对于解析器生成器,不同的令牌类型如下所示:

parse_statement() {
  switch(current_token.type) {
    case IF:
      parse_if_statement(); break;
    case WHILE:
      parse_while_statement(); break;
    //...
    case ID: case NUMBER: case LITERAL:
      parse_expression_statement(); break;
    default:
      syntax_error(); break;
  }
}
statement
  : IF condition body (ELSE body)?
  | WHILE condition body
  | ... | expression ';' ;
或者,如果只有关键字标记类型,则如下所示:

statement
  : if condition body (else body)?
  | while condition body
  | ... | expression ';' ;

if: {current_token.value == "if"} KEYWORD ;
else: {current_token.value == "else"} KEYWORD ;
while: {current_token.value == "while"} KEYWORD ;
这仅适用于支持语义谓词的解析器生成器。在许多其他国家,这根本不可能

为什么对比较运算符使用相同的令牌类型 当不同的标记总是出现在语法中的同一位置时,即语法不区分它们,将它们合并为单个标记类型是一种方便的快捷方式。同样,让我们将语法与比较类型与单个类型进行比较:

comparison_exp: additive_exp COMPARISON additive_exp ;
comparison_exp: additive_exp comparison additive_exp ;
comparison: LT | GT | LTE | GTE | EQ | NEQ;
以及个别类型:

comparison_exp: additive_exp COMPARISON additive_exp ;
comparison_exp: additive_exp comparison additive_exp ;
comparison: LT | GT | LTE | GTE | EQ | NEQ;
因此,如果您只有一种标记类型,则不需要详细说明语法中的所有选项


与第一个问题相比,这是一个更次要、更主观的问题。

谢谢您如此详细的回答。如果要将运算符分组为多个令牌类型,那么按优先级将它们分组是有意义的?在单个令牌类型中将*和/一起分组。在单个标记类型中将+和-组合在一起。@是的,但这也取决于其他因素。例如,在某些语言中,
*
也可以用作前缀运算符,
/
不能用作前缀运算符,因此这两者不能互换。将标记分组在一起还意味着,如果您以后更改语言的方式使其中一个运算符(而不是其他运算符)具有附加意义,则必须更改更多代码(例如,如果您将
*
/
分组在一起,然后添加前缀
*
运算符,则必须撤消分组).根据您的解释,我将尝试使用令牌类型,以便解析器的控制流(switch vs if)尽可能高效。也就是说,lexer的工作是在解析器遇到每个令牌时,生成易于解析器使用的令牌。有意义吗?@TedHenry,是的,有意义。“解析器只考虑令牌的类型,而不考虑其值。”这似乎是一条我需要进一步思考的坚实规则。解析器如何将字符串文本标记转换为解析树中的字符串节点?我假设订单不需要检查字符串标记的值来进行转换。解析器只是将值传递给字符串节点构造函数。对吗?谢谢。@TEDHERY:词法扫描程序创建一个字符串文本节点,并通知解析器它有一个字符串文本标记。语义值本身通过解析器传递给AST构造函数、求值器、编译器或其他什么。