什么原因导致快乐抛出一个解析错误?

我在Alex写了一个词法分析器,我试图把它和一个用Happy编写的解析器联系起来。 我会尽我所能在不粘贴大量代码的情况下总结我的问题。

我从我的词法分析器的单元测试中知道,字符串"x7"是lexed:

[TokenNonPrint 'x7', TokenEOF]

我的令牌类型(由词法分析器吐出)是Token 。 我已经定义了lexWrapalexEOF如下所述,它给了我下面的头和令牌声明:

%name parseTokens 
%tokentype { Token }
%lexer { lexWrap } { alexEOF }
%monad { Alex }
%error { parseError }

%token
  NONPRINT {TokenNonPrint $$}
  PLAIN { TokenPlain $$ }

我使用以下方法调用解析器+词法分析器组合:

parseExpr :: String -> Either String [Expr]
parseExpr s = runAlex s parseTokens

这是我的第一部作品:

exprs :: { [Expr] }
exprs
  : {- empty -} { trace "exprs 30" [] }
  | exprs expr { trace "exprs 31" $ $2 : $1 }

nonprint :: { Cmd }
  : NONPRINT { NonPrint $ parseNonPrint $1}

expr :: { Expr }
expr
  : nonprint {trace "expr 44" $ Cmd $ $1}
  | PLAIN { trace "expr 37" $ Plain $1 }

我将省略ExprNonPrint的数据类型声明,因为它们很长,并且这里只有构造函数CmdNonPrint 。 函数parseNonPrint在Parse.y的底部被定义为:

parseNonPrint :: Char -> NonPrint
parseNonPrint 'x7' = Bell

另外,我的错误处理函数如下所示:

parseError :: Token -> Alex a
parseError tokens = error ("Error processing token: " ++ show tokens)

这样写,我期望通过以下hspec测试:

parseExpr "x7" `shouldBe` Right [Cmd (NonPrint Bell)]

但是相反,我看到"exprs 30"打印一次(即使我正在运行5个不同的单元测试),并且我所有的parseExpr测试parseExpr返回Right [] 。 我不明白为什么会出现这种情况,但我改变了exprs生产以防止它:

exprs :: { [Expr] }
exprs
  : expr { trace "exprs 30" [$1] }
  | exprs expr { trace "exprs 31" $ $2 : $1 }

现在,我所有的测试都失败了,他们碰到的第一个标记--- parseExpr "x7"失败:

uncaught exception: ErrorCall (Error processing token: TokenNonPrint 'a')

我完全困惑,因为我希望解析器采用路径exprs -> expr -> nonprint -> NONPRINT并成功。 我不明白为什么这个输入会使解析器处于错误状态。 没有任何trace语句被击中(优化掉?)。

我究竟做错了什么?


事实证明,这个错误的原因是无害的线路

%lexer { lexWrap } { alexEOF }

这是关于使用Alex和Happy的链接问题所推荐的(不幸的是,Google的最佳搜索结果之一就像“使用Alex作为单身词法分析器和Happy一样”),修正方法是将其更改为以下内容:

%lexer { lexWrap } { TokenEOF }

我不得不深入到生成的代码来发现问题。 它是由从%tokens指令派生的代码引起的,它看起来如下所示(除了TokenNonPrint ,我试图追踪错误时将所有的令牌声明注释掉了):

happyNewToken action sts stk
    = lexWrap(tk -> 
    let cont i = happyDoAction i tk action sts stk in
    case tk of {
    alexEOF -> happyDoAction 2# tk action sts stk; -- !!!!
    TokenNonPrint happy_dollar_dollar -> cont 1#;
    _ -> happyError' tk
    })

显然,Happy将%tokens指令的每一行转换为模式匹配的一个分支。 它还在%lexer指令中为其标识的任何分支插入EOF标记。

通过插入值的名称,即alexEOF而不是数据构造函数TokenEOF ,case语句的该分支具有将名称alexEOF重新绑定到传递给lexWrap任何令牌的lexWrap ,从而映射原始绑定和lexWrap打开case语句以便每次都碰到EOF规则,这会以某种方式导致Happy进入错误状态。

错误不会被类型系统捕获,因为标识符alexEOF (或TokenEOF )不会出现在生成的代码中的任何其他位置。 错误地使用%lexer指令会导致GHC发出警告,但是,由于警告出现在生成的代码中,因此不可能将其与代码抛出的所有其他无害警告区分开来。

链接地址: http://www.djcxy.com/p/65639.html

上一篇: What causes Happy to throw a parse error?

下一篇: How can I unit test Alex code?