haskell - Json parser, incorrectly parsing string as a number -
i'm still pretty new haskell , functional programming in general, i'm writing small program parsec parse json , pretty print means of learning basic concepts. have far:
import text.parsec import text.parsec.string data jvalue = jstring string | jnumber double | jbool bool | jnull | jobject [(string, jvalue)] | jarray [jvalue] deriving (eq, ord, show) parsejstring, parsejnumber, parsejbool, parsejnull :: parser jvalue parsejstring = str <- between (char '"') (char '"') (many (noneof "\"")) return . jstring $ str parsejnumber = num <- many digit return . jnumber . read $ num parsejbool = val <- string "true" <|> string "false" case val of "true" -> return (jbool true) "false" -> return (jbool false) parsejnull = string "null" >> return jnull parsejvalue :: parser jvalue parsejvalue = parsejstring <|> parsejnumber <|> parsejbool <|> parsejnull
for now, i'm assuming numbers integers. individually, parsejstring
, parsejnumber
, parsejbool
, , parsejnull
work expected in ghci. additionally, parsejvalue
correctly parses strings , numbers.
ghci> parse parsejstring "test" "\"test input\"" right (jstring "test input") ghci> parse parsejnumber "test" "345" right (jnumber 345.0) ghci> parse parsejbool "test" "true" right (jbool true) ghci> parse parsejnull "test" "null" right jnull ghci> parse parsejvalue "test" "\"jvalue test\"" right (jstring "jvalue test") ghci> parse parsejvalue "test" "789" right (jnumber 789.0)
parsejvalue
fails, however, when try parse true
, false
, or null
, , interesting error.
ghci> parse parsejvalue "test" "true" right (jnumber *** exception: prelude.read: no parse
i successful parse, parse returns jnumber
followed error stating prelude.read failed. feel i'm missing core concept in building parsers, can't see i've gone wrong. also, making beginner mistakes code, i.e. of considered "bad" haskell?
the problem usage of many
in parsejnumber
. valid parse, when no character of following string consumed ("many p applies parser p 0 or more times. [...]"). need many1
:
parsejnumber = num <- many1 (oneof "0123456789") return $ jnumber (read num :: double)
edit:
somehow, think combination of (.)
, ($)
looks kind of weird. use (.) when can rid of function parameter (like in usage of (>>=)
) , ($) when i'm lazy write parentheses. in function parsejstring
not need (.)
in order right binding precedences. (i did same transformation in code above.)
parsejstring = str <- between (char '"') (char '"') (many (noneof "\"")) return $ jstring str
additionally, eliminate code-repetition refactoring parsejbool
:
parsejbool = val <- string "true" <|> string "false" return (case val of "true" -> jbool true "false" -> jbool false)
i rewrite case-construct (total) local function:
parsejbool = (string "true" <|> string "false") >>= return . tojbool -- there 2 possible strings pattern match tojbool "true" = jbool true tojbool _ = jbool false
last not least, can transform other functions use (>>=)
instead of do-blocks.
-- additionally, not need type signature `read` -- constructor `jnumber` infers correct type parsejnumber = many1 (oneof "0123456789") >>= return . jnumber . read parsejstring = between (char '"') (char '"') (many (noneof "\"")) >>= return . jstring
Comments
Post a Comment