Lissp Quick Start

(Outputs hidden for brevity.)
;;;; 1 Lissp Quick Start

"Lissp is a lightweight text language representing the Hissp
intermediate language. The Lissp reader parses the Lissp language's
symbolic expressions as Python objects. The Hissp compiler
then translates these syntax trees to Python expressions.

This document is written like a .lissp file, thoroughly demonstrating
Lissp's (and thereby Hissp's) features with minimal exposition. This
element enclosed in double quotes is a docstring for the module.

To fully understand these examples, you must see their Python
compilation and output. Some familiarity with Python is assumed.
Install the Hissp version matching this document. Follow along by
entering these examples in the REPL. It will show you the compiled
Python and evaluate it. Try variations that occur to you.

Familiarity with another Lisp dialect is not assumed, but helpful. If
you get confused or stuck, read the easier Hissp tutorial.

Some examples depend on state set by previous examples to work.
Prerequisites for examples not in the same section are marked with
'/!\'. Don't skip these! Re-enter them if you start a new session.
"

;;;; 2 Installation

;; These docs are for the latest development version of Hissp.
;; Install the latest Hissp version with
;; $ pip install git+https://github.com/gilch/hissp
;; Start the REPL with
;; $ lissp
;; You can quit with EOF or (exit).

;; Most examples are tested automatically, but details may be dated.
;; Report issues or try the current release version instead.

;;;; 3 Simple Atoms

;; To a first approximation, the Hissp intermediate language is made
;; of Python tuples representing syntax trees. The nodes are tuples
;; and we call the leaves "atoms". Simple atoms in Lissp are written
;; the same way as Python.

;;; 3.1 Singleton

None
...                                 ;Ellipsis

;;; 3.2 Boolean

False                               ;False == 0
True                                ;True == 1

;;; 3.3 Integer

42
-10_000
0x10
0o10
0b10
0b1111_0000_0000
0xF00

;;; 3.4 Floating-Point

3.
-4.2
4e2
-1.6e-2

;;; 3.5 Complex

5j                                  ;imaginary
4+2j                                ;complex
-1_2.3_4e-5_6-7_8.9_8e-7_6j         ;Very complex!

;;;; 4 Simple Tuples

;; Tuples group any atoms with (). Data tuples start with '.
'(None 2 3)
'(True
  False)

;;;; 5 Symbolic Atoms

;;; 5.1 Identifiers

object                              ;Python identifiers work in Lissp.
object.__class__                    ;Attribute identifier with dot, as Python.
object.__class__.__name__           ;Attributes chain.

;;; 5.2 Imports

math.                               ;Module handles import!
math..tau                           ;Fully-qualified identifier. (Module attribute.)
collections.abc.                    ;Submodule handle. Has package name.

builtins..object.__class__          ;Qualified attribute identifier.
collections.abc..Sequence.__class__.__name__ ;Chaining.

;;;; 6 Simple Forms and Calls

;; "Forms" are any data structures that can be evaluated as a Hissp program.
;; Simple atoms are forms. They simply evaluate to an equivalent object.

0x2a

;; Tuples can also be forms, but their evaluation rules are more complex.
;; The common case is a function call. For that, the first element must
;; be a callable. The remainder are arguments.

(print 1 2 3)                       ;This one compiles to a function call.
'(print 1 2 3)                      ;This one is a data tuple.

;; Data tuples and calls are enough to make simple collections.

