【问题标题】:Flex: matching signed int vs addition/subtractionFlex:匹配有符号整数与加法/减法
【发布时间】:2016-04-25 23:13:41
【问题描述】:

我有一个 flex 文件,它根据输入文件返回令牌。我目前已将其设置为当我在 flex 中看到一个有符号的 int 时返回一个令牌 INT,当我看到一个加法或减法时返回一个令牌 ADD 或 SUB。

我在 flex 中为一个有符号整数定义了一个宏

signedint = [+-]?[0-9]+

当 flex 看到这种模式时,它会返回一个 INT 令牌。

但是,我遇到了能够将有符号整数与加法或减法区分开来的问题。例如,

5 + 2

返回 3 个标记:INT、ADD、INT。这很好。

但是,当空格丢失时,我遇到了麻烦:

5+2
5 +2

这两个都只返回两个标记(2 个整数),因为 flex 将 5 视为一个有符号整数(正确)并返回它,然后看到 '+2' 并看到另一个有符号整数(它不返回任何空白)。

关于如何让它区分有符号整数和加法/减法的任何想法?

【问题讨论】:

    标签: regex flex-lexer


    【解决方案1】:

    在词汇层面解决这个问题的困难是许多语言甚至没有尝试的原因。解析器可以轻松区分一元前缀 +- 运算符和具有相同拼写的二元中缀运算符,除了一种极端情况(见下文),还有-2 被视为前缀减号后跟整数常量,-2 被视为单个标记之间没有真正的语义差异。在解析器中,如果您不希望运算符出现在 AST 中,可以使用常量折叠来计算常量子表达式。

    只有通过维护词法状态才能在词法扫描期间区分中缀和前缀运算符,这有效地将部分解析算法复制到词法扫描器中的手工构建状态机中。在普通算术表达式的情况下,它只是解析算法的一小部分,但即使如此,它也并不美观,并且使词法分析器/解析器组合的正确性验证变得复杂。

    忽略此处不相关的运算符优先级和结合性,算术表达式的语法可以简化为以下内容:

    expr: term
        | expr INFIX term
    term: CONSTANT | VARIABLE
        | '(' expr ')'
        | PREFIX term
    

    (省略了后缀运算符,包括函数调用和数组下标,但原理不受影响。)

    从该语法中,很容易推导出 FIRST、LAST 和 FOLLOW 集合。 term 只能以终端开头,expr 只能以term 开头,所以它们的 FIRST 集相同:

    FIRST(expr) = FIRST(term) = { (, PREFIX, CONSTANT, VARIABLE }
    

    通过类似的推理,termexpr 的 LAST 集也相同:

    LAST(expr) = LAST(term) = { ), CONSTANT, VARIABLE }
    

    最后,基于term 仅出现在expr 末尾的观察结果,非终结符的 FOLLOW 集,expr 要么在输入末尾,要么出现在语法中,后跟一个终端:

    FOLLOW(term) = FOLLOW(expr) = { ), INFIX, $ }
    

    (其中$ 是输入结束标记。)

    所有这些都可以让我们计算终端的 FOLLOW 集,使用观察非终端 V 的 LAST 中的每个终端 A 只能跟随 FOLLOW(V) 中的终端。 (在某些可能高估 FOLLOW 集的语法中,但在这种情况下并不重要。)这最终给了我们:

     terminal           can be followed by
     --------           ------------------
     INFIX              PREFIX, (, CONSTANT, VARIABLE
     (                  PREFIX, (, CONSTANT, VARIABLE
     PREFIX             PREFIX, (, CONSTANT, VARIABLE
     )                  INFIX, ), $
     CONSTANT           INFIX, ), $
     VARIABLE           INFIX, ), $
    

    简而言之,PREFIX 和 INFIX 永远不会出现在同一个上下文中。如果前一个标记是 INFIX、PREFIX 或 ((或者没有前一个标记),那么运算符必须是 PREFIX。否则,运算符必须是 INFIX。我们可以在 flex 中使用两个开始条件:一个用于我们可能会看到 CONSTANT 的情况,另一个用于我们无法合法看到 CONSTANT 的情况。第一个是 INITIAL 状态。

    这转化为下面的小 flex 描述:

    %x FINAL
    %%
    
    <INITIAL>[-+]?[[:digit:]]+  {
                                  BEGIN(FINAL); return CONSTANT;
                                }
    <INITIAL>[[:alpha:]][[:alnum:]]* {
                                  BEGIN(FINAL); return VARIABLE;
                                }
    <INITIAL>[(]                  return yytext[0];
    <INITIAL>[-]                  return PREFIX_MINUS;
    <INITIAL>[+]                  return PREFIX_PLUS;
    
    <FINAL>[)]                    return yytext[0];
    <FINAL>[-+*/] BEGIN(INITIAL); return yytext[0];
    
    <*>.                          /* Signal invalid token */
    

    当然,这还不完整。它省略了yylval 的设置以及不属于表达式一部分的输入的处理(包括换行符和其他空格)。

    尽管此解决方案有效,但很容易看出为什么首选将问题留给解析器:

    %%
    [-+*/()]                 return yytext[0];
    [[:digit:]]+             return CONSTANT;
    [[:alpha:]][[:alnum:]]*  return VARIABLE;
    

    但是有一种特殊情况需要小心处理。在 N 位 2 的补码表示中,可以表示 -2N,但不能表示 +2N,因为最大正数是 2N-1。如果有符号整数作为表达式延迟到解析器,则允许整数 2N 至关重要,即使它不适合正在使用的有符号整数类型。

    这可以通过使用无符号整数类型将整数值传递给解析器来实现,这反过来意味着解析器需要检测溢出情况。

    碰巧,这不是 C 处理这种情况的方式,这导致了一个有趣的异常。在 C 语言中(如上),整数常量不包含符号; -2 是两个令牌。但是整数常量确实包括一个(隐式)类型,在十进制整数的情况下,它是最小的有符号整数类型,它可以保存常量的值。由于一元否定保留类型,因此结果是在 32 位机器上,-2147483648 的类型为 long(或 long long),即使它可以表示为 int。这有时会引起混乱。

    【讨论】:

      【解决方案2】:
      signedint = \s*[+-]?\s*[0-9]+\s*
      

      这应该可以工作

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-23
        • 2012-04-08
        • 1970-01-01
        • 2015-08-13
        • 2018-02-22
        • 2010-09-19
        相关资源
        最近更新 更多