{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-}
{-# OPTIONS_GHC -fno-warn-name-shadowing #-}
module Data.UnitsOfMeasure.TH
( u
, declareBaseUnit
, declareDerivedUnit
, declareConvertibleUnit
) where
import Data.Char
import Numeric
import Text.Parse.Units
import Language.Haskell.TH
import Language.Haskell.TH.Quote
import Data.UnitsOfMeasure.Internal
import Data.UnitsOfMeasure.Convert
u :: QuasiQuoter
u = QuasiQuoter
{ quoteExp = uExp
, quotePat = uPat
, quoteType = uType
, quoteDec = uDec
}
uExp :: String -> Q Exp
uExp s
| Just (ei, s') <- readNumber s = mkLiteral ei =<< parseUnitQ s'
| otherwise = mkConversion =<< parseUnitQ s
where
mkLiteral (Left 0) Unity = [| zero |]
mkLiteral (Right 0) Unity = [| MkQuantity 0.0 |]
mkLiteral ei expr = [| (MkQuantity :: a -> Quantity a $(reifyUnit expr))
$(litE (either integerL rationalL ei)) |]
mkConversion expr = [| MkQuantity :: a -> Quantity a $(reifyUnit expr) |]
uPat :: String -> Q Pat
uPat s
| Just (Left i, s') <- readNumber s = mkPat (integerL i) [t|Integer |] s'
| Just (Right r, s') <- readNumber s = mkPat (rationalL r) [t|Rational|] s'
| otherwise = error "unable to parse literal"
where
mkPat l t s' = [p| MkQuantity $(litP l) |] `sigP` [t| Quantity $t $(uType s') |]
uType :: String -> Q Type
uType s = reifyUnit =<< parseUnitQ s
parseUnitQ :: String -> Q (UnitExp () String)
parseUnitQ s = case parseUnit universalSymbolTable s of
Right expr -> return expr
Left err -> fail ("unable to parse unit expression \"" ++ s ++ "\": " ++ err)
reifyUnit :: UnitExp () String -> Q Type
reifyUnit Unity = [t| One |]
reifyUnit (Unit _ s) = [t| MkUnit $(litT (strTyLit s)) |]
reifyUnit (u `Mult` v) = [t| $(reifyUnit u) *: $(reifyUnit v) |]
reifyUnit (u `Div` v) = [t| $(reifyUnit u) /: $(reifyUnit v) |]
reifyUnit (u `Pow` n) | n >= 0 = [t| $(reifyUnit u) ^: $(litT (numTyLit n)) |]
| otherwise = [t| One /: $(reifyUnit u) ^: $(litT (numTyLit (- n))) |]
uDec :: String -> Q [Dec]
uDec s = case parseUnitDecs s of
Just xs -> concat <$> mapM (uncurry declareUnit) xs
Nothing -> reportError ("unable to parse unit declarations: " ++ s) >> return []
data UnitDecl = BaseUnit
| DefinedUnit (UnitExp () String)
| ConversionUnit Rational (UnitExp () String)
parseUnitDecs :: String -> Maybe [(String, UnitDecl)]
parseUnitDecs = go
where
go [] = Just []
go (c:xs) | isSpace c || c == ',' = go xs
go xs = case span isAlpha xs of
([], _) -> Nothing
(u, ys) -> go' u ys
go' u [] = Just [(u, BaseUnit)]
go' u (c:xs) | isSpace c = go' u xs
go' u (',':xs) = ((u, BaseUnit) :) <$> go xs
go' u ('=':xs) = let (d, ys) = break (== ',') xs
in case readNumber d of
Just (ei, s)
| not (all isSpace s)
-> case parseUnit universalSymbolTable s of
Right e -> ((u, ConversionUnit (either fromInteger id ei) e) :) <$> go ys
_ -> Nothing
_ -> case parseUnit universalSymbolTable d of
Right e -> ((u, DefinedUnit e) :) <$> go ys
Left _ -> Nothing
go' _ _ = Nothing
declareUnit :: String -> UnitDecl -> Q [Dec]
declareUnit s ud = case ud of
BaseUnit -> [d| type instance MkUnit $(litT (strTyLit s)) = Base $(litT (strTyLit s))
instance HasCanonicalBaseUnit $(litT (strTyLit s))
|]
DefinedUnit u -> [d| type instance MkUnit $(litT (strTyLit s)) = $(reifyUnit u) |]
ConversionUnit _ (Unit Nothing s') | s == s'
-> reportError ("cannot define cyclic convertible unit: " ++ s) >> return []
ConversionUnit r u -> [d| type instance MkUnit $(litT (strTyLit s)) = Base $(litT (strTyLit s))
instance HasCanonicalBaseUnit $(litT (strTyLit s)) where
type CanonicalBaseUnit $(litT (strTyLit s)) = $(reifyUnit u)
conversionBase _ = MkQuantity $(litE (rationalL (recip r)))
|]
declareBaseUnit :: String -> Q [Dec]
declareBaseUnit s = declareUnit s BaseUnit
declareDerivedUnit :: String -> String -> Q [Dec]
declareDerivedUnit s d = case parseUnit universalSymbolTable d of
Right e -> declareUnit s (DefinedUnit e)
Left _ -> reportError ("unable to parse derived unit: " ++ d) >> return []
declareConvertibleUnit :: String -> Rational -> String -> Q [Dec]
declareConvertibleUnit derived r base = case parseUnit universalSymbolTable base of
Right e -> declareUnit derived (ConversionUnit r e)
Left _ -> reportError ("unable to parse convertible unit: " ++ base) >> return []
readNumber :: String -> Maybe (Either Integer Rational, String)
readNumber s
| [(r, s')] <- reads s = Just (Right r, s')
| [(i, s')] <- reads s = Just (Left i , s')
| [(r, s')] <- readSigned readFloat s = Just (Right r, s')
| otherwise = Nothing