summaryrefslogtreecommitdiff
path: root/lambda-calcul/haskell/src/Minilang/REPL.hs
blob: 7da2042016e22c3e9929bba02c297fd0ed328240 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeSynonymInstances #-}
{-# OPTIONS_GHC "-fno-warn-orphans" #-}

module Minilang.REPL where

import Control.Monad.Catch.Pure (CatchT (..), SomeException)
import Control.Monad.State
import Data.Text hiding (replicate)
import qualified Data.Text as Text
import qualified Data.Text.IO as Text
import Minilang.Lambda (loadProgram)
import Minilang.Lambda.Eval (Term, newEnv)
import Minilang.Lambda.Infer (newContext)
import Minilang.Lambda.Parser (parseProgram, pretty, resugar)
import Minilang.Lambda.Unify (Type)
import qualified Minilang.Lambda.Unify as Unify
import Minilang.REPL.Haskeline
import Minilang.REPL.IO
import Minilang.REPL.Pure
import Minilang.REPL.Purer
import Minilang.REPL.Run
import Minilang.REPL.Types
import System.Environment (lookupEnv)
import Text.Read (readMaybe)
import System.Console.Haskeline
  ( defaultBehavior,
    defaultSettings,
    historyFile,
    runInputTBehavior,
    setComplete,
  )
import System.IO (Handle, stderr)

-- | Read the evaluator selection from the @MINILANG_EVAL@ environment variable.
-- Returns 'DeBruijn' when the variable is absent or unrecognised.
evaluatorFromEnv :: IO Evaluator
evaluatorFromEnv =
  lookupEnv "MINILANG_EVAL" >>= \case
    Nothing  -> pure DeBruijn
    Just raw -> case readMaybe raw of
      Just ev -> pure ev
      Nothing -> do
        Text.hPutStrLn stderr $
          "Warning: unknown MINILANG_EVAL value " <> Text.pack (show raw)
          <> ", falling back to DeBruijn"
        pure DeBruijn

-- | Build the initial 'REPLEnv' using the given 'Evaluator'.
replEnvWith :: Evaluator -> REPLEnv
replEnvWith ev = initialREPL { evaluator = ev }

-- * Haskeline REPL

withTerminal :: Evaluator -> IO ()
withTerminal ev =
  runInputTBehavior defaultBehavior settings $
    evalStateT (runHaskeline runREPL) (replEnvWith ev)
  where
    settings = setComplete completion $ defaultSettings {historyFile = Just "~/.minilang.history"}

-- * Other REPLs

-- | Pure IO REPL
-- Used for testing purposes
withHandles ::
  Handle -> Handle -> IO ()
withHandles hin hout =
  evalStateT (runConsole runREPL) (IOEnv hin hout initialREPL)

-- | Simple IO REPL, used to evaluate programs from stdin or file loading
withIO :: Evaluator -> Handle -> Handle -> IO ()
withIO ev hin hout = do
  programText <- parseProgram <$> Text.hGetContents hin
  case programText of
    Left err -> Text.hPutStrLn hout (Text.pack (show err))
    Right prog -> case loadProgram ev Untyped newEnv newContext prog of
      Right (results, _, _) -> mapM_ (Text.hPutStrLn hout . render) results
      Left err -> Text.hPutStrLn hout (Text.pack $ show err)
  where
    render :: (Term, Type) -> Text
    render (term, _typ) = pretty (resugar term) -- TODO: handle types <> " : " <> Unify.pretty typ

-- * Pure REPL

withInput ::
  [Text] -> [Text]
withInput stream =
  outputText $ execState (runCatchT (runPure runREPL)) (PureEnv stream [] initialREPL)

-- * Purer REPL

-- This useful for rehydrating and environment from a sequence of inputs

withInputs ::
  REPLEnv -> [In] -> PurerEnv
withInputs initEnv stream =
  execState (runCatchT (runPurer runREPL)) (PurerEnv stream [] initEnv)

runProgram :: PurerREPL a -> PurerEnv
runProgram prog =
  execState (runCatchT (runPurer prog)) (PurerEnv [] [] initialREPL)