2016-11-29 24 views
4

基本的なことを学んだ後、私はBittorrentクライアントを使い始めたHaskellで「現実世界のアプリケーション」を試したかったのです。このblog postの説明に続いて、私はAttoparsec parser combinatorライブラリを使用しませんでした。代わりにHuttons bookに続いて、私はParser Combinatorsを書き始めました。上記Haskellアプリケーションのデバッグ

module Main where 

import System.Environment (getArgs) 
import qualified Data.Map as Map 
import Control.Monad (liftM, ap) 
import Data.Char (isDigit, isAlpha, isAlphaNum, ord) 
import Data.List(foldl') 

main :: IO() 
main = do 
    [fileName] <- getArgs 
    contents <- readFile fileName 
    download . parse $ contents 

parse :: String -> Maybe BenValue 
parse s = case runParser value s of 
    []  -> Nothing 
    [(p, _)] -> Just p 

download :: Maybe BenValue -> IO() 
download (Just p) = print p 
download _  = print "Oh!! Man!!" 

data BenValue = BenString String 
    | BenNumber Integer 
    | BenList [BenValue] 
    | BenDict (Map.Map String BenValue) 
    deriving(Show, Eq) 

-- From Hutton, this follows: a Parser is a function 
-- that takes a string and returns a list of results 
-- each containing a pair : a result of type a and 
-- an output string. (the string is the unconsumed part of the input). 
newtype Parser a = Parser (String -> [(a, String)]) 

-- Unit takes a value and returns a Parser (a function) 
unit :: a -> Parser a 
unit v = Parser (\inp -> [(v, inp)]) 

failure :: Parser a 
failure = Parser (\inp -> []) 

one :: Parser Char 
one = Parser $ \inp -> case inp of 
     []  -> [] 
     (x: xs) -> [(x, xs)] 

runParser :: Parser a -> String -> [(a, String)] 
runParser (Parser p) inp = p inp 

bind :: Parser a -> (a -> Parser b) -> Parser b 
bind (Parser p) f = Parser $ \inp -> case p inp of 
    []   -> [] 
    [(v, out)] -> runParser (f v) out 

instance Monad Parser where 
    return = unit 
    p >>= f = bind p f 

instance Applicative Parser where 
    pure = unit 
    (<*>) = ap 

instance Functor Parser where 
    fmap = liftM 

choice :: Parser a -> Parser a -> Parser a 
choice p q = Parser $ \inp -> case runParser p inp of 
    [] -> runParser q inp 
    x -> x 

satisfies :: (Char -> Bool) -> Parser Char 
satisfies p = do 
    x <- one 
    if p x 
    then unit x 
    else failure 

digit :: Parser Char 
digit = satisfies isDigit 

letter :: Parser Char 
letter = satisfies isAlpha 

alphanum :: Parser Char 
alphanum = satisfies isAlphaNum 

char :: Char -> Parser Char 
char x = satisfies (== x) 

many :: Parser a -> Parser [a] 
many p = choice (many1 p) (unit []) 

many1 :: Parser a -> Parser [a] 
many1 p = do 
    v <- p 
    vs <- many p 
    unit (v:vs) 

peek :: Parser Char 
peek = Parser $ \inp -> case inp of 
    []  -> [] 
    [email protected](x:xs) -> [(x, v)]  

taken :: Int -> Parser [Char] 
taken n = do 
    if n > 0 
    then do 
     v <- one 
     vs <- taken (n-1) 
     unit (v:vs) 
    else unit [] 

takeWhile1 :: (Char -> Bool) -> Parser [Char] 
takeWhile1 pred = do 
    v <- peek 
    if pred v 
    then do 
     one 
     vs <- takeWhile1 pred 
     unit (v:vs) 
    else unit [] 

decimal :: Integral a => Parser a 
decimal = foldl' step 0 `fmap` takeWhile1 isDigit 
    where step a c = a * 10 + fromIntegral (ord c - 48) 

string :: Parser BenValue 
string = do 
    n <- decimal 
    char ':' 
    BenString <$> taken n 

signed :: Num a => Parser a -> Parser a 
signed p = (negate <$> (char '-' *> p)) 
    `choice` (char '+' *> p) 
    `choice` p 

number :: Parser BenValue 
number = BenNumber <$> (char 'i' *> (signed decimal) <* char 'e') 

list :: Parser BenValue 
list = BenList <$> (char 'l' *> (many value) <* char 'e') 

dict :: Parser BenValue 
dict = do 
    char 'd' 
    pair <- many ((,) <$> string <*> value) 
    char 'e' 
    let pair' = (\(BenString s, v) -> (s,v)) <$> pair 
    let map' = Map.fromList pair' 
    unit $ BenDict map' 

value = string `choice` number `choice` list `choice` dict 

3つの源the blogのソースコードから理解コード読み取り/の混合である、the library:これは私は(解析段階、先に長い旅のまま)、これまで持っているコードであります、およびthe bookdownload関数はパーサーから得られた "解析木"を出力するだけです。パーサーが機能したら、download関数を入力してテストしてください。

  1. パーサーは、いくつかのトレントファイルで動作していません。 :(そこ間違いなく、私が間違って参照からのコードを使用している可能性があることのチャンスです。そして、明らかに何があるかどうかを知りたい。
  2. それは「おもちゃ」の例にもcombinatorrent
  3. から選んだテストファイル上で動作します私はDebianの/ Ubuntuのなどのような実世界の急流を選択すると、これは失敗します。
  4. 私はデバッグし、何が起こっているか見てみたい、GHCiの持つデバッグはまっすぐ前方にいないようです、私が言及した:trace/:historyスタイルのデバッグを試してみましたこのdocumentでは、非常に原始的です:-)。
  5. エキスパートには私の質問があります。「どのようにデバッグするのですか?」 :-)
  6. これをデバッグする方法にどのようなヒントがありますか本当に感謝します。

