{-# LANGUAGE LambdaCase #-} {-# LANGUAGE BlockArguments #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE DeriveAnyClass #-} module Main where import Control.Monad.IO.Class import Control.Monad.Reader import Control.Exception import qualified Data.ByteString.Lazy as Bsl import qualified Data.Text.Encoding as T import qualified Data.Map.Strict as Map import qualified Data.Text.IO as T import qualified Data.Set as Set import qualified Data.Text as T import Data.Sequence (Seq) import Data.Text ( Text ) import Data.Foldable import Data.Maybe import Data.IORef import Debug.Trace import Elab.Monad hiding (switch) import Elab.WiredIn import Elab.Eval import Elab import Options.Applicative import Presyntax.Presyntax (Posn(Posn)) import Presyntax.Parser import Presyntax.Tokens import Presyntax.Lexer import Prettyprinter import Syntax.Pretty import Syntax import System.Console.Haskeline import System.Exit main :: IO () main = do opts <- execParser parser case opts of Load files exp -> do env <- checkFiles files case exp of [] -> enterReplIn env xs -> traverse_ (evalArgExpr env) xs Check files verbose -> do env <- checkFiles files when verbose $ dumpEnv env Repl -> enterReplIn =<< checkFiles ["./test.tt"] evalArgExpr :: ElabEnv -> String -> IO () evalArgExpr env str = case runAlex (Bsl.fromStrict (T.encodeUtf8 inp)) parseExp of Right e -> flip runElab env (do (e, _) <- infer e liftIO . putStrLn . show . prettyTm . quote . zonk =<< Elab.Eval.eval e) `catch` \e -> do displayExceptions' inp (e :: SomeException) Left e -> liftIO $ print e where inp = T.pack str enterReplIn :: ElabEnv -> IO () enterReplIn env = runInputT defaultSettings (loop env') where env' = env { commHook = T.putStrLn . render . prettyTm . quote . zonk } loop :: ElabEnv -> InputT IO () loop env = do inp <- fmap T.pack <$> getInputLine "% " case inp of Nothing -> pure () Just inp -> case runAlex (Bsl.fromStrict (T.encodeUtf8 inp)) parseRepl of Left e -> do liftIO $ print e loop env Right st -> do env <- liftIO $ runElab (checkStatement st ask) env `catch` \e -> do displayExceptions' inp (e :: SomeException) pure env loop env checkFiles :: [String] -> IO ElabEnv checkFiles files = runElab (go files ask) =<< emptyEnv where go [] k = do env <- ask for_ (Map.toList (nameMap env)) \case (_, v@Defined{}) | Set.member v (definedNames env) -> pure () | otherwise -> let pos = fromJust (Map.lookup v (whereBound env)) in withSpan (fst pos) (snd pos) $ throwElab $ Elab.DeclaredUndefined v _ -> pure () metas <- liftIO $ readIORef (unsolvedMetas env) unless (Map.null metas) $ do liftIO $ reportUnsolved metas k go (x:xs) k = do code <- liftIO $ Bsl.readFile x case runAlex (code <> Bsl.singleton 10) parseProg of Left e -> liftIO $ print e *> error (show e) Right prog -> local (\e -> e { currentFile = Just (T.pack x) }) (checkProgram prog (go xs k)) `catchElab` \e -> liftIO $ displayAndDie (T.decodeUtf8 (Bsl.toStrict code)) (e :: SomeException) dumpEnv :: ElabEnv -> IO () dumpEnv env = for_ (Map.toList (nameMap env)) $ \(_, name) -> let nft = fst $ getEnv env Map.! name in T.putStrLn $ render (pretty name <+> align (nest (negate 1) (colon <+> prettyTm (quote (zonk nft))))) parser :: ParserInfo Opts parser = info (subparser (load <> check) <|> pure Repl <**> helper) (header "cubical - a cubical programming language") where load = command "load" $ info ((Load <$> (some (argument str (metavar "file..."))) <*> (many (strOption (long "eval" <> short 'e' <> help "Also evaluate this expression")))) <**> helper) (progDesc "Check and load a list of files in the REPL") check = command "check" $ info ((Check <$> some (argument str (metavar "file...")) <*> switch ( long "verbose" <> short 'v' <> help "Print the types of all declared/defined values")) <**> helper) (progDesc "Check a list of files") data Opts = Load { files :: [String], eval :: [String] } | Check { files :: [String], verbose :: Bool } | Repl deriving (Eq, Show, Ord) displayAndDie :: Exception e => Text -> e -> IO a displayAndDie lines e = do () <- throwIO e `catches` displayExceptions lines exitFailure displayExceptions :: Text -> [Handler ()] displayExceptions lines = [ Handler \(WhileChecking a b e) -> do T.putStrLn $ squiggleUnder a b lines displayExceptions' lines e , Handler \(SeeAlso a b e) -> do displayExceptions' lines e T.putStrLn $ squiggleUnder a b lines , Handler \(AttachedNote n e) -> do displayExceptions' lines e T.putStrLn $ "\x1b[1;32mnote\x1b[0m: " <> render n , Handler \(WhenCheckingEndpoint le ri ep e) -> do displayExceptions' lines e let endp = case ep of VI0 -> T.pack "left" VI1 -> T.pack "right" _ -> T.pack $ show (prettyTm (quote ep)) T.putStrLn . T.unlines $ [ "\x1b[1;32mnote\x1b[0m: This path was expected to fill the diagram" , "\t " <> render (prettyTm (quote le)) <> " " <> T.replicate 7 (T.singleton '\x2500') <> " " <> render (prettyTm (quote ri)) , "\x1b[1;32mnote\x1b[0m: the " <> endp <> " endpoint is incorrect" ] , Handler \(NotEqual ta tb) -> do putStrLn . unlines $ [ "\x1b[1;31merror\x1b[0m: Mismatch between actual and expected types:" , " * \x1b[1mActual\x1b[0m: " ++ showValue (zonk ta) , " * \x1b[1mExpected\x1b[0m: " ++ showValue (zonk tb) ] , Handler \(NoSuchPrimitive x) -> do putStrLn $ "Unknown primitive: " ++ T.unpack x , Handler \(NotInScope x) -> do putStrLn $ "Variable not in scope: " ++ show (pretty x) , Handler \(Elab.DeclaredUndefined n) -> do putStrLn $ "Name declared but not defined: " ++ show (pretty n) ] reportUnsolved :: Foldable t => Map.Map MV (t (Seq Projection, Value)) -> IO () reportUnsolved metas = do for_ (Map.toList metas) \(m, p) -> do case mvSpan m of Just (f, s, e) -> T.putStrLn . squiggleUnder s e =<< T.readFile (T.unpack f) Nothing -> pure () T.putStrLn . render $ "Unsolved metavariable" <+> prettyTm (Meta m) <+> pretty ':' <+> prettyTm (quote (mvType m)) <+> "should satisfy:" for_ p \p -> T.putStrLn . render $ indent 2 $ prettyTm (quote (VNe (HMeta m) (fst p))) <+> pretty '≡' <+> prettyTm (quote (snd p)) displayExceptions' :: Exception e => Text -> e -> IO () displayExceptions' lines e = displayAndDie lines e `catch` \(_ :: ExitCode) -> pure () squiggleUnder :: Posn -> Posn -> Text -> Text squiggleUnder (Posn l c) (Posn l' c') file | l == l' = let line = T.pack (show l) <> " | " <> T.lines file !! (l - 1) padding = T.replicate (length (show l)) (T.singleton ' ') <> " |" squiggle = T.replicate c " " <> T.pack "\x1b[1;31m" <> T.replicate (c' - c) "~" <> T.pack "\x1b[0m" in T.unlines [ padding, line, padding <> squiggle ] | otherwise = T.unlines (take (l' - l) (drop l (T.lines file))) dumpTokens :: Alex () dumpTokens = do t <- alexMonadScan case tokenClass t of TokEof -> pure () _ -> do traceM (show t) dumpTokens