1
1
mirror of https://github.com/anoma/juvix.git synced 2025-01-07 08:08:44 +03:00
juvix/app/Main.hs

429 lines
14 KiB
Haskell
Raw Normal View History

2022-01-18 14:25:42 +03:00
{-# LANGUAGE ApplicativeDo #-}
2022-01-18 14:25:42 +03:00
module Main (main) where
--------------------------------------------------------------------------------
import Commands.Extra
import Commands.MicroJuvix
import Commands.MiniHaskell
import Commands.Termination as T
2022-04-05 20:57:21 +03:00
import Control.Exception qualified as IO
2022-01-21 11:50:37 +03:00
import Control.Monad.Extra
2022-04-07 13:53:05 +03:00
import MiniJuvix.Pipeline
import MiniJuvix.Prelude hiding (Doc)
2022-04-11 14:08:37 +03:00
import MiniJuvix.Prelude.Pretty hiding (Doc)
2022-04-11 14:23:55 +03:00
import MiniJuvix.Syntax.Abstract.Pretty qualified as Abstract
2022-04-05 20:57:21 +03:00
import MiniJuvix.Syntax.Concrete.Language qualified as M
2022-04-07 13:49:08 +03:00
import MiniJuvix.Syntax.Concrete.Parser qualified as Parser
import MiniJuvix.Syntax.Concrete.Scoped.Highlight qualified as Scoper
import MiniJuvix.Syntax.Concrete.Scoped.InfoTable qualified as Scoper
2022-04-11 14:08:37 +03:00
import MiniJuvix.Syntax.Concrete.Scoped.Pretty qualified as Scoper
import MiniJuvix.Syntax.Concrete.Scoped.Pretty.Html
2022-04-07 13:49:08 +03:00
import MiniJuvix.Syntax.Concrete.Scoped.Scoper qualified as Scoper
2022-04-11 14:08:37 +03:00
import MiniJuvix.Syntax.MicroJuvix.Pretty qualified as Micro
import MiniJuvix.Syntax.MicroJuvix.TypeChecker qualified as MicroTyped
import MiniJuvix.Syntax.MiniHaskell.Pretty qualified as MiniHaskell
2022-04-05 20:57:21 +03:00
import MiniJuvix.Termination qualified as T
import MiniJuvix.Termination.CallGraph qualified as A
import MiniJuvix.Translation.AbstractToMicroJuvix qualified as Micro
import MiniJuvix.Translation.MonoJuvixToMiniHaskell qualified as MiniHaskell
2022-04-07 13:49:08 +03:00
import MiniJuvix.Translation.ScopedToAbstract qualified as Abstract
import MiniJuvix.Utils.Version (runDisplayVersion)
2022-01-18 14:25:42 +03:00
import Options.Applicative
import Options.Applicative.Help.Pretty
import System.Console.ANSI qualified as Ansi
import System.IO qualified as IO
import Text.Show.Pretty hiding (Html)
--------------------------------------------------------------------------------
2022-01-18 14:25:42 +03:00
data GlobalOptions = GlobalOptions
{ _globalNoColors :: Bool,
_globalShowNameIds :: Bool
2022-04-08 17:36:48 +03:00
}
2022-01-21 11:50:37 +03:00
data Command
= Scope ScopeOptions
2022-01-18 14:25:42 +03:00
| Parse ParseOptions
| Html HtmlOptions
| Termination TerminationCommand
2022-03-15 20:01:28 +03:00
| MiniHaskell MiniHaskellOptions
| MicroJuvix MicroJuvixCommand
| DisplayVersion
| DisplayRoot
| Highlight HighlightOptions
2022-01-18 14:25:42 +03:00
2022-04-08 17:36:48 +03:00
data CLI = CLI
{ _cliGlobalOptions :: GlobalOptions,
_cliCommand :: Command
}
2022-01-21 11:50:37 +03:00
data ScopeOptions = ScopeOptions
2022-04-07 13:53:05 +03:00
{ _scopeInputFiles :: NonEmpty FilePath,
2022-04-08 17:36:48 +03:00
_scopeInlineImports :: Bool
2022-01-18 14:25:42 +03:00
}
2022-01-21 11:50:37 +03:00
data ParseOptions = ParseOptions
{ _parseInputFile :: FilePath,
_parseNoPrettyShow :: Bool
2022-01-20 14:50:01 +03:00
}
2022-04-04 19:03:56 +03:00
newtype HighlightOptions = HighlightOptions
{ _highlightInputFile :: FilePath
}
data HtmlOptions = HtmlOptions
{ _htmlInputFile :: FilePath,
_htmlRecursive :: Bool,
_htmlTheme :: Theme
}
makeLenses ''GlobalOptions
makeLenses ''CLI
2022-04-08 17:36:48 +03:00
parseGlobalOptions :: Parser GlobalOptions
parseGlobalOptions = do
_globalNoColors <-
switch
( long "no-colors"
<> help "Disable globally ANSI formatting "
)
_globalShowNameIds <-
switch
( long "show-name-ids"
<> help "Show the unique number of each identifier when pretty printing"
)
2022-04-08 17:36:48 +03:00
pure GlobalOptions {..}
parseCLI :: Parser CLI
parseCLI = do
_cliGlobalOptions <- parseGlobalOptions
_cliCommand <- parseCommand
pure CLI {..}
parseHtml :: Parser HtmlOptions
parseHtml = do
_htmlInputFile <- parseInputFile
_htmlRecursive <-
switch
( long "recursive"
<> help "export imported modules recursively"
)
_htmlTheme <-
option
(eitherReader parseTheme)
( long "theme"
<> metavar "THEME"
<> value Ayu
<> showDefault
<> help "selects a theme: ayu (light); nord (dark)"
)
pure HtmlOptions {..}
where
parseTheme :: String -> Either String Theme
parseTheme s = case s of
"nord" -> Right Nord
"ayu" -> Right Ayu
_ -> Left $ "unrecognised theme: " <> s
parseHighlight :: Parser HighlightOptions
parseHighlight = do
_highlightInputFile <- parseInputFile
pure HighlightOptions {..}
parseParse :: Parser ParseOptions
parseParse = do
_parseInputFile <- parseInputFile
2022-01-21 11:50:37 +03:00
_parseNoPrettyShow <-
switch
( long "no-pretty-show"
<> help "Disable formatting of the Haskell AST"
)
2022-01-20 14:50:01 +03:00
pure ParseOptions {..}
2022-01-18 14:25:42 +03:00
parseScope :: Parser ScopeOptions
parseScope = do
_scopeInputFiles <-
2022-04-07 10:43:17 +03:00
some1 $
argument
str
( metavar "MINIJUVIX_FILE(s)"
<> help "Path to one ore more MiniJuvix files"
<> action "file"
)
2022-04-07 13:49:08 +03:00
_scopeInlineImports <-
switch
2022-02-03 12:24:43 +03:00
( long "inline-imports"
<> help "Show the code of imported modules next to the import statement"
)
_scopeNoColors <-
switch
( long "no-colors"
<> help "Disable ANSI formatting"
)
2022-04-07 13:49:08 +03:00
pure ScopeOptions {..}
2022-01-18 14:25:42 +03:00
parseDisplayVersion :: Parser Command
2022-04-07 13:49:08 +03:00
parseDisplayVersion =
flag'
DisplayVersion
(long "version" <> short 'v' <> help "Print the version and exit")
parseDisplayRoot :: Parser Command
parseDisplayRoot =
flag'
DisplayRoot
(long "show-root" <> help "Print the detected root of the project")
2022-04-08 17:36:48 +03:00
descr :: ParserInfo CLI
2022-01-21 11:50:37 +03:00
descr =
info
2022-04-08 17:36:48 +03:00
(parseCLI <**> helper)
2022-01-21 11:50:37 +03:00
( fullDesc
2022-01-18 14:25:42 +03:00
<> progDesc "The MiniJuvix compiler."
2022-01-20 14:50:01 +03:00
<> headerDoc (Just headDoc)
2022-01-18 14:25:42 +03:00
<> footerDoc (Just foot)
2022-01-21 11:50:37 +03:00
)
2022-01-18 14:25:42 +03:00
where
2022-01-21 11:50:37 +03:00
headDoc :: Doc
headDoc = dullblue $ bold $ underline "MiniJuvix help"
2022-01-21 11:50:37 +03:00
foot :: Doc
foot = bold "maintainers: " <> "The MiniJuvix Team"
2022-01-18 14:25:42 +03:00
parseCommand :: Parser Command
2022-01-21 11:50:37 +03:00
parseCommand =
parseDisplayVersion
2022-04-05 20:57:21 +03:00
<|> parseDisplayRoot
<|> hsubparser
( mconcat
[ commandParse,
commandScope,
commandHtml,
commandTermination,
commandMicroJuvix,
commandMiniHaskell,
commandHighlight
]
)
2022-01-20 14:50:01 +03:00
where
commandMicroJuvix :: Mod CommandFields Command
commandMicroJuvix = command "microjuvix" minfo
where
minfo :: ParserInfo Command
minfo =
info
(MicroJuvix <$> parseMicroJuvixCommand)
(progDesc "Subcommands related to MicroJuvix")
2022-03-15 20:01:28 +03:00
commandMiniHaskell :: Mod CommandFields Command
commandMiniHaskell = command "minihaskell" minfo
where
minfo :: ParserInfo Command
minfo =
info
(MiniHaskell <$> parseMiniHaskell)
(progDesc "Translate a MiniJuvix file to MiniHaskell")
2022-03-15 20:01:28 +03:00
commandHighlight :: Mod CommandFields Command
commandHighlight = command "highlight" minfo
where
minfo :: ParserInfo Command
minfo =
info
(Highlight <$> parseHighlight)
(progDesc "Highlight a MiniJuvix file")
2022-01-21 11:50:37 +03:00
commandParse :: Mod CommandFields Command
commandParse = command "parse" minfo
where
minfo :: ParserInfo Command
minfo =
info
(Parse <$> parseParse)
(progDesc "Parse a MiniJuvix file")
2022-01-21 11:50:37 +03:00
commandHtml :: Mod CommandFields Command
commandHtml = command "html" minfo
where
minfo :: ParserInfo Command
minfo =
info
(Html <$> parseHtml)
(progDesc "Generate HTML for a MiniJuvix file")
2022-01-21 11:50:37 +03:00
commandScope :: Mod CommandFields Command
commandScope = command "scope" minfo
where
minfo :: ParserInfo Command
minfo =
info
(Scope <$> parseScope)
(progDesc "Parse and scope a MiniJuvix file")
commandTermination :: Mod CommandFields Command
commandTermination = command "termination" minfo
where
minfo :: ParserInfo Command
minfo =
info
(Termination <$> parseTerminationCommand)
(progDesc "Subcommands related to termination checking")
mkScopePrettyOptions :: GlobalOptions -> ScopeOptions -> Scoper.Options
mkScopePrettyOptions g ScopeOptions {..} =
Scoper.defaultOptions
{ Scoper._optShowNameId = g ^. globalShowNameIds,
Scoper._optInlineImports = _scopeInlineImports
2022-01-21 11:50:37 +03:00
}
2022-01-20 14:50:01 +03:00
parseModuleIO :: FilePath -> IO (M.Module 'M.Parsed 'M.ModuleTop)
2022-04-05 17:12:13 +03:00
parseModuleIO = fromRightIO id . Parser.runModuleParserIO
2022-01-20 14:50:01 +03:00
minijuvixYamlFile :: FilePath
minijuvixYamlFile = "minijuvix.yaml"
findRoot :: IO FilePath
findRoot = do
r <- IO.try go :: IO (Either IO.SomeException FilePath)
case r of
Left err -> do
putStrLn "Something went wrong when figuring out the root of the project."
putStrLn (pack (IO.displayException err))
2022-04-04 16:53:16 +03:00
cur <- getCurrentDirectory
putStrLn ("I will try to use the current directory: " <> pack cur)
return cur
Right root -> return root
where
2022-04-05 20:57:21 +03:00
possiblePaths :: FilePath -> [FilePath]
possiblePaths start = takeWhile (/= "/") (aux start)
where
aux f = f : aux (takeDirectory f)
go :: IO FilePath
go = do
c <- getCurrentDirectory
l <- findFile (possiblePaths c) minijuvixYamlFile
case l of
Nothing -> return c
Just yaml -> return (takeDirectory yaml)
2022-04-07 10:43:17 +03:00
class HasEntryPoint a where
getEntryPoint :: FilePath -> a -> EntryPoint
instance HasEntryPoint ScopeOptions where
getEntryPoint root = EntryPoint root . _scopeInputFiles
2022-04-07 13:53:05 +03:00
2022-04-07 10:43:17 +03:00
instance HasEntryPoint ParseOptions where
getEntryPoint root = EntryPoint root . pure . _parseInputFile
2022-04-07 13:53:05 +03:00
2022-04-07 10:43:17 +03:00
instance HasEntryPoint HighlightOptions where
getEntryPoint root = EntryPoint root . pure . _highlightInputFile
2022-04-07 13:53:05 +03:00
2022-04-07 10:43:17 +03:00
instance HasEntryPoint HtmlOptions where
getEntryPoint root = EntryPoint root . pure . _htmlInputFile
2022-04-07 13:53:05 +03:00
2022-04-07 10:43:17 +03:00
instance HasEntryPoint MicroJuvixOptions where
getEntryPoint root = EntryPoint root . pure . _mjuvixInputFile
2022-04-07 13:53:05 +03:00
2022-04-07 10:43:17 +03:00
instance HasEntryPoint MiniHaskellOptions where
getEntryPoint root = EntryPoint root . pure . _mhaskellInputFile
2022-04-07 13:53:05 +03:00
2022-04-07 10:43:17 +03:00
instance HasEntryPoint CallsOptions where
getEntryPoint root = EntryPoint root . pure . _callsInputFile
2022-04-07 13:53:05 +03:00
2022-04-07 10:43:17 +03:00
instance HasEntryPoint CallGraphOptions where
getEntryPoint root = EntryPoint root . pure . _graphInputFile
2022-04-08 17:36:48 +03:00
runCLI :: CLI -> IO ()
2022-04-11 14:08:37 +03:00
runCLI cli = do
supportsAnsiStdOut <- Ansi.hSupportsANSI IO.stdout
let globalOptions = cli ^. cliGlobalOptions
useColors = not (globalOptions ^. globalNoColors)
2022-04-11 14:08:37 +03:00
toAnsiText' :: forall a. (HasAnsiBackend a, HasTextBackend a) => a -> Text
toAnsiText' = toAnsiText useColors
-- Note: Probably a GHC bug specialises renderStdOut to the type
-- of the arugment for its first usage in the rest of the do block .
renderStdOut :: (HasAnsiBackend a, HasTextBackend a) => a -> IO ()
renderStdOut = renderIO (supportsAnsiStdOut && useColors)
renderStdOutAbs :: Abstract.PPOutput -> IO ()
renderStdOutAbs = renderStdOut
renderStdOutMini :: MiniHaskell.PPOutput -> IO ()
renderStdOutMini = renderStdOut
renderStdOutMicro :: Micro.PPOutput -> IO ()
renderStdOutMicro = renderStdOut
root <- findRoot
2022-04-11 14:08:37 +03:00
case cli ^. cliCommand of
DisplayVersion -> runDisplayVersion
DisplayRoot -> putStrLn (pack root)
2022-04-07 10:43:17 +03:00
Highlight o -> do
let entry :: EntryPoint
entry = getEntryPoint root o
res <- runIO (upToScoping entry)
let tbl = res ^. Scoper.resultParserTable
items = tbl ^. Parser.infoParsedItems
2022-04-07 13:49:08 +03:00
names = res ^. (Scoper.resultScoperTable . Scoper.infoNames)
putStrLn (Scoper.go items names)
Parse ParseOptions {..} -> do
m <- parseModuleIO _parseInputFile
if _parseNoPrettyShow then print m else pPrint m
Scope opts -> do
l <- (^. Scoper.resultModules) <$> runIO (upToScoping (getEntryPoint root opts))
forM_ l $ \s -> do
renderStdOut (Scoper.ppOut (mkScopePrettyOptions globalOptions opts) s)
2022-04-07 10:43:17 +03:00
Html o@HtmlOptions {..} -> do
res <- runIO (upToScoping (getEntryPoint root o))
let m = head (res ^. Scoper.resultModules)
2022-04-11 14:08:37 +03:00
genHtml Scoper.defaultOptions _htmlRecursive _htmlTheme m
2022-04-08 17:36:48 +03:00
MicroJuvix (Pretty opts) -> do
micro <- head . (^. Micro.resultModules) <$> runIO (upToMicroJuvix (getEntryPoint root opts))
let ppOpts =
Micro.defaultOptions
{ Micro._optShowNameId = globalOptions ^. globalShowNameIds
}
renderStdOutMicro (Micro.ppOut ppOpts micro)
2022-04-11 14:08:37 +03:00
MicroJuvix (TypeCheck opts) -> do
micro <- head . (^. MicroTyped.resultModules) <$> runIO (upToMicroJuvixTyped (getEntryPoint root opts))
case MicroTyped.checkModule micro of
Right _ -> putStrLn "Well done! It type checks"
Left err -> printErrorAnsiSafe err >> exitFailure
2022-04-07 10:43:17 +03:00
MiniHaskell o -> do
2022-04-11 14:08:37 +03:00
minihaskell <- head . (^. MiniHaskell.resultModules) <$> runIO (upToMiniHaskell (getEntryPoint root o))
renderStdOutMini (MiniHaskell.ppOutDefault minihaskell)
Termination (Calls opts@CallsOptions {..}) -> do
2022-04-07 10:43:17 +03:00
a <- head . (^. Abstract.resultModules) <$> runIO (upToAbstract (getEntryPoint root opts))
let callMap0 = T.buildCallMap a
callMap = case _callsFunctionNameFilter of
Nothing -> callMap0
Just f -> T.filterCallMap f callMap0
opts' = T.callsPrettyOptions opts
renderStdOutAbs (Abstract.ppOut opts' callMap)
putStrLn ""
2022-04-07 10:43:17 +03:00
Termination (CallGraph o@CallGraphOptions {..}) -> do
a <- head . (^. Abstract.resultModules) <$> runIO (upToAbstract (getEntryPoint root o))
2022-03-07 19:14:16 +03:00
let callMap = T.buildCallMap a
opts' =
Abstract.defaultOptions
{ Abstract._optShowNameId = globalOptions ^. globalShowNameIds
}
2022-03-04 01:31:45 +03:00
completeGraph = T.completeCallGraph callMap
2022-03-07 19:14:16 +03:00
filteredGraph = maybe completeGraph (`T.unsafeFilterGraph` completeGraph) _graphFunctionNameFilter
recBehav = map T.recursiveBehaviour (T.reflexiveEdges filteredGraph)
renderStdOutAbs (Abstract.ppOut opts' filteredGraph)
putStrLn ""
forM_ recBehav $ \r -> do
let sopts =
Scoper.defaultOptions
{ Scoper._optShowNameId = globalOptions ^. globalShowNameIds
}
n = toAnsiText' (Scoper.ppOut sopts (A._recBehaviourFunction r))
renderStdOutAbs (Abstract.ppOut opts' r)
putStrLn ""
case T.findOrder r of
Nothing -> putStrLn (n <> " Fails the termination checking")
2022-03-07 19:14:16 +03:00
Just (T.LexOrder k) -> putStrLn (n <> " Terminates with order " <> show (toList k))
putStrLn ""
2022-01-18 14:25:42 +03:00
main :: IO ()
2022-04-08 17:36:48 +03:00
main = execParser descr >>= runCLI