2

私は Alex で字句解析器を書き、それを Happy で書かれたパーサーに接続しようとしています。膨大な量のコードを貼り付けずに、問題を要約するために最善を尽くします。

lexer の単体テストから、文字列"\x7"が lexed されることを知っています。

[TokenNonPrint '\x7', TokenEOF]

私のトークンタイプ(レクサーによって吐き出される)はToken. ここで説明したようにlexWrapandを定義しました。これにより、次のヘッダーとトークンの宣言が得られます。alexEOF

%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 }

Exprandのデータ型宣言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 once (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 }

これは、Happy で Alex を使用することに関するリンクされた質問で推奨されていました (残念ながら、「Happy で Alex をモナディック レクサーとして使用する」などのクエリに対する Google の上位の結果の 1 つです)。修正するには、次のように変更します。

%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明らかに、Happy はディレクティブの各行をパターン マッチの 1 つのブランチに変換します。また、ディレクティブで EOF トークンとして識別されたものの分岐も挿入します。%lexer

alexEOFデータ コンストラクターではなく、値の名前を挿入することにより、case ステートメントのこの分岐は、に渡されたトークンにTokenEOF名前を再バインドし、元のバインディングをシャドウし、case ステートメントを短絡する効果があります。そのため、毎回 EOF ルールにヒットするため、何らかの形で Happy がエラー状態になります。alexEOFlexWrap

alexEOF識別子(またはTokenEOF) は生成されたコードの他の場所には表示されないため、型システムは間違いを検出しません。このようにディレクティブを誤用する%lexerと、GHC は警告を発しますが、警告は生成されたコードに表示されるため、コードがスローする他のすべての無害な警告と区別することは不可能です。

于 2015-08-14T06:19:12.277 に答える