packages feed

calculator-0.2.0.1: src/Calculator/Parser/Expr.hs

module Calculator.Parser.Expr (parseExpr) where

--------------------------------------------------------------------------------

import           Calculator.Parser.Base        (parseId, parseNumber)
import           Calculator.Prim.Definitions   (unaryOps, binaryOps)
import           Calculator.Prim.Expr          (Expr (..), Operator, constEq)

--------------------------------------------------------------------------------

import           Control.Applicative           ((<$>), (<*))
import           Text.ParserCombinators.Parsec

--------------------------------------------------------------------------------
-- expr -> term ( "+-" term )*

parseExpr :: Parser Expr
parseExpr = do
  term <- parseTerm
  rest <- parseRestExpr
  return $ if null rest
           then term
           else BinOp (term, rest)

parseRestExpr :: Parser [(Operator, Expr)]
parseRestExpr = many $ do
  oper <- oneOf "+-"
  let (Just op) = lookup oper binaryOps
  expr <- parseTerm
  return (op, expr)

--------------------------------------------------------------------------------
-- term -> fact ( "*/" fact )*

parseTerm :: Parser Expr
parseTerm = do
  fact <- parseFact
  rest <- parseRestTerm
  return $ if null rest
           then fact
           else BinOp (fact, rest)

parseRestTerm :: Parser [(Operator, Expr)]
parseRestTerm = many $ do
  oper <- oneOf "*/"
  let (Just op) = lookup oper binaryOps
  expr <- parseFact
  return (op, expr)

--------------------------------------------------------------------------------
-- fact -> val ( "^" fact )?
-- Right recursion for right associativity

parseFact :: Parser Expr
parseFact = do
  val <- parseVal
  pow <- parsePower
  return $ if constEq (Constant 1) (snd pow)
           then val
           else BinOp (val, [pow])

parsePower :: Parser (Operator, Expr)
parsePower = let (Just op) = lookup '^' binaryOps
             in option (op, Constant 1) $ do
               _    <- char '^'
               fact <- parseFact
               return (op, fact)

--------------------------------------------------------------------------------
-- Unary operators
-- val  -> $? val'

parseVal :: Parser Expr
parseVal = do
  ch <- optionMaybe $ oneOf (map fst unaryOps)
  v  <- parseVal'
  return $ if ch == Nothing
           then v
           else let Just c  = ch
                    Just op = lookup c unaryOps
                in UnOp op v

--------------------------------------------------------------------------------
-- val' -> ( expr ) | func ( expr ) | var | number
  
parseVal' :: Parser Expr
parseVal' = parseBrackets <|> parseCall <|> parseVariable <|> parseConstant

parseBrackets :: Parser Expr
parseBrackets = do
  _ <- try (char '(')
  e <- parseExpr
  _ <- char ')'
  return e

parseVariable :: Parser Expr
parseVariable = Variable <$> parseId

parseConstant :: Parser Expr
parseConstant = Constant <$> parseNumber

parseCall :: Parser Expr
parseCall = do
  ident <- try (parseId <* char '(')
  args  <- parseExpr `sepBy` char ',' <* char ')'
  return $ Call ident args

--------------------------------------------------------------------------------