packages feed

tidal-1.7.10: main/Main.hs

{-
    Tidal REPL - mimicking ghci

    Copyright (C) 2021 Johannes Waldmann and contributors

    Forked from:
    https://github.com/jwaldmann/safe-tidal-cli/

    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/>.
-}

{-# language PatternSignatures, LambdaCase #-}

import qualified Language.Haskell.Interpreter as I
import qualified Sound.Tidal.Safe.Context as C
import Control.Monad (void)
import Control.Exception (throw)
import Control.Monad.IO.Class ( MonadIO(liftIO) )
import Control.Monad.Catch
    ( SomeException, MonadCatch(catch), catches, Handler(Handler) )
-- import qualified Mueval.Resources as MR
import System.Timeout ( timeout )
import System.IO ( hFlush, stderr, stdout, hPutStrLn, Handle )
import Data.Char (isSpace)
import Data.List (isPrefixOf)

main :: IO()
main = do
  
  -- from BootTidal.hs:
  tidal <- C.startTidal
    (C.superdirtTarget
      { C.oLatency = 0.1, C.oAddress = "127.0.0.1"
      , C.oPort = 57120})
    (C.defaultConfig {C.cFrameTimespan = 1/20})
    
  void $ I.runInterpreter
    $ catch (core tidal)
    $ \ (e :: SomeException) -> message stderr $ show e

core :: C.Stream -> I.InterpreterT IO ()
core tidal = do
    message stdout "[tidal] starting..."
  -- more settings at
  -- https://github.com/tidalcycles/tidali/blob/master/src/Main.hs
    I.set [ I.languageExtensions
              I.:= [ I.OverloadedStrings ]
            , I.installedModulesInScope I.:= False
            ]
    I.setImports
      [ "Prelude"
      , "Sound.Tidal.Safe.Context"
      , "Sound.Tidal.Safe.Boot"
      ]
    -- FIXME: replace lazy IO by some streaming mechanism?
    message stdout "[tidal] modules loaded..."
    input <- liftIO getContents 
    message stdout "[tidal] ready"
    mapM_ (work tidal . unlines) $ blocks $ lines input
    message stdout "safe-tidal-cli is done"

second :: Int
second = 10^6 :: Int

-- | will show at most 10 lines, at most 80 chars per line,
-- and run (evaluation and print) for at most 1 second
message :: Handle -> String -> I.InterpreterT IO ()
message h s = do
  let safe = unlines . safe_list 20 ["..."] . map (safe_list 120 "...") . lines
  liftIO $ void $ timeout (1 * second) $ do
    hPutStrLn h (safe s) ; hFlush h


-- | if `length xs <= c`, then `xs`, else `xs <> msg`
safe_list :: Int -> [a] -> [a] -> [a]
safe_list n msg xs =
  let (pre,post) = splitAt n xs
  in  if null post then pre
      else pre <> msg

work :: C.Stream -> String -> I.InterpreterT IO ()
work tidal contents = 
        ( if take 2 contents `elem` [ ":t", ":i", ":d", ":s" ]
          then do
            -- https://github.com/haskell-hint/hint/issues/101
            message stderr $ "not implemented " <>  contents
          else 
           I.typeChecksWithDetails contents >>= \ case
             Left errs -> throw $ I.WontCompile errs
             Right s ->
               if s == "Op ()" then do -- execute, print nothing
                 -- TODO: need timeout for evaluation of pattern:
                 x <- I.interpret contents (I.as :: C.Op ())
                 -- have timeout for execution of pattern:
                 liftIO $ void $ timeout (1 * second) $ C.exec tidal x
               else do -- print type and value
                 message stdout $ "type : " <> s
                 if isPrefixOf "IO" s then do
                   message stderr "cannot show value, will not execute action"
                 else do
                   v <- I.eval contents
                   message stdout $ "value: " <> v
        )
      `catches`
      [ Handler $ \ (e :: I.InterpreterError) ->
        message stderr $ unlines $ case e of
          I.UnknownError s -> [ "UnknownError", s ]
          I.WontCompile gs -> "WontCompile" : map I.errMsg gs
          I.NotAllowed s   -> [ "NotAllowed", s ]
          I.GhcException s -> [ "GhcException", s ]
      , Handler $ \ (e :: SomeException) ->
        message stderr $ show e
      ]

-- | Mimicking ghci, where a block is wrapped in `:{` and `:}`, on otherwise empty lines.

blocks :: [String] -> [[String]]
blocks [] = []
blocks (":{":ls) = b:(blocks ls')
  where (b, ls') = block ls
blocks (l:ls) = [l]:(blocks ls)

block :: [String] -> ([String], [String])
block [] = ([],[])
block (":}":ls) = ([],ls)
block (l:ls) = (l:b, ls')
  where (b, ls') = block ls