-- | Parse expressions with numbers and units. -- -- This module provides a basic expression grammar that parses numbers -- and units. 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 -- | Converts string to a Quantity using an expression grammar parser. parseExpr :: Parser Quantity parseExpr = spaces' >> parseExpr' <* spaces' -- parseExpr', parseTerm, parseFactor, parseExp, parseNestedExpr :: Parser Quantity -- parseExpr' = try parseTermOp <|> parseTerm -- parseTerm = try parseFactorOp <|> parseFactor -- parseFactor = try parseExpOp <|> parseExp -- parseExp = try parseNestedExpr <|> parseSymbolNum -- parseNestedExpr = spaces' >> char '(' *> spaces' >> parseExpr' <* spaces' <* char ')' <* spaces' "parseNested" -- parseExpOp, parseTermOp, parseFactorOp :: Parser Quantity -- parseExpOp = parseExp `chainl1` expOp -- parseTermOp = parseTerm `chainl1` addOp -- parseFactorOp = parseFactor `chainl1` mulOp 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) -- addOp = try parseAdd <|> parseSubtract "addOp" -- where parseAdd = char '+' >> spaces' >> return (addQuants) -- parseSubtract = char '-' >> spaces' >> return (subtractQuants) 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