packages feed

technique-0.2.5: lib/Technique/Translate.hs

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

-- |
-- Given a Technique Procedure (concrete syntax tree), translate it into an
-- internalized representation (abstract syntax tree) that can be subsequently
-- executed (that is, interpreted; evaluated).
module Technique.Translate where

import Control.Monad (foldM, when)
import Control.Monad.Except (MonadError (..))
import Control.Monad.State.Class (MonadState (..))
import Control.Monad.Trans.Except (Except (), runExcept)
import Control.Monad.Trans.State.Strict (StateT (..), runStateT)
import Core.Data
import Core.Text
import Data.DList (fromList, toList)
import Data.Foldable (traverse_)
import Technique.Builtins
import Technique.Failure
import Technique.Internal
import Technique.Language

-- |
-- Environment in the type-theory sense of the word: the map(s) between
-- names and their bindings.

-- TODO perhaps the role should be Maybe Attribute? This will likely need
-- work as there are three states: 1) as yet unspecified, 2) specified, and
-- 3) explicitly reset to any. Are (1) and (3) the same?
data Environment = Environment
  { environmentVariables :: Map Identifier Name,
    environmentFunctions :: Map Identifier Function,
    environmentRole :: Attribute,
    -- for reporting compiler errors
    environmentSource :: Source,
    -- the accumulator for the fold that the Translate monad represents
    environmentAccumulated :: Step
  }
  deriving (Eq, Show)

emptyEnvironment :: Environment
emptyEnvironment =
  Environment
    { environmentVariables = emptyMap,
      environmentFunctions = emptyMap,
      environmentRole = Inherit,
      environmentSource = emptySource,
      environmentAccumulated = NoOp
    }

newtype Translate a = Translate (StateT Environment (Except CompilationError) a)
  deriving (Functor, Applicative, Monad, MonadState Environment, MonadError CompilationError)

-- |
-- Take a translator action and an environment and spin it up into a Step
-- or nest of Steps ("Subroutine") suitable for interpretation. In other
-- words, translate between the concrete syntax types and the abstract
-- syntax we can feed to an evaluator.

-- we use runStateT rather than evalStateT as we did previously so we can
-- access the final state in test cases.
runTranslate :: Environment -> Translate a -> Either CompilationError (a, Environment)
runTranslate env (Translate action) = runExcept (runStateT action env)
{-# INLINE runTranslate #-}

translateTechnique :: Technique -> Translate [Function]
translateTechnique technique = do
  -- Stage 1: conduct translation
  funcs1 <- traverse translateProcedure (techniqueBody technique)

  -- Stage 2: resolve functions
  funcs2 <- traverse resolver funcs1
  return funcs2
  where
    resolver :: Function -> Translate Function
    resolver func = case func of
      Subroutine proc step -> do
        step' <- resolveFunctions step
        return (Subroutine proc step')
      _ -> error ("Illegal state: How did you get a top level " ++ (show func) ++ "?")

translateProcedure :: Procedure -> Translate Function
translateProcedure procedure =
  let is = procedureParams procedure
      o = procedureOffset procedure
      block = procedureBlock procedure
   in do
        env <- get

        -- calling runTranslate here *is* the act of refining, but there's no way
        -- we're going to remember that so make it explicit. Gives us the
        -- opportunity to modify the environment before descending if necessary.

        let subenv = env
        let result = runTranslate subenv $ do
              traverse_ (insertVariable o) is
              translateBlock block

        case result of
          Left e -> throwError e
          Right (step, _) -> do
            let func = Subroutine procedure step
            registerProcedure (locationOf procedure) func
            return func

-- |
-- Blocks are scoping mechanisms, so accumulated environment is discarded
-- once we finish resolving names within it.
translateBlock :: Block -> Translate Step
translateBlock (Block statements) = do
  traverse_ translateStatement statements

  env' <- get
  let step = environmentAccumulated env'
  return step

translateStatement :: Statement -> Translate ()
translateStatement statement = do
  case statement of
    Assignment o vars expr -> do
      -- FIXME this offset will be incorrect if > 1 variable.
      names <- traverse (insertVariable o) vars
      step <- translateExpression expr

      let step' = Asynchronous o names step
      appendStep step'
    Execute _ expr -> do
      step <- translateExpression expr
      appendStep step
    Declaration _ proc -> do
      _ <- translateProcedure proc
      return ()

    -- the remainder are functionally no-ops
    Comment _ _ -> return ()
    Blank _ -> return ()
    Series _ -> return ()

-- |
-- Note that this does NOT add the steps to the Environment.
translateExpression :: Expression -> Translate Step
translateExpression expr = do
  env <- get
  let attr = environmentRole env

  case expr of
    Application o i subexpr -> do
      let func = Unresolved i
      step <- translateExpression subexpr
      return (Invocation o attr func step)
    None o ->
      return (Known o Unitus)
    Text o text ->
      return (Known o (Literali text))
    Amount o qty ->
      return (Known o (Quanticle qty))
    Undefined o -> do
      failBecause o EncounteredUndefined
    Object o (Tablet bindings) -> do
      pairs <- foldM f [] bindings
      return (Bench o pairs)
      where
        f :: [(Label, Step)] -> Binding -> Translate [(Label, Step)]
        f acc (Binding label subexpr) = do
          step <- translateExpression subexpr
          return (acc <> [(label, step)])
    Variable o is -> do
      steps <- traverse g is
      case steps of
        [] -> return NoOp
        [step] -> return step
        _ -> return (Tuple o steps)
      where
        g :: Identifier -> Translate Step
        g i = do
          name <- lookupVariable o i
          return (Depends o name)
    Operation o oper subexpr1 subexpr2 ->
      let prim = case oper of
            WaitEither -> builtinProcedureWaitEither
            WaitBoth -> builtinProcedureWaitBoth
            Combine -> builtinProcedureCombineValues
       in do
            step1 <- translateExpression subexpr1
            step2 <- translateExpression subexpr2
            let tuple = Tuple o [step1, step2]
            return (Invocation o attr prim tuple)
    Grouping _ subexpr ->
      translateExpression subexpr
    Restriction _ subattr block ->
      applyRestriction subattr block

-- |
-- A given procedure call can either be to a user declared in-scope
-- procedure or to a primative builtin. We have Invocation as the Step
-- constructors for these cases.
registerProcedure :: Offset -> Function -> Translate ()
registerProcedure o func = do
  env <- get

  let i = functionName func
  let known = environmentFunctions env
  let defined = containsKey i known

  when defined $ do
    failBecause o (ProcedureAlreadyDeclared i)

  let known' = insertKeyValue i func known
  let env' = env {environmentFunctions = known'}

  put env'

-- the overloading of throw between MonadError / ExceptT and the GHC
-- exceptions mechansism is unfortunate. We're not throwing an exception,
-- end it's definitely not pure `error`. Wrap it for clarity.
failBecause :: Offset -> FailureReason -> Translate a
failBecause o reason = do
  env <- get
  let source = environmentSource env
  let source' = source {sourceOffset = o}

  let failure = CompilationError source' reason
  throwError failure

lookupVariable :: Offset -> Identifier -> Translate Name
lookupVariable o i = do
  env <- get
  let known = lookupKeyValue i (environmentVariables env)

  case known of
    Just name -> return name
    Nothing -> failBecause o (UseOfUnknownIdentifier i)

-- |
-- Identifiers are valid names but Names are unique, so that we can put
-- them into the environment map. This is where we check for reuse of an
-- already declared name (TODO) and given the local use of the identifier a
-- scope-local (or globally?) unique name.
insertVariable :: Offset -> Identifier -> Translate Name
insertVariable o i = do
  env <- get
  let known = environmentVariables env

  when (containsKey i known) $ do
    failBecause o (VariableAlreadyInUse i)

  let n = Name (singletonRope '!' <> unIdentifier i) -- TODO
  let known' = insertKeyValue i n known
  let env' = env {environmentVariables = known'}
  put env'
  return n

