diff --git a/src/Juvix/Compiler/Nockma/Evaluator.hs b/src/Juvix/Compiler/Nockma/Evaluator.hs index 8aebca6f95..59b57f8aed 100644 --- a/src/Juvix/Compiler/Nockma/Evaluator.hs +++ b/src/Juvix/Compiler/Nockma/Evaluator.hs @@ -326,5 +326,5 @@ eval inistack initerm = goOpScry = do Cell' typeFormula subFormula _ <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) void (evalArg crumbEvalFirst stack typeFormula) - subResult <- evalArg crumbEvalSecond stack subFormula - HashMap.lookupDefault impossible subResult <$> asks (^. storageKeyValueData) + key <- evalArg crumbEvalSecond stack subFormula + fromMaybeM (throwKeyNotInStorage key) (HashMap.lookup key <$> asks (^. storageKeyValueData)) diff --git a/src/Juvix/Compiler/Nockma/Evaluator/Error.hs b/src/Juvix/Compiler/Nockma/Evaluator/Error.hs index c0f80bd26d..ddae3b4f10 100644 --- a/src/Juvix/Compiler/Nockma/Evaluator/Error.hs +++ b/src/Juvix/Compiler/Nockma/Evaluator/Error.hs @@ -1,10 +1,13 @@ module Juvix.Compiler.Nockma.Evaluator.Error ( module Juvix.Compiler.Nockma.Evaluator.Error, module Juvix.Compiler.Nockma.Evaluator.Crumbs, + module Juvix.Compiler.Nockma.Evaluator.Storage, ) where +import Data.HashMap.Strict qualified as HashMap import Juvix.Compiler.Nockma.Evaluator.Crumbs +import Juvix.Compiler.Nockma.Evaluator.Storage import Juvix.Compiler.Nockma.Language import Juvix.Compiler.Nockma.Pretty.Base import Juvix.Prelude hiding (Atom, Path) @@ -17,6 +20,7 @@ data NockEvalError a ErrNoStack NoStack | -- TODO perhaps this should be a repl error type ErrAssignmentNotFound Text + | ErrKeyNotInStorage (KeyNotInStorage a) newtype GenericNockEvalError = GenericNockEvalError { _genericNockEvalErrorMessage :: AnsiText @@ -41,6 +45,11 @@ data InvalidPath a = InvalidPath _invalidPathPath :: Path } +data KeyNotInStorage a = KeyNotInStorage + { _keyNotInStorageKey :: Term a, + _keyNotInStorageStorage :: Storage a + } + data NoStack = NoStack throwInvalidPath :: (Members '[Error (NockEvalError a), Reader EvalCtx] r) => Term a -> Path -> Sem r x @@ -74,6 +83,16 @@ throwExpectedAtom a = do _expectedAtomCell = a } +throwKeyNotInStorage :: (Members '[Reader (Storage a), Error (NockEvalError a)] r) => Term a -> Sem r x +throwKeyNotInStorage k = do + s <- ask + throw $ + ErrKeyNotInStorage + KeyNotInStorage + { _keyNotInStorageKey = k, + _keyNotInStorageStorage = s + } + instance PrettyCode NoStack where ppCode _ = return "Missing stack" @@ -98,6 +117,19 @@ instance (PrettyCode a, NockNatural a) => PrettyCode (ExpectedCell a) where let cell = annotate AnnImportant "cell" return (ctx <> "Expected a" <+> cell <+> "but got:" <> line <> atm) +instance (PrettyCode a, NockNatural a) => PrettyCode (KeyNotInStorage a) where + ppCode :: forall r. (Member (Reader Options) r) => KeyNotInStorage a -> Sem r (Doc Ann) + ppCode KeyNotInStorage {..} = do + tm <- ppCode _keyNotInStorageKey + hashMapKvs <- vsep <$> (mapM combineKeyValue (HashMap.toList (_keyNotInStorageStorage ^. storageKeyValueData))) + return ("The key" <+> tm <+> "is not found in the storage." <> line <> "Storage contains the following key value pairs:" <> line <> hashMapKvs) + where + combineKeyValue :: (Term a, Term a) -> Sem r (Doc Ann) + combineKeyValue (t1, t2) = do + pt1 <- ppCode t1 + pt2 <- ppCode t2 + return (pt1 <+> ":=" <+> pt2) + instance (PrettyCode a, NockNatural a) => PrettyCode (NockEvalError a) where ppCode = \case ErrInvalidPath e -> ppCode e @@ -105,3 +137,4 @@ instance (PrettyCode a, NockNatural a) => PrettyCode (NockEvalError a) where ErrExpectedCell e -> ppCode e ErrNoStack e -> ppCode e ErrAssignmentNotFound e -> return (pretty e) + ErrKeyNotInStorage e -> ppCode e diff --git a/test/Anoma/Compilation/Positive.hs b/test/Anoma/Compilation/Positive.hs index 59c4e34500..40cbfdbff2 100644 --- a/test/Anoma/Compilation/Positive.hs +++ b/test/Anoma/Compilation/Positive.hs @@ -28,6 +28,7 @@ mkAnomaCallTest' enableDebug _testName relRoot mainFile args _testCheck = let _testProgramFormula = anomaCall args _testEvalOptions = defaultEvalOptions _testProgramStorage :: Storage Natural = emptyStorage + _testAssertEvalError :: Maybe (NockEvalError Natural -> Assertion) = Nothing return Test {..} withRootCopy :: (Prelude.Path Abs Dir -> IO a) -> IO a diff --git a/test/Nockma/Eval/Positive.hs b/test/Nockma/Eval/Positive.hs index 4fdb07bc33..f0402a68df 100644 --- a/test/Nockma/Eval/Positive.hs +++ b/test/Nockma/Eval/Positive.hs @@ -14,6 +14,7 @@ type Check = Sem '[Reader [Term Natural], Reader (Term Natural), EmbedIO] data Test = Test { _testEvalOptions :: EvalOptions, + _testAssertEvalError :: Maybe (NockEvalError Natural -> Assertion), _testProgramStorage :: Storage Natural, _testName :: Text, _testProgramSubject :: Term Natural, @@ -36,9 +37,13 @@ mkNockmaAssertion Test {..} = do case evalResult of Left natErr -> assertFailure ("Evaluation error: " <> show natErr) - Right r -> case r of - Left evalErr -> assertFailure ("Evaluation error: " <> unpack (ppTrace evalErr)) - Right res -> runM (runReader res (runReader traces _testCheck)) + Right r -> case _testAssertEvalError of + Nothing -> case r of + Left evalErr -> assertFailure ("Evaluation error: " <> unpack (ppTrace evalErr)) + Right res -> runM (runReader res (runReader traces _testCheck)) + Just checkErrFn -> case r of + Left evalErr -> checkErrFn evalErr + Right {} -> assertFailure "expected error" allTests :: TestTree allTests = @@ -95,8 +100,19 @@ compilerTest n mainFun _testCheck _evalInterceptStdlibCalls = Cell _testProgramSubject _testProgramFormula = runCompilerWithJuvix opts mempty [] f _testEvalOptions = EvalOptions {..} _testProgramStorage :: Storage Natural = emptyStorage + _testAssertEvalError :: Maybe (NockEvalError Natural -> Assertion) = Nothing in Test {..} +withAssertErrKeyNotInStorage :: Test -> Test +withAssertErrKeyNotInStorage Test {..} = + let _testAssertEvalError :: Maybe (NockEvalError Natural -> Assertion) = Just f + in Test {..} + where + f :: NockEvalError Natural -> Assertion + f = \case + ErrKeyNotInStorage {} -> return () + _ -> assertFailure "Expected ErrKeyNotInStorage error" + anomaTest :: Text -> Term Natural -> [Term Natural] -> Check () -> Bool -> Test anomaTest n mainFun args _testCheck _evalInterceptStdlibCalls = let f = @@ -116,10 +132,11 @@ anomaTest n mainFun args _testCheck _evalInterceptStdlibCalls = _testProgramFormula = anomaCall args _testProgramStorage :: Storage Natural = emptyStorage _testEvalOptions = EvalOptions {..} + _testAssertEvalError :: Maybe (NockEvalError Natural -> Assertion) = Nothing in Test {..} testWithStorage :: [(Term Natural, Term Natural)] -> Text -> Term Natural -> Term Natural -> Check () -> Test -testWithStorage s = Test defaultEvalOptions (Storage (HashMap.fromList s)) +testWithStorage s = Test defaultEvalOptions Nothing (Storage (HashMap.fromList s)) test :: Text -> Term Natural -> Term Natural -> Check () -> Test test = testWithStorage [] @@ -167,5 +184,6 @@ unitTests = test "call" [nock| [quote 1] |] [nock| [call [S [@ S]]] |] (eqNock [nock| 1 |]), test "replace" [nock| [0 1] |] [nock| [replace [[L [quote 1]] [@ S]]] |] (eqNock [nock| [1 1] |]), test "hint" [nock| [0 1] |] [nock| [hint [nil [trace [quote 2] [quote 3]]] [quote 1]] |] (eqTraces [[nock| 2 |]] >> eqNock [nock| 1 |]), - testWithStorage [([nock| 111 |], [nock| 222 |])] "scry" [nock| nil |] [nock| [scry [quote nil] [quote 111]] |] (eqNock [nock| 222 |]) + testWithStorage [([nock| 111 |], [nock| 222 |])] "scry" [nock| nil |] [nock| [scry [quote nil] [quote 111]] |] (eqNock [nock| 222 |]), + withAssertErrKeyNotInStorage $ testWithStorage [([nock| 333 |], [nock| 222 |]), ([nock| 3 |], [nock| 222 |])] "scry" [nock| nil |] [nock| [scry [quote nil] [quote 111]] |] (eqNock [nock| 222 |]) ]