packages feed

lhae-0.0.2: src/Model/CellExpression/Parser.hs

module Model.CellExpression.Parser (parse)
    where

import Data.List (find,intercalate,nub)
import Control.Applicative ((<$>))
import Text.ParserCombinators.Parsec ((<|>),(<?>),Parser,try)
import qualified Text.ParserCombinators.Parsec as P
import qualified Text.ParserCombinators.Parsec.Error as Error
import qualified Text.ParserCombinators.Parsec.Expr as E
import qualified Text.ParserCombinators.Parsec.Token as T
import qualified Text.ParserCombinators.Parsec.Language as L
import Model.CellContent (CellExpr (..),NamedReference(..),CompileReason(..))
import qualified Model.CellContent as CC
import I18n (__)

program,topLevelFormula,topLevelLiteral :: Parser CellExpr
expression,term,functionCall :: Parser CellExpr
number,stringExpr,list,subExpression :: Parser CellExpr
reference,cellRef,cellRangeRef :: Parser CellExpr

program = topLevelFormula <|> topLevelLiteral

topLevelLiteral = (try topLevelNumber) 
                  <|> (StringExpr <$> (P.many1 P.anyChar))

topLevelNumber = do
  whitespace
  n <- NumberExpr <$> signedDouble
  P.eof >> return n

topLevelFormula = do
  f <- P.char '=' >> whitespace >> expression
  P.eof >> return f

expression = E.buildExpressionParser table term <?> __ "formula"

table :: E.OperatorTable Char () CellExpr
table = [[ prefix "-" CC.UnaryOp]
        ,[ binary "^" CC.BinaryOp E.AssocRight]
        ,[ binary "*" CC.BinaryOp E.AssocLeft
         , binary "/" CC.BinaryOp E.AssocLeft]
        ,[ binary "+" CC.BinaryOp E.AssocLeft
         , binary "-" CC.BinaryOp E.AssocLeft]
        ]          
    where
      binary sign f = E.Infix $ do {reservedOp sign; return $ f sign}
      prefix sign f = E.Prefix $ do {reservedOp sign; return $ f sign}

term = subExpression <|> functionCall <|> number <|> list 
       <|> stringExpr <|> reference <?> __ "formula"

functionCall = do { name <- identifier
                  ; arg <- number <|> stringExpr <|> list 
                       <|> reference <|> subExpression <?> __ "argument"
                  ; return $ Call name arg
                  } <?> __ "function call"

number = (NumberExpr <$> double) <?> __ "number"

stringExpr = (StringExpr <$> stringLiteral) <?> __ "string"

list = do { symbol "["
          ; l <- P.sepBy1 expression $ symbol ","
          ; symbol "]"
          ; return $ ListExpr l
          } <?> "list"

reference = try cellRangeRef <|> try cellRef <?> __ "reference"

cellRef = do { string "$"
             ; row <- P.many1 (P.noneOf [','])
             ; string ","
             ; column <- P.many1 (P.noneOf ['$'])
             ; string "$"
             ; whitespace
             ; case (row,column) of
                 ("_",c) -> return $ NamedReference $ NamedColumn c
                 (r,"_") -> return $ NamedReference $ NamedRow r
                 _ -> return $ NamedReference $ NamedCell (row,column)
             } <?> __ "cell reference"

cellRangeRef = do { NamedReference (NamedCell from) <- cellRef
                  ; symbol ":"
                  ; NamedReference (NamedCell to) <- cellRef
                  ; return $ NamedReference $ NamedRange (from,to)
                  } <?> __ "cell range reference"

subExpression = do { subExpr <- P.between (symbol "(") (symbol ")") expression
                   ; return $ Sub subExpr
                   } <?> __ "subexpression"

lexer :: T.TokenParser ()
lexer = T.makeTokenParser $ L.emptyDef 
        { L.opStart  = P.oneOf "+-*/~^:"
        , L.opLetter = P.oneOf "+-*/~^:"
        , L.reservedOpNames = ["+","-","*","/","~","^",":"]
        , L.reservedNames = []}

double :: Parser Double
double = do
  f <- T.naturalOrFloat lexer
  case f of 
    Left i -> return $ realToFrac i
    Right d -> return d

signedDouble :: Parser Double
signedDouble = 
    let signed = do symbol "-"
                    ((*) (-1)) <$> double
    in
      P.choice [signed,double]

reservedOp :: String -> Parser ()
reservedOp = T.reservedOp lexer

whitespace :: Parser ()
whitespace = T.whiteSpace lexer

symbol :: String -> Parser ()
symbol s = T.symbol lexer s >> return ()

identifier :: Parser String
identifier = T.identifier lexer

stringLiteral :: Parser String
stringLiteral = T.stringLiteral lexer

string :: String -> Parser ()
string s = P.string s >> return ()

parse :: String -> CellExpr
parse "" = EmptyExpr
parse input = 
    case (P.parse program "" input) of
      Left e -> CompileErrorExpr $ ParseError $ errorMessage e
      Right x  -> x

errorMessage :: Error.ParseError -> String
errorMessage parseError =
    let errors = Error.errorMessages parseError
        Just unexpected = find (\e -> case e of
                                        Error.SysUnExpect _ -> True
                                        Error.UnExpect _ -> True
                                        _ -> False) errors
        expected = filter (\e -> case e of 
                                   Error.Expect "" -> False
                                   Error.Expect _ -> True
                                   _ -> False
                          ) errors
        message e = 
            case e of
              Error.SysUnExpect "" -> __ "end of input"
              Error.SysUnExpect a -> a
              Error.UnExpect "" -> __ "end of input"
              Error.UnExpect a -> a
              a -> Error.messageString a

        unexpectedMessage = unwords [ (__ "unexpected") ++ ":"
                                    , message unexpected]
        expectedMessage = 
            case expected of
              [] -> ""
              _ -> (__ "expecting") ++ " " ++  
                   (intercalate " or " $ nub $ map message expected)
    in
      case expectedMessage of
        "" -> unexpectedMessage
        _ -> unexpectedMessage ++ ", " ++ expectedMessage
--      ++ "#####" ++ show parseError