是什么原因导致Happy抛出一个解析错误?

时间:2015-08-13 19:06:56

标签: parsing haskell happy alex

我在Alex中写了一个词法分析器,我试图将它连接到用Happy编写的解析器。我会尽量总结我的问题,而不会粘贴大量的代码。

我从我的词法分析器的单元测试中知道,字符串"\x7"被列为:

[TokenNonPrint '\x7', TokenEOF]

我的令牌类型(由词法分析器吐出)是Token。我已按照here所述定义了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的所有测试都返回{{ 1}}。我不明白为什么会这样,但我更改了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语句被命中(优化了吗?)。

我做错了什么?

1 个答案:

答案 0 :(得分:1)

事实证明这个错误的原因是无害的行

%lexer { lexWrap } { alexEOF }

这是关于使用Alex with Happy的链接问题所推荐的(不幸的是,Google和#34等查询的Google搜索结果之一;使用Alex作为Happy的monadic lexer)。修复方法是将其更改为以下内容:

%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的任何标记,遮蔽原始绑定并使case语句短路,以便每次都达到EOF规则,这会导致Happy进入错误状态。

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