The parser I’m struggling to make work is this snippet:
parseIP : Parser IPAddress
parseIP =
succeed IPAddress
|= int
|. symbol "."
|= int
|. symbol "."
|= int
|. symbol "."
|= int
This parser looks reasonable on first blush, but the parser fails as |= int complains that there’s a period following the number and returns the ExpectingInt error. This is problematic in my use case as each component of an IP Address is an int, but also it’s delimited by a period.
I tried to wrap my head around the elm/parser docs, including using the number API to no avail. I’m pretty much stumped at this point on how to return an int from the parser when that int is followed by a . .
Sometimes parsers like int or variable cannot do exactly what you need. The “chomping” family of functions is meant for that case!
So I would use getChompedString, chompWhile and andThen to parse and validate each part:
type alias IPv4 =
{ a : Int
, b : Int
, c : Int
, d : Int
, mask : Int
}
segment : Parser Int
segment =
getChompedString (chompWhile Char.isDigit)
|> andThen checkSegment
checkSegment : String -> Parser Int
checkSegment str =
case String.toInt str of
Just n ->
if n >= 0 && n <= 255 then
succeed n
else
problem "invalid segment value"
Nothing ->
problem "segment is not a number"
mask : Parser Int
mask =
getChompedString (chompWhile Char.isDigit)
|> andThen checkMask
checkMask : String -> Parser Int
checkMask str =
case String.toInt str of
Just n ->
if n >= 0 && n <= 32 then
succeed n
else
problem "invalid subnet mask"
Nothing ->
problem "subnet mask is not a number"
ipv4 : Parser IPv4
ipv4 =
succeed IPv4
|= segment
|. symbol "."
|= segment
|. symbol "."
|= segment
|. symbol "."
|= segment
|. symbol "/"
|= mask
This way, you are also sure that a parsed IPv4 is valid:
You could also define an intRange parser to factorize some code:
intRange : Int -> Int -> Parser Int
intRange from to =
getChompedString (chompWhile Char.isDigit)
|> andThen (checkRange from to)
checkRange : Int -> Int -> String -> Parser Int
checkRange from to str =
case String.toInt str of
Just n ->
if n >= from && n <= to then
succeed n
else
rangeProblem from to
Nothing ->
rangeProblem from to
rangeProblem : Int -> Int -> Parser a
rangeProblem from to =
problem <|
String.join " "
[ "expected a number between"
, String.fromInt from
, "and"
, String.fromInt to
]
ipv4 : Parser IPv4
ipv4 =
succeed IPv4
|= intRange 0 255
|. symbol "."
|= intRange 0 255
|. symbol "."
|= intRange 0 255
|. symbol "."
|= intRange 0 255
|. symbol "/"
|= intRange 0 32
I ran into the same exact thing a couple weeks ago, trying to parse IP addresses and getting the unexpected behavior from the int parser. There’s an issue on elm/parser about it too.
I personally think this is a bug, but I haven’t really thought through the implications of trying to fix it.
Note that the first branch of your oneOf requires the end of the string, preventing other parsers after, but the second does not, which seems inconsistent.