packages feed

hiccup-0.40: TclObj.hs

{-# LANGUAGE BangPatterns #-}
module TclObj (
 TclObj
 ,mkTclStr
 ,mkTclBStr
 ,mkTclList
 ,mkTclList'
 ,mkTclInt
 ,mkTclDouble
 ,fromBlock
 ,fromBool
 ,empty
 ,isEmpty
 ,tclTrue
 ,tclFalse
 ,asStr
 ,asBool
 ,asInt
 ,asBStr
 ,asParsed
 ,asSeq
 ,asList
 ,asDouble
 ,(.==)
 ,strEq
 ,strNe
 ,trim
 ,objconcat
 ,tclObjTests ) where

import qualified BSParse as P
import qualified Data.ByteString.Char8 as BS
import Control.Monad
import RToken
import Util
import qualified Data.Sequence as S
import qualified Data.Foldable as F

import Test.HUnit

data TclObj = TclInt !Int BString |
              TclDouble !Double BString |
              TclList !(S.Seq TclObj) BString |
              TclBStr !BString (Maybe Int) (Either String Parsed) deriving (Show,Eq)

mkTclStr s  = mkTclBStr (pack s)
mkTclBStr s = TclBStr s (maybeInt s) (tryParsed s)
{-# INLINE mkTclBStr #-}

mkTclList l  = TclList (S.fromList l) (fromList (map asBStr l))
mkTclList' l = TclList l (fromList (map asBStr (F.toList l)))

fromBlock s p = TclBStr s (maybeInt s) p

maybeInt s = case BS.readInt (dropWhite s) of
                Nothing    -> Nothing
                Just (i,r) -> if BS.null r || BS.null (dropWhite r) then Just i else Nothing

strEq (TclInt i1 _) (TclInt i2 _) = i1 == i2
strEq o1            o2            = asBStr o1 == asBStr o2 

strNe (TclInt i1 _) (TclInt i2 _) = i1 /= i2
strNe o1            o2            = asBStr o1 /= asBStr o2 


mkTclInt !i = TclInt i bsval
 where bsval = pack (show i)
{-# INLINE mkTclInt #-}

mkTclDouble :: Double -> TclObj
mkTclDouble !d = TclDouble d bsval
 where bsval = pack (show d)

empty = TclBStr BS.empty Nothing (Left "bad parse")
isEmpty (TclInt _ _) = False
isEmpty (TclDouble _ _) = False
isEmpty v = BS.null (asBStr v)


tclTrue = mkTclInt 1
tclFalse = mkTclInt 0

fromBool !b = if b then tclTrue else tclFalse

trim = BS.reverse . dropWhite . BS.reverse . dropWhite

class ITObj o where
  asBool :: o -> Bool
  asInt :: (Monad m) => o -> m Int
  asBStr :: o -> BString
  asSeq   :: (Monad m) => o -> m (S.Seq o)

bstrAsInt bs = case BS.readInt bs of
               Nothing    -> fail ("Bad int: " ++ show bs)
               Just (i,r) -> if BS.null r then return i else fail ("Bad int: " ++ show bs)

bstrAsSeq st = liftM S.fromList (asListS st)
{-
instance ITObj BS.ByteString where
  asStr = BS.unpack
  asBool bs = (bs `elem` trueValues)
  asInt = bstrAsInt
  asBStr = id
  asParsed s = either fail return (resultToEither (P.runParse s) s)
  asSeq st = liftM S.fromList (asListS st)

-}

asList :: (Monad m) => TclObj -> m [TclObj]
asList o = liftM F.toList (asSeq o)

asStr o = unpack (asBStr o)

instance ITObj TclObj where
  asBool (TclList _ bs) = bs `elem` trueValues
  asBool (TclInt i _)     = i /= 0
  asBool (TclDouble d _)     = d /= 0.0
  asBool (TclBStr bs _ _) = bs `elem` trueValues

  asInt (TclInt i _) = return i
  asInt (TclDouble _ b) = fail $ "expected integer, got " ++ show b
  asInt (TclBStr _ (Just i) _) = return i
  asInt (TclBStr v Nothing _) = fail $ "Bad int: " ++ show v
  asInt (TclList _ v)         = bstrAsInt v

  asBStr (TclBStr s _ _) = s
  asBStr (TclInt _ b) = b
  asBStr (TclDouble _ b) = b
  asBStr (TclList _ b) = b
  {-# INLINE asBStr #-}

  asSeq (TclBStr s _ _) = bstrAsSeq s >>= return . fmap mkTclBStr
  asSeq (TclList l _)   = return l
  asSeq v               = return (S.singleton v)

instance Parseable TclObj where
  asParsed (TclBStr _ _ (Left f))  = fail f
  asParsed (TclBStr _ _ (Right r)) = return r
  asParsed (TclInt _ b) = return (singleTok b)
  asParsed (TclDouble _ b)  = return (singleTok b)
  asParsed (TclList _ s) = asParsed s
  {-# INLINE asParsed #-}

asDouble :: (Monad m) => TclObj -> m Double
asDouble (TclDouble d _) = return $! d
asDouble obj = do
  case asInt obj of 
    Just i  -> return $! (fromIntegral i)
    Nothing -> let strval = asStr obj 
               in case reads strval of
                 [(d,"")] -> return $! d -- TODO: not quite right.
                 _ -> fail $ "expected float but got " ++ show strval

fromList l = (map listEscape l)  `joinWith` ' '

asListS s = case P.parseList s of
               Nothing  -> fail $ "list parse failure: " ++ show s
               Just lst -> return lst


trueValues = map pack ["1", "true", "yes", "on"]

(.==) :: TclObj -> String -> Bool
(.==) bs str = (asBStr bs) == pack str
{-# INLINE (.==) #-}

objconcat :: [TclObj] -> TclObj
objconcat = mkTclBStr . (`joinWith` ' ') . map (trim . asBStr) 

-- # TESTS # --

testTrim = TestList [
     trim (int 10) ?=> "10",
     trim (str "10") ?=> "10",
     trim (str "     10 ") ?=> "10",
     trim (str "\t10\t") ?=> "10",
     trim (str "  \t    ") ?=> "",
     trim (str "     10 ") ?=> "10"
   ]
 where (?=>) s e = s ~=? (BS.pack e)
       int = asBStr . mkTclInt
       str = asBStr . mkTclStr

testAsBool = TestList [
   tclTrue `is` True,
   tclFalse `is` False,
   (fromBool True) `is` True,
   (int 1) `is` True,
   (int 0) `is` False,
   (int 2) `is` True,
   (int (-1)) `is` True,
   (str "true") `is` True,
   (str "yes") `is` True,
   (str "on") `is` True,
   (fromBool False) `is` False
  ]
 where is a b = asBool a ~=? b
       int i = mkTclInt i
       str s = mkTclStr s

tclObjTests = TestList [ testTrim, testAsBool ]

-- # ENDTESTS # --