packages feed

dao-0.1.0.1: src/Dao/Lib/Array.hs

-- "src/Dao/Lib/Array.hs"  built-in array object
-- 
-- Copyright (C) 2008-2014  Ramin Honary.
-- This file is part of the Dao System.
--
-- The Dao System is free software: you can redistribute it and/or
-- modify it under the terms of the GNU General Public License as
-- published by the Free Software Foundation, either version 3 of the
-- License, or (at your option) any later version.
-- 
-- The Dao System is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
-- 
-- You should have received a copy of the GNU General Public License
-- along with this program (see the file called "LICENSE"). If not, see
-- <http://www.gnu.org/licenses/agpl.html>.

-- | This module provides the Dao programming language implementation of the 'Array' built-in data
-- type. 'Array's can be indexed from zero to (n-1) where n is the number of elements in the array.
-- Reads and updates are O(n). Resizing an array is not possible.
module Dao.Lib.Array where

import           Dao.String
import           Dao.Predicate
import           Dao.Interpreter

import           Control.Applicative
import           Control.Monad
import           Control.Monad.IO.Class
import           Control.Monad.State

import           Data.Array.IO
import           Data.Monoid
import           Data.Typeable

import qualified Data.IntMap          as I

----------------------------------------------------------------------------------------------------

-- | The Dao programming language wrapper around Haskell's 'Data.Array.IO.IOArray' data type.
-- To inspect elements of the array outside of the IO monad, the only way for pure functions like
-- 'Dao.PPrint.PPrint' and 'Dao.Binary.Put' to work without being unsafe is to lazily evaluate these
-- functions after every update to the array. These functions are evaluated extra-lazily to ensure
-- that nothing actually happens until it becomes absolutely necessary to use them, if it ever is
-- necessary.
newtype Array = Array { toObjArray :: IOArray Int Object } deriving (Eq, Typeable)

arrayFromList :: Int -> [(Int, Object)] -> IO Array
arrayFromList len ox = do
  arr <- newArray (0, len) ONull
  forM_ ox $ \ (i, o) -> writeArray arr i o
  return $ Array{ toObjArray=arr }

arraySize :: Array -> IO Int
arraySize arr = uncurry subtract <$> getBounds (toObjArray arr)

arrayElems :: Array -> IO [Object]
arrayElems = getElems . toObjArray

arrayCheckIndex :: Array -> Int -> IO (Predicate ExecControl ())
arrayCheckIndex arr i = do
  bounds <- getBounds (toObjArray arr)
  return $
    if inRange bounds i
    then OK ()
    else PFail $ newError{ execErrorMessage=ustr "array index out of bounds" }

arrayLookup :: Array -> Int -> IO (Predicate ExecControl Object)
arrayLookup arr i = arrayCheckIndex arr i >>= \result -> case result of
  PFail err -> return (PFail err)
  Backtrack -> return Backtrack
  OK     () -> OK <$> readArray (toObjArray arr) i

arrayUpdate :: Array -> Int -> Object -> IO (Predicate ExecControl ())
arrayUpdate arr i o = arrayCheckIndex arr i >>= \result -> case result of
  PFail err -> return $ PFail err
  Backtrack -> return Backtrack
  OK     () -> OK <$> writeArray (toObjArray arr) i o

instance ReadIterable Array Object where
  readForLoop (Array arr) f = liftIO (getBounds arr) >>=
    flip execForM_ (liftIO . readArray arr >=> f) . range

instance UpdateIterable Array (Maybe Object) where
  updateForLoop a@(Array arr) f = do
    let err = "for loop iteration attempted to delete an item from an Array"
    liftIO (getBounds arr) >>=
      flip execForM_ (\i -> liftIO (readArray arr i) >>= f . Just >>=
              maybe (fail err) return >>= liftIO . writeArray arr i) . range
    return a

