Home > OS >  Parsing left associative in Haskell for only some operations
Parsing left associative in Haskell for only some operations

Time:01-12

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 ArithTerms.

  • Related