2012-01-26 9 views
8

私は小さな正規表現パーサーを実装してParsecを学習しようとしています。 >スター - - > exprのParsecを使って正規表現を解析する

expr = try star 
     <|> try litE 
     <|> lit 

litE = do c <- noneOf "*" 
      rest <- expr 
      return (c : rest) 

lit = do c <- noneOf "*" 
      return [c] 

star = do content <- expr 
      char '*' 
      return (content ++ "*") 

いくつかの無限ループがここにありますが(例えば式expr:私のようにHaskellでこれを実装しようとした

EXP : EXP * 
    | LIT EXP 
    | LIT 

:BNFでは、私の文法は何かのように見えますトークンを消費することなく)、パーサーループを永久にループさせます。 starの本質は、最後に必須のトークンを消費するためです。

どのような考えですか?

答えて

12

Parsec.Expr.buildExprParserを使用してください。この目的には理想的です。演算子、優先順位、結合性、および原子の解析方法を記述するだけで、コンビネータはパーサーを構築します。

*を単一のリテラル以上に適用できるように、用語を括弧でグループ化する機能を追加することもできます。私はあなたが正しいと確信しているが、私はなぜ理解していない

import Control.Applicative 
import Control.Monad 
import Text.ParserCombinators.Parsec 
import Text.ParserCombinators.Parsec.Expr 

data Term = Literal Char 
      | Sequence [Term] 
      | Repeat (Int, Maybe Int) Term 
      | Choice [Term] 
    deriving (Show) 

term :: Parser Term 
term = buildExpressionParser ops atom where 

    ops = [ [ Postfix (Repeat (0, Nothing) <$ char '*') 
      , Postfix (Repeat (1, Nothing) <$ char '+') 
      , Postfix (Repeat (0, Just 1) <$ char '?') 
      ] 
     , [ Infix (return sequence) AssocRight 
      ] 
     , [ Infix (choice <$ char '|') AssocRight 
      ] 
     ] 

    atom = msum [ Literal <$> lit 
       , parens term 
       ] 

    lit = noneOf "*+?|()" 
    sequence a b = Sequence $ (seqTerms a) ++ (seqTerms b) 
    choice a b = Choice $ (choiceTerms a) ++ (choiceTerms b) 
    parens = between (char '(') (char ')') 

    seqTerms (Sequence ts) = ts 
    seqTerms t = [t] 

    choiceTerms (Choice ts) = ts 
    choiceTerms t = [t] 

main = parseTest term "he(llo)*|wor+ld?" 
+2

うわー。それはとても簡単です、それはほとんど浮気のように感じます。 – Xodarap

+1

'[Term] - > Term'の代わりに' Sequence、Choice :: Term - > Term-> Term'を使ったほうがずっと簡単でしたが、正確には一致しないASTを扱う方法を示していると思います解析ツリー... – pat

6

あなたの文法は左回帰です。これは、tryでうまくいきません.Parsecは繰り返し戻ってくるでしょう。これにはいくつかの方法があります。おそらく最も簡単なだけで別のルールで*はオプション作っている:もちろん

lit :: Parser (Char, Maybe Char) 
lit = do 
    c <- noneOf "*" 
    s <- optionMaybe $ char '*' 
    return (c, s) 

、あなたはおそらく、とにかくデータ型で物事をラップ終わるだろう、とそれについて移動する方法はたくさんあります。私の頭の上から1つ、ここにある:

import Control.Applicative ((<$>)) 

data Term = Literal Char 
      | Sequence [Term] 
      | Star Term 

expr :: Parser Term 
expr = Sequence <$> many term 

term :: Parser Term 
term = do 
    c <- lit 
    s <- optionMaybe $ char '*' -- Easily extended for +, ?, etc. 
    return $ if isNothing s 
    then Literal c 
    else Star $ Literal c 

多分、より経験豊かなハスケラーがよりよい解決策になるだろう。

+1

は、ここに私の試み(私は良い測定のために、|+、および?を投げた)です。新しい 'lit'関数がプロダクション' EXP - > LIT * 'を追加したものの、左回帰ルール' EXP - > EXP * '...を保持しているようです。あるいは、私は星の関数を 'lit'のものに置き換えると思っていますか? – Xodarap

+1

さて、Kleeneの星は、あなたのコードの中で、直近の言葉にのみ適用されます。あなたのコードでは、あなたが望むものであってもなくてもよい(例えば 'a **'は冗長です) 。左帰属*は左回帰を取り除きます: 'EXP - > EXP *'は 'EXP - > LIT REST?'となり、 'REST - > *'となります。 1レベルの再帰を手動で代用し、式の「末尾」を明示的にします。 –

+0

ええ、括弧を入れてしまえばそれはうまくいかないでしょうが、私はあなたの意見を見ます。私はちょうど標準的な方法を介して左回帰を削除しようとし、私は私の連想を維持できると思う。これが問題だと指摘してくれてありがとう。 – Xodarap

関連する問題