準備
前回の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を使う。以下のようにする。
| |
出力結果は前回の記事と同じなので省略。