module MOO.Types (
IntT
, FltT
, StrT
, ObjT
, ErrT
, LstT
, ObjId
, Id
, LineNo
, Type(..)
, Value(..)
, Error(..)
, zero
, emptyString
, emptyList
, fromId
, toId
, builder2text
, equal
, comparable
, truthOf
, truthValue
, typeOf
, typeCode
, intValue
, fltValue
, strValue
, objValue
, errValue
, lstValue
, toText
, toBuilder
, toBuilder'
, toLiteral
, toMicroseconds
, error2text
, fromList
, fromListBy
, stringList
, objectList
, endOfTime
, Sizeable(..)
) where
import Control.Applicative ((<$>))
import Control.Concurrent (ThreadId)
import Control.Concurrent.STM (TVar)
import Data.CaseInsensitive (CI)
import Data.Hashable (Hashable)
import Data.HashMap.Strict (HashMap)
import Data.Int (Int32, Int64)
import Data.IntSet (IntSet)
import Data.List (intersperse)
import Data.Map (Map)
import Data.Monoid (Monoid, (<>), mappend, mconcat)
import Data.String (IsString)
import Data.Text (Text)
import Data.Text.Encoding (encodeUtf8, decodeUtf8)
import Data.Text.Lazy.Builder (Builder)
import Data.Time (UTCTime)
import Data.Time.Clock.POSIX (posixSecondsToUTCTime)
import Data.Typeable (Typeable)
import Database.VCache (VCacheable(put, get), PVar)
import Foreign.Storable (sizeOf)
import System.Random (StdGen)
import qualified Data.CaseInsensitive as CI
import qualified Data.HashMap.Strict as HM
import qualified Data.IntSet as IS
import qualified Data.Map as M
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as TLB
import qualified Data.Text.Lazy.Builder.Int as TLB
import qualified Data.Text.Lazy.Builder.RealFloat as TLB
import MOO.List (MOOList)
import MOO.String (MOOString)
import qualified MOO.List as Lst
import qualified MOO.String as Str
class Sizeable t where
storageBytes :: t -> Int
instance Sizeable () where
storageBytes _ = sizeOf (undefined :: Int)
instance Sizeable Bool where
storageBytes = sizeOf
instance Sizeable Int where
storageBytes = sizeOf
instance Sizeable Int32 where
storageBytes = sizeOf
instance Sizeable Int64 where
storageBytes = sizeOf
instance Sizeable Double where
storageBytes = sizeOf
instance Sizeable Text where
storageBytes t = sizeOf 'x' * (T.length t + 1)
instance Sizeable MOOString where
storageBytes = Str.storageBytes
instance Sizeable s => Sizeable (CI s) where
storageBytes = (* 2) . storageBytes . CI.original
instance Sizeable MOOList where
storageBytes = Lst.storageBytes
instance Sizeable a => Sizeable [a] where
storageBytes = foldr bytes (storageBytes ())
where bytes x s = s + storageBytes () + storageBytes x
instance Sizeable a => Sizeable (Maybe a) where
storageBytes Nothing = storageBytes ()
storageBytes (Just x) = storageBytes () + storageBytes x
instance (Sizeable a, Sizeable b) => Sizeable (a, b) where
storageBytes (x, y) = storageBytes () + storageBytes x + storageBytes y
instance (Sizeable k, Sizeable v) => Sizeable (Map k v) where
storageBytes =
M.foldrWithKey' (\k v s -> s + storageBytes k + storageBytes v) 0
instance Sizeable StdGen where
storageBytes _ = storageBytes () + 2 * storageBytes (undefined :: Int32)
instance Sizeable UTCTime where
storageBytes _ = 4 * storageBytes ()
instance Sizeable ThreadId where
storageBytes _ = storageBytes ()
instance Sizeable IntSet where
storageBytes x = storageBytes () + storageBytes (0 :: Int) * IS.size x
instance (Sizeable k, Sizeable v) => Sizeable (HashMap k v) where
storageBytes = HM.foldrWithKey bytes (storageBytes ())
where bytes k v s = s + storageBytes k + storageBytes v
instance Sizeable (TVar a) where
storageBytes _ = storageBytes ()
instance Sizeable (PVar a) where
storageBytes _ = storageBytes ()
# ifdef MOO_64BIT_INTEGER
type IntT = Int64
# else
type IntT = Int32
# endif
type FltT = Double
type StrT = MOOString
type ObjT = ObjId
type ErrT = Error
type LstT = MOOList
type ObjId = Int
type LineNo = Int
newtype Id = Id { unId :: CI Text }
deriving (Eq, Ord, Show, Monoid, IsString,
Hashable, Sizeable, Typeable)
instance VCacheable Id where
put = put . encodeUtf8 . fromId
get = toId . decodeUtf8 <$> get
class Ident a where
fromId :: Id -> a
toId :: a -> Id
instance Ident [Char] where
fromId = T.unpack . CI.original . unId
toId = Id . CI.mk . T.pack
instance Ident Text where
fromId = CI.original . unId
toId = Id . CI.mk
instance Ident MOOString where
fromId = Str.fromText . CI.original . unId
toId = Id . CI.mk . Str.toText
instance Ident Builder where
fromId = TLB.fromText . CI.original . unId
toId = Id . CI.mk . builder2text
builder2text :: Builder -> Text
builder2text = TL.toStrict . TLB.toLazyText
data Value = Int IntT
| Flt FltT
| Str StrT
| Obj ObjT
| Err ErrT
| Lst LstT
deriving (Eq, Show, Typeable)
instance VCacheable Value where
put v = put (typeOf v) >> case v of
Int x -> put (toInteger x)
Flt x -> put $ if isNegativeZero x then Nothing else Just (decodeFloat x)
Str x -> put x
Obj x -> put (toInteger x)
Err x -> put (fromEnum x)
Lst x -> put x
get = get >>= \t -> case t of
TInt -> Int . fromInteger <$> get
TFlt -> Flt . maybe (0.0) (uncurry encodeFloat) <$> get
TStr -> Str <$> get
TObj -> Obj . fromInteger <$> get
TErr -> Err . toEnum <$> get
TLst -> Lst <$> get
_ -> fail $ "get: unknown Value type (" ++ show (fromEnum t) ++ ")"
instance Sizeable Value where
storageBytes value = case value of
Int x -> box + storageBytes x
Flt x -> box + storageBytes x
Str x -> box + storageBytes x
Obj x -> box + storageBytes x
Err x -> box + storageBytes x
Lst x -> box + storageBytes x
where box = storageBytes ()
zero :: Value
zero = Int 0
emptyString :: Value
emptyString = Str Str.empty
emptyList :: Value
emptyList = Lst Lst.empty
equal :: Value -> Value -> Bool
(Str x) `equal` (Str y) = x `Str.equal` y
(Lst x) `equal` (Lst y) = x `Lst.equal` y
x `equal` y = x == y
instance Ord Value where
(Int x) `compare` (Int y) = x `compare` y
(Flt x) `compare` (Flt y) = x `compare` y
(Str x) `compare` (Str y) = x `compare` y
(Obj x) `compare` (Obj y) = x `compare` y
(Err x) `compare` (Err y) = x `compare` y
_ `compare` _ = error "Illegal comparison"
comparable :: Value -> Value -> Bool
comparable x y = case (typeOf x, typeOf y) of
(TLst, _ ) -> False
(tx , ty) -> tx == ty
data Type = TAny
| TNum
| TInt
| TFlt
| TStr
| TObj
| TErr
| TLst
deriving (Eq, Enum, Typeable)
instance VCacheable Type where
put = put . fromEnum
get = toEnum <$> get
data Error = E_NONE
| E_TYPE
| E_DIV
| E_PERM
| E_PROPNF
| E_VERBNF
| E_VARNF
| E_INVIND
| E_RECMOVE
| E_MAXREC
| E_RANGE
| E_ARGS
| E_NACC
| E_INVARG
| E_QUOTA
| E_FLOAT
deriving (Eq, Ord, Enum, Bounded, Show)
instance Sizeable Error where
storageBytes _ = storageBytes ()
truthOf :: Value -> Bool
truthOf (Int x) = x /= 0
truthOf (Flt x) = x /= 0.0
truthOf (Str t) = not (Str.null t)
truthOf (Lst v) = not (Lst.null v)
truthOf _ = False
truthValue :: Bool -> Value
truthValue False = zero
truthValue True = Int 1
typeOf :: Value -> Type
typeOf Int{} = TInt
typeOf Flt{} = TFlt
typeOf Str{} = TStr
typeOf Obj{} = TObj
typeOf Err{} = TErr
typeOf Lst{} = TLst
typeCode :: Type -> IntT
typeCode TNum = 2
typeCode TAny = 1
typeCode TInt = 0
typeCode TObj = 1
typeCode TStr = 2
typeCode TErr = 3
typeCode TLst = 4
typeCode TFlt = 9
intValue :: Value -> Maybe IntT
intValue (Int x) = Just x
intValue _ = Nothing
fltValue :: Value -> Maybe FltT
fltValue (Flt x) = Just x
fltValue _ = Nothing
strValue :: Value -> Maybe StrT
strValue (Str x) = Just x
strValue _ = Nothing
objValue :: Value -> Maybe ObjT
objValue (Obj x) = Just x
objValue _ = Nothing
errValue :: Value -> Maybe ErrT
errValue (Err x) = Just x
errValue _ = Nothing
lstValue :: Value -> Maybe LstT
lstValue (Lst x) = Just x
lstValue _ = Nothing
toText :: Value -> Text
toText (Str x) = Str.toText x
toText (Err x) = error2text x
toText (Lst _) = "{list}"
toText v = builder2text (toBuilder v)
toBuilder :: Value -> Builder
toBuilder (Int x) = TLB.decimal x
toBuilder (Obj x) = TLB.singleton '#' <> TLB.decimal x
toBuilder (Flt x) = TLB.realFloat x
toBuilder v = TLB.fromText (toText v)
toBuilder' :: Value -> Builder
toBuilder' (Lst x) = TLB.singleton '{' <> mconcat
(intersperse ", " $ map toBuilder' $ Lst.toList x) <>
TLB.singleton '}'
toBuilder' (Str x) = quote <> Str.foldr escape quote x
where quote, backslash :: Builder
quote = TLB.singleton '"'
backslash = TLB.singleton '\\'
escape :: Char -> Builder -> Builder
escape '"' = mappend backslash . mappend quote
escape '\\' = mappend backslash . mappend backslash
escape c = mappend (TLB.singleton c)
toBuilder' (Err x) = TLB.fromString (show x)
toBuilder' v = toBuilder v
toLiteral :: Value -> Text
toLiteral = builder2text . toBuilder'
toMicroseconds :: Value -> Maybe Integer
toMicroseconds (Int secs) = Just $ fromIntegral secs * 1000000
toMicroseconds (Flt secs) = Just $ ceiling $ secs * 1000000
toMicroseconds _ = Nothing
error2text :: Error -> Text
error2text E_NONE = "No error"
error2text E_TYPE = "Type mismatch"
error2text E_DIV = "Division by zero"
error2text E_PERM = "Permission denied"
error2text E_PROPNF = "Property not found"
error2text E_VERBNF = "Verb not found"
error2text E_VARNF = "Variable not found"
error2text E_INVIND = "Invalid indirection"
error2text E_RECMOVE = "Recursive move"
error2text E_MAXREC = "Too many verb calls"
error2text E_RANGE = "Range error"
error2text E_ARGS = "Incorrect number of arguments"
error2text E_NACC = "Move refused by destination"
error2text E_INVARG = "Invalid argument"
error2text E_QUOTA = "Resource limit exceeded"
error2text E_FLOAT = "Floating-point arithmetic error"
fromList :: [Value] -> Value
fromList = Lst . Lst.fromList
fromListBy :: (a -> Value) -> [a] -> Value
fromListBy f = fromList . map f
stringList :: [StrT] -> Value
stringList = fromListBy Str
objectList :: [ObjT] -> Value
objectList = fromListBy Obj
endOfTime :: UTCTime
endOfTime = posixSecondsToUTCTime $ fromIntegral (maxBound :: Int32)