12

私は Alex + Happy を使用してパーサーを構築することを学ぼうとしています。特にmonad、Alex のラッパーの使用法を学ぶことに興味があります。私はすでに Alex とHappyのドキュメントを見てきましたが、私にとっては、それらを一緒に使用するための有用な情報が本当に不足しています。basic私はそれらをとposnラッパーと一緒に動作させることができましたが、と途方に暮れていmonadます。

私はすでにアレックス、ハッピー、およびモナドレクサーに関する SO に関する別の質問を見てきました (以下を含む:アレックス + ハッピーを使用して単純なインタープリターを構築するためのチュートリアルはありますか?monad

オンラインのコードのほとんどは、カスタム レクサー関数で Happy を使用するか、basicまたはposnAlex ラッパーを使用します。

以下は、ini に似た構文の単純なレクサーです。

{
module IniLexer where
}

%wrapper "monad"



$spaces = [\ \t]
$alpha = [a-zA-Z]
$digits = [0-9]
$alnum = [$alpha$digits]


@identifier = $alpha $alnum*

@comment = \#.*

@integer = $digits+

@boolean = (true) | (false)

@string = \"[^\"]*\"


:-

@integer    { mkL LInteger }
@boolean    { mkL LBoolean }
@string     { mkL LString }

@identifier  { mkL LIdentifier }

\[@identifier\] { mkL LSection }

=           { mkL LAssign }

\;          { mkL LEndAssign }
@comment    ;
[\ \t \n]+  ;


{

data LexemeClass = LInteger | LBoolean | LString | LIdentifier | LSection | LAssign | LEndAssign | LEOF
    deriving (Eq, Show)


mkL :: LexemeClass -> AlexInput -> Int -> Alex Token
mkL c (p, _, _, str) len = let t = take len str
                           in case c of
                                LInteger -> return (IntegerNum ((read t) :: Integer) p)
                                LBoolean -> return (BooleanVal (if t == "true"
                                                                   then True
                                                                   else False
                                                               ) p)
                                LString -> return (StringTxt (take (length t - 2) (drop 1 t)) p)
                                LIdentifier -> return (Identifier t p)
                                LSection -> return (SectionHeader (take (length t - 2) (drop 1 t)) p)
                                LAssign -> return (Assignment p)
                                LEndAssign -> return (EndAssignment p)


-- No idea why I have to write this myself. Documentation doesn't mention it.
alexEOF :: Alex Token
alexEOF = return Eof



data Token = SectionHeader {identifier :: String, position :: AlexPosn} |
             Identifier {name :: String, position :: AlexPosn}          |
             Assignment {position :: AlexPosn}                          |
             EndAssignment {position :: AlexPosn}                       |
             IntegerNum {value :: Integer, position :: AlexPosn}        |
             BooleanVal {istrue :: Bool, position :: AlexPosn}          |
             StringTxt  {text :: String, position :: AlexPosn}          |
             Eof
    deriving (Eq, Show)


}

相対的な Happy パーサーは次のとおりです。

{
module Main where

import IniLexer

}



%name parseIniFile
%error {parseError}
%lexer  {alexMonadScan} {AlexEOF}
%monad {Alex}
%tokentype {Token}
%token
    SECTION     {SectionHeader name _ }
    IDENT       {Identifier name _ }
    '='         {Assignment _ }
    INT         {IntegerNum value _ }
    BOOL        {BooleanVal istrue _ }
    STRING      {StringTxt text _ }
    ';'         {EndAssignment _ }


%%


ConfigFile : SequenceOfSections                    {reverse $1}

SequenceOfSections : {- empty -}                   {   []  }
                   | SequenceOfSections Section    {$2 : $1}


Section : SECTION SectionBody                      {Section (identifier $1) (reverse $2)}


SectionBody : {- empty -}        {[]}
            | SectionBody AssignmentLine ';' {$2 : $1}


AssignmentLine : IDENT '=' Value      {(name $1, $3)}

Value : INT         {IntV (value $1)}
      | BOOL        {BoolV (istrue $1)}
      | STRING      {StringV (text $1)}


{

data Value = IntV Integer | BoolV Bool | StringV String
    deriving (Eq, Show)

data Section = Section String [(String, Value)]
    deriving (Eq, Show)

data IniFile = IniFile [Section]
    deriving (Eq, Show)


parseError :: [Token] -> Alex a
parseError t = fail "a"

main = do
    s <- getContents
    print $ parseIniFile $ runAlex s alexMonadScan

}

多くのコンパイラ エラーが発生します。

[...]
Couldn't match expected type `(AlexReturn t1 -> Alex a0) -> t0'
                with actual type `Alex Token'
    The function `alexMonadScan' is applied to one argument,
    but its type `Alex Token' has none
[...]

パーサーを使用するにはどのように変更すればよいalexMonadScanですか? Happyのドキュメントはまったく明確ではなく、明確にする例を使用しないように努めています (または、提供されている例は、私の観点からは明確ではありません)。

必要に応じて、この同じレクサー + パーサーの私のposnバージョンを投稿できます。

4

1 に答える 1

15

私が知る限り、レクサーの定義はまったく問題ありません。そこにバグがないと仮定すると、修正する必要がある唯一の問題は、パーサーの構成にあります。まず、使用しているレクサーが間違っていることです。その関数は Alex lexer へのインターフェースですが、型は

alexMonadScan :: Alex result

しかし、ハッピーが望んでいる字句解析器は型です

lexer :: (Token -> P a) -> P a

P使用しているモナドはどこにありますか。これが言っていることは、レクサーはAlex a継続が与えられたときを提供する必要があるということです。ここで必要なのは単純なラッパーです。

lexwrap :: (Token -> Alex a) -> Alex a
lexwrap cont = do
    token <- alexMonadScan
    cont token

または同等に

lexwrap = (alexMonadScan >>=)

次に、ディレクティブで使用alexEOFすると、%lexer入力ごとにパーサーが失敗します。そこで指定した名前は、生成されたコードの case ステートメントの分岐に挿入されるため、値ではなくデータ コンストラクターの名前を使用する必要があります。特に、Alex が発行するデータ コンストラクターを使用する必要があります。 EOF を通知します。

これにより、パーサーのレクサー行が少し異なります。

%lexer {lexwrap} {Eof}

(補足として、これが自分で記述する必要がある理由ですalexEOF = return Eof。内部で返すデータ コンストラクalexEOFターは、ファイルを終了するものとして Happy に指定したデータ コンストラクターとパターン マッチする必要があります。アレックスは知る方法がありません。 Happy は、あなたが Alex を介して何を放出することを選択したかを知る方法がありません。)

次の問題は、parseError の型が正しくないことです。モナドだけを使用する場合、それは確かに必要なタイプですが、レクサーをミックスに追加する場合、parseError は異なるタイプでなければなりません。また、fail の使用はおそらく推奨されないため、もう少し適切な定義を次に示します。

parseError :: Token -> Alex a
parseError _ = alexError "Why is using happy and alex so hard"

最後に、main 関数の定義が少し変わっています。パーサーを呼び出すためにやりたいことは、runAlex で呼び出すことです。したがって、ここにそのための簡単なラッパーがあります。渡された文字列は、解析したい文字列です。

parse :: String -> Either String [Section]
parse s = runAlex s parseIniFile

関数 parse のタイプは、parseIniFile の定義によって決まります。ここでは anですAlex [Section]ので anEither String [Section]を返します。

それがすべてだと思います。

于 2014-01-04T16:10:48.533 に答える