numeric-prelude-0.4.3.3: src/Number/Physical/Read.hs
{-# LANGUAGE RebindableSyntax #-}
{- |
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 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)