module Data.Quantities.ExprParser where
import Control.Applicative ((<*>), (<$>), (*>), (<*))
import Numeric (readFloat)
import System.Environment
import Text.ParserCombinators.Parsec
import Data.Quantities.Data (SimpleUnit(..), Quantity(..), baseQuant,
multiplyQuants, divideQuants, exptQuants)
main :: IO ()
main = do
args <- getArgs
putStrLn $ readExpr (head args)
readExpr :: String -> String
readExpr input = case parse parseExpr "arithmetic" input of
Left err -> "No match: " ++ show err
Right val -> show val
spaces' :: Parser String
spaces' = many $ char ' '
parseExprQuant :: String -> Either String Quantity
parseExprQuant input = case parse parseExpr "arithmetic" input of
Left err -> Left $ "No match: " ++ show err
Right val -> Right val
parseExpr :: Parser Quantity
parseExpr = spaces' >> parseExpr' <* spaces'
parseExpr', parseFactor, parseExp, parseNestedExpr :: Parser Quantity
parseExpr' = try parseFactorOp <|> parseFactor
parseFactor = try parseExpOp <|> parseExp
parseExp = try parseNestedExpr <|> parseSymbolNum
parseNestedExpr = spaces' >> char '(' *> spaces' >> parseExpr' <* spaces' <* char ')' <* spaces' <?> "parseNested"
parseExpOp, parseFactorOp :: Parser Quantity
parseExpOp = parseExp `chainl1` expOp
parseFactorOp = parseFactor `chainl1` mulOp
expOp, mulOp :: Parser (Quantity -> Quantity -> Quantity)
mulOp = try parseTimes <|> try parseDiv <|> parseImplicitTimes <?> "mulOp"
where parseTimes = char '*' >> spaces' >> return multiplyQuants
parseDiv = char '/' >> spaces' >> return divideQuants
parseImplicitTimes = return multiplyQuants
expOp = try (opChoice >> spaces' >> return exptQuants') <?> "expOp"
where opChoice = string "^" <|> string "**"
exptQuants' :: Quantity -> Quantity -> Quantity
exptQuants' q (Quantity y [] _) = exptQuants q y
exptQuants' a b = error $ "Used non-dimensionless exponent in " ++ showq
where showq = unwords ["(", show a, ") ** (", show b, ")"]
parseSymbolNum :: Parser Quantity
parseSymbolNum = try parseNum <|> parseSymbol'
parseSymbol' :: Parser Quantity
parseSymbol' = do
neg <- option "" $ string "-"
symf <- letter
rest <- many (alphaNum <|> char '_')
_ <- spaces'
return $ baseQuant (timesSign neg 1) [SimpleUnit (symf : rest) "" 1]
parseNum :: Parser Quantity
parseNum = do
num <- parseNum'
return $ baseQuant num []
parseNum' :: Parser Double
parseNum' = do
neg <- option "" $ string "-"
whole <- many1 digit
decimal <- option "" $ (:) <$> char '.' <*> many1 digit
exponential <- option "" parseExponential
_ <- spaces'
return $ timesSign neg $ fst $ head $ readFloat $ whole ++ decimal ++ exponential
parseExponential :: Parser String
parseExponential = do
e <- string "e"
neg <- option "" $ string "+" <|> string "-"
pow <- many1 digit
return $ e ++ neg ++ pow
timesSign :: String -> Double -> Double
timesSign sign x
| sign == "-" = x
| otherwise = x