zwirn-0.2.2.0: src/zwirn-lang/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.Either (lefts, rights)
import Data.List (intercalate)
import qualified Data.List.NonEmpty as NE
import qualified Data.Map as Map
import Data.Text (Text, pack, unpack)
import qualified Data.Text as T
import Data.Text.IO (readFile)
import Data.Version (showVersion)
import Paths_zwirn (version)
import System.IO (hPutStrLn, stderr)
import Text.Read (readMaybe)
import Zwirn.Core.Types (silence)
import Zwirn.Language.Block
import Zwirn.Language.Builtin.Prelude (builtinEnvironmentWithStream, builtinNames)
import Zwirn.Language.Environment
import Zwirn.Language.Evaluate
import Zwirn.Language.Location
import Zwirn.Language.Macro
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
import Zwirn.Language.TypeCheck.Infer
import Zwirn.Language.TypeCheck.Types
import Zwirn.Stream.Target (Targeted (..))
import Zwirn.Stream.Types
import Zwirn.Stream.UI
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,
ciConfig :: CiConfig,
macroMap :: MacroMap
}
data ErrorType
= ParseErr String RealSrcLoc
| TypeErr TypeError
| RotErr R.RotationError
| OtherErr String
| ManyErr [ErrorType]
instance Show ErrorType where
show (ParseErr s _) = s
show (TypeErr err) = unpack $ render err
show (RotErr err) = show err
show (OtherErr err) = err
show (ManyErr errs) = intercalate "\n" $ map show errs
data CIError
= CIError
{ eError :: ErrorType,
eEnv :: Environment
}
data CompilerOutput
= OutMessage Text
| OutEdits [CodeEdit]
| NoOutput
deriving (Eq, Show)
instance Show CIError where
show (CIError err _) = show err
type CI = StateT Environment (ExceptT CIError IO)
runCI :: Environment -> CI a -> IO (Either CIError a)
runCI env m = runExceptT $ evalStateT m env
debug :: (MonadIO m) => String -> m ()
debug msg = liftIO $ hPutStrLn stderr $ "[zwirnzi] " <> msg
compilerInterpreterBasic :: Text -> CI CompilerOutput
compilerInterpreterBasic input = do
sy <- runParser input
runSyntax True sy
compilerInterpreterBlock :: Int -> Text -> CI (CompilerOutput, Environment)
compilerInterpreterBlock line input = do
blocks <- runBlocks 0 input
b <- runGetBlock line blocks
sys <- parseBlock b
r <- mapM (runSyntax True) sys
e <- get
return (last r, e)
compilerInterpreterLine :: Int -> Text -> CI (CompilerOutput, Environment)
compilerInterpreterLine line input = do
blocks <- runBlocks 0 input
content <- runGetLine line blocks
sy <- runParserWithPos line "" content
r <- runSyntax True sy
e <- get
return (r, e)
compilerInterpreterWhole :: Text -> CI (CompilerOutput, Environment)
compilerInterpreterWhole input = do
blocks <- runBlocks 0 input
syss <- mapM parseBlock blocks
rs <- mapM (runSyntax True) $ concat syss
e <- get
return (last rs, e)
compilerInterpreterBoot :: [Text] -> CI Environment
compilerInterpreterBoot ps = mapM_ (runSyntax False . Command . noLoc . LoadCommand) ps >> get
-----------------------------------------------------
----------------- Throwing Errors -------------------
-----------------------------------------------------
throw :: ErrorType -> CI a
throw err = do
env <- get
throwError $ CIError err env
-- | catches an error by wrapping the result back into Either
catch :: CI a -> CI (Either CIError a)
catch c = catchError (fmap Right c) (return . Left)
-- | sequences the given actions, accumulating any occuring errors and wrapping them into ManyErr
catchMany :: [CI a] -> CI [a]
catchMany cs = do
es <- mapM catch cs
let errs = lefts es
vs = rights es
case errs of
[] -> return ()
[e] -> throw $ eError e
xs -> throw (ManyErr $ map eError xs)
return vs
filterErrors :: [CI a] -> CI [a]
filterErrors cs = do
es <- mapM catch cs
return $ rights es
-----------------------------------------------------
---------------------- Parser -----------------------
-----------------------------------------------------
extractParseErrPos :: String -> ErrorType
extractParseErrPos s = case mpos of
Just p -> ParseErr s p
Nothing -> OtherErr s
where
(ls, rs) = break (== ',') $ drop 20 s
(c1s, _) = break (== '\n') $ drop 9 rs
mpos = do
l <- readMaybe ls
c1 <- readMaybe c1s
return (RealSrcLoc "" l c1 l c1)
runParserWithPos :: Int -> Text -> Text -> CI Syntax
runParserWithPos ln srcp t = case parseSyntaxWithPos ln srcp t of
Left err -> throw $ extractParseErrPos err
Right s -> return s
runParser :: Text -> CI Syntax
runParser t = case parseSyntax t of
Left err -> throw $ extractParseErrPos err
Right s -> return s
runBlocks :: Int -> Text -> CI [Block]
runBlocks ln t = case parseBlocks ln t of
Left err -> throw $ OtherErr err
Right bs -> return bs
runGetBlock :: Int -> [Block] -> CI Block
runGetBlock i bs = case getBlock i bs of
Left err -> throw $ OtherErr err
Right b -> return b
runGetLine :: Int -> [Block] -> CI Text
runGetLine i bs = case getSingleLine i bs of
Left err -> throw $ OtherErr err
Right (Line _ _ c) -> return c
parseBlock :: Block -> CI [Syntax]
parseBlock (Block ls) = catchMany $ map (\l -> runParserWithPos (lStart l) "" (lContent l)) (NE.toList ls)
getSyntaxLine :: Int -> Text -> CI (Maybe Syntax)
getSyntaxLine ln input = do
blocks <- runBlocks 0 input
case getSingleLine ln blocks of
Right line -> Just <$> runParserWithPos (lStart line) "" (lContent line)
Left _ -> return Nothing
getSyntaxBlock :: Int -> Text -> CI [Syntax]
getSyntaxBlock ln input = do
blocks <- runBlocks 0 input
block <- runGetBlock ln blocks
parseBlock block
-----------------------------------------------------
---------------------- Macro ------------------------
-----------------------------------------------------
macroCI :: LocTerm -> CI (LocTerm, [CodeEdit])
macroCI t = do
mmap <- gets macroMap
case runMacros t mmap of
Just x -> return x
_ -> throw $ OtherErr "Unknown macro!"
-----------------------------------------------------
---------------------- Desugar ----------------------
-----------------------------------------------------
runSimplify :: LocTerm -> CI LocSimpleTerm
runSimplify t = return $ simplifyLoc t
runSimplifyDef :: Located Definition -> CI (Located SimpleDef, [CodeEdit])
runSimplifyDef (Located p (Definition x vs t)) = do
(t', es) <- macroCI t
return (Located p (LetS x (noLoc $ simplify $ TLambda vs t')), es)
-----------------------------------------------------
------------------- AST Rotation --------------------
-----------------------------------------------------
runRotate :: LocSimpleTerm -> CI LocSimpleTerm
runRotate s = case R.runRotate s of
Left err -> throw $ RotErr err
Right t -> return t
-----------------------------------------------------
-------------------- Type Check ---------------------
-----------------------------------------------------
runTypeCheck :: LocSimpleTerm -> CI Scheme
runTypeCheck s = do
Environment {intEnv = env} <- get
case inferTerm env s of
Left err -> throw (TypeErr err)
Right t -> return t
-----------------------------------------------------
-------------------- Interpreter --------------------
-----------------------------------------------------
interpret :: LocSimpleTerm -> 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
-----------------------------------------------------
----------------- Checking Options -----------------
-----------------------------------------------------
overwriteOk :: Text -> CI ()
overwriteOk name = do
overwrite <- gets (ciConfigOverwriteBuiltin . ciConfig)
when (not overwrite && name `elem` builtinNames) $ throw $ OtherErr "Cannot overwrite builtin function. Please use OverwriteBuiltin."
dynamicOk :: Text -> Scheme -> CI ()
dynamicOk name ty = do
dynamic <- gets (ciConfigDynamicTypes . ciConfig)
mayty <- gets (lookupType name . intEnv)
case mayty of
Just oldType ->
when (not dynamic && not (unifiable (schemeToType oldType, schemeToType ty))) $ throw $ OtherErr "Cannot overwrite definition with new type. Please use DynamicTypes."
Nothing -> return ()
-------------------------------------------------------
----------------- Interpreting Syntax -----------------
-------------------------------------------------------
runSyntax :: Bool -> Syntax -> CI CompilerOutput
runSyntax b (Exec t) = executeTerm b t
runSyntax _ (Command c) = runCommand (lValue c)
runSyntax b (Def d) = define b d
runSyntax b (DynDef d) = dynamicDefine b (lValue d)
runSyntax _ (MacroDef d) = macroDefine (lValue d)
executeTerm :: Bool -> LocTerm -> CI CompilerOutput
executeTerm ctx t = do
(t', es) <- macroCI t
s <- runSimplify t'
rot <- runRotate s
ty <- runTypeCheck rot
ex <- interpret rot
exCtx <- checkHighlight ctx ex
case ty of
Forall _ (Qual _ _ (TypeCon "Action")) -> do
str <- gets tStream
liftIO $ evalAction str (fromExp exCtx)
return $ OutEdits es
_ -> throw $ OtherErr "Can only execute actions!"
define :: Bool -> Located Definition -> CI CompilerOutput
define ctx d = do
(Located _ (LetS x st), es) <- runSimplifyDef d
rot <- runRotate st
ty <- runTypeCheck rot
ex <- interpret rot
exCtx <- checkHighlight ctx ex
overwriteOk x
dynamicOk x ty
modify (\env -> env {intEnv = extend (x, exCtx, ty) (intEnv env)})
return $ OutEdits es
dynamicDefine :: Bool -> DynamicDefinition -> CI CompilerOutput
dynamicDefine ctx (DynamicDefinition x t) = do
(t', es) <- macroCI t
s <- runSimplify t'
rot <- runRotate s
ty <- runTypeCheck rot
ex <- interpret rot
exCtx <- checkHighlight ctx ex
overwriteOk x
dynamicOk x ty
setExpression x ty exCtx
return $ OutEdits es
macroDefine :: MacroDefinition -> CI CompilerOutput
macroDefine (MacroDefinition x t) = do
(t', es) <- macroCI t
s <- runSimplify t'
rot <- runRotate s
_ <- runTypeCheck rot
modify (\env -> env {macroMap = Map.insert x t' (macroMap env)})
return $ OutEdits es
setExpression :: Text -> Scheme -> Expression -> CI ()
setExpression x ty exCtx
| isBasicType ty = do
if checkDependency x ty
then do
let newEx
| isNumberT ty = EZwirn $ getStateN (pure x)
| isTextT ty = EZwirn $ getStateT (pure x)
| isMapT ty = EZwirn $ getStateM (pure x)
| otherwise = EZwirn silence
modify (\env -> env {intEnv = extend (x, newEx, addDependency x ty) (intEnv env)})
str <- gets tStream
liftIO $ streamSet str x exCtx
else throw $ OtherErr "Cyclic dependency detected!"
| otherwise = throw $ OtherErr "Can only set basic types!"
---------------------------------------------------------
----------------- Interpreting Commands -----------------
---------------------------------------------------------
runCommand :: Command -> CI CompilerOutput
runCommand (ShowCommand t) = showCommand t
runCommand (TypeCommand t) = typeCommand t
runCommand (SetCommand f) = setCommand (unpack f)
runCommand (UnsetCommand _) = return NoOutput
runCommand (LoadCommand f) = NoOutput <$ loadCommand f
runCommand (InfoCommand f) = infoCommand f
runCommand ResetEnvCommand = resetEnvCommand
runCommand ResetConfigCommand = resetConfigCommand
runCommand ShowConfigPathCommand = showConfigPathCommand
runCommand StatusCommand = statusCommand
runCommand EnvCommand = envCommand
showCommand :: LocTerm -> CI CompilerOutput
showCommand t = do
(t', _) <- macroCI t
s <- runSimplify t'
rot <- runRotate s
ty <- runTypeCheck rot
if isBasicType ty
then do
ex <- interpret rot
stmv <- gets (sState . tStream)
prec <- gets (streamConfigPrecision . sConfig . tStream)
st <- liftIO $ readMVar stmv
return $ OutMessage $ pack $ showWithStatePrec (realToFrac prec) st ex
else throw $ OtherErr $ "Can not show expressions of type: " ++ unpack (ppscheme ty)
typeCommand :: LocTerm -> CI CompilerOutput
typeCommand t = do
(t', _) <- macroCI t
s <- runSimplify t'
rot <- runRotate s
ty <- runTypeCheck rot
return $ OutMessage $ ppTermHasType (t, ty)
loadCommand :: Text -> CI ()
loadCommand path = do
mayfile <- liftIO ((try $ readFile $ unpack path) :: IO (Either SomeException Text))
case mayfile of
Left _ -> throw $ OtherErr "File not found"
Right input -> do
blocks <- runBlocks 0 input
let content = concatMap getBlockContent blocks
ss <- mapM runParser content
mapM_ (runSyntax False) ss
infoCommand :: Text -> CI CompilerOutput
infoCommand n = do
env <- gets intEnv
case lookupFull n env of
Just (Annotated _ t (Just d)) -> return $ OutMessage $ n <> " :: " <> ppscheme t <> " \n" <> d
Just (Annotated _ t Nothing) -> return $ OutMessage $ n <> " :: " <> ppscheme t
Nothing -> return $ OutMessage $ pack $ "Couldn't find information about " ++ unpack n
resetConfigCommand :: CI CompilerOutput
resetConfigCommand = do
(Environment {confEnv = mayEnv}) <- get
case mayEnv of
Nothing -> throw $ OtherErr "Configuration not available."
Just (ConfigEnv _ reset) -> OutMessage . pack <$> liftIO reset
showConfigPathCommand :: CI CompilerOutput
showConfigPathCommand = do
(Environment {confEnv = mayEnv}) <- get
case mayEnv of
Nothing -> throw $ OtherErr "Configuration not available."
Just (ConfigEnv path _) -> OutMessage . pack <$> liftIO path
resetEnvCommand :: CI CompilerOutput
resetEnvCommand = do
str <- gets tStream
modify (\env -> env {intEnv = builtinEnvironmentWithStream str})
return $ OutMessage "Environment reset to default!"
setCommand :: String -> CI CompilerOutput
setCommand "DynamicTypes" = modify (\env -> env {ciConfig = (ciConfig env) {ciConfigDynamicTypes = True}}) >> return (OutMessage "Successfully enabled DynamicTypes.")
setCommand "OverwriteBuiltin" = modify (\env -> env {ciConfig = (ciConfig env) {ciConfigOverwriteBuiltin = True}}) >> return (OutMessage "Successfully enabled OverwriteBuiltin.")
setCommand _ = return $ OutMessage "Unknown compiler flag. The flags are: DynamicTypes, OverwriteBuiltin."
-- for now only prints out the "basic" non-builtin expressions
-- TODO: add a flag/modifier to print all expressions (via :env all)
envCommand :: CI CompilerOutput
envCommand = do
env <- gets (Map.toList . Map.filter isBasicExpression . eExpressions . intEnv)
str <- gets tStream
let builtin = Map.keys $ Map.filter isBasicExpression $ eExpressions $ builtinEnvironmentWithStream str
filtered = filter (\(k, _) -> k `notElem` builtin) env
return $ OutMessage $ T.intercalate "\n" $ map (\(k, Annotated _ ty _) -> k <> " :: " <> ppscheme ty) filtered
statusCommand :: CI CompilerOutput
statusCommand = do
env <- get
b <- liftIO (streamGetBPM (tStream env))
pm <- liftIO $ readMVar (sPlayMap $ tStream env)
return $ OutMessage $ renderStatus b pm
renderStatus :: Double -> PlayMap -> Text
renderStatus b pm = "zwirn " <> pack (showVersion version) <> "\ntempo: " <> pack (show b) <> "bpm\n" <> if null pm then "" else "active: " <> T.intercalate " | " ps
where
ps = map (\(key, Targeted _ (st, _, _)) -> ppID key <> renderState sol st) $ Map.toList pm
sol = noSolo pm
renderState True Normal = ""
renderState False Normal = " (not soloed)"
renderState _ Solo = " (solo)"
renderState _ Mute = " (muted)"
-- | true if no pattern has a solo status
noSolo :: PlayMap -> Bool
noSolo pm = all (\(_, Targeted _ (ps, _, _)) -> ps /= Solo) $ Map.toList pm
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