packages feed

snail (empty) → 0.1.0.0

raw patch · 25 files changed

+1580/−0 lines, 25 filesdep +HUnitdep +QuickCheckdep +base

Dependencies added: HUnit, QuickCheck, base, containers, hspec, hspec-discover, megaparsec, mtl, raw-strings-qq, snail, text, text-display

Files

+ LICENSE view
@@ -0,0 +1,21 @@+MIT License++Copyright (c) 2023 Barry Moore II++Permission is hereby granted, free of charge, to any person obtaining a copy+of this software and associated documentation files (the "Software"), to deal+in the Software without restriction, including without limitation the rights+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell+copies of the Software, and to permit persons to whom the Software is+furnished to do so, subject to the following conditions:++The above copyright notice and this permission notice shall be included in all+copies or substantial portions of the Software.++THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE+SOFTWARE.
+ README.md view
@@ -0,0 +1,90 @@+# Snail++A no-semantics programming language for gastropods.++## Why?++My colleagues and I are going to start working through [Types and Progamming+Languages][tapl]. In the book you implement languages of varying feature sets.+The book implements these languages in OCaml, however I had this Lisp parser+essentially ready for awhile. There are a handful of "Write you a Scheme+Interpreters"-like tutorials and they all use a parser relatively similar to+this one. However, there are some pretty subtle issues with most of the ones I+have seen. For example, the two examples below parse as two lexemes in a lot of+examples. Even Haskell's parser has [this issue][haskell-parse-issue]!++```+(1a)+(1 a)+```++## Is this really a programming language?++From the ["Programming language" wikipedia page][pl-wikipedia],++> A programming language is a system of notation for writing computer programs.++> The description of a programming language is usually split into the two components of syntax (form) and semantics (meaning)++Snail is used for writing interpreters or compilers. However, it doesn't define+**any** semantics. So, maybe?++## Syntax (form)++Snail describes valid lexemes, text literals, and s-expressions. The valid+lexemes are approximately from R5RS Scheme but this may change in the future.+We also use Haskell's line and block comments. Here is a valid snail program,++```+-- Prints `hello "world"` to the console+(print "hello \"world\"")++-- Prints 3 to the console+(print (+ 1 2))++{-+  Defines a function to add two numbers+  Applies the function to generate 3+  Prints 3 to the console+-}+(let+  (f (lambda (x y) (+ x y)))+  (print (f 2 1)))++(quote hello)++(nil)++(print true)++(print false)++-- end comment+```++Reminder, this program has no semantics. It is your job to take Snail's+Abstract Syntax Tree (AST) and define the semantics of an interpreter or+compiler.++## Getting the AST++You can see some examples in `test/Snail/IOSpec.hs`, but you can put your snail+program into some file, let's say `hello.snail`. The following Haskell will print+the AST or print a failure,++```haskell+module Main where++import Snail++main :: IO ()+main = do+  eResults <- readSnailFile "./hello.snail"+  case eResults of+    Right ast -> print ast+    Left failureString -> print failureString+```++[tapl]: https://www.cis.upenn.edu/~bcpierce/tapl+[haskell-parse-issue]: https://twitter.com/chiroptical/status/1471568781906518018+[pl-wikipedia]: https://en.wikipedia.org/wiki/Programming_language
+ snail-files/basic.snail view
@@ -0,0 +1,24 @@+-- Prints `hello "world"` to the console+(print "hello \"world\"")++-- Prints 3 to the console+(print (+ 1 2))++{-+  Defines a function to add two numbers+  Applies the function to generate 3+  Prints 3 to the console+-}+(let+  (f (lambda (x y) (+ x y)))+  (print (f 2 1)))++(quote hello)++(nil)++(print true)++(print false)++-- end comment
+ snail-files/fail-comment.snail view
@@ -0,0 +1,1 @@+{- ...
+ snail-files/fail-empty.snail view
+ snail-files/fail-nil.snail view
@@ -0,0 +1,5 @@+()++nil++()
+ snail-files/fail-quotes-2.snail view
@@ -0,0 +1,1 @@+(print "hello """)
+ snail-files/fail-quotes-3.snail view
@@ -0,0 +1,1 @@+(print """")
+ snail-files/fail-quotes.snail view
@@ -0,0 +1,1 @@+(print "hello "world"")
+ snail-files/fail.snail view
@@ -0,0 +1,3 @@+(let+  (f (lambda (x y) (+ x y)))+  (print (f 2 1))
+ snail-files/nil.snail view
@@ -0,0 +1,1 @@+nil
+ snail.cabal view
@@ -0,0 +1,94 @@+cabal-version: 1.12++-- This file has been generated from package.yaml by hpack version 0.35.2.+--+-- see: https://github.com/sol/hpack++name:           snail+version:        0.1.0.0+synopsis:       A programming language with no semantics+description:    An s-expression parser and abstract syntax tree for a programming language with no semantics. If you wanted to write an interpreter or compiler you convert the AST into your own.+category:       Parsing+homepage:       https://github.com/chiroptical/snail#readme+bug-reports:    https://github.com/chiroptical/snail/issues+author:         Barry Moore II+maintainer:     chiroptical@gmail.com+copyright:      Barry Moore II+license:        MIT+license-file:   LICENSE+build-type:     Simple+extra-source-files:+    README.md+data-files:+    snail-files/basic.snail+    snail-files/fail-comment.snail+    snail-files/fail-empty.snail+    snail-files/fail-nil.snail+    snail-files/fail-quotes-2.snail+    snail-files/fail-quotes-3.snail+    snail-files/fail-quotes.snail+    snail-files/fail.snail+    snail-files/nil.snail+    test/files/fennel-reference.fnl+    test/files/learnxyz.cl+    test/files/r5rs_pitfalls.scm++source-repository head+  type: git+  location: https://github.com/chiroptical/snail++library+  exposed-modules:+      Snail+      Snail.Characters+      Snail.IO+      Snail.Lexer+      Snail.ToText+  other-modules:+      Paths_snail+  hs-source-dirs:+      src+  default-extensions:+      ImportQualifiedPost+      LambdaCase+      OverloadedStrings+  build-depends:+      QuickCheck >=2.14.3 && <2.15+    , base >=4.15 && <5+    , containers >=0.6.7 && <0.7+    , megaparsec >=9.3.1 && <9.4+    , mtl >=2.2.2 && <2.3+    , text >=2.0.2 && <2.1+    , text-display >=0.0.5 && <0.1+  default-language: Haskell2010++test-suite snail-test+  type: exitcode-stdio-1.0+  main-is: Spec.hs+  other-modules:+      Gen+      Snail.IOSpec+      Snail.LexerSpec+      Snail.ToTextSpec+      Paths_snail+  hs-source-dirs:+      test+  default-extensions:+      ImportQualifiedPost+      LambdaCase+      OverloadedStrings+  ghc-options: -threaded -rtsopts -with-rtsopts=-N+  build-depends:+      HUnit >=1.6.2 && <1.7+    , QuickCheck >=2.14.3 && <2.15+    , base >=4.15 && <5+    , containers >=0.6.7 && <0.7+    , hspec >=2.10.10 && <2.11+    , hspec-discover >=2.10.10 && <2.11+    , megaparsec >=9.3.1 && <9.4+    , mtl >=2.2.2 && <2.3+    , raw-strings-qq ==1.1.*+    , snail+    , text >=2.0.2 && <2.1+    , text-display >=0.0.5 && <0.1+  default-language: Haskell2010
+ src/Snail.hs view
@@ -0,0 +1,8 @@+module Snail (+    module X,+) where++import Snail.Characters as X+import Snail.IO as X+import Snail.Lexer as X+import Snail.ToText as X
+ src/Snail/Characters.hs view
@@ -0,0 +1,25 @@+module Snail.Characters where++-- | The initial character of any text+initialCharacter :: String+initialCharacter = ['a' .. 'z'] <> ['A' .. 'Z']++-- | ...+specialInitialCharacter :: String+specialInitialCharacter = "!$%&*/:<=>?^_~#,'"++-- | ...+peculiarCharacter :: String+peculiarCharacter = "+-."++-- | ...+digitCharacter :: String+digitCharacter = ['0' .. '9']++-- | ...+specialSubsequentCharacter :: String+specialSubsequentCharacter = "+-.@\\"++-- | ...+parenthesisStartingCharacter :: String+parenthesisStartingCharacter = "'`@#,"
+ src/Snail/IO.hs view
@@ -0,0 +1,13 @@+module Snail.IO where++import Data.Text.IO qualified as Text+import Snail.Lexer+import Text.Megaparsec++-- | Given a 'FilePath', attempt to parse 'SnailAst' from a file.+readSnailFile :: FilePath -> IO (Either String [SnailAst])+readSnailFile fp = do+    contents <- Text.readFile fp+    pure $ case parse snailAst (show fp) contents of+        Left parseErrorBundle -> Left $ errorBundlePretty parseErrorBundle+        Right sexprs -> Right sexprs
+ src/Snail/Lexer.hs view
@@ -0,0 +1,158 @@+{-+  Definitions++  * Lexer: `Text -> [Text]`+  * Token: The leaves in your AST, e.g. TextLiteral, Number, etc++  Here, we implement a structurally aware lexer that supports one token type+  (text literals) for convenience.+-}+module Snail.Lexer (+    -- * The parsers you should use+    SnailAst (..),+    sExpression,+    snailAst,++    -- * Exported for testing+    nonQuoteCharacter,+    textLiteral,+) where++import Data.Text (Text)+import Data.Text qualified as Text+import Data.Void+import Snail.Characters+import Text.Megaparsec hiding (token)+import Text.Megaparsec.Char+import Text.Megaparsec.Char.Lexer qualified as L++-- | TODO: 'Void' is the error type but we should use an explicit error type+type Parser = Parsec Void Text++{- | Megaparsec's 'skipLineComment' takes a prefix and skips lines that begin+ with that prefix+-}+skipLineComment :: Parser ()+skipLineComment = L.skipLineComment "--"++{- | Megaparsec's 'skipBlockComment' takes prefix and suffix and skips anything+ in between+-}+skipBlockComment :: Parser ()+skipBlockComment = L.skipBlockCommentNested "{-" "-}"++{- | Generate a parser for whitespace in a language with 'skipLineComment' and+ 'skipBlockComment'+-}+spaces :: Parser ()+spaces = L.space space1 skipLineComment skipBlockComment++-- | Parse a 'Text' verbatim+symbol :: Text -> Parser Text+symbol = L.symbol spaces++-- | Parse an S-Expression+parens :: Parser a -> Parser a+parens = between (symbol "(") (symbol ")")++{- | The list of valid token characters, note that we allow invalid tokens at+ this point+-}+validCharacter :: Parser Char+validCharacter =+    oneOf+        ( initialCharacter+            <> specialInitialCharacter+            <> digitCharacter+            <> specialSubsequentCharacter+        )++{-+    A possibly empty tree of s-expressions++    Technically,+    @+    Token (SourcePos {..}, "hello")+    @++    isn't a valid s-expression. This is,++    @+    SExpression [Token (SourcePos {..}, "hello")]+    @++    and this is also valid,++    @+    SExpression []+    @++    The 'Data.Tree.Tree' type in containers is non-empty which isn't exactly what we are looking for+-}+data SnailAst+    = Lexeme (SourcePos, Text)+    | TextLiteral (SourcePos, Text)+    | SExpression (Maybe Char) [SnailAst]+    deriving (Eq, Show)++{- | Any 'Text' object that starts with an appropriately valid character. This+ could be an variable or function name. For example, `hello` is a valid+ lexeme in the s-expression below.++ @+ (hello)+ @+-}+lexeme :: Parser SnailAst+lexeme = do+    sourcePosition <- getSourcePos+    lexeme' <- some validCharacter+    pure $ Lexeme (sourcePosition, Text.pack lexeme')++-- | An escaped quote to support nesting `"` inside a 'textLiteral'+escapedQuote :: Parser Text+escapedQuote = string "\\\""++-- | Matches any non-quote character+nonQuoteCharacter :: Parser Text+nonQuoteCharacter = do+    character <- anySingleBut '\"'+    pure $ Text.singleton character++quote :: Parser Char+quote = char '\"'++quotes :: Parser a -> Parser a+quotes = between quote quote++{- | Matches a literal text and supports nested quotes, e.g.++ @+ ("hello\"")+ @+-}+textLiteral :: Parser SnailAst+textLiteral = do+    sourcePosition <- getSourcePos+    mText <- quotes . optional $ some $ escapedQuote <|> nonQuoteCharacter+    notFollowedBy (validCharacter <|> char '\"')+    pure $ case mText of+        Nothing -> TextLiteral (sourcePosition, "")+        Just text -> TextLiteral (sourcePosition, Text.concat text)++{- | Parse one of the possible structures in 'SnailAst'. These are parsed+ recursively separated by 'spaces' in 'sExpression'.+-}+leaves :: Parser SnailAst+leaves = lexeme <|> textLiteral <|> sExpression++-- | Parse an 'SExpression'+sExpression :: Parser SnailAst+sExpression =+    SExpression+        <$> optional (oneOf parenthesisStartingCharacter)+        <*> parens (leaves `sepEndBy` spaces)++-- | Parse a valid snail file+snailAst :: Parser [SnailAst]+snailAst = (spaces *> sExpression `sepEndBy1` spaces) <* eof
+ src/Snail/ToText.hs view
@@ -0,0 +1,15 @@+module Snail.ToText (toText) where++import Data.Text+import Snail.Lexer++toText :: SnailAst -> Text+toText = \case+    TextLiteral (_, txt) -> "\"" <> txt <> "\""+    Lexeme (_, lexeme) -> lexeme+    SExpression Nothing exprs ->+        let txt = Data.Text.unwords $ toText <$> exprs+         in "(" <> txt <> ")"+    SExpression (Just c) exprs ->+        let txt = Data.Text.unwords $ toText <$> exprs+         in singleton c <> "(" <> txt <> ")"
+ test/Gen.hs view
@@ -0,0 +1,12 @@+module Gen where++import Data.Text (Text)+import Data.Text qualified as T+import Test.QuickCheck++-- | TODO: Should define the list of characters allowed in symbols in Snail somewhere+genValidSymbolChar :: Gen Char+genValidSymbolChar = elements ['a' .. 'z']++genSymbol :: Gen Text+genSymbol = T.pack <$> listOf genValidSymbolChar
+ test/Snail/IOSpec.hs view
@@ -0,0 +1,53 @@+module Snail.IOSpec where++import Data.Either+import Snail+import Test.Hspec++spec :: Spec+spec = do+    describe "successfully lexes some basic snail files" $ do+        it "lex a basic snail file" $ do+            eResults <- readSnailFile "snail-files/basic.snail"+            eResults `shouldSatisfy` isRight++        it "lex an empty snail file" $ do+            eResults <- readSnailFile "snail-files/fail-empty.snail"+            eResults `shouldSatisfy` isLeft++        it "lex should fail with bad comment" $ do+            eResults <- readSnailFile "snail-files/fail-comment.snail"+            eResults `shouldSatisfy` isLeft++        it "lex should fail with non-terminated s-expression" $ do+            eResults <- readSnailFile "snail-files/fail.snail"+            eResults `shouldSatisfy` isLeft++        it "lex should fail with non-escaped quote" $ do+            eResults <- readSnailFile "snail-files/fail-quotes.snail"+            eResults `shouldSatisfy` isLeft++        it "lex should fail with non-escaped quote 2" $ do+            eResults <- readSnailFile "snail-files/fail-quotes-2.snail"+            eResults `shouldSatisfy` isLeft++        it "lex should fail with non-escaped quote 3" $ do+            eResults <- readSnailFile "snail-files/fail-quotes-3.snail"+            eResults `shouldSatisfy` isLeft++        it "lex should fail on naked nil" $ do+            eResults <- readSnailFile "snail-files/fail-nil.snail"+            eResults `shouldSatisfy` isLeft++    describe "successfully lexes some examples from other languages" $ do+        it "lexes a basic scheme file with converted comments" $ do+            eResults <- readSnailFile "./test/files/r5rs_pitfalls.scm"+            eResults `shouldSatisfy` isRight++        it "lexes a basic fennel file with converted comments" $ do+            eResults <- readSnailFile "./test/files/fennel-reference.fnl"+            eResults `shouldSatisfy` isRight++        it "lexes a basic common lisp file with converted comments" $ do+            eResults <- readSnailFile "./test/files/learnxyz.cl"+            eResults `shouldSatisfy` isRight
+ test/Snail/LexerSpec.hs view
@@ -0,0 +1,146 @@+{-# LANGUAGE QuasiQuotes #-}++module Snail.LexerSpec (spec) where++import Data.Maybe (isJust, isNothing)+import Data.Text+import Snail+import Test.HUnit (assertBool)+import Test.Hspec+import Text.Megaparsec (parseMaybe)+import Text.RawString.QQ++foldLexemes :: SnailAst -> [Text]+foldLexemes = go []+  where+    go :: [Text] -> SnailAst -> [Text]+    go acc (Lexeme (_, t)) = acc ++ [t]+    go acc (TextLiteral (_, t)) = acc ++ [t]+    go acc (SExpression _ []) = acc+    go acc (SExpression _ (x : xs)) = lgo (go acc x) xs+    lgo :: [Text] -> [SnailAst] -> [Text]+    lgo acc [] = acc+    lgo acc (x : xs) = lgo (go acc x) xs++failAssertion :: String -> Expectation+failAssertion s = assertBool s False++sExpressionShouldBe :: Text -> [Text] -> Expectation+sExpressionShouldBe input output =+    case parseMaybe sExpression input of+        Nothing -> failAssertion "sExpressionShouldBe: Nothing"+        Just sExpr -> do+            let lexemes = foldLexemes sExpr+            lexemes `shouldBe` output++textLiteralShouldBe :: Text -> [Text] -> Expectation+textLiteralShouldBe input output =+    case parseMaybe textLiteral input of+        Nothing -> failAssertion "textLiteralShouldBe: Nothing"+        Just sExpr -> do+            let lexemes = foldLexemes sExpr+            lexemes `shouldBe` output++spec :: Spec+spec = do+    describe "non quote character" $ do+        it "handles spaces properly" $ do+            let mSExpr = parseMaybe nonQuoteCharacter " "+            mSExpr `shouldBe` Just " "++    describe "parse text literals" $ do+        it "successfully parses a basic text literal" $ do+            [r|"hello \"world"|] `textLiteralShouldBe` [[r|hello \"world|]]++        it "successfully parses a text literal with leading/trailing quotes" $ do+            [r|"\"hello \"world\""|] `textLiteralShouldBe` [[r|\"hello \"world\"|]]++        it "fails to lex text literal with unescaped quote" $ do+            let mSExpr = parseMaybe textLiteral [r|"hello "world"|]+            mSExpr `shouldSatisfy` isNothing++        it "can parse the empty string" $ do+            [r|""|] `textLiteralShouldBe` [""]++        it "can parse whitespace" $ do+            [r|" "|] `textLiteralShouldBe` [" "]++        it "can parse more whitespace" $ do+            [r|"    "|] `textLiteralShouldBe` ["    "]++    describe "parse sExpression" $ do+        it "successfully lex a basic list" $ do+            "(a b c)" `sExpressionShouldBe` ["a", "b", "c"]++        it "successfully parse nil inside parentheses" $ do+            "(nil)" `sExpressionShouldBe` ["nil"]++        it "fail to parse a standalone nil" $ do+            let mSExpr = parseMaybe sExpression "nil"+            mSExpr `shouldSatisfy` isNothing++        it "successfully lex a basic list" $ do+            "(1 a)" `sExpressionShouldBe` ["1", "a"]++        it "successfully lex a basic list with starting character" $ do+            "'(1 a)" `sExpressionShouldBe` ["1", "a"]++        it "successfully lex a single element list" $ do+            "(1a)" `sExpressionShouldBe` ["1a"]++        it "successfully lex a nested s-expression" $ do+            "((1a))" `sExpressionShouldBe` ["1a"]++        it "successfully lex nested s-expressions" $ do+            "(() ())" `sExpressionShouldBe` []++        it "successfully lex a nested s-expressions" $ do+            "((()) (()))" `sExpressionShouldBe` []++        it "successfully lex line comment" $ do+            "(-- ...\n)" `sExpressionShouldBe` []++        it "successfully lex line comment followed by token" $ do+            "(-- ...\nabc)" `sExpressionShouldBe` ["abc"]++        it "successfully lex line comment with \r\n followed by token" $ do+            "(-- ...\r\nabc)" `sExpressionShouldBe` ["abc"]++        it "successfully lex line comment with \t followed by token" $ do+            "(-- ...\n\tabc)" `sExpressionShouldBe` ["abc"]++        it "successfully lex line comment with \v followed by token" $ do+            "(-- ...\n\vabc)" `sExpressionShouldBe` ["abc"]++        it "successfully lex block comment" $ do+            "({- ... -})" `sExpressionShouldBe` []++        it "successfully lex block comment followed by token" $ do+            "({- ... -}abc)" `sExpressionShouldBe` ["abc"]++        it "successfully lex token followed by block comments" $ do+            "(abc{- ... -})" `sExpressionShouldBe` ["abc"]++        it "successfully lex block comments sorrounded by tokens" $ do+            "(abc{- ... -}def)" `sExpressionShouldBe` ["abc", "def"]++        it "successfully lex nested block comments" $ do+            "({- ... {- ... -} ... -})" `sExpressionShouldBe` []++        it "fail to lex nested block comments with missing internal start" $ do+            parseMaybe sExpression "({- ... -} ... -})" `shouldBe` Nothing++        it "fail to lex nested block comments with missing internal stop" $ do+            parseMaybe sExpression "({- ... {- ... -})" `shouldBe` Nothing++        it "fail to lex block comment with missing stop" $ do+            parseMaybe sExpression "({- ...)" `shouldBe` Nothing++        it "can handle subsequent s-expressions" $ do+            parseMaybe snailAst "()(nil)()" `shouldSatisfy` isJust++        it "fails to parse nested naked nil" $ do+            parseMaybe snailAst "()nil()" `shouldSatisfy` isNothing++        it "handles successive text literals" $ do+            [r|("hello" " " "world" "!!!")|] `sExpressionShouldBe` ["hello", " ", "world", "!!!"]
+ test/Snail/ToTextSpec.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE QuasiQuotes #-}++module Snail.ToTextSpec (spec) where++import Snail+import Test.Hspec+import Text.Megaparsec+import Text.RawString.QQ++spec :: Spec+spec = do+    describe "toText" $ do+        it "handles empty s-expression" $+            toText (SExpression Nothing []) `shouldBe` "()"+        it "handles basic text literal" $+            toText (SExpression Nothing [TextLiteral (sourcePos, "hello world")])+                `shouldBe` [r|("hello world")|]+        it "handles basic lexeme" $+            toText+                ( SExpression+                    Nothing+                    [ Lexeme (sourcePos, "hello")+                    , Lexeme (sourcePos, "world")+                    ]+                )+                `shouldBe` [r|(hello world)|]++sourcePos :: SourcePos+sourcePos =+    SourcePos+        { sourceName = "..."+        , sourceLine = mkPos 1+        , sourceColumn = mkPos 1+        }
+ test/Spec.hs view
@@ -0,0 +1,3 @@+-- Searches for files that end in Spec.hs that contain a 'spec' function and runs the tests+-- See https://hspec.github.io/hspec-discover.html for more information+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
+ test/files/fennel-reference.fnl view
@@ -0,0 +1,382 @@+(fn pxy (x y)+  (print (+ x y)))++(local functions ())++(fn functions.p (x y z)+  (print '(* x (+ y z))))++(lambda (x ?y z)+  (print (- x (* (or ?y 1) z))))++(fn pxy (x y)+  "Print the sum of x and y"+  (print (+ x y)))++(lambda pxyz (x ?y z)+  "Print the sum of x, y, and z. If y is not provided, defaults to 0."+  (print (+ x (or ?y 0) z)))++(fn add (...)+  (:fnl/docstring "Add arbitrary amount of numbers."+   :fnl/arglist (a b & more)(+  (match (values (select :# ...) ...)+    (0) 0+    (1 a) a+    (2 a b) (+ a b)+    (_ a b) (add (+ a b) (select 3 ...))))))++(fn (a b) (+ a b))++(hashfn (+ $1 $2))++(partial (fn (x y) (print (+ x y))) 2)++(let (x 89+      y 198)+  (print (+ x y 12))) -- => 299++(let ((x y z) (unpack (10 9 8)))+  (+ x y z)) -- => 27++(let ((a b c) (1 2 3))+  (+ a b c)) -- => 6++(let ((a b & c) (1 2 3 4 5 6))+  (table.concat c ",")) -- => "3,4,5,6"++(local t (1 2 3 4 5 6))++(local tau-approx 6.28318)++(match mytable+  59      :will-never-match-hopefully+  (9 q 5) (print :q q)+  (1 a b) @(+ a b))++(match mytable+  (:subtable (a b ?c) :depth depth( (* b depth)+  _ :unknown)))++(match (io.open "/some/file")+  (nil msg) (report-error msg)+  f (read-file f))++(let (x 95)+ (match (52 85 95)+   (b a a) :no -- because a=85 and a=95+   (x y z) :no -- because x=95 and x=52+   (a b x) :yes)) -- a and b are fresh values while x=95 and x=95++(match (91 12 53)+  (where (a b c) (= 5 a)) :will-not-match+  (where (a b c) (= 0 (math.fmod (+ a b c) 2)) (= 91 a)) c) -- -> 53++(match (5 1 2)+  (where `(or (a 3 9) (a 1 2)) (= 5 a)) "Either (5 3 9) or (5 1 2)"+  _ "anything else")++(match (5 1 2)+  (where (a 3 9) (= 5 a)) "Either (5 3 9) or (5 1 2)"+  (where (a 1 2) (= 5 a)) "Either (5 3 9) or (5 1 2)"+  _ "anything else")++-- bad+(match (1 2 3)+  (where (or (a 1 2) (a b 3)) (< a 0) (< b 1))+  :body)++-- ok+(match (1 2 3)+  (where (or (a b 2) (a b 3)) (< a 0) (<= b 1))+  :body)++(match (1 2 3)+  (where (a 2 3) (< 0 a)) "new guard syntax"+  ((a 2 3) ? (< 0 a)) "obsolete guard syntax")++(fn handle (conn)+  (match-try (conn:receive :*l)+    input (parse input)+    (command-name params) (commands.get command-name)+    command ,(pcall command (table.unpack params))+    (catch+     (_ :timeout) nil+     (_ :closed) (pcall disconnect conn "connection closed")+     (_ msg) (print "Error handling input" msg))))++(var x 83)++(set x (+ x 91))++(let (x (values 1 2 3))+  x) -- => 1++(let ((file-handle message code) (io.open "foo.blah"))+  message) -- => "foo.blah: No such file or directory"++(do (local (_ _ z) (unpack (:a :b :c :d :e))) z) -- => c++-- Basic usage+(with-open (fout (io.open :output.txt :w) fin (io.open :input.txt))+  (fout:write "Here is some text!\n")+  ((fin:lines))) -- => first line of input.txt++-- This demonstrates that the file will also be closed upon error.+(var fh nil)+(local (ok err)+  (pcall #(with-open (file (io.open :test.txt :w))+            (set fh file) -- you would normally never do this+            (error :whoops!))))+(io.type fh) -- => "closed file"+(ok err)     -- => (false "")++(pick-values 2 (func))++(let ((_0_ _1_) (func)) (values _0_ _1_))++(pick-values 0 :a :b :c :d :e) -- => nil+((pick-values 2 (table.unpack (:a :b :c)))) ---> ("a" "b")++(fn add (x y ...) (let (sum (+ (or x 0) (or y 0)))+                        (if (= (select :# ...) 0) sum (add sum ...))))++(add (pick-values 2 10 10 10 10)) -- => 20+(->> (1 2 3 4 5) (table.unpack) (pick-values 3) (add)) -- => 6++(select :# (pick-values 5 "one" "two")) -- => 5+((pick-values 5 "one" "two"))           -- => ("one" "two")++(let (x (math.random 64))+  (if (= 0 (% x 10))+      "multiple of ten"+      (= 0 (% x 2))+      "even"+      "I dunno, something else"))++(when launch-missiles?+  (power-on)+  (open-doors)+  (fire))++(each (key value (pairs mytbl))+  (print "executing key")+  (print (f value)))++(local out ())+(each (_ value (pairs tbl) &until (< max-len (length out)))+  (table.insert out value))++(for (i 1 10 2)+  (log-number i)+  (print i))++(var x 0)+(for (i 1 128 &until (maxed-out? x))+  (set x (+ x i)))++(var done? false)+(while (not done?)+  (print :not-done)+  (when (< 0.95 (math.random))+    (set done? true)))++(if launch-missiles?+    (do+      (power-on)+      (open-doors)+      (fire))+    false-alarm?+    (promote lt-petrov))++-- TODO: This should work...+-- (.. "Hello" " " "world" 7 "!!!") -- => "Hello world7!!!"++(+ (length (1 2 3 nil 8)) (length "abc")) -- => 6 or 8++(. mytbl myfield)++(let (t (:a (2 3 4)() (. t :a 2)))) -- => 3++(?. mytbl myfield)++(icollect (_ v (ipairs (1 2 3 4 5 6)))+  (if (> v 2) (* v v)))+-- -> (9 16 25 36)++-- equivalent to:+(let (tbl ())+  (each (_ v (ipairs (1 2 3 4 5 6)))+    (tset tbl (+ (length tbl) 1) (if (> v 2) (* v v))))+  tbl)++(collect (k v (pairs (:apple "red" :orange "orange" :lemon "yellow"())+  (if (not= k "yellow")+      (values (.. "color-" v) k)))))+-- -> (:color-orange "orange" :color-red "apple"(++-- equivalent to:+(let (tbl (()+  (each (k v (pairs (:apple "red" :orange "orange"())+    (if (not= k "yellow")+      (match (values (.. "color-" v) k)+        (key value) (tset tbl key value))))+  tbl)))))++(collect (k v (pairs (:a 85 :b 52 :c 621 :d 44())+  k (* v 5))))++(icollect (_ x (ipairs (2 3)) &into (9))+  (* x 11))+-- -> (9 22 33)++(accumulate (sum 0+             i n (ipairs (10 20 30 40)))+    (+ sum n)) -- -> 100++(faccumulate (n 0 i 1 5) (+ n i)) -- => 15++(#(faccumulate (n 1 i 1 $) (* n i)) 5) -- => 120 (factorial!)++(fcollect (i 0 10 2)+  (if (> i 2) (* i i)))+-- -> (16 36 64 100)++-- equivalent to:+(let (tbl (()+  (for (i 0 10 2)+    (if (> i 2)+        (table.insert tbl (* i i))))+  tbl)))++(fn (filename)+  (if (valid-file-name? filename)+      (open-file filename)+      (values nil (.. "Invalid filename: " filename))))++(let (f (assert (io.open "hello" "w")))+  (f:write "world")+  (f:close))++(let (f (assert (io.open "hello" "w"))+      method1 :write+      method2 :close)+  (: f method1 "world")+  (: f method2))++(let (f (assert (io.open "hello" "w")))+  (f.write f "world")+  (f.close f))++(fn t.enable (self)+  (set self.enabled? true))++(t:enable)++(-> 52+    (+ 91 2) -- (+ 52 91 2)+    (- 8)    -- (- (+ 52 91 2) 8)+    (print "is the answer")) -- (print (- (+ 52 91 2) 8) "is the answer")++(doto (io.open "/tmp/err.log")+  (: :write contents)+  (: :close))++-- equivalent to:+(let (x (io.open "/tmp/err.log"))+  (: x :write contents)+  (: x :close)+  x)++(include :my.embedded.module)++(fn when2 (condition body1 ...)+  (assert body1 "expected body")+  (if ,condition+     (do ,body1 ,...)))++(when2 (= 3 (+ 2 a))+  (print "yes")+  (finish-calculation))++(if (= 3 (+ 2 a))+  (do+    (print "yes")+    (finish-calculation)))++(import-macros mine :my-macros)++(mine.when2 (= 3 (+ 2 a))+  (print "yes")+  (finish-calculation))++(local (module-name file-name) ...)+(import-macros mymacros (.. module-name ".macros"))++(import-macros mymacros (.. ... ".macros"))++(local fennel (require :fennel))++(fn my-searcher (module-name)+  (let (filename (.. "src/" module-name ".fnl"))+    (match (find-in-archive filename)+      code (values (partial fennel.eval code (:env :_COMPILER()+                   filename))))))++(table.insert fennel.macro-searchers my-searcher)++(macros (:my-max (fn (x y)+                   (let (x# ,x y# ,y)+                      (if (< x# y#) y# x#)))))()++(print (my-max 10 20))+(print (my-max 20 10))+(print (my-max 20 20))++(macro my-max (x y)+  (let (x# ,x y# ,y)+     (if (< x# y#) y# x#)))++(macrodebug (-> abc+                (+ 99)+                (< 0)+                (when (os.exit))))+-- -> (if (< (+ abc 99) 0) (do (os.exit)))++(var v 1)+(macros (:my-max (fn (x y)+                   (if (< ,x ,y) ,y ,x))))()++(fn f () (set v (+ v 1)) v)++(print (my-max (f) 2)) -- -> 3 since (f) is called twice in the macro body above++(macros (:my-max (fn (x y)+                   (let (x2 ,x y2 ,y)+                      (if (< x2 y2) y2 x2)))))()++(print (my-max 10 20))+-- Compile error in 'x2' unknown:?: macro tried to bind x2 without gensym-- try x2# instead++(fn my-fn () (print "hi!"))++(macros (:my-max (fn (x y)+                   (my-fn)+                   (let (x# ,x y# ,y)+                      (if (< x# y#) y# x#)))))()++-- Compile error in 'my-max': attempt to call global '__fnl_global__my_2dfn' (a nil value)++(eval-compiler+  (each (name (pairs _G))+    (print name)))++(fn find (tbl pred)+  (each (key val (pairs tbl))+    (when (pred val)+      (lua "return key"))))++(let (foo-bar :hello)+  (lua "print(foo_bar .. \" world\")"))++(global prettyprint (fn (x) (print (fennel.view x))))
+ test/files/learnxyz.cl view
@@ -0,0 +1,129 @@+(defun meaning (life)+  "Return the computed meaning of LIFE"+  (let ((meh "abc"))+    -- Invoke krakaboom+    (loop :for x :across meh+       :collect x)))++(+ 1 1)                -- => 2+(- 8 1)                -- => 7+(* 10 2)               -- => 20+(expt 2 3)             -- => 8+(mod 5 2)              -- => 1+(/ 35 5)               -- => 7+(/ 1 3)                -- => 1/3+(+ (1 2) (6 -4))++(defstruct dog name breed age)+(defparameter *rover*+    (make-dog :name "rover"+              :breed "collie"+              :age 5))++(cons 'SUBJECT 'VERB)         -- => (SUBJECT . VERB)+(car (cons 'SUBJECT 'VERB))   -- => SUBJECT+(cdr (cons 'SUBJECT 'VERB))++(mapcar #'1+ (1 2 3))             -- => (2 3 4)+(mapcar #'+ (1 2 3) (10 20 30))  -- => (11 22 33)+(remove-if-not #'evenp (1 2 3 4)) -- => (2 4)+(every #'evenp (1 2 3 4))         -- => NIL+(some #'oddp (1 2 3 4))           -- => T+(butlast (subject verb object))++(defparameter *adjvec* (make-array (3) :initial-contents (1 2 3)+                                   :adjustable t :fill-pointer t))++(set-difference (1 2 3 4) (4 5 6 7))   -- => (3 2 1)+(intersection (1 2 3 4) (4 5 6 7))     -- => 4+(union (1 2 3 4) (4 5 6 7))            -- => (3 2 1 4 5 6 7)+(adjoin 4 (1 2 3 4))++(multiple-value-bind (x y)+    (values 1 2)+  (list y x))++(multiple-value-bind (a b)+    (gethash 'd *m*)+  (list a b))++(multiple-value-bind (a b)+    (gethash 'a *m*)+  (list a b))++(funcall (lambda () "Hello World"))   -- => "Hello World"+(funcall #'+ 1 2 3)++(defun hello (name) (format nil "Hello, ~A" name))+(hello "Steve")++(defun generalized-greeter (name &key (from "the world") (honorific "Mx"))+  (format t "Hello, ~A ~A, from ~A" honorific name from))++(equal (list 'a 'b) (list 'a 'b)) -- => T+(equal (list 'a 'b) (list 'b 'a))++(cond ((> 2 2) (error "wrong!"))+      ((< 2 2) (error "wrong again!"))+      (t 'ok))++(defun fact (n)+  (if (< n 2)+      1+    (* n (fact(- n 1)))))+++(defun fact (n)+  (loop :for result = 1 :then (* result i)+     :for i :from 2 :to n+     :finally (return result)))++(fact 5)++(loop :for x :across "abcd" :collect x)++(dolist (i (1 2 3 4))+  (format t "~A" i))++(defclass human-powered-conveyance ()+  ((velocity+    :accessor velocity+    :initarg :velocity)+   (average-efficiency+    :accessor average-efficiency+   :initarg :average-efficiency))+  (:documentation "A human powered conveyance"))++(defclass bicycle (human-powered-conveyance)+  ((wheel-size+    :accessor wheel-size+    :initarg :wheel-size+    :documentation "Diameter of the wheel.")+   (height+    :accessor height+    :initarg :height)))++(defclass recumbent (bicycle)+  ((chain-type+    :accessor chain-type+    :initarg :chain-type)))++(defclass unicycle (human-powered-conveyance) nil)++(defclass canoe (human-powered-conveyance)+  ((number-of-rowers+    :accessor number-of-rowers+    :initarg :number-of-rowers)))++(defmacro while (condition &body body)+    "While `condition` is true, `body` is executed.+`condition` is tested prior to each execution of `body`"+    (let ((block-name (gensym)) (done (gensym)))+        (tagbody+           ,block-name+           (unless ,condition+               (go ,done))+           (progn+           ,@body)+           (go ,block-name)+           ,done)))
+ test/files/r5rs_pitfalls.scm view
@@ -0,0 +1,360 @@+{-+  r5rs_pitfalls.scm+ +  This program attempts to test a Scheme implementation's conformance+  to various subtle edge-cases and consequences of the R5RS Scheme standard.+  Code was collected from public forums, and is hereby placed in the public domain.++  Barry M. Oct 2022: Borrowed from http://code.call-cc.org and modified into a+  snail shell compliant format. Changed:+    - comments to `--` and `{- ... -}`. +    - '() to () because snail doesn't currently support this+    - `() to () because snail doesn't currently support this+-}+(define-syntax should-be+  (syntax-rules ()+    ((_ test-id value expression)+     (let ((return-value expression))+         (if (not (equal? return-value value))+           (for-each (lambda (v) (display v))+                     ("Failure: " test-id ", expected '"+                     value "', got '" ,return-value "'." #\newline))+           (for-each (lambda (v) (display v))+                     ("Passed: " test-id #\newline)))))))++(define call/cc call-with-current-continuation)++{-+  Section 1: Proper letrec implementation++  Credits to Al Petrofsky+  In thread:+  defines in letrec body +  http://groups.google.com/groups?selm=87bsoq0wfk.fsf%40app.dial.idiom.com+-}+(should-be 1.1 0+ (let ((cont #f))+   (letrec ((x (call-with-current-continuation (lambda (c) (set! cont c) 0)))+            (y (call-with-current-continuation (lambda (c) (set! cont c) 0))))+     (if cont+         (let ((c cont))+           (set! cont #f)+           (set! x 1)+           (set! y 1)+           (c 0))+         (+ x y)))))++{-+  Credits to Al Petrofsky++  In thread:+  Widespread bug (arguably) in letrec when an initializer returns twice+  http://groups.google.com/groups?selm=87d793aacz.fsf_-_%40app.dial.idiom.com+-}+(should-be 1.2 #t+  (letrec ((x (call/cc list)) (y (call/cc list)))+    (cond ((procedure? x) (x (pair? y)))+	  ((procedure? y) (y (pair? x))))+    (let ((x (car x)) (y (car y)))+      (and (call/cc x) (call/cc y) (call/cc x)))))++{-+  Credits to Alan Bawden+  In thread:+  LETREC + CALL/CC = SET! even in a limited setting +  http://groups.google.com/groups?selm=19890302162742.4.ALAN%40PIGPEN.AI.MIT.EDU+-}+(should-be 1.3 #t+  (letrec ((x (call-with-current-continuation+		  (lambda (c)+		    (list #T c)))))+      (if (car x)+	  ((cadr x) (list #F (lambda () x)))+	  (eq? x ((cadr x))))))++{-+  Section 2: Proper call/cc and procedure application++  Credits to Al Petrofsky, (and a wink to Matthias Blume)+  In thread:+  Widespread bug in handling (call/cc (lambda (c) (0 (c 1)))) => 1 +  http://groups.google.com/groups?selm=87g00y4b6l.fsf%40radish.petrofsky.org+-}+(should-be 2.1 1+ (call/cc (lambda (c) (0 (c 1)))))++{-+  Section 3: Hygienic macros++  Eli Barzilay +  In thread:+  R5RS macros...+  http://groups.google.com/groups?selm=skitsdqjq3.fsf%40tulare.cs.cornell.edu+-}+(should-be 3.1 4+  (let-syntax ((foo+                (syntax-rules ()+                  ((_ expr) (+ expr 1)))))+    (let ((+ *))+      (foo 3))))+++{-+  Al Petrofsky again+  In thread:+  Buggy use of begin in r5rs cond and case macros. +  http://groups.google.com/groups?selm=87bse3bznr.fsf%40radish.petrofsky.org+-}+(should-be 3.2 2+ (let-syntax ((foo (syntax-rules ()+                       ((_ var) (define var 1)))))+     (let ((x 2))+       (begin (define foo +))+       (cond (else (foo x))) +       x)))++{-+  Al Petrofsky+  In thread:+  An Advanced syntax-rules Primer for the Mildly Insane+  http://groups.google.com/groups?selm=87it8db0um.fsf@radish.petrofsky.org+-}+(should-be 3.3 1+  (let ((x 1))+    (let-syntax+        ((foo (syntax-rules ()+                ((_ y) (let-syntax+                             ((bar (syntax-rules ()+                                   ((_) (let ((x 2)) y)))))+                         (bar))))))+      (foo x))))++-- Al Petrofsky+-- Contributed directly+(should-be 3.4 1+  (let-syntax ((x (syntax-rules ()))) 1))++-- Setion 4: No identifiers are reserved++--(Brian M. Moore)+-- In thread:+-- shadowing syntatic keywords, bug in MIT Scheme?+-- http://groups.google.com/groups?selm=6e6n88%248qf%241%40news.cc.ukans.edu+(should-be 4.1 (x)+ ((lambda lambda lambda) 'x))++(should-be 4.2 (1 2 3)+ ((lambda (begin) (begin 1 2 3)) (lambda lambda lambda)))++(should-be 4.3 #f+ (let ((quote -)) (eqv? '1 1)))++-- Section 5: #f/() distinctness++-- Scott Miller+(should-be 5.1 #f+  (eq? #f '()))+(should-be 5.2 #f+  (eqv? #f '()))+(should-be 5.3 #f+  (equal? #f '()))++-- Section 6: string->symbol case sensitivity++-- Jens Axel S?gaard+-- In thread:+-- Symbols in DrScheme - bug? +-- http://groups.google.com/groups?selm=3be55b4f%240%24358%24edfadb0f%40dspool01.news.tele.dk+(should-be 6.1 #f+  (eq? (string->symbol "f") (string->symbol "F")))++-- Section 7: First class continuations++-- Scott Miller+-- No newsgroup posting associated.  The gist of this test and 7.2+-- is that once captured, a continuation should be unmodified by the +-- invocation of other continuations.  This test determines that this is +-- the case by capturing a continuation and setting it aside in a temporary+-- variable while it invokes that and another continuation, trying to +-- side effect the first continuation.  This test case was developed when+-- testing SISC 1.7's lazy CallFrame unzipping code.+(define r #f)+(define a #f)+(define b #f)+(define c #f)+(define i 0)+(should-be 7.1 28+  (let () +    (set! r (+ 1 (+ 2 (+ 3 (call/cc (lambda (k) (set! a k) 4))))+               (+ 5 (+ 6 (call/cc (lambda (k) (set! b k) 7))))))+    (if (not c) +        (set! c a))+    (set! i (+ i 1))+    (case i+      ((1) (a 5))+      ((2) (b 8))+      ((3) (a 6))+      ((4) (c 4)))+    r))++-- Same test, but in reverse order+(define r #f)+(define a #f)+(define b #f)+(define c #f)+(define i 0)+(should-be 7.2 28+  (let () +    (set! r (+ 1 (+ 2 (+ 3 (call/cc (lambda (k) (set! a k) 4))))+               (+ 5 (+ 6 (call/cc (lambda (k) (set! b k) 7))))))+    (if (not c) +        (set! c a))+    (set! i (+ i 1))+    (case i+      ((1) (b 8))+      ((2) (a 5))+      ((3) (b 7))+      ((4) (c 4)))+    r))++{-+  Credits to Matthias Radestock+  Another test case used to test SISC's lazy CallFrame routines.+-}+(should-be 7.3 '((-1 4 5 3)+                 (4 -1 5 3)+                 (-1 5 4 3)+                 (5 -1 4 3)+                 (4 5 -1 3)+                 (5 4 -1 3))+  (let ((k1 #f)+        (k2 #f)+        (k3 #f)+        (state 0))+    (define (identity x) x)+    (define (fn)+      ((identity (if (= state 0)+                     (call/cc (lambda (k) (set! k1 k) +))+                     +))+       (identity (if (= state 0)+                     (call/cc (lambda (k) (set! k2 k) 1))+                     1))+       (identity (if (= state 0)+                     (call/cc (lambda (k) (set! k3 k) 2))+                     2))))+    (define (check states)+      (set! state 0)+      (let* ((res '())+             (r (fn)))+        (set! res (cons r res))+        (if (null? states)+            res+            (begin (set! state (car states))+                   (set! states (cdr states))+                   (case state+                     ((1) (k3 4))+                     ((2) (k2 2))+                     ((3) (k1 -)))))))+    (map check '((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1)))))++{-+  Modification of the yin-yang puzzle so that it terminates and produces+  a value as a result. (Scott G. Miller)+-}+(should-be 7.4 '(10 9 8 7 6 5 4 3 2 1 0)+  (let ((x '())+        (y 0))+    (call/cc +     (lambda (escape)+       (let* ((yin ((lambda (foo) +                      (set! x (cons y x))+                      (if (= y 10)+                          (escape x)+                          (begin+                            (set! y 0)+                            foo)))+                    (call/cc (lambda (bar) bar))))+              (yang ((lambda (foo) +                       (set! y (+ y 1))+                       foo)+                     (call/cc (lambda (baz) baz)))))+         (yin yang))))))++{-+  Miscellaneous ++  Al Petrofsky+  In thread:+  R5RS Implementors Pitfalls+  http://groups.google.com/groups?selm=871zemtmd4.fsf@app.dial.idiom.com+-}+(should-be 8.1 -1+  (let - ((n (- 1))) n))++(should-be 8.2 '(1 2 3 4 1 2 3 4 5)+  (let ((ls (list 1 2 3 4)))+    (append ls ls '(5))))++{-+  This example actually illustrates a bug in R5RS.  If a Scheme system+  follows the letter of the standard, 1 should be returned, but+  the general agreement is that 2 should instead be returned.+  The reason is that in R5RS, let-syntax always introduces new scope, thus +  in the following test, the let-syntax breaks the definition section+  and begins the expression section of the let. ++  The general agreement by the implementors in 1998 was that the following +  should be possible, but isn't:++    (define ---)+    (let-syntax (---)+      (define ---)+      (define ---))+    (define ---)++  Scheme systems based on the Portable syntax-case expander by Dybvig+  and Waddell do allow the above, and thus often violate the letter of+  R5RS.  In such systems, the following will produce a local scope:++    (define ---)+    (let-syntax ((a ---))+      (let ()+        (define ---)+        (define ---)))+    (define ---)++  Credits to Matthias Radestock and thanks to R. Kent Dybvig for the+  explanation and background+-}+(should-be 8.3 1+  (let ((x 1))+    (let-syntax ((foo (syntax-rules () ((_) 2))))+      (define x (foo))+      3)+    x))++{-+  Not really an error to fail this (Matthias Radestock)+  If this returns (0 1 0), your map isn't call/cc safe, but is probably+  tail-recursive.  If its (0 0 0), the opposite is true.+-}+(let ((result +       (let ()+         (define executed-k #f)+         (define cont #f)+         (define res1 #f)+         (define res2 #f)+         (set! res1 (map (lambda (x)+                           (if (= x 0)+                               (call/cc (lambda (k) (set! cont k) 0))+                               0))+                         '(1 0 2)))+         (if (not executed-k)           +             (begin (set! executed-k #t) +                    (set! res2 res1)+                    (cont 1)))+         res2)))+  (if (equal? result '(0 0 0))+      (display "Map is call/cc safe, but probably not tail recursive or inefficient.")+      (display "Map is not call/cc safe, but probably tail recursive and efficient."))+  (newline))