zwirn-0.1.0.0: src/Zwirn/Language/Compiler.hs
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-}
{-# OPTIONS_GHC -Wno-unused-top-binds #-}
module Zwirn.Language.Compiler where
{-
Compiler.hs - implementation of a compiler-interpreter for zwirn
Copyright (C) 2023, Martin Gius
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this library. If not, see <http://www.gnu.org/licenses/>.
-}
import Control.Concurrent (readMVar)
import Control.Exception (SomeException, try)
import Control.Monad
import Control.Monad.Except
import Control.Monad.State
import Data.List (sortOn)
import Data.Text (Text, unpack)
import Data.Text.IO (readFile)
import Text.Read (readMaybe)
import Zwirn.Core.Types (silence)
import Zwirn.Language.Block
import Zwirn.Language.Builtin.Prelude (builtinNames)
import Zwirn.Language.Environment
import Zwirn.Language.Evaluate
import Zwirn.Language.Parser
import Zwirn.Language.Pretty
import qualified Zwirn.Language.Rotate as R
import Zwirn.Language.Simple
import Zwirn.Language.Syntax
import Zwirn.Language.TypeCheck.Constraint (runSolve)
import Zwirn.Language.TypeCheck.Infer
import Zwirn.Language.TypeCheck.Types
import Zwirn.Stream
import Prelude hiding (readFile)
newtype CIMessage
= CIMessage Text
deriving (Show, Eq)
data CurrentBlock
= CurrentBlock Int Int
deriving (Eq, Show)
data ConfigEnv
= ConfigEnv
{ cConfigPath :: IO String,
cResetConfig :: IO String
}
data CiConfig = CiConfig
{ ciConfigOverwriteBuiltin :: Bool,
ciConfigDynamicTypes :: Bool
}
data Environment
= Environment
{ tStream :: Stream,
intEnv :: InterpreterEnv,
confEnv :: Maybe ConfigEnv,
currBlock :: Maybe CurrentBlock,
ciConfig :: CiConfig
}
data CIError
= CIError
{ eError :: String,
eEnv :: Environment
}
instance Show CIError where
show (CIError err _) = err
type CI = StateT Environment (ExceptT CIError IO)
runCI :: Environment -> CI a -> IO (Either CIError a)
runCI env m = runExceptT $ evalStateT m env
compilerInterpreterBasic :: Text -> CI String
compilerInterpreterBasic input = do
as <- runParser input
runActions True as
compilerInterpreterBlock :: Int -> Int -> Text -> CI (String, Environment, Int, Int)
compilerInterpreterBlock line editor input = do
blocks <- runBlocks 0 input
(Block strt end content) <- runGetBlock line blocks
setCurrentBlock strt end
as <- runParserWithPos strt editor content
r <- runActions True as
e <- get
return (r, e, strt, end)
compilerInterpreterLine :: Int -> Int -> Text -> CI (String, Environment, Int, Int)
compilerInterpreterLine line editor input = do
setCurrentBlock line line
blocks <- runBlocks 0 input
content <- runGetLine line blocks
as <- runParserWithPos line editor content
r <- runActions True as
e <- get
return (r, e, line, line)
compilerInterpreterWhole :: Int -> Text -> CI (String, Environment, Int, Int)
compilerInterpreterWhole editor input = do
blocks <- runBlocks 0 input
let sorted = sortOn (\(Block x _ _) -> x) blocks
(Block strt _ _) = head sorted
(Block _ end _) = last sorted
liftIO $ print sorted
setCurrentBlock strt end
let parseBlock (Block s _ c) = runParserWithPos s editor c
ass <- mapM parseBlock sorted
rs <- mapM (runActions True) ass
e <- get
return (last rs, e, strt, end)
compilerInterpreterBoot :: [Text] -> CI Environment
compilerInterpreterBoot ps = runActions False (map Load ps) >> get
-----------------------------------------------------
----------------- Throwing Errors -------------------
-----------------------------------------------------
throw :: String -> CI a
throw err = do
env <- get
throwError $ CIError err env
setCurrentBlock :: Int -> Int -> CI ()
setCurrentBlock st en = modify (\env -> env {currBlock = Just $ CurrentBlock st en})
-----------------------------------------------------
---------------------- Parser -----------------------
-----------------------------------------------------
runParserWithPos :: Int -> Int -> Text -> CI [Action]
runParserWithPos ln ed t = case parseActionsWithPos ln ed t of
Left err -> throw err
Right as -> return as
runParser :: Text -> CI [Action]
runParser t = case parseActions t of
Left err -> throw err
Right as -> return as
runBlocks :: Int -> Text -> CI [Block]
runBlocks ln t = case parseBlocks ln t of
Left err -> throw err
Right bs -> return bs
runGetBlock :: Int -> [Block] -> CI Block
runGetBlock i bs = case getBlock i bs of
Left err -> throw err
Right b -> return b
runGetLine :: Int -> [Block] -> CI Text
runGetLine i bs = case getLn i bs of
Left err -> throw err
Right b -> return b
-----------------------------------------------------
---------------------- Desugar ----------------------
-----------------------------------------------------
runSimplify :: Term -> CI SimpleTerm
runSimplify t = return $ simplify t
runSimplifyDef :: Def -> CI SimpleDef
runSimplifyDef d = return $ simplifyDef d
-----------------------------------------------------
------------------- AST Rotation --------------------
-----------------------------------------------------
runRotate :: SimpleTerm -> CI SimpleTerm
runRotate s = case R.runRotate s of
Left err -> throw err
Right t -> return t
-----------------------------------------------------
-------------------- Type Check ---------------------
-----------------------------------------------------
runTypeCheck :: SimpleTerm -> CI Scheme
runTypeCheck s = do
Environment {intEnv = env} <- get
case inferTerm env s of
Left err -> throw $ show err
Right t -> return t
-----------------------------------------------------
-------------------- Interpreter --------------------
-----------------------------------------------------
interpret :: SimpleTerm -> CI Expression
interpret input = do
env <- gets intEnv
return $ evaluate env input
-- if ctx is false, highlighting should be disabled
checkHighlight :: Bool -> Expression -> CI Expression
checkHighlight True x = return x
checkHighlight False x = return $ removePosExp x
-----------------------------------------------------
----------------- Compiling Actions -----------------
-----------------------------------------------------
defAction :: Bool -> Def -> CI ()
defAction ctx d = do
(LetS x st) <- runSimplifyDef d
rot <- runRotate st
ty@(Forall _ (Qual _ typ)) <- runTypeCheck rot
ex <- interpret rot
exCtx <- checkHighlight ctx ex
dynamic <- gets (ciConfigDynamicTypes . ciConfig)
if dynamic
then checkAndDefine x ty exCtx
else do
mayty <- gets (lookupType x . intEnv)
case mayty of
Just (Forall _ (Qual _ oldType)) -> case runSolve [(oldType, typ)] of
Left _ -> throw "Cannot overwrite definition with new type. Please use DynamicTypes."
Right _ -> checkAndDefine x ty exCtx
Nothing -> checkAndDefine x ty exCtx
checkAndDefine :: Text -> Scheme -> Expression -> CI ()
checkAndDefine x ty exCtx = do
overwrite <- gets (ciConfigOverwriteBuiltin . ciConfig)
if overwrite
then modify (\env -> env {intEnv = extend (x, exCtx, ty) (intEnv env)})
else
if x `elem` builtinNames
then throw "Failed to overwrite builtin function. Please enable OverwriteBuiltin."
else modify (\env -> env {intEnv = extend (x, exCtx, ty) (intEnv env)})
showAction :: Term -> CI String
showAction t = do
s <- runSimplify t
rot <- runRotate s
ty <- runTypeCheck rot
if isBasicType ty
then do
ex <- interpret rot
stmv <- gets (sState . tStream)
st <- liftIO $ readMVar stmv
return $ showWithState st ex
else throw $ "Can not show expressions of type: " ++ ppscheme ty
typeAction :: Term -> CI String
typeAction t = do
s <- runSimplify t
rot <- runRotate s
ty <- runTypeCheck rot
return $ ppTermHasType (t, ty)
loadAction :: Text -> CI ()
loadAction path = do
mayfile <- liftIO ((try $ readFile $ unpack path) :: IO (Either SomeException Text))
case mayfile of
Left _ -> throw "file not found"
Right input -> do
blocks <- runBlocks 0 input
let sorted = sortOn (\(Block x _ _) -> x) blocks
ass <- mapM (runParser . bContent) sorted
mapM_ (runActions False) ass
infoAction :: Text -> CI String
infoAction n = do
env <- gets intEnv
case lookupFull n env of
Just (Annotated _ t (Just d)) -> return $ unpack n ++ " :: " ++ ppscheme t ++ "\n" ++ unpack d
Just (Annotated _ t Nothing) -> return $ unpack n ++ " :: " ++ ppscheme t
Nothing -> throw $ "couldn't find information about " ++ unpack n
streamAction :: Bool -> Text -> Term -> CI ()
streamAction ctx key t = do
s <- runSimplify t
rot <- runRotate s
ty <- runTypeCheck rot
ex <- interpret rot
exCtx <- checkHighlight ctx ex
if isBasicType ty
then
( do
str <- gets tStream
liftIO $ streamReplace str key (fromExp exCtx)
)
else
if isBus ty
then
( do
str <- gets tStream
let mayindex = readMaybe $ unpack key
case mayindex of
Just ind -> liftIO $ streamReplaceBus str ind (fromExp exCtx)
Nothing -> throw "Please use an integer as bus index."
)
else throw "Can only stream base types!"
streamSetAction :: Bool -> Text -> Term -> CI ()
streamSetAction ctx x t = do
s <- runSimplify t
rot <- runRotate s
ty@(Forall _ (Qual _ typ)) <- runTypeCheck rot
ex <- interpret rot
exCtx <- checkHighlight ctx ex
dynamic <- gets (ciConfigDynamicTypes . ciConfig)
if dynamic
then checkAndSet x ty exCtx
else do
mayty <- gets (lookupType x . intEnv)
case mayty of
Just (Forall _ (Qual _ oldType)) -> case runSolve [(oldType, typ)] of
Left _ -> throw "Cannot overwrite definition with new type. Please use DynamicTypes."
Right _ -> checkAndSet x ty exCtx
Nothing -> checkAndSet x ty exCtx
checkAndSet :: Text -> Scheme -> Expression -> CI ()
checkAndSet x ty exCtx =
if isBasicType ty
then
( do
overwrite <- gets (ciConfigOverwriteBuiltin . ciConfig)
if overwrite
then setExpression x ty exCtx
else
if x `elem` builtinNames
then throw "Failed to overwrite builtin function. Please enable OverwriteBuiltin."
else setExpression x ty exCtx
)
else throw "Can only set basic types!"
setExpression :: Text -> Scheme -> Expression -> CI ()
setExpression x ty exCtx = do
modify (\env -> env {intEnv = extend (x, newEx, ty) (intEnv env)})
str <- gets tStream
liftIO $ streamSet str x exCtx
where
newEx
| isNumberT ty = EZwirn $ getStateN (pure x)
| isTextT ty = EZwirn $ getStateT (pure x)
| isMapT ty = EZwirn $ getStateM (pure x)
| otherwise = EZwirn silence
streamOnceAction :: Bool -> Term -> CI ()
streamOnceAction ctx t = do
s <- runSimplify t
rot <- runRotate s
ty <- runTypeCheck rot
ex <- interpret rot
exCtx <- checkHighlight ctx ex
if isBasicType ty
then
( do
str <- gets tStream
liftIO $ streamFirst str (fromExp exCtx)
)
else throw "Can only stream base types!"
streamSetTempoAction :: Tempo -> Text -> CI ()
streamSetTempoAction CPS t = gets tStream >>= \str -> liftIO $ streamSetCPS str (toRational (read $ unpack t :: Double))
streamSetTempoAction BPM t = gets tStream >>= \str -> liftIO $ streamSetBPM str (toRational (read $ unpack t :: Double))
resetConfigAction :: CI String
resetConfigAction = do
(Environment {confEnv = mayEnv}) <- get
case mayEnv of
Nothing -> throw "Configuration not available."
Just (ConfigEnv _ reset) -> liftIO reset
getConfigPathAction :: CI String
getConfigPathAction = do
(Environment {confEnv = mayEnv}) <- get
case mayEnv of
Nothing -> throw "Configuration not available."
Just (ConfigEnv path _) -> liftIO path
runAction :: Bool -> Action -> CI String
runAction b (StreamAction i t) = streamAction b i t >> return ""
runAction b (StreamSet i t) = streamSetAction b i t >> return ""
runAction b (StreamOnce t) = streamOnceAction b t >> return ""
runAction _ (StreamSetTempo mode t) = streamSetTempoAction mode t >> return ""
runAction _ (Show t) = showAction t
runAction b (Def d) = defAction b d >> return ""
runAction _ (Type t) = typeAction t
runAction _ (Load p) = loadAction p >> return ""
runAction _ (Info p) = infoAction p
runAction _ ConfigPath = getConfigPathAction
runAction _ ResetConfig = resetConfigAction
runActions :: Bool -> [Action] -> CI String
runActions b as = last <$> mapM (runAction b) as
isNumberT :: Scheme -> Bool
isNumberT (Forall _ (Qual _ (TypeCon "Number"))) = True
isNumberT _ = False
isTextT :: Scheme -> Bool
isTextT (Forall _ (Qual _ (TypeCon "Text"))) = True
isTextT _ = False
isMapT :: Scheme -> Bool
isMapT (Forall _ (Qual _ (TypeCon "Map"))) = True
isMapT _ = False