lol-calculus-1.20160822: Language/LOL/Calculus/REPL.hs
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE Rank2Types #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ViewPatterns #-}
{-# OPTIONS_GHC -fno-warn-tabs #-}
module Language.LOL.Calculus.REPL where
import Control.Applicative (Applicative(..), (<$>))
import Control.Exception
import Control.Monad
import Control.Monad.State
import Data.Bool
import qualified Data.Char as Char
import Data.Either (Either(..))
import Data.Eq (Eq(..))
import Data.Foldable (Foldable(..))
import Data.Function (($), (.), const, flip)
import Data.Functor.Identity
import qualified Data.List as List
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (Maybe(..))
import Data.Monoid (Monoid(..), (<>))
import Data.Ord (Ord(..))
import Data.String (String)
import Data.Text (Text)
import qualified Data.Text as Text
import Data.Text.Buildable (Buildable(..))
import qualified Data.Text.Lazy as TL
import qualified Data.Text.Lazy.Builder as Builder
import Data.Tuple (fst)
import Data.Typeable as Typeable
import Prelude (Integer, Num(..), div, error)
import System.Console.Haskeline
import System.Directory
import System.FilePath
import System.IO (IO, readFile)
import qualified Text.Parsec as R
import Text.Show (Show(..))
import Language.LOL.Calculus
import Language.LOL.Calculus.Read
-- * Type 'REPL'
-- | /Read Eval Print Loop/ monad.
newtype REPL a
= REPL
{ unREPL :: StateT REPL_State (InputT IO) a }
deriving ( Functor
, Applicative
, Monad
, MonadIO
, MonadState REPL_State )
data REPL_State
= REPL_State
{ repl_state_env :: Env
, repl_state_load_dir :: FilePath
, repl_state_load_done :: Map FilePath ()
}
main :: IO ()
main = do
cwd <- getCurrentDirectory
runInputT defaultSettings $
evalStateT (unREPL main_loop)
REPL_State
{ repl_state_env = prelude axioms
, repl_state_load_dir = cwd
, repl_state_load_done = mempty
}
where
main_loop :: REPL ()
main_loop = do
let prompt = "> "
line <- repl_read_command prompt
case slice <$> line of
Just (cmd, input)
| not (length cmd > 1 && cmd `List.isPrefixOf` "quit") ->
dispatch cmd input
_ -> return ()
main_loop
slice :: String -> (String, String)
slice (':':str) = List.break Char.isSpace str
slice str = ("", str)
-- ** I/O
print :: Buildable a => a -> TL.Text
print = Builder.toLazyText . build
repl_write_string_ln :: String -> REPL ()
repl_write_string_ln = REPL . lift . outputStrLn
repl_write_string :: String -> REPL ()
repl_write_string = REPL . lift . outputStr
repl_show :: Show a => a -> REPL ()
repl_show = repl_write_string_ln . show
repl_print :: Buildable a => a -> REPL ()
repl_print = repl_write_string . TL.unpack . print
repl_print_ln :: Buildable a => a -> REPL ()
repl_print_ln = repl_write_string_ln . TL.unpack . print
repl_read :: Parser Identity x -> String -> (x -> REPL ()) -> REPL ()
repl_read _p [] _k = return ()
repl_read p s k =
case runIdentity $ read p s of
Right x -> k x
Left err -> do
repl_write_string_ln "Parsing error:"
repl_show err
repl_write_string " Command_Input: "
repl_write_string_ln s
-- ** Commands
repl_read_command :: String -> REPL (Maybe String)
repl_read_command prompt = do
line <- REPL . lift $ getInputLine prompt
case line of
Just s@(':':_) -> return $ Just s
Just l@(_:_) | List.last l == ' ' -> parse_block l
Just s -> return $ Just s
Nothing -> return Nothing
where
parse_block :: String -> REPL (Maybe String)
parse_block s = do
line <- REPL . lift $ getInputLine ""
case line of
Just l@(_:_) | List.last l == ' ' -> parse_block (s <> ('\n':l))
_ -> return $ Just s
type Command_Name = String
type Command = Command_Input -> IO Command_Output
command :: Command_Name -> Command
command "" = command "let"
command cmd =
case List.find (\p -> cmd `List.isPrefixOf` fst p) commands of
Nothing -> \_ -> return $ Left $ Error_Command cmd
Just (_, c) -> c
where
commands :: [(String, Command)]
commands =
[ ("assume", command_assume) -- Type -> IO ()
, ("code" , command_code) -- Var -> Term
, ("dump" , command_dump) -- Term -> Text
, ("equiv", command_equiv) -- Term -> Term -> Bool
-- , ("echo" , command_echo) -- Term -> Term
, ("let" , command_let) -- Var -> Term -> IO ()
, ("load" , command_load) -- FilePath -> IO ()
, ("nf" , command_normalize normalize) -- Term -> Term
, ("nf_dump", command_normalize_dump normalize) -- Term -> Term
, ("reset", command_reset) -- () -> IO ()
, ("type" , command_type) -- Term -> Type
, ("type_dump", command_type_dump) -- Term -> Text
, ("whnf" , command_normalize whnf) -- Term -> Term
]
command_run :: Command_Name -> Command
command_run cmd (Command_Input i st) =
command cmd $
Command_Input (strip_spaces i) st
where
strip_spaces =
List.reverse . List.dropWhile Char.isSpace .
List.reverse . List.dropWhile Char.isSpace
dispatch :: Command_Name -> String -> REPL ()
dispatch cmd i = do
st <- get
output <- liftIO $ command_run cmd (Command_Input i st)
case output of
Left (err::Error) ->
repl_print err
Right (Command_Result msg new_st) -> do
case TL.unpack $ print msg of
[] -> return ()
o -> repl_write_string_ln o
put new_st
data Command_Input
= Command_Input String REPL_State
type Command_Output
= Either Error Command_Result
data Error
= Error_Parse String R.ParseError
| Error_Type Type_Error
| Error_Let Var_Name Type_Error
| Error_Code Var_Name
| Error_Command Command_Name
| Error_IO IOException
| Error_Load FilePath Error
deriving (Show)
instance Buildable Error where
build err =
case err of
Error_Parse s e -> "Error: parsing: " <> build s <> "\n" <> build (show e) <> "\n"
Error_Type e -> build e
Error_Let var e -> "Error: in let: " <> build var <> "\n " <> build e
Error_Code var -> "Error: no such variable in environment: " <> build var
Error_Command cmd -> "Error: unrecognized command: " <> build cmd <> "\n"
Error_IO e -> "Error: " <> build (show e) <> "\n"
Error_Load file e ->
"Error: loading: " <> build file <> "\n"
<> build e
data Command_Result
= forall msg.
( Buildable msg
) => Command_Result msg REPL_State
command_assume :: Command
command_assume (Command_Input str st) =
return $
let ctx = context_from_env $ repl_state_env st in
case runIdentity $ read parse_assume str of
Left err -> Left $ Error_Parse str err
Right (v, ty) ->
case type_of ctx ty of
Left err -> Left $ Error_Type err
Right _ty_ty -> Right $
Command_Result (""::Text) $
st{repl_state_env = env_insert v
(TeTy_Axiom (Axiom_Type_Assume ty)) ty $
repl_state_env st
}
command_code :: Command
command_code (Command_Input str st) =
return $
let env = repl_state_env st in
case runIdentity $ read parse_var_name str of
Left err -> Left $ Error_Parse str err
Right var ->
case Map.lookup var env of
Nothing -> Left $ Error_Code var
Just item -> Right $ Command_Result
(form_given $ env_item_term item)
st
command_dump :: Command
command_dump (Command_Input str st) =
return $
case runIdentity $ read parse_term str of
Left err -> Left $ Error_Parse str err
Right te -> Right $ Command_Result (show te) st
command_let :: Command
command_let (Command_Input [] st) =
return $ Right $ Command_Result (""::Text) st
command_let (Command_Input str st) =
let toks_or_err = runIdentity $ lex lex_all str in
case toks_or_err of
Left err -> return $ Left $ Error_Parse str err
Right [] -> return $ Right $ Command_Result (""::Text) st
Right toks ->
case runIdentity $ parse parse_let_or_term toks of
Left err -> return $ Left $ Error_Parse str err
Right let_or_term -> do
let ctx = context_from_env $ repl_state_env st
case let_or_term of
Left (v, mty, te) ->
let ety = case mty of
Nothing -> type_of ctx te
Just ty -> do
_ty_ty <- type_of ctx ty
const ty <$> check ctx (context_apply ctx ty) te
in
return $
case ety of
Left err -> Left $ Error_Let v err
Right ty ->
Right $ Command_Result (build v <> " : " <> build ty) $
st{repl_state_env = env_insert v te ty $ repl_state_env st}
Right let_te ->
case type_of ctx let_te of
Left err -> return $ Left $ Error_Type err
Right _ty ->
let norm_te = normalize ctx let_te in
case norm_te of
TeTy_Axiom (axiom_cast -> Just (Axiom_Term (io::IO (Term Var_Name)) _o_ty)) -> do
io_te <- io
return $ Right $ Command_Result io_te st
_ ->
return $ Right $ Command_Result norm_te st
command_load :: Command
command_load (Command_Input file input_st) = do
err_or_io <- try $ do
path <- canonicalizePath (repl_state_load_dir input_st </> file)
content <- readFile path
return (path, content)
case err_or_io of
Left (err::IOException) -> return $ Left $ Error_IO err
Right (modul, content) ->
case Map.lookup modul $ repl_state_load_done input_st of
Just _ -> return $ Right $
Command_Result ("Module already loaded: " <> modul) input_st
_ ->
case runIdentity $ R.runParserT (parse_commands <* R.eof) () modul content of
Left err -> return $ Left $ Error_Load modul $ Error_Parse content err
Right cmds -> do
let old_dir = repl_state_load_dir input_st
err_or_st <-
foldM (\err_or_st (cmd, i) ->
case err_or_st of
Left _ -> return err_or_st
Right (Command_Result last_msg last_st) -> do
o <- command_run cmd (Command_Input i last_st)
case o of
Left _ -> return o
Right (Command_Result msg running_st) ->
return $ Right $
Command_Result (build last_msg <> "\n" <> build msg) running_st
)
(Right $ Command_Result ("Loading: " <> modul)
input_st{repl_state_load_dir = takeDirectory modul})
cmds
return $
case err_or_st of
Left err -> Left $ Error_Load modul err
Right (Command_Result msg result_st) ->
Right $ Command_Result msg $ result_st
{ repl_state_load_dir = old_dir
, repl_state_load_done =
Map.insert modul () $
repl_state_load_done result_st
}
command_normalize
:: (Context Var_Name -> Term Var_Name -> Term Var_Name)
-> Command
command_normalize norm (Command_Input str st) = do
let ctx = context_from_env $ repl_state_env st
case runIdentity $ read parse_term str of
Left err -> return $ Left $ Error_Parse str err
Right (te::Term Var_Name) ->
let n_te = norm ctx te in
case n_te of
TeTy_Axiom (axiom_cast -> Just (Axiom_Term (o::IO (Term Var_Name)) _o_ty)) -> do
r <- o
return $ Right $ Command_Result r st
TeTy_Axiom (axiom_cast -> Just (Axiom_Term (o::IO Text) _o_ty)) -> do
r <- o
return $ Right $ Command_Result r st
_ ->
return $ Right $ Command_Result n_te st
command_equiv :: Command
command_equiv (Command_Input str st) = do
let ctx = context_from_env $ repl_state_env st
return $
case runIdentity $ read ((,) <$> parse_term <* (parse_token Token_Equal >> parse_token Token_Equal) <*> parse_term) str of
Left err -> Left $ Error_Parse str err
Right (x_te, y_te) -> Right $
Command_Result (if equiv ctx x_te y_te then "True" else "False"::Text) st
command_normalize_dump
:: (Context Var_Name -> Term Var_Name -> Term Var_Name)
-> Command
command_normalize_dump norm (Command_Input str st) = do
let ctx = context_from_env $ repl_state_env st
return $
case runIdentity $ read parse_term str of
Left err -> Left $ Error_Parse str err
Right te -> Right $ Command_Result (show $ norm ctx te) st
command_reset :: Command
command_reset (Command_Input _ st) =
return $ Right $
Command_Result (""::Text) st{repl_state_env = mempty}
command_type :: Command
command_type (Command_Input [] st) = do
let env = repl_state_env st
return $
Right $ Command_Result
(
foldr (flip (<>)) "" $
List.intersperse "\n" $
(\(name, item) ->
build name <> " : "
<> build (form_given $ env_item_type item))
<$> Map.toList env
)
st
command_type (Command_Input str st) = do
let ctx = context_from_env $ repl_state_env st
return $
case runIdentity $ read parse_term str of
Left err -> Left $ Error_Parse str err
Right te ->
case type_of ctx te of
Left err -> Left $ Error_Type err
Right ty -> Right $ Command_Result (normalize ctx ty) st
command_type_dump :: Command
command_type_dump (Command_Input [] st) = do
let env = repl_state_env st
return $
Right $ Command_Result
(
foldr (flip (<>)) "" $
List.intersperse "\n" $
(\(name, item) ->
build name <> " : "
<> build (show $ form_given $ env_item_type item))
<$> Map.toList env
)
st
command_type_dump (Command_Input str st) = do
let ctx = context_from_env $ repl_state_env st
return $
case runIdentity $ read parse_term str of
Left err -> Left $ Error_Parse str err
Right te ->
case type_of ctx te of
Left err -> Left $ Error_Type err
Right ty -> Right $ Command_Result (show $ normalize ctx ty) st
{-
command_echo :: String -> REPL ()
command_echo str =
repl_read parse_term str repl_print_ln
-}
-- * Builtins
builtin :: Axioms -> [Text] -> Env
builtin =
foldl $ \env str ->
let ctx = context_from_env env in
read_string parse_let str $ \(v, mty, te) ->
let ety = case mty of
Just ty -> do
_ty_ty <- type_of ctx ty
const ty <$> check ctx (context_apply ctx ty) te
Nothing -> type_of ctx te in
case ety of
Left err -> error $ show err
Right ty -> env_insert v te ty env
where
read_string p s k =
case runIdentity $ read p s of
Right x -> k x
Left err -> error $
"Parsing_error:\n" <> show err
<> " Input: " <> Text.unpack s
axioms :: Axioms
axioms =
(axioms_monopoly <>) $
(Map.fromList axioms_io <>) $
(Map.fromList axioms_int <>) $
(Map.fromList axioms_text <>) $
Map.fromList
[ ("Unit", item Axiom_Type_Unit)
]
where
item :: (Axiomable (Axiom ax), Typeable ax) => Axiom ax -> Env_Item
item = env_item_from_axiom context
{-
axioms_arr =
[ -- ("Arr", item $ Axiom_Term_Abst)
]
-}
axioms_text =
[ ("Text" , item Axiom_Type_Text)
, ("text_empty", item $ axiom_term (""::Text))
, ("text_hello", item $ axiom_term ("Hello World!"::Text))
]
axioms_int =
[ ("Int" , item Axiom_Type_Integer)
, ("int_zero" , item $ axiom_term (0::Integer))
, ("int_one" , item $ axiom_term (1::Integer))
, ("int_add" , item $ axiom_term ((+)::Integer -> Integer -> Integer))
, ("int_neg" , item $ axiom_term (negate::Integer -> Integer))
, ("int_sub" , item $ axiom_term ((-)::Integer -> Integer -> Integer))
, ("int_mul" , item $ axiom_term ((*)::Integer -> Integer -> Integer))
, ("int_div" , item $ axiom_term (div::Integer -> Integer -> Integer))
]
axioms_io =
[ ("IO" , item (Axiom_Type_IO::Axiom (IO A)))
, ("return_io", item return_io)
, ("bind_io" , item bind_io)
, ("join_io" , item join_io)
-- , ("return_io_text", item $ axiom_term $ (return::Text -> IO Text))
-- , ("return_io_int", item $ Axiom_Term $ (return::Int -> IO Int))
]
where
-- | @return_io : ∀(A:*) -> A -> IO A@
return_io :: Axiom Axiom_Type_Abst
return_io =
Axiom_Type_Abst "A" (TeTy_Axiom . ax_te) ax_ty
where
ax_te :: Type Var_Name
-> Axiom (Axiom_Term (Term Var_Name -> IO (Term Var_Name)))
ax_te ty = Axiom_Term return (\ctx -> context_lift ctx <$> ty)
ax_ty :: Abstraction (Suggest Var_Name) Type Var_Name
ax_ty =
(("A" =?) `abstract`) $
axiom_type_of context $
axiom_term (return::A -> IO A)
-- | @bind_io : ∀(A:*) -> ∀(B:*) -> IO A -> (A -> IO B) -> IO B@
bind_io :: Axiom Axiom_Type_Abst
bind_io = ax1_te
where
ax1_te :: Axiom Axiom_Type_Abst
ax1_te =
Axiom_Type_Abst "A"
(\(Type_Abst _ _ ty_a_abst) ->
TeTy_Axiom $
Axiom_Type_Abst "B"
(TeTy_Axiom . ax0_te)
ty_a_abst)
ax1_ty
ax1_ty =
(("A" =?) `abstract`) $
Type_Abst "B"
(Type_Sort (Type_Level_0, Type_Morphism_Mono))
ax0_ty
ax0_te
:: Type Var_Name
-> Axiom (Axiom_Term
( IO (Term Var_Name)
-> (Term Var_Name -> IO (Term Var_Name))
-> IO (Term Var_Name)
))
ax0_te ty =
Axiom_Term (>>=) (\ctx -> context_lift ctx <$> ty)
ax0_ty =
(("B" =?) `abstract`) $
axiom_type_of context $
axiom_term ((>>=)::IO A -> (A -> IO B) -> IO B)
-- | @join_io : ∀(A:*) -> IO (IO A) -> IO A@
join_io :: Axiom Axiom_Type_Abst
join_io = ax1_te
where
ax1_te :: Axiom Axiom_Type_Abst
ax1_te =
Axiom_Type_Abst "A"
(\ty_a ->
TeTy_Axiom $
Axiom_Term
(join::IO (IO (Term Var_Name)) -> IO (Term Var_Name))
(\ctx -> context_lift ctx <$> ty_a))
ax1_ty
ax1_ty =
(("A" =?) `abstract`) $
axiom_type_of context $
axiom_term (join::IO (IO A) -> IO A)
prelude :: Axioms -> Env
prelude axs =
builtin axs $ List.concat $ (Text.unlines <$>) <$>
[ prelude_bool
, prelude_either
]
where
prelude_bool :: [[Text]]
prelude_bool =
[ ["Bool_Polytype : *p = (R:*) -> R -> R -> R"]
, ["Bool : *m = Monotype Bool_Polytype"]
, ["True : Bool = monotype Bool_Polytype (λ(R:*) (True:R) (False:R) -> True)"]
, ["False : Bool = monotype Bool_Polytype (λ(R:*) (True:R) (False:R) -> False)"]
, [ "eq (x:Bool) (y:Bool) : Bool"
, " = monotype Bool_Polytype"
, " (λ(R:*) (True:R) (False:R) ->"
, " polytype Bool_Polytype x R"
, " (polytype Bool_Polytype y R True False)"
, " (polytype Bool_Polytype y R False True)"
, " )"
]
, [ "and (x:Bool) (y:Bool) : Bool"
, " = monotype Bool_Polytype"
, " (λ(R:*) (True:R) (False:R) ->"
, " polytype Bool_Polytype x R"
, " (polytype Bool_Polytype y R True False)"
, " (polytype Bool_Polytype y R False False)"
, " )"
]
, [ "or (x:Bool) (y:Bool) : Bool"
, " = monotype Bool_Polytype"
, " (λ(R:*) (True:R) (False:R) ->"
, " polytype Bool_Polytype x R"
, " (polytype Bool_Polytype y R True True)"
, " (polytype Bool_Polytype y R True False)"
, " )"
]
, [ "xor (x:Bool) (y:Bool) : Bool"
, " = monotype Bool_Polytype"
, " (λ(R:*) (True:R) (False:R) ->"
, " polytype Bool_Polytype x R"
, " (polytype Bool_Polytype y R False True)"
, " (polytype Bool_Polytype y R True False)"
, " )"
]
, [ "not (x:Bool) : Bool"
, " = monotype Bool_Polytype"
, " (λ(R:*) (True:R) (False:R) ->"
, " polytype Bool_Polytype x R False True)"
]
]
prelude_either :: [[Text]]
prelude_either =
[ ["Either_Polytype (A:*) (B:*) : *p = (R:*) -> (A -> R) -> (B -> R) -> R"]
, ["Either (A:*) (B:*) : *m = Monotype (Either_Polytype A B)"]
, [ "Left (A:*) (B:*) (x:A)"
, " : Either A B"
, " = monotype (Either_Polytype A B)"
, " (λ(R:*) (Left:A -> R) (Right:B -> R) -> Left x)"
]
, [ "Right (A:*) (B:*) (x:B)"
, " : Either A B"
, " = monotype (Either_Polytype A B)"
, " (λ(R:*) (Left:A -> R) (Right:B -> R) -> Right x)"
]
, [ "either (A:*) (B:*) (R:*) (l:A -> R) (r:B -> R) (e:Either A B) : R"
, " = polytype (Either_Polytype A B) e R l r"
]
]