mirror of
https://github.com/anoma/juvix.git
synced 2024-12-15 01:52:11 +03:00
830b3be304
This PR introduces FileExt type, and consequently, one can generalise methods and matches based on the file extension; for example, `parseInputJuvixAsmFile` is now an app. `parseInputFile FileExtJuvixAsm`
233 lines
6.6 KiB
Haskell
233 lines
6.6 KiB
Haskell
-- | Contains common options reused in several commands.
|
|
module CommonOptions
|
|
( module CommonOptions,
|
|
module Juvix.Prelude,
|
|
module Options.Applicative,
|
|
)
|
|
where
|
|
|
|
import Control.Exception qualified as GHC
|
|
import Data.List.NonEmpty qualified as NonEmpty
|
|
import Juvix.Compiler.Core.Data.TransformationId.Parser
|
|
import Juvix.Data.FileExt
|
|
import Juvix.Prelude
|
|
import Options.Applicative
|
|
import System.Process
|
|
import Text.Read (readMaybe)
|
|
import Prelude (show)
|
|
|
|
-- | Paths that are input are used to detect the root of the project.
|
|
data AppPath f = AppPath
|
|
{ _pathPath :: Prepath f,
|
|
_pathIsInput :: Bool
|
|
}
|
|
deriving stock (Data, Eq)
|
|
|
|
makeLenses ''AppPath
|
|
|
|
instance Show (AppPath f) where
|
|
show = Prelude.show . (^. pathPath)
|
|
|
|
parseInputFiles :: NonEmpty FileExt -> Parser (AppPath File)
|
|
parseInputFiles exts' = do
|
|
let exts = NonEmpty.toList exts'
|
|
mvars = intercalate "|" (map toMetavar exts)
|
|
dotExts = intercalate ", " (map Prelude.show exts)
|
|
helpMsg = "Path to a " <> dotExts <> " file"
|
|
completers = foldMap (completer . extCompleter) exts
|
|
_pathPath <-
|
|
argument
|
|
somePreFileOpt
|
|
( metavar mvars
|
|
<> help helpMsg
|
|
<> completers
|
|
<> action "file"
|
|
)
|
|
pure AppPath {_pathIsInput = True, ..}
|
|
|
|
parseInputFile :: FileExt -> Parser (AppPath File)
|
|
parseInputFile = parseInputFiles . NonEmpty.singleton
|
|
|
|
parseGenericInputFile :: Parser (AppPath File)
|
|
parseGenericInputFile = do
|
|
_pathPath <-
|
|
argument
|
|
somePreFileOpt
|
|
( metavar "INPUT_FILE"
|
|
<> help "Path to input file"
|
|
<> action "file"
|
|
)
|
|
pure AppPath {_pathIsInput = True, ..}
|
|
|
|
parseGenericOutputFile :: Parser (AppPath File)
|
|
parseGenericOutputFile = do
|
|
_pathPath <-
|
|
option
|
|
somePreFileOpt
|
|
( long "output"
|
|
<> short 'o'
|
|
<> metavar "OUTPUT_FILE"
|
|
<> help "Path to output file"
|
|
<> action "file"
|
|
)
|
|
pure AppPath {_pathIsInput = False, ..}
|
|
|
|
parseGenericOutputDir :: Mod OptionFields (Prepath Dir) -> Parser (AppPath Dir)
|
|
parseGenericOutputDir m = do
|
|
_pathPath <-
|
|
option
|
|
somePreDirOpt
|
|
( long "output-dir"
|
|
<> metavar "OUTPUT_DIR"
|
|
<> help "Path to output directory"
|
|
<> action "directory"
|
|
<> m
|
|
)
|
|
pure AppPath {_pathIsInput = False, ..}
|
|
|
|
somePreDirOpt :: ReadM (Prepath Dir)
|
|
somePreDirOpt = mkPrepath <$> str
|
|
|
|
somePreFileOpt :: ReadM (Prepath File)
|
|
somePreFileOpt = mkPrepath <$> str
|
|
|
|
someFileOpt :: ReadM (SomeBase File)
|
|
someFileOpt = eitherReader aux
|
|
where
|
|
aux :: String -> Either String (SomeBase File)
|
|
aux s = maybe (Left $ s <> " is not a file path") Right (parseSomeFile s)
|
|
|
|
someDirOpt :: ReadM (SomeBase Dir)
|
|
someDirOpt = eitherReader aux
|
|
where
|
|
aux :: String -> Either String (SomeBase Dir)
|
|
aux s = maybe (Left $ s <> " is not a directory path") Right (parseSomeDir s)
|
|
|
|
naturalNumberOpt :: ReadM Word
|
|
naturalNumberOpt = eitherReader aux
|
|
where
|
|
aux :: String -> Either String Word
|
|
aux s = maybe (Left $ s <> " is not a nonnegative number") Right (readMaybe s :: Maybe Word)
|
|
|
|
extCompleter :: FileExt -> Completer
|
|
extCompleter ext = mkCompleter $ \word -> do
|
|
let cmd = unwords ["compgen", "-o", "plusdirs", "-f", "-X", "!*" <> Prelude.show ext, "--", requote word]
|
|
result <- GHC.try @GHC.SomeException $ readProcess "bash" ["-c", cmd] ""
|
|
return . lines . fromRight [] $ result
|
|
|
|
requote :: String -> String
|
|
requote s =
|
|
let -- Bash doesn't appear to allow "mixed" escaping
|
|
-- in bash completions. So we don't have to really
|
|
-- worry about people swapping between strong and
|
|
-- weak quotes.
|
|
unescaped =
|
|
case s of
|
|
-- It's already strongly quoted, so we
|
|
-- can use it mostly as is, but we must
|
|
-- ensure it's closed off at the end and
|
|
-- there's no single quotes in the
|
|
-- middle which might confuse bash.
|
|
('\'' : rs) -> unescapeN rs
|
|
-- We're weakly quoted.
|
|
('"' : rs) -> unescapeD rs
|
|
-- We're not quoted at all.
|
|
-- We need to unescape some characters like
|
|
-- spaces and quotation marks.
|
|
elsewise -> unescapeU elsewise
|
|
in strong unescaped
|
|
where
|
|
strong :: String -> String
|
|
strong ss = '\'' : foldr go "'" ss
|
|
where
|
|
-- If there's a single quote inside the
|
|
-- command: exit from the strong quote and
|
|
-- emit it the quote escaped, then resume.
|
|
go '\'' t = "'\\''" ++ t
|
|
go h t = h : t
|
|
|
|
-- Unescape a strongly quoted string
|
|
-- We have two recursive functions, as we
|
|
-- can enter and exit the strong escaping.
|
|
unescapeN = goX
|
|
where
|
|
goX ('\'' : xs) = goN xs
|
|
goX (x : xs) = x : goX xs
|
|
goX [] = []
|
|
|
|
goN ('\\' : '\'' : xs) = '\'' : goN xs
|
|
goN ('\'' : xs) = goX xs
|
|
goN (x : xs) = x : goN xs
|
|
goN [] = []
|
|
|
|
-- Unescape an unquoted string
|
|
unescapeU = goX
|
|
where
|
|
goX [] = []
|
|
goX ('\\' : x : xs) = x : goX xs
|
|
goX (x : xs) = x : goX xs
|
|
|
|
-- Unescape a weakly quoted string
|
|
unescapeD = goX
|
|
where
|
|
-- Reached an escape character
|
|
goX ('\\' : x : xs)
|
|
-- If it's true escapable, strip the
|
|
-- slashes, as we're going to strong
|
|
-- escape instead.
|
|
| x `elem` ("$`\"\\\n" :: String) = x : goX xs
|
|
| otherwise = '\\' : x : goX xs
|
|
-- We've ended quoted section, so we
|
|
-- don't recurse on goX, it's done.
|
|
goX ('"' : xs) =
|
|
xs
|
|
-- Not done, but not a special character
|
|
-- just continue the fold.
|
|
goX (x : xs) =
|
|
x : goX xs
|
|
goX [] =
|
|
[]
|
|
|
|
optDeBruijn :: Parser Bool
|
|
optDeBruijn =
|
|
switch
|
|
( long "show-de-bruijn"
|
|
<> help "Show variable de Bruijn indices"
|
|
)
|
|
|
|
optIdentIds :: Parser Bool
|
|
optIdentIds =
|
|
switch
|
|
( long "show-ident-ids"
|
|
<> help "Show identifier IDs"
|
|
)
|
|
|
|
optArgsNum :: Parser Bool
|
|
optArgsNum =
|
|
switch
|
|
( long "show-args-num"
|
|
<> help "Show identifier arguments number"
|
|
)
|
|
|
|
optNoDisambiguate :: Parser Bool
|
|
optNoDisambiguate =
|
|
switch
|
|
( long "no-disambiguate"
|
|
<> help "Don't disambiguate the names of bound variables"
|
|
)
|
|
|
|
optTransformationIds :: Parser [TransformationId]
|
|
optTransformationIds =
|
|
option
|
|
(eitherReader parseTransf)
|
|
( long "transforms"
|
|
<> short 't'
|
|
<> value []
|
|
<> metavar "[Transform]"
|
|
<> completer (mkCompleter (return . completionsString))
|
|
<> help "hint: use autocomplete"
|
|
)
|
|
where
|
|
parseTransf :: String -> Either String [TransformationId]
|
|
parseTransf = mapLeft unpack . parseTransformations . pack
|