diff --git a/app/Commands/Dev.hs b/app/Commands/Dev.hs index 1b6e725429..89ecea4a64 100644 --- a/app/Commands/Dev.hs +++ b/app/Commands/Dev.hs @@ -12,6 +12,7 @@ import Commands.Dev.Geb qualified as Geb import Commands.Dev.Highlight qualified as Highlight import Commands.Dev.Internal qualified as Internal import Commands.Dev.MigrateJuvixYaml qualified as MigrateJuvixYaml +import Commands.Dev.Nockma qualified as Nockma import Commands.Dev.Options import Commands.Dev.Parse qualified as Parse import Commands.Dev.Runtime qualified as Runtime @@ -33,3 +34,4 @@ runCommand = \case DisplayRoot opts -> DisplayRoot.runCommand opts JuvixDevRepl opts -> Repl.runCommand opts MigrateJuvixYaml opts -> runFilesIO $ MigrateJuvixYaml.runCommand opts + Nockma opts -> Nockma.runCommand opts diff --git a/app/Commands/Dev/Nockma.hs b/app/Commands/Dev/Nockma.hs new file mode 100644 index 0000000000..d45602f70d --- /dev/null +++ b/app/Commands/Dev/Nockma.hs @@ -0,0 +1,9 @@ +module Commands.Dev.Nockma where + +import Commands.Base +import Commands.Dev.Nockma.Options +import Commands.Dev.Nockma.Repl as Repl + +runCommand :: forall r. (Members '[Embed IO, App] r) => NockmaCommand -> Sem r () +runCommand = \case + NockmaRepl opts -> Repl.runCommand opts diff --git a/app/Commands/Dev/Nockma/Options.hs b/app/Commands/Dev/Nockma/Options.hs new file mode 100644 index 0000000000..3720873bfa --- /dev/null +++ b/app/Commands/Dev/Nockma/Options.hs @@ -0,0 +1,20 @@ +module Commands.Dev.Nockma.Options where + +import Commands.Dev.Nockma.Repl.Options +import CommonOptions + +data NockmaCommand + = NockmaRepl NockmaReplOptions + deriving stock (Data) + +parseNockmaCommand :: Parser NockmaCommand +parseNockmaCommand = hsubparser commandRepl + where + commandRepl :: Mod CommandFields NockmaCommand + commandRepl = command "repl" replInfo + + replInfo :: ParserInfo NockmaCommand + replInfo = + info + (NockmaRepl <$> parseNockmaReplOptions) + (progDesc "Run the nockma repl") diff --git a/app/Commands/Dev/Nockma/Repl.hs b/app/Commands/Dev/Nockma/Repl.hs new file mode 100644 index 0000000000..1c43ed5975 --- /dev/null +++ b/app/Commands/Dev/Nockma/Repl.hs @@ -0,0 +1,177 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Commands.Dev.Nockma.Repl where + +import Commands.Base hiding (Atom) +import Commands.Dev.Nockma.Repl.Options +import Control.Exception (throwIO) +import Control.Monad.State.Strict qualified as State +import Data.String.Interpolate (__i) +import Juvix.Compiler.Nockma.Evaluator (NockEvalError, evalRepl, fromReplTerm, programAssignments) +import Juvix.Compiler.Nockma.Language +import Juvix.Compiler.Nockma.Pretty (ppPrint) +import Juvix.Compiler.Nockma.Translation.FromSource (parseProgramFile, parseReplStatement, parseReplText, parseText) +import Juvix.Parser.Error +import Juvix.Prelude.Pretty +import System.Console.Haskeline +import System.Console.Repline qualified as Repline +import Prelude (read) + +type ReplS = State.StateT ReplState IO + +data ReplState = ReplState + { _replStateProgram :: Maybe (Program Natural), + _replStateStack :: Maybe (Term Natural), + _replStateLoadedFile :: Maybe (FilePath) + } + +type Repl a = Repline.HaskelineT ReplS a + +makeLenses ''ReplState + +printHelpTxt :: Repl () +printHelpTxt = liftIO $ putStrLn helpTxt + where + helpTxt :: Text = + [__i| + EXPRESSION Evaluate a Nockma expression in the context of the current stack + STACK_EXPRESSION / EXPRESSION Evaluate a Nockma EXPRESSION in the context of STACK_EXPRESSION + :load FILE Load a file containing Nockma assignments + :reload Reload the current file + :help Print help text and describe options + :set-stack EXPRESSION Set the current stack + :get-stack Print the current stack + :dir NATURAL Convert a natural number representing a position into a sequence of L and Rs. S means the empty sequence + :quit Exit the REPL + |] + +quit :: String -> Repl () +quit _ = liftIO (throwIO Interrupt) + +printStack :: String -> Repl () +printStack _ = Repline.dontCrash $ do + stack <- getStack + case stack of + Nothing -> noStackErr + Just s -> liftIO (putStrLn (ppPrint s)) + +noStackErr :: a +noStackErr = error "no stack is set. Use :set-stack to set a stack." + +setStack :: String -> Repl () +setStack s = Repline.dontCrash $ do + newStack <- readReplTerm s + State.modify (set replStateStack (Just newStack)) + +loadFile :: String -> Repl () +loadFile s = Repline.dontCrash $ do + State.modify (set replStateLoadedFile (Just s)) + prog <- readProgram s + State.modify (set replStateProgram (Just prog)) + +reloadFile :: Repl () +reloadFile = Repline.dontCrash $ do + fp <- State.gets (^. replStateLoadedFile) + case fp of + Nothing -> error "no file loaded" + Just f -> do + prog <- readProgram f + State.modify (set replStateProgram (Just prog)) + +options :: [(String, String -> Repl ())] +options = + [ ("quit", quit), + ("get-stack", printStack), + ("set-stack", setStack), + ("load", loadFile), + ("reload", const reloadFile), + ("dir", direction') + ] + +banner :: Repline.MultiLine -> Repl String +banner = \case + Repline.MultiLine -> return "... " + Repline.SingleLine -> return "nockma> " + +getStack :: Repl (Maybe (Term Natural)) +getStack = State.gets (^. replStateStack) + +getProgram :: Repl (Maybe (Program Natural)) +getProgram = State.gets (^. replStateProgram) + +readProgram :: FilePath -> Repl (Program Natural) +readProgram s = fromMegaParsecError <$> parseProgramFile s + +fromMegaParsecError :: Either MegaparsecError a -> a +fromMegaParsecError = \case + Left e -> error (prettyText e) + Right a -> a + +direction' :: String -> Repl () +direction' s = Repline.dontCrash $ do + let n = read s :: Natural + p = run (runFailDefault (error "invalid position") (decodePath (EncodedPath n))) + liftIO (putStrLn (ppPrint p)) + +readTerm :: String -> Repl (Term Natural) +readTerm s = return (fromMegaParsecError (parseText (strip (pack s)))) + +readReplTerm :: String -> Repl (Term Natural) +readReplTerm s = do + mprog <- getProgram + let t = run $ runError @NockEvalError (fromReplTerm (programAssignments mprog) (fromMegaParsecError (parseReplText (strip (pack s))))) + case t of + Left e -> error (show e) + Right tv -> return tv + +readStatement :: String -> Repl (ReplStatement Natural) +readStatement s = return (fromMegaParsecError (parseReplStatement (strip (pack s)))) + +evalStatement :: ReplStatement Natural -> Repl () +evalStatement = \case + ReplStatementAssignment as -> do + prog <- fromMaybe (Program []) <$> getProgram + let p' = over programStatements (++ [StatementAssignment as]) prog + State.modify (set replStateProgram (Just p')) + ReplStatementExpression t -> do + s <- getStack + prog <- getProgram + let et = + run + . runError @(ErrNockNatural Natural) + . runError @NockEvalError + $ evalRepl prog s t + case et of + Left e -> error (show e) + Right ev -> case ev of + Left e -> error (show e) + Right res -> liftIO (putStrLn (ppPrint res)) + +replCommand :: String -> Repl () +replCommand input = Repline.dontCrash $ do + readStatement input >>= evalStatement + +replAction :: ReplS () +replAction = + Repline.evalReplOpts + Repline.ReplOpts + { prefix = Just ':', + command = replCommand, + initialiser = return (), + finaliser = return Repline.Exit, + multilineCommand = Just "multiline", + tabComplete = Repline.Word (\_ -> return []), + options, + banner + } + +runCommand :: forall r. (Members '[Embed IO, App] r) => NockmaReplOptions -> Sem r () +runCommand _ = embed . (`State.evalStateT` iniState) $ replAction + where + iniState :: ReplState + iniState = + ReplState + { _replStateStack = Nothing, + _replStateProgram = Nothing, + _replStateLoadedFile = Nothing + } diff --git a/app/Commands/Dev/Nockma/Repl/Options.hs b/app/Commands/Dev/Nockma/Repl/Options.hs new file mode 100644 index 0000000000..4ac9aac147 --- /dev/null +++ b/app/Commands/Dev/Nockma/Repl/Options.hs @@ -0,0 +1,13 @@ +module Commands.Dev.Nockma.Repl.Options where + +import CommonOptions + +newtype NockmaReplOptions = NockmaReplOptions + { _nockmaReplOptionsStackFile :: Maybe (AppPath File) + } + deriving stock (Data) + +parseNockmaReplOptions :: Parser NockmaReplOptions +parseNockmaReplOptions = do + _nockmaReplOptionsStackFile <- optional (parseInputFile FileExtNock) + pure NockmaReplOptions {..} diff --git a/app/Commands/Dev/Options.hs b/app/Commands/Dev/Options.hs index 804c7e4add..d86577a613 100644 --- a/app/Commands/Dev/Options.hs +++ b/app/Commands/Dev/Options.hs @@ -19,6 +19,7 @@ import Commands.Dev.Geb.Options import Commands.Dev.Highlight.Options import Commands.Dev.Internal.Options import Commands.Dev.MigrateJuvixYaml.Options +import Commands.Dev.Nockma.Options import Commands.Dev.Parse.Options import Commands.Dev.Repl.Options import Commands.Dev.Runtime.Options @@ -40,6 +41,7 @@ data DevCommand | Termination TerminationCommand | JuvixDevRepl ReplOptions | MigrateJuvixYaml MigrateJuvixYamlOptions + | Nockma NockmaCommand deriving stock (Data) parseDevCommand :: Parser DevCommand @@ -57,7 +59,8 @@ parseDevCommand = commandShowRoot, commandTermination, commandJuvixDevRepl, - commandMigrateJuvixYaml + commandMigrateJuvixYaml, + commandNockma ] ) @@ -146,3 +149,10 @@ commandMigrateJuvixYaml = info (MigrateJuvixYaml <$> parseMigrateJuvixYaml) (progDesc "Migrate juvix.yaml to Package.juvix in the current project") + +commandNockma :: Mod CommandFields DevCommand +commandNockma = + command "nockma" $ + info + (Nockma <$> parseNockmaCommand) + (progDesc "Subcommands related to the nockma backend") diff --git a/cntlines.sh b/cntlines.sh index 00cae1a710..9b50c967b0 100755 --- a/cntlines.sh +++ b/cntlines.sh @@ -17,6 +17,7 @@ RUNTIME=$((RUNTIME_C+RUNTIME_VAMPIR+RUNTIME_JVA)) BACKENDC=$(count src/Juvix/Compiler/Backend/C/) GEB=$(count src/Juvix/Compiler/Backend/Geb/) VAMPIR=$(count src/Juvix/Compiler/Backend/VampIR/) +NOCK=$(count src/Juvix/Compiler/Nockma) REG=$(count src/Juvix/Compiler/Reg/) ASM=$(count src/Juvix/Compiler/Asm/) CORE=$(count src/Juvix/Compiler/Core/) @@ -34,7 +35,7 @@ PRELUDE=$(count src/Juvix/Prelude/) STORE=$(count src/Juvix/Compiler/Store/) FRONT=$((CONCRETE + INTERNAL + BUILTINS + PIPELINE)) -BACK=$((BACKENDC + GEB + VAMPIR + REG + ASM + CORE)) +BACK=$((BACKENDC + GEB + VAMPIR + NOCK + REG + ASM + CORE)) OTHER=$((APP + STORE + HTML + EXTRA + DATA + PRELUDE)) TESTS=$(count test/) @@ -49,6 +50,7 @@ echo "Middle and back end: $BACK LOC" echo " VampIR backend: $VAMPIR LOC" echo " GEB backend: $GEB LOC" echo " C backend: $BACKENDC LOC" +echo " Nockma: $NOCK LOC" echo " JuvixReg: $REG LOC" echo " JuvixAsm: $ASM LOC" echo " JuvixCore: $CORE LOC" diff --git a/src/Juvix/Compiler/Backend/VampIR/Pretty/Base.hs b/src/Juvix/Compiler/Backend/VampIR/Pretty/Base.hs index d73fff6891..ca33753804 100644 --- a/src/Juvix/Compiler/Backend/VampIR/Pretty/Base.hs +++ b/src/Juvix/Compiler/Backend/VampIR/Pretty/Base.hs @@ -6,7 +6,6 @@ import Juvix.Compiler.Backend.VampIR.Language import Juvix.Compiler.Backend.VampIR.Pretty.Keywords import Juvix.Compiler.Backend.VampIR.Pretty.Options import Juvix.Data.CodeAnn -import Juvix.Data.NameKind class PrettyCode c where ppCode :: (Member (Reader Options) r) => c -> Sem r (Doc Ann) diff --git a/src/Juvix/Compiler/Concrete/Translation/FromParsed/Analysis/Scoping.hs b/src/Juvix/Compiler/Concrete/Translation/FromParsed/Analysis/Scoping.hs index 4dfbb8de85..5f4950b231 100644 --- a/src/Juvix/Compiler/Concrete/Translation/FromParsed/Analysis/Scoping.hs +++ b/src/Juvix/Compiler/Concrete/Translation/FromParsed/Analysis/Scoping.hs @@ -28,7 +28,6 @@ import Juvix.Compiler.Concrete.Translation.FromSource.Data.Context qualified as import Juvix.Compiler.Pipeline.EntryPoint import Juvix.Compiler.Store.Scoped.Language as Store import Juvix.Data.FixityInfo qualified as FI -import Juvix.Data.NameKind import Juvix.Prelude hiding (scoped) scopeCheck :: diff --git a/src/Juvix/Compiler/Internal/Pretty.hs b/src/Juvix/Compiler/Internal/Pretty.hs index 56bfd66470..78b8d555b0 100644 --- a/src/Juvix/Compiler/Internal/Pretty.hs +++ b/src/Juvix/Compiler/Internal/Pretty.hs @@ -11,7 +11,6 @@ import Juvix.Compiler.Internal.Pretty.Options import Juvix.Data.PPOutput import Juvix.Prelude import Prettyprinter.Render.Terminal qualified as Ansi -import Prettyprinter.Render.Text (renderStrict) ppOutDefault :: (PrettyCode c) => c -> AnsiText ppOutDefault = mkAnsiText . PPOutput . doc defaultOptions diff --git a/src/Juvix/Compiler/Internal/Translation/FromConcrete.hs b/src/Juvix/Compiler/Internal/Translation/FromConcrete.hs index 51cc70ae09..d4959a8eb0 100644 --- a/src/Juvix/Compiler/Internal/Translation/FromConcrete.hs +++ b/src/Juvix/Compiler/Internal/Translation/FromConcrete.hs @@ -34,7 +34,6 @@ import Juvix.Compiler.Store.Language qualified as Store import Juvix.Compiler.Store.Scoped.Data.InfoTable qualified as S import Juvix.Compiler.Store.Scoped.Language (createExportsTable) import Juvix.Compiler.Store.Scoped.Language qualified as S -import Juvix.Data.NameKind import Juvix.Prelude import Safe (lastMay) diff --git a/src/Juvix/Compiler/Nockma/Evaluator.hs b/src/Juvix/Compiler/Nockma/Evaluator.hs new file mode 100644 index 0000000000..c331beeef9 --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Evaluator.hs @@ -0,0 +1,216 @@ +module Juvix.Compiler.Nockma.Evaluator + ( module Juvix.Compiler.Nockma.Evaluator, + module Juvix.Compiler.Nockma.Evaluator.Error, + ) +where + +import Juvix.Compiler.Nockma.Evaluator.Error +import Juvix.Compiler.Nockma.Language +import Juvix.Prelude hiding (Atom, Path) + +asAtom :: (Member (Error NockEvalError) r) => Term a -> Sem r (Atom a) +asAtom = \case + TermAtom a -> return a + TermCell {} -> throw ExpectedAtom + +asCell :: (Member (Error NockEvalError) r) => Term a -> Sem r (Cell a) +asCell = \case + TermAtom {} -> throw ExpectedCell + TermCell c -> return c + +asBool :: (Member (Error NockEvalError) 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 = asAtom >=> nockPath + +subTermT' :: Path -> Traversal (Term a) (Term a) (First (Term a)) (Term a) +subTermT' pos f = subTermT pos (f . First . Just) + +subTermT :: Path -> Traversal' (Term a) (Term a) +subTermT = go + where + go :: Path -> (forall f. (Applicative f) => (Term a -> f (Term a)) -> Term a -> f (Term a)) + go = \case + [] -> id + d : ds -> \g t -> case t of + TermAtom {} -> pure t + TermCell c -> case d of + 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 + case term ^? subTermT pos of + Nothing -> throw InvalidPath + Just t -> return t + +setSubTerm :: (Member (Error NockEvalError) 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 InvalidPath + | otherwise -> return new + +parseCell :: forall r a. (Members '[Error NockEvalError, Error (ErrNockNatural a)] r, NockNatural a) => Cell a -> Sem r (ParsedCell a) +parseCell c = case c ^. cellLeft of + TermAtom a -> ParsedOperatorCell <$> parseOperatorCell a (c ^. cellRight) + TermCell l -> return (ParsedAutoConsCell (AutoConsCell l (c ^. cellRight))) + where + parseOperatorCell :: Atom a -> Term a -> Sem r (OperatorCell a) + parseOperatorCell a t = do + op <- nockOp a + return + OperatorCell + { _operatorCellOp = op, + _operatorCellTerm = t + } + +fromReplTerm :: (Members '[Error NockEvalError] r) => HashMap Text (Term a) -> ReplTerm a -> Sem r (Term a) +fromReplTerm namedTerms = \case + ReplName n -> maybe (throw (AssignmentNotFound n)) return (namedTerms ^. at n) + ReplTerm t -> return t + +programAssignments :: Maybe (Program a) -> HashMap Text (Term a) +programAssignments mprog = + hashMap + [ (as ^. assignmentName, as ^. assignmentBody) + | StatementAssignment as <- mprog ^. _Just . programStatements + ] + +-- | The stack provided in the replExpression has priority +evalRepl :: + forall r a. + (Members '[Error NockEvalError, Error (ErrNockNatural a)] r, NockNatural a) => + Maybe (Program a) -> + Maybe (Term a) -> + ReplExpression a -> + Sem r (Term a) +evalRepl mprog defaultStack expr = do + (mstack, t) <- case expr of + ReplExpressionTerm tm -> return (defaultStack, tm) + ReplExpressionWithStack w -> do + t' <- fromReplTerm namedTerms (w ^. withStackStack) + return (Just t', w ^. withStackTerm) + stack <- maybe errNoStack return mstack + fromReplTerm namedTerms t >>= eval stack + where + errNoStack :: Sem r x + errNoStack = throw NoStack + + namedTerms :: HashMap Text (Term a) + namedTerms = programAssignments mprog + +eval :: + forall r a. + (Members '[Error NockEvalError, Error (ErrNockNatural a)] r, NockNatural a) => + Term a -> + Term a -> + Sem r (Term a) +eval stack = \case + TermAtom {} -> throw ExpectedCell + TermCell c -> do + pc <- parseCell c + case pc of + ParsedAutoConsCell a -> goAutoConsCell a + ParsedOperatorCell o -> goOperatorCell o + where + goAutoConsCell :: AutoConsCell a -> Sem r (Term a) + goAutoConsCell c = do + _cellLeft <- eval stack (TermCell (c ^. autoConsCellLeft)) + _cellRight <- eval stack (c ^. autoConsCellRight) + return (TermCell Cell {..}) + + 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 + 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 + + goOpHint :: Sem r (Term a) + goOpHint = do + -- Ignore the hint and evaluate + h <- asCell (c ^. operatorCellTerm) + eval stack (h ^. cellRight) + + goOpPush :: Sem r (Term a) + goOpPush = do + cellTerm <- asCell (c ^. operatorCellTerm) + l <- eval stack (cellTerm ^. cellLeft) + let s = TermCell Cell {_cellLeft = l, _cellRight = stack} + eval s (cellTerm ^. cellRight) + + goOpReplace :: Sem r (Term a) + goOpReplace = do + cellTerm <- asCell (c ^. operatorCellTerm) + rt1 <- asCell (cellTerm ^. cellLeft) + r <- asPath (rt1 ^. cellLeft) + let t1 = rt1 ^. cellRight + t1' <- eval stack t1 + t2' <- eval stack (cellTerm ^. cellRight) + setSubTerm t2' r t1' + + goOpApply :: Sem r (Term a) + goOpApply = do + cellTerm <- asCell (c ^. operatorCellTerm) + t1' <- eval stack (cellTerm ^. cellLeft) + t2' <- eval stack (cellTerm ^. cellRight) + eval t1' t2' + + goOpIf :: Sem r (Term a) + goOpIf = do + cellTerm <- asCell (c ^. operatorCellTerm) + let t0 = cellTerm ^. cellLeft + Cell t1 t2 <- asCell (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 (c ^. operatorCellTerm) + l <- eval stack (cellTerm ^. cellLeft) + r <- eval stack (cellTerm ^. cellRight) + return . TermAtom $ + if + | l == r -> nockTrue + | otherwise -> nockFalse + + goOpCall :: Sem r (Term a) + goOpCall = do + cellTerm <- asCell (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 (c ^. operatorCellTerm) + t1' <- eval stack (cellTerm ^. cellLeft) + eval t1' (cellTerm ^. cellRight) diff --git a/src/Juvix/Compiler/Nockma/Evaluator/Error.hs b/src/Juvix/Compiler/Nockma/Evaluator/Error.hs new file mode 100644 index 0000000000..ef1f9ff9cf --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Evaluator/Error.hs @@ -0,0 +1,19 @@ +module Juvix.Compiler.Nockma.Evaluator.Error where + +import Juvix.Prelude hiding (Atom) +import Juvix.Prelude.Pretty + +data NockEvalError + = InvalidPath + | ExpectedAtom + | ExpectedCell + | NoStack + | AssignmentNotFound Text + deriving stock (Show) + +newtype GenericNockEvalError = GenericNockEvalError + { _genericNockEvalErrorMessage :: AnsiText + } + +class ToGenericNockEvalError a where + toGenericNockEvalError :: a -> GenericNockEvalError diff --git a/src/Juvix/Compiler/Nockma/Language.hs b/src/Juvix/Compiler/Nockma/Language.hs new file mode 100644 index 0000000000..523603a3af --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Language.hs @@ -0,0 +1,259 @@ +module Juvix.Compiler.Nockma.Language where + +import Data.HashMap.Strict qualified as HashMap +import GHC.Base (Type) +import Juvix.Prelude hiding (Atom, Path) +import Juvix.Prelude.Pretty + +data ReplStatement a + = ReplStatementExpression (ReplExpression a) + | ReplStatementAssignment (Assignment a) + +data ReplExpression a + = ReplExpressionTerm (ReplTerm a) + | ReplExpressionWithStack (WithStack a) + +data WithStack a = WithStack + { _withStackStack :: ReplTerm a, + _withStackTerm :: ReplTerm a + } + +data ReplTerm a + = ReplName Text + | ReplTerm (Term a) + +newtype Program a = Program + { _programStatements :: [Statement a] + } + +data Statement a + = StatementAssignment (Assignment a) + | StatementStandalone (Term a) + +data Assignment a = Assignment + { _assignmentName :: Text, + _assignmentBody :: Term a + } + +data Term a + = TermAtom (Atom a) + | TermCell (Cell a) + deriving stock (Show, Eq, Lift) + +data Cell a = Cell + { _cellLeft :: Term a, + _cellRight :: Term a + } + deriving stock (Show, Eq, Lift) + +data Atom a = Atom + { _atom :: a, + _atomInfo :: Irrelevant (Maybe AtomHint) + } + deriving stock (Show, Eq, Lift) + +data AtomHint + = AtomHintOp + | AtomHintPath + | AtomHintBool + | AtomHintNil + deriving stock (Show, Eq, Lift) + +data NockOp + = OpAddress + | OpQuote + | OpApply + | OpIsCell + | OpInc + | OpEq + | OpIf + | OpSequence + | OpPush + | OpCall + | OpReplace + | OpHint + deriving stock (Bounded, Enum, Eq, Generic) + +instance Hashable NockOp + +instance Pretty NockOp where + pretty = \case + OpAddress -> "@" + OpQuote -> "quote" + OpApply -> "apply" + OpIsCell -> "isCell" + OpInc -> "suc" + OpEq -> "=" + OpIf -> "if" + OpSequence -> "seq" + OpPush -> "push" + OpCall -> "call" + OpReplace -> "replace" + OpHint -> "hint" + +atomOps :: HashMap Text NockOp +atomOps = HashMap.fromList [(prettyText op, op) | op <- allElements] + +data OperatorCell a = OperatorCell + { _operatorCellOp :: NockOp, + _operatorCellTerm :: Term a + } + +data AutoConsCell a = AutoConsCell + { _autoConsCellLeft :: Cell a, + _autoConsCellRight :: Term a + } + +data ParsedCell a + = ParsedOperatorCell (OperatorCell a) + | ParsedAutoConsCell (AutoConsCell a) + +newtype EncodedPath = EncodedPath + { _encodedPath :: Natural + } + +data Direction + = L + | R + deriving stock (Show) + +type Path = [Direction] + +emptyPath :: Path +emptyPath = [] + +makeLenses ''Cell +makeLenses ''Atom +makeLenses ''OperatorCell +makeLenses ''AutoConsCell +makeLenses ''Program +makeLenses ''Assignment +makeLenses ''WithStack +makeLenses ''EncodedPath + +naturalNockOps :: HashMap Natural NockOp +naturalNockOps = HashMap.fromList [(serializeOp op, op) | op <- allElements] + +nockOpsNatural :: HashMap NockOp Natural +nockOpsNatural = HashMap.fromList (swap <$> HashMap.toList naturalNockOps) + +parseOp :: (Member Fail r) => Natural -> Sem r NockOp +parseOp n = failMaybe (naturalNockOps ^. at n) + +serializeOp :: NockOp -> Natural +serializeOp = \case + OpAddress -> 0 + OpQuote -> 1 + OpApply -> 2 + OpIsCell -> 3 + OpInc -> 4 + OpEq -> 5 + OpIf -> 6 + OpSequence -> 7 + OpPush -> 8 + OpCall -> 9 + OpReplace -> 10 + OpHint -> 11 + +decodePath :: forall r. (Member Fail r) => EncodedPath -> Sem r Path +decodePath ep = execOutputList (go (ep ^. encodedPath)) + where + go :: Natural -> Sem (Output Direction ': r) () + go = \case + 0 -> fail + 1 -> return () + x -> + if + | even x -> do + go (x `div` 2) + output L + | otherwise -> do + go ((x - 1) `div` 2) + output R + +decodePath' :: EncodedPath -> Path +decodePath' = run . runFailDefault (error "encoded path cannot be 0") . decodePath + +encodePath :: Path -> EncodedPath +encodePath = EncodedPath . foldl' step 1 + where + step :: Natural -> Direction -> Natural + step n = \case + R -> 2 * n + 1 + L -> 2 * n + +class (Eq a) => NockNatural a where + type ErrNockNatural a :: Type + nockNatural :: (Member (Error (ErrNockNatural a)) r) => Atom a -> Sem r Natural + serializeNockOp :: NockOp -> a + serializePath :: Path -> a + + errInvalidOp :: Atom a -> ErrNockNatural a + + errInvalidPath :: Atom a -> ErrNockNatural a + + nockOp :: (Member (Error (ErrNockNatural a)) r) => Atom a -> Sem r NockOp + nockOp atm = do + n <- nockNatural atm + failWithError (errInvalidOp atm) (parseOp n) + + nockPath :: (Member (Error (ErrNockNatural a)) r) => Atom a -> Sem r Path + nockPath atm = do + n <- nockNatural atm + failWithError (errInvalidPath atm) (decodePath (EncodedPath n)) + + nockTrue :: Atom a + nockFalse :: Atom a + nockSucc :: Atom a -> Atom a + nockNil :: Atom a + +data NockNaturalNaturalError + = NaturalInvalidPath (Atom Natural) + | NaturalInvalidOp (Atom Natural) + deriving stock (Show) + +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)) + nockSucc = over atom succ + errInvalidOp atm = NaturalInvalidOp atm + errInvalidPath atm = NaturalInvalidPath atm + serializeNockOp = serializeOp + serializePath = (^. encodedPath) . encodePath + +class IsNock nock where + toNock :: nock -> Term Natural + +instance IsNock (Term Natural) where + toNock = id + +instance IsNock (Atom Natural) where + toNock = TermAtom + +instance IsNock (Cell Natural) where + toNock = TermCell + +instance IsNock Natural where + toNock n = toNock (Atom n (Irrelevant Nothing)) + +instance IsNock NockOp where + toNock op = toNock (Atom (serializeOp op) (Irrelevant (Just AtomHintOp))) + +instance IsNock Bool where + toNock = \case + False -> toNock (nockFalse @Natural) + True -> toNock (nockTrue @Natural) + +instance IsNock Path where + toNock pos = TermAtom (Atom (encodePath pos ^. encodedPath) (Irrelevant (Just AtomHintPath))) + +instance IsNock EncodedPath where + toNock = toNock . decodePath' + +infixr 5 # + +(#) :: (IsNock x, IsNock y) => x -> y -> Term Natural +a # b = TermCell (Cell (toNock a) (toNock b)) diff --git a/src/Juvix/Compiler/Nockma/Pretty.hs b/src/Juvix/Compiler/Nockma/Pretty.hs new file mode 100644 index 0000000000..32a1c56e78 --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Pretty.hs @@ -0,0 +1,28 @@ +module Juvix.Compiler.Nockma.Pretty + ( module Juvix.Compiler.Nockma.Pretty, + module Juvix.Compiler.Nockma.Language, + module Juvix.Compiler.Nockma.Pretty.Base, + module Juvix.Compiler.Nockma.Pretty.Options, + module Juvix.Data.PPOutput, + ) +where + +import Juvix.Compiler.Nockma.Language +import Juvix.Compiler.Nockma.Pretty.Base +import Juvix.Compiler.Nockma.Pretty.Options +import Juvix.Data.PPOutput +import Juvix.Prelude +import Prettyprinter.Render.Terminal qualified as Ansi + +ppOutDefault :: (PrettyCode c) => c -> AnsiText +ppOutDefault = mkAnsiText . PPOutput . doc defaultOptions + +ppOut :: (CanonicalProjection a Options, PrettyCode c) => a -> c -> AnsiText +ppOut o = mkAnsiText . PPOutput . doc (project o) + +ppTrace :: (PrettyCode c) => c -> Text +ppTrace = + Ansi.renderStrict . reAnnotateS stylize . layoutPretty defaultLayoutOptions . doc defaultOptions + +ppPrint :: (PrettyCode c) => c -> Text +ppPrint = renderStrict . toTextStream . ppOutDefault diff --git a/src/Juvix/Compiler/Nockma/Pretty/Base.hs b/src/Juvix/Compiler/Nockma/Pretty/Base.hs new file mode 100644 index 0000000000..926ce130ad --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Pretty/Base.hs @@ -0,0 +1,79 @@ +module Juvix.Compiler.Nockma.Pretty.Base + ( module Juvix.Compiler.Nockma.Pretty.Base, + module Juvix.Data.CodeAnn, + module Juvix.Compiler.Nockma.Pretty.Options, + ) +where + +import Juvix.Compiler.Nockma.Language +import Juvix.Compiler.Nockma.Pretty.Options +import Juvix.Data.CodeAnn +import Juvix.Prelude hiding (Atom, Path) + +doc :: (PrettyCode c) => Options -> c -> Doc Ann +doc opts = + run + . runReader opts + . ppCode + +class PrettyCode c where + ppCode :: (Member (Reader Options) r) => c -> Sem r (Doc Ann) + +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) + . failFromError @(ErrNockNatural a) + $ do + h' <- failMaybe (h ^. unIrrelevant) + case h' of + AtomHintOp -> nockOp atm >>= ppCode + AtomHintPath -> nockPath atm >>= ppCode + AtomHintBool + | atm == nockTrue -> return (annotate (AnnKind KNameInductive) "true") + | atm == nockFalse -> return (annotate (AnnKind KNameAxiom) "false") + | otherwise -> fail + AtomHintNil -> return (annotate (AnnKind KNameConstructor) "nil") + +instance PrettyCode Natural where + ppCode = return . pretty + +instance PrettyCode Path where + ppCode = \case + [] -> return "S" + ds -> mconcatMapM ppCode ds + +instance PrettyCode Direction where + ppCode = + return . \case + L -> annotate (AnnKind KNameAxiom) "L" + R -> annotate AnnKeyword "R" + +instance PrettyCode NockOp where + ppCode = + return . annotate (AnnKind KNameFunction) . pretty + +instance (PrettyCode a, NockNatural a) => PrettyCode (Cell a) where + ppCode c = do + m <- asks (^. optPrettyMode) + inside <- case m of + AllDelimiters -> do + l' <- ppCode (c ^. cellLeft) + r' <- ppCode (c ^. cellRight) + return (l' <+> r') + MinimizeDelimiters -> sep <$> mapM ppCode (unfoldCell c) + return (oneLineOrNextBrackets inside) + +unfoldCell :: Cell a -> NonEmpty (Term a) +unfoldCell c = c ^. cellLeft :| go [] (c ^. cellRight) + where + go :: [Term a] -> Term a -> [Term a] + go acc = \case + t@TermAtom {} -> reverse (t : acc) + TermCell (Cell l r) -> go (l : acc) r + +instance (PrettyCode a, NockNatural a) => PrettyCode (Term a) where + ppCode = \case + TermAtom t -> ppCode t + TermCell c -> ppCode c diff --git a/src/Juvix/Compiler/Nockma/Pretty/Options.hs b/src/Juvix/Compiler/Nockma/Pretty/Options.hs new file mode 100644 index 0000000000..6b2ba92d63 --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Pretty/Options.hs @@ -0,0 +1,32 @@ +module Juvix.Compiler.Nockma.Pretty.Options where + +import Juvix.Prelude + +data PrettyMode + = MinimizeDelimiters + | AllDelimiters + deriving stock (Data) + +defaultOptions :: Options +defaultOptions = + Options + { _optPrettyMode = MinimizeDelimiters + } + +newtype Options = Options + { _optPrettyMode :: PrettyMode + } + +traceOptions :: Options +traceOptions = + Options + { _optPrettyMode = AllDelimiters + } + +makeLenses ''Options + +fromGenericOptions :: GenericOptions -> Options +fromGenericOptions GenericOptions {} = defaultOptions + +instance CanonicalProjection GenericOptions Options where + project = fromGenericOptions diff --git a/src/Juvix/Compiler/Nockma/Stdlib.hs b/src/Juvix/Compiler/Nockma/Stdlib.hs new file mode 100644 index 0000000000..511dcf68e4 --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Stdlib.hs @@ -0,0 +1,301 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Juvix.Compiler.Nockma.Stdlib where + +import Juvix.Compiler.Nockma.Translation.FromSource.QQ +import Juvix.Prelude + +stdlib :: Term Natural +stdlib = + [nock| +[ [ [ 7 + [ 8 + [1 1 1] + [ 1 + 8 + [1 0] + 8 + [ 1 + 6 + [5 [1 0] 0 60] + [0 6] + 9 + 2 + 10 + [60 8 [9 342 0 31] 9 2 10 [6 0 124] 0 2] + 10 + [6 8 [9 20 0 31] 9 2 10 [6 [0 125] 0 14] 0 2] + 0 + 1 + ] + 9 + 2 + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 7.107.949 [0 7] 0] + 0 + 1 + ] + [ [ 7 + [ 8 + [1 0 0] + [ 1 + 6 + [5 [1 0] 0 12] + [0 13] + 9 + 2 + 10 + [6 [8 [9 342 0 7] 9 2 10 [6 0 28] 0 2] 4 0 13] + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.579.297 [0 7] 0] + 0 + 1 + ] + [ [ 7 + [ 8 + [1 0 0] + [ 1 + 6 + [5 [0 12] 0 13] + [1 0] + 6 + [8 [9 343 0 7] 9 2 10 [6 [0 28] 0 29] 0 2] + [1 0] + 1 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.648.940 [0 7] 0] + 0 + 1 + ] + [ 7 + [ 8 + [1 1 1] + [ 1 + 6 + [5 [1 0] 0 13] + [0 0] + 8 + [1 0] + 8 + [ 1 + 6 + [8 [9 343 0 31] 9 2 10 [6 [0 124] 0 125] 0 2] + [0 6] + 9 + 2 + 10 + [60 8 [9 47 0 31] 9 2 10 [6 [0 124] 0 125] 0 2] + 10 + [6 4 0 6] + 0 + 1 + ] + 9 + 2 + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 7.760.228 [0 7] 0] + 0 + 1 + ] + [ 7 + [ 8 + [1 0] + [ 1 + 6 + [5 [1 0] 0 6] + [0 0] + 8 + [1 0] + 8 + [1 6 [5 [0 30] 4 0 6] [0 6] 9 2 10 [6 4 0 6] 0 1] + 9 + 2 + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.514.020 [0 7] 0] + 0 + 1 + ] + 7 + [ 8 + [1 0 0] + [ 1 + 6 + [6 [5 [0 12] 0 13] [1 1] 1 0] + [ 6 + [ 8 + [ 1 + 6 + [5 [1 0] 0 28] + [1 0] + 6 + [ 6 + [6 [5 [1 0] 0 29] [1 1] 1 0] + [ 6 + [ 9 + 2 + 10 + [ 14 + [8 [9 342 0 15] 9 2 10 [6 0 60] 0 2] + 8 + [9 342 0 15] + 9 + 2 + 10 + [6 0 61] + 0 + 2 + ] + 0 + 1 + ] + [1 0] + 1 + 1 + ] + 1 + 1 + ] + [1 0] + 1 + 1 + ] + 9 + 2 + 0 + 1 + ] + [1 0] + 1 + 1 + ] + 1 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.845.548 [0 7] 0] + 0 + 1 + ] + 7 + [8 [1 0 0] [1 6 [8 [9 84 0 7] 9 2 10 [6 [0 28] 0 29] 0 2] [1 1] 1 0] 0 1] + 11 + [1.953.718.630 1 6.845.543 [0 7] 0] + 0 + 1 + ] + [ 7 + [ 8 + [1 0 0] + [1 6 [8 [9 343 0 7] 9 2 10 [6 [0 28] 0 29] 0 2] [1 1] 1 0] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.648.935 [0 7] 0] + 0 + 1 + ] + [ 7 + [ 8 + [1 1 1] + [ 1 + 6 + [5 [1 0] 0 13] + [0 0] + 8 + [9 47 0 7] + 9 + 2 + 10 + [ 6 + [0 28] + 7 + [0 3] + 8 + [9 4 0 7] + 9 + 2 + 10 + [6 [0 29] 7 [0 3] 8 [9 170 0 7] 9 2 10 [6 [0 28] 0 29] 0 2] + 0 + 2 + ] + 0 + 2 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.582.125 [0 7] 0] + 0 + 1 + ] + 7 + [ 8 + [1 0 0] + [ 1 + 6 + [5 [1 0] 0 13] + [0 12] + 9 + 2 + 10 + [ 6 + [8 [9 342 0 7] 9 2 10 [6 0 28] 0 2] + 8 + [9 342 0 7] + 9 + 2 + 10 + [6 0 29] + 0 + 2 + ] + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.452.595 [0 7] 0] + 0 + 1 + ] + [0 3] + 909 +] +|] diff --git a/src/Juvix/Compiler/Nockma/Translation/FromSource.hs b/src/Juvix/Compiler/Nockma/Translation/FromSource.hs new file mode 100644 index 0000000000..0d14136444 --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Translation/FromSource.hs @@ -0,0 +1,8 @@ +module Juvix.Compiler.Nockma.Translation.FromSource + ( module Juvix.Compiler.Nockma.Translation.FromSource.Base, + module Juvix.Compiler.Nockma.Translation.FromSource.QQ, + ) +where + +import Juvix.Compiler.Nockma.Translation.FromSource.Base +import Juvix.Compiler.Nockma.Translation.FromSource.QQ diff --git a/src/Juvix/Compiler/Nockma/Translation/FromSource/Base.hs b/src/Juvix/Compiler/Nockma/Translation/FromSource/Base.hs new file mode 100644 index 0000000000..759654d6b6 --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Translation/FromSource/Base.hs @@ -0,0 +1,173 @@ +module Juvix.Compiler.Nockma.Translation.FromSource.Base where + +import Data.HashMap.Internal.Strict qualified as HashMap +import Data.List.NonEmpty qualified as NonEmpty +import Data.Text qualified as Text +import Juvix.Compiler.Nockma.Language qualified as N +import Juvix.Parser.Error +import Juvix.Prelude hiding (Atom, many, some) +import Juvix.Prelude.Parsing hiding (runParser) +import Juvix.Prelude.Pretty +import Text.Megaparsec qualified as P +import Text.Megaparsec.Char.Lexer qualified as L + +type Parser = Parsec Void Text + +fromMegaParsecError :: Either MegaparsecError a -> a +fromMegaParsecError = \case + Left e -> error (prettyText e) + Right a -> a + +parseText :: Text -> Either MegaparsecError (N.Term Natural) +parseText = runParser "" + +parseReplText :: Text -> Either MegaparsecError (N.ReplTerm Natural) +parseReplText = runParserFor replTerm "" + +parseProgramFile :: (MonadIO m) => FilePath -> m (Either MegaparsecError (N.Program Natural)) +parseProgramFile fp = do + txt <- readFile fp + return (runParserProgram fp txt) + +parseReplStatement :: Text -> Either MegaparsecError (N.ReplStatement Natural) +parseReplStatement = runParserFor replStatement "" + +runParserProgram :: FilePath -> Text -> Either MegaparsecError (N.Program Natural) +runParserProgram = runParserFor program + +runParserFor :: Parser a -> FilePath -> Text -> Either MegaparsecError a +runParserFor p f input = case P.runParser (spaceConsumer >> p <* eof) f input of + Left err -> Left (MegaparsecError err) + Right t -> Right t + +runParser :: FilePath -> Text -> Either MegaparsecError (N.Term Natural) +runParser = runParserFor term + +spaceConsumer :: Parser () +spaceConsumer = L.space space1 empty empty + +lexeme :: Parser a -> Parser a +lexeme = L.lexeme spaceConsumer + +symbol :: Text -> Parser Text +symbol = L.symbol spaceConsumer + +lsbracket :: Parser () +lsbracket = void (lexeme "[") + +rsbracket :: Parser () +rsbracket = void (lexeme "]") + +dottedNatural :: Parser Natural +dottedNatural = lexeme $ do + firstDigit <- digit + rest <- many (digit <|> dotAndDigit) + return (foldl' (\acc n -> acc * 10 + fromIntegral (digitToInt n)) 0 (firstDigit : rest)) + where + dotAndDigit :: Parser Char + dotAndDigit = char '.' *> satisfy isDigit + + digit :: Parser Char + digit = satisfy isDigit + +atomOp :: Parser (N.Atom Natural) +atomOp = do + op' <- choice [symbol opName $> op | (opName, op) <- HashMap.toList N.atomOps] + return (N.Atom (N.serializeNockOp op') (Irrelevant (Just N.AtomHintOp))) + +atomDirection :: Parser (N.Atom Natural) +atomDirection = do + dirs <- + symbol "S" $> [] + <|> NonEmpty.toList <$> some (choice [symbol "L" $> N.L, symbol "R" $> N.R]) + return (N.Atom (N.serializePath dirs) (Irrelevant (Just N.AtomHintPath))) + +atomNat :: Parser (N.Atom Natural) +atomNat = (\n -> N.Atom n (Irrelevant Nothing)) <$> dottedNatural + +atomBool :: Parser (N.Atom Natural) +atomBool = + choice + [ symbol "true" $> N.nockTrue, + symbol "false" $> N.nockFalse + ] + +atomNil :: Parser (N.Atom Natural) +atomNil = symbol "nil" $> N.nockNil + +patom :: Parser (N.Atom Natural) +patom = + atomOp + <|> atomNat + <|> atomDirection + <|> atomBool + <|> atomNil + +cell :: Parser (N.Cell Natural) +cell = do + lsbracket + firstTerm <- term + restTerms <- some term + rsbracket + return (buildCell firstTerm restTerms) + where + buildCell :: N.Term Natural -> NonEmpty (N.Term Natural) -> N.Cell Natural + buildCell h = \case + x :| [] -> N.Cell h x + y :| (w : ws) -> N.Cell h (N.TermCell (buildCell y (w :| ws))) + +term :: Parser (N.Term Natural) +term = + N.TermAtom <$> patom + <|> N.TermCell <$> cell + +assig :: Parser (N.Assignment Natural) +assig = do + n <- name + symbol ":=" + t <- term + return + N.Assignment + { _assignmentName = n, + _assignmentBody = t + } + +program :: Parser (N.Program Natural) +program = N.Program <$> many statement <* eof + where + statement :: Parser (N.Statement Natural) + statement = + P.try (N.StatementAssignment <$> assig) + <|> N.StatementStandalone <$> term + +name :: Parser Text +name = lexeme $ do + h <- P.satisfy isLetter + hs <- P.takeWhileP Nothing isAlphaNum + return (Text.cons h hs) + +withStack :: Parser (N.WithStack Natural) +withStack = do + st <- replTerm + symbol "/" + tm <- replTerm + return + N.WithStack + { _withStackStack = st, + _withStackTerm = tm + } + +replExpression :: Parser (N.ReplExpression Natural) +replExpression = + N.ReplExpressionWithStack <$> P.try withStack + <|> N.ReplExpressionTerm <$> replTerm + +replStatement :: Parser (N.ReplStatement Natural) +replStatement = + N.ReplStatementAssignment <$> P.try assig + <|> N.ReplStatementExpression <$> replExpression + +replTerm :: Parser (N.ReplTerm Natural) +replTerm = + N.ReplName <$> name + <|> N.ReplTerm <$> term diff --git a/src/Juvix/Compiler/Nockma/Translation/FromSource/QQ.hs b/src/Juvix/Compiler/Nockma/Translation/FromSource/QQ.hs new file mode 100644 index 0000000000..5d6ffff438 --- /dev/null +++ b/src/Juvix/Compiler/Nockma/Translation/FromSource/QQ.hs @@ -0,0 +1,27 @@ +module Juvix.Compiler.Nockma.Translation.FromSource.QQ + ( module Juvix.Compiler.Nockma.Translation.FromSource.QQ, + module Juvix.Compiler.Nockma.Language, + ) +where + +import Control.Monad.Fail qualified as M +import Juvix.Compiler.Nockma.Language +import Juvix.Compiler.Nockma.Translation.FromSource.Base +import Juvix.Prelude +import Language.Haskell.TH.Quote +import Language.Haskell.TH.Syntax + +nock :: QuasiQuoter +nock = + QuasiQuoter + { quotePat = err, + quoteDec = err, + quoteType = err, + quoteExp = qqNockTerm + } + where + err :: String -> Q a + err = const (M.fail "QuasiQuote `nock` can only be used as an expression") + + qqNockTerm :: String -> Q Exp + qqNockTerm = lift . fromMegaParsecError . parseText . pack diff --git a/src/Juvix/Data/CodeAnn.hs b/src/Juvix/Data/CodeAnn.hs index 182890d798..9ff390128e 100644 --- a/src/Juvix/Data/CodeAnn.hs +++ b/src/Juvix/Data/CodeAnn.hs @@ -1,5 +1,6 @@ module Juvix.Data.CodeAnn ( module Juvix.Data.CodeAnn, + module Juvix.Data.NameKind, module Juvix.Prelude.Pretty, ) where @@ -9,7 +10,7 @@ import Juvix.Data.Keyword import Juvix.Data.NameKind import Juvix.Extra.Strings qualified as Str import Juvix.Prelude -import Juvix.Prelude.Pretty hiding (braces, group, list, parens) +import Juvix.Prelude.Pretty hiding (braces, brackets, group, list, parens) import Prettyprinter.Render.Terminal (Color (..), bold, colorDull) type Ann = CodeAnn @@ -228,6 +229,9 @@ kwAt = delimiter Str.at_ code :: Doc Ann -> Doc Ann code = annotate AnnCode +brackets :: Doc Ann -> Doc Ann +brackets = enclose kwBracketL kwBracketR + braces :: Doc Ann -> Doc Ann braces = enclose kwBraceL kwBraceR diff --git a/src/Juvix/Data/Effect/Fail.hs b/src/Juvix/Data/Effect/Fail.hs index b872edc270..42e3835d96 100644 --- a/src/Juvix/Data/Effect/Fail.hs +++ b/src/Juvix/Data/Effect/Fail.hs @@ -20,9 +20,21 @@ runFailDefault :: a -> Sem (Fail ': r) a -> Sem r a -runFailDefault defaultVal = fmap (fromMaybe defaultVal . (^? _Right)) . runError @() . reinterpret (\Fail -> throw ()) +runFailDefault defaultVal = runFailDefaultM (return defaultVal) {-# INLINE runFailDefault #-} +-- | Run a 'Fail' effect with a default value. +runFailDefaultM :: + Sem r a -> + Sem (Fail ': r) a -> + Sem r a +runFailDefaultM defaultVal s = do + x <- runError @() (reinterpret (\Fail -> throw ()) s) + case x of + Left {} -> defaultVal + Right y -> return y +{-# INLINE runFailDefaultM #-} + ignoreFail :: Sem (Fail ': r) a -> Sem r () @@ -56,3 +68,17 @@ failFromException m = do case r of Left {} -> fail Right a -> return a + +failFromError :: forall e r a. (Member Fail r) => Sem (Error e ': r) a -> Sem r a +failFromError s = do + res <- runError s + case res of + Left {} -> fail + Right x -> return x + +failWithError :: forall e r a. (Member (Error e) r) => e -> Sem (Fail ': r) a -> Sem r a +failWithError err s = do + res <- runFail s + case res of + Nothing -> throw err + Just x -> return x diff --git a/src/Juvix/Data/Emacs/SExp.hs b/src/Juvix/Data/Emacs/SExp.hs index b582c36e24..d798849da2 100644 --- a/src/Juvix/Data/Emacs/SExp.hs +++ b/src/Juvix/Data/Emacs/SExp.hs @@ -2,7 +2,6 @@ module Juvix.Data.Emacs.SExp where import Juvix.Prelude import Juvix.Prelude.Pretty -import Prettyprinter.Render.Text class ToSExp a where toSExp :: a -> SExp diff --git a/src/Juvix/Data/Error/GenericError.hs b/src/Juvix/Data/Error/GenericError.hs index 3741c6ec64..5c722e099a 100644 --- a/src/Juvix/Data/Error/GenericError.hs +++ b/src/Juvix/Data/Error/GenericError.hs @@ -7,7 +7,6 @@ import Juvix.Data.Loc import Juvix.Prelude.Base import Juvix.Prelude.Pretty import Prettyprinter.Render.Terminal qualified as Ansi -import Prettyprinter.Render.Text import System.Console.ANSI qualified as Ansi data GenericError = GenericError diff --git a/src/Juvix/Data/FileExt.hs b/src/Juvix/Data/FileExt.hs index 4fbb8bc041..dbe62b88e6 100644 --- a/src/Juvix/Data/FileExt.hs +++ b/src/Juvix/Data/FileExt.hs @@ -22,6 +22,7 @@ data FileExt | FileExtMarkdown | FileExtHtml | FileExtCss + | FileExtNock deriving stock (Eq) juvixFileExt :: (IsString a) => a @@ -66,6 +67,9 @@ cFileExt = ".c" cssFileExt :: (IsString a) => a cssFileExt = ".css" +nockFileExt :: (IsString a) => a +nockFileExt = ".nock" + fileExtToText :: FileExt -> Text fileExtToText = \case FileExtJuvix -> juvixFileExt @@ -82,6 +86,7 @@ fileExtToText = \case FileExtMarkdown -> markdownFileExt FileExtHtml -> htmlFileExt FileExtCss -> cssFileExt + FileExtNock -> nockFileExt toMetavar :: FileExt -> String toMetavar = \case @@ -99,6 +104,7 @@ toMetavar = \case FileExtMarkdown -> "MARKDOWN_FILE" FileExtHtml -> "HTML_FILE" FileExtCss -> "CSS_FILE" + FileExtNock -> "NOCK_FILE" instance Show FileExt where show = Text.unpack . fileExtToText @@ -150,6 +156,9 @@ isHtmlFile = (== Just htmlFileExt) . fileExtension isCssFile :: Path b File -> Bool isCssFile = (== Just cssFileExt) . fileExtension +isNockFile :: Path b File -> Bool +isNockFile = (== Just nockFileExt) . fileExtension + toFileExt :: Path b File -> Maybe FileExt toFileExt p | isJuvixFile p = Just FileExtJuvix @@ -166,6 +175,7 @@ toFileExt p | isMarkdownFile p = Just FileExtMarkdown | isHtmlFile p = Just FileExtHtml | isCssFile p = Just FileExtCss + | isNockFile p = Just FileExtNock | otherwise = Nothing fileExtension' :: Path b File -> String diff --git a/src/Juvix/Data/Irrelevant.hs b/src/Juvix/Data/Irrelevant.hs index 94a01d829c..8b7c948b1e 100644 --- a/src/Juvix/Data/Irrelevant.hs +++ b/src/Juvix/Data/Irrelevant.hs @@ -11,7 +11,7 @@ import Prelude (show) newtype Irrelevant a = Irrelevant { _unIrrelevant :: a } - deriving newtype (Generic) + deriving stock (Generic, Data, Lift) instance (Serialize a) => Serialize (Irrelevant a) where put (Irrelevant x) = S.put x diff --git a/src/Juvix/Prelude/Base.hs b/src/Juvix/Prelude/Base.hs index 9ed64fcba7..43e7233d34 100644 --- a/src/Juvix/Prelude/Base.hs +++ b/src/Juvix/Prelude/Base.hs @@ -160,7 +160,7 @@ import GHC.Generics (Generic) import GHC.Num import GHC.Real import GHC.Stack.Types -import Language.Haskell.TH.Syntax (Lift) +import Language.Haskell.TH.Syntax (Exp, Lift, Q) import Lens.Micro.Platform import Path import Path.IO qualified as Path hiding (getCurrentDir, setCurrentDir, withCurrentDir) @@ -556,3 +556,6 @@ indexedByInt getIx l = IntMap.fromList [(getIx i, i) | i <- toList l] indexedByHash :: (Foldable f, Hashable k) => (a -> k) -> f a -> HashMap k a indexedByHash getIx l = HashMap.fromList [(getIx i, i) | i <- toList l] + +hashMap :: (Foldable f, Hashable k) => f (k, v) -> HashMap k v +hashMap = HashMap.fromList . toList diff --git a/src/Juvix/Prelude/Lens.hs b/src/Juvix/Prelude/Lens.hs index b2ea79a8ae..af1371eabf 100644 --- a/src/Juvix/Prelude/Lens.hs +++ b/src/Juvix/Prelude/Lens.hs @@ -25,3 +25,6 @@ overM :: (Applicative m) => Lens' a b -> (b -> m b) -> a -> m a overM l f a = do a' <- f (a ^. l) return $ set l a' a + +setAndRemember :: LensLike ((,) a) s t a b -> b -> s -> (a, t) +setAndRemember = (<<.~) diff --git a/src/Juvix/Prelude/Pretty.hs b/src/Juvix/Prelude/Pretty.hs index 347f6919f4..2d49538fc9 100644 --- a/src/Juvix/Prelude/Pretty.hs +++ b/src/Juvix/Prelude/Pretty.hs @@ -3,6 +3,7 @@ module Juvix.Prelude.Pretty module Prettyprinter, module Prettyprinter.Render.Terminal, module Prettyprinter.Util, + module Prettyprinter.Render.Text, ) where @@ -12,6 +13,7 @@ import Prettyprinter hiding (concatWith, defaultLayoutOptions, hsep, sep, vsep) import Prettyprinter qualified as PP import Prettyprinter.Render.Terminal (AnsiStyle) import Prettyprinter.Render.Terminal qualified as Ansi +import Prettyprinter.Render.Text (renderStrict) import Prettyprinter.Render.Text qualified as Text import Prettyprinter.Util (reflow) import Prelude @@ -183,14 +185,20 @@ spaceOrEmpty = flatAlt (pretty ' ') mempty oneLineOrNext :: Doc a -> Doc a oneLineOrNext x = PP.group (flatAlt (line <> indent' x) (space <> x)) +oneLineOrNextBrackets :: Doc a -> Doc a +oneLineOrNextBrackets = oneLineOrNextDelims lbracket rbracket + oneLineOrNextNoIndent :: Doc a -> Doc a oneLineOrNextNoIndent x = PP.group (flatAlt (line <> x) (space <> x)) oneLineOrNextBlock :: Doc a -> Doc a oneLineOrNextBlock x = PP.group (flatAlt (line <> indent' x <> line) (space <> x <> space)) +oneLineOrNextDelims :: Doc a -> Doc a -> Doc a -> Doc a +oneLineOrNextDelims l r x = PP.group (flatAlt (l <> line <> indent' x <> line <> r) (l <> x <> r)) + oneLineOrNextBraces :: Doc a -> Doc a -oneLineOrNextBraces x = PP.group (flatAlt (lbrace <> line <> indent' x <> line <> rbrace) (lbrace <> x <> rbrace)) +oneLineOrNextBraces = oneLineOrNextDelims lbrace rbrace nextLine :: Doc a -> Doc a nextLine x = PP.group (line <> x) diff --git a/test/Base.hs b/test/Base.hs index 781b65973f..f7acb02ef3 100644 --- a/test/Base.hs +++ b/test/Base.hs @@ -22,7 +22,8 @@ import Juvix.Extra.Paths hiding (rootBuildDir) import Juvix.Prelude hiding (assert) import Juvix.Prelude.Env import Test.Tasty -import Test.Tasty.HUnit +import Test.Tasty.HUnit hiding (assertFailure) +import Test.Tasty.HUnit qualified as HUnit data AssertionDescr = Single Assertion @@ -111,3 +112,6 @@ testRunIOEitherTermination :: testRunIOEitherTermination entry = testRunIOEither entry . evalTermination iniTerminationState + +assertFailure :: (MonadIO m) => String -> m a +assertFailure = liftIO . HUnit.assertFailure diff --git a/test/Main.hs b/test/Main.hs index a6ff0b951c..94bd58b744 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -10,6 +10,7 @@ import Examples qualified import Format qualified import Formatter qualified import Internal qualified +import Nockma qualified import Package qualified import Parsing qualified import Resolver qualified @@ -45,7 +46,8 @@ fastTests = Format.allTests, Formatter.allTests, Package.allTests, - BackendMarkdown.allTests + BackendMarkdown.allTests, + Nockma.allTests ] main :: IO () diff --git a/test/Nockma.hs b/test/Nockma.hs new file mode 100644 index 0000000000..8b3e3dd737 --- /dev/null +++ b/test/Nockma.hs @@ -0,0 +1,8 @@ +module Nockma where + +import Base +import Nockma.Eval qualified as Eval +import Nockma.Parse qualified as Parse + +allTests :: TestTree +allTests = testGroup "Nockma tests" [Parse.allTests, Eval.allTests] diff --git a/test/Nockma/Eval.hs b/test/Nockma/Eval.hs new file mode 100644 index 0000000000..836c5a6a7f --- /dev/null +++ b/test/Nockma/Eval.hs @@ -0,0 +1,7 @@ +module Nockma.Eval where + +import Base +import Nockma.Eval.Positive qualified as P + +allTests :: TestTree +allTests = testGroup "Nockma eval" [P.allTests] diff --git a/test/Nockma/Eval/Positive.hs b/test/Nockma/Eval/Positive.hs new file mode 100644 index 0000000000..8455c10466 --- /dev/null +++ b/test/Nockma/Eval/Positive.hs @@ -0,0 +1,70 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Nockma.Eval.Positive where + +import Base hiding (Path) +import Juvix.Compiler.Nockma.Evaluator +import Juvix.Compiler.Nockma.Language +import Juvix.Compiler.Nockma.Pretty +import Juvix.Compiler.Nockma.Translation.FromSource.QQ + +type Check = Sem '[Reader (Term Natural), Embed IO] + +data Test = Test + { _testName :: Text, + _testProgramSubject :: Term Natural, + _testProgramFormula :: Term Natural, + _testCheck :: Check () + } + +makeLenses ''Test + +allTests :: TestTree +allTests = testGroup "Nockma eval unit positive" (map mk tests) + where + mk :: Test -> TestTree + mk Test {..} = testCase (unpack _testName) $ do + let evalResult = + run + . runError @(ErrNockNatural Natural) + . runError @NockEvalError + $ 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) + Right res -> runM (runReader res _testCheck) + +eqNock :: Term Natural -> Check () +eqNock expected = do + actual <- ask + unless (expected == actual) (err actual) + where + err :: Term Natural -> Check () + err actual = do + let msg = + "Expected:\n" + <> ppTrace expected + <> "\nBut got:\n" + <> ppTrace actual + assertFailure (unpack msg) + +tests :: [Test] +tests = + [ Test "address" [nock| [0 1] |] [nock| [[@ R] [@ L]] |] (eqNock [nock| [1 0] |]), + Test "address nested" [nock| [0 1 2 3 4 5] |] [nock| [@ RRRRR] |] (eqNock [nock| 5 |]), + Test "quote" [nock| [0 1] |] [nock| [quote [1 0]] |] (eqNock [nock| [1 0] |]), + Test "apply" [nock| [0 1] |] [nock| [apply [@ S] [quote [@ R]]] |] (eqNock [nock| 1 |]), + Test "isCell atom" [nock| [0 1] |] [nock| [isCell 11] |] (eqNock [nock| false |]), + Test "isCell cell" [nock| [0 1] |] [nock| [isCell [1 0]] |] (eqNock [nock| true |]), + Test "suc" [nock| [0 1] |] [nock| [suc [quote 1]] |] (eqNock [nock| 2 |]), + Test "eq" [nock| [0 1] |] [nock| [= [1 0] [1 0]] |] (eqNock [nock| true |]), + Test "eq" [nock| [0 1] |] [nock| [= [1 0] [0 1]] |] (eqNock [nock| false |]), + Test "if" [nock| [0 1] |] [nock| [if [quote true] [@ L] [@ R]] |] (eqNock [nock| 0 |]), + Test "if" [nock| [0 1] |] [nock| [if [quote false] [@ L] [@ R]] |] (eqNock [nock| 1 |]), + Test "seq" [nock| [0 1] |] [nock| [seq [[suc [@ L]] [@ R]] [suc [@ L]]] |] (eqNock [nock| 2 |]), + Test "push" [nock| [0 1] |] [nock| [push [[suc [@ L]] [@ S]]] |] (eqNock [nock| [1 0 1] |]), + 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 [@ LLLL] [quote 1]] |] (eqNock [nock| 1 |]) + ] diff --git a/test/Nockma/Parse.hs b/test/Nockma/Parse.hs new file mode 100644 index 0000000000..e81c1aa086 --- /dev/null +++ b/test/Nockma/Parse.hs @@ -0,0 +1,7 @@ +module Nockma.Parse where + +import Base +import Nockma.Parse.Positive qualified as P + +allTests :: TestTree +allTests = testGroup "Nockma parse" [P.allTests] diff --git a/test/Nockma/Parse/Positive.hs b/test/Nockma/Parse/Positive.hs new file mode 100644 index 0000000000..7a892161b9 --- /dev/null +++ b/test/Nockma/Parse/Positive.hs @@ -0,0 +1,56 @@ +module Nockma.Parse.Positive where + +import Base +import Data.ByteString qualified as BS +import Juvix.Compiler.Nockma.Language hiding (Path) +import Juvix.Compiler.Nockma.Pretty (ppPrint) +import Juvix.Compiler.Nockma.Translation.FromSource (parseText) +import Juvix.Parser.Error +import Juvix.Prelude.Pretty +import Text.Megaparsec + +data PosTest = PosTest + { _name :: String, + _relDir :: Path Rel Dir, + _file :: Path Rel File + } + +makeLenses ''PosTest + +root :: Path Abs Dir +root = relToProject $(mkRelDir "tests/nockma/positive") + +testDescr :: PosTest -> TestDescr +testDescr PosTest {..} = + let tRoot = root _relDir + file' = tRoot _file + in TestDescr + { _testName = _name, + _testRoot = tRoot, + _testAssertion = Steps $ \step -> do + step "Parsing" + txt <- decodeUtf8 <$> BS.readFile (toFilePath file') + nockmaTerm <- assertParse txt + + step "Pretty printing" + let ppTerm = ppPrint nockmaTerm + + step "parse . pretty . parse == parse" + prettyNockmaTerm <- assertParse ppTerm + assertEqual "expected equal" nockmaTerm prettyNockmaTerm + } + +assertParse :: Text -> IO (Term Natural) +assertParse txt = case parseText txt of + Left (MegaparsecError b) -> assertFailure ("Nockma parsing failed " <> unpack (prettyText (errorBundlePretty b))) + Right t -> return t + +allTests :: TestTree +allTests = testGroup "Nockma parse positive" (map (mkTest . testDescr) tests) + +tests :: [PosTest] +tests = + [ PosTest "Identity" $(mkRelDir ".") $(mkRelFile "Identity.nock"), + PosTest "Identity Pretty" $(mkRelDir ".") $(mkRelFile "IdentityPretty.pnock"), + PosTest "Stdlib" $(mkRelDir ".") $(mkRelFile "Stdlib.nock") + ] diff --git a/tests/nockma/positive/Identity.nock b/tests/nockma/positive/Identity.nock new file mode 100644 index 0000000000..46b82807f6 --- /dev/null +++ b/tests/nockma/positive/Identity.nock @@ -0,0 +1,4 @@ +[8 + [1 [[0 6] 0 0]] + [9 2 0 2] +] diff --git a/tests/nockma/positive/IdentityPretty.pnock b/tests/nockma/positive/IdentityPretty.pnock new file mode 100644 index 0000000000..229441ece4 --- /dev/null +++ b/tests/nockma/positive/IdentityPretty.pnock @@ -0,0 +1,4 @@ +[push + [quote [[@ 6] 0 0]] + [call 2 0 2] +] diff --git a/tests/nockma/positive/Stdlib.nock b/tests/nockma/positive/Stdlib.nock new file mode 100644 index 0000000000..4b37c8be45 --- /dev/null +++ b/tests/nockma/positive/Stdlib.nock @@ -0,0 +1,290 @@ +[ [ [ 7 + [ 8 + [1 1 1] + [ 1 + 8 + [1 0] + 8 + [ 1 + 6 + [5 [1 0] 0 60] + [0 6] + 9 + 2 + 10 + [60 8 [9 342 0 31] 9 2 10 [6 0 124] 0 2] + 10 + [6 8 [9 20 0 31] 9 2 10 [6 [0 125] 0 14] 0 2] + 0 + 1 + ] + 9 + 2 + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 7.107.949 [0 7] 0] + 0 + 1 + ] + [ [ 7 + [ 8 + [1 0 0] + [ 1 + 6 + [5 [1 0] 0 12] + [0 13] + 9 + 2 + 10 + [6 [8 [9 342 0 7] 9 2 10 [6 0 28] 0 2] 4 0 13] + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.579.297 [0 7] 0] + 0 + 1 + ] + [ [ 7 + [ 8 + [1 0 0] + [ 1 + 6 + [5 [0 12] 0 13] + [1 0] + 6 + [8 [9 343 0 7] 9 2 10 [6 [0 28] 0 29] 0 2] + [1 0] + 1 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.648.940 [0 7] 0] + 0 + 1 + ] + [ 7 + [ 8 + [1 1 1] + [ 1 + 6 + [5 [1 0] 0 13] + [0 0] + 8 + [1 0] + 8 + [ 1 + 6 + [8 [9 343 0 31] 9 2 10 [6 [0 124] 0 125] 0 2] + [0 6] + 9 + 2 + 10 + [60 8 [9 47 0 31] 9 2 10 [6 [0 124] 0 125] 0 2] + 10 + [6 4 0 6] + 0 + 1 + ] + 9 + 2 + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 7.760.228 [0 7] 0] + 0 + 1 + ] + [ 7 + [ 8 + [1 0] + [ 1 + 6 + [5 [1 0] 0 6] + [0 0] + 8 + [1 0] + 8 + [1 6 [5 [0 30] 4 0 6] [0 6] 9 2 10 [6 4 0 6] 0 1] + 9 + 2 + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.514.020 [0 7] 0] + 0 + 1 + ] + 7 + [ 8 + [1 0 0] + [ 1 + 6 + [6 [5 [0 12] 0 13] [1 1] 1 0] + [ 6 + [ 8 + [ 1 + 6 + [5 [1 0] 0 28] + [1 0] + 6 + [ 6 + [6 [5 [1 0] 0 29] [1 1] 1 0] + [ 6 + [ 9 + 2 + 10 + [ 14 + [8 [9 342 0 15] 9 2 10 [6 0 60] 0 2] + 8 + [9 342 0 15] + 9 + 2 + 10 + [6 0 61] + 0 + 2 + ] + 0 + 1 + ] + [1 0] + 1 + 1 + ] + 1 + 1 + ] + [1 0] + 1 + 1 + ] + 9 + 2 + 0 + 1 + ] + [1 0] + 1 + 1 + ] + 1 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.845.548 [0 7] 0] + 0 + 1 + ] + 7 + [8 [1 0 0] [1 6 [8 [9 84 0 7] 9 2 10 [6 [0 28] 0 29] 0 2] [1 1] 1 0] 0 1] + 11 + [1.953.718.630 1 6.845.543 [0 7] 0] + 0 + 1 + ] + [ 7 + [ 8 + [1 0 0] + [1 6 [8 [9 343 0 7] 9 2 10 [6 [0 28] 0 29] 0 2] [1 1] 1 0] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.648.935 [0 7] 0] + 0 + 1 + ] + [ 7 + [ 8 + [1 1 1] + [ 1 + 6 + [5 [1 0] 0 13] + [0 0] + 8 + [9 47 0 7] + 9 + 2 + 10 + [ 6 + [0 28] + 7 + [0 3] + 8 + [9 4 0 7] + 9 + 2 + 10 + [6 [0 29] 7 [0 3] 8 [9 170 0 7] 9 2 10 [6 [0 28] 0 29] 0 2] + 0 + 2 + ] + 0 + 2 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.582.125 [0 7] 0] + 0 + 1 + ] + 7 + [ 8 + [1 0 0] + [ 1 + 6 + [5 [1 0] 0 13] + [0 12] + 9 + 2 + 10 + [ 6 + [8 [9 342 0 7] 9 2 10 [6 0 28] 0 2] + 8 + [9 342 0 7] + 9 + 2 + 10 + [6 0 29] + 0 + 2 + ] + 0 + 1 + ] + 0 + 1 + ] + 11 + [1.953.718.630 1 6.452.595 [0 7] 0] + 0 + 1 + ] + [0 3] + 909 +]