summaryrefslogtreecommitdiff
path: root/lambda-calcul/haskell/src/Minilang/REPL/Haskeline.hs
blob: a822116a051850454ddd8c233bcc20b3795c4440 (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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC "-fno-warn-orphans" #-}
{-# LANGUAGE LambdaCase #-}

module Minilang.REPL.Haskeline where

import Control.Exception (throw)
import Control.Monad.Catch
import Control.Monad.Reader
import Control.Monad.State
import Data.Bifunctor
import Data.List
  ( isPrefixOf,
    isSuffixOf,
  )
import Data.Text (pack, unpack)
import Data.Text.IO as Text
import Minilang.REPL.Run (handleCommand)
import Minilang.REPL.Types
import System.Console.Haskeline
  ( Completion (Completion, replacement),
    CompletionFunc,
    InputT,
    getInputLineWithInitial,
    outputStrLn,
  )
import System.Directory (listDirectory)
import Minilang.Lambda.Parser (resugar, pretty)
import qualified Minilang.Lambda.Unify as Unify

-- * Haskeline REPL

newtype Haskeline a = Haskeline {runHaskeline :: StateT REPLEnv (InputT IO) a}
  deriving (Functor, Applicative, Monad, MonadIO, MonadState REPLEnv)

hoist :: InputT IO a -> Haskeline a
hoist m = Haskeline $ StateT $ \s -> m >>= \a -> pure (a, s)

instance MonadREPL Haskeline where
  input = do
    i <- hoist (getInputLineWithInitial "λΠ> " ("", ""))
    case i of
      Nothing -> pure EOF
      Just (pack -> t) -> pure $ interpret t
  output = hoist . outputStrLn . render
  prompt = pure ()
  load = fmap (first FileError) . hoist . lift . try . Text.readFile . unpack

  getEnv = get
  setEnv = put

render :: Out -> String
render = \case
  Msg t -> unpack t
  Evaluated term typ -> unpack $ pretty (resugar term) <> " : " <> Unify.pretty typ
  other -> show other

completion :: CompletionFunc IO
completion = completion' . first reverse

data CompleteWith
  = SetUnset String
  | LoadFile
  | AllCommands

completion' :: CompletionFunc IO
completion' (inputString, _) =
  case matchedCompletionRule inputString of
    SetUnset com : _ -> setUnsetCompletion com
    LoadFile : _ -> completeLoadWithFiles (drop 6 inputString)
    _ -> allCommandCompletions
  where
    matchedCompletionRule prefix =
      snd
        <$> filter
          (($ prefix) . fst)
          [ ((":set" `isPrefixOf`), SetUnset ":set"),
            ((":unset" `isPrefixOf`), SetUnset ":unset"),
            ((":load" `isPrefixOf`), LoadFile),
            (const True, AllCommands)
          ]

    completeLoadWithFiles prefix = do
      files <- filter (prefix `isPrefixOf`) . filter (".mtt" `isSuffixOf`) <$> listDirectory "."
      pure ("", fmap (\fp -> Completion (":load " <> fp) fp True) files)

    setUnsetCompletion com =
      pure
        ( "",
          filter
            ((inputString `isPrefixOf`) . replacement)
            [ Completion (com <> " step") "step - Step through evaluation process, displaying each rule application" True,
              Completion (com <> " debug") "debug - Debug parsing of input" True
            ]
        )

    allCommandCompletions =
      pure
        ( "",
          filter
            ((inputString `isPrefixOf`) . replacement)
            [ Completion ":quit" ":quit - Quit minilang REPL" False,
              Completion ":env" ":env - Dump current environment and context (types and values)" False,
              Completion ":clear" ":clear - Clear environment" False,
              Completion ":load" ":load - Load a file into the REPL" True,
              Completion ":set" ":set - Set some properties of the REPL" True,
              Completion ":unset" ":unset - Unset some properties of the REPL" True,
              Completion ":help" ":help - Provide help on commands" True
            ]
        )

instance MonadCatch Haskeline where
  Haskeline m `catch` f = Haskeline $ m `catch` \e -> runHaskeline (f e)

instance MonadThrow Haskeline where
  throwM = throw