{-# LANGUAGE NoImplicitPrelude #-} {- | Convert a human readable string to a physical value. -} module Number.Physical.Read where import qualified Number.Physical as Value import qualified Number.Physical.UnitDatabase as Db import qualified Algebra.VectorSpace as VectorSpace -- import Algebra.Module((*>)) import qualified Algebra.Field as Field import qualified Data.Map as Map import Data.Map (Map) import Text.ParserCombinators.Parsec import Control.Monad(liftM) import NumericPrelude.Base import NumericPrelude.Numeric mulPrec :: Int mulPrec = 7 -- How to handle the 'prec' argument? readsNat :: (Enum i, Ord i, Read v, VectorSpace.C a v) => Db.T i a -> Int -> ReadS (Value.T i v) readsNat db prec = readParen (prec>=mulPrec) (map (\(x, rest) -> let (Value.Cons cu c, rest') = readUnitPart (createDict db) rest in (Value.Cons cu (c *> x), rest')) . readsPrec mulPrec) readUnitPart :: (Ord i, Field.C a) => Map String (Value.T i a) -> String -> (Value.T i a, String) readUnitPart dict str = let parseUnit = do p <- parseProduct rest <- many anyChar return (product (map (\(unit,n) -> Map.findWithDefault (error ("unknown unit '" ++ unit ++ "'")) unit dict ^ n) p), rest) in case parse parseUnit "unit" str of Left msg -> error (show msg) Right val -> val {-| This function could also return the value, but a list of pairs (String, Integer) is easier for testing. -} parseProduct :: Parser [(String, Integer)] parseProduct = skipMany space >> ((do p <- ignoreSpace parsePower t <- parseProductTail return (p : t)) <|> parseProductTail) parseProductTail :: Parser [(String, Integer)] parseProductTail = let parseTail c f = do _ <- ignoreSpace (char c) p <- ignoreSpace parsePower t <- parseProductTail return (f p : t) in parseTail '*' id <|> parseTail '/' (\(x,n) -> (x,-n)) <|> return [] parsePower :: Parser (String, Integer) parsePower = do w <- ignoreSpace (many1 (letter <|> char '\181')) e <- liftM read (ignoreSpace (char '^') >> many1 digit) <|> return 1 return (w,e) {- Turns a parser into one that ignores subsequent whitespaces. -} ignoreSpace :: Parser a -> Parser a ignoreSpace p = do x <- p skipMany space return x createDict :: Db.T i a -> Map String (Value.T i a) createDict db = Map.fromList (concatMap (\Db.UnitSet {Db.unit = xu, Db.scales = s} -> map (\Db.Scale {Db.symbol = sym, Db.magnitude = x} -> (sym, Value.Cons xu x)) s) db)