分隔符,比较符,运算符令牌类型

时间:2018-06-20 17:35:32

标签: parsing compiler-construction lexical-analysis

摘自《编译器原理,技术和工具,第二版》。 (“紫皮书”),作者是Aho,Lam,Sethi和Ullman:

图3.2:令牌示例pg。 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"

在上面,他们将ifelse分为自己的令牌类型。在我看到的大多数示例中,它们将是单个keyword令牌类型,令牌的值将为ifelse。为每个关键字使用单独的令牌类型而不是keyword令牌类型有什么好处?

拥有comparison这样的令牌类型有什么好处?为什么没有像下面这样的每种比较都具有令牌类型?

[Token]       [Informal Description]                  [Sample Lexemes]
lt            <                                       <
gt            >                                       >
lte           <=                                      <=
gte           >=                                      >=
eq            ==                                      ==
neq           !=                                      !=

2 个答案:

答案 0 :(得分:1)

当运算符在语法上相同时,关于各个运算符如何表示的观点也各不相同。即使没有真正的语法差异并且语义差异受到限制,许多人也会为不同的运算符编写单独的作品。

话虽如此,有些语言==>=<=在语法上是不同的。在C(及其系列)中,这些运算符的优先级有所不同,因此可以编写不带括号的a <= b == b <= c,尽管包含该表达式的代码不太可能在代码审查中幸免。 (即使带括号,该表达式也是有问题的。)在Python中,a <= b <= c是有效的级联比较,而a <= b >= c不是。等等

一般规则是,如果令牌在语言语法中具有独特的作用,则差异必须对解析器可见,并且解析器仅考虑令牌的类型,而不考虑其值。因此,在任何实际语法中,ifthenelse必须是不同的标记类型。

答案 1 :(得分:1)

为什么要对关键字使用不同的令牌类型

编写解析器时,通常switch是令牌类型。如果令牌类型不足以做出决定,那么这意味着您还必须检查case内部的令牌值。如果令牌的值表示为字符串,则比较起来也将更加昂贵(即使字符串是interned,if-else-if序列的效率仍然不如switch高效)。在许多解析器生成器中,基于令牌的值进行决策是不可能的,或者比仅使用令牌的类型还要复杂。

为说明这一点,以下是手写解析器的摘录,其中不同的关键字具有不同的标记类型:

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;
  }
}

还有相同的代码,并非如此:

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_error

对于解析器生成器,它看起来像这样,具有不同的令牌类型:

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;

因此,如果您只有一种标记类型,则无需在语法中拼出所有选项。

与第一个问题相比,这是一个更为次要和主观的事情。