diff --git a/app/Commands/Dev/Nockma/Repl.hs b/app/Commands/Dev/Nockma/Repl.hs index eb02af9646..f48928f163 100644 --- a/app/Commands/Dev/Nockma/Repl.hs +++ b/app/Commands/Dev/Nockma/Repl.hs @@ -10,7 +10,7 @@ import Data.String.Interpolate (__i) import Juvix.Compiler.Nockma.Evaluator (NockEvalError, evalRepl, fromReplTerm, programAssignments) import Juvix.Compiler.Nockma.Evaluator.Options import Juvix.Compiler.Nockma.Language -import Juvix.Compiler.Nockma.Pretty (ppPrint) +import Juvix.Compiler.Nockma.Pretty import Juvix.Compiler.Nockma.Pretty qualified as Nockma import Juvix.Compiler.Nockma.Translation.FromSource (parseProgramFile, parseReplStatement, parseReplText, parseText) import Juvix.Parser.Error @@ -110,14 +110,20 @@ direction' s = Repline.dontCrash $ do liftIO (putStrLn (ppPrint p)) readTerm :: String -> Repl (Term Natural) -readTerm s = return (fromMegaParsecError (parseText (strip (pack s)))) +readTerm = return . fromMegaParsecError . parseText . strip . pack readReplTerm :: String -> Repl (Term Natural) readReplTerm s = do mprog <- getProgram - let t = run $ runError @NockEvalError (fromReplTerm (programAssignments mprog) (fromMegaParsecError (parseReplText (strip (pack s))))) + let t = + run + . runError @(NockEvalError Natural) + . fromReplTerm (programAssignments mprog) + . fromMegaParsecError + . parseReplText + $ strip (pack s) case t of - Left e -> error (show e) + Left e -> error (ppTrace e) Right tv -> return tv readStatement :: String -> Repl (ReplStatement Natural) @@ -137,12 +143,12 @@ evalStatement = \case . runM . runReader defaultEvalOptions . runError @(ErrNockNatural Natural) - . runError @NockEvalError + . runError @(NockEvalError Natural) $ evalRepl (putStrLn . Nockma.ppTrace) prog s t case et of Left e -> error (show e) Right ev -> case ev of - Left e -> error (show e) + Left e -> error (ppTrace e) Right res -> liftIO (putStrLn (ppPrint res)) replCommand :: String -> Repl () diff --git a/src/Juvix/Compiler/Nockma/Evaluator.hs b/src/Juvix/Compiler/Nockma/Evaluator.hs index 7ada4da0ce..ab42737545 100644 --- a/src/Juvix/Compiler/Nockma/Evaluator.hs +++ b/src/Juvix/Compiler/Nockma/Evaluator.hs @@ -8,25 +8,27 @@ where import Juvix.Compiler.Nockma.Evaluator.Error import Juvix.Compiler.Nockma.Evaluator.Options import Juvix.Compiler.Nockma.Language -import Juvix.Compiler.Nockma.Pretty import Juvix.Prelude hiding (Atom, Path) -asAtom :: (Member (Error NockEvalError) r) => Term a -> Sem r (Atom a) +asAtom :: (Members '[Reader EvalCtx, Error (NockEvalError a)] r) => Term a -> Sem r (Atom a) asAtom = \case TermAtom a -> return a - TermCell {} -> throw ExpectedAtom + TermCell c -> throwExpectedAtom c -asCell :: (Member (Error NockEvalError) r) => Text -> Term a -> Sem r (Cell a) -asCell msg = \case - TermAtom {} -> throw (ExpectedCell msg) +asCell :: (Members '[Reader EvalCtx, Error (NockEvalError a)] r) => Term a -> Sem r (Cell a) +asCell = \case + TermAtom a -> throwExpectedCell a TermCell c -> return c -asBool :: (Member (Error NockEvalError) r, NockNatural a) => Term a -> Sem r Bool +asBool :: (Members '[Reader EvalCtx, Error (NockEvalError a)] r, NockNatural a) => Term a -> Sem r Bool asBool t = do a <- asAtom t return (a == nockTrue) -asPath :: (Members '[Error NockEvalError, Error (ErrNockNatural a)] r, NockNatural a) => Term a -> Sem r Path +asPath :: + (Members '[Reader EvalCtx, Error (NockEvalError a), Error (ErrNockNatural a)] r, NockNatural a) => + Term a -> + Sem r Path asPath = asAtom >=> nockPath subTermT' :: Path -> Traversal (Term a) (Term a) (First (Term a)) (Term a) @@ -44,26 +46,26 @@ subTermT = go L -> (\l' -> TermCell (set cellLeft l' c)) <$> go ds g (c ^. cellLeft) R -> (\r' -> TermCell (set cellRight r' c)) <$> go ds g (c ^. cellRight) -subTerm :: (Member (Error NockEvalError) r) => Term a -> Path -> Sem r (Term a) -subTerm term pos = do +subTerm :: (Members '[Reader EvalCtx, Error (NockEvalError a)] r) => Term a -> Path -> Sem r (Term a) +subTerm term pos = case term ^? subTermT pos of - Nothing -> throw (InvalidPath "subterm") + Nothing -> throwInvalidPath term pos Just t -> return t -setSubTerm :: (Member (Error NockEvalError) r) => Term a -> Path -> Term a -> Sem r (Term a) +setSubTerm :: forall a r. (Members '[Error (NockEvalError a)] r) => Term a -> Path -> Term a -> Sem r (Term a) setSubTerm term pos repTerm = let (old, new) = setAndRemember (subTermT' pos) repTerm term in if - | isNothing (getFirst old) -> throw @NockEvalError (error "") + | isNothing (getFirst old) -> throw @(NockEvalError a) (error "") | otherwise -> return new parseCell :: forall r a. - (Members '[Error NockEvalError, Error (ErrNockNatural a)] r, NockNatural a) => + (Members '[Error (NockEvalError a), Error (ErrNockNatural a)] r, NockNatural a) => Cell a -> Sem r (ParsedCell a) parseCell c = case c ^. cellLeft of - TermAtom a -> operatorOrStdlibCall a (c ^. cellRight) (c ^. cellInfo . unIrrelevant) + TermAtom a -> operatorOrStdlibCall a (c ^. cellRight) (c ^. cellCall) TermCell l -> return (ParsedAutoConsCell (AutoConsCell l (c ^. cellRight))) where operatorOrStdlibCall :: Atom a -> Term a -> Maybe (StdlibCall a) -> Sem r (ParsedCell a) @@ -85,9 +87,9 @@ parseCell c = case c ^. cellLeft of _operatorCellTerm = t } -fromReplTerm :: (Members '[Error NockEvalError] r) => HashMap Text (Term a) -> ReplTerm a -> Sem r (Term a) +fromReplTerm :: forall a r. (Members '[Error (NockEvalError a)] r) => HashMap Text (Term a) -> ReplTerm a -> Sem r (Term a) fromReplTerm namedTerms = \case - ReplName n -> maybe (throw (AssignmentNotFound n)) return (namedTerms ^. at n) + ReplName n -> maybe (throw @(NockEvalError a) (ErrAssignmentNotFound n)) return (namedTerms ^. at n) ReplTerm t -> return t programAssignments :: Maybe (Program a) -> HashMap Text (Term a) @@ -100,7 +102,7 @@ programAssignments mprog = -- | The stack provided in the replExpression has priority evalRepl :: forall r a. - (PrettyCode a, Integral a, Members '[Reader EvalOptions, Error NockEvalError, Error (ErrNockNatural a)] r, NockNatural a) => + (Integral a, Members '[Reader EvalOptions, Error (NockEvalError a), Error (ErrNockNatural a)] r, NockNatural a) => (Term a -> Sem r ()) -> Maybe (Program a) -> Maybe (Term a) -> @@ -116,158 +118,196 @@ evalRepl handleTrace mprog defaultStack expr = do fromReplTerm namedTerms t >>= runOutputSem @(Term a) handleTrace . eval stack where errNoStack :: Sem r x - errNoStack = throw NoStack + errNoStack = throw @(NockEvalError a) (ErrNoStack NoStack) namedTerms :: HashMap Text (Term a) namedTerms = programAssignments mprog eval :: - forall r a. - (PrettyCode a, Integral a, Members '[Reader EvalOptions, Output (Term a), Error NockEvalError, Error (ErrNockNatural a)] r, NockNatural a) => + forall s a. + (Integral a, Members '[Reader EvalOptions, Output (Term a), Error (NockEvalError a), Error (ErrNockNatural a)] s, NockNatural a) => Term a -> Term a -> - Sem r (Term a) -eval stack = \case - TermAtom a -> throw (ExpectedCell ("eval " <> ppTrace a)) - TermCell c -> - parseCell c >>= \case - ParsedAutoConsCell a -> goAutoConsCell a - ParsedOperatorCell o -> goOperatorCell o - ParsedStdlibCallCell o -> do - ignore <- asks (^. evalIgnoreStdlibCalls) - if - | ignore -> goOperatorCell (o ^. stdlibCallRaw) - | otherwise -> goStdlibCall (o ^. stdlibCallCell) + Sem s (Term a) +eval inistack initerm = + topEvalCtx $ + recEval inistack initerm where - goStdlibCall :: StdlibCall a -> Sem r (Term a) - goStdlibCall StdlibCall {..} = do - args' <- eval stack _stdlibCallArgs - let binArith :: (a -> a -> a) -> Sem r (Term a) - binArith f = case args' of - TCell (TAtom l) (TAtom r) -> return (TCell (TAtom (f l r)) stack) - _ -> error "expected a cell with two atoms" - - unaArith :: (a -> a) -> Sem r (Term a) - unaArith f = case args' of - TAtom n -> return (TCell (TAtom (f n)) stack) - _ -> error "expected an atom" - - binCmp :: (a -> a -> Bool) -> Sem r (Term a) - binCmp f = case args' of - TCell (TAtom l) (TAtom r) -> return (TCell (TermAtom (nockBool (f l r))) stack) - _ -> error "expected a cell with two atoms" - - case _stdlibCallFunction of - StdlibDec -> unaArith pred - StdlibAdd -> binArith (+) - StdlibMul -> binArith (*) - StdlibSub -> binArith (-) - StdlibDiv -> binArith div - StdlibMod -> binArith mod - StdlibLt -> binCmp (<) - StdlibLe -> binCmp (<=) - - goAutoConsCell :: AutoConsCell a -> Sem r (Term a) - goAutoConsCell c = do - l' <- eval stack (TermCell (c ^. autoConsCellLeft)) - r' <- eval stack (c ^. autoConsCellRight) - return (TermCell (Cell l' r')) - - goOperatorCell :: OperatorCell a -> Sem r (Term a) - goOperatorCell c = case c ^. operatorCellOp of - OpAddress -> goOpAddress - OpQuote -> goOpQuote - OpApply -> goOpApply - OpIsCell -> goOpIsCell - OpInc -> goOpInc - OpEq -> goOpEq - OpIf -> goOpIf - OpSequence -> goOpSequence - OpPush -> goOpPush - OpCall -> goOpCall - OpReplace -> goOpReplace - OpHint -> goOpHint - OpTrace -> goOpTrace - where - goOpAddress :: Sem r (Term a) - goOpAddress = asPath (c ^. operatorCellTerm) >>= subTerm stack - - goOpQuote :: Sem r (Term a) - goOpQuote = return (c ^. operatorCellTerm) - - goOpIsCell :: Sem r (Term a) - goOpIsCell = return . TermAtom $ case c ^. operatorCellTerm of - TermCell {} -> nockTrue - TermAtom {} -> nockFalse - - goOpTrace :: Sem r (Term a) - goOpTrace = do - Cell' tr a _ <- asCell "OpTrace" (c ^. operatorCellTerm) - tr' <- eval stack tr - output tr' - eval stack a - - goOpHint :: Sem r (Term a) - goOpHint = do - -- Ignore the hint and evaluate - h <- asCell "OpHint" (c ^. operatorCellTerm) - eval stack (h ^. cellRight) - - goOpPush :: Sem r (Term a) - goOpPush = do - cellTerm <- asCell "OpPush" (c ^. operatorCellTerm) - l <- eval stack (cellTerm ^. cellLeft) - let s = TermCell (Cell l stack) - eval s (cellTerm ^. cellRight) - - goOpReplace :: Sem r (Term a) - goOpReplace = do - Cell' rot1 t2 _ <- asCell "OpReplace 1" (c ^. operatorCellTerm) - Cell' ro t1 _ <- asCell "OpReplace 2" rot1 - r <- asPath ro - t1' <- eval stack t1 - t2' <- eval stack t2 - setSubTerm t2' r t1' - - goOpApply :: Sem r (Term a) - goOpApply = do - cellTerm <- asCell "OpApply" (c ^. operatorCellTerm) - t1' <- eval stack (cellTerm ^. cellLeft) - t2' <- eval stack (cellTerm ^. cellRight) - eval t1' t2' - - goOpIf :: Sem r (Term a) - goOpIf = do - cellTerm <- asCell "OpIf 1" (c ^. operatorCellTerm) - let t0 = cellTerm ^. cellLeft - Cell' t1 t2 _ <- asCell "OpIf 2" (cellTerm ^. cellRight) - cond <- eval stack t0 >>= asBool - if - | cond -> eval stack t1 - | otherwise -> eval stack t2 - - goOpInc :: Sem r (Term a) - goOpInc = TermAtom . nockSucc <$> (eval stack (c ^. operatorCellTerm) >>= asAtom) - - goOpEq :: Sem r (Term a) - goOpEq = do - cellTerm <- asCell "OpEq" (c ^. operatorCellTerm) - l <- eval stack (cellTerm ^. cellLeft) - r <- eval stack (cellTerm ^. cellRight) - return . TermAtom $ + recEval :: + forall r. + (r ~ Reader EvalCtx ': s) => + Term a -> + Term a -> + Sem r (Term a) + recEval stack term = case term of + TermAtom a -> throwExpectedCell a + TermCell c -> + parseCell c >>= \case + ParsedAutoConsCell a -> goAutoConsCell a + ParsedOperatorCell o -> goOperatorCell o + ParsedStdlibCallCell o -> do + ignore <- asks (^. evalIgnoreStdlibCalls) if - | l == r -> nockTrue - | otherwise -> nockFalse - - goOpCall :: Sem r (Term a) - goOpCall = do - cellTerm <- asCell "OpCall" (c ^. operatorCellTerm) - r <- asPath (cellTerm ^. cellLeft) - t' <- eval stack (cellTerm ^. cellRight) - subTerm t' r >>= eval t' - - goOpSequence :: Sem r (Term a) - goOpSequence = do - cellTerm <- asCell "OpSequence" (c ^. operatorCellTerm) - t1' <- eval stack (cellTerm ^. cellLeft) - eval t1' (cellTerm ^. cellRight) + | ignore -> goOperatorCell (o ^. stdlibCallRaw) + | otherwise -> goStdlibCall (o ^. stdlibCallCell) + where + loc :: Maybe Interval + loc = term ^. termLoc + + goStdlibCall :: StdlibCall a -> Sem r (Term a) + goStdlibCall StdlibCall {..} = do + let w = EvalCrumbStdlibCallArgs (CrumbStdlibCallArgs _stdlibCallFunction) + args' <- withCrumb w (recEval stack _stdlibCallArgs) + let binArith :: (a -> a -> a) -> Sem r (Term a) + binArith f = case args' of + TCell (TAtom l) (TAtom r) -> return (TCell (TAtom (f l r)) stack) + _ -> error "expected a cell with two atoms" + + unaArith :: (a -> a) -> Sem r (Term a) + unaArith f = case args' of + TAtom n -> return (TCell (TAtom (f n)) stack) + _ -> error "expected an atom" + + binCmp :: (a -> a -> Bool) -> Sem r (Term a) + binCmp f = case args' of + TCell (TAtom l) (TAtom r) -> return (TCell (TermAtom (nockBool (f l r))) stack) + _ -> error "expected a cell with two atoms" + + case _stdlibCallFunction of + StdlibDec -> unaArith pred + StdlibAdd -> binArith (+) + StdlibMul -> binArith (*) + StdlibSub -> binArith (-) + StdlibDiv -> binArith div + StdlibMod -> binArith mod + StdlibLt -> binCmp (<) + StdlibLe -> binCmp (<=) + + goAutoConsCell :: AutoConsCell a -> Sem r (Term a) + goAutoConsCell c = do + let w a = + EvalCrumbAutoCons + CrumbAutoCons + { _crumbAutoConsTag = a, + _crumbAutoConsLoc = loc + } + l' <- withCrumb (w crumbEvalFirst) (recEval stack (TermCell (c ^. autoConsCellLeft))) + r' <- withCrumb (w crumbEvalSecond) (recEval stack (c ^. autoConsCellRight)) + return (TermCell (Cell l' r')) + + goOperatorCell :: OperatorCell a -> Sem r (Term a) + goOperatorCell c = case c ^. operatorCellOp of + OpAddress -> goOpAddress + OpQuote -> return goOpQuote + OpApply -> goOpApply + OpIsCell -> return goOpIsCell + OpInc -> goOpInc + OpEq -> goOpEq + OpIf -> goOpIf + OpSequence -> goOpSequence + OpPush -> goOpPush + OpCall -> goOpCall + OpReplace -> goOpReplace + OpHint -> goOpHint + OpTrace -> goOpTrace + where + crumb crumbTag = + EvalCrumbOperator $ + CrumbOperator + { _crumbOperatorOp = c ^. operatorCellOp, + _crumbOperatorTag = crumbTag, + _crumbOperatorLoc = loc + } + + evalArg :: CrumbTag -> Term a -> Term a -> Sem r (Term a) + evalArg crumbTag stack' arg = do + withCrumb (crumb crumbTag) (recEval stack' arg) + + goOpAddress :: Sem r (Term a) + goOpAddress = do + cr <- withCrumb (crumb crumbDecodeFirst) (asPath (c ^. operatorCellTerm)) + withCrumb (crumb crumbEval) (subTerm stack cr) + + goOpQuote :: Term a + goOpQuote = c ^. operatorCellTerm + + goOpIsCell :: Term a + goOpIsCell = TermAtom $ case c ^. operatorCellTerm of + TermCell {} -> nockTrue + TermAtom {} -> nockFalse + + goOpTrace :: Sem r (Term a) + goOpTrace = do + Cell' tr a _ <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) + tr' <- evalArg crumbEvalFirst stack tr + output tr' + evalArg crumbEvalSecond stack a + + goOpHint :: Sem r (Term a) + goOpHint = do + -- Ignore the hint and evaluate + h <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) + evalArg crumbEvalFirst stack (h ^. cellRight) + + goOpPush :: Sem r (Term a) + goOpPush = do + cellTerm <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) + l <- evalArg crumbEvalFirst stack (cellTerm ^. cellLeft) + let s = TermCell (Cell l stack) + evalArg crumbEvalSecond s (cellTerm ^. cellRight) + + goOpReplace :: Sem r (Term a) + goOpReplace = do + Cell' rot1 t2 _ <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) + Cell' ro t1 _ <- withCrumb (crumb crumbDecodeSecond) (asCell rot1) + r <- withCrumb (crumb crumbDecodeThird) (asPath ro) + t1' <- evalArg crumbEvalFirst stack t1 + t2' <- evalArg crumbEvalSecond stack t2 + setSubTerm t2' r t1' + + goOpApply :: Sem r (Term a) + goOpApply = do + cellTerm <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) + t1' <- evalArg crumbEvalFirst stack (cellTerm ^. cellLeft) + t2' <- evalArg crumbEvalSecond stack (cellTerm ^. cellRight) + evalArg crumbEvalSecond t1' t2' + + goOpIf :: Sem r (Term a) + goOpIf = do + cellTerm <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) + let t0 = cellTerm ^. cellLeft + Cell' t1 t2 _ <- withCrumb (crumb crumbDecodeSecond) (asCell (cellTerm ^. cellRight)) + cond <- evalArg crumbEvalFirst stack t0 >>= asBool + if + | cond -> evalArg crumbTrueBranch stack t1 + | otherwise -> evalArg crumbFalseBranch stack t2 + + goOpInc :: Sem r (Term a) + goOpInc = + TermAtom . nockSucc + <$> ( evalArg crumbEvalFirst stack (c ^. operatorCellTerm) + >>= withCrumb (crumb crumbDecodeFirst) . asAtom + ) + + goOpEq :: Sem r (Term a) + goOpEq = do + cellTerm <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) + l <- evalArg crumbEvalFirst stack (cellTerm ^. cellLeft) + r <- evalArg crumbEvalSecond stack (cellTerm ^. cellRight) + return . TermAtom $ + if + | l == r -> nockTrue + | otherwise -> nockFalse + + goOpCall :: Sem r (Term a) + goOpCall = do + cellTerm <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) + r <- withCrumb (crumb crumbDecodeSecond) (asPath (cellTerm ^. cellLeft)) + t' <- evalArg crumbEvalFirst stack (cellTerm ^. cellRight) + subTerm t' r >>= evalArg crumbEvalSecond t' + + goOpSequence :: Sem r (Term a) + goOpSequence = do + cellTerm <- withCrumb (crumb crumbDecodeFirst) (asCell (c ^. operatorCellTerm)) + t1' <- evalArg crumbEvalFirst stack (cellTerm ^. cellLeft) + evalArg crumbEvalSecond t1' (cellTerm ^. cellRight) diff --git a/src/Juvix/Compiler/Nockma/Evaluator/Crumbs.hs b/src/Juvix/Compiler/Nockma/Evaluator/Crumbs.hs new file mode 100644 index 0000000000..be608d2703 --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Evaluator/Crumbs.hs @@ -0,0 +1,111 @@ +module Juvix.Compiler.Nockma.Evaluator.Crumbs where + +import Juvix.Compiler.Nockma.Language +import Juvix.Compiler.Nockma.Pretty.Base +import Juvix.Prelude hiding (Atom) + +data EvalCrumb + = EvalCrumbStdlibCallArgs CrumbStdlibCallArgs + | EvalCrumbOperator CrumbOperator + | EvalCrumbAutoCons CrumbAutoCons + +newtype EvalCtx = EvalCtx + { _evalStack :: [EvalCrumb] + } + +topEvalCtx :: Sem (Reader EvalCtx ': r) a -> Sem r a +topEvalCtx = runReader (EvalCtx []) + +newtype CrumbStdlibCallArgs = CrumbStdlibCallArgs + { _crumbStdlibCallArgsFunction :: StdlibFunction + } + +newtype CrumbTag = CrumbTag {_crumbTag :: Text} + +crumbEval :: CrumbTag +crumbEval = CrumbTag "Evaluating itself" + +crumbDecodeThird :: CrumbTag +crumbDecodeThird = CrumbTag "Decoding third argument" + +crumbDecodeSecond :: CrumbTag +crumbDecodeSecond = CrumbTag "Decoding second argument" + +crumbDecodeFirst :: CrumbTag +crumbDecodeFirst = CrumbTag "Decoding first argument" + +crumbEvalFirst :: CrumbTag +crumbEvalFirst = CrumbTag "Evaluating first argument" + +crumbTrueBranch :: CrumbTag +crumbTrueBranch = CrumbTag "Evaluating true branch" + +crumbFalseBranch :: CrumbTag +crumbFalseBranch = CrumbTag "Evaluating false branch" + +crumbEvalSecond :: CrumbTag +crumbEvalSecond = CrumbTag "Evaluating second argument" + +data CrumbAutoCons = CrumbAutoCons + { _crumbAutoConsTag :: CrumbTag, + _crumbAutoConsLoc :: Maybe Interval + } + +data CrumbOperator = CrumbOperator + { _crumbOperatorOp :: NockOp, + _crumbOperatorTag :: CrumbTag, + _crumbOperatorLoc :: Maybe Interval + } + +makeLenses ''EvalCtx + +withCrumb :: (Members '[Reader EvalCtx] r) => EvalCrumb -> Sem r a -> Sem r a +withCrumb c = local (over evalStack (c :)) + +instance PrettyCode CrumbTag where + ppCode (CrumbTag a) = + return + . annotate AnnImportant + $ pretty a + +instance PrettyCode CrumbStdlibCallArgs where + ppCode CrumbStdlibCallArgs {..} = do + op <- annotate AnnImportant <$> ppCode _crumbStdlibCallArgsFunction + return ("Evaluating address to arguments to stdlib call for" <+> op) + +ppCtx :: (Member (Reader Options) r) => EvalCtx -> Sem r (Doc Ann) +ppCtx c = do + ctx <- ppCode c + let title = annotate AnnImportant "Evaluation trace:" + return (title <> line <> ctx <> line) + +ppLoc :: (Member (Reader Options) r) => Maybe Interval -> Sem r (Doc Ann) +ppLoc = \case + Nothing -> return mempty + Just x -> do + x' <- ppCode x + return (x' <> ":") + +instance PrettyCode CrumbOperator where + ppCode CrumbOperator {..} = do + tag <- ppCode _crumbOperatorTag + loc <- ppLoc _crumbOperatorLoc + op <- ppCode _crumbOperatorOp + return (loc <+> tag <+> "for" <+> op) + +instance PrettyCode CrumbAutoCons where + ppCode CrumbAutoCons {..} = do + let au = annotate AnnImportant "AutoCons" + loc <- ppLoc _crumbAutoConsLoc + tag <- ppCode _crumbAutoConsTag + return (loc <+> tag <+> "for" <+> au) + +instance PrettyCode EvalCrumb where + ppCode = \case + EvalCrumbAutoCons a -> ppCode a + EvalCrumbStdlibCallArgs a -> ppCode a + EvalCrumbOperator a -> ppCode a + +instance PrettyCode EvalCtx where + ppCode (EvalCtx l) = + itemize <$> mapM (fmap nest' . ppCode) (reverse l) diff --git a/src/Juvix/Compiler/Nockma/Evaluator/Error.hs b/src/Juvix/Compiler/Nockma/Evaluator/Error.hs index 2d04e39fda..ef840aad3b 100644 --- a/src/Juvix/Compiler/Nockma/Evaluator/Error.hs +++ b/src/Juvix/Compiler/Nockma/Evaluator/Error.hs @@ -1,15 +1,22 @@ -module Juvix.Compiler.Nockma.Evaluator.Error where +module Juvix.Compiler.Nockma.Evaluator.Error + ( module Juvix.Compiler.Nockma.Evaluator.Error, + module Juvix.Compiler.Nockma.Evaluator.Crumbs, + ) +where -import Juvix.Prelude hiding (Atom) -import Juvix.Prelude.Pretty +import Juvix.Compiler.Nockma.Evaluator.Crumbs +import Juvix.Compiler.Nockma.Language +import Juvix.Compiler.Nockma.Pretty.Base +import Juvix.Prelude hiding (Atom, Path) -data NockEvalError - = InvalidPath Text - | ExpectedAtom - | ExpectedCell Text - | NoStack - | AssignmentNotFound Text - deriving stock (Show) +data NockEvalError a + = ErrInvalidPath (InvalidPath a) + | ErrExpectedAtom (ExpectedAtom a) + | ErrExpectedCell (ExpectedCell a) + | -- TODO perhaps this should be a repl error type + ErrNoStack NoStack + | -- TODO perhaps this should be a repl error type + ErrAssignmentNotFound Text newtype GenericNockEvalError = GenericNockEvalError { _genericNockEvalErrorMessage :: AnsiText @@ -17,3 +24,84 @@ newtype GenericNockEvalError = GenericNockEvalError class ToGenericNockEvalError a where toGenericNockEvalError :: a -> GenericNockEvalError + +data ExpectedCell a = ExpectedCell + { _expectedCellCtx :: EvalCtx, + _expectedCellAtom :: Atom a + } + +data ExpectedAtom a = ExpectedAtom + { _expectedAtomCtx :: EvalCtx, + _expectedAtomCell :: Cell a + } + +data InvalidPath a = InvalidPath + { _invalidPathCtx :: EvalCtx, + _invalidPathTerm :: Term a, + _invalidPathPath :: Path + } + +data NoStack = NoStack + +throwInvalidPath :: (Members '[Error (NockEvalError a), Reader EvalCtx] r) => Term a -> Path -> Sem r x +throwInvalidPath tm p = do + ctx <- ask + throw $ + ErrInvalidPath + InvalidPath + { _invalidPathCtx = ctx, + _invalidPathTerm = tm, + _invalidPathPath = p + } + +throwExpectedCell :: (Members '[Error (NockEvalError a), Reader EvalCtx] r) => Atom a -> Sem r x +throwExpectedCell a = do + ctx <- ask + throw $ + ErrExpectedCell + ExpectedCell + { _expectedCellCtx = ctx, + _expectedCellAtom = a + } + +throwExpectedAtom :: (Members '[Error (NockEvalError a), Reader EvalCtx] r) => Cell a -> Sem r x +throwExpectedAtom a = do + ctx <- ask + throw $ + ErrExpectedAtom + ExpectedAtom + { _expectedAtomCtx = ctx, + _expectedAtomCell = a + } + +instance PrettyCode NoStack where + ppCode _ = return "Missing stack" + +instance (PrettyCode a, NockNatural a) => PrettyCode (InvalidPath a) where + ppCode InvalidPath {..} = do + ctx <- ppCtx _invalidPathCtx + path <- ppCode _invalidPathPath + tm <- ppCode _invalidPathTerm + return (ctx <> "The path" <+> path <+> "is invalid for the following term:" <> line <> tm) + +instance (PrettyCode a, NockNatural a) => PrettyCode (ExpectedAtom a) where + ppCode ExpectedAtom {..} = do + cell <- ppCode _expectedAtomCell + ctx <- ppCtx _expectedAtomCtx + let atm = annotate AnnImportant "atom" + return (ctx <> "Expected an" <+> atm <+> "but got:" <> line <> cell) + +instance (PrettyCode a, NockNatural a) => PrettyCode (ExpectedCell a) where + ppCode ExpectedCell {..} = do + atm <- ppCode _expectedCellAtom + ctx <- ppCtx _expectedCellCtx + let cell = annotate AnnImportant "cell" + return (ctx <> "Expected an" <+> atm <+> "but got:" <> line <> cell) + +instance (PrettyCode a, NockNatural a) => PrettyCode (NockEvalError a) where + ppCode = \case + ErrInvalidPath e -> ppCode e + ErrExpectedAtom e -> ppCode e + ErrExpectedCell e -> ppCode e + ErrNoStack e -> ppCode e + ErrAssignmentNotFound e -> return (pretty e) diff --git a/src/Juvix/Compiler/Nockma/Language.hs b/src/Juvix/Compiler/Nockma/Language.hs index 227d9281be..62f3725c52 100644 --- a/src/Juvix/Compiler/Nockma/Language.hs +++ b/src/Juvix/Compiler/Nockma/Language.hs @@ -52,16 +52,28 @@ data StdlibCall a = StdlibCall deriving stock instance (Lift a) => Lift (StdlibCall a) +data CellInfo a = CellInfo + { _cellInfoLoc :: Maybe Interval, + _cellInfoCall :: Maybe (StdlibCall a) + } + deriving stock (Lift) + data Cell a = Cell' { _cellLeft :: Term a, _cellRight :: Term a, - _cellInfo :: Irrelevant (Maybe (StdlibCall a)) + _cellInfo :: Irrelevant (CellInfo a) + } + deriving stock (Show, Eq, Lift) + +data AtomInfo = AtomInfo + { _atomInfoHint :: Maybe AtomHint, + _atomInfoLoc :: Maybe Interval } deriving stock (Show, Eq, Lift) data Atom a = Atom { _atom :: a, - _atomInfo :: Irrelevant (Maybe AtomHint) + _atomInfo :: Irrelevant AtomInfo } deriving stock (Show, Eq, Lift) @@ -192,6 +204,25 @@ makeLenses ''Program makeLenses ''Assignment makeLenses ''WithStack makeLenses ''EncodedPath +makeLenses ''AtomInfo +makeLenses ''CellInfo + +atomHint :: Lens' (Atom a) (Maybe AtomHint) +atomHint = atomInfo . unIrrelevant . atomInfoHint + +termLoc :: Lens' (Term a) (Maybe Interval) +termLoc f = \case + TermAtom a -> TermAtom <$> atomLoc f a + TermCell a -> TermCell <$> cellLoc f a + +cellLoc :: Lens' (Cell a) (Maybe Interval) +cellLoc = cellInfo . unIrrelevant . cellInfoLoc + +cellCall :: Lens' (Cell a) (Maybe (StdlibCall a)) +cellCall = cellInfo . unIrrelevant . cellInfoCall + +atomLoc :: Lens' (Atom a) (Maybe Interval) +atomLoc = atomInfo . unIrrelevant . atomInfoLoc naturalNockOps :: HashMap Natural NockOp naturalNockOps = HashMap.fromList [(serializeOp op, op) | op <- allElements] @@ -294,15 +325,21 @@ nockBoolLiteral b instance NockNatural Natural where type ErrNockNatural Natural = NockNaturalNaturalError nockNatural a = return (a ^. atom) - nockTrue = Atom 0 (Irrelevant (Just AtomHintBool)) - nockFalse = Atom 1 (Irrelevant (Just AtomHintBool)) - nockNil = Atom 0 (Irrelevant (Just AtomHintNil)) + nockTrue = Atom 0 (Irrelevant (atomHintInfo AtomHintBool)) + nockFalse = Atom 1 (Irrelevant (atomHintInfo AtomHintBool)) + nockNil = Atom 0 (Irrelevant (atomHintInfo AtomHintNil)) nockSucc = over atom succ errInvalidOp atm = NaturalInvalidOp atm errInvalidPath atm = NaturalInvalidPath atm serializeNockOp = serializeOp serializePath = (^. encodedPath) . encodePath +atomHintInfo :: AtomHint -> AtomInfo +atomHintInfo h = + emptyAtomInfo + { _atomInfoHint = Just h + } + class IsNock nock where toNock :: nock -> Term Natural @@ -316,10 +353,10 @@ instance IsNock (Cell Natural) where toNock = TermCell instance IsNock Natural where - toNock n = TermAtom (Atom n (Irrelevant Nothing)) + toNock = TAtom instance IsNock NockOp where - toNock op = toNock (Atom (serializeOp op) (Irrelevant (Just AtomHintOp))) + toNock op = toNock (Atom (serializeOp op) (Irrelevant (atomHintInfo AtomHintOp))) instance IsNock Bool where toNock = \case @@ -327,7 +364,7 @@ instance IsNock Bool where True -> toNock (nockTrue @Natural) instance IsNock Path where - toNock pos = TermAtom (Atom (encodePath pos ^. encodedPath) (Irrelevant (Just AtomHintPath))) + toNock pos = TermAtom (Atom (encodePath pos ^. encodedPath) (Irrelevant (atomHintInfo AtomHintPath))) instance IsNock EncodedPath where toNock = toNock . decodePath' @@ -374,16 +411,30 @@ stdlibNumArgs = \case pattern Cell :: Term a -> Term a -> Cell a pattern Cell {_cellLeft', _cellRight'} <- Cell' _cellLeft' _cellRight' _ where - Cell a b = Cell' a b (Irrelevant Nothing) + Cell a b = Cell' a b (Irrelevant emptyCellInfo) {-# COMPLETE TCell, TAtom #-} pattern TCell :: Term a -> Term a -> Term a pattern TCell l r <- TermCell (Cell' l r _) where - TCell a b = TermCell (Cell' a b (Irrelevant Nothing)) + TCell a b = TermCell (Cell a b) pattern TAtom :: a -> Term a pattern TAtom a <- TermAtom (Atom a _) where - TAtom a = TermAtom (Atom a (Irrelevant Nothing)) + TAtom a = TermAtom (Atom a (Irrelevant emptyAtomInfo)) + +emptyCellInfo :: CellInfo a +emptyCellInfo = + CellInfo + { _cellInfoCall = Nothing, + _cellInfoLoc = Nothing + } + +emptyAtomInfo :: AtomInfo +emptyAtomInfo = + AtomInfo + { _atomInfoHint = Nothing, + _atomInfoLoc = Nothing + } diff --git a/src/Juvix/Compiler/Nockma/Pretty/Base.hs b/src/Juvix/Compiler/Nockma/Pretty/Base.hs index 4bb2a819a8..e11bb7ac6f 100644 --- a/src/Juvix/Compiler/Nockma/Pretty/Base.hs +++ b/src/Juvix/Compiler/Nockma/Pretty/Base.hs @@ -24,11 +24,11 @@ runPrettyCode :: (PrettyCode c) => Options -> c -> Doc Ann runPrettyCode opts = run . runReader opts . ppCode instance (PrettyCode a, NockNatural a) => PrettyCode (Atom a) where - ppCode atm@(Atom k h) = runFailDefaultM (annotate (AnnKind KNameFunction) <$> ppCode k) + ppCode atm = runFailDefaultM (annotate (AnnKind KNameFunction) <$> ppCode (atm ^. atom)) . failFromError @(ErrNockNatural a) $ do whenM (asks (^. optIgnoreHints)) fail - h' <- failMaybe (h ^. unIrrelevant) + h' <- failMaybe (atm ^. atomHint) case h' of AtomHintOp -> nockOp atm >>= ppCode AtomHintPath -> nockPath atm >>= ppCode @@ -38,6 +38,9 @@ instance (PrettyCode a, NockNatural a) => PrettyCode (Atom a) where | otherwise -> fail AtomHintNil -> return (annotate (AnnKind KNameConstructor) "nil") +instance PrettyCode Interval where + ppCode = return . pretty + instance PrettyCode Natural where ppCode = return . pretty @@ -70,7 +73,7 @@ instance (PrettyCode a, NockNatural a) => PrettyCode (Cell a) where m <- asks (^. optPrettyMode) stdlibCall <- runFail $ do failWhenM (asks (^. optIgnoreHints)) - failMaybe (c ^. cellInfo . unIrrelevant) >>= ppCode + failMaybe (c ^. cellCall) >>= ppCode components <- case m of AllDelimiters -> do l' <- ppCode (c ^. cellLeft) diff --git a/src/Juvix/Compiler/Nockma/Translation/FromAsm.hs b/src/Juvix/Compiler/Nockma/Translation/FromAsm.hs index f9e0ce76b2..30a6911d06 100644 --- a/src/Juvix/Compiler/Nockma/Translation/FromAsm.hs +++ b/src/Juvix/Compiler/Nockma/Translation/FromAsm.hs @@ -771,7 +771,7 @@ callStdlibOn' s f = do _stdlibCallFunction = f } - callCell = (OpPush #. (decodeFn # callFn)) {_cellInfo = Irrelevant (Just meta)} + callCell = set cellCall (Just meta) (OpPush #. (decodeFn # callFn)) output (toNock callCell) output (replaceTopStackN fNumArgs s) @@ -1014,14 +1014,20 @@ evalCompiledNock' :: (Members '[Reader EvalOptions, Output (Term Natural)] r) => evalCompiledNock' stack mainTerm = do evalT <- runError @(ErrNockNatural Natural) - . runError @NockEvalError + . runError @(NockEvalError Natural) $ eval stack mainTerm case evalT of Left e -> error (show e) Right ev -> case ev of - Left e -> error (show e) + Left e -> error (ppTrace e) Right res -> return res -- | Used in testing and app getStack :: StackId -> Term Natural -> Term Natural -getStack st m = fromRight' (run (runError @NockEvalError (subTerm m (stackPath st)))) +getStack st m = + fromRight' + . run + . runError @(NockEvalError Natural) + . topEvalCtx + . subTerm m + $ stackPath st diff --git a/src/Juvix/Compiler/Nockma/Translation/FromSource/Base.hs b/src/Juvix/Compiler/Nockma/Translation/FromSource/Base.hs index fffa95f203..277a0610c3 100644 --- a/src/Juvix/Compiler/Nockma/Translation/FromSource/Base.hs +++ b/src/Juvix/Compiler/Nockma/Translation/FromSource/Base.hs @@ -6,6 +6,7 @@ import Data.Text qualified as Text import Juvix.Compiler.Nockma.Language import Juvix.Extra.Strings qualified as Str import Juvix.Parser.Error +import Juvix.Parser.Lexer (onlyInterval, withLoc) import Juvix.Prelude hiding (Atom, many, some) import Juvix.Prelude.Parsing hiding (runParser) import Text.Megaparsec qualified as P @@ -14,10 +15,10 @@ import Text.Megaparsec.Char.Lexer qualified as L type Parser = Parsec Void Text parseText :: Text -> Either MegaparsecError (Term Natural) -parseText = runParser "" +parseText = runParser noFile parseReplText :: Text -> Either MegaparsecError (ReplTerm Natural) -parseReplText = runParserFor replTerm "" +parseReplText = runParserFor replTerm noFile parseTermFile :: (MonadIO m) => FilePath -> m (Either MegaparsecError (Term Natural)) parseTermFile fp = do @@ -30,7 +31,10 @@ parseProgramFile fp = do return (runParserProgram fp txt) parseReplStatement :: Text -> Either MegaparsecError (ReplStatement Natural) -parseReplStatement = runParserFor replStatement "" +parseReplStatement = runParserFor replStatement noFile + +noFile :: FilePath +noFile = "/" runParserProgram :: FilePath -> Text -> Either MegaparsecError (Program Natural) runParserProgram = runParserFor program @@ -74,18 +78,36 @@ dottedNatural = lexeme $ do atomOp :: Parser (Atom Natural) atomOp = do - op' <- choice [symbol opName $> op | (opName, op) <- HashMap.toList atomOps] - return (Atom (serializeNockOp op') (Irrelevant (Just AtomHintOp))) + WithLoc loc op' <- withLoc (choice [symbol opName $> op | (opName, op) <- HashMap.toList atomOps]) + let info = + AtomInfo + { _atomInfoHint = Just AtomHintOp, + _atomInfoLoc = Just loc + } + return (Atom (serializeNockOp op') (Irrelevant info)) atomDirection :: Parser (Atom Natural) atomDirection = do - dirs <- - symbol "S" $> [] - <|> NonEmpty.toList <$> some (choice [symbol "L" $> L, symbol "R" $> R]) - return (Atom (serializePath dirs) (Irrelevant (Just AtomHintPath))) + WithLoc loc dirs <- + withLoc $ + symbol "S" $> [] + <|> NonEmpty.toList <$> some (choice [symbol "L" $> L, symbol "R" $> R]) + let info = + AtomInfo + { _atomInfoHint = Just AtomHintOp, + _atomInfoLoc = Just loc + } + return (Atom (serializePath dirs) (Irrelevant info)) atomNat :: Parser (Atom Natural) -atomNat = (\n -> Atom n (Irrelevant Nothing)) <$> dottedNatural +atomNat = do + WithLoc loc n <- withLoc dottedNatural + let info = + AtomInfo + { _atomInfoHint = Nothing, + _atomInfoLoc = Just loc + } + return (Atom n (Irrelevant info)) atomBool :: Parser (Atom Natural) atomBool = @@ -94,6 +116,11 @@ atomBool = symbol "false" $> nockFalse ] +atomWithLoc :: Parser a -> Atom Natural -> Parser (Atom Natural) +atomWithLoc p n = do + loc <- onlyInterval p + return (set atomLoc (Just loc) n) + atomNil :: Parser (Atom Natural) atomNil = symbol "nil" $> nockNil @@ -110,13 +137,18 @@ iden = lexeme (takeWhile1P (Just "") isAlphaNum) cell :: Parser (Cell Natural) cell = do - lsbracket + lloc <- onlyInterval lsbracket c <- optional stdlibCall firstTerm <- term restTerms <- some term - rsbracket + rloc <- onlyInterval rsbracket let r = buildCell firstTerm restTerms - return (set cellInfo (Irrelevant c) r) + info = + CellInfo + { _cellInfoCall = c, + _cellInfoLoc = Just (lloc <> rloc) + } + return (set cellInfo (Irrelevant info) r) where stdlibCall :: Parser (StdlibCall Natural) stdlibCall = do diff --git a/src/Juvix/Data/Loc.hs b/src/Juvix/Data/Loc.hs index d6673dcc73..2bf63d6f42 100644 --- a/src/Juvix/Data/Loc.hs +++ b/src/Juvix/Data/Loc.hs @@ -7,7 +7,7 @@ import Prettyprinter import Text.Megaparsec qualified as M newtype Pos = Pos {_unPos :: Word64} - deriving stock (Show, Eq, Ord, Data, Generic) + deriving stock (Show, Eq, Ord, Data, Generic, Lift) deriving newtype (Hashable, Num, Enum, Real, Integral) instance Serialize Pos @@ -26,7 +26,7 @@ data FileLoc = FileLoc -- | Offset wrt the start of the input. Used for syntax highlighting. _locOffset :: !Pos } - deriving stock (Show, Eq, Generic, Data) + deriving stock (Show, Eq, Generic, Data, Lift) instance Hashable FileLoc @@ -72,7 +72,7 @@ data Interval = Interval _intervalStart :: FileLoc, _intervalEnd :: FileLoc } - deriving stock (Show, Ord, Eq, Generic, Data) + deriving stock (Show, Ord, Eq, Generic, Data, Lift) instance Hashable Interval diff --git a/src/Juvix/Parser/Lexer.hs b/src/Juvix/Parser/Lexer.hs index 045eb6fe01..80f288457c 100644 --- a/src/Juvix/Parser/Lexer.hs +++ b/src/Juvix/Parser/Lexer.hs @@ -205,7 +205,7 @@ curLoc = do offset <- getOffset return (mkLoc offset sp) -onlyInterval :: ParsecS r a -> ParsecS r Interval +onlyInterval :: (MonadParsec e Text m) => m a -> m Interval onlyInterval = fmap snd . interval interval :: (MonadParsec e Text m) => m a -> m (a, Interval) @@ -215,7 +215,7 @@ interval ma = do end <- curLoc return (res, mkInterval start end) -withLoc :: ParsecS r a -> ParsecS r (WithLoc a) +withLoc :: (MonadParsec e Text m) => m a -> m (WithLoc a) withLoc ma = do (a, i) <- interval ma return (WithLoc i a) diff --git a/test/Nockma/Compile/Positive.hs b/test/Nockma/Compile/Positive.hs index e1c18e45f1..57cf54ed03 100644 --- a/test/Nockma/Compile/Positive.hs +++ b/test/Nockma/Compile/Positive.hs @@ -162,7 +162,12 @@ eqTraces expected = do subStackPred :: StackId -> Path -> (Term Natural -> Check ()) -> Check () subStackPred st subp p = do s <- getStack st <$> ask - case run (runError @NockEvalError (subTerm s subp)) of + let res = + run + . runError @(NockEvalError Natural) + . topEvalCtx + $ subTerm s subp + case res of Left {} -> assertFailure "Subterm path is not valid" Right n -> p n diff --git a/test/Nockma/Eval/Positive.hs b/test/Nockma/Eval/Positive.hs index 39f58243e0..25001a9bab 100644 --- a/test/Nockma/Eval/Positive.hs +++ b/test/Nockma/Eval/Positive.hs @@ -29,12 +29,12 @@ allTests = testGroup "Nockma eval unit positive" (map mk tests) . runReader defaultEvalOptions . ignoreOutput @(Term Natural) . runError @(ErrNockNatural Natural) - . runError @NockEvalError + . runError @(NockEvalError Natural) $ eval _testProgramSubject _testProgramFormula case evalResult of Left natErr -> assertFailure ("Evaluation error: " <> show natErr) Right r -> case r of - Left evalErr -> assertFailure ("Evaluation error: " <> show evalErr) + Left evalErr -> assertFailure ("Evaluation error: " <> unpack (ppTrace evalErr)) Right res -> runM (runReader res _testCheck) eqNock :: Term Natural -> Check ()