是什么导致 Happy 抛出解析错误?
What causes Happy to throw a parse error?
我用 Alex 编写了一个词法分析器,我正试图将它连接到用 Happy 编写的解析器。我会尽力总结我的问题而不粘贴大量代码。
我从词法分析器的单元测试中知道字符串 "\x7"
被词法化为:
[TokenNonPrint '\x7', TokenEOF]
我的标记类型(由词法分析器吐出)是 Token
。我已经按照 here 的描述定义了 lexWrap
和 alexEOF
,这给了我以下 header 和标记声明:
%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" $ : }
nonprint :: { Cmd }
: NONPRINT { NonPrint $ parseNonPrint }
expr :: { Expr }
expr
: nonprint {trace "expr 44" $ Cmd $ }
| PLAIN { trace "expr 37" $ Plain }
我将省略 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
return Right []
。我不明白为什么会这样,但我更改了 exprs
生产以防止它:
exprs :: { [Expr] }
exprs
: expr { trace "exprs 30" [] }
| exprs expr { trace "exprs 31" $ : }
现在我所有的测试都在他们命中的第一个标记上失败了 --- parseExpr "\x7"
失败并显示:
uncaught exception: ErrorCall (Error processing token: TokenNonPrint '\a')
我完全糊涂了,因为我希望解析器采用路径 exprs -> expr -> nonprint -> NONPRINT
并成功。我不明白为什么这个输入会使解析器处于错误状态。 None 个 trace
语句被命中(优化掉了?)。
我做错了什么?
原来这个错误的原因是无害的行
%lexer { lexWrap } { alexEOF }
这是由有关将 Alex 与 Happy 结合使用的链接问题推荐的(不幸的是,对于诸如“将 Alex 用作具有 Happy 的单子词法分析器”之类的查询,Google 的最高结果之一)。解决方法是更改它到以下内容:
%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 发出警告,但是,由于警告出现在生成的代码中,因此无法将它与代码抛出的所有其他无害警告区分开来。
我用 Alex 编写了一个词法分析器,我正试图将它连接到用 Happy 编写的解析器。我会尽力总结我的问题而不粘贴大量代码。
我从词法分析器的单元测试中知道字符串 "\x7"
被词法化为:
[TokenNonPrint '\x7', TokenEOF]
我的标记类型(由词法分析器吐出)是 Token
。我已经按照 here 的描述定义了 lexWrap
和 alexEOF
,这给了我以下 header 和标记声明:
%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" $ : }
nonprint :: { Cmd }
: NONPRINT { NonPrint $ parseNonPrint }
expr :: { Expr }
expr
: nonprint {trace "expr 44" $ Cmd $ }
| PLAIN { trace "expr 37" $ Plain }
我将省略 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
return Right []
。我不明白为什么会这样,但我更改了 exprs
生产以防止它:
exprs :: { [Expr] }
exprs
: expr { trace "exprs 30" [] }
| exprs expr { trace "exprs 31" $ : }
现在我所有的测试都在他们命中的第一个标记上失败了 --- parseExpr "\x7"
失败并显示:
uncaught exception: ErrorCall (Error processing token: TokenNonPrint '\a')
我完全糊涂了,因为我希望解析器采用路径 exprs -> expr -> nonprint -> NONPRINT
并成功。我不明白为什么这个输入会使解析器处于错误状态。 None 个 trace
语句被命中(优化掉了?)。
我做错了什么?
原来这个错误的原因是无害的行
%lexer { lexWrap } { alexEOF }
这是由有关将 Alex 与 Happy 结合使用的链接问题推荐的(不幸的是,对于诸如“将 Alex 用作具有 Happy 的单子词法分析器”之类的查询,Google 的最高结果之一)。解决方法是更改它到以下内容:
%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 发出警告,但是,由于警告出现在生成的代码中,因此无法将它与代码抛出的所有其他无害警告区分开来。