ありがとうございます。

+0

私はこれは質問から少し外れていると思いますが、バイナリデータを使って作業している場合は、 'readFile'と' String'を使用するのがベストのアイデアではないかもしれません。代わりに['ByteString'](https://hackage.haskell.org/package/bytestring-0.10.8.1/docs/Data-ByteString.html)とそれに対応するすべての関数(これにはバージョンの['readFile'](http://hackage.haskell.org/package/bytestring-0.10.8.1/docs/Data-ByteString-Char8.html#v:readFile)も参照してください。 – Alec

+0

また、ポイント2で指定されたファイルでコードを試してみると、 'hGetContents:無効な引数(無効なバイトシーケンス)'(これはおそらく私の最初のコメントに関連しています)を与えてくれます。あなたはどんな出力を見ていますか? – Alec

+0

私はこれをWindowsマシンで試してみました。それが問題であれば訴訟を起こさず、別のOSでこれを試してみましょう。ありがとう。 – user3169543

答えて

3

ハスケルコードは純粋なので、他の言語よりも重要ではありません。私がいくつかのJavaコードをステップ実行するとき、私はしばしば特定の変数がどこで変更されるのかを見極めようとしています。これは、物事が不変であることを考えると、明らかにハスケルでは問題になりません。

つまり、GHCiでコードスニペットを実行して、何が起きているのかを心配することなくデバッグすることができます。実行するものがグローバルな状態を変更するか、実行するものは、深い内部私たちのプログラム。この作業モードは、期待される入力の全範囲で作業するためにゆっくりと設計を反復することでメリットがあります。

構文解析は、命令言語であっても、少し不快です。誰も戻ってくるのにNothingのパーサを実行したいとは思わない - あなたはを知りたいのですが、なぜあなたは何も返されません。そのため、ほとんどのパーサライブラリは、何が問題になったかについての情報を提供するのに役立ちます。これは、attoparsecのようなパーサーを使用するためのポイントです。また、attoparsecはデフォルトでByteStringで動作します。バイナリデータには最適です。独自のパーサ実装をロールしたい場合は、それもデバッグする必要があります。

最後に、あなたのコメントに基づいて、文字エンコーディングに問題があるようです。これはまさに我々がByteStringを持っている理由です - それはバイトのパックされたシーケンスを表します - エンコーディングはありません。 OverloadedStringsという拡張子は、普通の文字列のように見えるByteStringリテラルを作るのもかなり簡単です。

関連する問題