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.

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