packages feed

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

module Calculator.Parser.Expr (parseExpr) where

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

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

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

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

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

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

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

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

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

parseRestTerm :: Parser [(Operator, Expr)]
parseRestTerm = many $ do
  oper <- oneOf "*/"
  _    <- spaces
  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
  _   <- spaces
  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 '^'
               _    <- spaces
               fact <- parseFact
               return (op, fact)

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

parseVal :: Parser Expr
parseVal = do
  ch <- optionMaybe $ oneOf (map fst unaryOps) <* spaces
  v  <- parseVal'
  return $ if isNothing ch
           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 (spaces >> char '(')
  e <- parseExpr
  _ <- spaces
  _ <- char ')'
  return e

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

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

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

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