-- |
-- Accumulate a Step.
appendStep :: Step -> Translate ()
appendStep step = do
  env <- get
  let steps = environmentAccumulated env

  -- see the Monoid instance for Step for the clever here
  let steps' = mappend steps step

  let env' = env {environmentAccumulated = steps'}
  put env'

-- |
-- This begins a new (more refined) scope and does *not* add its
-- declarations or variables to the current environment.
applyRestriction :: Attribute -> Block -> Translate Step
applyRestriction attr block = do
  env <- get

  let subenv =
        env
          { environmentRole = attr
          }

  let result = runTranslate subenv (translateBlock block)

  case result of
    Left e -> throwError e
    Right (steps, _) -> return steps

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

-- |
-- The second stage of translation phase: iterate through the Steps and
-- where a function call is made, look up to see if we actually know what
-- it is.
resolveFunctions :: Step -> Translate Step
resolveFunctions step = case step of
  Invocation o attr func substep -> do
    func' <- lookupFunction o func
    substep' <- resolveFunctions substep
    return (Invocation o attr func' substep')
  Tuple o substeps -> do
    substeps' <- traverse resolveFunctions substeps
    return (Tuple o substeps')
  Asynchronous o names substep -> do
    substep' <- resolveFunctions substep
    return (Asynchronous o names substep')
  Nested o sublist -> do
    let actual = toList sublist
    actual' <- traverse resolveFunctions actual
    let sublist' = fromList actual'
    return (Nested o sublist')
  Bench o pairs -> do
    pairs' <- traverse f pairs
    return (Bench o pairs')
    where
      f :: (Label, Step) -> Translate (Label, Step)
      f (label, substep) = do
        substep' <- resolveFunctions substep
        return (label, substep')
  Known _ _ -> return step
  Depends _ _ -> return step
  NoOp -> return step

lookupFunction :: Offset -> Function -> Translate Function
lookupFunction o func = do
  env <- get

  let i = functionName func
      known = environmentFunctions env
      result = lookupKeyValue i known

  case result of
    Nothing -> failBecause o (CallToUnknownProcedure i)
    Just actual -> return actual

-- |
-- Update the environment's idea of where in the source we are, so that if
-- we need to generate an error message we can offer one with position
-- information.
setLocationFrom :: (Located a) => a -> Translate ()
setLocationFrom thing = do
  env <- get
  let source = environmentSource env
  let o = locationOf thing
  let source' = source {sourceOffset = o}
  let env' = env {environmentSource = source'}
  put env'