I am trying to make a parser that will be able to parse expressions such as 2<3
succesfully as Oper Less (Const (IntVal 2)) (Const (IntVal 3))
while at the same time failing parseing chains such as 2 < 3 < 4
while also at the same time still being able to parse succesfully 2 2 < 5
. I have tried to use chainl1
to keep my operations left associative for both
,-
and other operations. My problem seems to be with pOperHelper
and when using pTerm
in it. It might be because I don't grasp chainl1
fully. I get the following output
ghci> parseString "2 < 3 < 4"
Left "Unexpected error"
ghci> parseString "2 < 3"
Right (Oper Less (Const (IntVal 2)) (Const (IntVal *** Exception: Prelude.read: no parse
for the MVE below:
module MVEParser (ParseError, parseString, pOper, pOperHelper, pTerm, pExpr) where
import Data.Char
import Text.ParserCombinators.ReadP
import Control.Applicative ((<|>))
type ParseError = String -- you may replace this
type Parser a = ReadP a
data Value =
IntVal Int
deriving (Eq, Show, Read)
data Op = Plus | Minus | Less | Greater
deriving (Eq, Show, Read)
data Exp =
Const Value
| Oper Op Exp Exp
deriving (Eq, Show, Read)
space :: Parser Char
space = satisfy isSpace
spaceBeforeAfter :: String -> Parser String
spaceBeforeAfter x = do spaces; str <- string x; space; return str
spaces :: Parser String
spaces = many space
symbol :: String -> Parser String
symbol = token . string
token :: Parser a -> Parser a
token combinator = (do spaces
combinator)
pExpr :: Parser Exp
pExpr = {- chainl1 pTerm pOper -}chainl1 pTerm (pOperHelper pOper)
pTerm :: Parser Exp
pTerm =
(do
skipSpaces
pv <- munch isDigit
skipSpaces
return (Const (IntVal (read pv))))
pOper :: ReadP (Exp -> Exp -> Exp)
pOper = (symbol " " >> return (Oper Plus))
<|> (symbol "-" >> return (Oper Minus))
<|> (symbol "<" >> return (Oper Less))
<|> (symbol ">" >> return (Oper Greater))
pOperHelper :: ReadP (Exp -> Exp -> Exp) -> ReadP (Exp -> Exp -> Exp)
pOperHelper op = do
operator <- op
term <- pTerm
skipSpaces
nextChar <- look
case nextChar of
(c:_) | c `elem` ['<', '>'] -> pfail
_ -> return operator
parseString input = let x = readP_to_S (do e <- pExpr; token eof; return e) input
in case x of
[(a, "")] -> Right a
_ -> Left "Unexpected error"
Why is the Prelude.read
occuring though, and is there a smarter way I can make use of chainl1
or similar and accomplish what I intend?
CodePudding user response:
chain*
parsers are specifically designed to allow things like 2 < 3 < 4
. What you want is a grammar that distinguishes between arithmetic operators and comparison operators. For example, you are currently using a grammar like
Expr -> Term (Op Term)
Term -> Digit
Op -> ' ' | '-' | '<' | '>'
Digit -> '0' | ... | '9'
when what you want is something more complicated, which creates comparison operators differently (and with lower precedence) than arithmetic operators. Something like
Expr -> ArithTerm (CompOp ArithTerm)
CompOp -> '<' | '>'
ArithTerm -> ArithTerm (ArithOp NumTerm) | NumTerm
NumTerm -> Digit
ArithOp -> ' ' | '-'
Digit -> '0' | ... | '9'
Now 2 < 3 < 4
is not allowed, because neither 2 < 3
nor 3 < 4
is an ArithTerm
, and so can't appear on one side other of <
. 1 < 2 2
is fine because 1
and 2 2
are both ArithTerm
s.