instance ObjectFunctor Array Int where
  objectFMap f = do
    arr <- get
    let a = toObjArray arr
    (bounds, elems) <- liftIO $ return (,) <*> getBounds a <*> getElems a
    forM_ (zip (range bounds) elems) $ \ (i, o) -> focalPathSuffix (Subscript [obj i] NullRef) $
      withInnerLens [] (f i o) >>= \ (_, (changed, ox)) ->
        when changed $ forM_ ox (liftIO . uncurry (writeArray $ toObjArray arr))

instance ObjectFunctor Array  Object  where { objectFMap f = objectFMap (\i -> f (obj i)) }
instance ObjectFunctor Array [Object] where { objectFMap f = objectFMap (\i -> f [obj i]) }

_objToInt :: [Object] -> Exec Int
_objToInt ix = case ix of
  [i] -> (derefObject i >>= xmaybe . fromObj) <|>
    throwBadTypeError "Array index value cannot be cast to integer" i []
  ix  -> throwArityError "Array index is not one-dimensional" 1 ix []

arrayFromArgs :: [Object] -> IO Array
arrayFromArgs lists =
  forM lists
    (\o -> maybe (return (1, [o])) id $ msum $
        [ fromObj o >>= \ox  -> Just $ return (length ox, ox)
        , fromObj o >>= \arr -> Just $ return (,) <*> arraySize arr <*> arrayElems arr
        ]
    ) >>= uncurry arrayFromList . (\ (ix, ox) -> (sum ix, zip [0..] $ concat ox)) . unzip

loadLibrary_Array :: DaoSetup
loadLibrary_Array = do
  daoClass (haskellType::Array)
  daoFunction "Array" $
    daoFunc
    { funcAutoDerefParams=True
    , daoForeignFunc = \ () -> fmap (flip (,) () . Just . obj) . liftIO . arrayFromArgs
    }

instance ObjectClass Array where { obj=new; fromObj=objFromHata; }

instance HataClass Array where
  haskellDataInterface = interface "Array" $ do
    autoDefEquality >> autoDefReadIterable >> autoDefUpdateIterable >> autoDefTraverse
    defSizer (fmap OInt . liftIO . arraySize)
    defIndexer $ \arr i -> _objToInt i >>= liftIO . arrayLookup arr >>= predicate
    defIndexUpdater $ \i f -> focusLiftExec (_objToInt i) >>= \i -> do
      arr <- get
      (_arr, (changed, o)) <- focusLiftExec (liftIO (arrayLookup arr i) >>= predicate) >>=
        flip withInnerLens f . Just
      case o of
        Nothing -> fail "cannot delete items from array"
        Just  o -> when changed (liftIO $ writeArray (toObjArray arr) i o) >> return (Just o)
    let init ox = case ox of -- initializer list in round-brackets must be empty
          [] -> return (Array $ error "uninitialized Array") -- SUCCESS: return an uninitialized Array
          _  -> execThrow "cannot initialize array with parameters" ExecErrorUntyped [] -- FAIL
    let fromList m i maxbnd arr ox = case ox of
          []   -> liftIO $ arrayFromList maxbnd (I.assocs m)
          o:ox -> case o of
            InitAssign ref op o -> do
              i <- derefObject ref >>= _objToInt . return
              if i<0
              then execThrow "assigned to negative index value" op [(assertFailed, OInt i)]
              else do
                ref <- pure $ fromObj ref <|> Just (RefObject ref NullRef)
                o <- evalUpdateOp ref op o (I.lookup i m)
                case o of
                  Just  o -> do
                    a <- fromList (I.insert i o m) (i+1) (max i maxbnd) arr ox
                    return $ arr{ toObjArray=toObjArray a }
                  Nothing -> execThrow "index to Array evaluated to void" op $
                    maybe [] (\ref -> [(errOfReference, obj ref)]) ref
            InitSingle o -> fromList (I.insert i o m) (i+1) (max i maxbnd) arr ox
    defInitializer init (fromList mempty 0 0)