packages feed

inferno-core-0.1.0.0: src/Inferno/Eval.hs

{-# LANGUAGE MonoLocalBinds #-}
{-# LANGUAGE ScopedTypeVariables #-}

module Inferno.Eval where

-- import Control.Monad (foldM, when)
import Control.Monad.Catch (MonadCatch, SomeException, try)
import Control.Monad.Except
  ( Except,
    ExceptT,
    MonadError (throwError),
    forM,
    runExcept,
    runExceptT,
  )
import Control.Monad.Identity (Identity)
import Control.Monad.Reader (ask, local)
import Data.Foldable (foldrM)
import Data.List.NonEmpty (NonEmpty (..), toList)
import qualified Data.Map as Map
import Data.Maybe (catMaybes)
import qualified Data.Text as Text
import Inferno.Eval.Error
  ( EvalError (AssertionFailed, RuntimeError),
  )
import Inferno.Module.Builtin (enumBoolHash)
import Inferno.Types.Syntax
  ( BaseType (..),
    Expr (..),
    ExtIdent (..),
    Ident (..),
    ImplExpl (..),
    InfernoType (TBase),
    Lit (LDouble, LHex, LInt, LText),
    Pat (..),
    tListToList,
    toEitherList,
  )
import Inferno.Types.Value
  ( ImplEnvM,
    Value
      ( VArray,
        VDouble,
        VEmpty,
        VEnum,
        VFun,
        VInt,
        VOne,
        VText,
        VTuple,
        VTypeRep,
        VWord64
      ),
    runImplEnvM,
  )
import Inferno.Types.VersionControl (VCObjectHash)
import Inferno.Utils.Prettyprinter (renderPretty)
import Prettyprinter
  ( LayoutOptions (LayoutOptions),
    PageWidth (Unbounded),
    Pretty (pretty),
    layoutPretty,
  )
import Prettyprinter.Render.Text (renderStrict)

type TermEnv hash c m = (Map.Map ExtIdent (Value c m), Map.Map hash (Value c m))

type Interpreter t = Except EvalError t

emptyTmenv :: TermEnv hash c m
emptyTmenv = (Map.empty, Map.empty)

eval :: (MonadError EvalError m, MonadError EvalError (ImplEnvM m c), Pretty c) => TermEnv VCObjectHash c (ImplEnvM m c) -> Expr (Maybe VCObjectHash) a -> ImplEnvM m c (Value c (ImplEnvM m c))
eval env@(localEnv, pinnedEnv) expr = case expr of
  Lit_ (LInt k) -> return $
    VFun $ \case
      VTypeRep (TBase TInt) -> return $ VInt k
      VTypeRep (TBase TDouble) -> return $ VDouble $ fromIntegral k
      _ -> throwError $ RuntimeError "Invalid runtime rep for numeric constant."
  Lit_ (LDouble k) -> return $ VDouble k
  Lit_ (LHex w) -> return $ VWord64 w
  Lit_ (LText t) -> return $ VText t
  InterpolatedString_ es -> do
    res <- forM (toEitherList es) $ either (return . VText) (\(_, e, _) -> eval env e)
    return $ VText $ Text.concat $ map toText res
    where
      toText (VText t) = t
      toText e = renderStrict $ layoutPretty (LayoutOptions Unbounded) $ pretty e
  Array_ es ->
    foldrM (\(e, _) vs -> eval env e >>= return . (: vs)) [] es >>= return . VArray
  ArrayComp_ e srcs mCond -> do
    vals <- sequence' env srcs
    VArray <$> case mCond of
      Nothing ->
        forM vals $ \vs ->
          let nenv = foldr (uncurry Map.insert) localEnv vs in eval (nenv, pinnedEnv) e
      Just (_, cond) ->
        catMaybes
          <$> ( forM vals $ \vs -> do
                  let nenv = foldr (uncurry Map.insert) localEnv vs
                  eval (nenv, pinnedEnv) cond >>= \case
                    VEnum hash "true" ->
                      if hash == enumBoolHash
                        then Just <$> (eval (nenv, pinnedEnv) e)
                        else throwError $ RuntimeError "failed to match with a bool"
                    VEnum hash "false" ->
                      if hash == enumBoolHash
                        then return Nothing
                        else throwError $ RuntimeError "failed to match with a bool"
                    _ -> throwError $ RuntimeError "failed to match with a bool"
              )
    where
      sequence' :: (MonadError EvalError m, Pretty c) => TermEnv VCObjectHash c (ImplEnvM m c) -> NonEmpty (a, Ident, a, Expr (Maybe VCObjectHash) a, Maybe a) -> ImplEnvM m c [[(ExtIdent, Value c (ImplEnvM m c))]]
      sequence' env'@(localEnv', pinnedEnv') = \case
        (_, Ident x, _, e_s, _) :| [] -> do
          eval env' e_s >>= \case
            VArray vals -> return $ map ((: []) . (ExtIdent $ Right x,)) vals
            _ -> throwError $ RuntimeError "failed to match with an array"
        (_, Ident x, _, e_s, _) :| (r : rs) -> do
          eval env' e_s >>= \case
            VArray vals ->
              concat
                <$> ( forM vals $ \v -> do
                        res <- sequence' (Map.insert (ExtIdent $ Right x) v localEnv', pinnedEnv') (r :| rs)
                        return $ map ((ExtIdent $ Right x, v) :) res
                    )
            _ -> throwError $ RuntimeError "failed to match with an array"
  Enum_ (Just hash) _ i -> return $ VEnum hash i
  Enum_ Nothing _ _ -> throwError $ RuntimeError "All enums must be pinned"
  Var_ (Just hash) _ x ->
    case Map.lookup hash pinnedEnv of
      Just v -> return v
      Nothing -> throwError $ RuntimeError $ show x <> "(" <> show hash <> ") not found in the pinned env"
  Var_ Nothing _ (Expl x) -> do
    case Map.lookup x localEnv of
      Just v -> return v
      Nothing -> throwError $ RuntimeError $ show x <> " not found in the unpinned env"
  Var_ Nothing _ (Impl x) -> do
    implEnv <- ask
    case Map.lookup x implEnv of
      Just v -> return v
      Nothing -> throwError $ RuntimeError $ show x <> " not found in the implicit env"
  OpVar_ (Just hash) _ x ->
    case Map.lookup hash pinnedEnv of
      Just v -> return v
      Nothing -> throwError $ RuntimeError $ show x <> "(" <> show hash <> ") not found in the pinned env"
  OpVar_ Nothing _ (Ident x) -> do
    case Map.lookup (ExtIdent $ Right x) localEnv of
      Just v -> return v
      Nothing -> throwError $ RuntimeError $ show x <> " not found in env"
  TypeRep_ t -> pure $ VTypeRep t
  Op_ _ Nothing _ op _ -> throwError $ RuntimeError $ show op <> " should be pinned"
  Op_ a (Just hash) _ns op b -> do
    a' <- eval env a
    b' <- eval env b
    case Map.lookup hash pinnedEnv of
      Nothing -> throwError $ RuntimeError $ show op <> "(" <> show hash <> ") not found in the pinned env"
      Just (VFun f) ->
        f a' >>= \case
          VFun f' -> f' b'
          _ -> throwError $ RuntimeError $ show op <> " not bound to a binary function in env"
      Just _ -> throwError $ RuntimeError $ show op <> " not bound to a function in env"
  PreOp_ Nothing _ op _ -> throwError $ RuntimeError $ show op <> " should be pinned"
  PreOp_ (Just hash) _ns op a -> do
    a' <- eval env a
    case Map.lookup hash pinnedEnv of
      Nothing -> throwError $ RuntimeError $ show op <> "(" <> show hash <> ") not found in the pinned env"
      Just (VFun f) -> f a'
      Just _ -> throwError $ RuntimeError $ show op <> " not bound to a function in env"
  Lam_ args body -> go localEnv $ toList args
    where
      go nenv = \case
        [] -> eval (nenv, pinnedEnv) body
        (_, Just x) : xs ->
          return $ VFun $ \arg -> go (Map.insert x arg nenv) xs
        (_, Nothing) : xs -> return $ VFun $ \_arg -> go nenv xs
  App_ fun arg -> do
    eval env fun >>= \case
      VFun f -> do
        argv <- eval env arg
        f argv
      _ -> throwError $ RuntimeError "failed to match with a function"
  Let_ (Expl x) e body -> do
    e' <- eval env e
    let nenv = Map.insert x e' localEnv
    eval (nenv, pinnedEnv) body
  Let_ (Impl x) e body -> do
    e' <- eval env e
    local (\impEnv -> Map.insert x e' impEnv) $ eval env body
  If_ cond tr fl ->
    eval env cond >>= \case
      VEnum hash "true" ->
        if hash == enumBoolHash
          then eval env tr
          else throwError $ RuntimeError "failed to match with a bool"
      VEnum hash "false" ->
        if hash == enumBoolHash
          then eval env fl
          else throwError $ RuntimeError "failed to match with a bool"
      _ -> throwError $ RuntimeError "failed to match with a bool"
  Tuple_ es ->
    foldrM (\(e, _) vs -> eval env e >>= return . (: vs)) [] (tListToList es) >>= return . VTuple
  One_ e -> eval env e >>= return . VOne
  Empty_ -> return $ VEmpty
  Assert_ cond e ->
    eval env cond >>= \case
      VEnum hash "false" ->
        if hash == enumBoolHash
          then throwError AssertionFailed
          else throwError $ RuntimeError "failed to match with a bool"
      VEnum hash "true" ->
        if hash == enumBoolHash
          then eval env e
          else throwError $ RuntimeError "failed to match with a bool"
      _ -> throwError $ RuntimeError "failed to match with a bool"
  Case_ e pats -> do
    v <- eval env e
    matchAny v pats
    where
      matchAny v ((_, p, _, body) :| []) = case match v p of
        Just nenv -> eval nenv body
        Nothing -> throwError $ RuntimeError $ "non-exhaustive patterns in case detected in " <> (Text.unpack $ renderPretty v)
      matchAny v ((_, p, _, body) :| (r : rs)) = case match v p of
        Just nenv -> eval nenv body
        Nothing -> matchAny v (r :| rs)

      match v p = case (v, p) of
        (_, PVar _ (Just (Ident x))) -> Just $ (Map.insert (ExtIdent $ Right x) v localEnv, pinnedEnv)
        (_, PVar _ Nothing) -> Just env
        (VEnum h1 _, PEnum _ (Just h2) _ _) ->
          if h1 == h2
            then Just env
            else Nothing
        (VInt i1, PLit _ (LInt i2)) ->
          if i1 == i2
            then Just env
            else Nothing
        (VDouble d1, PLit _ (LDouble d2)) ->
          if d1 == d2
            then Just env
            else Nothing
        (VText t1, PLit _ (LText t2)) ->
          if t1 == t2
            then Just env
            else Nothing
        (VWord64 h1, PLit _ (LHex h2)) ->
          if h1 == h2
            then Just env
            else Nothing
        (VOne v', POne _ p') -> match v' p'
        (VEmpty, PEmpty _) -> Just env
        (VTuple vs, PTuple _ ps _) -> matchTuple vs $ tListToList ps
        _ -> Nothing

      matchTuple [] [] = Just env
      matchTuple (v' : vs) ((p', _) : ps) = do
        env1 <- match v' p'
        env2 <- matchTuple vs ps
        -- since variables in patterns must be linear,
        -- env1 and env2 should not overlap
        return $ env1 <> env2
      matchTuple _ _ = Nothing
  CommentAbove _ e -> eval env e
  CommentAfter e _ -> eval env e
  CommentBelow e _ -> eval env e
  Bracketed_ e -> eval env e
  RenameModule_ _ _ e -> eval env e
  OpenModule_ _ _ e -> eval env e

runEvalIO ::
  (MonadCatch m, Pretty c) =>
  ImplEnvM (ExceptT EvalError m) c (TermEnv VCObjectHash c (ImplEnvM (ExceptT EvalError m) c)) ->
  Map.Map ExtIdent (Value c (ImplEnvM (ExceptT EvalError m) c)) ->
  Expr (Maybe VCObjectHash) a ->
  m (Either EvalError (Value c (ImplEnvM (ExceptT EvalError m) c)))
runEvalIO env implicitEnv ex = do
  input <- try $ runExceptT $ runImplEnvM implicitEnv $ (env >>= \env' -> eval env' ex)
  return $ case input of
    Left (e :: SomeException) -> Left $ RuntimeError $ show e
    Right res -> res

pureEval ::
  (Pretty c) =>
  TermEnv VCObjectHash c (ImplEnvM (ExceptT EvalError Identity) c) ->
  Map.Map ExtIdent (Value c (ImplEnvM (ExceptT EvalError Identity) c)) ->
  Expr (Maybe VCObjectHash) a ->
  Either EvalError (Value c (ImplEnvM (ExceptT EvalError Identity) c))
pureEval env implicitEnv ex = runExcept $ runImplEnvM implicitEnv $ eval env ex