flaw-script/Flaw/Script.hs

Summary

Maintainability
Test Coverage
{-|
Module: Flaw.Script
Description: Scripting in Haskell.
License: MIT
-}

{-# LANGUAGE GADTs, ViewPatterns #-}

module Flaw.Script
  ( Interpreter()
  , newInterpreter
  , interpret
  , I.InterpreterError(..)
  ) where

import Control.Concurrent.MVar
import Control.Concurrent.STM
import Control.Monad
import Control.Monad.Catch
import qualified Data.Text as T
import Data.Typeable
import qualified Language.Haskell.Interpreter as I

import Flaw.Book
import Flaw.Flow

newtype Interpreter = Interpreter
  { interpreterTaskQueue :: TQueue Task
  }

data Task where
  Task :: Typeable a =>
    { taskModules :: [T.Text]
    , taskExpression :: !T.Text
    , taskResultVar :: {-# UNPACK #-} !(TMVar (Either I.InterpreterError a))
    } -> Task

newInterpreter :: [T.Text] -> IO (Interpreter, IO ())
newInterpreter (map T.unpack -> searchPaths) = withSpecialBook $ \bk -> do
  taskQueue <- newTQueueIO
  activeVar <- newTVarIO True
  closedVar <- newEmptyMVar

  book bk $ forkFlow $ flip finally (putMVar closedVar ()) $ void $ I.runInterpreter $ do
    -- extensions set once, since they are not reset with `reset`
    I.set
      [ I.languageExtensions I.:= defaultExtensions
      , I.searchPath I.:= searchPaths
      ]

    let
      step = do
        -- wait for task
        maybeTask <- I.liftIO $ atomically $ let
          getTask = Just <$> readTQueue taskQueue
          getUnactive = do
            active <- readTVar activeVar
            when active retry
            return Nothing
          in getTask `orElse` getUnactive

        case maybeTask of
          -- if there's task
          Just Task
            { taskModules = map T.unpack -> modules
            , taskExpression = T.unpack -> expression
            , taskResultVar = resultVar
            } -> do
            -- handle task
            handle (I.liftIO . atomically . putTMVar resultVar . Left) $ do
              -- load modules
              I.loadModules modules
              I.setTopLevelModules modules
              let
                run :: (I.MonadInterpreter m, Typeable a) => TMVar (Either I.InterpreterError a) -> a -> m a
                run _ witness = I.interpret expression witness
              I.liftIO . atomically . putTMVar resultVar . Right =<< run resultVar undefined
            -- repeat
            step
          -- otherwise we're shutting down
          Nothing -> return ()
      in step

  -- graceful shutdown
  book bk $ return ((), do
    atomically $ writeTVar activeVar False
    takeMVar closedVar
    )

  return Interpreter
    { interpreterTaskQueue = taskQueue
    }

interpret :: Typeable a => Interpreter -> [T.Text] -> T.Text -> IO a
interpret Interpreter
  { interpreterTaskQueue = taskQueue
  } modules expression = do
  resultVar <- newEmptyTMVarIO
  atomically $ do
    writeTQueue taskQueue Task
      { taskModules = modules
      , taskExpression = expression
      , taskResultVar = resultVar
      }
  eitherResult <- atomically $ takeTMVar resultVar
  case eitherResult of
    Left e -> throwM e
    Right result -> return result

-- | Extensions considered harmless and useful to be enabled automatically.
defaultExtensions :: [I.Extension]
defaultExtensions =
  [
  -- , I.OverlappingInstances -- deprecated
  -- , I.UndecidableInstances -- dangerous
  -- , I.IncoherentInstances -- deprecated
  -- , I.DoRec -- ?
    I.RecursiveDo -- harmless
  , I.ParallelListComp -- harmless
  , I.MultiParamTypeClasses -- needed
  , I.NoMonomorphismRestriction -- useful in scripts
  , I.FunctionalDependencies -- needed
  -- , I.Rank2Types -- obsolete, replaced with RankNTypes
  , I.RankNTypes -- needed
  -- , I.PolymorphicComponents -- obsolete, replaced with RankNTypes
  , I.ExistentialQuantification -- needed
  -- , I.ScopedTypeVariables -- usually unexpected
  , I.PatternSignatures -- needed
  , I.ImplicitParams -- harmless
  , I.FlexibleContexts -- needed
  , I.FlexibleInstances -- needed
  , I.EmptyDataDecls -- needed
  , I.NoCPP -- not sure, try to refrain from for now
  , I.KindSignatures -- harmless, also implied by TypeFamilies
  , I.BangPatterns -- harmless
  , I.TypeSynonymInstances -- implied by FlexibleInstances
  , I.TemplateHaskell -- needed
  , I.NoForeignFunctionInterface -- not needed in scripts
  , I.Arrows -- harmless
  , I.Generics -- ?
  -- , I.ImplicitPrelude -- enabled by default
  , I.NamedFieldPuns -- harmless
  , I.PatternGuards -- harmless
  , I.GeneralizedNewtypeDeriving -- needed
  -- , I.ExtensibleRecords -- ?
  -- , I.RestrictedTypeSynonyms -- ?
  -- , I.HereDocuments -- ?
  , I.NoMagicHash -- not needed
  , I.TypeFamilies -- needed
  , I.StandaloneDeriving -- needed
  , I.UnicodeSyntax -- harmless
  -- , I.UnliftedFFITypes -- FFI is disabled
  -- , I.InterruptibleFFI -- FFI is disabled
  -- , I.CApiFFI -- FFI is disabled
  , I.NoLiberalTypeSynonyms -- dangerous
  -- , I.TypeOperators -- not needed
  , I.RecordWildCards -- harmless
  -- , I.RecordPuns -- ?
  -- , I.DisambiguateRecordFields -- implied by DuplicateRecordFields
  -- , I.TraditionalRecordSyntax -- enabled by default
  , I.OverloadedStrings -- needed
  , I.GADTs -- needed
  -- , I.GADTSyntax -- implied by GADTs
  -- , I.MonoPatBinds -- ?
  -- , I.RelaxedPolyRec -- fed by ScopedTypeVariables
  -- , I.ExtendedDefaultRules -- ?
  -- , I.UnboxedTuples -- not needed
  , I.DeriveDataTypeable -- needed
  , I.DeriveGeneric -- needed
  , I.DefaultSignatures -- needed
  , I.InstanceSigs -- harmless
  -- , I.ConstrainedClassMethods -- implied by MultiParamTypeClasses
  -- , I.PackageImports -- generally not recommended
  -- , I.ImpredicativeTypes -- dangerous
  -- , I.NewQualifiedOperators -- ?
  -- , I.PostfixOperators -- seem to be harmless, but surprising
  , I.QuasiQuotes -- needed
  , I.TransformListComp -- harmless
  , I.MonadComprehensions -- harmless
  , I.ViewPatterns -- needed
  -- , I.XmlSyntax -- ?
  -- , I.RegularPatterns -- ?
  , I.TupleSections -- harmless
  -- , I.GHCForeignImportPrim -- ?
  -- , I.NPlusKPatterns -- ?
  -- , I.DoAndIfThenElse -- ?
  , I.MultiWayIf -- harmless
  , I.LambdaCase -- harmless
  , I.NoRebindableSyntax -- dangerous
  , I.ExplicitForAll -- harmless
  -- , I.DatatypeContexts -- deprecated
  -- , I.MonoLocalBinds -- implied by GADTs, TypeFamilies
  -- , I.DeriveFunctor -- not needed?
  -- , I.DeriveTraversable -- not needed?
  -- , I.DeriveFoldable -- not needed
  -- , I.NondecreasingIndentation -- ?
  -- , I.SafeImports -- ?
  -- , I.Safe -- not declaring safety
  -- , I.Trustworthy -- not declaring safety
  -- , I.Unsafe -- not declaring safety
  , I.ConstraintKinds -- needed
  -- , I.PolyKinds -- no kind stuff yet
  -- , I.DataKinds -- no kind stuff yet
  -- , I.ParallelArrays -- ?
  , I.RoleAnnotations -- harmless
  , I.OverloadedLists -- needed
  , I.EmptyCase -- harmless
  -- , I.AutoDeriveTypeable -- ?
  , I.NegativeLiterals -- useful
  , I.BinaryLiterals -- harmless
  , I.NumDecimals -- harmless
  -- , I.NullaryTypeClasses -- replaced by MultiParamTypeClasses
  -- , I.ExplicitNamespaces -- implied by TypeFamilies
  -- , I.AllowAmbiguousTypes -- dangerous
  -- , I.JavaScriptFFI -- GHCJS only
  , I.PatternSynonyms -- needed
  , I.PartialTypeSignatures -- useful in scripts
  , I.NamedWildCards -- harmless
  -- , I.DeriveAnyClass -- dangerous
  -- , I.DeriveLift -- not needed?
  , I.StaticPointers -- harmless
  -- , I.StrictData -- dangerous
  -- , I.Strict -- dangerous
  -- , I.ApplicativeDo -- dangerous
  , I.DuplicateRecordFields -- useful
  , I.TypeApplications -- harmless
  , I.TypeInType -- no kind stuff yet
  -- , I.UndecidableSuperClasses -- dangerous
  , I.MonadFailDesugaring -- needed to check that it will work with future GHC versions
  -- , I.TemplateHaskellQuotes -- implied by TemplateHaskell
  -- , I.OverloadedLabels -- harmless, but not needed
  ]