1
1
mirror of https://github.com/anoma/juvix.git synced 2024-07-14 19:30:34 +03:00

Support basic dependencies (#1622)

This commit is contained in:
janmasrovira 2022-12-20 13:05:40 +01:00 committed by GitHub
parent 445376e338
commit af63c36574
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
137 changed files with 2903 additions and 1892 deletions

View File

@ -13,18 +13,19 @@ data App m a where
ExitMsg :: ExitCode -> Text -> App m a
ExitJuvixError :: JuvixError -> App m a
PrintJuvixError :: JuvixError -> App m ()
AskRoot :: App m FilePath
AskInvokeDir :: App m (Path Abs Dir)
AskPkgDir :: App m (Path Abs Dir)
AskPackage :: App m Package
AskGlobalOptions :: App m GlobalOptions
RenderStdOut :: (HasAnsiBackend a, HasTextBackend a) => a -> App m ()
RunPipelineEither :: Path -> Sem PipelineEff a -> App m (Either JuvixError (BuiltinsState, a))
RunPipelineEither :: AppPath File -> Sem PipelineEff a -> App m (Either JuvixError (Artifacts, a))
Say :: Text -> App m ()
Raw :: ByteString -> App m ()
SayRaw :: ByteString -> App m ()
makeSem ''App
runAppIO :: forall r a. Member (Embed IO) r => GlobalOptions -> FilePath -> Package -> Sem (App ': r) a -> Sem r a
runAppIO g root pkg = interpret $ \case
runAppIO :: forall r a. Member (Embed IO) r => GlobalOptions -> Path Abs Dir -> Path Abs Dir -> Package -> Sem (App ': r) a -> Sem r a
runAppIO g invokeDir pkgDir pkg = interpret $ \case
RenderStdOut t
| g ^. globalOnlyErrors -> return ()
| otherwise -> embed $ do
@ -32,9 +33,10 @@ runAppIO g root pkg = interpret $ \case
renderIO (not (g ^. globalNoColors) && sup) t
AskGlobalOptions -> return g
AskPackage -> return pkg
AskRoot -> return root
AskInvokeDir -> return invokeDir
AskPkgDir -> return pkgDir
RunPipelineEither input p -> do
entry <- embed (getEntryPoint' g root pkg input)
entry <- embed (getEntryPoint' invokeDir g pkgDir pkg input)
embed (runIOEither iniState entry p)
Say t
| g ^. globalOnlyErrors -> return ()
@ -45,13 +47,13 @@ runAppIO g root pkg = interpret $ \case
printErr e
embed exitFailure
ExitMsg exitCode t -> embed (putStrLn t >> exitWith exitCode)
Raw b -> embed (ByteString.putStr b)
SayRaw b -> embed (ByteString.putStr b)
where
printErr e =
embed $ hPutStrLn stderr $ run $ runReader (project' @GenericOptions g) $ Error.render (not (g ^. globalNoColors)) (g ^. globalOnlyErrors) e
getEntryPoint' :: GlobalOptions -> FilePath -> Package -> Path -> IO EntryPoint
getEntryPoint' opts root pkg inputFile = do
getEntryPoint' :: Path Abs Dir -> GlobalOptions -> Path Abs Dir -> Package -> AppPath File -> IO EntryPoint
getEntryPoint' invokeDir opts root pkg inputFile = do
estdin <-
if
| opts ^. globalStdin -> Just <$> getContents
@ -59,27 +61,33 @@ getEntryPoint' opts root pkg inputFile = do
return
EntryPoint
{ _entryPointRoot = root,
_entryPointResolverRoot = root,
_entryPointNoTermination = opts ^. globalNoTermination,
_entryPointNoPositivity = opts ^. globalNoPositivity,
_entryPointNoStdlib = opts ^. globalNoStdlib,
_entryPointStdlibPath = opts ^. globalStdlibPath,
_entryPointPackage = pkg,
_entryPointModulePaths = pure (inputFile ^. pathPath),
_entryPointModulePaths = pure (someBaseToAbs invokeDir (inputFile ^. pathPath)),
_entryPointGenericOptions = project opts,
_entryPointStdin = estdin
}
someBaseToAbs' :: Members '[App] r => SomeBase a -> Sem r (Path Abs a)
someBaseToAbs' f = do
r <- askInvokeDir
return (someBaseToAbs r f)
askGenericOptions :: Members '[App] r => Sem r GenericOptions
askGenericOptions = project <$> askGlobalOptions
getEntryPoint :: Members '[Embed IO, App] r => Path -> Sem r EntryPoint
getEntryPoint :: Members '[Embed IO, App] r => AppPath File -> Sem r EntryPoint
getEntryPoint inputFile = do
opts <- askGlobalOptions
root <- askRoot
root <- askPkgDir
pkg <- askPackage
embed (getEntryPoint' opts root pkg inputFile)
invokeDir <- askInvokeDir
embed (getEntryPoint' invokeDir opts root pkg inputFile)
runPipeline :: Member App r => Path -> Sem PipelineEff a -> Sem r a
runPipeline :: Member App r => AppPath File -> Sem PipelineEff a -> Sem r a
runPipeline input p = do
r <- runPipelineEither input p
case r of

View File

@ -12,54 +12,37 @@ import System.Process qualified as P
runCommand :: Members '[Embed IO, App] r => CompileOptions -> Sem r ()
runCommand opts@CompileOptions {..} = do
root <- askRoot
miniC <- (^. MiniC.resultCCode) <$> runPipeline _compileInputFile upToMiniC
let inputFile = _compileInputFile ^. pathPath
result <- embed (runCompile root inputFile opts miniC)
inputFile <- someBaseToAbs' (_compileInputFile ^. pathPath)
result <- runCompile inputFile opts miniC
case result of
Left err -> printFailureExit err
_ -> return ()
inputCFile :: FilePath -> FilePath -> FilePath
inputCFile projRoot inputFileCompile =
projRoot </> juvixBuildDir </> outputMiniCFile
inputCFile :: Members '[App] r => Path Abs File -> Sem r (Path Abs File)
inputCFile inputFileCompile = do
root <- askPkgDir
return (root <//> juvixBuildDir <//> outputMiniCFile)
where
outputMiniCFile :: FilePath
outputMiniCFile = takeBaseName inputFileCompile <> ".c"
outputMiniCFile :: Path Rel File
outputMiniCFile = replaceExtension' ".c" (filename inputFileCompile)
runCompile :: FilePath -> FilePath -> CompileOptions -> Text -> IO (Either Text ())
runCompile projRoot inputFileCompile o minic = do
createDirectoryIfMissing True (projRoot </> juvixBuildDir)
TIO.writeFile (inputCFile projRoot inputFileCompile) minic
prepareRuntime projRoot o
runCompile :: Members '[Embed IO, App] r => Path Abs File -> CompileOptions -> Text -> Sem r (Either Text ())
runCompile inputFileCompile o minic = do
root <- askPkgDir
ensureDir (root <//> juvixBuildDir)
f <- inputCFile inputFileCompile
embed (TIO.writeFile (toFilePath f) minic)
prepareRuntime o
case o ^. compileTarget of
TargetWasm -> runM (runError (clangCompile projRoot inputFileCompile o))
TargetWasm -> runError (clangCompile inputFileCompile o)
TargetC -> return (Right ())
TargetNative -> runM (runError (clangNativeCompile projRoot inputFileCompile o))
TargetNative -> runError (clangNativeCompile inputFileCompile o)
prepareRuntime :: FilePath -> CompileOptions -> IO ()
prepareRuntime projRoot o = do
mapM_ writeRuntime runtimeProjectDir
prepareRuntime :: forall r. Members '[Embed IO, App] r => CompileOptions -> Sem r ()
prepareRuntime o = mapM_ writeRuntime runtimeProjectDir
where
wasiStandaloneRuntimeDir :: [(FilePath, BS.ByteString)]
wasiStandaloneRuntimeDir = $(FE.makeRelativeToProject "c-runtime/wasi-standalone" >>= FE.embedDir)
standaloneRuntimeDir :: [(FilePath, BS.ByteString)]
standaloneRuntimeDir = $(FE.makeRelativeToProject "c-runtime/standalone" >>= FE.embedDir)
wasiLibCRuntimeDir :: [(FilePath, BS.ByteString)]
wasiLibCRuntimeDir = $(FE.makeRelativeToProject "c-runtime/wasi-libc" >>= FE.embedDir)
builtinCRuntimeDir :: [(FilePath, BS.ByteString)]
builtinCRuntimeDir = $(FE.makeRelativeToProject "c-runtime/builtins" >>= FE.embedDir)
wallocDir :: [(FilePath, BS.ByteString)]
wallocDir = $(FE.makeRelativeToProject "c-runtime/walloc" >>= FE.embedDir)
libcRuntime :: [(FilePath, BS.ByteString)]
libcRuntime = wasiLibCRuntimeDir <> builtinCRuntimeDir
runtimeProjectDir :: [(FilePath, BS.ByteString)]
runtimeProjectDir :: [(Path Rel File, BS.ByteString)]
runtimeProjectDir = case o ^. compileTarget of
TargetNative -> libcRuntime
_ -> case o ^. compileRuntime of
@ -67,99 +50,135 @@ prepareRuntime projRoot o = do
RuntimeWasiLibC -> libcRuntime
RuntimeStandalone -> standaloneRuntimeDir <> builtinCRuntimeDir <> wallocDir
writeRuntime :: (FilePath, BS.ByteString) -> IO ()
writeRuntime (filePath, contents) =
BS.writeFile (projRoot </> juvixBuildDir </> takeFileName filePath) contents
writeRuntime :: (Path Rel File, BS.ByteString) -> Sem r ()
writeRuntime (filePath, contents) = do
root <- askPkgDir
embed (BS.writeFile (toFilePath (root <//> juvixBuildDir <//> filePath)) contents)
wasiStandaloneRuntimeDir :: [(Path Rel File, BS.ByteString)]
wasiStandaloneRuntimeDir = map (first relFile) $(FE.makeRelativeToProject "c-runtime/wasi-standalone" >>= FE.embedDir)
standaloneRuntimeDir :: [(Path Rel File, BS.ByteString)]
standaloneRuntimeDir = map (first relFile) $(FE.makeRelativeToProject "c-runtime/standalone" >>= FE.embedDir)
wasiLibCRuntimeDir :: [(Path Rel File, BS.ByteString)]
wasiLibCRuntimeDir = map (first relFile) $(FE.makeRelativeToProject "c-runtime/wasi-libc" >>= FE.embedDir)
builtinCRuntimeDir :: [(Path Rel File, BS.ByteString)]
builtinCRuntimeDir = map (first relFile) $(FE.makeRelativeToProject "c-runtime/builtins" >>= FE.embedDir)
wallocDir :: [(Path Rel File, BS.ByteString)]
wallocDir = map (first relFile) $(FE.makeRelativeToProject "c-runtime/walloc" >>= FE.embedDir)
libcRuntime :: [(Path Rel File, BS.ByteString)]
libcRuntime = wasiLibCRuntimeDir <> builtinCRuntimeDir
clangNativeCompile ::
forall r.
Members '[Embed IO, Error Text] r =>
FilePath ->
FilePath ->
Members '[Embed IO, App, Error Text] r =>
Path Abs File ->
CompileOptions ->
Sem r ()
clangNativeCompile projRoot inputFileCompile o = runClang (nativeArgs outputFile inputFile)
clangNativeCompile inputFileCompile o = do
inputFile <- getInputFile
outputFile <- getOutputFile
runClang (nativeArgs outputFile inputFile)
where
outputFile :: FilePath
outputFile = maybe (takeBaseName inputFileCompile) (^. pathPath) (o ^. compileOutputFile)
getOutputFile :: Sem r (Path Abs File)
getOutputFile = case o ^. compileOutputFile of
Nothing -> return (removeExtension' inputFileCompile)
Just f -> someBaseToAbs' (f ^. pathPath)
inputFile :: FilePath
inputFile = inputCFile projRoot inputFileCompile
getInputFile :: Sem r (Path Abs File)
getInputFile = inputCFile inputFileCompile
clangCompile ::
forall r.
Members '[Embed IO, Error Text] r =>
FilePath ->
FilePath ->
Members '[Embed IO, App, Error Text] r =>
Path Abs File ->
CompileOptions ->
Sem r ()
clangCompile projRoot inputFileCompile o = clangArgs >>= runClang
clangCompile inputFileCompile o = do
outputFile <- getOutputFile
inputFile <- getInputFile
let clangArgs :: Sem r [String]
clangArgs = case o ^. compileRuntime of
RuntimeStandalone -> do
standaloneLibArgs outputFile inputFile
RuntimeWasiStandalone -> wasiStandaloneArgs outputFile inputFile
RuntimeWasiLibC -> wasiLibcArgs outputFile inputFile
clangArgs >>= runClang
where
clangArgs :: Sem r [String]
clangArgs = case o ^. compileRuntime of
RuntimeStandalone ->
return (standaloneLibArgs projRoot outputFile inputFile)
RuntimeWasiStandalone -> wasiStandaloneArgs projRoot outputFile inputFile <$> sysrootEnvVar
RuntimeWasiLibC -> wasiLibcArgs outputFile inputFile <$> sysrootEnvVar
getOutputFile :: Sem r (Path Abs File)
getOutputFile = maybe (return defaultOutputFile) someBaseToAbs' (o ^? compileOutputFile . _Just . pathPath)
outputFile :: FilePath
outputFile = maybe (takeBaseName inputFileCompile <> ".wasm") (^. pathPath) (o ^. compileOutputFile)
defaultOutputFile :: Path Abs File
defaultOutputFile = replaceExtension' ".wasm" inputFileCompile
inputFile :: FilePath
inputFile = inputCFile projRoot inputFileCompile
getInputFile :: Sem r (Path Abs File)
getInputFile = inputCFile inputFileCompile
sysrootEnvVar :: Sem r String
sysrootEnvVar =
fromMaybeM (throw msg) (embed (lookupEnv "WASI_SYSROOT_PATH"))
where
msg :: Text
msg = "Missing environment variable WASI_SYSROOT_PATH"
sysrootEnvVar :: Members '[Error Text, Embed IO] r => Sem r (Path Abs Dir)
sysrootEnvVar =
absDir
<$> fromMaybeM (throw msg) (embed (lookupEnv "WASI_SYSROOT_PATH"))
where
msg :: Text
msg = "Missing environment variable WASI_SYSROOT_PATH"
commonArgs :: FilePath -> [String]
commonArgs :: Path Abs File -> [String]
commonArgs wasmOutputFile =
[ "-std=c99",
"-Oz",
"-I",
juvixBuildDir,
toFilePath juvixBuildDir,
"-o",
wasmOutputFile
toFilePath wasmOutputFile
]
standaloneLibArgs :: FilePath -> FilePath -> FilePath -> [String]
standaloneLibArgs projRoot wasmOutputFile inputFile =
commonArgs wasmOutputFile
<> [ "--target=wasm32",
"-nodefaultlibs",
"-nostartfiles",
"-Wl,--no-entry",
projRoot </> juvixBuildDir </> "walloc.c",
inputFile
]
standaloneLibArgs :: Members '[App, Embed IO] r => Path Abs File -> Path Abs File -> Sem r [String]
standaloneLibArgs wasmOutputFile inputFile = do
root <- askPkgDir
return $
commonArgs wasmOutputFile
<> [ "--target=wasm32",
"-nodefaultlibs",
"-nostartfiles",
"-Wl,--no-entry",
toFilePath (root <//> juvixBuildDir <//> $(mkRelFile "walloc.c")),
toFilePath inputFile
]
wasiStandaloneArgs :: FilePath -> FilePath -> FilePath -> FilePath -> [String]
wasiStandaloneArgs projRoot wasmOutputFile inputFile sysrootPath =
wasiCommonArgs sysrootPath wasmOutputFile
<> [ projRoot </> juvixBuildDir </> "walloc.c",
inputFile
]
wasiStandaloneArgs :: Members '[App, Error Text, Embed IO] r => Path Abs File -> Path Abs File -> Sem r [String]
wasiStandaloneArgs wasmOutputFile inputFile = do
root <- askPkgDir
com <- wasiCommonArgs wasmOutputFile
return $
com
<> [ toFilePath (root <//> juvixBuildDir <//> $(mkRelFile "walloc.c")),
toFilePath inputFile
]
wasiLibcArgs :: FilePath -> FilePath -> FilePath -> [String]
wasiLibcArgs wasmOutputFile inputFile sysrootPath =
wasiCommonArgs sysrootPath wasmOutputFile
<> ["-lc", inputFile]
wasiLibcArgs :: Members '[App, Error Text, Embed IO] r => Path Abs File -> Path Abs File -> Sem r [String]
wasiLibcArgs wasmOutputFile inputFile = do
com <- wasiCommonArgs wasmOutputFile
return $ com <> ["-lc", toFilePath inputFile]
nativeArgs :: FilePath -> FilePath -> [String]
nativeArgs :: Path Abs File -> Path Abs File -> [String]
nativeArgs outputFile inputFile =
commonArgs outputFile <> [inputFile]
commonArgs outputFile <> [toFilePath inputFile]
wasiCommonArgs :: FilePath -> FilePath -> [String]
wasiCommonArgs sysrootPath wasmOutputFile =
commonArgs wasmOutputFile
<> [ "-nodefaultlibs",
"--target=wasm32-wasi",
"--sysroot",
sysrootPath
]
wasiCommonArgs :: Members '[App, Error Text, Embed IO] r => Path Abs File -> Sem r [String]
wasiCommonArgs wasmOutputFile = do
sysrootPath <- sysrootEnvVar
return $
commonArgs wasmOutputFile
<> [ "-nodefaultlibs",
"--target=wasm32-wasi",
"--sysroot",
toFilePath sysrootPath
]
runClang ::
Members '[Embed IO, Error Text] r =>

View File

@ -2,7 +2,10 @@ module Commands.Compile.Options where
import CommonOptions
data CompileTarget = TargetC | TargetWasm | TargetNative
data CompileTarget
= TargetC
| TargetWasm
| TargetNative
deriving stock (Show, Data)
data CompileRuntime
@ -14,8 +17,8 @@ data CompileRuntime
data CompileOptions = CompileOptions
{ _compileTarget :: CompileTarget,
_compileRuntime :: CompileRuntime,
_compileOutputFile :: Maybe Path,
_compileInputFile :: Path
_compileOutputFile :: Maybe (AppPath File),
_compileInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -30,4 +30,4 @@ runCommand = \case
Core opts -> Core.runCommand opts
Asm opts -> Asm.runCommand opts
Runtime opts -> Runtime.runCommand opts
DisplayRoot -> DisplayRoot.runCommand
DisplayRoot opts -> DisplayRoot.runCommand opts

View File

@ -12,20 +12,21 @@ import Juvix.Extra.Paths
runCommand :: forall r. Members '[Embed IO, App] r => AsmCompileOptions -> Sem r ()
runCommand opts = do
s <- embed (readFile file)
case Asm.runParser file s of
file <- getFile
s <- embed (readFile (toFilePath file))
case Asm.runParser (toFilePath file) s of
Left err -> exitJuvixError (JuvixError err)
Right tab -> case run $ runError $ asmToMiniC asmOpts tab of
Left err -> exitJuvixError err
Right C.MiniCResult {..} -> do
root <- askRoot
embed $ createDirectoryIfMissing True (root </> juvixBuildDir)
let cFile = inputCFile root file
embed $ TIO.writeFile cFile _resultCCode
Compile.runCommand opts {_compileInputFile = Path cFile False}
root <- askPkgDir
ensureDir (root <//> juvixBuildDir)
cFile <- inputCFile file
embed $ TIO.writeFile (toFilePath cFile) _resultCCode
Compile.runCommand opts {_compileInputFile = AppPath (Abs cFile) False}
where
file :: FilePath
file = opts ^. compileInputFile . pathPath
getFile :: Sem r (Path Abs File)
getFile = someBaseToAbs' (opts ^. compileInputFile . pathPath)
asmOpts :: Asm.Options
asmOpts = Asm.makeOptions (asmTarget (opts ^. compileTarget)) (opts ^. compileDebug)
@ -36,9 +37,10 @@ runCommand opts = do
TargetNative64 -> Backend.TargetCNative64
TargetC -> Backend.TargetCWasm32Wasi
inputCFile :: FilePath -> FilePath -> FilePath
inputCFile projRoot inputFileCompile =
projRoot </> juvixBuildDir </> outputMiniCFile
inputCFile :: Members '[App] r => Path Abs File -> Sem r (Path Abs File)
inputCFile inputFileCompile = do
root <- askPkgDir
return (root <//> juvixBuildDir <//> outputMiniCFile)
where
outputMiniCFile :: FilePath
outputMiniCFile = takeBaseName inputFileCompile <> ".c"
outputMiniCFile :: Path Rel File
outputMiniCFile = replaceExtension' ".c" (filename inputFileCompile)

View File

@ -12,8 +12,9 @@ import Juvix.Compiler.Asm.Translation.FromSource qualified as Asm
runCommand :: forall r. Members '[Embed IO, App] r => AsmRunOptions -> Sem r ()
runCommand opts = do
s <- embed (readFile file)
case Asm.runParser file s of
afile :: Path Abs File <- someBaseToAbs' file
s <- embed (readFile (toFilePath afile))
case Asm.runParser (toFilePath afile) s of
Left err -> exitJuvixError (JuvixError err)
Right tab ->
let v = if opts ^. asmRunNoValidate then Nothing else Asm.validate' tab
@ -35,7 +36,7 @@ runCommand opts = do
Nothing ->
exitMsg (ExitFailure 1) "no 'main' function"
where
file :: FilePath
file :: SomeBase File
file = opts ^. asmRunInputFile . pathPath
doRun ::

View File

@ -4,7 +4,7 @@ import CommonOptions
data AsmRunOptions = AsmRunOptions
{ _asmRunNoValidate :: Bool,
_asmRunInputFile :: Path
_asmRunInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -8,8 +8,9 @@ import Juvix.Compiler.Asm.Translation.FromSource qualified as Asm
runCommand :: forall r. Members '[Embed IO, App] r => AsmValidateOptions -> Sem r ()
runCommand opts = do
s <- embed (readFile file)
case Asm.runParser file s of
afile :: Path Abs File <- someBaseToAbs' file
s <- embed (readFile (toFilePath afile))
case Asm.runParser (toFilePath afile) s of
Left err -> exitJuvixError (JuvixError err)
Right tab -> do
case Asm.validate' tab of
@ -23,5 +24,5 @@ runCommand opts = do
renderStdOut (Asm.ppOutDefault tab tab)
exitMsg ExitSuccess ""
where
file :: FilePath
file :: SomeBase File
file = opts ^. asmValidateInputFile . pathPath

View File

@ -3,7 +3,7 @@ module Commands.Dev.Asm.Validate.Options where
import CommonOptions
data AsmValidateOptions = AsmValidateOptions
{ _asmValidateInputFile :: Path,
{ _asmValidateInputFile :: AppPath File,
_asmValidateNoPrint :: Bool
}
deriving stock (Data)

View File

@ -8,11 +8,12 @@ import Juvix.Compiler.Core.Translation.FromSource qualified as Core
runCommand :: forall r. Members '[Embed IO, App] r => CoreEvalOptions -> Sem r ()
runCommand opts = do
s <- embed (readFile f)
case Core.runParser f Core.emptyInfoTable s of
f :: Path Abs File <- someBaseToAbs' b
s <- embed (readFile (toFilePath f))
case Core.runParser (toFilePath f) Core.emptyInfoTable s of
Left err -> exitJuvixError (JuvixError err)
Right (tab, Just node) -> do evalAndPrint opts tab node
Right (_, Nothing) -> return ()
where
f :: FilePath
f = opts ^. coreEvalInputFile . pathPath
b :: SomeBase File
b = opts ^. coreEvalInputFile . pathPath

View File

@ -6,7 +6,7 @@ import Juvix.Compiler.Core.Pretty.Options qualified as Core
data CoreEvalOptions = CoreEvalOptions
{ _coreEvalNoIO :: Bool,
_coreEvalInputFile :: Path,
_coreEvalInputFile :: AppPath File,
_coreEvalShowDeBruijn :: Bool
}
deriving stock (Data)

View File

@ -10,8 +10,9 @@ import Juvix.Compiler.Core.Translation.FromSource qualified as Core
runCommand :: forall r a. (Members '[Embed IO, App] r, CanonicalProjection a Eval.EvalOptions, CanonicalProjection a Core.Options, CanonicalProjection a CoreReadOptions) => a -> Sem r ()
runCommand opts = do
s' <- embed (readFile f)
(tab, mnode) <- getRight (mapLeft JuvixError (Core.runParser f Core.emptyInfoTable s'))
inputFile :: Path Abs File <- someBaseToAbs' sinputFile
s' <- embed . readFile . toFilePath $ inputFile
(tab, mnode) <- getRight (mapLeft JuvixError (Core.runParser (toFilePath inputFile) Core.emptyInfoTable s'))
let tab' = Core.applyTransformations (project opts ^. coreReadTransformations) tab
embed (Scoper.scopeTrace tab')
unless (project opts ^. coreReadNoPrint) $ do
@ -30,5 +31,5 @@ runCommand opts = do
embed (putStrLn "-- Node")
renderStdOut (Core.ppOut opts node)
embed (putStrLn "")
f :: FilePath
f = project opts ^. coreReadInputFile . pathPath
sinputFile :: SomeBase File
sinputFile = project opts ^. coreReadInputFile . pathPath

View File

@ -11,7 +11,7 @@ data CoreReadOptions = CoreReadOptions
_coreReadShowDeBruijn :: Bool,
_coreReadEval :: Bool,
_coreReadNoPrint :: Bool,
_coreReadInputFile :: Path
_coreReadInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -1,6 +1,15 @@
module Commands.Dev.DisplayRoot where
import Commands.Base
import Commands.Dev.DisplayRoot.Options
import Data.Yaml
runCommand :: Members '[Embed IO, App] r => Sem r ()
runCommand = askRoot >>= say . pack
runCommand :: forall r. Members '[Embed IO, App] r => RootOptions -> Sem r ()
runCommand RootOptions {..} = do
askPkgDir >>= say . pack . toFilePath
when _rootPrintPackage printPackage
where
printPackage :: Sem r ()
printPackage = do
say "+----------------------------+"
askPackage >>= say . decodeUtf8 . encode . rawPackage

View File

@ -0,0 +1,22 @@
module Commands.Dev.DisplayRoot.Options where
import CommonOptions
data RootOptions = RootOptions
{ _rootPrintPackage :: Bool,
_rootMainFile :: Maybe (AppPath File)
}
deriving stock (Data)
makeLenses ''RootOptions
parseRoot :: Parser RootOptions
parseRoot = do
_rootPrintPackage <-
switch
( long "print-package"
<> help "print the juvix.yaml file as parsed"
)
_rootMainFile <- optional parseInputJuvixFile
pure RootOptions {..}

View File

@ -9,8 +9,8 @@ import System.Process qualified as Process
runCommand :: Members '[Embed IO, App] r => DocOptions -> Sem r ()
runCommand DocOptions {..} = do
ctx <- runPipeline _docInputFile upToInternalTyped
let docDir = _docOutputDir ^. pathPath
docDir <- someBaseToAbs' (_docOutputDir ^. pathPath)
Doc.compile docDir "proj" ctx
when _docOpen $ case openCmd of
Nothing -> say "Could not recognize the 'open' command for your OS"
Just opencmd -> embed (void (Process.spawnProcess opencmd [docDir </> Doc.indexFileName]))
Just opencmd -> embed (void (Process.spawnProcess opencmd [toFilePath (docDir <//> Doc.indexFileName)]))

View File

@ -3,9 +3,9 @@ module Commands.Dev.Doc.Options where
import CommonOptions
data DocOptions = DocOptions
{ _docOutputDir :: Path,
{ _docOutputDir :: AppPath Dir,
_docOpen :: Bool,
_docInputFile :: Path
_docInputFile :: AppPath File
}
deriving stock (Data)
@ -15,7 +15,7 @@ parseDoc :: Parser DocOptions
parseDoc = do
_docOutputDir <-
parseGenericOutputDir
( value "doc"
( value (Rel (relDir "doc"))
<> showDefault
<> help "html output directory"
)

View File

@ -15,10 +15,10 @@ runCommand HighlightOptions {..} = do
genOpts <- askGenericOptions
say (Highlight.goError (run $ runReader genOpts $ errorIntervals err))
Right r -> do
inputFile <- someBaseToAbs' (_highlightInputFile ^. pathPath)
let tbl = r ^. _2 . Scoper.resultParserTable
items = tbl ^. Parser.infoParsedItems
names = r ^. _2 . (Scoper.resultScoperTable . Scoper.infoNames)
inputFile = _highlightInputFile ^. pathPath
hinput =
Highlight.filterInput
inputFile
@ -26,4 +26,4 @@ runCommand HighlightOptions {..} = do
{ _highlightNames = names,
_highlightParsed = items
}
raw (Highlight.go _highlightBackend hinput)
sayRaw (Highlight.go _highlightBackend hinput)

View File

@ -9,7 +9,7 @@ import Juvix.Compiler.Concrete.Data.Highlight
data HighlightOptions = HighlightOptions
{ _highlightBackend :: HighlightBackend,
_highlightInputFile :: Path
_highlightInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -3,7 +3,7 @@ module Commands.Dev.Internal.Arity.Options where
import CommonOptions
newtype InternalArityOptions = InternalArityOptions
{ _internalArityInputFile :: Path
{ _internalArityInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -9,7 +9,7 @@ data InternalCoreEvalOptions = InternalCoreEvalOptions
{ _internalCoreEvalTransformations :: [TransformationId],
_internalCoreEvalShowDeBruijn :: Bool,
_internalCoreEvalNoIO :: Bool,
_internalCoreEvalInputFile :: Path
_internalCoreEvalInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -3,7 +3,7 @@ module Commands.Dev.Internal.Pretty.Options where
import CommonOptions
newtype InternalPrettyOptions = InternalPrettyOptions
{ _internalPrettyInputFile :: Path
{ _internalPrettyInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -4,7 +4,7 @@ import CommonOptions
data InternalTypeOptions = InternalTypeOptions
{ _internalTypePrint :: Bool,
_internalTypeInputFile :: Path
_internalTypeInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -3,7 +3,7 @@ module Commands.Dev.MiniC.Options where
import CommonOptions
newtype MiniCOptions = MiniCOptions
{ _miniCInputFile :: Path
{ _miniCInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -8,11 +8,13 @@ module Commands.Dev.Options
module Commands.Dev.Scope.Options,
module Commands.Dev.Doc.Options,
module Commands.Dev.Termination.Options,
module Commands.Dev.DisplayRoot.Options,
)
where
import Commands.Dev.Asm.Options hiding (Compile)
import Commands.Dev.Core.Options
import Commands.Dev.DisplayRoot.Options
import Commands.Dev.Doc.Options
import Commands.Dev.Highlight.Options
import Commands.Dev.Internal.Options
@ -24,7 +26,7 @@ import Commands.Dev.Termination.Options
import CommonOptions
data DevCommand
= DisplayRoot
= DisplayRoot RootOptions
| Highlight HighlightOptions
| Internal InternalCommand
| Core CoreCommand
@ -122,7 +124,7 @@ commandShowRoot :: Mod CommandFields DevCommand
commandShowRoot =
command "root" $
info
(pure DisplayRoot)
(DisplayRoot <$> parseRoot)
(progDesc "Show the root path for a Juvix project")
commandTermination :: Mod CommandFields DevCommand

View File

@ -4,7 +4,7 @@ import CommonOptions
data ParseOptions = ParseOptions
{ _parseNoPrettyShow :: Bool,
_parseInputFile :: Path
_parseInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -6,7 +6,7 @@ import Juvix.Compiler.Concrete.Pretty qualified as Scoper
data ScopeOptions = ScopeOptions
{ _scopeInlineImports :: Bool,
_scopeInputFile :: Path
_scopeInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -5,7 +5,7 @@ import Data.Text qualified as Text
data CallGraphOptions = CallGraphOptions
{ _graphFunctionNameFilter :: Maybe (NonEmpty Text),
_graphInputFile :: Path
_graphInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -8,7 +8,7 @@ import Juvix.Compiler.Abstract.Pretty.Base qualified as Abstract
data CallsOptions = CallsOptions
{ _callsFunctionNameFilter :: Maybe (NonEmpty Text),
_callsShowDecreasingArgs :: Abstract.ShowDecrArgs,
_callsInputFile :: Path
_callsInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -68,7 +68,7 @@ type DoctorEff = '[Log, Embed IO]
checkCmdOnPath :: Members DoctorEff r => String -> [Text] -> Sem r ()
checkCmdOnPath cmd errMsg =
whenM (isNothing <$> embed (findExecutable cmd)) (mapM_ warning errMsg)
whenM (isNothing <$> findExecutable (relFile cmd)) (mapM_ warning errMsg)
checkClangTargetSupported :: Members DoctorEff r => String -> [Text] -> Sem r ()
checkClangTargetSupported target errMsg = do

View File

@ -10,27 +10,24 @@ import System.Process qualified as P
runCommand :: forall r. Members '[Embed IO, App] r => CompileOptions -> Sem r ()
runCommand opts = do
root <- askRoot
let inputFile = opts ^. compileInputFile . pathPath
result <- embed (runCompile root inputFile opts)
root <- askPkgDir
inputFile <- someBaseToAbs' (opts ^. compileInputFile . pathPath)
result <- runCompile root inputFile opts
case result of
Left err -> printFailureExit err
_ -> return ()
juvixIncludeDir :: FilePath
juvixIncludeDir = juvixBuildDir </> "include"
runCompile :: FilePath -> FilePath -> CompileOptions -> IO (Either Text ())
runCompile :: Members '[App, Embed IO] r => Path Abs Dir -> Path Abs File -> CompileOptions -> Sem r (Either Text ())
runCompile projRoot inputFile o = do
createDirectoryIfMissing True (projRoot </> juvixBuildDir)
createDirectoryIfMissing True (projRoot </> juvixIncludeDir)
ensureDir (projRoot <//> juvixBuildDir)
ensureDir (projRoot <//> juvixIncludeDir)
prepareRuntime projRoot o
case o ^. compileTarget of
TargetWasm32Wasi -> runM (runError (clangWasmWasiCompile inputFile o))
TargetNative64 -> runM (runError (clangNativeCompile inputFile o))
TargetWasm32Wasi -> runError (clangWasmWasiCompile inputFile o)
TargetNative64 -> runError (clangNativeCompile inputFile o)
TargetC -> return $ Right ()
prepareRuntime :: FilePath -> CompileOptions -> IO ()
prepareRuntime :: forall r. Members '[App, Embed IO] r => Path Abs Dir -> CompileOptions -> Sem r ()
prepareRuntime projRoot o = do
mapM_ writeHeader headersDir
case o ^. compileTarget of
@ -52,64 +49,74 @@ prepareRuntime projRoot o = do
nativeDebugRuntime :: BS.ByteString
nativeDebugRuntime = $(FE.makeRelativeToProject "runtime/_build.native64-debug/libjuvix.a" >>= FE.embedFile)
headersDir :: [(FilePath, BS.ByteString)]
headersDir = $(FE.makeRelativeToProject "runtime/include" >>= FE.embedDir)
writeRuntime :: BS.ByteString -> IO ()
writeRuntime :: BS.ByteString -> Sem r ()
writeRuntime =
BS.writeFile (projRoot </> juvixBuildDir </> "libjuvix.a")
embed
. BS.writeFile (toFilePath (projRoot <//> juvixBuildDir <//> $(mkRelFile "libjuvix.a")))
writeHeader :: (FilePath, BS.ByteString) -> IO ()
writeHeader (filePath, contents) = do
createDirectoryIfMissing True (projRoot </> juvixIncludeDir </> takeDirectory filePath)
BS.writeFile (projRoot </> juvixIncludeDir </> filePath) contents
headersDir :: [(Path Rel File, BS.ByteString)]
headersDir = map (first relFile) $(FE.makeRelativeToProject "runtime/include" >>= FE.embedDir)
writeHeader :: (Path Rel File, BS.ByteString) -> Sem r ()
writeHeader (filePath, contents) = embed $ do
ensureDir (projRoot <//> juvixIncludeDir <//> parent filePath)
BS.writeFile (toFilePath (projRoot <//> juvixIncludeDir <//> filePath)) contents
clangNativeCompile ::
forall r.
Members '[Embed IO, Error Text] r =>
FilePath ->
Members '[App, Embed IO, Error Text] r =>
Path Abs File ->
CompileOptions ->
Sem r ()
clangNativeCompile inputFile o =
runClang (native64Args o outputFile inputFile)
clangNativeCompile inputFile o = do
outputFile' <- outputFile
runClang (native64Args o outputFile' inputFile)
where
outputFile :: FilePath
outputFile = maybe defaultOutputFile (^. pathPath) (o ^. compileOutputFile)
outputFile :: Sem r (Path Abs File)
outputFile = maybe (return defaultOutputFile) someBaseToAbs' (o ^? compileOutputFile . _Just . pathPath)
defaultOutputFile :: FilePath
defaultOutputFile :: Path Abs File
defaultOutputFile
| o ^. compilePreprocess = takeBaseName inputFile <> ".out.c"
| o ^. compileAssembly = takeBaseName inputFile <> ".s"
| otherwise = takeBaseName inputFile
| o ^. compilePreprocess = replaceExtension' ".out.c" inputFile
| o ^. compileAssembly = replaceExtension' ".s" inputFile
| otherwise = removeExtension' inputFile
clangWasmWasiCompile ::
forall r.
Members '[Embed IO, Error Text] r =>
FilePath ->
Members '[App, Embed IO, Error Text] r =>
Path Abs File ->
CompileOptions ->
Sem r ()
clangWasmWasiCompile inputFile o = clangArgs >>= runClang
where
clangArgs :: Sem r [String]
clangArgs = wasiArgs o outputFile inputFile <$> sysrootEnvVar
clangArgs = do
outputFile' <- outputFile
wasiArgs o outputFile' inputFile <$> sysrootEnvVar
outputFile :: FilePath
outputFile = maybe defaultOutputFile (^. pathPath) (o ^. compileOutputFile)
outputFile :: Sem r (Path Abs File)
outputFile = case o ^? compileOutputFile . _Just . pathPath of
Just f -> someBaseToAbs' f
Nothing -> return defaultOutputFile
defaultOutputFile :: FilePath
defaultOutputFile
| o ^. compilePreprocess = takeBaseName inputFile <> ".out.c"
| o ^. compileAssembly = takeBaseName inputFile <> ".wat"
| otherwise = takeBaseName inputFile <> ".wasm"
defaultOutputFile :: Path Abs File
defaultOutputFile = replaceExtension' extension inputFile
where
extension :: String
extension
| o ^. compilePreprocess = ".out.c"
| o ^. compileAssembly = ".wat"
| otherwise = ".wasm"
sysrootEnvVar :: Sem r String
sysrootEnvVar :: Sem r (Path Abs Dir)
sysrootEnvVar =
fromMaybeM (throw msg) (embed (lookupEnv "WASI_SYSROOT_PATH"))
absDir
<$> fromMaybeM (throw msg) (embed (lookupEnv "WASI_SYSROOT_PATH"))
where
msg :: Text
msg = "Missing environment variable WASI_SYSROOT_PATH"
commonArgs :: CompileOptions -> FilePath -> [String]
commonArgs :: CompileOptions -> Path Abs File -> [String]
commonArgs o outputFile =
["-E" | o ^. compilePreprocess]
<> ["-S" | o ^. compileAssembly]
@ -121,26 +128,26 @@ commonArgs o outputFile =
"-Werror",
"-std=c11",
"-I",
juvixIncludeDir,
toFilePath juvixIncludeDir,
"-o",
outputFile
toFilePath outputFile
]
<> ( if
| not (o ^. compilePreprocess || o ^. compileAssembly) ->
[ "-L",
juvixBuildDir
toFilePath juvixBuildDir
]
| otherwise -> []
)
native64Args :: CompileOptions -> FilePath -> FilePath -> [String]
native64Args :: CompileOptions -> Path Abs File -> Path Abs File -> [String]
native64Args o outputFile inputFile =
commonArgs o outputFile
<> [ "-DARCH_NATIVE64",
"-DAPI_LIBC",
"-m64",
"-O3",
inputFile
toFilePath inputFile
]
<> ( if
| not (o ^. compilePreprocess || o ^. compileAssembly) ->
@ -148,7 +155,7 @@ native64Args o outputFile inputFile =
| otherwise -> []
)
wasiArgs :: CompileOptions -> FilePath -> FilePath -> FilePath -> [String]
wasiArgs :: CompileOptions -> Path Abs File -> Path Abs File -> Path Abs Dir -> [String]
wasiArgs o outputFile inputFile sysrootPath =
commonArgs o outputFile
<> [ "-DARCH_WASM32",
@ -157,8 +164,8 @@ wasiArgs o outputFile inputFile sysrootPath =
"-nodefaultlibs",
"--target=wasm32-wasi",
"--sysroot",
sysrootPath,
inputFile
toFilePath sysrootPath,
toFilePath inputFile
]
<> ( if
| not (o ^. compilePreprocess || o ^. compileAssembly) ->

View File

@ -2,16 +2,19 @@ module Commands.Extra.Compile.Options where
import CommonOptions
data CompileTarget = TargetWasm32Wasi | TargetNative64 | TargetC
data CompileTarget
= TargetWasm32Wasi
| TargetNative64
| TargetC
deriving stock (Show, Data)
data CompileOptions = CompileOptions
{ _compileDebug :: Bool,
_compilePreprocess :: Bool,
_compileAssembly :: Bool,
_compileOutputFile :: Maybe Path,
_compileOutputFile :: Maybe (AppPath File),
_compileTarget :: CompileTarget,
_compileInputFile :: Path
_compileInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -10,4 +10,5 @@ runCommand :: Members '[Embed IO, App] r => HtmlOptions -> Sem r ()
runCommand HtmlOptions {..} = do
res <- runPipeline _htmlInputFile upToScoping
let m = head (res ^. Scoper.resultModules)
embed (Html.genHtml Concrete.defaultOptions _htmlRecursive _htmlTheme (_htmlOutputDir ^. pathPath) _htmlPrintMetadata m)
outDir <- someBaseToAbs' (_htmlOutputDir ^. pathPath)
embed (Html.genHtml Concrete.defaultOptions _htmlRecursive _htmlTheme outDir _htmlPrintMetadata m)

View File

@ -6,8 +6,8 @@ import Juvix.Compiler.Backend.Html.Data.Theme
data HtmlOptions = HtmlOptions
{ _htmlRecursive :: Bool,
_htmlTheme :: Theme,
_htmlOutputDir :: Path,
_htmlInputFile :: Path,
_htmlOutputDir :: AppPath Dir,
_htmlInputFile :: AppPath File,
_htmlPrintMetadata :: Bool
}
deriving stock (Data)
@ -33,7 +33,7 @@ parseHtml = do
)
_htmlOutputDir <-
parseGenericOutputDir
( value "html"
( value (Rel $(mkRelDir "html"))
<> showDefault
<> help "html output directory"
<> action "directory"

View File

@ -26,13 +26,13 @@ init = do
say "✨ Your next Juvix adventure is about to begin! ✨"
say "I will help you set it up"
pkg <- getPackage
say ("creating " <> pack juvixYamlFile)
embed (encodeFile juvixYamlFile pkg)
say ("creating " <> pack (toFilePath juvixYamlFile))
embed (encodeFile (toFilePath juvixYamlFile) (rawPackage pkg))
say "you are all set"
checkNotInProject :: forall r. Members '[Embed IO] r => Sem r ()
checkNotInProject =
whenM (embed (doesFileExist juvixYamlFile)) err
whenM (doesFileExist juvixYamlFile) err
where
err :: Sem r ()
err = do
@ -43,11 +43,12 @@ getPackage :: forall r. Members '[Embed IO] r => Sem r Package
getPackage = do
tproj <- getProjName
say "Tell me the version of your project [leave empty for 0.0.0]"
tversion <- getVersion
tversion :: SemVer <- getVersion
return
Package
{ _packageName = Just tproj,
_packageVersion = Just (prettySemVer tversion)
{ _packageName = tproj,
_packageVersion = Ideal tversion,
_packageDependencies = mempty
}
getProjName :: forall r. Members '[Embed IO] r => Sem r Text
@ -66,7 +67,7 @@ getProjName = do
where
getDefault :: Sem r (Maybe Text)
getDefault = runFail $ do
dir <- map toLower <$> (embed getCurrentDirectory >>= Fail.last . splitDirectories)
dir <- map toLower . dropTrailingPathSeparator . toFilePath . dirname <$> getCurrentDir
Fail.fromRight (parse projectNameParser (pack dir))
readName :: Maybe Text -> Sem r Text
readName def = go

View File

@ -5,7 +5,6 @@ module Commands.Repl where
import Commands.Base hiding (command)
import Commands.Repl.Options
import Control.Exception (throwIO)
import Control.Monad.IO.Class
import Control.Monad.State.Strict qualified as State
import Data.String.Interpolate (i, __i)
import Evaluator
@ -23,7 +22,6 @@ import Juvix.Data.Error.GenericError qualified as Error
import Juvix.Extra.Paths
import Juvix.Extra.Version
import Juvix.Prelude.Pretty qualified as P
import Root
import System.Console.ANSI qualified as Ansi
import System.Console.Haskeline
import System.Console.Repline
@ -41,10 +39,10 @@ data ReplContext = ReplContext
}
data ReplState = ReplState
{ _replStateReplRoot :: FilePath,
{ _replStatePkgDir :: Path Abs Dir,
_replStateInvokeDir :: Path Abs Dir,
_replStateContext :: Maybe ReplContext,
_replStateGlobalOptions :: GlobalOptions,
_replStateMkEntryPoint :: FilePath -> Repl EntryPoint
_replStateGlobalOptions :: GlobalOptions
}
makeLenses ''ReplState
@ -77,7 +75,26 @@ welcomeMsg = liftIO (putStrLn [i|Juvix REPL version #{versionTag}: https://juvix
runCommand :: Members '[Embed IO, App] r => ReplOptions -> Sem r ()
runCommand opts = do
let printHelpTxt :: String -> Repl ()
root <- askPkgDir
package <- askPackage
let getReplEntryPoint :: SomeBase File -> Repl EntryPoint
getReplEntryPoint inputFile = do
gopts <- State.gets (^. replStateGlobalOptions)
absInputFile :: Path Abs File <- replMakeAbsolute inputFile
return $
EntryPoint
{ _entryPointRoot = root,
_entryPointResolverRoot = root,
_entryPointNoTermination = gopts ^. globalNoTermination,
_entryPointNoPositivity = gopts ^. globalNoPositivity,
_entryPointNoStdlib = gopts ^. globalNoStdlib,
_entryPointPackage = package,
_entryPointModulePaths = pure absInputFile,
_entryPointGenericOptions = project gopts,
_entryPointStdin = Nothing
}
printHelpTxt :: String -> Repl ()
printHelpTxt _ = helpTxt
multilineCmd :: String
@ -88,21 +105,21 @@ runCommand opts = do
loadEntryPoint :: EntryPoint -> Repl ()
loadEntryPoint ep = do
(bs, res) <- liftIO (runIO' iniState ep upToCore)
(artif, res) <- liftIO (runIO' iniState ep upToCore)
State.modify
( set
replStateContext
( Just
( ReplContext
{ _replContextBuiltins = bs,
{ _replContextBuiltins = artif ^. artifactBuiltins,
_replContextExpContext = expressionContext res,
_replContextEntryPoint = ep
}
)
)
)
let epPath :: FilePath = ep ^. entryPointModulePaths . _head1
liftIO (putStrLn [i|OK loaded: #{epPath}|])
let epPath :: Path Abs File = ep ^. entryPointModulePaths . _head1
liftIO (putStrLn [i|OK loaded: #{toFilePath epPath}|])
reloadFile :: String -> Repl ()
reloadFile _ = do
@ -112,29 +129,24 @@ runCommand opts = do
loadEntryPoint entryPoint
Nothing -> noFileLoadedMsg
loadFile :: String -> Repl ()
loadFile args = do
mkEntryPoint <- State.gets (^. replStateMkEntryPoint)
let f = unpack (strip (pack args))
entryPoint <- mkEntryPoint f
pSomeFile :: String -> SomeBase File
pSomeFile = someFile . unpack . strip . pack
loadFile :: SomeBase File -> Repl ()
loadFile f = do
entryPoint <- getReplEntryPoint f
loadEntryPoint entryPoint
loadPrelude :: Repl ()
loadPrelude = do
mStdlibPath <- State.gets (^. replStateGlobalOptions . globalStdlibPath)
case mStdlibPath of
Nothing -> loadDefaultPrelude
Just stdlibDir' -> do
absStdlibDir <- liftIO (makeAbsolute stdlibDir')
loadFile (absStdlibDir </> preludePath)
loadPrelude = loadDefaultPrelude
loadDefaultPrelude :: Repl ()
loadDefaultPrelude = defaultPreludeEntryPoint >>= loadEntryPoint
printRoot :: String -> Repl ()
printRoot _ = do
r <- State.gets (^. replStateReplRoot)
liftIO $ putStrLn (pack r)
r <- State.gets (^. replStatePkgDir)
liftIO $ putStrLn (pack (toFilePath r))
displayVersion :: String -> Repl ()
displayVersion _ = liftIO (putStrLn versionTag)
@ -202,7 +214,7 @@ runCommand opts = do
-- `repline`'s `multilineCommand` logic overrides this no-op.
(multilineCmd, Repline.dontCrash . \_ -> return ()),
("quit", quit),
("load", Repline.dontCrash . loadFile),
("load", Repline.dontCrash . loadFile . pSomeFile),
("reload", Repline.dontCrash . reloadFile),
("prelude", Repline.dontCrash . const loadPrelude),
("root", printRoot),
@ -249,18 +261,30 @@ runCommand opts = do
tabComplete = Prefix (wordCompleter optsCompleter) defaultMatcher
replAction :: ReplS ()
replAction = evalReplOpts ReplOpts {..}
replAction = do
evalReplOpts
ReplOpts
{ prefix,
multilineCommand,
initialiser,
finaliser,
tabComplete,
command,
options,
banner
}
root <- askRoot
pkgDir <- askPkgDir
invokeDir <- askInvokeDir
globalOptions <- askGlobalOptions
embed
( State.evalStateT
replAction
( ReplState
{ _replStateReplRoot = root,
{ _replStatePkgDir = pkgDir,
_replStateInvokeDir = invokeDir,
_replStateContext = Nothing,
_replStateGlobalOptions = globalOptions,
_replStateMkEntryPoint = getReplEntryPoint
_replStateGlobalOptions = globalOptions
}
)
)
@ -268,38 +292,26 @@ runCommand opts = do
defaultPreludeEntryPoint :: Repl EntryPoint
defaultPreludeEntryPoint = do
opts <- State.gets (^. replStateGlobalOptions)
root <- State.gets (^. replStateReplRoot)
root <- State.gets (^. replStatePkgDir)
return $
EntryPoint
{ _entryPointRoot = root,
_entryPointResolverRoot = defaultStdlibPath root,
_entryPointNoTermination = opts ^. globalNoTermination,
_entryPointNoPositivity = opts ^. globalNoPositivity,
_entryPointNoStdlib = opts ^. globalNoStdlib,
_entryPointStdlibPath = opts ^. globalStdlibPath,
_entryPointPackage = emptyPackage,
_entryPointModulePaths = pure (defaultStdlibPath root </> preludePath),
_entryPointPackage = defaultPackage root,
_entryPointModulePaths = pure (defaultStdlibPath root <//> preludePath),
_entryPointGenericOptions = project opts,
_entryPointStdin = Nothing
}
getReplEntryPoint :: FilePath -> Repl EntryPoint
getReplEntryPoint inputFile = do
opts <- State.gets (^. replStateGlobalOptions)
absInputFile <- liftIO (makeAbsolute inputFile)
absStdlibPath <- liftIO (mapM makeAbsolute (opts ^. globalStdlibPath))
(root, package) <- liftIO (findRoot (Just absInputFile))
return $
EntryPoint
{ _entryPointRoot = root,
_entryPointNoTermination = opts ^. globalNoTermination,
_entryPointNoPositivity = opts ^. globalNoPositivity,
_entryPointNoStdlib = opts ^. globalNoStdlib,
_entryPointStdlibPath = absStdlibPath,
_entryPointPackage = package,
_entryPointModulePaths = pure absInputFile,
_entryPointGenericOptions = project opts,
_entryPointStdin = Nothing
}
replMakeAbsolute :: SomeBase b -> Repl (Path Abs b)
replMakeAbsolute = \case
Abs p -> return p
Rel r -> do
invokeDir <- State.gets (^. replStateInvokeDir)
return (invokeDir <//> r)
inferExpressionIO' :: ReplContext -> Text -> IO (Either JuvixError Internal.Expression)
inferExpressionIO' ctx = inferExpressionIO "" (ctx ^. replContextExpContext) (ctx ^. replContextBuiltins)

View File

@ -3,7 +3,7 @@ module Commands.Repl.Options where
import CommonOptions
data ReplOptions = ReplOptions
{ _replInputFile :: Maybe Path,
{ _replInputFile :: Maybe (AppPath File),
_replNoPrelude :: Bool
}
deriving stock (Data)

View File

@ -4,7 +4,7 @@ import Commands.Dev.Internal.Typecheck.Options qualified as Internal
import CommonOptions
newtype TypecheckOptions = TypecheckOptions
{ _typecheckInputFile :: Path
{ _typecheckInputFile :: AppPath File
}
deriving stock (Data)

View File

@ -14,86 +14,98 @@ import System.Process
import Prelude (show)
-- | Paths that are input are used to detect the root of the project.
data Path = Path
{ _pathPath :: FilePath,
data AppPath f = AppPath
{ _pathPath :: SomeBase f,
_pathIsInput :: Bool
}
deriving stock (Data)
makeLenses ''Path
makeLenses ''AppPath
instance Show Path where
show = (^. pathPath)
instance Show (AppPath f) where
show = Prelude.show . (^. pathPath)
parseInputJuvixFile :: Parser Path
parseInputJuvixFile :: Parser (AppPath File)
parseInputJuvixFile = do
_pathPath <-
argument
str
someFileOpt
( metavar "JUVIX_FILE"
<> help "Path to a .juvix file"
<> completer juvixCompleter
)
pure Path {_pathIsInput = True, ..}
pure AppPath {_pathIsInput = True, ..}
parseInputJuvixCoreFile :: Parser Path
parseInputJuvixCoreFile :: Parser (AppPath File)
parseInputJuvixCoreFile = do
_pathPath <-
argument
str
someFileOpt
( metavar "JUVIX_CORE_FILE"
<> help "Path to a .jvc file"
<> completer juvixCoreCompleter
)
pure Path {_pathIsInput = True, ..}
pure AppPath {_pathIsInput = True, ..}
parseInputJuvixAsmFile :: Parser Path
parseInputJuvixAsmFile :: Parser (AppPath File)
parseInputJuvixAsmFile = do
_pathPath <-
argument
str
someFileOpt
( metavar "JUVIX_ASM_FILE"
<> help "Path to a .jva file"
<> completer juvixAsmCompleter
)
pure Path {_pathIsInput = True, ..}
pure AppPath {_pathIsInput = True, ..}
parseInputCFile :: Parser Path
parseInputCFile :: Parser (AppPath File)
parseInputCFile = do
_pathPath <-
argument
str
someFileOpt
( metavar "C_FILE"
<> help "Path to a .c file"
<> completer juvixCCompleter
)
pure Path {_pathIsInput = True, ..}
pure AppPath {_pathIsInput = True, ..}
parseGenericOutputFile :: Parser Path
parseGenericOutputFile :: Parser (AppPath File)
parseGenericOutputFile = do
_pathPath <-
option
str
someFileOpt
( long "output"
<> short 'o'
<> metavar "OUTPUT_FILE"
<> help "Path to output file"
<> action "file"
)
pure Path {_pathIsInput = False, ..}
pure AppPath {_pathIsInput = False, ..}
parseGenericOutputDir :: Mod OptionFields FilePath -> Parser Path
parseGenericOutputDir :: Mod OptionFields (SomeBase Dir) -> Parser (AppPath Dir)
parseGenericOutputDir m = do
_pathPath <-
option
str
someDirOpt
( long "output-dir"
<> metavar "OUTPUT_DIR"
<> help "Path to output directory"
<> action "directory"
<> m
)
pure Path {_pathIsInput = False, ..}
pure AppPath {_pathIsInput = False, ..}
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)
extCompleter :: String -> Completer
extCompleter ext = mkCompleter $ \word -> do
@ -186,7 +198,7 @@ requote s =
goX [] =
[]
class HasPaths a where
class HasAppPaths a where
paths :: Traversal' a FilePath
optDeBruijn :: Parser Bool

View File

@ -13,7 +13,7 @@ import Juvix.Compiler.Core.Pretty qualified as Core
import Text.Megaparsec.Pos qualified as M
data EvalOptions = EvalOptions
{ _evalInputFile :: Path,
{ _evalInputFile :: AppPath File,
_evalNoIO :: Bool
}
@ -58,6 +58,6 @@ evalAndPrint opts tab node = do
embed (putStrLn "")
where
defaultLoc :: Interval
defaultLoc = singletonInterval (mkLoc 0 (M.initialPos f))
f :: FilePath
defaultLoc = singletonInterval (mkLoc 0 (M.initialPos (fromSomeFile f)))
f :: SomeBase File
f = project opts ^. evalInputFile . pathPath

View File

@ -3,11 +3,10 @@ module GlobalOptions
)
where
import CommonOptions
import Juvix.Compiler.Abstract.Pretty.Options qualified as Abstract
import Juvix.Compiler.Internal.Pretty.Options qualified as Internal
import Juvix.Data.Error.GenericError qualified as E
import Juvix.Prelude
import Options.Applicative hiding (hidden)
data GlobalOptions = GlobalOptions
{ _globalNoColors :: Bool,
@ -17,10 +16,9 @@ data GlobalOptions = GlobalOptions
_globalStdin :: Bool,
_globalNoTermination :: Bool,
_globalNoPositivity :: Bool,
_globalNoStdlib :: Bool,
_globalStdlibPath :: Maybe FilePath
_globalNoStdlib :: Bool
}
deriving stock (Eq, Show, Data)
deriving stock (Eq, Show)
makeLenses ''GlobalOptions
@ -53,8 +51,7 @@ defaultGlobalOptions =
_globalNoTermination = False,
_globalStdin = False,
_globalNoPositivity = False,
_globalNoStdlib = False,
_globalStdlibPath = Nothing
_globalNoStdlib = False
}
-- | Get a parser for global flags which can be hidden or not depending on
@ -101,14 +98,4 @@ parseGlobalFlags = do
( long "no-stdlib"
<> help "Do not use the standard library"
)
_globalStdlibPath <-
optional
( strOption
( long "stdlib-path"
<> metavar "PATH"
<> help "Specify path to the standard library"
<> action "directory"
)
)
return GlobalOptions {..}

View File

@ -2,7 +2,6 @@ module Main (main) where
import App
import CommonOptions
import Juvix.Compiler.Pipeline
import Root
import TopCommand
import TopCommand.Options
@ -10,9 +9,7 @@ import TopCommand.Options
main :: IO ()
main = do
let p = prefs showHelpOnEmpty
(global, cli) <- customExecParser p descr >>= secondM makeAbsPaths
(root, pkg) <- findRoot' cli
runM (runAppIO global root pkg (runTopCommand cli))
where
findRoot' :: TopCommand -> IO (FilePath, Package)
findRoot' cli = findRoot (topCommandInputFile cli)
cwd <- getCurrentDir
(global, cli) <- customExecParser p descr
(root, pkg) <- findRootAndChangeDir (topCommandInputFile cli)
runM (runAppIO global cwd root pkg (runTopCommand cli))

View File

@ -2,14 +2,15 @@ module Root where
import Control.Exception qualified as IO
import Data.ByteString qualified as ByteString
import Data.Yaml
import Juvix.Compiler.Pipeline
import Juvix.Extra.Paths qualified as Paths
import Juvix.Prelude
findRoot :: Maybe FilePath -> IO (FilePath, Package)
findRoot minputFile = do
whenJust (takeDirectory <$> minputFile) setCurrentDirectory
findRootAndChangeDir :: Maybe (SomeBase File) -> IO (Path Abs Dir, Package)
findRootAndChangeDir minputFile = do
whenJust minputFile $ \case
Abs d -> setCurrentDir (parent d)
Rel d -> setCurrentDir (parent d)
r <- IO.try go
case r of
Left (err :: IO.SomeException) -> do
@ -18,22 +19,21 @@ findRoot minputFile = do
exitFailure
Right root -> return root
where
possiblePaths :: FilePath -> [FilePath]
possiblePaths start = takeWhile (/= "/") (aux start)
where
aux f = f : aux (takeDirectory f)
possiblePaths :: Path Abs Dir -> [Path Abs Dir]
possiblePaths p = p : toList (parents p)
go :: IO (FilePath, Package)
go :: IO (Path Abs Dir, Package)
go = do
c <- getCurrentDirectory
l <- findFile (possiblePaths c) Paths.juvixYamlFile
cwd <- getCurrentDir
l <- findFile (possiblePaths cwd) Paths.juvixYamlFile
case l of
Nothing -> return (c, emptyPackage)
Just yaml -> do
bs <- ByteString.readFile yaml
Nothing -> return (cwd, defaultPackage cwd)
Just yamlPath -> do
bs <- ByteString.readFile (toFilePath yamlPath)
let isEmpty = ByteString.null bs
root = parent yamlPath
pkg <-
if
| isEmpty -> return emptyPackage
| otherwise -> decodeThrow bs
return (takeDirectory yaml, pkg)
| isEmpty -> return (defaultPackage root)
| otherwise -> readPackageIO root
return (root, pkg)

View File

@ -23,10 +23,10 @@ data TopCommand
| JuvixRepl ReplOptions
deriving stock (Data)
topCommandInputFile :: TopCommand -> Maybe FilePath
topCommandInputFile :: TopCommand -> Maybe (SomeBase File)
topCommandInputFile = firstJust getInputFile . universeBi
where
getInputFile :: Path -> Maybe FilePath
getInputFile :: AppPath File -> Maybe (SomeBase File)
getInputFile p
| p ^. pathIsInput = Just (p ^. pathPath)
| otherwise = Nothing
@ -134,12 +134,6 @@ parseTopCommand =
<|> parseCompilerCommand
<|> parseUtility
makeAbsPaths :: TopCommand -> IO TopCommand
makeAbsPaths = transformBiM go
where
go :: Path -> IO Path
go = traverseOf pathPath canonicalizePath
descr :: ParserInfo (GlobalOptions, TopCommand)
descr =
info

@ -1 +1 @@
Subproject commit c91ec2eb6daf49c993a1f55673e75d97cb8dada3
Subproject commit a2790ca49ceeb569559224abe1587305ea1861fa

View File

@ -15,6 +15,7 @@ github: anoma/juvix
extra-source-files:
- README.org
- assets/*
- juvix-stdlib/juvix.yaml
- juvix-stdlib/**/*.juvix
- runtime/include/**/*.h
- runtime/**/*.a
@ -23,6 +24,7 @@ extra-source-files:
dependencies:
- aeson == 2.0.*
- aeson-better-errors == 0.9.*
- ansi-terminal == 0.11.*
- base == 4.16.*
- blaze-html == 0.9.*
@ -31,8 +33,8 @@ dependencies:
- directory == 1.3.*
- dlist == 1.0.*
- edit-distance == 0.2.*
- exceptions == 0.10.*
- extra == 1.7.*
- transformers == 0.5.*
- file-embed == 0.0.*
- filepath == 1.4.*
- gitrev == 1.3.*
@ -45,19 +47,20 @@ dependencies:
- path-io == 1.7.*
- polysemy == 1.7.*
- polysemy-plugin == 0.4.*
- pretty == 1.1.*
- prettyprinter == 1.7.*
- prettyprinter-ansi-terminal == 1.1.*
- pretty == 1.1.*
- process == 1.6.*
- safe == 0.3.*
- semirings == 0.6.*
- singletons == 3.0.*
- singletons-th == 3.1.*
- Stream == 0.4.*
- time == 1.11.*
- template-haskell == 2.18.*
- text == 1.2.*
- th-utilities == 0.2.*
- time == 1.11.*
- transformers == 0.5.*
- unix-compat == 0.5.*
- unordered-containers == 0.2.*
- versions == 5.0.*
- yaml == 0.11.*

View File

@ -9,6 +9,7 @@ import Data.ByteString qualified as BS
import Data.ByteString.Builder qualified as Builder
import Data.HashMap.Strict qualified as HashMap
import Data.Time.Clock
import Data.Versions (prettyV)
import Juvix.Compiler.Abstract.Translation.FromConcrete qualified as Abstract
import Juvix.Compiler.Backend.Html.Data
import Juvix.Compiler.Backend.Html.Extra
@ -34,7 +35,7 @@ import Text.Blaze.Html5.Attributes qualified as Attr
data DocParams = DocParams
{ _docParamBase :: Text,
_docOutputDir :: FilePath
_docOutputDir :: Path Abs Dir
}
makeLenses ''DocParams
@ -64,8 +65,8 @@ indexTree = foldr insertModule emptyTree
emptyTree :: Tree Symbol (Maybe TopModulePath)
emptyTree = Tree Nothing mempty
indexFileName :: FilePath
indexFileName = "index.html"
indexFileName :: Path Rel File
indexFileName = $(mkRelFile "index.html")
createIndexFile ::
forall r.
@ -74,7 +75,7 @@ createIndexFile ::
Sem r ()
createIndexFile ps = do
outDir <- asks (^. docOutputDir)
indexHtml >>= (template mempty >=> writeHtml (outDir </> indexFileName))
indexHtml >>= (template mempty >=> writeHtml (outDir <//> indexFileName))
where
indexHtml :: Sem r Html
indexHtml = do
@ -126,7 +127,7 @@ createIndexFile ps = do
summary "Subtree"
<> ul (mconcatMap li c')
compile :: Members '[Embed IO] r => FilePath -> Text -> InternalTypedResult -> Sem r ()
compile :: Members '[Embed IO] r => Path Abs Dir -> Text -> InternalTypedResult -> Sem r ()
compile dir baseName ctx = runReader params . runReader normTable . runReader entry $ do
copyAssets
mapM_ goTopModule topModules
@ -147,15 +148,15 @@ compile dir baseName ctx = runReader params . runReader normTable . runReader en
. Scoped.mainModule
copyAssets :: forall s. Members '[Embed IO, Reader DocParams] s => Sem s ()
copyAssets = do
toAssetsDir <- (</> "assets") <$> asks (^. docOutputDir)
let writeAsset :: (FilePath, BS.ByteString) -> Sem s ()
toAssetsDir <- (<//> $(mkRelDir "assets")) <$> asks (^. docOutputDir)
let writeAsset :: (Path Rel File, BS.ByteString) -> Sem s ()
writeAsset (filePath, fileContents) =
Prelude.embed $ BS.writeFile (toAssetsDir </> takeFileName filePath) fileContents
Prelude.embed (createDirectoryIfMissing True toAssetsDir)
Prelude.embed $ BS.writeFile (toFilePath (toAssetsDir <//> filePath)) fileContents
ensureDir toAssetsDir
mapM_ writeAsset assetFiles
where
assetFiles :: [(FilePath, BS.ByteString)]
assetFiles = $(assetsDir)
assetFiles :: [(Path Rel File, BS.ByteString)]
assetFiles = assetsDir
params :: DocParams
params =
@ -166,19 +167,19 @@ compile dir baseName ctx = runReader params . runReader normTable . runReader en
topModules :: HashMap NameId (Module 'Scoped 'ModuleTop)
topModules = getAllModules mainMod
writeHtml :: Members '[Embed IO] r => FilePath -> Html -> Sem r ()
writeHtml :: Members '[Embed IO] r => Path Abs File -> Html -> Sem r ()
writeHtml f h = Prelude.embed $ do
createDirectoryIfMissing True dir
Builder.writeFile f (Html.renderHtmlBuilder h)
ensureDir dir
Builder.writeFile (toFilePath f) (Html.renderHtmlBuilder h)
where
dir :: FilePath
dir = takeDirectory f
dir :: Path Abs Dir
dir = parent f
moduleDocPath :: Members '[Reader HtmlOptions, Reader DocParams] r => Module 'Scoped 'ModuleTop -> Sem r FilePath
moduleDocPath :: Members '[Reader HtmlOptions, Reader DocParams] r => Module 'Scoped 'ModuleTop -> Sem r (Path Abs File)
moduleDocPath m = do
relPath <- moduleDocRelativePath (m ^. modulePath . S.nameConcrete)
outDir <- asks (^. docOutputDir)
return (outDir </> relPath)
return (outDir <//> relPath)
topModulePath ::
Module 'Scoped 'ModuleTop -> TopModulePath
@ -215,8 +216,8 @@ template rightMenu' content' = do
packageHeader :: Sem r Html
packageHeader = do
pkgName' <- toHtml <$> asks (^. entryPointPackage . packageName')
version' <- toHtml <$> asks (^. entryPointPackage . packageVersion')
pkgName' <- toHtml <$> asks (^. entryPointPackage . packageName)
version' <- toHtml <$> asks (^. entryPointPackage . packageVersion . to prettyV)
return $
Html.div ! Attr.id "package-header" $
( Html.span ! Attr.class_ "caption" $
@ -258,7 +259,7 @@ goTopModule ::
goTopModule m = do
runReader docHtmlOpts $ do
fpath <- moduleDocPath m
Prelude.embed (putStrLn ("processing " <> pack fpath))
Prelude.embed (putStrLn ("processing " <> pack (toFilePath fpath)))
docHtml >>= writeHtml fpath
runReader srcHtmlOpts $ do
@ -285,7 +286,7 @@ goTopModule m = do
return $
ul ! Attr.id "page-menu" ! Attr.class_ "links" $
li (a ! Attr.href sourceRef' $ "Source")
<> li (a ! Attr.href (fromString indexFileName) $ "Index")
<> li (a ! Attr.href (fromString (toFilePath indexFileName)) $ "Index")
content :: Sem s Html
content = do

View File

@ -12,6 +12,7 @@ import Juvix.Compiler.Concrete.Data.ScopedName qualified as S
import Juvix.Compiler.Concrete.Extra
import Juvix.Compiler.Concrete.Language
import Juvix.Compiler.Concrete.Pretty.Base
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver
import Juvix.Compiler.Internal.Pretty qualified as Internal
import Juvix.Extra.Paths
import Juvix.Extra.Version
@ -34,11 +35,11 @@ kindSuffix = \case
HtmlSrc -> "-src"
HtmlOnly -> ""
genHtml :: Options -> Bool -> Theme -> FilePath -> Bool -> Module 'Scoped 'ModuleTop -> IO ()
genHtml :: Options -> Bool -> Theme -> Path Abs Dir -> Bool -> Module 'Scoped 'ModuleTop -> IO ()
genHtml opts recursive theme outputDir printMetadata entry = do
createDirectoryIfMissing True outputDir
ensureDir outputDir
copyAssetFiles
withCurrentDirectory outputDir $ do
withCurrentDir outputDir $ do
mapM_ outputModule allModules
where
allModules
@ -47,25 +48,26 @@ genHtml opts recursive theme outputDir printMetadata entry = do
copyAssetFiles :: IO ()
copyAssetFiles = do
createDirectoryIfMissing True toAssetsDir
ensureDir toAssetsDir
mapM_ writeAsset assetFiles
where
assetFiles :: [(FilePath, BS.ByteString)]
assetFiles = $(assetsDir)
assetFiles :: [(Path Rel File, BS.ByteString)]
assetFiles = assetsDir
writeAsset :: (FilePath, BS.ByteString) -> IO ()
writeAsset :: (Path Rel File, BS.ByteString) -> IO ()
writeAsset (filePath, fileContents) =
BS.writeFile (toAssetsDir </> takeFileName filePath) fileContents
toAssetsDir = outputDir </> "assets"
BS.writeFile (toFilePath (toAssetsDir <//> filePath)) fileContents
toAssetsDir = outputDir <//> $(mkRelDir "assets")
outputModule :: Module 'Scoped 'ModuleTop -> IO ()
outputModule m = do
createDirectoryIfMissing True (takeDirectory htmlFile)
putStrLn $ "Writing " <> pack htmlFile
ensureDir (parent htmlFile)
putStrLn $ "Writing " <> pack (toFilePath htmlFile)
utc <- getCurrentTime
Text.writeFile htmlFile (genModule opts HtmlOnly printMetadata utc theme m)
Text.writeFile (toFilePath htmlFile) (genModule opts HtmlOnly printMetadata utc theme m)
where
htmlFile = topModulePathToDottedPath (m ^. modulePath . S.nameConcrete) <.> ".html"
htmlFile :: Path Rel File
htmlFile = relFile (topModulePathToDottedPath (m ^. modulePath . S.nameConcrete) <.> ".html")
genModuleHtml :: Options -> HtmlKind -> Bool -> UTCTime -> Theme -> Module 'Scoped 'ModuleTop -> Html
genModuleHtml opts htmlKind printMetadata utc theme m =
@ -215,15 +217,12 @@ putTag ann x = case ann of
nameIdAttr :: S.NameId -> AttributeValue
nameIdAttr (S.NameId k) = fromString . show $ k
moduleDocRelativePath :: Members '[Reader HtmlOptions] r => TopModulePath -> Sem r FilePath
moduleDocRelativePath :: Members '[Reader HtmlOptions] r => TopModulePath -> Sem r (Path Rel File)
moduleDocRelativePath m = do
suff <- kindSuffix <$> asks (^. htmlOptionsKind)
return (topModulePathToRelativeFilePath (Just "html") suff joinDot m)
where
joinDot :: FilePath -> FilePath -> FilePath
joinDot l r = l <.> r
return (topModulePathToRelativePathDot ".html" suff m)
nameIdAttrRef :: Members '[Reader HtmlOptions] r => TopModulePath -> Maybe S.NameId -> Sem r AttributeValue
nameIdAttrRef tp s = do
pth <- moduleDocRelativePath tp
pth <- toFilePath <$> moduleDocRelativePath tp
return (fromString pth <> preEscapedToValue '#' <>? (nameIdAttr <$> s))

View File

@ -15,12 +15,12 @@ data HighlightInput = HighlightInput
makeLenses ''HighlightInput
filterInput :: FilePath -> HighlightInput -> HighlightInput
filterInput :: Path Abs File -> HighlightInput -> HighlightInput
filterInput absPth HighlightInput {..} =
HighlightInput
{ _highlightNames = filterByLoc absPth _highlightNames,
_highlightParsed = filterByLoc absPth _highlightParsed
}
filterByLoc :: HasLoc p => FilePath -> [p] -> [p]
filterByLoc absPth = filter ((== absPth) . (^. intervalFile) . getLoc)
filterByLoc :: HasLoc p => Path Abs File -> [p] -> [p]
filterByLoc p = filter ((== toFilePath p) . (^. intervalFile) . getLoc)

View File

@ -23,7 +23,7 @@ instance HasLoc Name where
NameUnqualified s -> getLoc s
instance Pretty QualifiedName where
pretty (QualifiedName (Path path) s) =
pretty (QualifiedName (SymbolPath path) s) =
let symbols = snoc (toList path) s
in dotted (map pretty symbols)
where
@ -35,13 +35,13 @@ instance Pretty Name where
NameQualified q -> pretty q
NameUnqualified s -> pretty s
newtype Path = Path
newtype SymbolPath = SymbolPath
{ _pathParts :: NonEmpty Symbol
}
deriving stock (Show, Eq, Ord)
data QualifiedName = QualifiedName
{ _qualifiedPath :: Path,
{ _qualifiedPath :: SymbolPath,
_qualifiedSymbol :: Symbol
}
deriving stock (Show, Eq, Ord, Generic)
@ -52,13 +52,13 @@ instance HasLoc QualifiedName where
instance Hashable QualifiedName
instance HasLoc Path where
getLoc (Path p) = getLoc (NonEmpty.head p) <> getLoc (NonEmpty.last p)
instance HasLoc SymbolPath where
getLoc (SymbolPath p) = getLoc (NonEmpty.head p) <> getLoc (NonEmpty.last p)
deriving newtype instance Hashable Path
deriving newtype instance Hashable SymbolPath
makeLenses ''QualifiedName
makeLenses ''Path
makeLenses ''SymbolPath
-- | A.B.C corresponds to TopModulePath [A,B] C
data TopModulePath = TopModulePath
@ -79,31 +79,6 @@ instance HasLoc TopModulePath where
[] -> getLoc _modulePathName
(x : _) -> getLoc x <> getLoc _modulePathName
topModulePathToFilePath :: Member Files r => TopModulePath -> Sem r FilePath
topModulePathToFilePath = topModulePathToFilePath' (Just ".juvix")
topModulePathToRelativeFilePath :: Maybe String -> String -> (FilePath -> FilePath -> FilePath) -> TopModulePath -> FilePath
topModulePathToRelativeFilePath ext suffix joinpath mp = relFilePath
where
relDirPath :: FilePath
relDirPath = foldr (joinpath . toPath) mempty (mp ^. modulePathDir)
relFilePath :: FilePath
relFilePath = addExt (relDirPath `joinpath'` toPath (mp ^. modulePathName) <> suffix)
joinpath' :: FilePath -> FilePath -> FilePath
joinpath' l r
| null l = r
| otherwise = joinpath l r
addExt = case ext of
Nothing -> id
Just e -> (<.> e)
toPath :: Symbol -> FilePath
toPath s = unpack (s ^. symbolText)
topModulePathToFilePath' ::
Member Files r => Maybe String -> TopModulePath -> Sem r FilePath
topModulePathToFilePath' ext mp =
getAbsPath (topModulePathToRelativeFilePath ext "" (</>) mp)
topModulePathToDottedPath :: IsString s => TopModulePath -> s
topModulePathToDottedPath (TopModulePath l r) =
fromText $ mconcat $ intersperse "." $ map (^. symbolText) $ l ++ [r]

View File

@ -52,10 +52,8 @@ newtype ModulesCache = ModulesCache
makeLenses ''ModulesCache
data ScopeParameters = ScopeParameters
{ -- | Usually set to ".juvix".
_scopeFileExtension :: String,
-- | Used for import cycle detection.
newtype ScopeParameters = ScopeParameters
{ -- | Used for import cycle detection.
_scopeTopParents :: [Import 'Parsed]
}

View File

@ -40,11 +40,11 @@ instance Hashable AbsModulePath
-- | Tells whether the first argument is an immediate child of the second argument.
-- In other words, tells whether the first argument is a local module of the second.
isChildOf :: AbsModulePath -> AbsModulePath -> Bool
isChildOf child parent
isChildOf child parentMod
| null (child ^. absLocalPath) = False
| otherwise =
init (child ^. absLocalPath) == parent ^. absLocalPath
&& child ^. absTopModulePath == parent ^. absTopModulePath
init (child ^. absLocalPath) == parentMod ^. absLocalPath
&& child ^. absTopModulePath == parentMod ^. absTopModulePath
-- | Appends a local path to the absolute path
-- e.g. TopMod.Local <.> Inner == TopMod.Local.Inner

View File

@ -3,6 +3,7 @@ module Juvix.Compiler.Concrete.Translation where
-- import Juvix.Compiler.Concrete.Translation.FromParsed
import Juvix.Compiler.Concrete.Language
import Juvix.Compiler.Concrete.Translation.FromParsed qualified as Scoper
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver
import Juvix.Compiler.Concrete.Translation.FromSource qualified as Parser
import Juvix.Compiler.Pipeline.EntryPoint
import Juvix.Prelude
@ -10,7 +11,7 @@ import Juvix.Prelude
type JudocStash = State (Maybe (Judoc 'Parsed))
fromSource ::
Members '[Files, Error JuvixError, NameIdGen] r =>
Members '[Files, Error JuvixError, NameIdGen, Reader EntryPoint, PathResolver] r =>
EntryPoint ->
Sem r Scoper.ScoperResult
fromSource = Parser.fromSource >=> Scoper.fromParsed

View File

@ -7,14 +7,16 @@ where
import Juvix.Compiler.Concrete.Data.InfoTable
import Juvix.Compiler.Concrete.Language
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.Scoping
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.Scoping.Data.Context
import Juvix.Compiler.Concrete.Translation.FromSource qualified as Parser
import Juvix.Compiler.Concrete.Translation.FromSource.Data.Context qualified as Parsed
import Juvix.Compiler.Pipeline.EntryPoint
import Juvix.Prelude
fromParsed ::
Members '[Error JuvixError, Files, NameIdGen] r =>
Members '[Error JuvixError, Files, NameIdGen, Reader EntryPoint, PathResolver] r =>
Parsed.ParserResult ->
Sem r ScoperResult
fromParsed pr = mapError (JuvixError @ScoperError) $ do

View File

@ -0,0 +1,185 @@
module Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver
( module Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.Base,
module Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.Error,
module Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.PackageInfo,
PathResolver,
addDependency,
withPath,
withPathFile,
expectedModulePath,
runPathResolverPipe,
evalPathResolverPipe,
ResolverState,
resolverFiles,
resolverPackages,
)
where
import Data.HashMap.Strict qualified as HashMap
import Data.HashSet qualified as HashSet
import Juvix.Compiler.Concrete.Data.Name
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.Base
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.Error
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.PackageInfo
import Juvix.Compiler.Pipeline.EntryPoint
import Juvix.Extra.Stdlib (ensureStdlib)
import Juvix.Prelude
data PathResolver m a where
-- | Currently, we pass (Just entrypoint) only for the current package and Nothing for all dependencies
AddDependency :: Maybe EntryPoint -> Dependency -> PathResolver m ()
ExpectedModulePath :: TopModulePath -> PathResolver m (Path Abs File)
WithPath ::
TopModulePath ->
(Either PathResolverError (Path Abs Dir, Path Rel File) -> m x) ->
PathResolver m x
makeSem ''PathResolver
newtype ResolverEnv = ResolverEnv
{ _envRoot :: Path Abs Dir
}
data ResolverState = ResolverState
{ -- | juvix files indexed by relative path
_resolverFiles :: HashMap (Path Rel File) (NonEmpty PackageInfo),
-- | PackageInfos indexed by root
_resolverPackages :: HashMap (Path Abs Dir) PackageInfo
}
deriving stock (Show)
makeLenses ''ResolverState
makeLenses ''ResolverEnv
iniResolverState :: ResolverState
iniResolverState =
ResolverState
{ _resolverPackages = mempty,
_resolverFiles = mempty
}
mkPackageInfo ::
forall r.
Members '[Files, Error Text] r =>
Maybe EntryPoint ->
Path Abs Dir ->
Sem r PackageInfo
mkPackageInfo mpackageEntry _packageRoot = do
_packagePackage <- maybe (readPackage _packageRoot) (return . (^. entryPointPackage)) mpackageEntry
let deps :: [Dependency] = _packagePackage ^. packageDependencies
ensureStdlib _packageRoot deps
files :: [Path Rel File] <- map (fromJust . stripProperPrefix _packageRoot) <$> walkDirRelAccum juvixAccum _packageRoot []
let _packageRelativeFiles = HashSet.fromList files
_packageAvailableRoots =
HashSet.fromList (_packageRoot : map (^. dependencyPath) deps)
return PackageInfo {..}
where
juvixAccum :: Path Abs Dir -> [Path Rel Dir] -> [Path Rel File] -> [Path Abs File] -> Sem r ([Path Abs File], Recurse Rel)
juvixAccum cd _ files acc = return (newJuvixFiles <> acc, RecurseFilter (not . isHiddenDirectory))
where
newJuvixFiles :: [Path Abs File]
newJuvixFiles = [cd <//> f | f <- files, isJuvixFile f]
dependencyCached :: Members '[State ResolverState] r => Dependency -> Sem r Bool
dependencyCached d = HashMap.member (d ^. dependencyPath) <$> gets (^. resolverPackages)
withPathFile :: Members '[PathResolver] r => TopModulePath -> (Either PathResolverError (Path Abs File) -> Sem r a) -> Sem r a
withPathFile m f = withPath m (f . mapRight (uncurry (<//>)))
addDependency' ::
Members '[State ResolverState, Files, Error Text] r =>
Maybe EntryPoint ->
Dependency ->
Sem r ()
addDependency' me d = do
let p :: Path Abs Dir = d ^. dependencyPath
unlessM (dependencyCached d) $ do
pkgInfo <- mkPackageInfo me p
-- traceM ("adding dependency " <> pack (toFilePath p))
-- traceM ("has " <> prettyText (pkgInfo ^. packageRelativeFiles . to HashSet.size) <> " files")
modify' (set (resolverPackages . at p) (Just pkgInfo))
forM_ (pkgInfo ^. packageRelativeFiles) $ \f -> do
-- traceM ("adding file " <> pack (toFilePath f))
modify' (over resolverFiles (HashMap.insertWith (<>) f (pure pkgInfo)))
forM_ (pkgInfo ^. packagePackage . packageDependencies) (addDependency' Nothing)
currentPackage :: Members '[State ResolverState, Reader ResolverEnv] r => Sem r PackageInfo
currentPackage = do
curRoot <- asks (^. envRoot)
gets (^?! resolverPackages . at curRoot . _Just)
-- | Returns the root of the package where the module belongs and the path to
-- the module relative to the root.
resolvePath' :: Members '[State ResolverState, Reader ResolverEnv] r => TopModulePath -> Sem r (Either PathResolverError (Path Abs Dir, Path Rel File))
resolvePath' mp = do
z <- gets (^. resolverFiles)
curPkg <- currentPackage
let rel = topModulePathToRelativePath' mp
packagesWithModule = z ^. at rel
visible :: PackageInfo -> Bool
visible p = HashSet.member (p ^. packageRoot) (curPkg ^. packageAvailableRoots)
return $ case filter visible (maybe [] toList packagesWithModule) of
[r] -> Right (r ^. packageRoot, rel)
[] ->
Left
( ErrMissingModule
MissingModule
{ _missingInfo = curPkg,
_missingModule = mp
}
)
(r : rs) ->
Left
( ErrDependencyConflict
DependencyConflict
{ _conflictPackages = r :| rs,
_conflictPath = mp
}
)
expectedPath' :: Members '[Reader ResolverEnv] r => TopModulePath -> Sem r (Path Abs File)
expectedPath' m = do
root <- asks (^. envRoot)
return (root <//> topModulePathToRelativePath' m)
re ::
forall r a.
Members '[Files, Error Text] r =>
Sem (PathResolver ': r) a ->
Sem (Reader ResolverEnv ': State ResolverState ': r) a
re = reinterpret2H helper
where
helper ::
forall rInitial x.
PathResolver (Sem rInitial) x ->
Tactical PathResolver (Sem rInitial) (Reader ResolverEnv ': (State ResolverState ': r)) x
helper = \case
AddDependency me m -> addDependency' me m >>= pureT
ExpectedModulePath m -> expectedPath' m >>= pureT
WithPath m a -> do
x :: Either PathResolverError (Path Abs Dir, Path Rel File) <- resolvePath' m
oldroot <- asks (^. envRoot)
x' <- pureT x
a' <- bindT a
st' <- get
let root' = case x of
Left {} -> oldroot
Right (r, _) -> r
raise (evalPathResolver' st' root' (a' x'))
evalPathResolver' :: Members '[Files, Error Text] r => ResolverState -> Path Abs Dir -> Sem (PathResolver ': r) a -> Sem r a
evalPathResolver' st root = fmap snd . runPathResolver' st root
runPathResolver' :: Members '[Files, Error Text] r => ResolverState -> Path Abs Dir -> Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolver' st root = runState st . runReader (ResolverEnv root) . re
runPathResolver :: Members '[Files, Error Text] r => Path Abs Dir -> Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolver = runPathResolver' iniResolverState
runPathResolverPipe :: Members '[Files, Reader EntryPoint] r => Sem (PathResolver ': r) a -> Sem r (ResolverState, a)
runPathResolverPipe a = do
r <- asks (^. entryPointResolverRoot)
runError (runPathResolver r (raiseUnder a)) >>= either error return
evalPathResolverPipe :: Members '[Files, Reader EntryPoint] r => Sem (PathResolver ': r) a -> Sem r a
evalPathResolverPipe = fmap snd . runPathResolverPipe

View File

@ -0,0 +1,28 @@
module Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.Base where
import Juvix.Compiler.Concrete.Data.Name
import Juvix.Prelude
topModulePathToRelativePath' :: TopModulePath -> Path Rel File
topModulePathToRelativePath' = topModulePathToRelativePath ".juvix" "" (</>)
topModulePathToRelativePath :: String -> String -> (FilePath -> FilePath -> FilePath) -> TopModulePath -> Path Rel File
topModulePathToRelativePath ext suffix joinpath mp = relFile relFilePath
where
relDirPath :: FilePath
relDirPath = foldr (joinpath . toPath) mempty (mp ^. modulePathDir)
relFilePath :: FilePath
relFilePath = addExt (relDirPath `joinpath'` toPath (mp ^. modulePathName) <> suffix)
joinpath' :: FilePath -> FilePath -> FilePath
joinpath' l r
| null l = r
| otherwise = joinpath l r
addExt = (<.> ext)
toPath :: Symbol -> FilePath
toPath s = unpack (s ^. symbolText)
topModulePathToRelativePathDot :: String -> String -> TopModulePath -> Path Rel File
topModulePathToRelativePathDot ext suff m = topModulePathToRelativePath ext suff joinDot m
where
joinDot :: FilePath -> FilePath -> FilePath
joinDot l r = l <.> r

View File

@ -0,0 +1,61 @@
module Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.Error where
import Juvix.Compiler.Concrete.Language
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.Base
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.PackageInfo
import Juvix.Compiler.Pipeline.Package
import Juvix.Data.CodeAnn
import Juvix.Prelude
data PathResolverError
= ErrDependencyConflict DependencyConflict
| ErrMissingModule MissingModule
deriving stock (Show)
instance PrettyCodeAnn PathResolverError where
ppCodeAnn = \case
ErrDependencyConflict e -> ppCodeAnn e
ErrMissingModule e -> ppCodeAnn e
data DependencyConflict = DependencyConflict
{ _conflictPackages :: NonEmpty PackageInfo,
_conflictPath :: TopModulePath
}
deriving stock (Show)
instance PrettyCodeAnn DependencyConflict where
ppCodeAnn DependencyConflict {..} =
"The module name "
<> code (pretty _conflictPath)
<> " is ambiguous. It is defined in these packages:"
<> line
<> indent' (itemize (item <$> toList _conflictPackages))
where
item :: PackageInfo -> Doc CodeAnn
item pkg = pcode (pkg ^. packagePackage . packageName) <+> "at" <+> pretty (pkg ^. packageRoot)
pcode :: Pretty a => a -> Doc CodeAnn
pcode = code . pretty
data MissingModule = MissingModule
{ _missingModule :: TopModulePath,
_missingInfo :: PackageInfo
}
deriving stock (Show)
instance PrettyCodeAnn MissingModule where
ppCodeAnn MissingModule {..} =
"The module"
<+> pcode _missingModule
<+> "does not exist."
<> line
<> suggestion
where
suggestion :: Doc Ann
suggestion =
"It should be in"
<+> pcode (_missingInfo ^. packageRoot <//> topModulePathToRelativePath' _missingModule)
<> line
<> "or in one of the dependencies:"
<> line
<> itemize (map pcode (_missingInfo ^.. packagePackage . packageDependencies . each . dependencyPath))

View File

@ -0,0 +1,18 @@
module Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.PackageInfo where
import Juvix.Compiler.Pipeline.EntryPoint
import Juvix.Prelude
data PackageInfo = PackageInfo
{ _packageRoot :: Path Abs Dir,
-- | files relative to the root of the package
_packageRelativeFiles :: HashSet (Path Rel File),
_packageAvailableRoots :: HashSet (Path Abs Dir),
_packagePackage :: Package
}
deriving stock (Show)
makeLenses ''PackageInfo
packageFiles :: PackageInfo -> [Path Abs File]
packageFiles k = [k ^. packageRoot <//> f | f <- toList (k ^. packageRelativeFiles)]

View File

@ -20,11 +20,13 @@ import Juvix.Compiler.Concrete.Data.Scope qualified as S
import Juvix.Compiler.Concrete.Data.ScopedName qualified as S
import Juvix.Compiler.Concrete.Extra qualified as P
import Juvix.Compiler.Concrete.Language
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.Scoping.Data.Context
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.Scoping.Error
import Juvix.Compiler.Concrete.Translation.FromSource (runModuleParser)
import Juvix.Compiler.Concrete.Translation.FromSource qualified as Parser
import Juvix.Compiler.Concrete.Translation.FromSource.Data.Context (ParserResult)
import Juvix.Compiler.Pipeline.EntryPoint
import Juvix.Prelude
iniScoperState :: ScoperState
@ -38,12 +40,11 @@ iniScoperState =
iniScopeParameters :: ScopeParameters
iniScopeParameters =
ScopeParameters
{ _scopeFileExtension = ".juvix",
_scopeTopParents = mempty
{ _scopeTopParents = mempty
}
scopeCheck ::
Members '[Files, Error ScoperError, NameIdGen] r =>
Members '[Files, Error ScoperError, NameIdGen, Reader EntryPoint, PathResolver] r =>
ParserResult ->
InfoTable ->
NonEmpty (Module 'Parsed 'ModuleTop) ->
@ -216,13 +217,13 @@ bindLocalModuleSymbol _moduleExportInfo _moduleRefModule =
checkImport ::
forall r.
Members '[Error ScoperError, State Scope, Reader ScopeParameters, Files, State ScoperState, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen] r =>
Members '[Error ScoperError, State Scope, Reader ScopeParameters, Files, State ScoperState, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen, PathResolver] r =>
Import 'Parsed ->
Sem r (Import 'Scoped)
checkImport import_@(Import path) = do
checkCycle
cache <- gets (^. scoperModulesCache . cachedModules)
moduleRef <- maybe (readParseModule path >>= local addImport . checkTopModule) return (cache ^. at path)
moduleRef <- maybe (readScopeModule import_) return (cache ^. at path)
let checked = moduleRef ^. moduleRefModule
sname = checked ^. modulePath
moduleId = sname ^. S.nameId
@ -232,8 +233,6 @@ checkImport import_@(Import path) = do
modify (over scoperModules (HashMap.insert moduleId moduleRef'))
return (Import checked)
where
addImport :: ScopeParameters -> ScopeParameters
addImport = over scopeTopParents (cons import_)
checkCycle :: Sem r ()
checkCycle = do
topp <- asks (^. scopeTopParents)
@ -334,7 +333,7 @@ checkQualifiedExpr ::
Members '[Error ScoperError, State Scope, State ScoperState, InfoTableBuilder] r =>
QualifiedName ->
Sem r ScopedIden
checkQualifiedExpr q@(QualifiedName (Path p) sym) = do
checkQualifiedExpr q@(QualifiedName (SymbolPath p) sym) = do
es <- filter entryIsExpression <$> lookupQualifiedSymbol (toList p, sym)
case es of
[] -> notInScope
@ -384,25 +383,32 @@ exportScope Scope {..} = do
(MultipleExportConflict _scopePath s (e :| es))
)
readParseModule ::
Members '[Error ScoperError, Reader ScopeParameters, Files, Parser.InfoTableBuilder, NameIdGen] r =>
withPath' ::
forall r a.
Members '[PathResolver, Error ScoperError] r =>
TopModulePath ->
Sem r (Module 'Parsed 'ModuleTop)
readParseModule mp = do
path <- modulePathToFilePath mp
(Path Abs File -> Sem r a) ->
Sem r a
withPath' mp a = withPathFile mp (either err a)
where
err :: PathResolverError -> Sem r a
err = throw . ErrTopModulePath . TopModulePathError mp
readScopeModule ::
Members '[Error ScoperError, Reader ScopeParameters, Files, Parser.InfoTableBuilder, NameIdGen, PathResolver, State ScoperState, InfoTableBuilder] r =>
Import 'Parsed ->
Sem r (ModuleRef'' 'S.NotConcrete 'ModuleTop)
readScopeModule import_ = withPath' (import_ ^. importModule) $ \path -> do
txt <- readFile' path
pr <- runModuleParser path txt
case pr of
Left err -> throw (ErrParser (MegaParsecError err))
Right (tbl, m) -> Parser.mergeTable tbl $> m
modulePathToFilePath ::
Members '[Reader ScopeParameters, Files] r =>
TopModulePath ->
Sem r FilePath
modulePathToFilePath mp = do
ext <- asks (^. scopeFileExtension)
topModulePathToFilePath' (Just ext) mp
Right (tbl, m) -> do
Parser.mergeTable tbl
local addImport (checkTopModule m)
where
addImport :: ScopeParameters -> ScopeParameters
addImport = over scopeTopParents (cons import_)
checkOperatorSyntaxDef ::
forall r.
@ -510,7 +516,7 @@ createExportsTable ei = foldr (HashSet.insert . getNameId) HashSet.empty (HashMa
checkTopModules ::
forall r.
Members '[Error ScoperError, Reader ScopeParameters, Files, State ScoperState, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen] r =>
Members '[Error ScoperError, Reader ScopeParameters, Files, State ScoperState, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen, PathResolver] r =>
NonEmpty (Module 'Parsed 'ModuleTop) ->
Sem r (NonEmpty (Module 'Scoped 'ModuleTop), HashSet NameId)
checkTopModules modules = do
@ -520,14 +526,14 @@ checkTopModules modules = do
checkTopModule_ ::
forall r.
Members '[Error ScoperError, Reader ScopeParameters, Files, State ScoperState, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen] r =>
Members '[Error ScoperError, Reader ScopeParameters, Files, State ScoperState, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen, PathResolver] r =>
Module 'Parsed 'ModuleTop ->
Sem r (Module 'Scoped 'ModuleTop)
checkTopModule_ = fmap (^. moduleRefModule) . checkTopModule
checkTopModule ::
forall r.
Members '[Error ScoperError, Reader ScopeParameters, Files, State ScoperState, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen] r =>
Members '[Error ScoperError, Reader ScopeParameters, Files, State ScoperState, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen, PathResolver] r =>
Module 'Parsed 'ModuleTop ->
Sem r (ModuleRef'' 'S.NotConcrete 'ModuleTop)
checkTopModule m@(Module path params doc body) = do
@ -536,11 +542,11 @@ checkTopModule m@(Module path params doc body) = do
modify (over (scoperModulesCache . cachedModules) (HashMap.insert path r))
return r
where
checkPath :: Members '[Files, Reader ScopeParameters, Error ScoperError] s => Sem s ()
checkPath :: Members '[Error ScoperError, PathResolver] s => Sem s ()
checkPath = do
expectedPath <- modulePathToFilePath path
let actualPath = getLoc path ^. intervalFile
unlessM (fromMaybe True <$> equalPaths' expectedPath actualPath) $
expectedPath <- expectedModulePath path
let actualPath = absFile (getLoc path ^. intervalFile)
unlessM (equalPaths expectedPath actualPath) $
throw
( ErrWrongTopModuleName
WrongTopModuleName
@ -598,7 +604,7 @@ withScope ma = do
checkModuleBody ::
forall r.
Members '[Error ScoperError, State Scope, Reader ScopeParameters, State ScoperState, Files, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen] r =>
Members '[Error ScoperError, State Scope, Reader ScopeParameters, State ScoperState, Files, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen, PathResolver] r =>
[Statement 'Parsed] ->
Sem r (ExportInfo, [Statement 'Scoped])
checkModuleBody body = do
@ -610,7 +616,7 @@ checkModuleBody body = do
checkLocalModule ::
forall r.
Members '[Error ScoperError, State Scope, Reader ScopeParameters, State ScoperState, Files, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen] r =>
Members '[Error ScoperError, State Scope, Reader ScopeParameters, State ScoperState, Files, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen, PathResolver] r =>
Module 'Parsed 'ModuleLocal ->
Sem r (Module 'Scoped 'ModuleLocal)
checkLocalModule Module {..} = do
@ -686,7 +692,7 @@ lookupModuleSymbol n = do
notInScope = throw (ErrModuleNotInScope (ModuleNotInScope n))
(path, sym) = case n of
NameUnqualified s -> ([], s)
NameQualified (QualifiedName (Path p) s) -> (toList p, s)
NameQualified (QualifiedName (SymbolPath p) s) -> (toList p, s)
getModuleRef :: SymbolEntry -> Maybe (ModuleRef' 'S.NotConcrete)
getModuleRef = \case
@ -706,7 +712,7 @@ getExportInfo modId = do
_ :&: ent -> ent ^. moduleExportInfo
checkOpenImportModule ::
Members '[Error ScoperError, Reader ScopeParameters, Files, State Scope, State ScoperState, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen] r =>
Members '[Error ScoperError, Reader ScopeParameters, Files, State Scope, State ScoperState, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen, PathResolver] r =>
OpenModule 'Parsed ->
Sem r (OpenModule 'Scoped)
checkOpenImportModule op
@ -714,7 +720,7 @@ checkOpenImportModule op
let moduleNameToTopModulePath :: Name -> TopModulePath
moduleNameToTopModulePath = \case
NameUnqualified s -> TopModulePath [] s
NameQualified (QualifiedName (Path p) s) -> TopModulePath (toList p) s
NameQualified (QualifiedName (SymbolPath p) s) -> TopModulePath (toList p) s
import_ :: Import 'Parsed
import_ = Import (moduleNameToTopModulePath (op ^. openModuleName))
in do
@ -783,7 +789,7 @@ checkOpenModuleNoImport OpenModule {..}
checkOpenModule ::
forall r.
Members '[Error ScoperError, Reader ScopeParameters, Files, State Scope, State ScoperState, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen] r =>
Members '[Error ScoperError, Reader ScopeParameters, Files, State Scope, State ScoperState, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen, PathResolver] r =>
OpenModule 'Parsed ->
Sem r (OpenModule 'Scoped)
checkOpenModule op
@ -1102,14 +1108,14 @@ checkPatternName n = do
Nothing -> PatternScopedVar <$> groupBindLocalVariable sym -- the symbol is a variable
where
(path, sym) = case n of
NameQualified (QualifiedName (Path p) s) -> (toList p, s)
NameQualified (QualifiedName (SymbolPath p) s) -> (toList p, s)
NameUnqualified s -> ([], s)
-- check whether the symbol is a constructor in scope
getConstructorRef :: Sem r (Maybe ConstructorRef)
getConstructorRef = do
entries <- mapMaybe getConstructor <$> lookupQualifiedSymbol (path, sym)
case entries of
[] -> case Path <$> nonEmpty path of
[] -> case SymbolPath <$> nonEmpty path of
Nothing -> return Nothing -- There is no constructor with such a name
Just pth -> throw (ErrQualSymNotInScope (QualSymNotInScope (QualifiedName pth sym)))
[e] -> return (Just (set (constructorRefName . S.nameConcrete) n e)) -- There is one constructor with such a name
@ -1295,7 +1301,7 @@ checkParsePatternAtom ::
checkParsePatternAtom = checkPatternAtom >=> parsePatternAtom
checkStatement ::
Members '[Error ScoperError, Reader ScopeParameters, Files, State Scope, State ScoperState, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen] r =>
Members '[Error ScoperError, Reader ScopeParameters, Files, State Scope, State ScoperState, Reader LocalVars, InfoTableBuilder, Parser.InfoTableBuilder, NameIdGen, PathResolver] r =>
Statement 'Parsed ->
Sem r (Statement 'Scoped)
checkStatement s = case s of

View File

@ -39,6 +39,7 @@ data ScoperError
| ErrAliasBinderPattern AliasBinderPattern
| ErrImplicitPatternLeftApplication ImplicitPatternLeftApplication
| ErrConstructorExpectedLeftApplication ConstructorExpectedLeftApplication
| ErrTopModulePath TopModulePathError
deriving stock (Show)
instance ToGenericError ScoperError where
@ -71,3 +72,4 @@ instance ToGenericError ScoperError where
ErrAliasBinderPattern e -> genericError e
ErrImplicitPatternLeftApplication e -> genericError e
ErrConstructorExpectedLeftApplication e -> genericError e
ErrTopModulePath e -> genericError e

View File

@ -13,11 +13,31 @@ import Juvix.Compiler.Concrete.Data.ScopedName qualified as S
import Juvix.Compiler.Concrete.Language
import Juvix.Compiler.Concrete.Language qualified as L
import Juvix.Compiler.Concrete.Pretty.Options (Options, fromGenericOptions)
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver.Error
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.Scoping.Error.Pretty
import Juvix.Data.CodeAnn
import Juvix.Parser.Error qualified as Parser
import Juvix.Prelude
data TopModulePathError = TopModulePathError
{ _topModulePathErrorPath :: TopModulePath,
_topModulePathError :: PathResolverError
}
deriving stock (Show)
instance ToGenericError TopModulePathError where
genericError TopModulePathError {..} = do
let msg = ppCodeAnn _topModulePathError
return
GenericError
{ _genericErrorLoc = i,
_genericErrorMessage = AnsiText msg,
_genericErrorIntervals = [i]
}
where
i :: Interval
i = getLoc _topModulePathErrorPath
data MultipleDeclarations = MultipleDeclarations
{ _multipleDeclEntry :: SymbolEntry,
_multipleDeclSymbol :: Text,
@ -406,8 +426,8 @@ instance ToGenericError UnusedOperatorDef where
<> ppCode opts' _unusedOperatorDef
data WrongTopModuleName = WrongTopModuleName
{ _wrongTopModuleNameExpectedPath :: FilePath,
_wrongTopModuleNameActualPath :: FilePath,
{ _wrongTopModuleNameExpectedPath :: Path Abs File,
_wrongTopModuleNameActualPath :: Path Abs File,
_wrongTopModuleNameActualName :: TopModulePath
}
deriving stock (Show)

View File

@ -34,7 +34,7 @@ fromSource e = mapError (JuvixError @ParserError) $ do
goFile ::
forall r.
Members '[Files, Error ParserError, InfoTableBuilder, NameIdGen] r =>
FilePath ->
Path Abs File ->
Sem r (Module 'Parsed 'ModuleTop)
goFile fileName = do
input <- getFileContents fileName
@ -43,7 +43,7 @@ fromSource e = mapError (JuvixError @ParserError) $ do
Left er -> throw er
Right (tbl, m) -> mergeTable tbl $> m
where
getFileContents :: FilePath -> Sem r Text
getFileContents :: Path Abs File -> Sem r Text
getFileContents fp
| fp == e ^. mainModulePath,
Just txt <- e ^. entryPointStdin =
@ -65,12 +65,12 @@ expressionFromTextSource fp txt = mapError (JuvixError @ParserError) $ do
-- | The fileName is only used for reporting errors. It is safe to pass
-- an empty string.
runModuleParser :: Members '[NameIdGen] r => FilePath -> Text -> Sem r (Either ParserError (InfoTable, Module 'Parsed 'ModuleTop))
runModuleParser :: Members '[NameIdGen] r => Path Abs File -> Text -> Sem r (Either ParserError (InfoTable, Module 'Parsed 'ModuleTop))
runModuleParser fileName input = do
m <-
runInfoTableBuilder $
evalState (Nothing @(Judoc 'Parsed)) $
P.runParserT topModuleDef fileName input
P.runParserT topModuleDef (toFilePath fileName) input
case m of
(_, Left err) -> return (Left (ParserError err))
(tbl, Right r) -> return (Right (tbl, r))
@ -118,7 +118,7 @@ name :: Members '[InfoTableBuilder, JudocStash, NameIdGen] r => ParsecS r Name
name = do
parts <- dottedSymbol
return $ case nonEmptyUnsnoc parts of
(Just p, n) -> NameQualified (QualifiedName (Path p) n)
(Just p, n) -> NameQualified (QualifiedName (SymbolPath p) n)
(Nothing, n) -> NameUnqualified n
mkTopModulePath :: NonEmpty Symbol -> TopModulePath

View File

@ -4,7 +4,7 @@ module Juvix.Compiler.Core.Info where
import Data.Dynamic
import Data.HashMap.Strict qualified as HashMap
import Juvix.Prelude
import Juvix.Prelude hiding (insert)
class Typeable a => IsInfo a
@ -21,7 +21,7 @@ empty :: Info
empty = Info HashMap.empty
singleton :: forall a. IsInfo a => a -> Info
singleton a = Juvix.Compiler.Core.Info.insert a Juvix.Compiler.Core.Info.empty
singleton a = insert a mempty
member :: forall a. IsInfo a => Key a -> Info -> Bool
member k i = HashMap.member (typeRep k) (i ^. infoMap)

View File

@ -17,7 +17,7 @@ data FunCall = FunCall
}
newtype CallRow = CallRow
{ _callRow :: Maybe (Int, Rel')
{ _callRow :: Maybe (Int, SizeRel')
}
deriving stock (Eq, Show, Generic)

View File

@ -28,7 +28,7 @@ data ReflexiveEdge = ReflexiveEdge
data RecursiveBehaviour = RecursiveBehaviour
{ _recursiveBehaviourFun :: FunctionName,
_recursiveBehaviourMatrix :: [[Rel]]
_recursiveBehaviourMatrix :: [[SizeRel]]
}
deriving stock (Show)

View File

@ -3,33 +3,33 @@ module Juvix.Compiler.Internal.Translation.FromAbstract.Analysis.Termination.Dat
import Juvix.Prelude
import Prettyprinter
data Rel
= RJust Rel'
data SizeRel
= RJust SizeRel'
| RNothing
deriving stock (Eq, Show, Generic)
data Rel'
data SizeRel'
= REq
| RLe
deriving stock (Eq, Show, Generic)
instance Hashable Rel'
instance Hashable SizeRel'
instance Hashable Rel
instance Hashable SizeRel
toRel :: Rel' -> Rel
toRel = RJust
toSizeRel :: SizeRel' -> SizeRel
toSizeRel = RJust
mul' :: Rel' -> Rel' -> Rel'
mul' :: SizeRel' -> SizeRel' -> SizeRel'
mul' REq a = a
mul' RLe _ = RLe
instance Pretty Rel where
instance Pretty SizeRel where
pretty r = case r of
RJust r' -> pretty r'
RNothing -> pretty ("?" :: Text)
instance Pretty Rel' where
instance Pretty SizeRel' where
pretty r = case r of
REq -> pretty ("=" :: Text)
RLe -> pretty ("" :: Text)

View File

@ -115,10 +115,10 @@ reflexiveEdges (CompleteCallGraph es) = mapMaybe reflexive (toList es)
Just $ ReflexiveEdge (e ^. edgeFrom) (e ^. edgeMatrices)
| otherwise = Nothing
callMatrixDiag :: CallMatrix -> [Rel]
callMatrixDiag :: CallMatrix -> [SizeRel]
callMatrixDiag m = [col i r | (i, r) <- zip [0 :: Int ..] m]
where
col :: Int -> CallRow -> Rel
col :: Int -> CallRow -> SizeRel
col i (CallRow row) = case row of
Nothing -> RNothing
Just (j, r')
@ -134,7 +134,7 @@ recursiveBehaviour re =
findOrder :: RecursiveBehaviour -> Maybe LexOrder
findOrder rb = LexOrder <$> listToMaybe (mapMaybe (isLexOrder >=> nonEmpty) allPerms)
where
b0 :: [[Rel]]
b0 :: [[SizeRel]]
b0 = rb ^. recursiveBehaviourMatrix
indexed = map (zip [0 :: Int ..] . take minLength) b0
@ -144,13 +144,13 @@ findOrder rb = LexOrder <$> listToMaybe (mapMaybe (isLexOrder >=> nonEmpty) allP
startB = removeUselessColumns indexed
-- removes columns that don't have at least one ≺ in them
removeUselessColumns :: [[(Int, Rel)]] -> [[(Int, Rel)]]
removeUselessColumns :: [[(Int, SizeRel)]] -> [[(Int, SizeRel)]]
removeUselessColumns = transpose . filter (any (isLess . snd)) . transpose
isLexOrder :: [Int] -> Maybe [Int]
isLexOrder = go startB
where
go :: [[(Int, Rel)]] -> [Int] -> Maybe [Int]
go :: [[(Int, SizeRel)]] -> [Int] -> Maybe [Int]
go [] _ = Just []
go b perm = case perm of
[] -> error "The permutation should have one element at least!"

View File

@ -1,6 +1,7 @@
module Juvix.Compiler.Pipeline
( module Juvix.Compiler.Pipeline,
module Juvix.Compiler.Pipeline.EntryPoint,
module Juvix.Compiler.Pipeline.Artifacts,
module Juvix.Compiler.Pipeline.ExpressionContext,
)
where
@ -12,10 +13,12 @@ import Juvix.Compiler.Backend.C qualified as C
import Juvix.Compiler.Builtins
import Juvix.Compiler.Concrete qualified as Concrete
import Juvix.Compiler.Concrete.Translation.FromParsed qualified as Scoper
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver
import Juvix.Compiler.Concrete.Translation.FromSource qualified as Parser
import Juvix.Compiler.Core.Language qualified as Core
import Juvix.Compiler.Core.Translation qualified as Core
import Juvix.Compiler.Internal qualified as Internal
import Juvix.Compiler.Pipeline.Artifacts
import Juvix.Compiler.Pipeline.EntryPoint
import Juvix.Compiler.Pipeline.ExpressionContext
import Juvix.Compiler.Pipeline.Setup
@ -23,7 +26,7 @@ import Juvix.Compiler.Reg.Data.InfoTable qualified as Reg
import Juvix.Compiler.Reg.Translation.FromAsm qualified as Reg
import Juvix.Prelude
type PipelineEff = '[Reader EntryPoint, Files, NameIdGen, Builtins, Error JuvixError, Embed IO]
type PipelineEff = '[PathResolver, Reader EntryPoint, Files, NameIdGen, Builtins, Error JuvixError, Embed IO]
arityCheckExpression ::
Members '[Error JuvixError, NameIdGen, Builtins] r =>
@ -108,49 +111,49 @@ compile ::
compile = typecheck >=> C.fromInternal
upToParsing ::
Members '[Reader EntryPoint, Files, Error JuvixError, NameIdGen] r =>
Members '[Reader EntryPoint, Files, Error JuvixError, NameIdGen, PathResolver] r =>
Sem r Parser.ParserResult
upToParsing = entrySetup >>= Parser.fromSource
upToScoping ::
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError] r =>
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError, PathResolver] r =>
Sem r Scoper.ScoperResult
upToScoping = upToParsing >>= Scoper.fromParsed
upToAbstract ::
Members '[Reader EntryPoint, Files, NameIdGen, Builtins, Error JuvixError] r =>
Members '[Reader EntryPoint, Files, NameIdGen, Builtins, Error JuvixError, PathResolver] r =>
Sem r Abstract.AbstractResult
upToAbstract = upToScoping >>= Abstract.fromConcrete
upToInternal ::
Members '[Reader EntryPoint, Files, NameIdGen, Builtins, Error JuvixError] r =>
Members '[Reader EntryPoint, Files, NameIdGen, Builtins, Error JuvixError, PathResolver] r =>
Sem r Internal.InternalResult
upToInternal = upToAbstract >>= Internal.fromAbstract
upToInternalArity ::
Members '[Reader EntryPoint, Files, NameIdGen, Builtins, Error JuvixError] r =>
Members '[Reader EntryPoint, Files, NameIdGen, Builtins, Error JuvixError, PathResolver] r =>
Sem r Internal.InternalArityResult
upToInternalArity = upToInternal >>= Internal.arityChecking
upToInternalTyped ::
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError, Builtins] r =>
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError, Builtins, PathResolver] r =>
Sem r Internal.InternalTypedResult
upToInternalTyped = upToInternalArity >>= Internal.typeChecking
upToInternalReachability ::
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError, Builtins] r =>
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError, Builtins, PathResolver] r =>
Sem r Internal.InternalTypedResult
upToInternalReachability =
Internal.filterUnreachable <$> upToInternalTyped
upToCore ::
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError, Builtins] r =>
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError, Builtins, PathResolver] r =>
Sem r Core.CoreResult
upToCore =
upToInternalReachability >>= Core.fromInternal
upToMiniC ::
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError, Builtins] r =>
Members '[Reader EntryPoint, Files, NameIdGen, Error JuvixError, Builtins, PathResolver] r =>
Sem r C.MiniCResult
upToMiniC = upToInternalReachability >>= C.fromInternal
@ -168,17 +171,28 @@ regToMiniC lims = return . C.fromReg lims
-- Run pipeline
--------------------------------------------------------------------------------
runIOEither :: forall a. BuiltinsState -> EntryPoint -> Sem PipelineEff a -> IO (Either JuvixError (BuiltinsState, a))
runIOEither :: forall a. BuiltinsState -> EntryPoint -> Sem PipelineEff a -> IO (Either JuvixError (Artifacts, a))
runIOEither builtinsState entry =
runM
. runError
. fmap makeArtifacts
. runBuiltins builtinsState
. runNameIdGen
. mapError (JuvixError @FilesError)
. runFilesIO (entry ^. entryPointRoot)
. runFilesIO
. runReader entry
. runPathResolverPipe
where
makeArtifacts :: (BuiltinsState, (ResolverState, any)) -> (Artifacts, any)
makeArtifacts (builtins, (resolver, a)) =
( Artifacts
{ _artifactBuiltins = builtins,
_artifactResolver = resolver
},
a
)
runIO :: BuiltinsState -> GenericOptions -> EntryPoint -> Sem PipelineEff a -> IO (BuiltinsState, a)
runIO :: BuiltinsState -> GenericOptions -> EntryPoint -> Sem PipelineEff a -> IO (Artifacts, a)
runIO builtinsState opts entry = runIOEither builtinsState entry >=> mayThrow
where
mayThrow :: Either JuvixError r -> IO r
@ -186,5 +200,5 @@ runIO builtinsState opts entry = runIOEither builtinsState entry >=> mayThrow
Left err -> runM $ runReader opts $ printErrorAnsiSafe err >> embed exitFailure
Right r -> return r
runIO' :: BuiltinsState -> EntryPoint -> Sem PipelineEff a -> IO (BuiltinsState, a)
runIO' :: BuiltinsState -> EntryPoint -> Sem PipelineEff a -> IO (Artifacts, a)
runIO' builtinsState = runIO builtinsState defaultGenericOptions

View File

@ -0,0 +1,13 @@
-- | Stuff that is generated when the pipeline is run
module Juvix.Compiler.Pipeline.Artifacts where
import Juvix.Compiler.Builtins
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver
import Juvix.Prelude
data Artifacts = Artifacts
{ _artifactBuiltins :: BuiltinsState,
_artifactResolver :: ResolverState
}
makeLenses ''Artifacts

View File

@ -5,41 +5,45 @@ module Juvix.Compiler.Pipeline.EntryPoint
where
import Juvix.Compiler.Pipeline.Package
import Juvix.Extra.Paths (juvixStdlibDir)
import Juvix.Prelude
-- | The head of _entryModulePaths is assumed to be the Main module
data EntryPoint = EntryPoint
{ _entryPointRoot :: FilePath,
{ _entryPointRoot :: Path Abs Dir,
-- | initial root for the path resolver. Usually it should be equal to
-- _entryPointRoot. Used only for the command `juvix repl`.
_entryPointResolverRoot :: Path Abs Dir,
_entryPointNoTermination :: Bool,
_entryPointNoPositivity :: Bool,
_entryPointNoStdlib :: Bool,
_entryPointStdlibPath :: Maybe FilePath,
_entryPointPackage :: Package,
_entryPointStdin :: Maybe Text,
_entryPointGenericOptions :: GenericOptions,
_entryPointModulePaths :: NonEmpty FilePath
_entryPointModulePaths :: NonEmpty (Path Abs File)
}
deriving stock (Eq, Show)
defaultStdlibPath :: FilePath -> FilePath
defaultStdlibPath root = root </> juvixStdlibDir
makeLenses ''EntryPoint
defaultEntryPoint :: FilePath -> EntryPoint
defaultEntryPoint mainFile =
defaultEntryPoint :: Path Abs Dir -> Path Abs File -> EntryPoint
defaultEntryPoint root mainFile =
EntryPoint
{ _entryPointRoot = ".",
{ _entryPointRoot = root,
_entryPointResolverRoot = root,
_entryPointNoTermination = False,
_entryPointNoPositivity = False,
_entryPointNoStdlib = False,
_entryPointStdlibPath = Nothing,
_entryPointStdin = Nothing,
_entryPointPackage = emptyPackage,
_entryPointPackage = defaultPackage root,
_entryPointGenericOptions = defaultGenericOptions,
_entryPointModulePaths = pure mainFile
}
makeLenses ''EntryPoint
mainModulePath :: Lens' EntryPoint FilePath
mainModulePath :: Lens' EntryPoint (Path Abs File)
mainModulePath = entryPointModulePaths . _head1
entryPointFromPackage :: Path Abs Dir -> Path Abs File -> Package -> EntryPoint
entryPointFromPackage root mainFile pkg =
(defaultEntryPoint root mainFile)
{ _entryPointPackage = pkg
}

View File

@ -1,40 +1,153 @@
module Juvix.Compiler.Pipeline.Package where
module Juvix.Compiler.Pipeline.Package
( module Juvix.Compiler.Pipeline.Package.Dependency,
RawPackage,
Package,
Package' (..),
defaultPackage,
packageName,
packageVersion,
packageDependencies,
rawPackage,
emptyPackage,
readPackage,
readPackageIO,
)
where
import Data.Aeson (genericToEncoding)
import Data.Aeson.BetterErrors
import Data.Aeson.TH
import Data.Kind qualified as GHC
import Data.Versions
import Data.Yaml
import Juvix.Compiler.Pipeline.Package.Dependency
import Juvix.Extra.Paths
import Juvix.Prelude
import Lens.Micro.Platform qualified as Lens
data Package = Package
{ _packageName :: Maybe Text,
_packageVersion :: Maybe Text
type NameType :: IsProcessed -> GHC.Type
type family NameType s = res | res -> s where
NameType 'Raw = Maybe Text
NameType 'Processed = Text
type VersionType :: IsProcessed -> GHC.Type
type family VersionType s = res | res -> s where
VersionType 'Raw = Maybe Text
VersionType 'Processed = Versioning
type DependenciesType :: IsProcessed -> GHC.Type
type family DependenciesType s = res | res -> s where
DependenciesType 'Raw = Maybe [RawDependency]
DependenciesType 'Processed = [Dependency]
data Package' (s :: IsProcessed) = Package
{ _packageName :: NameType s,
_packageVersion :: VersionType s,
_packageDependencies :: DependenciesType s
}
deriving stock (Eq, Show, Generic)
deriving stock (Generic)
$( deriveJSON
defaultOptions
{ fieldLabelModifier = over Lens._head toLower . dropPrefix "_package",
rejectUnknownFields = True
}
''Package
)
makeLenses ''Package'
type Package = Package' 'Processed
type RawPackage = Package' 'Raw
deriving stock instance Eq RawPackage
deriving stock instance Eq Package
deriving stock instance Show RawPackage
deriving stock instance Show Package
instance ToJSON RawPackage where
toEncoding = genericToEncoding options
where
options :: Options
options =
defaultOptions
{ fieldLabelModifier = over Lens._head toLower . dropPrefix "_package",
rejectUnknownFields = True
}
-- | TODO: is it a good idea to return the empty package if it fails to parse?
instance FromJSON RawPackage where
parseJSON = toAesonParser' (fromMaybe rawDefaultPackage <$> p)
where
p :: Parse' (Maybe RawPackage)
p = perhaps $ do
_packageName <- keyMay "name" asText
_packageVersion <- keyMay "version" asText
_packageDependencies <- keyMay "dependencies" fromAesonParser
return Package {..}
-- | Has the implicit stdlib dependency
rawDefaultPackage :: Package' 'Raw
rawDefaultPackage =
Package
{ _packageName = Nothing,
_packageVersion = Nothing,
_packageDependencies = Nothing
}
-- | Has the implicit stdlib dependency
defaultPackage :: Path Abs Dir -> Package
defaultPackage r = fromRight impossible . run . runError @Text . processPackage r $ rawDefaultPackage
-- | Has no dependencies
emptyPackage :: Package
emptyPackage =
Package
{ _packageName = Nothing,
_packageVersion = Nothing
{ _packageName = defaultPackageName,
_packageVersion = Ideal defaultVersion,
_packageDependencies = []
}
makeLenses ''Package
rawPackage :: Package -> RawPackage
rawPackage pkg =
Package
{ _packageName = Just (pkg ^. packageName),
_packageVersion = Just (prettyV (pkg ^. packageVersion)),
_packageDependencies = Just (map rawDependency (pkg ^. packageDependencies))
}
packageName' :: Getting r Package Text
packageName' f p = (\(Const r) -> Const r) (f name)
processPackage :: forall r. Members '[Error Text] r => Path Abs Dir -> Package' 'Raw -> Sem r Package
processPackage dir pkg = do
let _packageName = fromMaybe defaultPackageName (pkg ^. packageName)
_packageDependencies =
let rawDeps :: [RawDependency] = fromMaybe [stdlibDefaultDep] (pkg ^. packageDependencies)
in map (processDependency dir) rawDeps
_packageVersion <- getVersion
return Package {..}
where
name :: Text
name = fromMaybe "my-package" (p ^. packageName)
getVersion :: Sem r Versioning
getVersion = case pkg ^. packageVersion of
Nothing -> return (Ideal defaultVersion)
Just ver -> case versioning ver of
Right v -> return v
Left err -> throw (pack (errorBundlePretty err))
packageVersion' :: Getting r Package Text
packageVersion' f p = (\(Const r) -> Const r) (f name)
defaultPackageName :: Text
defaultPackageName = "my-project"
defaultVersion :: SemVer
defaultVersion = SemVer 0 0 0 [] Nothing
-- | given some directory d it tries to read the file d/juvix.yaml and parse its contents
readPackage :: forall r. Members '[Files, Error Text] r => Path Abs Dir -> Sem r Package
readPackage adir = do
bs <- readFileBS' yamlPath
either (throw . pack . prettyPrintParseException) (processPackage adir) (decodeEither' bs)
where
name :: Text
name = fromMaybe "no version" (p ^. packageVersion)
yamlPath = adir <//> juvixYamlFile
readPackageIO :: Path Abs Dir -> IO Package
readPackageIO dir = do
let x :: Sem '[Error Text, Files, Embed IO] Package
x = readPackage dir
m <- runM $ runError $ runFilesIO (runError x)
case m of
Left err -> runM (runReader defaultGenericOptions (printErrorAnsiSafe err)) >> exitFailure
Right (Left err) -> putStrLn err >> exitFailure
Right (Right r) -> return r

View File

@ -0,0 +1,65 @@
module Juvix.Compiler.Pipeline.Package.Dependency
( RawDependency,
Dependency,
Dependency' (..),
processDependency,
stdlibDefaultDep,
dependencyPath,
rawDependency,
)
where
-- import Lens.Micro.Platform qualified as Lens
import Data.Aeson.BetterErrors
import Data.Kind qualified as GHC
import Data.Yaml
import Juvix.Extra.Paths
import Juvix.Prelude
type PathType :: IsProcessed -> GHC.Type
type family PathType s = res | res -> s where
PathType 'Raw = SomeBase Dir
PathType 'Processed = Path Abs Dir
-- | dependencies paths are canonicalized just after reading the package
newtype Dependency' (s :: IsProcessed) = Dependency
{ _dependencyPath :: PathType s
}
deriving stock (Generic)
type RawDependency = Dependency' 'Raw
type Dependency = Dependency' 'Processed
deriving stock instance Eq RawDependency
deriving stock instance Eq Dependency
deriving stock instance Show RawDependency
deriving stock instance Show Dependency
instance ToJSON RawDependency where
toEncoding (Dependency p) = toEncoding (fromSomeDir p)
instance FromJSON RawDependency where
parseJSON = toAesonParser id (Dependency <$> p)
where
p :: Parse Text (SomeBase Dir)
p = do
str <- asString
let dir = parseSomeDir str
maybe (throwCustomError ("failed to parse directory: " <> pack str)) pure dir
rawDependency :: Dependency -> RawDependency
rawDependency (Dependency p) = Dependency (Abs p)
processDependency :: Path Abs Dir -> RawDependency -> Dependency
processDependency r (Dependency p) = case p of
Rel a -> Dependency (r <//> a)
Abs a -> Dependency a
stdlibDefaultDep :: RawDependency
stdlibDefaultDep = Dependency (Rel juvixStdlibDir)
makeLenses ''Dependency'

View File

@ -1,25 +1,34 @@
module Juvix.Compiler.Pipeline.Setup where
import Juvix.Compiler.Concrete.Translation.FromParsed.Analysis.PathResolver
import Juvix.Compiler.Pipeline.EntryPoint
import Juvix.Extra.Paths
import Juvix.Extra.Stdlib
import Juvix.Prelude
entrySetup ::
Members '[Reader EntryPoint, Files] r =>
Members '[Reader EntryPoint, Files, PathResolver] r =>
Sem r EntryPoint
entrySetup = do
e <- ask
unless (e ^. entryPointNoStdlib) setupStdlib
registerDependencies
return e
setupStdlib ::
Members '[Reader EntryPoint, Files] r =>
Sem r ()
setupStdlib = do
registerDependencies :: Members '[Reader EntryPoint, PathResolver] r => Sem r ()
registerDependencies = do
e <- ask
stdlibRootPath <- case e ^. entryPointStdlibPath of
Nothing -> do
let d = defaultStdlibPath (e ^. entryPointRoot)
updateStdlib d
addDependency (Just e) (Dependency (e ^. entryPointRoot))
stdlibDep ::
forall r.
Members '[Reader EntryPoint, Files, PathResolver] r =>
Sem r Dependency
stdlibDep = Dependency <$> getRoot
where
getRoot :: Sem r (Path Abs Dir)
getRoot = do
e <- ask
let d :: Path Abs Dir
d = defaultStdlibPath (e ^. entryPointRoot)
runReader d updateStdlib
return d
Just p -> return p
registerStdlib stdlibRootPath

View File

@ -8,6 +8,8 @@ module Juvix.Data
module Juvix.Data.IsImplicit,
module Juvix.Data.Loc,
module Juvix.Data.NameId,
module Juvix.Data.Processed,
module Juvix.Data.Uid,
module Juvix.Data.Universe,
module Juvix.Data.Usage,
module Juvix.Data.Wildcard,
@ -26,6 +28,8 @@ import Juvix.Data.Hole
import Juvix.Data.IsImplicit
import Juvix.Data.Loc
import Juvix.Data.NameId qualified
import Juvix.Data.Processed
import Juvix.Data.Uid
import Juvix.Data.Universe
import Juvix.Data.Usage
import Juvix.Data.Wildcard

View File

@ -42,6 +42,13 @@ stylize a = case a of
AnnDef {} -> mempty
AnnRef {} -> mempty
class PrettyCodeAnn a where
ppCodeAnn :: a -> Doc CodeAnn
instance HasAnsiBackend (Doc CodeAnn) where
toAnsiDoc = fmap stylize
toAnsiStream = fmap stylize . layoutPretty defaultLayoutOptions
-- | for builtin stuff
primitive :: Text -> Doc Ann
primitive = annotate (AnnKind KNameAxiom) . pretty

View File

@ -3,10 +3,63 @@ module Juvix.Data.Effect.Files
module Juvix.Data.Effect.Files.Base,
module Juvix.Data.Effect.Files.Pure,
module Juvix.Data.Effect.Files.IO,
module Juvix.Data.Effect.Files,
)
where
import Data.HashSet qualified as HashSet
import Juvix.Data.Effect.Files.Base
import Juvix.Data.Effect.Files.Error
import Juvix.Data.Effect.Files.IO
import Juvix.Data.Effect.Files.Pure (runFilesPure)
import Juvix.Prelude.Base
import Juvix.Prelude.Path
-- | for now we only check for string equality
equalPaths :: Path Abs File -> Path Abs File -> Sem r Bool
equalPaths a b = return (a == b)
walkDirRelAccum ::
forall acc r.
Member Files r =>
(Path Abs Dir -> [Path Rel Dir] -> [Path Rel File] -> acc -> Sem r (acc, Recurse Rel)) ->
Path Abs Dir ->
acc ->
Sem r acc
walkDirRelAccum handler topdir' ini = execState ini (walkDirRel helper topdir')
where
helper :: Path Abs Dir -> [Path Rel Dir] -> [Path Rel File] -> Sem (State acc ': r) (Recurse Rel)
helper cd dirs files = do
(acc', r) <- get >>= raise . handler cd dirs files
put acc'
return r
walkDirRel ::
forall r.
Member Files r =>
(Path Abs Dir -> [Path Rel Dir] -> [Path Rel File] -> Sem r (Recurse Rel)) ->
Path Abs Dir ->
Sem r ()
walkDirRel handler topdir = do
let walkAvoidLoop :: Path Rel Dir -> Sem (State (HashSet Uid) ': r) ()
walkAvoidLoop curdir =
unlessM (checkLoop (topdir <//> curdir)) $
walktree curdir
walktree :: Path Rel Dir -> Sem (State (HashSet Uid) ': r) ()
walktree curdir = do
let fullDir :: Path Abs Dir = topdir <//> curdir
(subdirs, files) <- listDirRel fullDir
action <- raise (handler fullDir subdirs files)
case action of
RecurseNever -> return ()
RecurseFilter fi ->
let ds = map (curdir <//>) (filter fi subdirs)
in mapM_ walkAvoidLoop ds
checkLoop :: Path Abs Dir -> Sem (State (HashSet Uid) ': r) Bool
checkLoop dir = do
visited <- get
ufid <- pathUid dir
if
| HashSet.member ufid visited -> return True
| otherwise -> modify' (HashSet.insert ufid) $> False
evalState mempty (walkAvoidLoop $(mkRelDir "."))

View File

@ -1,10 +1,12 @@
module Juvix.Data.Effect.Files.Base
( module Juvix.Data.Effect.Files.Base,
module Juvix.Data.Effect.Files.Error,
module Juvix.Data.Uid,
)
where
import Juvix.Data.Effect.Files.Error
import Juvix.Data.Uid
import Juvix.Prelude.Base
import Path
@ -14,31 +16,23 @@ data RecursorArgs = RecursorArgs
_recFiles :: [Path Rel File]
}
data Recurse r
= RecurseNever
| RecurseFilter (Path r Dir -> Bool)
makeLenses ''RecursorArgs
data Files m a where
ReadFile' :: FilePath -> Files m Text
ReadFileBS' :: FilePath -> Files m ByteString
FileExists' :: FilePath -> Files m Bool
EqualPaths' :: FilePath -> FilePath -> Files m (Maybe Bool)
GetAbsPath :: FilePath -> Files m FilePath
CanonicalizePath' :: FilePath -> Files m FilePath
RegisterStdlib :: FilePath -> Files m ()
UpdateStdlib :: FilePath -> Files m ()
EnsureDir' :: Path Abs Dir -> Files m ()
DirectoryExists' :: Path Abs Dir -> Files m Bool
FileExists' :: Path Abs File -> Files m Bool
GetDirAbsPath :: Path Rel Dir -> Files m (Path Abs Dir)
ListDirRel :: Path Abs Dir -> Files m ([Path Rel Dir], [Path Rel File])
PathUid :: Path Abs b -> Files m Uid
ReadFile' :: Path Abs File -> Files m Text
ReadFileBS' :: Path Abs File -> Files m ByteString
RemoveDirectoryRecursive' :: Path Abs Dir -> Files m ()
WriteFile' :: Path Abs File -> Text -> Files m ()
WriteFileBS :: Path Abs File -> ByteString -> Files m ()
makeSem ''Files
data StdlibState = StdlibState
{ _stdlibRoot :: FilePath,
_stdlibFilePaths :: HashSet FilePath
}
newtype FilesState = FilesState
{ _stdlibState :: Maybe StdlibState
}
makeLenses ''FilesState
makeLenses ''StdlibState
initState :: FilesState
initState = FilesState Nothing

View File

@ -5,7 +5,8 @@ import Juvix.Data.Loc
import Juvix.Prelude.Base
import Juvix.Prelude.Pretty
data FilesErrorCause = StdlibConflict
data FilesErrorCause
= StdlibConflict
deriving stock (Show)
data FilesError = FilesError

View File

@ -5,89 +5,34 @@ module Juvix.Data.Effect.Files.IO
where
import Data.ByteString qualified as ByteString
import Data.HashSet qualified as HashSet
import Juvix.Data.Effect.Files.Base
import Juvix.Extra.Stdlib qualified as Stdlib
import Juvix.Prelude.Base
import Juvix.Prelude.Path
import Path.IO qualified as Path
import System.Posix.Types qualified as P
import System.PosixCompat.Files qualified as P
-- | Try to resolve a filepath `p` to a path within standard library.
--
-- When `p` is not a member of the standard library or no stanard library is
-- registered, resolve `p` to an absolute path based at `rootPath` instead.
--
-- This function throws an error if `p` is a member of the standard library and
-- also present within `rootPath`.
stdlibOrFile ::
forall r.
Members '[Embed IO, Error FilesError] r =>
FilePath ->
FilePath ->
Maybe StdlibState ->
Sem r FilePath
stdlibOrFile p rootPath m = case m of
Nothing -> return (rootPath </> p)
Just s
| HashSet.member (normalise p) (s ^. stdlibFilePaths) ->
ifM
isConflict
( throw
FilesError
{ _filesErrorPath = pAbsPath,
_filesErrorCause = StdlibConflict
}
)
(return (s ^. stdlibRoot </> p))
| otherwise -> return pAbsPath
where
pAbsPath :: FilePath
pAbsPath = rootPath </> p
isConflict :: Sem r Bool
isConflict = do
cRootPath <- embed (canonicalizePath rootPath)
cStdlibPath <- embed (canonicalizePath (s ^. stdlibRoot))
andM [return (cRootPath /= cStdlibPath), embed (doesFileExist pAbsPath)]
runFilesIO' ::
forall r a r'.
(r' ~ Embed IO : State FilesState ': Error FilesError ': r) =>
FilePath ->
runFilesIO ::
forall r a.
Member (Embed IO) r =>
Sem (Files ': r) a ->
Sem r' a
runFilesIO' rootPath = reinterpret3 helper
Sem (Error FilesError ': r) a
runFilesIO = reinterpret helper
where
helper :: forall rInitial x. Files (Sem rInitial) x -> Sem r' x
helper :: forall rInitial x. Files (Sem rInitial) x -> Sem (Error FilesError ': r) x
helper = \case
ReadFile' f -> embed (readFile f)
ReadFileBS' f -> embed (ByteString.readFile f)
FileExists' f -> embed (doesFileExist f)
EqualPaths' f h ->
embed
( do
f' <- canonicalizePath f
h' <- canonicalizePath h
return (Just (equalFilePath f' h'))
)
RegisterStdlib stdlibRootPath -> do
absStdlibRootPath <- embed (makeAbsolute stdlibRootPath)
fs <- embed (getFilesRecursive absStdlibRootPath)
let paths = normalise . makeRelative absStdlibRootPath <$> fs
modify
( set
stdlibState
( Just
StdlibState
{ _stdlibRoot = absStdlibRootPath,
_stdlibFilePaths = HashSet.fromList paths
}
)
)
UpdateStdlib p -> runReader p Stdlib.updateStdlib
CanonicalizePath' f -> embed (canonicalizePath f)
GetAbsPath f -> do
s <- gets (^. stdlibState)
p <- stdlibOrFile f rootPath s
embed (canonicalizePath p)
runFilesIO :: Member (Embed IO) r => FilePath -> Sem (Files ': r) a -> Sem (Error FilesError ': r) a
runFilesIO rootPath = evalState initState . subsume . runFilesIO' rootPath
ReadFile' f -> embed (readFile (toFilePath f))
WriteFileBS p bs -> embed (ByteString.writeFile (toFilePath p) bs)
WriteFile' f txt -> embed (writeFile (toFilePath f) txt)
EnsureDir' p -> Path.ensureDir p
DirectoryExists' p -> Path.doesDirExist p
ReadFileBS' f -> embed (ByteString.readFile (toFilePath f))
FileExists' f -> Path.doesFileExist f
RemoveDirectoryRecursive' d -> removeDirRecur d
ListDirRel p -> embed @IO (Path.listDirRel p)
PathUid f -> do
status <- embed (P.getFileStatus (toFilePath f))
let P.CDev dev = P.deviceID status
P.CIno fid = P.fileID status
return (Uid (dev, fid))
GetDirAbsPath f -> canonicalizePath f

View File

@ -8,7 +8,7 @@ import Juvix.Prelude.Path
import Prelude qualified
data FS = FS
{ _fsRoot :: Path Rel Dir,
{ _fsRoot :: Path Abs Dir,
_fsNode :: FSNode
}
@ -17,6 +17,13 @@ data FSNode = FSNode
_dirDirs :: HashMap (Path Rel Dir) FSNode
}
emptyFS :: FS
emptyFS =
FS
{ _fsRoot = $(mkAbsDir "/"),
_fsNode = emptyNode
}
emptyNode :: FSNode
emptyNode =
FSNode
@ -27,25 +34,13 @@ emptyNode =
makeLenses ''FS
makeLenses ''FSNode
-- | Files should have a common root
mkFS :: HashMap FilePath Text -> FS
mkFS tbl = case (HashMap.toList (rootNode ^. dirDirs), toList (rootNode ^. dirFiles)) of
([(r, d')], []) -> FS r d'
_ -> impossible
mkFS :: HashMap (Path Abs File) Text -> FS
mkFS tbl = run (execState emptyFS go)
where
rootNode :: FSNode
rootNode = HashMap.foldlWithKey' insertFile emptyNode tbl
insertFile :: FSNode -> FilePath -> Text -> FSNode
insertFile dir0 fp contents = go (splitPath fp) dir0
where
go :: [FilePath] -> FSNode -> FSNode
go l dir = case l of
[] -> impossible
[f] -> set (dirFiles . at (relFile f)) (Just contents) dir
(d : ds) -> over dirDirs (HashMap.alter (Just . helper) (relDir d)) dir
where
helper :: Maybe FSNode -> FSNode
helper = maybe (helper (Just emptyNode)) (go ds)
go :: Sem '[State FS] ()
go = forM_ (HashMap.toList tbl) $ \(p, txt) -> do
ensureDirHelper (parent p)
writeFileHelper p txt
toTree :: FS -> Tree FilePath
toTree fs = Node (toFilePath (fs ^. fsRoot)) (go (fs ^. fsNode))
@ -62,27 +57,34 @@ toTree fs = Node (toFilePath (fs ^. fsRoot)) (go (fs ^. fsNode))
instance Show FS where
show = drawTree . toTree
runFilesEmpty :: FilePath -> Sem (Files ': r) a -> Sem r a
runFilesEmpty rootPath = runFilesPure rootPath mempty
runFilesEmpty :: Sem (Files ': r) a -> Sem r a
runFilesEmpty = runFilesPure mempty $(mkAbsDir "/")
runFilesPure :: FilePath -> HashMap FilePath Text -> Sem (Files ': r) a -> Sem r a
runFilesPure rootPath fs = interpret $ \case
ReadFile' f -> return (readHelper f)
EqualPaths' {} -> return Nothing
FileExists' f -> return (HashMap.member f fs)
RegisterStdlib {} -> return ()
UpdateStdlib {} -> return ()
GetAbsPath f -> return (rootPath </> f)
CanonicalizePath' f -> return (normalise (rootPath </> f))
ReadFileBS' f -> return (encodeUtf8 (readHelper f))
runFilesPure :: HashMap (Path Abs File) Text -> Path Abs Dir -> Sem (Files ': r) a -> Sem r a
runFilesPure ini cwd a = evalState (mkFS ini) (re cwd a)
re :: Path Abs Dir -> Sem (Files ': r) a -> Sem (State FS ': r) a
re cwd = reinterpret $ \case
ReadFile' f -> lookupFile' f
FileExists' f -> isJust <$> lookupFile f
PathUid p -> return (Uid (toFilePath p))
ReadFileBS' f -> encodeUtf8 <$> lookupFile' f
GetDirAbsPath p -> return (absDir (cwd' </> toFilePath p))
EnsureDir' p -> ensureDirHelper p
DirectoryExists' p -> isJust <$> lookupDir p
WriteFile' p t -> writeFileHelper p t
WriteFileBS p t -> writeFileHelper p (decodeUtf8 t)
RemoveDirectoryRecursive' p -> removeDirRecurHelper p
ListDirRel p -> do
n <- lookupDir' p
return (HashMap.keys (n ^. dirDirs), HashMap.keys (n ^. dirFiles))
where
root :: FS
root = mkFS fs
readHelper :: FilePath -> Text
readHelper f = fromMaybe (missingErr root f) (HashMap.lookup f fs)
cwd' :: FilePath
cwd' = toFilePath cwd
missingErr :: FS -> FilePath -> a
missingErr root f =
missingErr :: Members '[State FS] r => FilePath -> Sem r a
missingErr f = do
root <- get @FS
error $
pack $
"file "
@ -91,37 +93,79 @@ missingErr root f =
<> "\nThe contents of the mocked file system are:\n"
<> Prelude.show root
findDir :: FS -> Path Rel Dir -> FSNode
findDir root p = go (root ^. fsNode) (destructPath p)
where
go :: FSNode -> [Path Rel Dir] -> FSNode
go d = \case
[] -> d
(h : hs) -> go (HashMap.lookupDefault err h (d ^. dirDirs)) hs
err :: a
err = missingErr root (toFilePath p)
checkRoot :: Members '[State FS] r => Path Abs Dir -> Sem r ()
checkRoot r = do
root <- gets (^. fsRoot)
unless True (error ("roots do not match: " <> pack (toFilePath root) <> "\n" <> pack (toFilePath r)))
walkDirRel' ::
forall m.
Monad m =>
FS ->
(RecursorArgs -> m (Path Rel Dir -> Bool)) ->
Maybe (Path Rel Dir) ->
m ()
walkDirRel' root f start = go (root ^. fsRoot) fs0
removeDirRecurHelper :: Members '[State FS] r => Path Abs Dir -> Sem r ()
removeDirRecurHelper p = do
checkRoot r
modify (over fsNode (fromMaybe emptyNode . go dirs))
where
fs0 :: FSNode
fs0 = maybe (root ^. fsNode) (findDir root) start
go :: Path Rel Dir -> FSNode -> m ()
go cur (FSNode files dirs) = do
w <- f args
let dirs' = filter (w . fst) (HashMap.toList dirs)
forM_ dirs' $ \(d, n) -> go (cur <//> d) n
where
args :: RecursorArgs
args =
RecursorArgs
{ _recCurDir = cur,
_recFiles = HashMap.keys files,
_recDirs = HashMap.keys dirs
}
(r, dirs) = destructAbsDir p
go :: [Path Rel Dir] -> FSNode -> Maybe FSNode
go = \case
[] -> const Nothing
(d : ds) -> Just . over dirDirs (HashMap.alter helper d)
where
helper :: Maybe FSNode -> Maybe FSNode
helper = go ds . fromMaybe emptyNode
ensureDirHelper :: Members '[State FS] r => Path Abs Dir -> Sem r ()
ensureDirHelper p = do
checkRoot r
modify (over fsNode (go dirs))
where
(r, dirs) = destructAbsDir p
go :: [Path Rel Dir] -> FSNode -> FSNode
go = \case
[] -> id
(d : ds) -> over dirDirs (HashMap.alter (Just . helper) d)
where
helper :: Maybe FSNode -> FSNode
helper = go ds . fromMaybe emptyNode
writeFileHelper :: Members '[State FS] r => Path Abs File -> Text -> Sem r ()
writeFileHelper p contents = do
checkRoot r
modify (over fsNode (go dirs))
where
(r, dirs, f) = destructAbsFile p
go :: [Path Rel Dir] -> FSNode -> FSNode
go = \case
[] -> set (dirFiles . at f) (Just contents)
(d : ds) -> over dirDirs (HashMap.alter (Just . helper) d)
where
helper :: Maybe FSNode -> FSNode
helper = maybe (error "directory does not exist") (go ds)
lookupDir :: Members '[State FS] r => Path Abs Dir -> Sem r (Maybe FSNode)
lookupDir p = do
checkRoot p
r <- gets (^. fsNode)
return (go r (snd (destructAbsDir p)))
where
go :: FSNode -> [Path Rel Dir] -> Maybe FSNode
go d = \case
[] -> return d
(h : hs) -> do
d' <- HashMap.lookup h (d ^. dirDirs)
go d' hs
lookupDir' :: forall r. Members '[State FS] r => Path Abs Dir -> Sem r FSNode
lookupDir' p = fromMaybeM err (lookupDir p)
where
err :: Sem r FSNode
err = missingErr (toFilePath p)
lookupFile :: Members '[State FS] r => Path Abs File -> Sem r (Maybe Text)
lookupFile p = do
node <- lookupDir (parent p)
return (node >>= HashMap.lookup (filename p) . (^. dirFiles))
lookupFile' :: Members '[State FS] r => Path Abs File -> Sem r Text
lookupFile' p =
fromMaybeM err (lookupFile p)
where
err = missingErr (toFilePath p)

View File

@ -0,0 +1,10 @@
module Juvix.Data.Processed where
import Juvix.Prelude.Base
data IsProcessed
= Raw
| Processed
deriving stock (Eq, Show)
$(genSingletons [''IsProcessed])

19
src/Juvix/Data/Uid.hs Normal file
View File

@ -0,0 +1,19 @@
module Juvix.Data.Uid where
import Data.Type.Equality
import Juvix.Prelude.Base
import Type.Reflection qualified as R
data Uid = forall a.
(Typeable a, Hashable a, Eq a) =>
Uid
{ _fileUid :: a
}
instance Eq Uid where
Uid a == Uid b = case testEquality (R.typeOf a) (R.typeOf b) of
Nothing -> False
Just Refl -> a == b
instance Hashable Uid where
hashWithSalt s (Uid u) = hashWithSalt s u

View File

@ -1,23 +1,24 @@
module Juvix.Extra.Paths where
module Juvix.Extra.Paths
( module Juvix.Extra.Paths,
module Juvix.Extra.Paths.Base,
)
where
import Data.FileEmbed qualified as FE
import Juvix.Extra.Paths.Base
import Juvix.Prelude.Base
import Juvix.Prelude.Path
import Language.Haskell.TH.Syntax
assetsDir :: Q Exp
assetsDir = FE.makeRelativeToProject "assets" >>= FE.embedDir
relToProject :: Path Rel a -> Path Abs a
relToProject r = $(projectPath) <//> r
stdlibDir :: Q Exp
stdlibDir = FE.makeRelativeToProject "juvix-stdlib" >>= FE.embedDir
assetsDir :: [(Path Rel File, ByteString)]
assetsDir = map (first relFile) $(assetsDirQ)
juvixYamlFile :: FilePath
juvixYamlFile = "juvix.yaml"
juvixBuildDir :: FilePath
juvixBuildDir = ".juvix-build"
juvixStdlibDir :: FilePath
juvixStdlibDir = juvixBuildDir </> "stdlib"
preludePath :: FilePath
preludePath = "Stdlib" </> "Prelude.juvix"
-- | Given a relative file from the root of the project, checks that the file
-- exists and returns the absolute path
mkProjFile :: Path Rel File -> Q Exp
mkProjFile r = do
let p = relToProject r
ensureFile p
lift p

View File

@ -0,0 +1,35 @@
module Juvix.Extra.Paths.Base where
import Data.FileEmbed qualified as FE
import Juvix.Prelude
import Language.Haskell.TH.Syntax
assetsDirQ :: Q Exp
assetsDirQ = FE.makeRelativeToProject "assets" >>= FE.embedDir
projectFilePath :: Q Exp
projectFilePath = FE.makeRelativeToProject "." >>= lift
projectPath :: Q Exp
projectPath = FE.makeRelativeToProject "." >>= lift . absDir
stdlibDir :: Q Exp
stdlibDir = FE.makeRelativeToProject "juvix-stdlib" >>= FE.embedDir
juvixYamlFile :: Path Rel File
juvixYamlFile = $(mkRelFile "juvix.yaml")
juvixBuildDir :: Path Rel Dir
juvixBuildDir = $(mkRelDir ".juvix-build")
juvixIncludeDir :: Path Rel Dir
juvixIncludeDir = juvixBuildDir <//> $(mkRelDir "include")
juvixStdlibDir :: Path Rel Dir
juvixStdlibDir = juvixBuildDir <//> $(mkRelDir "stdlib")
preludePath :: Path Rel File
preludePath = $(mkRelFile "Stdlib/Prelude.juvix")
defaultStdlibPath :: Path Abs Dir -> Path Abs Dir
defaultStdlibPath root = root <//> juvixStdlibDir

View File

@ -1,49 +1,72 @@
module Juvix.Extra.Stdlib where
import Data.ByteString qualified as BS
import Juvix.Compiler.Pipeline.Package.Dependency
import Juvix.Data.Effect.Files
import Juvix.Extra.Paths
import Juvix.Extra.Version
import Juvix.Prelude.Base
import Juvix.Prelude
type RootPath = FilePath
type StdlibRoot = Path Abs Dir
stdlibFiles :: [(FilePath, ByteString)]
stdlibFiles = filter isJuvixFile $(stdlibDir)
stdlibFiles :: [(Path Rel File, ByteString)]
stdlibFiles = mapMaybe helper $(stdlibDir)
where
isJuvixFile :: (FilePath, ByteString) -> Bool
isJuvixFile (fp, _) = takeExtension fp == ".juvix"
helper :: (FilePath, ByteString) -> Maybe (Path Rel File, ByteString)
helper (fp', bs)
| isStdLibFile fp = Just (fp, bs)
| otherwise = Nothing
where
fp :: Path Rel File
fp = relFile fp'
isStdLibFile :: Path Rel File -> Bool
isStdLibFile = isJuvixFile .||. isYamlFile
isYamlFile :: Path Rel File -> Bool
isYamlFile = (== juvixYamlFile)
writeStdlib :: forall r. Members '[Reader RootPath, Embed IO] r => Sem r ()
ensureStdlib :: Members '[Files] r => Path Abs Dir -> [Dependency] -> Sem r ()
ensureStdlib pkgRoot deps = whenJust (firstJust isStdLib deps) $ \stdlibRoot ->
runReader stdlibRoot updateStdlib
where
isStdLib :: Dependency -> Maybe (Path Abs Dir)
isStdLib dep = do
case stripProperPrefix pkgRoot (dep ^. dependencyPath) of
Just p
| p == juvixStdlibDir -> Just (dep ^. dependencyPath)
_ -> Nothing
writeStdlib :: forall r. Members '[Reader StdlibRoot, Files] r => Sem r ()
writeStdlib = do
rootDir <- ask
forM_ (first (rootDir </>) <$> stdlibFiles) (uncurry writeJuvixFile)
forM_ (first (rootDir <//>) <$> stdlibFiles) (uncurry writeJuvixFile)
where
writeJuvixFile :: FilePath -> ByteString -> Sem r ()
writeJuvixFile p bs = embed (createDirectoryIfMissing True (takeDirectory p) >> BS.writeFile p bs)
writeJuvixFile :: Path Abs File -> ByteString -> Sem r ()
writeJuvixFile p bs = do
ensureDir' (parent p)
writeFileBS p bs
stdlibVersionFile :: Member (Reader RootPath) r => Sem r FilePath
stdlibVersionFile = (</> ".version") <$> ask
stdlibVersionFile :: Member (Reader StdlibRoot) r => Sem r (Path Abs File)
stdlibVersionFile = (<//> $(mkRelFile ".version")) <$> ask
writeVersion :: forall r. Members '[Reader RootPath, Embed IO] r => Sem r ()
writeVersion = (embed . flip writeFile versionTag) =<< stdlibVersionFile
writeVersion :: forall r. Members '[Reader StdlibRoot, Files] r => Sem r ()
writeVersion = stdlibVersionFile >>= flip writeFile' versionTag
readVersion :: Members '[Reader RootPath, Embed IO] r => Sem r (Maybe Text)
readVersion :: Members '[Reader StdlibRoot, Files] r => Sem r (Maybe Text)
readVersion = do
vf <- stdlibVersionFile
embed (whenMaybeM (doesFileExist vf) (readFile vf))
whenMaybeM (fileExists' vf) (readFile' vf)
updateStdlib :: forall r. Members '[Reader RootPath, Embed IO] r => Sem r ()
updateStdlib :: forall r. Members '[Reader StdlibRoot, Files] r => Sem r ()
updateStdlib =
whenM shouldUpdate $ do
whenM
(embed . doesDirectoryExist =<< ask)
(embed . removeDirectoryRecursive =<< ask)
(ask @StdlibRoot >>= directoryExists')
(ask @StdlibRoot >>= removeDirectoryRecursive')
writeStdlib
writeVersion
where
shouldUpdate :: Sem r Bool
shouldUpdate =
orM
[ not <$> (embed . doesDirectoryExist =<< ask),
[ not <$> (ask @StdlibRoot >>= directoryExists'),
(Just versionTag /=) <$> readVersion
]

View File

@ -2,6 +2,7 @@ module Juvix.Prelude
( module Juvix.Prelude.Base,
module Juvix.Prelude.Lens,
module Juvix.Prelude.Trace,
module Juvix.Prelude.Path,
module Juvix.Data,
)
where
@ -9,4 +10,5 @@ where
import Juvix.Data
import Juvix.Prelude.Base
import Juvix.Prelude.Lens
import Juvix.Prelude.Path
import Juvix.Prelude.Trace

View File

@ -0,0 +1,14 @@
module Juvix.Prelude.Aeson
( module Juvix.Prelude.Aeson,
module Data.Aeson,
module Data.Aeson.Text,
)
where
import Data.Aeson
import Data.Aeson.Text
import Data.Text.Lazy qualified as Lazy
import Juvix.Prelude.Base
encodeToText :: ToJSON a => a -> Text
encodeToText = Lazy.toStrict . encodeToLazyText

View File

@ -58,11 +58,11 @@ module Juvix.Prelude.Base
module Polysemy.State,
module Language.Haskell.TH.Syntax,
module Prettyprinter,
module System.Directory,
module System.Exit,
module System.FilePath,
module System.IO,
module Text.Show,
module Control.Monad.Catch,
Data,
Text,
pack,
@ -73,12 +73,15 @@ module Juvix.Prelude.Base
HashSet,
IsString (..),
Alternative (..),
MonadIO (..),
)
where
import Control.Applicative
import Control.Monad.Catch (MonadMask, MonadThrow, throwM)
import Control.Monad.Extra hiding (fail)
import Control.Monad.Fix
import Control.Monad.IO.Class (MonadIO (..))
import Data.Bifunctor hiding (first, second)
import Data.Bitraversable
import Data.Bool
@ -99,7 +102,7 @@ import Data.Hashable
import Data.Int
import Data.IntMap.Strict (IntMap)
import Data.IntSet (IntSet)
import Data.List.Extra hiding (groupSortOn, head, last, mconcatMap)
import Data.List.Extra hiding (allSame, groupSortOn, head, last, mconcatMap)
import Data.List.Extra qualified as List
import Data.List.NonEmpty qualified as NonEmpty
import Data.List.NonEmpty.Extra
@ -141,8 +144,8 @@ import GHC.Real
import GHC.Stack.Types
import Language.Haskell.TH.Syntax (Lift)
import Lens.Micro.Platform hiding (both)
import Path (parseAbsDir, toFilePath)
import Path.IO (listDirRecur)
import Path
import Path.IO qualified as Path
import Polysemy
import Polysemy.Embed
import Polysemy.Error hiding (fromEither)
@ -153,9 +156,8 @@ import Polysemy.State
import Prettyprinter (Doc, (<+>))
import Safe.Exact
import Safe.Foldable
import System.Directory
import System.Exit
import System.FilePath
import System.FilePath (FilePath, dropTrailingPathSeparator, normalise, (<.>), (</>))
import System.IO hiding
( appendFile,
getContents,
@ -165,12 +167,15 @@ import System.IO hiding
hPutStr,
hPutStrLn,
interact,
openBinaryTempFile,
openTempFile,
putStr,
putStrLn,
readFile,
readFile',
writeFile,
)
import System.IO.Error
import Text.Show (Show)
import Text.Show qualified as Show
@ -198,6 +203,14 @@ toUpperFirst (x : xs) = Char.toUpper x : xs
-- Foldable
--------------------------------------------------------------------------------
allSame :: forall t a. (Eq a, Foldable t) => t a -> Bool
allSame t
| null t = True
| otherwise = all (== h) t
where
h :: a
h = foldr1 const t
mconcatMap :: (Monoid c, Foldable t) => (a -> c) -> t a -> c
mconcatMap f = List.mconcatMap f . toList
@ -346,19 +359,6 @@ fromRightIO' pp = do
fromRightIO :: (e -> Text) -> IO (Either e r) -> IO r
fromRightIO pp = fromRightIO' (putStrLn . pp)
--------------------------------------------------------------------------------
-- Files
--------------------------------------------------------------------------------
-- | Recursively get all files in the given directory.
--
-- The function returns absolute paths.
getFilesRecursive :: FilePath -> IO [FilePath]
getFilesRecursive p = do
pathP <- makeAbsolute p >>= parseAbsDir
(_, files) <- listDirRecur pathP
return (toFilePath <$> files)
--------------------------------------------------------------------------------
-- Misc
--------------------------------------------------------------------------------
@ -409,3 +409,9 @@ instance CanonicalProjection a () where
-- | 'project' with type arguments swapped. Useful for type application
project' :: forall b a. CanonicalProjection a b => a -> b
project' = project
ensureFile :: (MonadIO m, MonadThrow m) => Path Abs File -> m ()
ensureFile f =
unlessM
(Path.doesFileExist f)
(throwM (mkIOError doesNotExistErrorType "" Nothing (Just (toFilePath f))))

View File

@ -1,28 +1,90 @@
module Juvix.Prelude.Path
( module Juvix.Prelude.Path,
module Path,
module Path.IO,
)
where
import Data.List.NonEmpty qualified as NonEmpty
import Juvix.Prelude.Base
import Path hiding ((</>))
import Juvix.Prelude.Path.OrphanInstances ()
import Path hiding ((<.>), (</>))
import Path qualified
import Path.IO hiding (listDirRel, walkDirRel)
import Path.Internal
absDir :: FilePath -> Path Abs Dir
absDir r = fromMaybe (error ("not an absolute file path: " <> pack r)) (parseAbsDir r)
-- | Synonym for Path.</>. Useful to avoid name clashes
infixr 5 <//>
-- | Synonym for Path.</>. Useful to avoid name clashes
(<//>) :: Path b Dir -> Path Rel t -> Path b t
(<//>) = (Path.</>)
someFile :: FilePath -> SomeBase File
someFile r = fromMaybe (error ("not a file path: " <> pack r)) (parseSomeFile r)
someDir :: FilePath -> SomeBase Dir
someDir r = fromMaybe (error ("not a dir path: " <> pack r)) (parseSomeDir r)
relFile :: FilePath -> Path Rel File
relFile = fromJust . parseRelFile
relFile r = fromMaybe (error ("not a relative file path: " <> pack r)) (parseRelFile r)
relDir :: FilePath -> Path Rel Dir
relDir = fromJust . parseRelDir
relDir r = fromMaybe (error ("not a relative directory path: " <> pack r)) (parseRelDir r)
destructPath :: Path b Dir -> [Path Rel Dir]
destructPath p = map relDir (splitPath (toFilePath p))
absFile :: FilePath -> Path Abs File
absFile r = fromMaybe (error ("not an absolute file path: " <> pack r)) (parseAbsFile r)
destructFilePath :: Path b File -> ([Path Rel Dir], Path Rel File)
destructFilePath p = case nonEmptyUnsnoc (nonEmpty' (splitPath (toFilePath p))) of
(ps, f) -> (fmap relDir (maybe [] toList ps), relFile f)
destructAbsDir :: Path Abs Dir -> (Path Abs Dir, [Path Rel Dir])
destructAbsDir d = go d []
where
go :: Path Abs Dir -> [Path Rel Dir] -> (Path Abs Dir, [Path Rel Dir])
go p acc
| isRoot p = (p, acc)
| otherwise = go (parent p) (dirname p : acc)
isRoot :: Path a Dir -> Bool
isRoot p = parent p == p
-- | is the root of absolute files always "/" ?
destructAbsFile :: Path Abs File -> (Path Abs Dir, [Path Rel Dir], Path Rel File)
destructAbsFile x = (root, dirs, filename x)
where
(root, dirs) = destructAbsDir (parent x)
isJuvixFile :: Path b File -> Bool
isJuvixFile = (== Just ".juvix") . fileExtension
isHiddenDirectory :: Path b Dir -> Bool
isHiddenDirectory p
| toFilePath p == relRootFP = False
| otherwise = case toFilePath (dirname p) of
'.' : _ -> True
_ -> False
someBaseToAbs :: Path Abs Dir -> SomeBase b -> Path Abs b
someBaseToAbs root = \case
Rel r -> root <//> r
Abs a -> a
removeExtension :: Path b File -> Maybe (Path b File)
removeExtension = fmap fst . splitExtension
removeExtension' :: Path b File -> Path b File
removeExtension' = fst . fromJust . splitExtension
replaceExtension' :: String -> Path b File -> Path b File
replaceExtension' ext = fromJust . replaceExtension ext
parents :: Path Abs a -> NonEmpty (Path Abs Dir)
parents = go [] . parent
where
go :: [Path Abs Dir] -> Path Abs Dir -> NonEmpty (Path Abs Dir)
go ac p
| isRoot p = NonEmpty.reverse (p :| ac)
| otherwise = go (p : ac) (parent p)
withTempDir' :: (MonadIO m, MonadMask m) => (Path Abs Dir -> m a) -> m a
withTempDir' = withSystemTempDir "tmp"

View File

@ -0,0 +1,65 @@
{-# OPTIONS_GHC -Wno-orphans #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Avoid restricted extensions" #-}
{-# HLINT ignore "Avoid restricted flags" #-}
module Juvix.Prelude.Path.OrphanInstances where
import Juvix.Prelude.Base
import Path
import Path.IO
import Prettyprinter
instance Pretty (Path a b) where
pretty = pretty . toFilePath
deriving stock instance Data b => Data (SomeBase b)
instance AnyPath (SomeBase File) where
type AbsPath (SomeBase File) = Path Abs File
type RelPath (SomeBase File) = Path Rel File
canonicalizePath :: MonadIO m => SomeBase File -> m (Path Abs File)
canonicalizePath = \case
Abs a -> canonicalizePath a
Rel a -> canonicalizePath a
makeAbsolute :: MonadIO m => SomeBase File -> m (Path Abs File)
makeAbsolute = \case
Abs a -> makeAbsolute a
Rel a -> makeAbsolute a
makeRelative :: MonadThrow m => Path Abs Dir -> SomeBase File -> m (Path Rel File)
makeRelative r = \case
Abs a -> makeRelative r a
Rel a -> makeRelative r a
makeRelativeToCurrentDir :: MonadIO m => SomeBase File -> m (Path Rel File)
makeRelativeToCurrentDir = \case
Abs a -> makeRelativeToCurrentDir a
Rel a -> makeRelativeToCurrentDir a
instance AnyPath (SomeBase Dir) where
type AbsPath (SomeBase Dir) = Path Abs Dir
type RelPath (SomeBase Dir) = Path Rel Dir
canonicalizePath :: MonadIO m => SomeBase Dir -> m (Path Abs Dir)
canonicalizePath = \case
Abs a -> canonicalizePath a
Rel a -> canonicalizePath a
makeAbsolute :: MonadIO m => SomeBase Dir -> m (Path Abs Dir)
makeAbsolute = \case
Abs a -> makeAbsolute a
Rel a -> makeAbsolute a
makeRelative :: MonadThrow m => Path Abs Dir -> SomeBase Dir -> m (Path Rel Dir)
makeRelative r = \case
Abs a -> makeRelative r a
Rel a -> makeRelative r a
makeRelativeToCurrentDir :: MonadIO m => SomeBase Dir -> m (Path Rel Dir)
makeRelativeToCurrentDir = \case
Abs a -> makeRelativeToCurrentDir a
Rel a -> makeRelativeToCurrentDir a

View File

@ -123,3 +123,6 @@ ordinal = \case
11 -> "eleventh"
12 -> "twelfth"
n -> pretty n <> "th"
itemize :: (Functor f, Foldable f) => f (Doc ann) -> Doc ann
itemize = vsep . fmap ("" <>)

View File

@ -9,19 +9,20 @@ type FailMsg = String
data NegTest = NegTest
{ _name :: String,
_relDir :: FilePath,
_file :: FilePath,
_relDir :: Path Rel Dir,
_file :: Path Rel File,
_checkErr :: ArityCheckerError -> Maybe FailMsg
}
testDescr :: NegTest -> TestDescr
testDescr NegTest {..} =
let tRoot = root </> _relDir
let tRoot = root <//> _relDir
file' = tRoot <//> _file
in TestDescr
{ _testName = _name,
_testRoot = tRoot,
_testAssertion = Single $ do
let entryPoint = defaultEntryPoint _file
let entryPoint = defaultEntryPoint tRoot file'
result <- runIOEither iniState entryPoint upToInternalArity
case mapLeft fromJuvixError result of
Left (Just tyError) -> whenJust (_checkErr tyError) assertFailure
@ -35,8 +36,8 @@ allTests =
"Arity checker negative tests"
(map (mkTest . testDescr) tests)
root :: FilePath
root = "tests/negative"
root :: Path Abs Dir
root = relToProject $(mkRelDir "tests/negative")
wrongError :: Maybe FailMsg
wrongError = Just "Incorrect error"
@ -45,43 +46,43 @@ tests :: [NegTest]
tests =
[ NegTest
"Too many arguments in expression"
"Internal"
"TooManyArguments.juvix"
$(mkRelDir "Internal")
$(mkRelFile "TooManyArguments.juvix")
$ \case
ErrTooManyArguments {} -> Nothing
_ -> wrongError,
NegTest
"Pattern match a function type"
"Internal"
"FunctionPattern.juvix"
$(mkRelDir "Internal")
$(mkRelFile "FunctionPattern.juvix")
$ \case
ErrPatternFunction {} -> Nothing
_ -> wrongError,
NegTest
"Function type (* → *) application"
"Internal"
"FunctionApplied.juvix"
$(mkRelDir "Internal")
$(mkRelFile "FunctionApplied.juvix")
$ \case
ErrFunctionApplied {} -> Nothing
_ -> wrongError,
NegTest
"Expected explicit pattern"
"Internal"
"ExpectedExplicitPattern.juvix"
$(mkRelDir "Internal")
$(mkRelFile "ExpectedExplicitPattern.juvix")
$ \case
ErrWrongPatternIsImplicit {} -> Nothing
_ -> wrongError,
NegTest
"Expected explicit argument"
"Internal"
"ExpectedExplicitArgument.juvix"
$(mkRelDir "Internal")
$(mkRelFile "ExpectedExplicitArgument.juvix")
$ \case
ErrExpectedExplicitArgument {} -> Nothing
_ -> wrongError,
NegTest
"Function clause with two many patterns in the lhs"
"Internal"
"LhsTooManyPatterns.juvix"
$(mkRelDir "Internal")
$(mkRelFile "LhsTooManyPatterns.juvix")
$ \case
ErrLhsTooManyPatterns {} -> Nothing
_ -> wrongError

View File

@ -8,23 +8,22 @@ import Juvix.Compiler.Backend qualified as Backend
import Juvix.Compiler.Backend.C qualified as C
import Juvix.Compiler.Pipeline qualified as Pipeline
import Runtime.Base qualified as Runtime
import System.IO.Extra (withTempDir)
asmCompileAssertion :: FilePath -> FilePath -> (String -> IO ()) -> Assertion
asmCompileAssertion :: Path Abs File -> Path Abs File -> (String -> IO ()) -> Assertion
asmCompileAssertion mainFile expectedFile step = do
step "Parse"
s <- readFile mainFile
case runParser mainFile s of
s <- readFile (toFilePath mainFile)
case runParser (toFilePath mainFile) s of
Left err -> assertFailure (show err)
Right tab -> do
step "Generate C code"
case run $ runError @JuvixError $ Pipeline.asmToMiniC asmOpts tab of
Left {} -> assertFailure "code generation failed"
Right C.MiniCResult {..} ->
withTempDir
withTempDir'
( \dirPath -> do
let cFile = dirPath </> takeBaseName mainFile <> ".c"
TIO.writeFile cFile _resultCCode
let cFile = dirPath <//> replaceExtension' ".c" (filename mainFile)
TIO.writeFile (toFilePath cFile) _resultCCode
Runtime.clangAssertion cFile expectedFile "" step
)
where

View File

@ -6,11 +6,13 @@ import Base
testDescr :: Run.PosTest -> TestDescr
testDescr Run.PosTest {..} =
let tRoot = Run.root </> _relDir
let tRoot = Run.root <//> _relDir
file' = tRoot <//> _file
expected' = tRoot <//> _expectedFile
in TestDescr
{ _testName = _name,
_testRoot = tRoot,
_testAssertion = Steps $ asmCompileAssertion _file _expectedFile
_testAssertion = Steps $ asmCompileAssertion file' expected'
}
allTests :: TestTree

View File

@ -10,9 +10,8 @@ import Juvix.Compiler.Asm.Pretty
import Juvix.Compiler.Asm.Transformation.Validate
import Juvix.Compiler.Asm.Translation.FromSource
import Juvix.Data.PPOutput
import System.IO.Extra (withTempDir)
asmRunAssertion :: FilePath -> FilePath -> (InfoTable -> Either AsmError InfoTable) -> (InfoTable -> Assertion) -> (String -> IO ()) -> Assertion
asmRunAssertion :: Path Abs File -> Path Abs File -> (InfoTable -> Either AsmError InfoTable) -> (InfoTable -> Assertion) -> (String -> IO ()) -> Assertion
asmRunAssertion mainFile expectedFile trans testTrans step = do
step "Parse"
r <- parseFile mainFile
@ -29,10 +28,10 @@ asmRunAssertion mainFile expectedFile trans testTrans step = do
Nothing ->
case tab ^. infoMainFunction of
Just sym -> do
withTempDir
withTempDir'
( \dirPath -> do
let outputFile = dirPath </> "out.out"
hout <- openFile outputFile WriteMode
let outputFile = dirPath <//> $(mkRelFile "out.out")
hout <- openFile (toFilePath outputFile) WriteMode
step "Interpret"
r' <- doRun hout tab (getFunInfo tab sym)
case r' of
@ -44,14 +43,14 @@ asmRunAssertion mainFile expectedFile trans testTrans step = do
ValVoid -> return ()
_ -> hPutStrLn hout (ppPrint tab value')
hClose hout
actualOutput <- TIO.readFile outputFile
actualOutput <- TIO.readFile (toFilePath outputFile)
step "Compare expected and actual program output"
expected <- TIO.readFile expectedFile
assertEqDiff ("Check: RUN output = " <> expectedFile) actualOutput expected
expected <- TIO.readFile (toFilePath expectedFile)
assertEqDiff ("Check: RUN output = " <> toFilePath expectedFile) actualOutput expected
)
Nothing -> assertFailure "no 'main' function"
asmRunErrorAssertion :: FilePath -> (String -> IO ()) -> Assertion
asmRunErrorAssertion :: Path Abs File -> (String -> IO ()) -> Assertion
asmRunErrorAssertion mainFile step = do
step "Parse"
r <- parseFile mainFile
@ -64,10 +63,10 @@ asmRunErrorAssertion mainFile step = do
Nothing ->
case tab ^. infoMainFunction of
Just sym -> do
withTempDir
withTempDir'
( \dirPath -> do
let outputFile = dirPath </> "out.out"
hout <- openFile outputFile WriteMode
let outputFile = dirPath <//> $(mkRelFile "out.out")
hout <- openFile (toFilePath outputFile) WriteMode
step "Interpret"
r' <- doRun hout tab (getFunInfo tab sym)
hClose hout
@ -77,10 +76,11 @@ asmRunErrorAssertion mainFile step = do
)
Nothing -> assertBool "" True
parseFile :: FilePath -> IO (Either ParserError InfoTable)
parseFile :: Path Abs File -> IO (Either ParserError InfoTable)
parseFile f = do
s <- readFile f
return $ runParser f s
let f' = toFilePath f
s <- readFile f'
return $ runParser f' s
doRun ::
Handle ->

View File

@ -5,20 +5,21 @@ import Base
data NegTest = NegTest
{ _name :: String,
_relDir :: FilePath,
_file :: FilePath
_relDir :: Path Rel Dir,
_file :: Path Rel File
}
root :: FilePath
root = "tests/Asm/negative"
root :: Path Abs Dir
root = relToProject $(mkRelDir "tests/Asm/negative")
testDescr :: NegTest -> TestDescr
testDescr NegTest {..} =
let tRoot = root </> _relDir
let tRoot = root <//> _relDir
file' = tRoot <//> _file
in TestDescr
{ _testName = _name,
_testRoot = tRoot,
_testAssertion = Steps $ asmRunErrorAssertion _file
_testAssertion = Steps $ asmRunErrorAssertion file'
}
allTests :: TestTree
@ -31,14 +32,14 @@ tests :: [NegTest]
tests =
[ NegTest
"Division by zero"
"."
"test001.jva",
$(mkRelDir ".")
$(mkRelFile "test001.jva"),
NegTest
"Invalid memory access"
"."
"test002.jva",
$(mkRelDir ".")
$(mkRelFile "test002.jva"),
NegTest
"No matching case branch"
"."
"test003.jva"
$(mkRelDir ".")
$(mkRelFile "test003.jva")
]

View File

@ -5,21 +5,23 @@ import Base
data PosTest = PosTest
{ _name :: String,
_relDir :: FilePath,
_file :: FilePath,
_expectedFile :: FilePath
_relDir :: Path Rel Dir,
_file :: Path Rel File,
_expectedFile :: Path Rel File
}
root :: FilePath
root = "tests/Asm/positive"
root :: Path Abs Dir
root = relToProject $(mkRelDir "tests/Asm/positive")
testDescr :: PosTest -> TestDescr
testDescr PosTest {..} =
let tRoot = root </> _relDir
let tRoot = root <//> _relDir
file' = tRoot <//> _file
expected' = tRoot <//> _expectedFile
in TestDescr
{ _testName = _name,
_testRoot = tRoot,
_testAssertion = Steps $ asmRunAssertion _file _expectedFile return (const (return ()))
_testAssertion = Steps $ asmRunAssertion file' expected' return (const (return ()))
}
allTests :: TestTree
@ -32,182 +34,182 @@ tests :: [PosTest]
tests =
[ PosTest
"Arithmetic opcodes"
"."
"test001.jva"
"out/test001.out",
$(mkRelDir ".")
$(mkRelFile "test001.jva")
$(mkRelFile "out/test001.out"),
PosTest
"Direct call"
"."
"test002.jva"
"out/test002.out",
$(mkRelDir ".")
$(mkRelFile "test002.jva")
$(mkRelFile "out/test002.out"),
PosTest
"Indirect call"
"."
"test003.jva"
"out/test003.out",
$(mkRelDir ".")
$(mkRelFile "test003.jva")
$(mkRelFile "out/test003.out"),
PosTest
"Tail calls"
"."
"test004.jva"
"out/test004.out",
$(mkRelDir ".")
$(mkRelFile "test004.jva")
$(mkRelFile "out/test004.out"),
PosTest
"Tracing IO"
"."
"test005.jva"
"out/test005.out",
$(mkRelDir ".")
$(mkRelFile "test005.jva")
$(mkRelFile "out/test005.out"),
PosTest
"IO builtins"
"."
"test006.jva"
"out/test006.out",
$(mkRelDir ".")
$(mkRelFile "test006.jva")
$(mkRelFile "out/test006.out"),
PosTest
"Higher-order functions"
"."
"test007.jva"
"out/test007.out",
$(mkRelDir ".")
$(mkRelFile "test007.jva")
$(mkRelFile "out/test007.out"),
PosTest
"Branch"
"."
"test008.jva"
"out/test008.out",
$(mkRelDir ".")
$(mkRelFile "test008.jva")
$(mkRelFile "out/test008.out"),
PosTest
"Case"
"."
"test009.jva"
"out/test009.out",
$(mkRelDir ".")
$(mkRelFile "test009.jva")
$(mkRelFile "out/test009.out"),
PosTest
"Recursion"
"."
"test010.jva"
"out/test010.out",
$(mkRelDir ".")
$(mkRelFile "test010.jva")
$(mkRelFile "out/test010.out"),
PosTest
"Tail recursion"
"."
"test011.jva"
"out/test011.out",
$(mkRelDir ".")
$(mkRelFile "test011.jva")
$(mkRelFile "out/test011.out"),
PosTest
"Temporary stack"
"."
"test012.jva"
"out/test012.out",
$(mkRelDir ".")
$(mkRelFile "test012.jva")
$(mkRelFile "out/test012.out"),
PosTest
"Fibonacci numbers in linear time"
"."
"test013.jva"
"out/test013.out",
$(mkRelDir ".")
$(mkRelFile "test013.jva")
$(mkRelFile "out/test013.out"),
PosTest
"Trees"
"."
"test014.jva"
"out/test014.out",
$(mkRelDir ".")
$(mkRelFile "test014.jva")
$(mkRelFile "out/test014.out"),
PosTest
"Functions returning functions"
"."
"test015.jva"
"out/test015.out",
$(mkRelDir ".")
$(mkRelFile "test015.jva")
$(mkRelFile "out/test015.out"),
PosTest
"Arithmetic"
"."
"test016.jva"
"out/test016.out",
$(mkRelDir ".")
$(mkRelFile "test016.jva")
$(mkRelFile "out/test016.out"),
PosTest
"Closures as arguments"
"."
"test017.jva"
"out/test017.out",
$(mkRelDir ".")
$(mkRelFile "test017.jva")
$(mkRelFile "out/test017.out"),
PosTest
"Closure extension"
"."
"test018.jva"
"out/test018.out",
$(mkRelDir ".")
$(mkRelFile "test018.jva")
$(mkRelFile "out/test018.out"),
PosTest
"Recursion through higher-order functions"
"."
"test019.jva"
"out/test019.out",
$(mkRelDir ".")
$(mkRelFile "test019.jva")
$(mkRelFile "out/test019.out"),
PosTest
"Tail recursion through higher-order functions"
"."
"test020.jva"
"out/test020.out",
$(mkRelDir ".")
$(mkRelFile "test020.jva")
$(mkRelFile "out/test020.out"),
PosTest
"Higher-order functions and recursion"
"."
"test021.jva"
"out/test021.out",
$(mkRelDir ".")
$(mkRelFile "test021.jva")
$(mkRelFile "out/test021.out"),
PosTest
"Self-application"
"."
"test022.jva"
"out/test022.out",
$(mkRelDir ".")
$(mkRelFile "test022.jva")
$(mkRelFile "out/test022.out"),
PosTest
"McCarthy's 91 function"
"."
"test023.jva"
"out/test023.out",
$(mkRelDir ".")
$(mkRelFile "test023.jva")
$(mkRelFile "out/test023.out"),
PosTest
"Higher-order recursive functions"
"."
"test024.jva"
"out/test024.out",
$(mkRelDir ".")
$(mkRelFile "test024.jva")
$(mkRelFile "out/test024.out"),
PosTest
"Dynamic closure extension"
"."
"test025.jva"
"out/test025.out",
$(mkRelDir ".")
$(mkRelFile "test025.jva")
$(mkRelFile "out/test025.out"),
PosTest
"Currying & uncurrying"
"."
"test026.jva"
"out/test026.out",
$(mkRelDir ".")
$(mkRelFile "test026.jva")
$(mkRelFile "out/test026.out"),
PosTest
"Fast exponentiation"
"."
"test027.jva"
"out/test027.out",
$(mkRelDir ".")
$(mkRelFile "test027.jva")
$(mkRelFile "out/test027.out"),
PosTest
"Lists"
"."
"test028.jva"
"out/test028.out",
$(mkRelDir ".")
$(mkRelFile "test028.jva")
$(mkRelFile "out/test028.out"),
PosTest
"Structural equality"
"."
"test029.jva"
"out/test029.out",
$(mkRelDir ".")
$(mkRelFile "test029.jva")
$(mkRelFile "out/test029.out"),
PosTest
"Mutual recursion"
"."
"test030.jva"
"out/test030.out",
$(mkRelDir ".")
$(mkRelFile "test030.jva")
$(mkRelFile "out/test030.out"),
PosTest
"Temporary stack with branching"
"."
"test031.jva"
"out/test031.out",
$(mkRelDir ".")
$(mkRelFile "test031.jva")
$(mkRelFile "out/test031.out"),
PosTest
"Church numerals"
"."
"test032.jva"
"out/test032.out",
$(mkRelDir ".")
$(mkRelFile "test032.jva")
$(mkRelFile "out/test032.out"),
PosTest
"Ackermann function"
"."
"test033.jva"
"out/test033.out",
$(mkRelDir ".")
$(mkRelFile "test033.jva")
$(mkRelFile "out/test033.out"),
PosTest
"Higher-order function composition"
"."
"test034.jva"
"out/test034.out",
$(mkRelDir ".")
$(mkRelFile "test034.jva")
$(mkRelFile "out/test034.out"),
PosTest
"Nested lists"
"."
"test035.jva"
"out/test035.out",
$(mkRelDir ".")
$(mkRelFile "test035.jva")
$(mkRelFile "out/test035.out"),
PosTest
"Streams without memoization"
"."
"test036.jva"
"out/test036.out"
$(mkRelDir ".")
$(mkRelFile "test036.jva")
$(mkRelFile "out/test036.out")
]

View File

@ -15,15 +15,17 @@ data Test = Test
fromTest :: Test -> TestTree
fromTest = mkTest . toTestDescr
troot :: FilePath
troot = "tests/Asm/positive/"
troot :: Path Abs Dir
troot = relToProject $(mkRelDir "tests/Asm/positive/")
toTestDescr :: Test -> TestDescr
toTestDescr Test {..} =
let Run.PosTest {..} = _testEval
tRoot = troot </> _relDir
tRoot = troot <//> _relDir
file' = tRoot <//> _file
expected' = tRoot <//> _expectedFile
in TestDescr
{ _testName = _name,
_testRoot = tRoot,
_testAssertion = Steps $ asmRunAssertion _file _expectedFile _testTransformation _testAssertion
_testAssertion = Steps $ asmRunAssertion file' expected' _testTransformation _testAssertion
}

View File

@ -5,7 +5,7 @@ import Juvix.Compiler.Asm.Data.InfoTable
import Juvix.Compiler.Asm.Transformation.Validate
import Juvix.Compiler.Asm.Translation.FromSource
asmValidateErrorAssertion :: FilePath -> (String -> IO ()) -> Assertion
asmValidateErrorAssertion :: Path Abs File -> (String -> IO ()) -> Assertion
asmValidateErrorAssertion mainFile step = do
step "Parse"
r <- parseFile mainFile
@ -17,7 +17,7 @@ asmValidateErrorAssertion mainFile step = do
Just _ -> assertBool "" True
Nothing -> assertFailure "no error"
parseFile :: FilePath -> IO (Either ParserError InfoTable)
parseFile :: Path Abs File -> IO (Either ParserError InfoTable)
parseFile f = do
s <- readFile f
return $ runParser f s
s <- readFile (toFilePath f)
return $ runParser (toFilePath f) s

View File

@ -5,20 +5,21 @@ import Base
data NegTest = NegTest
{ _name :: String,
_relDir :: FilePath,
_file :: FilePath
_relDir :: Path Rel Dir,
_file :: Path Rel File
}
root :: FilePath
root = "tests/Asm/negative"
root :: Path Abs Dir
root = relToProject $(mkRelDir "tests/Asm/negative")
testDescr :: NegTest -> TestDescr
testDescr NegTest {..} =
let tRoot = root </> _relDir
let tRoot = root <//> _relDir
file' = tRoot <//> _file
in TestDescr
{ _testName = _name,
_testRoot = tRoot,
_testAssertion = Steps $ asmValidateErrorAssertion _file
_testAssertion = Steps $ asmValidateErrorAssertion file'
}
allTests :: TestTree
@ -31,42 +32,42 @@ tests :: [NegTest]
tests =
[ NegTest
"Wrong stack height on exit"
"."
"vtest001.jva",
$(mkRelDir ".")
$(mkRelFile "vtest001.jva"),
NegTest
"Arithmetic type mismatch"
"."
"vtest002.jva",
$(mkRelDir ".")
$(mkRelFile "vtest002.jva"),
NegTest
"Function type mismatch"
"."
"vtest003.jva",
$(mkRelDir ".")
$(mkRelFile "vtest003.jva"),
NegTest
"Not enough function arguments"
"."
"vtest004.jva",
$(mkRelDir ".")
$(mkRelFile "vtest004.jva"),
NegTest
"Missing return"
"."
"vtest005.jva",
$(mkRelDir ".")
$(mkRelFile "vtest005.jva"),
NegTest
"Branch stack height mismatch"
"."
"vtest006.jva",
$(mkRelDir ".")
$(mkRelFile "vtest006.jva"),
NegTest
"Branch type mismatch"
"."
"vtest007.jva",
$(mkRelDir ".")
$(mkRelFile "vtest007.jva"),
NegTest
"Case stack height mismatch"
"."
"vtest008.jva",
$(mkRelDir ".")
$(mkRelFile "vtest008.jva"),
NegTest
"Case type mismatch"
"."
"vtest009.jva",
$(mkRelDir ".")
$(mkRelFile "vtest009.jva"),
NegTest
"Value stack type mismatch"
"."
"vtest010.jva"
$(mkRelDir ".")
$(mkRelFile "vtest010.jva")
]

Some files were not shown because too many files have changed in this diff Show More