less prototype, less bad code implementation of CCHM type theory
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

292 lines
10 KiB

  1. {-# LANGUAGE LambdaCase #-}
  2. {-# LANGUAGE BlockArguments #-}
  3. {-# LANGUAGE OverloadedStrings #-}
  4. {-# LANGUAGE ScopedTypeVariables #-}
  5. {-# LANGUAGE DeriveAnyClass #-}
  6. {-# LANGUAGE NamedFieldPuns #-}
  7. module Main where
  8. import Control.Monad.IO.Class
  9. import Control.Monad.Reader
  10. import Control.Exception
  11. import qualified Data.ByteString.Lazy as Bsl
  12. import qualified Data.Text.Encoding as T
  13. import qualified Data.Text.Lazy.IO as Lt
  14. import qualified Data.Map.Strict as Map
  15. import qualified Data.Text.Lazy as Lt
  16. import qualified Data.Text.IO as T
  17. import qualified Data.Set as Set
  18. import qualified Data.Text as T
  19. import Data.Sequence (Seq)
  20. import Data.Text ( Text )
  21. import Data.Foldable
  22. import Data.Maybe
  23. import Data.IORef
  24. import Debug (traceDocM)
  25. import Elab.Monad hiding (switch)
  26. import Elab.WiredIn
  27. import Elab.Eval
  28. import Elab
  29. import Options.Applicative
  30. import Presyntax.Presyntax (Posn(Posn))
  31. import Presyntax.Parser
  32. import Presyntax.Tokens
  33. import Presyntax.Lexer
  34. import Prettyprinter
  35. import Syntax.Pretty
  36. import Syntax
  37. import System.Console.Haskeline
  38. import System.Exit
  39. main :: IO ()
  40. main = do
  41. opts <- execParser parser
  42. case opts of
  43. Load files exp -> do
  44. env <- checkFiles files
  45. case exp of
  46. [] -> enterReplIn env
  47. xs -> traverse_ (evalArgExpr env) xs
  48. Check files verbose -> do
  49. env <- checkFiles files
  50. when verbose $ dumpEnv env
  51. Repl -> enterReplIn =<< emptyEnv
  52. evalArgExpr :: ElabEnv -> String -> IO ()
  53. evalArgExpr env str =
  54. case runAlex (Bsl.fromStrict (T.encodeUtf8 inp)) parseExp of
  55. Right e ->
  56. flip runElab env (do
  57. (e, _) <- infer e
  58. liftIO . print . prettyTm . quote =<< Elab.Eval.eval e)
  59. `catch` \e -> do
  60. displayExceptions' inp (e :: SomeException)
  61. Left e -> liftIO $ print e
  62. where
  63. inp = T.pack str
  64. enterReplIn :: ElabEnv -> IO ()
  65. enterReplIn env =
  66. do
  67. let env' = mkrepl env
  68. envref <- newIORef env'
  69. runInputT (setComplete (complete envref) defaultSettings) (loop env' envref)
  70. where
  71. mkrepl env = env { commHook = Lt.putStrLn . render }
  72. complete :: IORef ElabEnv -> (String, String) -> IO (String, [Completion])
  73. complete c = completeWord Nothing " \n\t\r" go where
  74. go w = do
  75. env <- readIORef c
  76. let
  77. w' = T.pack w
  78. words = Set.toList $ Set.filter ((w' `T.isPrefixOf`) . getNameText) (definedNames env)
  79. pure (map (simpleCompletion . T.unpack . getNameText) words)
  80. loop :: ElabEnv -> IORef ElabEnv -> InputT IO ()
  81. loop env envvar = do
  82. inp <- fmap T.pack <$> getInputLine "% "
  83. case inp of
  84. Nothing -> pure ()
  85. Just inp | ":r" `T.isPrefixOf` inp -> reload env envvar
  86. Just inp ->
  87. case runAlex (Bsl.fromStrict (T.encodeUtf8 inp)) parseRepl of
  88. Left e -> do
  89. liftIO $ print e
  90. loop env envvar
  91. Right st -> do
  92. env <- liftIO $
  93. runElab (checkStatement st ask) env
  94. `catch` \e -> do
  95. displayExceptions' inp (e :: SomeException)
  96. pure env
  97. metas <- liftIO $ atomicModifyIORef' (unsolvedMetas env) (\x -> (mempty, x))
  98. unless (Map.null metas) $ do
  99. liftIO $ reportUnsolved (Just inp) metas
  100. liftIO $ writeIORef envvar env
  101. loop env envvar
  102. reload :: ElabEnv -> IORef ElabEnv -> InputT IO ()
  103. reload env@ElabEnv{loadedFiles} envref = do
  104. newe <- liftIO $ try $ mkrepl <$> checkFiles (reverse loadedFiles)
  105. case newe of
  106. Left e -> do
  107. liftIO $ do displayExceptions' ":r" (e :: SomeException)
  108. loop env envref
  109. Right newe -> do
  110. liftIO $ writeIORef envref newe
  111. loop newe envref
  112. checkFiles :: [String] -> IO ElabEnv
  113. checkFiles files = runElab (go 1 files ask) =<< emptyEnv where
  114. size = length files
  115. sl = length (show size)
  116. pad s
  117. | length s < sl = replicate (sl - length s) ' ' ++ s
  118. | otherwise = s
  119. go _ [] k = do
  120. env <- ask
  121. for_ (Map.toList (nameMap env)) \case
  122. (_, v@Defined{})
  123. | Set.member v (definedNames env) -> pure ()
  124. | otherwise ->
  125. let
  126. pos = fromJust (Map.lookup v (whereBound env))
  127. in withSpan (fst pos) (snd pos) $ throwElab $ Elab.DeclaredUndefined v
  128. _ -> pure ()
  129. metas <- liftIO $ atomicModifyIORef' (unsolvedMetas env) (\x -> (mempty, x))
  130. unless (Map.null metas) $ do
  131. liftIO $ reportUnsolved Nothing metas
  132. k
  133. go n (x:xs) k = do
  134. liftIO . putStrLn $ "[" ++ pad (show n) ++ "/" ++ show size ++ "] Loading " ++ x
  135. code <- liftIO $ Bsl.readFile x
  136. case runAlex (code <> Bsl.singleton 10) parseProg of
  137. Left e -> liftIO (print e) *> k
  138. Right prog ->
  139. local (\e -> e { currentFile = Just (T.pack x), loadedFiles = x:loadedFiles e }) (checkProgram prog (go (n + 1 :: Int) xs k))
  140. `catchElab` \e -> liftIO $ displayAndDie (T.decodeUtf8 (Bsl.toStrict code)) (e :: SomeException)
  141. dumpEnv :: ElabEnv -> IO ()
  142. dumpEnv env = for_ (Map.toList (nameMap env)) $ \(_, name) ->
  143. let nft = fst $ getEnv env Map.! name in
  144. Lt.putStrLn $ render (pretty name <+> nest (negate 1) (colon <+> align (prettyTm (quote (zonk nft)))))
  145. parser :: ParserInfo Opts
  146. parser = info (subparser (load <> check <> repl) <|> pure Repl <**> helper) (header "cubical - a cubical programming language")
  147. where
  148. load = command "load" $
  149. info ((Load <$> (some (argument str (metavar "file...")))
  150. <*> (many (strOption (long "eval" <> short 'e' <> help "Also evaluate this expression"))))
  151. <**> helper) (progDesc "Check and load a list of files in the REPL")
  152. repl = command "load" $
  153. info ((Load <$> (many (argument str (metavar "file...")))
  154. <*> (many (strOption (long "eval" <> short 'e' <> help "Also evaluate this expression"))))
  155. <**> helper) (progDesc "Enter the REPL, optionally with loaded files")
  156. check = command "check" $
  157. info ((Check <$> some (argument str (metavar "file..."))
  158. <*> switch ( long "verbose"
  159. <> short 'v'
  160. <> help "Print the types of all declared/defined values"))
  161. <**> helper)
  162. (progDesc "Check a list of files")
  163. data Opts
  164. = Load { files :: [String], eval :: [String] }
  165. | Check { files :: [String], verbose :: Bool }
  166. | Repl
  167. deriving (Eq, Show, Ord)
  168. displayAndDie :: Exception e => Text -> e -> IO a
  169. displayAndDie lines e = do
  170. () <- throwIO e `catches` displayExceptions lines
  171. exitFailure
  172. displayExceptions :: Text -> [Handler ()]
  173. displayExceptions lines =
  174. [ Handler \(WhileChecking a b e) -> do
  175. T.putStrLn $ squiggleUnder a b lines
  176. displayExceptions' lines e
  177. , Handler \(SeeAlso a b e) -> do
  178. displayExceptions' lines e
  179. T.putStrLn $ squiggleUnder a b lines
  180. , Handler \(AttachedNote n e) -> do
  181. displayExceptions' lines e
  182. Lt.putStrLn $ "\x1b[1;32mnote\x1b[0m: " <> render n
  183. , Handler \(WhenCheckingEndpoint dir le ri ep e) -> do
  184. displayExceptions' lines e
  185. let
  186. endp = case ep of
  187. VI0 -> Lt.pack "left"
  188. VI1 -> Lt.pack "right"
  189. _ -> render . prettyTm $ quoteWith False mempty ep
  190. left = render (prettyTm (quoteWith False mempty le))
  191. right = render (prettyTm (quoteWith False mempty ri))
  192. Lt.putStrLn . Lt.unlines $
  193. [ "\n\x1b[1;32mnote\x1b[0m: This path was expected to fill the diagram <<"
  194. , "\t " <> redact left <> " " <> Lt.replicate 7 (Lt.singleton '\x2500') <> " " <> redact right
  195. , " >> in the direction " <> render (pretty dir) <> ", but the " <> endp <> " endpoint is incorrect"
  196. ]
  197. , Handler \(NotEqual ta tb) -> do
  198. Lt.putStrLn . render . vsep $
  199. [ "\x1b[1;31merror\x1b[0m: Mismatch between actual and expected types:"
  200. , indent 2 $ "* \x1b[1mActual\x1b[0m:" <> softline <> align (prettyVl (zonk ta))
  201. , indent 2 $ "* \x1b[1mExpected\x1b[0m:" <> softline <> align (prettyVl (zonk tb))
  202. ]
  203. , Handler \(NoSuchPrimitive x) -> do
  204. putStrLn $ "Unknown primitive: " ++ T.unpack x
  205. , Handler \(NotInScope x) -> do
  206. putStrLn $ "Variable not in scope: " ++ show (pretty x)
  207. , Handler \(Elab.DeclaredUndefined n) -> do
  208. putStrLn $ "Name declared but not defined: " ++ show (pretty n)
  209. , Handler \Elab.PathConPretype -> do
  210. putStrLn $ "Pretypes can not have path constructors, either change this definition so it lands in Type or remove it."
  211. ]
  212. redact :: Lt.Text -> Lt.Text
  213. redact x
  214. | length (Lt.lines x) >= 2 = head (Lt.lines x) <> Lt.pack "\x1b[1;32m[...]\x1b[0m"
  215. | otherwise = x
  216. reportUnsolved :: Foldable t => Maybe Text -> Map.Map MV (t (Seq Projection, Value)) -> IO ()
  217. reportUnsolved code metas = do
  218. for_ (Map.toList metas) \(m, p) -> do
  219. case mvSpan m of
  220. Just (f, s, e) ->
  221. case code of
  222. Just code -> T.putStrLn $ squiggleUnder s e code
  223. Nothing -> T.putStrLn . squiggleUnder s e =<< T.readFile (T.unpack f)
  224. Nothing -> pure ()
  225. case null p of
  226. True -> do
  227. Lt.putStrLn . render $ "Unsolved metavariable" <+> prettyTm (Meta m) <+> pretty ':' <+> align (prettyVl (zonk (mvType m)) <> pretty '.')
  228. _ -> do
  229. Lt.putStrLn . render $
  230. "Unsolved metavariable" <+> prettyTm (Meta m) <+> pretty ':' <+> align (prettyVl (zonk (mvType m)) <+> "should satisfy:")
  231. for_ p \p ->
  232. Lt.putStrLn . render $ indent 2 $ prettyTm (quote (zonk (VNe (HMeta m) (fst p)))) <+> pretty '≡' <+> prettyTm (quote (snd p))
  233. when (mvInteraction m && not (Map.null (mvContext m))) do
  234. putStrLn "Context (first 10 entries):"
  235. for_ (take 10 (Map.toList (mvContext m))) \(x, t) -> unless (isIdkT t) do
  236. Lt.putStrLn . render $ indent 2 $ pretty x <+> pretty ':' <+> prettyVl (zonk t)
  237. displayExceptions' :: Exception e => Text -> e -> IO ()
  238. displayExceptions' lines e = displayAndDie lines e `catch` \(_ :: ExitCode) -> pure ()
  239. squiggleUnder :: Posn -> Posn -> Text -> Text
  240. squiggleUnder (Posn l c) (Posn l' c') file
  241. | l == l' =
  242. let
  243. line = T.pack (show l) <> " | " <> T.lines file !! (l - 1)
  244. padding = T.replicate (length (show l)) (T.singleton ' ') <> " |"
  245. squiggle = T.replicate c " " <> T.pack "\x1b[1;31m" <> T.replicate (c' - c) "~" <> T.pack "\x1b[0m"
  246. in T.unlines [ padding, line, padding <> squiggle ]
  247. | otherwise = T.unlines (take (l' - l) (drop l (T.lines file)))
  248. dumpTokens :: Alex ()
  249. dumpTokens = do
  250. t <- alexMonadScan
  251. case tokenClass t of
  252. TokEof -> pure ()
  253. _ -> do
  254. traceDocM (viaShow t)
  255. dumpTokens