Skip to content

Commit

Permalink
Update REPL artifacts with builtins from stored modules (#2639)
Browse files Browse the repository at this point in the history
Builtin information needs to be propagated from stored modules to REPL
artifacts to avoid "The builtin _ has not been defined" errors.

This PR adds a test suite for the REPL in the Haskell test code. This
means some of the slow smoke tests can be moved to fast haskell unit
tests. In future we should refactor the REPL code by putting in the main
src target and unit testing more features (e.g :doc, :def).

* Closes #2638
  • Loading branch information
paulcadman authored Feb 26, 2024
1 parent 6b74d84 commit a091a7f
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 97 deletions.
53 changes: 1 addition & 52 deletions app/Commands/Repl.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import Control.Monad.State.Strict qualified as State
import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Reader (mapReaderT)
import Data.String.Interpolate (i, __i)
import Evaluator
import HaskelineJB
import Juvix.Compiler.Concrete.Data.Scope (scopePath)
import Juvix.Compiler.Concrete.Data.Scope qualified as Scoped
Expand All @@ -25,8 +24,6 @@ import Juvix.Compiler.Core.Extra.Value
import Juvix.Compiler.Core.Info qualified as Info
import Juvix.Compiler.Core.Info.NoDisplayInfo qualified as Info
import Juvix.Compiler.Core.Pretty qualified as Core
import Juvix.Compiler.Core.Transformation qualified as Core
import Juvix.Compiler.Core.Transformation.DisambiguateNames (disambiguateNames)
import Juvix.Compiler.Internal.Language qualified as Internal
import Juvix.Compiler.Internal.Pretty qualified as Internal
import Juvix.Compiler.Pipeline.Repl
Expand Down Expand Up @@ -190,7 +187,7 @@ replCommand opts input_ = catchAll $ do
doEvalIO' :: Artifacts -> Core.Node -> IO (Either JuvixError Core.Node)
doEvalIO' artif' n =
mapLeft (JuvixError @Core.CoreError)
<$> doEvalIO False replDefaultLoc (Core.computeCombinedInfoTable $ artif' ^. artifactCoreModule) n
<$> Core.doEvalIO False replDefaultLoc (Core.computeCombinedInfoTable $ artif' ^. artifactCoreModule) n

compileString :: Repl (Maybe Core.Node)
compileString = do
Expand Down Expand Up @@ -605,51 +602,3 @@ renderOut = render'

renderOutLn :: (P.HasAnsiBackend a, P.HasTextBackend a) => a -> Repl ()
renderOutLn t = renderOut t >> replNewline

runTransformations ::
forall r.
(Members '[State Artifacts, Error JuvixError, Reader EntryPoint] r) =>
Bool ->
[Core.TransformationId] ->
Core.Node ->
Sem r Core.Node
runTransformations shouldDisambiguate ts n = runCoreInfoTableBuilderArtifacts $ do
sym <- addNode n
applyTransforms shouldDisambiguate ts
getNode sym
where
addNode :: Core.Node -> Sem (Core.InfoTableBuilder ': r) Core.Symbol
addNode node = do
sym <- Core.freshSymbol
Core.registerIdentNode sym node
-- `n` will get filtered out by the transformations unless it has a
-- corresponding entry in `infoIdentifiers`
md <- Core.getModule
let name = Core.freshIdentName md "_repl"
idenInfo =
Core.IdentifierInfo
{ _identifierName = name,
_identifierSymbol = sym,
_identifierLocation = Nothing,
_identifierArgsNum = 0,
_identifierType = Core.mkDynamic',
_identifierIsExported = False,
_identifierBuiltin = Nothing,
_identifierPragmas = mempty,
_identifierArgNames = []
}
Core.registerIdent name idenInfo
return sym

applyTransforms :: Bool -> [Core.TransformationId] -> Sem (Core.InfoTableBuilder ': r) ()
applyTransforms shouldDisambiguate' ts' = do
md <- Core.getModule
md' <- mapReader Core.fromEntryPoint $ Core.applyTransformations ts' md
let md'' =
if
| shouldDisambiguate' -> disambiguateNames md'
| otherwise -> md'
Core.setModule md''

getNode :: Core.Symbol -> Sem (Core.InfoTableBuilder ': r) Core.Node
getNode sym = fromMaybe impossible . flip Core.lookupIdentifierNode' sym <$> Core.getModule
8 changes: 0 additions & 8 deletions app/Evaluator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,6 @@ data EvalOptions = EvalOptions

makeLenses ''EvalOptions

doEvalIO ::
Bool ->
Interval ->
Core.InfoTable ->
Core.Node ->
IO (Either Core.CoreError Core.Node)
doEvalIO noIO i tab node = runM (Core.doEval noIO i tab node)

evalAndPrint ::
forall r a.
(Members '[EmbedIO, App] r, CanonicalProjection a EvalOptions, CanonicalProjection a Core.Options) =>
Expand Down
8 changes: 8 additions & 0 deletions src/Juvix/Compiler/Core/Evaluator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,14 @@ doEval noIO loc tab node
| noIO = catchEvalError loc (eval stderr (tab ^. identContext) [] node)
| otherwise = liftIO (catchEvalErrorIO loc (evalIO (tab ^. identContext) [] node))

doEvalIO ::
Bool ->
Interval ->
InfoTable ->
Node ->
IO (Either CoreError Node)
doEvalIO noIO i tab node = runM (doEval noIO i tab node)

-- | Catch EvalError and convert it to CoreError. Needs a default location in case
-- no location is available in EvalError.
catchEvalError :: (MonadIO m) => Location -> a -> m (Either CoreError a)
Expand Down
2 changes: 1 addition & 1 deletion src/Juvix/Compiler/Core/Extra/Value.hs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ toValue tab = \case
ValueConstrApp
ConstrApp
{ _constrAppName = ci ^. constructorName,
_constrAppFixity = ci ^. constructorFixity,
_constrAppFixity = Irrelevant (ci ^. constructorFixity),
_constrAppArgs = map (toValue tab) (drop paramsNum _constrArgs)
}
where
Expand Down
6 changes: 4 additions & 2 deletions src/Juvix/Compiler/Core/Language/Value.hs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import Juvix.Compiler.Core.Language.Nodes

data ConstrApp = ConstrApp
{ _constrAppName :: Text,
_constrAppFixity :: Maybe Fixity,
_constrAppFixity :: Irrelevant (Maybe Fixity),
_constrAppArgs :: [Value]
}
deriving stock (Eq)

-- | Specifies Core values for user-friendly pretty printing.
data Value
Expand All @@ -16,13 +17,14 @@ data Value
| ValueWildcard
| ValueFun
| ValueType
deriving stock (Eq)

makeLenses ''ConstrApp

instance HasAtomicity ConstrApp where
atomicity ConstrApp {..}
| null _constrAppArgs = Atom
| otherwise = Aggregate (fromMaybe appFixity _constrAppFixity)
| otherwise = Aggregate (fromMaybe appFixity (_constrAppFixity ^. unIrrelevant))

instance HasAtomicity Value where
atomicity = \case
Expand Down
2 changes: 1 addition & 1 deletion src/Juvix/Compiler/Core/Pretty/Base.hs
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ goUnary fixity name = \case
instance PrettyCode ConstrApp where
ppCode ConstrApp {..} = do
n <- ppName KNameConstructor _constrAppName
case _constrAppFixity of
case _constrAppFixity ^. unIrrelevant of
Nothing -> do
args <- mapM (ppRightExpression appFixity) _constrAppArgs
return $ hsep (n : args)
Expand Down
4 changes: 2 additions & 2 deletions src/Juvix/Compiler/Core/Transformation/MatchToCase.hs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ goMatchToCase recur node = case node of
ValueConstrApp
ConstrApp
{ _constrAppName = ci ^. constructorName,
_constrAppFixity = ci ^. constructorFixity,
_constrAppFixity = Irrelevant (ci ^. constructorFixity),
_constrAppArgs = replicate argsNum ValueWildcard
}
Nothing ->
Expand All @@ -239,7 +239,7 @@ goMatchToCase recur node = case node of
ValueConstrApp
ConstrApp
{ _constrAppName = ci ^. constructorName,
_constrAppFixity = ci ^. constructorFixity,
_constrAppFixity = Irrelevant (ci ^. constructorFixity),
_constrAppArgs = drop paramsNum (take argsNum args)
}
binders' <- getBranchBinders col matrix tag
Expand Down
2 changes: 2 additions & 0 deletions src/Juvix/Compiler/Pipeline/Artifacts.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Juvix.Compiler.Pipeline.Artifacts
where

import Juvix.Compiler.Builtins
import Juvix.Compiler.Builtins.Effect qualified as Builtins
import Juvix.Compiler.Concrete.Data.InfoTableBuilder qualified as Scoped
import Juvix.Compiler.Concrete.Data.Scope qualified as S
import Juvix.Compiler.Core.Data.InfoTableBuilder qualified as Core
Expand All @@ -26,6 +27,7 @@ import Juvix.Prelude
appendArtifactsModuleTable :: ModuleTable -> Artifacts -> Artifacts
appendArtifactsModuleTable mtab =
over artifactInternalTypedTable (computeCombinedInfoTable importTab <>)
. over (artifactBuiltins . Builtins.builtinsTable) (computeCombinedBuiltins mtab <>)
. over (artifactCoreModule . Core.moduleImportsTable) (computeCombinedCoreInfoTable mtab <>)
. over artifactModuleTable (mtab <>)
where
Expand Down
50 changes: 50 additions & 0 deletions src/Juvix/Compiler/Pipeline/Repl.hs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import Juvix.Compiler.Concrete.Translation.FromParsed qualified as Scoper
import Juvix.Compiler.Concrete.Translation.FromSource qualified as Parser
import Juvix.Compiler.Concrete.Translation.FromSource.ParserResultBuilder (runParserResultBuilder)
import Juvix.Compiler.Core qualified as Core
import Juvix.Compiler.Core.Transformation qualified as Core
import Juvix.Compiler.Core.Transformation.DisambiguateNames (disambiguateNames)
import Juvix.Compiler.Internal qualified as Internal
import Juvix.Compiler.Internal.Translation.FromInternal.Analysis.Termination.Checker
import Juvix.Compiler.Pipeline.Artifacts
Expand Down Expand Up @@ -167,3 +169,51 @@ compileReplInputIO fp txt = do
Parser.ReplExpression e -> ReplPipelineResultNode <$> compileExpression e
Parser.ReplImport i -> registerImport i $> ReplPipelineResultImport (i ^. importModulePath)
Parser.ReplOpenImport i -> return (ReplPipelineResultOpen (i ^. openModuleName))

runTransformations ::
forall r.
(Members '[State Artifacts, Error JuvixError, Reader EntryPoint] r) =>
Bool ->
[Core.TransformationId] ->
Core.Node ->
Sem r Core.Node
runTransformations shouldDisambiguate ts n = runCoreInfoTableBuilderArtifacts $ do
sym <- addNode n
applyTransforms shouldDisambiguate ts
getNode sym
where
addNode :: Core.Node -> Sem (Core.InfoTableBuilder ': r) Core.Symbol
addNode node = do
sym <- Core.freshSymbol
Core.registerIdentNode sym node
-- `n` will get filtered out by the transformations unless it has a
-- corresponding entry in `infoIdentifiers`
md <- Core.getModule
let name = Core.freshIdentName md "_repl"
idenInfo =
Core.IdentifierInfo
{ _identifierName = name,
_identifierSymbol = sym,
_identifierLocation = Nothing,
_identifierArgsNum = 0,
_identifierType = Core.mkDynamic',
_identifierIsExported = False,
_identifierBuiltin = Nothing,
_identifierPragmas = mempty,
_identifierArgNames = []
}
Core.registerIdent name idenInfo
return sym

applyTransforms :: Bool -> [Core.TransformationId] -> Sem (Core.InfoTableBuilder ': r) ()
applyTransforms shouldDisambiguate' ts' = do
md <- Core.getModule
md' <- mapReader Core.fromEntryPoint $ Core.applyTransformations ts' md
let md'' =
if
| shouldDisambiguate' -> disambiguateNames md'
| otherwise -> md'
Core.setModule md''

getNode :: Core.Symbol -> Sem (Core.InfoTableBuilder ': r) Core.Node
getNode sym = fromMaybe impossible . flip Core.lookupIdentifierNode' sym <$> Core.getModule
8 changes: 8 additions & 0 deletions src/Juvix/Compiler/Store/Extra.hs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
module Juvix.Compiler.Store.Extra where

import Data.HashMap.Strict qualified as HashMap
import Juvix.Compiler.Concrete.Data.Builtins
import Juvix.Compiler.Concrete.Data.ScopedName qualified as S
import Juvix.Compiler.Concrete.Language (TopModulePath)
import Juvix.Compiler.Core.Data.InfoTable qualified as Core
import Juvix.Compiler.Internal.Data.Name
import Juvix.Compiler.Store.Core.Extra
import Juvix.Compiler.Store.Internal.Language
import Juvix.Compiler.Store.Language
Expand Down Expand Up @@ -42,3 +44,9 @@ computeCombinedScopedInfoTable mtab =
computeCombinedCoreInfoTable :: ModuleTable -> Core.InfoTable
computeCombinedCoreInfoTable mtab =
mconcatMap (toCore . (^. moduleInfoCoreTable)) (HashMap.elems (mtab ^. moduleTable))

computeCombinedBuiltins :: ModuleTable -> HashMap BuiltinPrim Name
computeCombinedBuiltins mtab =
mconcatMap
(^. moduleInfoInternalModule . internalModuleInfoTable . infoBuiltins)
(HashMap.elems (mtab ^. moduleTable))
3 changes: 3 additions & 0 deletions src/Juvix/Data/Error/GenericError.hs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ render ansi endChar err = do
renderText :: (ToGenericError e, Member (Reader GenericOptions) r) => e -> Sem r Text
renderText = render False False

renderTextDefault :: (ToGenericError e) => e -> Text
renderTextDefault = run . runReader defaultGenericOptions . renderText

-- | Render the error with Ansi formatting (if any).
renderAnsiText :: (ToGenericError e, Member (Reader GenericOptions) r) => e -> Sem r Text
renderAnsiText = render True False
Expand Down
4 changes: 3 additions & 1 deletion test/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import Nockma qualified
import Package qualified
import Parsing qualified
import Reg qualified
import Repl qualified
import Resolver qualified
import Runtime qualified
import Scope qualified
Expand All @@ -39,7 +40,8 @@ slowTests =
Examples.allTests,
Casm.allTests,
VampIR.allTests,
Anoma.allTests
Anoma.allTests,
Repl.allTests
]

fastTests :: TestTree
Expand Down
7 changes: 7 additions & 0 deletions test/Repl.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Repl where

import Base
import Repl.Positive qualified as P

allTests :: TestTree
allTests = testGroup "Juvix REPL tests" [P.allTests]
21 changes: 21 additions & 0 deletions test/Repl/Assertions.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Repl.Assertions where

import Base
import Juvix.Compiler.Core qualified as Core
import Juvix.Compiler.Core.Language.Value qualified as Core
import Juvix.Compiler.Core.Pretty qualified as Core

assertNoJuvixError :: Either JuvixError a -> IO a
assertNoJuvixError = either (assertFailure . ("JuvixError: " <>) . unpack . renderTextDefault) return

assertPrettyCodeEqual :: (Core.PrettyCode a, Eq a) => a -> a -> Assertion
assertPrettyCodeEqual expected actual = unless (expected == actual) (assertFailure (unpack msg))
where
msg :: Text
msg = "expected: " <> Core.ppTrace expected <> "\n but got: " <> Core.ppTrace actual

assertNodeEqual :: Core.Node -> Core.Node -> Assertion
assertNodeEqual = assertPrettyCodeEqual

assertValueEqual :: Core.Value -> Core.Value -> Assertion
assertValueEqual = assertPrettyCodeEqual
Loading

0 comments on commit a091a7f

Please sign in to comment.