'(1 2 3)                            ;tuple
(list '(1 2 3))
(set '(1 2 3))
(dict '((1 2) (3 4)))               ;Uses nested tuples.
(bytes '(98 121 116 101 115))

(help sum)                          ;Python's online help function still works.

;;;; 7 String Atoms

:control-word                       ;Colon prefix. Similar to Lisp ":keywords".
'symbol                             ;Apostrophe prefix. Represents identifier.

;;; 7.1 Munging

'+                                  ;Read-time munging of invalid identifiers.
'Also-a-symbol!                     ;Alias for 'AlsoQz_aQz_symbolQzBANG_
'𝐀                                  ;Alias for 'A (unicode normal form KC)
'->>
:->>                                ;Don't represent identifiers, don't munge.
:                                   ;Still a control word.

;;; 7.2 Escaping

'SPAM\ \"\(\)\;EGGS                 ;These would terminate a symbol if not escaped.
'\42                                ;Digits can't start identifiers.
'\.
'\\
'\a\b\c                             ;Escapes allowed, but not required here.
1\2                                 ;Backslashes work in other atoms.
N\one

;;; 7.3 String Literals

"raw string"
'not-string'                        ;symbol
#"Say \"Cheese!\" \u263a"           ;Hash strings process Python escapes.
"Say \"Cheese!\" \u263a"            ;Raw strings don't.

"string
with
newlines
"                                   ;Same as #"string\nwith\nnewlines\n".

"one\"
string\\"                           ;Tokenizer expects paired \'s, even raw.

;;;; 8 Advanced Calls

(dict :)                            ;Left paren before function! Notice the :.

;; All arguments pair with a target! No commas!
(dict : spam "foo"  eggs "bar"  ham "baz")

(print : :? 1  :? 2  :? 3  sep "-") ;:? is a positional target.
(print 1 : :? 2  :? 3  sep "-")     ;Arguments before : implicitly pair with :?.
(print 1 2 : :? 3  sep "-")         ;Keep sliding : over. It's shorter.
(print 1 2 3 : sep "-")             ;Next isn't a :?. The : stops here.

(print 1                            ;Implicitly a positional :? target.
       : :* "abc"                   ;Target :* to unpack iterable.
       :? 2                         ;:? is still allowed after :*.
       :* "xyz"                     ;:* is a repeatable positional target.
       :** (dict : sep "-")         ;Target :** to unpack mapping.
       flush True                   ;Kwargs still allowed after :**.
       :** (dict : end #"!?\n"))    ;Multiple :** allowed too.

(print : :? "Hello, World!")
(print "Hello, World!" :)           ;Same. Slid : over. Compare.
(print "Hello, World!")             ;No : is the same as putting it last!

(.upper "shout!")                   ;Method calls need a . and a "self".
(.float builtins. 'inf)             ;Method call syntax, though not a method.
(builtins..float 'inf)              ;Same effect, but not method syntax.

;;; 8.1 Operators

;; Hissp is simpler than Python. No operators! Use calls instead.

(operator..add 40 2)
(.update (globals) : + operator..add) ;/!\ Assignment. Identifier munged.
(+ 40 2)                            ;No operators. This is still a function call!

;;;; 9 Simple Lambdas

;; Lambdas are one of Hissp's two "special forms".
;; They look like calls, but are special-cased in the Hissp compiler
;; to work differently. The first element must be 'lambda', the second
;; is the parameters, and finally the body.

(.update (globals)
         : greet
         (lambda (salutation name)
           (print (.format "{}, {}!"
                           (.title salutation)
                           name))))
(greet "hello" "World")
(greet "hi" "Bob")

;;; 9.1 Obligatory Factorial I

;; We now have just enough to make more interesting programs.

(.update (globals)
         : factorial_I
         (lambda (i)
           (functools..reduce operator..mul
                              (range i 0 -1)
                              1)))
(factorial_I 0)
(factorial_I 3)
(factorial_I 5)

;;; 9.2 Control Flow

;; Hissp is simpler than Python. No control flow! Use higher-order functions instead.

(any (map (lambda (c) (print c))      ;Loops!
          "abc"))

((.get (dict : y (lambda () (print "Yes!")) ;Branches!
             n (lambda () (print "Canceled.")))
       (input "enter y/n> ")
       (lambda () (print "Unrecognized input."))))

;; Don't worry, Hissp metaprogramming will make this much easier,
;; but our limited tools so far are enough to implement a ternary.

(.update (globals) : bool->caller (dict))

;; True calls left.
(operator..setitem bool->caller True (lambda (L R) (L)))

;; False calls right.
(operator..setitem bool->caller False (lambda (L R) (R)))

(.update (globals)
         : ternary
         (lambda (condition then_thunk else_thunk)
           ((operator..getitem bool->caller (bool condition))
            then_thunk else_thunk)))

;;; 9.3 Obligatory Factorial II

;; Now we have enough for a recursive version.
(.update (globals)
         : factorial_II
         (lambda (i)
           (ternary (operator..le i 1)
                    (lambda () 1)
                    (lambda ()
                      (operator..mul i (factorial_II (operator..sub i 1)))))))
(factorial_II 5)

;;;; 10 Advanced Lambdas

;; Python parameter types are rather involved. Lambda does all of them.
;; Like calls, they are all paired. :? means no default.
(lambda (: a :?  b :?  :/ :?        ;positional only
         c :?  d :?                 ;normal
         e 1  f 2                   ;default
         :* args  h 4  i :?  j 1    ;star args, key word
         :** kwargs)
  ;; Body. (Lambdas return empty tuple when body is empty.)
  (print (globals))
  (print (locals))                  ;side effects
  b)                                ;last value is returned

(lambda (: a :?  b :?  c 1))        ;Note the : separator like calls.
(lambda (a : b :?  c 1))            ;`a` now implicitly paired with :?.
(lambda (a b : c 1))                ;Next isn't paired with :?. The : stops here.

(lambda (: :* a))                   ;Star arg must pair with star, as Python.
(lambda (: :* :?  x :?))            ;Empty star arg, so x is keyword only.
(lambda (:* : x :?))                ;Slid : over one. Still a kwonly.
(lambda (:* x :))                   ;Implicit :? is the same. Compare.
(lambda (:* a))                     ;Kwonly! Not star arg! Final : implied.

(lambda (a b : x None  y None))     ;Normal, then positional defaults.
(lambda (:* a b : x None  y None))  ;Keyword only, then keyword defaults.

(lambda (spam eggs) eggs)           ;Simple cases look like other Lisps, but
((lambda abc                        ; params not strictly required to be a tuple.
   (print c b a))                   ;There are three parameters.
 3 2 1)

(lambda (:))                        ;Explicit : still allowed with no params.
(lambda : (print "oops"))           ;Thunk resembles Python.
((lambda :x1 x))                    ;Control words are strings are iterable.

;;;; 11 Quote

;; Quote is the only other special form. Looks like a call, but isn't.

;; A "form" is any Hissp data that can be evaluated.
;; Not all data is a valid program in Hissp. E.g. ``(7 42)`` is a
;; tuple, containing the integers 7 in the function position, and 42
;; after in the first argument position. It would compile to a
;; syntactically-valid Python program, but evaluation would crash,
;; because ints are not callable in Python. Try it.

;; Quotation suppresses evaluation of Hissp data.
;; Treating the code itself as data is the key concept in metaprogramming.

(quote (7 42))

;; Other objects evaluate to themselves, but strings and tuples have
;; special evaluation rules in Hissp. Tuples represent invocations of
;; functions, macros, and special forms.

(quote (print 1 2 3 : sep "-"))     ;Just a tuple.

;; Notice how the string gets an extra layer of quotes vs identifiers.
;; This particular tuple happens to be a valid form.
;; The readerless function runs the Hissp compiler without the Lissp reader.
;; (Remember, _ is the last result that wasn't None in the Python REPL.)
(hissp.compiler..readerless _)      ;It compiles to Python
(eval _)                            ; and Python can evaluate that.

;; Programmatically modifying the data before compiling it is when
;; things start to get interesting, but more on that later.

;; Hissp-level strings contain Python code to include in the compiled
;; output. These usually contain identifiers, but can be anything.
;; Thus, Lissp identifiers read as strings at the Hissp level.
(quote identifier)                  ;Just a string.

;; The raw strings and hash strings in Lissp ("..."/#"..." syntax)
;; also read as strings at the Hissp level, but they contain a Python
;; string literal instead of a Python identifier.
(quote "a string")                  ;"..."/#"..." is reader syntax!
(eval (quote "a string"))           ;Python code. For a string.

;; Quoting does not suppress munging, however. That happens at read
;; time. Quoting doesn't happen until compile time.
(quote +)

;; Quoting works on any Hissp data.
(quote 42)                          ;Just a number. It was before though.

;; Strings in Hissp are also used for module handles and control
;; words. The compiler does some extra processing before emitting these
;; as Python code. Quoting suppresses this processing too.

math.                               ;Compiler coverts this to an import.
(quote math.)                       ;Quoting suppresses. No __import__.
(quote :?)                          ;Just a string. It was before though?
:?                                  ;Just a string?
((lambda (: a :?) a))               ;Oops, not quite! Contextual meaning here.
((lambda (: a (quote :?)) a))       ;Just a string. Even in context.

;;;; 12 Simple Reader Macros

;; Reader macros are metaprograms to abbreviate Hissp and don't
;; represent it directly. They apply to the next parsed Hissp object
;; at read time, before the Hissp compiler sees it, and thus before
;; they are compiled and evaluated. They end in # except for a few
;; builtins-- ' ! ` , ,@

;;; 12.1 Quote

;; The ' reader macro is simply an abbreviation for the quote special form.

'x                                  ;(quote x). Symbols are just quoted identifiers!
'(print "Hi")                       ;Quote to reveal the Hissp syntax tree.

;;; 12.2 Template Quote

;; (Like quasiquote, backquote, or syntax-quote from other Lisps.)
;; This is a DSL for making Hissp trees programmatically.
;; They're very useful for metaprogramming.

`print                              ;Automatic full qualification!
`foo+2                              ;Not builtin. Still munges.

`(print "Hi")                       ;Code as data. Seems to act like quote.
'`(print "Hi")                      ;But it's making a program to create the data.
`(print ,(.upper "Hi"))             ;Unquote (,) interpolates.

`(,'foo+2 foo+2)                    ;Interpolations not auto-qualified!
`(print ,@"abc")                    ;Splice unquote (,@) interpolates and unpacks.
`(print (.upper "abc"))             ;Template quoting is recursive
`(print ,@(.upper "abc"))           ; unless suppressed by an unquote.

;; Full qualification prevents accidental name collisions in
;; programmatically generated code. But full qualification doesn't work on
;; local variables, which can't be imported. For these, we use a template
;; count prefix instead of a qualifier to ensure a variable can only
;; be used in the same template it was defined in. The gensym reader
;; macro ($#) generates a symbol with the current template's number.
`($#eggs $#spam $#bacon $#spam)     ;Generated symbols for macro hygiene.
`$#spam                             ;Template number in name prevents collisions.

;; If you don't specify, by default, the template number is a prefix,
;; but you can put them anywhere in the symbol; $ marks the positions.
`$#spam$.$eggs$                     ;Lacking a gensym prefix, it gets fully qualified.

;; This is typically used for partially-qualified variables.
`,'$#self.$foo                      ;Interpolation suppressed auto-qualification.

;; You can use templates to make collections with interpolated values.
;; When your intent is to create data rather than code, unquote
;; each element.

(list `(,@"abc"
        ,1
        ,(+ 1 1)
        ,(+ 1 2)))

`(0 "a" 'b)                         ;Beware of strings and symbols.
`(,0 ,"a" ,'b)                      ;Just unquote everything in data templates.

(dict `((,0 ,1)
        ,@(.items (dict : spam "eggs"  foo 2)) ;dict unpacking
        (,3 ,4)))

;;;; 13 Compiler Macros

;; We can use functions to to create forms for evaluation.
;; This is metaprogramming: code that writes code.

(.update (globals)                  ;assign fills in a template to make a form.
         : assign
         (lambda (key value)
           `(.update (globals) : ,key ,value)))

;; Notice the arguments to it are quoted.
(assign 'SPAM '"eggs")              ;The result is a valid Hissp form.
(hissp.compiler..readerless _)      ;Hissp can compile it,
(eval _)                            ; and Python can evaluate that.
SPAM                                ;'eggs'

;; We can accomplish this more easily with a macro invocation.

;; Unqualified invocations are macro invocations if the identifier is in
;; the current module's _macro_ namespace. The REPL includes one, but
;; .lissp files don't have one until you create it.
(dir)
(dir _macro_)

;; Macros run at compile time, so they get all of their arguments
;; unevaluated. The compiler inserts the resulting Hissp at that point
;; in the program. Like special forms, macro invocations look like
;; function calls, but aren't.
(setattr _macro_ 'assign assign)    ;We can use our assign function as a macro!

;; Macro invocations look like ordinary function calls, but they aren't.
(assign SPAM "ham")                 ;This runs a metaprogram!
SPAM                                ;'ham'

;; We almost could have accomplished this one with a function, but we'd
;; have to either quote the variable name or use a : to pass it in as a
;; keyword. The macro invocation is a little shorter. Furthermore, the
;; globals function gets the globals dict for the current module. Thus,
;; an assign function would assign globals to the module it is defined
;; in, not the one where it is used! You could get around this by
;; walking up a stack frame with inspect, but that's brittle. The macro
;; version just works because it writes the code in line for you, so
;; the globals call appears in the right module.

;; Macros are a feature of the Hissp compiler. Macroexpansion happens at
;; compile time, after the reader, so macros also work in readerless
;; mode, or with Hissp readers other than Lissp, like Hebigo.

;; Hissp already comes with a define macro for global assignment.
;; Our assign macro just re-implemented this.
(help hissp.._macro_.define)

;; An invocation qualified with _macro_ is a macro invocation.
(hissp.._macro_.define SPAM "eggs") ;Note SPAM is not quoted.
SPAM                                ;'eggs'

;; See the macro expansion by calling it like a method with all arguments quoted.
;; This way, the callable isn't qualified with _macro_, so it's a normal call.
(.define hissp.._macro_ 'SPAM '"eggs") ;Method syntax is never macro invocation.

;; The REPL's default _macro_ namespace already has the bundled macros.
(help _macro_.define)


(setattr _macro_
         'triple
         (lambda (x)
           `(+ ,x (+ ,x ,x))))      ;Use a template to make Hissp.
(triple 4)                          ;12

(define loud-number
  (lambda x
    (print x)
    x))
(triple (loud-number 14))           ;Triples the *code*, not just the *value*.

;; But what if we want the expanded code to only run it once?
;; We can use a lambda to make a local variable and immediately call it.
((lambda (x)
   (+ x (+ x x)))
 (loud-number 14))

;; Python also allows us to use a default argument up front.
((lambda (: x (loud-number 14))
   (+ x (+ x x))))

;; Let's try making a template to produce code like that.
(setattr _macro_
         'oops-triple
         (lambda (expression)
           `((lambda (: x ,expression) ;Expand to lambda call for a local.
               (+ x (+ x x))))))
(oops-triple 14)                    ;Oops. Templates qualify symbols!

;; Remember, gensyms are an alternative to qualification for locals.
;; (Thus, gensyms are never auto-qualified by templates.)
(setattr _macro_
         'once-triple
         (lambda x
           `((lambda (: $#x ,x)
               (+ $#x (+ $#x $#x))))))
(once-triple (loud-number 14))

;; Notice the special QzMaybe_ qualifier generated by this template.
;; Templates create these for symbols in the invocation position when
;; they can't tell if _macro_ would work. The compiler skips QzMaybe_
;; unless it can resolve the symbol with QzMaybe_ as _macro_.
`(+ 1 2 3 4)

;; Recursive macro. (A multiary +). Note the QzMaybe_. If this had
;; been qualified like a global instead, the recursion wouldn't work.
(setattr _macro_
         '+
         (lambda (first : :* args)
           (.__getitem__ ; Tuple method. Templates produce tuples.
             `(,first ; Result when no args left.
               (operator..add ,first (+ ,@args))) ; Otherwise recur.
             (bool args))))        ;Bools are ints, remember?
(+ 1 2 3 4)

;; Notice that a new template doesn't qualify + with QzMaybe_ now that
;; it detects a macro with that name.
`(+ 1 2 3 4)

(setattr _macro_
         '*
         (lambda (first : :* args)
           (.__getitem__
             `(,first
               (operator..mul ,first (* ,@args)))
             (bool args))))
(* 1 2 3 4)

;; Macros only work as invocations, not arguments!
(functools..reduce * '(1 2 3 4))    ;Oops.
(functools..reduce (lambda xy (* x y)) ;Invocation, not argument.
                   '(1 2 3 4))

;; Sometimes you actually do want a name collision (or "capture"),
;; when the macro user should expect an implicit new local binding
;; (an "anaphor"). Don't qualify and don't gensym in that case.
;; Unquoting suppresses the recursive template quoting of tuples,
;; while the normal quote doesn't qualify symbols, so this combination
;; suppresses auto-qualification.
(setattr _macro_
         'XY
         (lambda (: :* body)
           `(lambda (,'X ,'Y)       ;,'X instead of $#X
              ,body)))

(functools..reduce (XY * X Y)       ;Invocation, not argument!
                   '(1 2 3 4))
((XY + Y X) "Eggs" "Spam")

;; It's possible for a macro to shadow a global. They live in different namespaces.
(+ 1 2 3 4)                         ;_macro_.+, not the global.
(functools..reduce + '(1 2 3 4))    ;Global function, not the macro!

(dir)                               ;Has QzPLUS_, but not QzSTAR_.
(dir _macro_)                       ;Has both.

;; Notice the qualifier on sep. Qualifying a keyword doesn't make sense.
(setattr _macro_
         'p123
         (lambda (sep)
           `(print 1 2 3 : sep ,sep)))

;; Note the : didn't have to be quoted here, because it's in a macro
;; invocation, not a call. The compiler also discarded the qualifier
;; on sep, because it's a kwarg.
(p123 :)

;;;; 14 Compiling and Running Files

;; ``$ lissp`` can run a .lissp file as __main__.
;; You cannot import .lissp directly. Compile it to .py first.

;; Finds spam.lissp & eggs.lissp in the current package & compile to spam.py & eggs.py
(.write_text (pathlib..Path "eggs.lissp")
             #"(print \"Hello World!\")")
(.write_text (pathlib..Path "spam.lissp")
             #"(print \"Hello from spam!\")
(.update (globals) : x 42)")
(hissp.reader..transpile __package__ 'spam 'eggs) ; Side effects on compilation

spam..x                             ; and import!
spam..x                             ;Python caches imports.
eggs.

(any (map (lambda f (os..remove f)) ;Cleanup.
     '(eggs.lissp spam.lissp spam.py eggs.py)))

;;;; 15 The Bundled Macros

;; To make the REPL more usable, it comes with some basic macros already
;; defined. Their design has been deliberately restricted so that their
;; compiled output does not require the Hissp package to be installed to
;; work. While these may suffice for small or embedded Hissp projects,
;; you will probably want a more capable macro suite (such as Hebigo's)
;; for general use. You are not required to use the bundled macros at all,
;; so by default, they don't work in .lissp files unqualified. For
;; convenience, hissp._macro_ is a reference to hissp.macros._macro_,
;; making all the bundled macros available qualified with hissp.._macro_.

;;; 15.1 Collections

(@ 1 2 3)                           ;list
(# 1 2 3)                           ;set
(% 1 2  3 4  5 6)                   ;dict (alternates key, value)

;; We can make tuples at the reader level already.
'(1 2 3)                            ;data tuple (recursively quoted)
`(,1 ,2 ,3)                         ;data tuple (via template)

;; Collection macro mnemonics:
;; Array list() (@rray)
;; Hash set() (#set)
;; and dict() of key-value pairs (%).

(@ (ord "*") :* "abc" 42 :* '(2 3)) ;List, with unpacking.
`(,(ord "*") ,@"abc" ,42 ,@'(2 3))  ;Tuple, with unpacking (via splice).

(# 1 :* (@ 1 2 3) 4)                ;Set, with unpacking.

(% 1 2  :** (dict : x 3  y 4)  5 6) ;Dict, with mapping unpacking.

;;; 15.2 Side Effect

(print (prog1 0                     ;Sequence for side effects, eval to first.
         (print 1)
         (print 2)))

(print (progn (print 1)             ;Sequence for side effects, eval to last.
              (print 2)
              3))

(prog1                              ;Sequence for side effects, eval to first.
  (progn (print 1)                  ;Sequence for side effects, eval to last.
         3)
  (print 2))

;;; 15.3 Definition

(deftype Point2D (tuple)
  __doc__ "Simple ordered pair."
  __new__ (lambda (cls x y)
            (.__new__ tuple cls `(,x ,y)))
  __repr__ (lambda (self)
             (.format "Point2D({!r}, {!r})" : :* self)))
(Point2D 1 2)

(deftype@ ((lambda (cls)
             (setattr cls 's (operator..concat cls.s "Out"))
             cls)
           (lambda (cls)
             (setattr cls 's (operator..concat cls.s "Inside"))
             cls))
          Decorated ()
  s "@")
Decorated.s

;; Define a function in the _macro_ namespace.
;; Creates the _macro_ namespace if absent.
;; Can also have a docstring.
(defmacro p123 (sep)
  "Prints 1 2 3 with the given separator"
  `(print 1 2 3 : sep ,sep))

(help _macro_.p123)

(define SPAM "tomato")              ;We've seen this one already.
SPAM

;; Like define, but won't overwrite an existing global.
;; Useful when sending the whole file to the REPL repeatedly or when
;; using importlib.reload and you want to cache an expensive object
;; instead of re-initializing it every time.
(defonce CACHE (types..SimpleNamespace : x 1))
(setattr CACHE 'x 42)
(defonce CACHE (progn (print "not evaluated")
                      (types..SimpleNamespace : x 1)))
CACHE

;;; 15.4 Locals

(let (x "a"                         ;Create locals.
      y "b")                        ;Any number of pairs.
  (print x y)
  (let (x "x"
        y (+ x x))                  ;Not in scope until body.
    (print x y))                    ;Outer variables shadowed.
  (print x y))                      ;Inner went out of scope.

(let-from (a b : :* cs) "abcdefg"   ;Locals from iterable.
  (print cs b a))

(% 1 2  3 4)
(let*from ((ab cd) (.items _)    ;Nested let-froms.
           (a b) ab
           (c d) cd)
  (print a b c d))

(let*from ((ab cd) (.items _)    ;Try to avoid excessive stack frames.
           (a b c d) `(,@ab ,@cd))
  (print a b c d))

(let-from (a c b d)                 ;Didn't really need let*from this time.
          `(,@(.keys _) ,@(.values _)) ; Not always this easy though.
  (print a b c d))

;;; 15.5 Configuration

(attach (types..SimpleNamespace) + : a 1  b "Hi")

(doto (list)
  (.extend "bar")
  (.sort)
  (.append "foo"))

(define spam (dict))
(set! spam 2 10)                    ;Like operator..setitem, but returns value given.
spam
(zap! operator..iadd spam 2 1)      ;Augmented item assignment, like +=.
spam

(define spam (types..SimpleNamespace))
(set@ spam.foo 10)                  ;Similarly for attributes.
spam
(zap@ operator..iadd spam.foo 1)
spam

;; set/zap mnemonics: @tribute, !tem.

;;; 15.6 Threading

(-> "world!"                        ;Thread-first
    (.title)
    (->> (print "Hello")))          ;Thread-last
(help _macro_.->)
(help _macro_.->>)

;;; 15.7 The Prelude

;; An inline convenience micro-prelude for Hissp.
;; Imports partial and reduce; star imports from operator and itertools;
;; defines Python interop utilities engarde, enter, and Ensue; and
;; imports a copy of hissp.macros.._macro_ (if available). Usually the
;; first form in a file, because it overwrites _macro_, but completely
;; optional. Implied for $ lissp -c commands.
(prelude)                           ;/!\ Or (hissp.._macro_.prelude)

;;; 15.8 Control Flow

;; Hissp has no innate control flow, but you can build them with macros.

;; Like recursion with tail elimination.
(loop-from x '(3)                   ;Unpacks as let-from.
  (when x
    (print x)
    (recur-from (@ (sub x 1)))))

(any-map index (range 1 11)         ;Imperative loop with break.
  (print index : end " ")
  (not_ (mod index 7)))
;; 1 2 3 4 5 6 7 True

(any*map (i c) (enumerate "abc" 1)  ;As any-map, but with starmap.
  (print (mul i c)))

(any-map c "ab"
  (if-else (eq c "b")               ;ternary conditional
    (print "Yes")
    (print "No")))

(any-map x (@ -0.6 -0.0 42.0 math..nan)
  (cond (lt x 0) (print "Negative") ;if-else cascade
        (eq x 0) (print "Zero")
        (gt x 0) (print "Positive")
        :else (print "Not a number")))

(any-map c "abc"
  (print "in loop")
  (unless (eq c "b")                ;else-only block
    (print "in unless")
    (print c))
  (when (eq c "a")                  ;if-only block
    (print "in when")
    (print c)))

(any-map x '(1 2 spam 42)
  (case x (print "default")         ;switch case
    (0 2 4 6 8) (print "even")
    (1 3 5 7 spam) (print "odd")))

;; Shortcutting logical and.
(&& True True False)
(&& False (print "oops"))
(&& True 42)

;; Shortcutting logical or.
(|| True (print "oops"))
(|| 42 False)

;;; 15.9 Raising Exceptions

(throw Exception)                   ;Raise exception objects or classes.
(throw (TypeError "message"))

(throw-from Exception (Exception "message")) ;Explicit chaining.

;; There's also a throw* you normally shouldn't use. See API doc.

;; Assertions. They're always about something, which is
;; threaded-first into the predicate expression, and is the result of
;; the form. The message expressions are optional. In this context,
;; the `it` refers to the something.
;; Try turning off __debug__ in a new REPL: $ python -Om hissp
(ensure 7 (-> (mod 2) (eq 0))
  it "That's odd.")

;; Note that for pre-compiled code, it's the __debug__ state at
;; compile time, not at run time, that determines if ensure
;; assertions are turned on.

;;; 15.10 Obligatory Factorial III

;; With the prelude, we can define a nicer-looking version.
(define factorial-III
  (lambda i
    (if-else (le i 1)
      1
      (mul i (factorial-III (sub i 1))))))
(factorial-III 7)

;;;; 16 Exception handling

;; Defined by the prelude. Guards against the targeted exception classes.
(engarde `(,FloatingPointError ,ZeroDivisionError)               ;two targets
         (lambda e (print "Oops!") e)                            ;handler (returns exception)
         truediv 6 0)                                            ;calls it on your behalf

(engarde ArithmeticError repr truediv 6 0)                       ;superclass target
(engarde ArithmeticError repr truediv 6 2)                       ;returned answer

;; You can nest them.
(engarde Exception                                               ;The outer engarde
  print
  engarde ZeroDivisionError                                      ; calls the inner.
  (lambda e (print "It means what you want it to mean."))
  truediv "6" 0)                                                 ;Try variations.

(engarde Exception
         (lambda x x.__cause__)
         (lambda : (throw-from Exception (Exception "msg"))))

;;;; 17 Generators

;; Defined by the prelude, Ensue gives you infinite lazy iterables,
;; easy as recursion. Compare to loop-from.
(define fibonacci
  (lambda (: a 1  b 1)
    (Ensue (lambda (step)
             (set@ step.Y a)        ;Y for yield.
             (fibonacci b (add a b))))))
(list (itertools..islice (fibonacci) 7))


(define my-range                    ;Terminate by not returning an Ensue.
  (lambda in
    (Ensue (lambda (step)
             (when (lt i n)         ;Acts like a while loop.
               (set@ step.Y i)
               (my-range (add i 1) n)))))) ;Conditional recursion.
(list (my-range 1 6))

;; Set F to yield From.
(Ensue (lambda (step)
         (attach step :
           Y '(1 2 3 4 5)
           F True)
         None))
(list _)

(define recycle
  (lambda (itr)
    (Ensue (lambda (step)
             (attach step :         ;Implicit continuation.
               Y itr
               F 1)))))             ;The step is an Ensue instance.
(-> '(1 2 3) (recycle) (islice 7) (list))

(define echo
  (Ensue (lambda (step)
           (set@ step.Y step.sent)
           step)))
(.send echo None)                   ;Always send a None first. Same as Python.
(.send echo "Yodle!")               ;Generators are two-way.
(.send echo 42)

;;;; 18 Context Managers

(define wrap
  (contextlib..contextmanager
   (lambda (msg)
     (print "enter" msg)
     (Ensue (lambda (step)
              (set@ step.Y msg)
              (Ensue (lambda (step)
                       (print "exit" msg))))))))

;; Defined by the prelude. Like a with statement.
(enter (wrap 'A)
       (lambda a (print a)))

(enter (wrap 'A)
 enter (wrap 'B)
 enter (wrap 'C)                    ;You can stack them.
 (lambda abc (print a b c)))

(define suppress-zde
  (contextlib..contextmanager
   (lambda :
     (Ensue (lambda (step)
              (attach step :
                Y None
                X ZeroDivisionError) ;X for eXcept (can be a tuple).
              (Ensue (lambda (step)
                       (print "Caught a" step.sent))))))))
(enter (suppress-zde)
  (lambda _ (truediv 1 0)))
(enter (suppress-zde)
  (lambda _ (truediv 4 2)))         ;No exception, so step.sent was .send() value.
(enter (suppress-zde)
  (lambda _ (throw Exception)))

;;;; 19 Advanced Reader Macros

;;; 19.1 The Discard Macro

_#"The discard reader macro _# omits the next form.
It's a way to comment out code structurally.
It can also make block comments like this one.
This would show up when compiled if not for _#.
"
(print 1 _#(I'm not here!) 2 3)

;;; 19.2 Fully-Qualified Reader Macros

;; Invoke any fully-qualified callable on the next parsed object at read time.
builtins..hex#3840                  ;Fully-Qualified name ending in # is a reader macro.
builtins..ord#Q                     ;Reader macros make literal notation extensible.
math..exp#1                         ;e^1. Or to whatever number. At read time.

;; Reader macros compose like functions.
'hissp.munger..demunge#Qz_QzGT_QzGT_   ;Note the starting '.
''x
'\'x

;; The reader normally discards them, but
'builtins..repr#;comments are parsed objects too!

_#"Except for strings and tuples, objects in Hissp should evaluate to
themselves. But when the object lacks a Python literal notation,
the compiler is in a pickle!
"
builtins..float#inf

;;; 19.3 Inject

_#"The 'inject' reader macro compiles and evaluates the next form at
read time and injects the resulting object directly into the Hissp
tree, like a fully-qualified reader macro does.
"

'(1 2 (operator..add 1 2))          ;Quoting happens at compile time.
'(1 2 .#(operator..add 1 2))        ;Inject happens at read time.

(fractions..Fraction 1 2)           ;Run time eval. Compiles to equivalent code.
.#(fractions..Fraction 1 2)         ;Read time eval. Compiles to equivalent object.

_#"Recall that Hissp-level string objects can represent
arbitrary Python code. It's usually used for identifiers,
but can be anything, even complex formulas.
"
(lambda abc
  ;; Hissp may not have operators, but Python does.
  .#"(-b + (b**2 - 4*a*c)**0.5)/(2*a)")

_#"Remember the raw string and hash string reader syntax makes Python-
level strings, via a Hissp-level string containing a Python string
literal. It is NOT for creating a Hissp-level string, which would
normally contain Python code. Use inject for that.
"
'"a string"                         ;Python code for a string. In a string.
;; Injection of an object to the Hissp level. In this case, a string object.
'.#"a string"                       ;Quoting renders a Hissp-level string as data.

_#"Objects without literals don't pickle until the compiler has to emit
them as Python code. That may never happen if another macro gets there first.
"
'builtins..repr#(re..compile#.#"[1-9][0-9]*" builtins..float#inf)
re..compile#.#"[1-9][0-9]*"

;; Statement injections work at the top level only.
.#"from operator import *"          ;All your operator are belong to us.

;;;; 20 The Bundled Reader Macros

(reduce XY#(add Y X) "abcd")        ;Binary anaphoric lambda.
(list (map X#(@ X) "abc"))          ;Unary anaphoric lambda.

(engarde Exception
         X#(print X.__cause__)      ;Unary again.
         &#(throw-from Exception (Exception "msg"))) ;Nullary/thunk.

;; Also XYZ# XYZW# See API doc.

b#"bytes"                           ;Bytes reader macro.
b'bytes'                            ;NameError about 'bQzAPOS_bytesQzAPOS_'

b#"bytes
with
newlines
"                                   ;Same as b#"bytes\nwith\nnewlines\n".

(help _macro_.b\#)                  ;Unqualified reader macros live in _macro_ too.

;; The en- reader macro.
(en#list 1 2 3)
(en#.extend _ 4 5 6)                ;Methods too.
_

(en#collections..deque 1 2 3)       ;Generalizes to any function of 1 iterable.

;; Anaphoric lambda of any number of args.
(define enjoin en#X#(.join "" (map str X)))
(enjoin "Sum: "(add 2 3)". Product: "(mul 2 3)".")

;; Not technically a reader macro, but a bundled macro for defining them.
;; Alias makes a new reader macro to abbreviate a qualifier.
;; This is an alternative to adding an import to _macro_ or globals.
(hissp.._macro_.alias M: hissp.._macro_)
'M:#alias
M:#b\#                              ;b# macro callable
(M:#b\# "b# macro at compile time")
hissp.._macro_.b\##"Fully qualified b# macro at read time."

;; A couple of aliases are bundled:
op#add
i#chain

;; A bundled abbreviation
(list chain#(.items (dict : a 1  b 2  c 3)))

(@#upper "shout")                   ;Get an attribute without calling it.
(_)

(define class-name @#__class__.__name__) ;Attributes chain.
(class-name object)
(class-name "foo")

(define first get#0)                ;Similarly, for items.
(first "abc")

(get#(slice None None -1) "abc")    ;Slicing.
(get#'+ (dict : foo 2  + 1))        ;These also work on dicts.

;; Measures execution time.
time#(time..sleep .05)

(add 5 spy#(mul 7 3))                  ;Debug subexpressions.
;; stderr: ('mul', 7, 3) => 21

;; Anaphoric assignment target namespace.
;; Very powerful, but more imperative in style.
;; Use responsibly.
the#(print (set@ the.x (add 1 1))
           the.x)

;; Comment string. Parsed objects, remember?
<<#;Don't worry about the "quotes".

;;; 20.1 Aside: Extra (!), the Final Builtin Reader Macro

_#"Reader macros take one primary argument, but additional arguments
can be passed in with the extra macro !. A reader macro consumes the
next parsed object, and if it's an Extra, consumes one again. Thus,
extras must be written between the # and primary argument, but because
they're often optional refinements, which are easier to define as
trailing optional parameters in Python functions, they get passed
in after the primary argument.
"
(setattr _macro_ 'L\# en#list)

L#primary
L#!1 primary

;; Alias can work on reader macros too!
M:#!b"Read-time b# via alias."      ;Extra arg for alias with (!)

L# !1 !2 primary                    ;Note the order!
.#(en#list "primary" 1 2)           ;Inject. Note the order.

!1                                  ;! is for a single Extra.
hissp.reader..Extra#(: :? 0 :* (1 2 3)) ; but Extra can have multiple elements.
!!!1 2 3                            ;Extras can have extras. They stack.

L#!: !:* !(0 1 2) !:? !3 primary    ;Unpacking works like calls.
L#!0 !: !:* !(1 2 3)primary         ;Same effect.
L#hissp.reader..Extra#(0 : :* (1 2 3))primary ;Same effect.

(setattr _macro_ 'E\# hissp.reader..Extra)

L# !0 E#(1 2) !3 primary            ;Same effect.
L#E#(0 : :* (1 2 3))primary         ;Same effect.

;; Kwargs also work like calls.
builtins..dict#()
builtins..dict#!: !spam !1  !foo !2  !:** !.#(dict : eggs 3  bar 4)()
builtins..dict#E#(: spam 1  foo 2  :** .#(dict : eggs 3  bar 4))()
builtins..dict#!: !!spam 1 !!foo 2 !!:** .#(dict : eggs 3  bar 4)()

;; Yeah, you can nest these if you have to.
L# !x
   !L# !1 L# !A
          inner
   !y
outer

;; The compiler will evaluate tuples no matter how the reader produces them.
builtins..tuple#L# !"Hello" !"World!" print

;;; 20.2 Joined Comment String

<<#!;C:\bin
   !;C:\Users\ME\Documents
   !;C:\Users\ME\Pictures
";"

;; Embed other languages without escapes.
(exec
  <<#
  !;for i in 'abc':
  !;    for j in 'xyz':
  !;        print(i+j, end=" ")
  !;print('.')
  !;
  #"\n")