packages feed

spectacle-1.0.0: src/Language/Spectacle/Syntax/Prime.hs

-- | Prime (or time) variable usage and substitution.
--
-- @since 1.0.0
module Language.Spectacle.Syntax.Prime
  ( -- * Labels
    Prime (Prime),
    Effect (PrimeVar),

    -- * Syntax
    prime,

    -- * Interpreters
    runPrime,
    substPrime,
    RuntimeState (RuntimeState, plains, primes, callStack),
    substitute,
  )
where

import Data.Coerce (coerce)
import Data.Function ((&))
import Data.Void (absurd)

import Data.Functor.Loom (hoist, runLoom, (~>~))
import Data.Type.Rec (Has, Name, Rec)
import qualified Data.Type.Rec as Rec
import Language.Spectacle.Exception.RuntimeException
  ( RuntimeException (VariableException),
    VariableException (CyclicReference),
  )
import Language.Spectacle.Lang
  ( Effect,
    Lang (Op, Pure, Scoped),
    Member,
    Members,
    Op (OHere, OThere),
    Scoped (SHere, SThere),
    decomposeOp,
    decomposeS,
    runLang,
    scope,
  )
import Language.Spectacle.RTS.Registers
  ( RuntimeState (RuntimeState, callStack, plains, primes),
    StateFun,
    Thunk (Evaluated, Thunk, Unchanged),
    getRegister,
    setRegister,
  )
import Language.Spectacle.Syntax.Env
  ( Env,
    get,
    gets,
    modify,
    put,
    runEnv,
  )
import Language.Spectacle.Syntax.Error (Error, runError, throwE)
import Language.Spectacle.Syntax.NonDet (NonDet, oneOf, runNonDetA)
import Language.Spectacle.Syntax.Plain (runPlain)
import Language.Spectacle.Syntax.Prime.Internal
  ( Effect (PrimeVar),
    Prime (Prime),
  )

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

-- | 'prime' for a variable named @s@ is the value of @s@ in the next time frame.
--
-- @since 1.0.0
prime :: (Member Prime effs, Has s a ctx) => Name s -> Lang ctx effs a
prime nm = scope (PrimeVar nm)
{-# INLINE prime #-}

-- | Discharges a 'Prime' effect. This interpreter carries out the substitution of primed variables
-- using a call-by-need evaluation strategy.
--
-- @since 1.0.0
runPrime ::
  forall ctx effs a.
  Members '[Env, Error RuntimeException, NonDet] effs =>
  Lang ctx (Prime ': effs) a ->
  Lang ctx effs a
runPrime = \case
  Pure x -> pure x
  Op op k -> case decomposeOp op of
    Left other -> Op other (runPrime . k)
    Right bottom -> absurd (coerce bottom)
  Scoped scoped loom -> do
    case decomposeS scoped of
      Left other -> Scoped other loomPrime
      Right (PrimeVar name) -> do
        rst <- get
        case getRegister name (primes rst) of
          Thunk expr ->
            if show name `elem` callStack rst
              then throwE (VariableException (CyclicReference (callStack rst)))
              else do
                x <- substitute name expr
                modify \rst' ->
                  rst' {primes = setRegister name x (primes rst')}
                runLoom loomPrime (pure x)
          Evaluated x -> runLoom loomPrime (pure x)
          Unchanged -> do
            rst' <- gets (Rec.get name . plains)
            runLoom loomPrime (pure rst')
    where
      loomPrime = loom ~>~ hoist runPrime

-- | Alternative interpreter for 'Prime' that substitutes with a 'Rec' rather than a record of
-- thunks.
--
-- @since 1.0.0
substPrime :: Rec ctx -> Lang ctx (Prime ': effs) a -> Lang ctx effs a
substPrime vars = \case
  Pure x -> pure x
  Op op k -> case decomposeOp op of
    Left other -> Op other (substPrime vars . k)
    Right bottom -> absurd (coerce bottom)
  Scoped scoped loom -> case decomposeS scoped of
    Left other -> Scoped other loomSubstPrime
    Right (PrimeVar name) -> do
      runLoom loomSubstPrime (pure $ Rec.get name vars)
    where
      loomSubstPrime = loom ~>~ hoist (substPrime vars)

-- | Evaluates the thunk for a primed variable.
--
-- @since 1.0.0
substitute ::
  (Members '[Env, NonDet, Error RuntimeException] effs, Has s a ctx) =>
  Name s ->
  StateFun ctx a ->
  Lang ctx effs a
substitute name expr = do
  rst <- get
  let result =
        expr
          & introduceState
          & runPrime
          & runEnv rst
          & runPlain (plains rst)
          & runNonDetA @[]
          & runError
          & runLang
  case result of
    Left e -> throwE e
    Right results -> do
      (rst', x) <- oneOf results
      put (rst' {primes = setRegister name x (primes rst')})
      pure x
  where
    introduceState ::
      Lang ctx (Prime : effs) a ->
      Lang ctx (Prime ': Env ': effs) a
    introduceState = \case
      Pure x -> pure x
      Op op k
        | OHere op' <- op -> Op (OHere op') k'
        | OThere op' <- op -> Op (OThere (OThere op')) k'
        where
          k' = introduceState . k
      Scoped scoped loom
        | SHere scoped' <- scoped -> Scoped (SHere scoped') loom'
        | SThere scoped' <- scoped -> Scoped (SThere (SThere scoped')) loom'
        where
          loom' = loom ~>~ hoist introduceState