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'