Flex:匹配signed int vs加/减

时间:2016-01-20 07:55:18

标签: regex flex-lexer

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

我在flex

中为signed int定义了一个宏
signedint = [+-]?[0-9]+

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

然而,我遇到了能够区分有符号整数与加法或减法的问题。例如,

5 + 2

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

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

5+2
5 +2

其中任何一个只返回两个令牌(2个整数),因为flex将5视为signedint(正确)并返回它,然后看到'+2'并看到另一个有符号的int(它不会为空格返回任何内容) )。

关于如何区分有符号的int和加/减的任何想法?

2 个答案:

答案 0 :(得分:3)

在词汇层面解决这个问题的难度是许多语言甚至不尝试的原因。解析器可以很容易地区分一元前缀 + - 运算符和具有相同拼写的二进制中缀运算符,除了一个角点情况(见下文)之外,还有-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(expr) = LAST(term) = { ), CONSTANT, VARIABLE }

最后,FOLLOW为非终端设置,基于观察term仅出现在expr的末尾,而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状态。

这转化为以下小的弹性描述:

%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 -bit 2的补码表示中,可以表示-2 N ,但不能表示+ 2 N ,因为最大正数为2 N -1。如果有符号整数作为表达式推迟到解析器,则允许整数2 N 是很重要的,即使它不适合正在使用的有符号整数类型。

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

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

答案 1 :(得分:-1)

sed

这应该有效