我用Alex编写了一个lexer,并试图将它连接到一个用“快乐”编写的解析器。我将尽力总结我的问题,而不粘贴大量代码块。
从我对lexer的单元测试中,我知道字符串"\x7"是按以下顺序排列的:
[TokenNonPrint '\x7', TokenEOF]我的令牌类型(由lexer发出)是Token。我已经将lexWrap和alexEOF定义为描述了here,它为我提供了以下头部和令牌声明:
%name parseTokens
%tokentype { Token }
%lexer { lexWrap } { alexEOF }
%monad { Alex }
%error { parseError }
%token
NONPRINT {TokenNonPrint $$}
PLAIN { TokenPlain $$ }我使用以下方法调用parser+lexer组合:
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 }我将省略Expr和NonPrint的数据类型声明,因为它们是长的,只有构造函数Cmd和NonPrint才重要。函数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返回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语句被击中(优化离开?)。
我做错了什么?
发布于 2015-08-14 06:19:12
事实证明,这个错误的原因是无伤大雅的线条。
%lexer { lexWrap } { alexEOF }这是由关于在快乐中使用Alex的链接问题推荐的(不幸的是,Google的查询结果之一是“使用Alex作为带有快乐的一元使用的词汇”)。
%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
})显然,快乐将%tokens指令的每一行转换为模式匹配的一个分支。它还插入一个分支,用于在%lexer指令中标识为EOF令牌的任何内容。
通过插入值alexEOF的名称,而不是数据构造函数TokenEOF,case语句的这个分支具有将名称alexEOF重新绑定到传递给lexWrap的任何令牌的效果,从而隐藏原来的绑定并短路case语句,使其每次都命中EOF规则,从而在某种程度上导致愉快进入错误状态。
类型系统不会捕捉到错误,因为在生成的代码中,标识符alexEOF (或TokenEOF)没有出现在任何其他地方。这样误用%lexer指令会导致GHC发出警告,但是,由于该警告出现在生成的代码中,因此不可能将其与代码抛出的所有其他无害警告区分开来。
https://stackoverflow.com/questions/31996489
复制相似问题