準備
前回のsrc/IPAddr.elm
を全て消し、内容を以下の通りにする。
|
|
$ elm repl > import Parser exposing (..) > import IPAddr exposing (..)
Parserの基本
以下の2つのステップに分かれる。
- Parserを作る
- Parserを実行する -
Parser.run
を用いる
ライブラリでは、標準で用意されているParserと、それらを組み合わせて新たなParserを作るための関数が用意されている。
> run int "123" Ok 123 : Result (List Parser.DeadEnd) Int > run int "123abc" Ok 123 : Result (List Parser.DeadEnd) Int > run int "abc123abc" Err [{ col = 1, problem = ExpectingInt, row = 1 }] : Result (List Parser.DeadEnd) Int
succeed
何もパースせず、決まった結果だけを返すパーサー。
> run (succeed "Hello") "123abcde" Ok "Hello" : Result (List Parser.DeadEnd) String
パーサーを組み合わせるときの基本になる。
|.と|=
|.
演算子は、右辺のParserの結果を無視した新しいParserを返す。|=
演算子は、右辺のParserの結果を左辺のParserの値に適用した新しいParserを返す。
何を言っているのかわかりづらいと思うので例を示す。
|
|
> run parser "1 2" Ok 3 : Result (List DeadEnd) Int
以下の型はParser (Int -> Int -> Int)
である。
|
|
左辺のParser (Int -> Int -> Int)
の値に右辺のParser Int
の値Int
を適用すると、結果の型はParser (Int -> Int)
となる。
|
|
|. spaces
によって、スペースはパースの結果に影響しない。結果の型はParser (Int -> Int)
のまま。
|
|
左辺のParser (Int -> Int)
の値に右辺のParser Int
の値Int
を適用すると、結果の型はParser Int
となる。
|
|
カスタム型やレコードも関数みたいなものなので、次のようにしてパース結果を各フィールドに入れることができる。
|
|
> run parser "1 2" Ok (TwoInt 1 2)
fromString作成(失敗例)
ということで次のように定義すればIPアドレスがパースできそうである。
|
|
ところが、これはうまくいかない。
> fromString "192.168.1.1" Nothing : Maybe IPAddr > run ipParser "192.168.1.1" Err [{ col = 1, problem = ExpectingInt, row = 1 }] : Result (List DeadEnd) IPAddr
これは、ピリオドのせいでIntではなくFloatと認識されてしまったことが原因。実際、ピリオドではなく他の文字で代用するとうまく動く。
Chompers
「elm parser period」でググったら、まったく同じ悩みを持っている人がいた。そこを参考にしつつ自分でコードを書く。
結局、標準搭載のint
は使わずに、数字を一文字ずつ読み取っていく戦略をとる。そのために、elm/parserのChompersの関数群の力を借りる。
chompWhile
ある条件を満たしている間読み進めるだけのパーサを作成する。「数字が現れている間読み続ける」パーサは以下のように定義できる。
|
|
getChompedString
chompWhileで読み進めた値をString値として取得するParserを作る。上の関数は次のように書き直せる。
|
|
andThen
digitで得られた値はString型なので、これをInt型に変換した新しいParserが欲しい。ついでに、値が[0,255]に収まっているかどうかもチェックして、不正ならエラーを吐くようにしたい。これはParser.andThen
関数で実現できる。パースによって得られた値に対して、条件をチェックしたり、値を変換したりするために利用される。単に値を変換するだけなら、Parser.map
を利用しても良い。
|
|
fromStringの実装
作ったbyte
を使う。以下のようにする。
|
|
出力結果は前回の記事と同じなので省略。