packages feed

octopus-0.0.2.0: Language/Octopus.hs

{-# LANGUAGE OverloadedStrings #-}
module Language.Octopus where

import Import
import qualified Data.Sequence as Seq
import qualified Data.Map as Map

import Control.Monad.State
import Control.Monad.Reader
import System.IO.Error
import Control.Concurrent.MVar

import Language.Octopus.Data
import Language.Octopus.Parser (parseOctopusFile)
import qualified Language.Octopus.Primitive as Oct
import Language.Octopus.Basis
import Language.Octopus.Data.Shortcut
import Language.Octopus.Libraries


eval :: ImportsCache -> Val -> Val -> IO Val
eval cache env code = evalStateT (runReaderT (reduce code) cache) startState
    where
    startState = MState { environ = env --Ob Map.empty
                        , control = [NormK []]
                        , nextTag = startTag
                        }


reduce :: Val -> Machine Val
reduce x@(Nm _) = done x
reduce x@(By _) = done x
reduce x@(Tx _) = done x
reduce x@(Fp _) = done x
reduce x@(Tg _ _) = done x
reduce x@(Ab _ _) = done x
reduce x@(Cl _ _ _) = done x
reduce x@(Ce _) = done x
reduce x@(Ar _) = done x
--reduce x@(Eh _ _) = done x
--reduce x@(Ex _ _) = done x
reduce x@(Pr _) = done x
reduce sq@(Sq xs) = case toList xs of
    [] -> done sq
    (x:xs) -> push (Es [] xs) >> reduce x
reduce ob@(Ob m) = case ensureCombination ob of
    Just (f, x) -> push (Op x) >> reduce f
    Nothing -> case Map.toList m of
                [] -> done (mkOb [])
                ((k,v):xs) -> push (Eo k [] xs) >> reduce v
reduce (Sy x) = gets environ >>= \env -> case Oct.resolveSymbol x env of
    Just val -> done val
    Nothing -> do
        env <- gets environ
        raise (getTag exnScopeError, mkSq [exnScopeError, Sy x, env])

combine :: Val -> Val -> Machine Val
combine (Pr Vau) x = case x of
    Sq xs -> case toList xs of
        [var, ast] -> do
            env <- gets environ
            done $ Cl var ast env
        _ -> raise $ mkTypeError (Pr Vau) "(Pat, *)" x
    _ -> raise $ mkTypeError (Pr Vau) "(Pat, *)" x
combine f@(Pr _) x = push (Ap f) >> reduce x
combine f x = case ensureClosure f of --all of these are operatives
    Just (var, ast, env) -> do
        caller <- gets environ
        let x' = mkSq [caller, x]
        case Oct.match var x' of
            Right env' -> swapEnv (env' `Oct.extend` env) >> reduce ast
            Left err -> raise err
    _ -> raise $ mkTypeError (Tx "#<apply>") "* → *" f

apply :: Val -> Val -> Machine Val
apply (Pr Eval) x = case ensureThunk x of
    Just (env, ast) -> swapEnv env >> reduce ast
    _ -> raise $ mkTypeError (Pr Eval) "Thunk *" x
apply (Pr Ifz) x = case x of
    Sq xs -> case toList xs of
        [p, c, a] -> done $ Oct.ifz p c a
        _ -> tyErr
    _ -> tyErr
    where tyErr = raise $ mkTypeError (Pr Ifz) "(*, *, *)" x
apply (Pr Imp) x = impFile x
apply (Pr Extends) x = case x of
    Sq xs -> case toList xs of
        [] -> done $ mkOb []
        xs -> done $ foldr1 Oct.extend xs
    _ -> raise $ mkTypeError (Pr Extends) "[*]" x
apply (Pr MkTag) x = case x of
    Tx spelling -> done =<< mkTag spelling
    _ -> raise $ mkTypeError (Pr MkTag) "Tx" x
apply (Pr Handle) x = case x of
    Sq xs -> case toList xs of
        [tag, handler, body] -> case tag of
            Tg i _ -> pushK (HndlK i handler) >> reduce body
            _ -> tyErr
        _ -> tyErr
    _ -> tyErr
    where tyErr = raise $ mkTypeError (Pr Handle) "∀ r. (Tg, * → r, `r)" x --FIXME give the type as a monad
apply (Pr Raise) x = case x of
    Sq xs -> case toList xs of
        [tag, payload] -> case tag of
            Tg i _ -> curry raise i payload
            _ -> tyErr
        _ -> tyErr
    _ -> tyErr
    where tyErr = raise $ mkTypeError (Pr Raise) "(Tg, *)" x
apply (Pr pr) x | pr `elem` (map fst table) = do
    result <- liftIO $ (fromJust $ lookup pr table) x
    case result of
        Right val -> done val
        Left err -> raise err
    where
    table =
        [ (OpenFp, Oct.openFp)
        , (ReadFp, Oct.readFp)
        , (WriteFp, Oct.writeFp)
        , (FlushFp, Oct.flushFp)
        , (CloseFp, Oct.closeFp)
        ]
