module Data.Quantities.ExprParser where
import Control.Applicative ((<*>), (<$>), (*>), (<*))
import Data.Either (partitionEithers)
import qualified Data.Map as M
import Numeric (readFloat)
import Text.ParserCombinators.Parsec
import Data.Quantities.Convert (addQuants, subtractQuants, convert)
import Data.Quantities.Data
spaces' :: Parser String
spaces' = many $ char ' '
parseExprQuant :: Definitions -> String ->
Either (QuantityError Double) (Quantity Double)
parseExprQuant d input = case parse (parseExpr d) "arithmetic" input of
Left err -> Left $ ParserError $ show err
Right val -> val
type EQuant = Either (QuantityError Double) (Quantity Double)
parseExpr :: Definitions -> Parser EQuant
parseExpr d = (try (parseConvertExpr d) <|> parseSingle) <* eof
where parseSingle = spaces >> parseExpr' d <* spaces
parseConvertExpr :: Definitions -> Parser EQuant
parseConvertExpr d = do
_ <- spaces'
exp1 <- parseExpr' d <* spaces
exp2 <- string "=>" >> spaces >> parseExpr' d
_ <- spaces'
return $ do
e1 <- exp1
e2 <- exp2
u2 <- units <$> exp2
case magnitude e2 of
1.0 -> convert e1 u2
_ -> Left $ ScalingFactorError e2
parseExpr', parseTerm :: Definitions -> Parser EQuant
parseFactor, parseExpt, parseNestedExpr :: Definitions -> Parser EQuant
parseExpr' d = try (parseTermOp d) <|> parseTerm d
parseTerm d = try (parseFactorOp d) <|> parseFactor d
parseFactor d = try (parseExptOp d) <|> parseExpt d
parseExpt d = try (parseNestedExpr d) <|> parseESymbolNum d
parseNestedExpr d = spaces' >> char '(' *> spaces' >>
parseExpr' d
<* spaces' <* char ')' <* spaces' <?> "parseNested"
parseExptOp, parseTermOp, parseFactorOp :: Definitions -> Parser EQuant
parseExptOp d = parseExpt d `chainl1` exptOp
parseTermOp d = parseTerm d `chainl1` addOp
parseFactorOp d = parseFactor d `chainl1` mulOp
exptOp, addOp, mulOp :: Parser (EQuant -> EQuant -> EQuant)
addOp = try parseAdd <|> parseSubtract <?> "addOp"
where parseAdd = char '+' >> spaces' >> return addEQuants
parseSubtract = char '-' >> spaces' >> return subtractEQuants
mulOp = try parseTimes <|> try parseDiv <|> parseImplicitTimes <?> "mulOp"
where parseTimes = char '*' >> spaces' >> return multiplyEQuants
parseDiv = divOp >> spaces' >> return divideEQuants
where divOp = try (string "/") <|> string "per "
parseImplicitTimes = return multiplyEQuants
exptOp = try (opChoice >> spaces' >> return exptEQuants) <?> "expOp"
where opChoice = string "^" <|> string "**"
addEQuants :: EQuant -> EQuant -> EQuant
addEQuants (Right a) (Right b) = addQuants a b
addEQuants (Left a) _ = Left a
addEQuants _ (Left b) = Left b
subtractEQuants :: EQuant -> EQuant -> EQuant
subtractEQuants (Right a) (Right b) = subtractQuants a b
subtractEQuants (Left a) _ = Left a
subtractEQuants _ (Left b) = Left b
multiplyEQuants :: EQuant -> EQuant -> EQuant
multiplyEQuants (Right a) (Right b) = Right $ multiplyQuants a b
multiplyEQuants (Left a) _ = Left a
multiplyEQuants _ (Left b) = Left b
divideEQuants :: EQuant -> EQuant -> EQuant
divideEQuants (Right a) (Right b) = Right $ divideQuants a b
divideEQuants (Left a) _ = Left a
divideEQuants _ (Left b) = Left b
exptEQuants :: EQuant -> EQuant -> EQuant
exptEQuants (Left a) _ = Left a
exptEQuants _ (Left b) = Left b
exptEQuants (Right q) (Right (Quantity y (CompoundUnit _ []))) = Right $ exptQuants q y
exptEQuants a b = Left $ ParserError $ "Used non-dimensionless exponent in " ++ showq
where showq = unwords ["(", show a, ") ** (", show b, ")"]
parseESymbolNum :: Definitions -> Parser EQuant
parseESymbolNum d = try (parseENum d) <|> parseESymbol d
parseESymbol :: Definitions -> Parser EQuant
parseESymbol d = do
q <- parseSymbol'
return $ preprocessQuantity d q
parseENum :: Definitions -> Parser EQuant
parseENum d = do
q <- parseNum
return $ Right $ q { units = (units q) { defs = d } }
preprocessQuantity :: Definitions -> Quantity Double -> EQuant
preprocessQuantity d (Quantity x us)
| null errs = Right $ Quantity x (CompoundUnit d us')
| otherwise = Left $ head errs
where ppUnits = map (preprocessUnit d) (sUnits us)
(errs, us') = partitionEithers ppUnits
preprocessUnit :: Definitions -> SimpleUnit -> Either (QuantityError Double) SimpleUnit
preprocessUnit d (SimpleUnit s _ p)
| rs `elem` unitsList d = Right $ SimpleUnit ns np p
| otherwise = Left $ UndefinedUnitError s
where (rp, rs) = prefixParser d s
np = prefixSynonyms d M.! rp
ns = synonyms d M.! rs
prefixParser :: Definitions -> String -> (String, String)
prefixParser d input = if input `elem` unitsList d
then ("", input)
else case parse (prefixParser' d) "arithmetic" input of
Left _ -> ("", input)
Right val -> splitAt (length val) input
prefixParser' :: Definitions -> Parser String
prefixParser' d = do
pr <- choice $ map (try . string) (prefixes d)
_ <- choice $ map (try . string) (unitsList d)
return pr
parseMultExpr :: Parser (Quantity Double)
parseMultExpr = spaces' >> parseMultExpr' <* spaces'
parseMultExpr', parseMultFactor, parseMultExpt, parseMultNestedExpr :: Parser (Quantity Double)
parseMultExpr' = try parseMultFactorOp <|> parseMultFactor
parseMultFactor = try parseMultExptOp <|> parseMultExpt
parseMultExpt = try parseMultNestedExpr <|> parseSymbolNum
parseMultNestedExpr = spaces' >> char '(' *> spaces' >>
parseMultExpr'
<* spaces' <* char ')' <* spaces' <?> "parseNested"
parseMultExptOp, parseMultFactorOp :: Parser (Quantity Double)
parseMultExptOp = parseMultExpt `chainl1` exptMultOp
parseMultFactorOp = parseMultFactor `chainl1` mulMultOp
exptMultOp, mulMultOp :: Parser (Quantity Double -> Quantity Double -> Quantity Double)
mulMultOp = try parseTimes <|> try parseDiv <|> parseImplicitTimes <?> "mulMultOp"
where parseTimes = char '*' >> spaces' >> return multiplyQuants
parseDiv = char '/' >> spaces' >> return divideQuants
parseImplicitTimes = return multiplyQuants
exptMultOp = try (opChoice >> spaces' >> return exptMultQuants') <?> "expMultOp"
where opChoice = string "^" <|> string "**"
exptMultQuants' :: (Quantity Double -> Quantity Double -> Quantity Double)
exptMultQuants' q (Quantity y (CompoundUnit _ [])) = exptQuants q y
exptMultQuants' a b = error $ "Used non-dimensionless exponent in " ++ showq
where showq = unwords ["(", show a, ") ** (", show b, ")"]
parseSymbolNum :: Parser (Quantity Double)
parseSymbolNum = try parseNum <|> parseSymbol'
parseSymbol' :: Parser (Quantity Double)
parseSymbol' = do
neg <- option "" $ string "-"
symf <- letter
rest <- many (alphaNum <|> char '_')
_ <- spaces'
return $ baseQuant (timesSign neg 1) [SimpleUnit (symf : rest) "" 1]
parseNum :: Parser (Quantity Double)
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