module Data.Quantities.Definitions where
import Control.Monad.State
import Data.Either (partitionEithers)
import qualified Data.Map as M
import qualified Data.Set as S
import qualified Text.ParserCombinators.Parsec as P
import Data.Quantities.Convert (convertBase')
import Data.Quantities.Data
type DefineMonad = StateT Definitions (Either QuantityError)
makeDefinitions :: [Definition] -> Either QuantityError Definitions
makeDefinitions ds = execStateT (mapM addDefinition ds) emptyDefinitions
addDefinition :: Definition -> DefineMonad ()
addDefinition (PrefixDefinition sym fac syns) = do
d <- get
let newd = emptyDefinitions {
prefixes = sym : syns
, prefixValues = M.singleton sym fac
, prefixSynonyms = M.fromList $ zip (sym : syns) (repeat sym) }
defCheck = checkDefined (sym : syns) (prefixes d)
if null defCheck
then put $ d `unionDefinitions` newd
else lift . Left . PrefixAlreadyDefinedError $ head defCheck ++ "-"
addDefinition (BaseDefinition sym utype syns) = do
d <- get
let defCheck = checkDefined (sym : syns) (unitsList d)
if null defCheck
then put $ d `unionDefinitions` emptyDefinitions {
bases = M.singleton sym (1, [SimpleUnit sym "" 1])
, unitsList = sym : syns
, synonyms = M.fromList $ zip (sym : syns) (repeat sym)
, unitTypes = M.singleton sym utype }
else lift . Left $ UnitAlreadyDefinedError (head defCheck)
addDefinition (UnitDefinition sym q syns) = do
d <- get
let pq = preprocessQuantity d q
defCheck = checkDefined (sym : syns) (unitsList d)
if null defCheck
then case pq of
(Right pq') -> do
let (Quantity baseFac baseUnits _) = convertBase' d pq'
put $ d `unionDefinitions` emptyDefinitions {
bases = M.singleton sym (baseFac, baseUnits)
, synonyms = M.fromList $ zip (sym : syns) (repeat sym)
, unitsList = sym : syns }
(Left err) -> lift . Left $ err
else lift . Left $ UnitAlreadyDefinedError $ head defCheck
checkDefined :: [Symbol] -> [Symbol] -> [Symbol]
checkDefined a b = S.toList $ S.intersection (S.fromList a) (S.fromList b)
preprocessQuantity :: Definitions -> Quantity -> Either QuantityError Quantity
preprocessQuantity d (Quantity x us _)
| null errs = Right $ Quantity x us' d
| otherwise = Left $ head errs
where ppUnits = map (preprocessUnit d) us
(errs, us') = partitionEithers ppUnits
preprocessUnit :: Definitions -> SimpleUnit -> Either QuantityError 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 P.parse (prefixParser' d) "arithmetic" input of
Left _ -> ("", input)
Right val -> splitAt (length val) input
prefixParser' :: Definitions -> P.Parser String
prefixParser' d = do
pr <- P.choice $ map (P.try . P.string) (prefixes d)
_ <- P.choice $ map (P.try . P.string) (unitsList d)
return pr