content: module State where
  
  import Control.Monad.Except
  import Control.Monad.State
  import Data.IORef
  import Data.Map qualified as M
  import Error
  import Token qualified as T
  
  type Variables o = M.Map T.Ident (IORef o)
  
  newtype Context o = Context
    { environments :: [Variables o]
    }
  
  declare :: T.Ident -> o -> Context o -> IO (Context o)
  declare ident val (Context []) = do
    valRef <- newIORef val
    return $ Context [M.singleton ident valRef]
  declare ident val (Context (env : rest)) = do
    valRef <- newIORef val
    return $ Context (M.insert ident valRef env : rest)
  
  resolve :: T.Ident -> Context o -> IO (Either RuntimeError o)
  resolve name (Context []) = return $ Left $ NameError name
  resolve name (Context envs) = search envs
    where
      search [] = return $ Left $ NameError name
      search (m : rest) = case M.lookup name m of
        Just ref -> do
          value <- readIORef ref
          return $ Right value
        Nothing -> search rest
  
  assign :: T.Ident -> o -> Context o -> IO (Either RuntimeError (Context o))
  assign name _ (Context []) = return $ Left $ NameError name
  assign name obj (Context (env : rest)) =
    case M.lookup name env of
      Just ref -> do
        writeIORef ref obj
        return $ Right $ Context (env : rest)
      Nothing -> do
        res <- assign name obj (Context rest)
        case res of
          Right (Context newRest) -> return $ Right $ Context (env : newRest)
          Left err -> return $ Left err
  
  scope :: Context o -> Context o
  scope (Context envs) = Context (M.empty : envs)
  
  descope :: Context o -> Context o
  descope (Context (_ : rest)) = Context rest
  descope (Context []) = Context []
  
  type Interpreter o a = ExceptT RuntimeError (StateT (Context o) IO) a
  