packages feed

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

{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE NamedFieldPuns #-}

module Inferno.Module
  ( Module (..),
    PinnedModule,
    BuiltinModuleHash (..),
    BuiltinFunHash (..),
    BuiltinEnumHash (..),
    buildPinnedQQModules,
    combineTermEnvs,
    pinnedModuleNameToHash,
    pinnedModuleHashToTy,
    pinnedModuleTerms,
    ToValue (..),
  )
where

import Control.Monad (foldM)
import Control.Monad.Except (MonadError)
import Data.Bifunctor (bimap)
import Data.Foldable (foldl')
import qualified Data.IntMap as IntMap
import qualified Data.Map as Map
import qualified Data.Set as Set
import Inferno.Eval (TermEnv, eval)
import Inferno.Eval.Error (EvalError)
import Inferno.Infer (inferExpr)
import Inferno.Infer.Env (Namespace (..), TypeMetadata (..))
import Inferno.Infer.Pinned (pinExpr)
import qualified Inferno.Infer.Pinned as Pinned
import Inferno.Module.Cast (ToValue (..))
import Inferno.Parse (OpsTable, TopLevelDefn (..))
import Inferno.Types.Module
  ( BuiltinEnumHash (..),
    BuiltinFunHash (..),
    BuiltinModuleHash (..),
    Module (..),
    PinnedModule,
    pinnedModuleHashToTy,
    pinnedModuleNameToHash,
    pinnedModuleTerms,
  )
import Inferno.Types.Syntax
  ( Expr (..),
    ExtIdent (..),
    Ident (..),
    ImplExpl (..),
    ModuleName,
    Scoped (..),
    SigVar (..),
    sigVarToExpr,
  )
import Inferno.Types.Type
  ( BaseType (TEnum),
    ImplType (..),
    InfernoType (TBase),
    TCScheme (..),
  )
import Inferno.Types.Value (ImplEnvM, Value)
import Inferno.Types.VersionControl (Pinned (..), VCObjectHash, pinnedToMaybe, vcHash)
import Prettyprinter (Pretty)
import Text.Megaparsec (SourcePos)

combineTermEnvs ::
  MonadError EvalError m =>
  Map.Map ModuleName (PinnedModule (ImplEnvM m c (TermEnv VCObjectHash c (ImplEnvM m c)))) ->
  ImplEnvM m c (TermEnv VCObjectHash c (ImplEnvM m c))
combineTermEnvs modules = foldM (\env m -> (env <>) <$> pinnedModuleTerms m) mempty $ Map.elems modules

buildPinnedQQModules ::
  (MonadError EvalError m, Pretty c) =>
  [(ModuleName, OpsTable, [TopLevelDefn (Either (TCScheme, ImplEnvM m c (Value c (ImplEnvM m c))) (Maybe TCScheme, Expr () SourcePos))])] ->
  Map.Map ModuleName (PinnedModule (ImplEnvM m c (TermEnv VCObjectHash c (ImplEnvM m c))))
buildPinnedQQModules modules =
  snd $
    foldl'
      ( \(alreadyPinnedModulesMap, alreadyBuiltModules) (moduleNm, opsTable, sigs) ->
          -- first build the new module
          let newMod =
                buildModule alreadyPinnedModulesMap alreadyBuiltModules sigs $
                  Module
                    { moduleName = moduleNm,
                      moduleOpsTable = opsTable,
                      moduleTypeClasses = mempty,
                      moduleObjects = (Map.singleton (ModuleNamespace moduleNm) $ vcHash $ BuiltinModuleHash moduleNm, mempty, pure mempty)
                    }
           in -- then insert it into the temporary module pin map as well as the final module map
              ( Pinned.insertHardcodedModule moduleNm (Map.map Builtin $ pinnedModuleNameToHash newMod) alreadyPinnedModulesMap,
                Map.insert moduleNm newMod alreadyBuiltModules
              )
      )
      mempty
      modules
  where
    buildModule ::
      (MonadError EvalError m, Pretty c) =>
      Map.Map (Scoped ModuleName) (Map.Map Namespace (Pinned VCObjectHash)) ->
      Map.Map ModuleName (PinnedModule (ImplEnvM m c (TermEnv VCObjectHash c (ImplEnvM m c)))) ->
      [TopLevelDefn (Either (TCScheme, ImplEnvM m c (Value c (ImplEnvM m c))) (Maybe TCScheme, Expr () SourcePos))] ->
      PinnedModule (ImplEnvM m c (TermEnv VCObjectHash c (ImplEnvM m c))) ->
      PinnedModule (ImplEnvM m c (TermEnv VCObjectHash c (ImplEnvM m c)))
    buildModule _ _ [] m = m
    buildModule alreadyPinnedModulesMap alreadyBuiltModules (Signature {..} : xs) m@Module {moduleName, moduleObjects = (nsMap, tyMap, mTrmEnv)} =
      let sigVarToNamespace = \case
            SigVar n -> FunNamespace $ Ident n
            SigOpVar n -> OpNamespace $ Ident n
          (sig, ns, hsh, mTrmEnv') = case def of
            Left (sig', mVal) ->
              let ns' = sigVarToNamespace name
                  hsh' = vcHash $ BuiltinFunHash (sigVarToExpr LocalScope name, sig)
               in (sig', ns', hsh', (\val (local, pinned) -> (local, Map.insert hsh val pinned)) <$> mVal <*> mTrmEnv)
            Right (_mSig, expr) ->
              let pinMap =
                    Pinned.openModule moduleName $
                      Pinned.insertHardcodedModule
                        moduleName
                        (Map.map Builtin nsMap)
                        alreadyPinnedModulesMap
                  pinnedExpr = either (error . show) id $ pinExpr pinMap expr
                  inferEnv = Map.insert moduleName m $ alreadyBuiltModules
                  (pinnedExpr', sig') =
                    either (\err -> error $ "Could not infer the type of this expression: " <> show err) (\(e, typ, _) -> (e, typ)) $
                      inferExpr inferEnv $ pinnedExpr
                  ns' = sigVarToNamespace name
                  hsh' = vcHash $ BuiltinFunHash (sigVarToExpr LocalScope name, sig)
                  mVal =
                    combineTermEnvs alreadyBuiltModules >>= \env ->
                      mTrmEnv >>= \env' -> eval (env <> env') $ bimap pinnedToMaybe id pinnedExpr'
               in (sig', ns', hsh', (\val (local, pinned) -> (local, Map.insert hsh val pinned)) <$> mVal <*> mTrmEnv)
       in buildModule alreadyPinnedModulesMap alreadyBuiltModules xs $
            m
              { moduleObjects =
                  ( Map.insert ns hsh nsMap,
                    Map.insert
                      hsh
                      TypeMetadata
                        { identExpr = sigVarToExpr (Scope moduleName) name,
                          docs = documentation,
                          ty = sig
                        }
                      tyMap,
                    mTrmEnv'
                  )
              }
    buildModule alreadyPinnedModulesMap alreadyBuiltModules (TypeClassInstance tCl : xs) m@Module {moduleTypeClasses = tCls} =
      buildModule alreadyPinnedModulesMap alreadyBuiltModules xs m {moduleTypeClasses = Set.insert tCl tCls}
    buildModule alreadyPinnedModulesMap alreadyBuiltModules (Export modNm : xs) Module {moduleName, moduleOpsTable = opsTable, moduleTypeClasses = tyCls, moduleObjects = (nsMap, tyMap, mTrmEnv)} =
      case Map.lookup modNm alreadyBuiltModules of
        Nothing -> error $ "buildModule: Module " <> show modNm <> " does not exist."
        Just Module {moduleOpsTable = opsTable', moduleTypeClasses = tyCls', moduleObjects = (nsMap', tyMap', mTrmEnv')} ->
          buildModule
            alreadyPinnedModulesMap
            alreadyBuiltModules
            xs
            Module
              { moduleName,
                moduleOpsTable = IntMap.unionWith (<>) opsTable opsTable',
                moduleTypeClasses = tyCls <> tyCls',
                moduleObjects = (nsMap <> nsMap', tyMap <> tyMap', mTrmEnv >>= \x -> mTrmEnv' >>= \y -> pure $ x <> y)
              }
    buildModule alreadyPinnedModulesMap alreadyBuiltModules (EnumDef doc nm cs : xs) m@Module {moduleObjects = (nsMap, tyMap, mTrmEnv)} =
      let enumTy = ForallTC [] Set.empty $ ImplType Map.empty $ TBase $ TEnum nm $ Set.fromList cs
          hsh = vcHash $ BuiltinEnumHash enumTy
          nms = TypeNamespace (Ident nm) : [EnumNamespace c | c <- cs]
       in buildModule alreadyPinnedModulesMap alreadyBuiltModules xs $
            m
              { moduleObjects =
                  ( Map.fromList [(n, hsh) | n <- nms] `Map.union` nsMap,
                    Map.insert
                      hsh
                      TypeMetadata
                        { identExpr = Var () () LocalScope (Expl $ ExtIdent $ Right "_"),
                          docs = doc,
                          ty = enumTy
                        }
                      tyMap,
                    mTrmEnv
                  )
              }