apply (Pr pr) x =
    case lookup pr table of
        Just f -> case f pr x of
            Right val -> done val
            Left err -> raise err
        Nothing -> error $ "INTERNAL ERROR (Octopus.apply): unknown primitive " ++ show pr
    where
    table = 
        [ (Match, binary Oct.match "TODO")
        
        , (Delete, binary Oct.delete "TODO")
        , (Keys, unary Oct.keys)
        , (Get, binary Oct.get "TODO")
        
        , (Eq, binary Oct.eq "(*, *)")
        , (Neq, binary Oct.neq "(*, *)")
        , (Lt, binary Oct.lt "(Nm, Nm)")
        , (Lte, binary Oct.lte "(Nm, Nm)")
        , (Gt, binary Oct.gt "(Nm, Nm)")
        , (Gt, binary Oct.gt "(Nm, Nm)")
    
        , (Add, binary Oct.add "(Nm, Nm)")
        , (Sub, binary Oct.sub "(Nm, Nm)")
        , (Mul, binary Oct.mul "(Nm, Nm)")
        , (Div, binary Oct.div "(Nm, Nm)")
    
        , (Len, unary Oct.len)
        , (Cat, binary Oct.cat "∀ f :: Sq * | Tx | By. (f, f)")
        , (Cut, binary Oct.cut "(Sq * | Tx | By, Nat)")
    
        , (Numer, unary Oct.numer)
        , (Denom, unary Oct.denom)
        , (NumParts, unary Oct.numParts)
        ]
    binary :: (Val -> Val -> Fallible Val) -> Text -> Primitive -> Val -> Fallible Val
    binary op ty pr x = case x of
        Sq xs -> case toList xs of
            [a, b] -> op a b
            _ -> tyErr
        _ -> tyErr
        where tyErr = Left $ mkTypeError (Pr pr) ty x
    unary :: (Val -> Fallible Val) -> Primitive -> Val -> Fallible Val
    unary op pr x = op x
--TODO Ks
--TODO apply objects that have a __call__ field, passing the object as the first parameter
--TODO unwrap Ab and try the __call__ protocol trick
apply f x = error "TODO apply"

done :: Val -> Machine Val
done x = do
    k <- gets control
    case k of
        [] -> return x
        (NormK []):_                        -> pop >> done x
        (NormK (Re _:_)):_                  -> pop >> done x
        (NormK (Es xs []:_)):_              -> pop >> done (Sq . Seq.fromList $ reverse (x:xs))
        (NormK (Es xs (x':xs'):_)):_        -> replace (Es (x:xs) xs') >> reduce x'
        (NormK (Eo k xs []:_)):_            -> pop >> done (Ob . Map.fromList $ (k,x):xs)
        (NormK (Eo k xs ((k',x'):xs'):_)):_ -> replace (Eo k' ((k,x):xs) xs') >> reduce x'
        (NormK (Op arg:ks)):kss             -> pop >> combine x arg
        (NormK (Ap f:ks)):kss               -> pop >> apply f x
        (HndlK _ _):kss                     -> pop >> done x
        (ImptK _ slot):kss                  -> liftIO (slot `putMVar` Right x) >> pop >> done x


raise :: Exn -> Machine Val
raise (tg, payload) = do
    (top, rest) <- splitStack tg
    case rest of
        [] -> error $ "unhandled exception: " ++ show (tg, payload) --return here
        (ImptK file slot):kss -> do
            let err = (getTag exnImportError, mkSq [exnImportError, Tx file, payload])
            liftIO $ slot `putMVar` Left err
            modify $ \s -> s { control = kss }
            raise err
        (HndlK _ handler):kss -> do
            modify $ \s -> s { control = kss }
            reduce $ mkCall handler payload


impFile :: Val -> Machine Val
impFile (Tx pathstr) = do
    let path = pathstr --FIXME normalize path
        path' = unpack path
    --TODO check against builtin files, or else pre-populate the cache
    --TODO check the path against the current imports on stack to avoid circular import
    cache_var <- ask
    cache <- liftIO $ takeMVar cache_var
    case Map.lookup path cache of
        Just loaded_var -> do
            liftIO $ cache_var `putMVar` cache
            val_e <- liftIO $ readMVar loaded_var
            case val_e of
                Right val -> done val
                Left err -> raise err
        Nothing -> do
            loading_var <- liftIO newEmptyMVar
            liftIO $ cache_var `putMVar` Map.insert path loading_var cache
            contents_e <- liftIO $ tryIOError $ readFile path'
            case contents_e of
                Right contents -> do
                    case parseOctopusFile path' contents of
                        Right (_, val) -> do
                            swapEnv initialEnv --TODO consider which env to start a file off with
                            pushK (ImptK path loading_var)
                            reduce val
                        Left raw_err -> do
                            let err = (getTag exnSyntaxError, mkSq [exnSyntaxError, Tx . pack $ show raw_err])
                            liftIO $ loading_var `putMVar` Left err
                            raise err
                Left raw_err -> do
                    --TODO format the IOError the way I want it, not the way Haskell gives it
                    let err = (getTag exnIOError, mkSq [exnIOError, Tx . pack $ show raw_err])
                    liftIO $ loading_var `putMVar` Left err
                    raise err
impFile x = raise $ mkTypeError (Pr Imp) "Tx" x