2

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

我从我的词法分析器的单元测试中知道该字符串"\x7"被用于:

[TokenNonPrint '\x7', TokenEOF]

我的令牌类型(由词法分析器吐出)是Token. 我已经定义lexWrap并如此alexEOF所述,它为我提供了以下标头和令牌声明:

%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它们很长,而且这里只有构造函数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"print一次(即使我正在运行 5 个不同的单元测试)和我所有的parseExprreturn测试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语句被命中(优化掉了?)。

我究竟做错了什么?

4

1 回答 1

1

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

%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为模式匹配的一个分支。它还为指令标识为 EOF 标记的任何内容插入一个分支。%lexer

通过插入值的名称alexEOF,而不是数据构造函数,TokenEOFcase 语句的此分支具有将名称重新绑定alexEOF到传入的任何标记的效果lexWrap,隐藏原始绑定并短路 case 语句这样它每次都符合 EOF 规则,这会导致 Happy 进入错误状态。

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

于 2015-08-14T06:19:12.277 回答