packages feed

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

module Calculator.Parser.Expr (parseExpr) where

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

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

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

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

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

parseExpr :: Parser Expr
parseExpr = do
  term <- parseTerm
  rest <- parseRestExpr
  if null rest
    then return term
    else return $ 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
  if null rest
    then return fact
    else return $ 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
  if constEq (Constant 1) (snd pow)
    then return val
    else return $ 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)

--------------------------------------------------------------------------------
-- val -> func? ( expr ) | number
-- Parentheses can be parsed as function calls with no function

parseVal :: Parser Expr
parseVal = parseCall <|> parseVariable <|> parseConstant

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

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

parseCall :: Parser Expr
parseCall = do
  ident <- optionMaybe parseId
  _     <- char '('
  expr  <- parseExpr
  _     <- char ')'
  return $ case ident of
             Nothing -> expr
             Just s  -> Function s expr

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