Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Excise Extra take 2 (Kwarg tags) #245

Merged
merged 10 commits into from
Dec 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
204 changes: 2 additions & 202 deletions docs/lissp_whirlwind_tour.rst
Original file line number Diff line number Diff line change
Expand Up @@ -897,7 +897,7 @@ Lissp Whirlwind Tour
;;; represent it directly. Tags apply to the next parsed Hissp object
;;; at read time, before the Hissp compiler sees it, and thus before
;;; they are compiled and evaluated. Tags end in # except for a few
;;; builtins-- ' ! ` , ,@
;;; builtins-- ' ` , ,@

;;;; 11.1 Quote

Expand Down Expand Up @@ -1327,7 +1327,7 @@ Lissp Whirlwind Tour


;; Remember, a gensym hash prefix is an alternative to qualification
;; for locals. (Thus, templates don't qualified them.)
;; for locals. (Thus, templates don't qualify them.)
#> (setattr _macro_
#.. 'once-triple
#.. (lambda x
Expand Down Expand Up @@ -1886,203 +1886,3 @@ Lissp Whirlwind Tour
;; Statement injections work at the top level only.
#> .#"from operator import *" ;All your operator are belong to us.
>>> from operator import *


;;;; 15.4 Extra (!), the Final Builtin Reader Macro

;;; Reader macros have one primary argument, but additional arguments
;;; can be passed in with the extra macro !. A tag consumes the next
;;; parsed object, and if it's an Extra, consumes one again. Thus,
;;; extras must be written between the tag and primary, 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.

#> (setattr _macro_ 'L\# en#list) ; (help _macro_.en\#)
#..
>>> setattr(
... _macro_,
... 'LQzHASH_',
... (lambda *_Qz6RFWTTVXz_xs:
... list(
... _Qz6RFWTTVXz_xs)))


#> L#primary
>>> ['primary']
['primary']

#> L#!1 primary
>>> ['primary', 1]
['primary', 1]


;; Alias can work on reader macros too!
#> (hissp.._macro_.alias M: hissp.._macro_)
>>> # hissp.._macro_.alias
... # hissp.macros.._macro_.defmacro
... # hissp.macros.._macro_.let
... (lambda _QzAW22OE5Kz_fn=(lambda _QzARAQTXTEz_prime,_QzARAQTXTEz_reader=None,*_QzARAQTXTEz_args:(
... 'Aliases ``hissp.._macro_`` as ``MQzCOLON_#``.',
... # hissp.macros.._macro_.ifQz_else
... (lambda b,c,a:c()if b else a())(
... _QzARAQTXTEz_reader,
... (lambda :
... __import__('builtins').getattr(
... __import__('hissp')._macro_,
... ('{}{}').format(
... _QzARAQTXTEz_reader,
... # hissp.macros.._macro_.ifQz_else
... (lambda b,c,a:c()if b else a())(
... 'hissp.._macro_'.endswith(
... '._macro_'),
... (lambda :'QzHASH_'),
... (lambda :('')))))(
... _QzARAQTXTEz_prime,
... *_QzARAQTXTEz_args)),
... (lambda :
... ('{}.{}').format(
... 'hissp.._macro_',
... _QzARAQTXTEz_prime))))[-1]):(
... __import__('builtins').setattr(
... _QzAW22OE5Kz_fn,
... '__doc__',
... 'Aliases ``hissp.._macro_`` as ``MQzCOLON_#``.'),
... __import__('builtins').setattr(
... _QzAW22OE5Kz_fn,
... '__qualname__',
... ('.').join(
... ('_macro_',
... 'MQzCOLON_QzHASH_',))),
... __import__('builtins').setattr(
... __import__('operator').getitem(
... __import__('builtins').globals(),
... '_macro_'),
... 'MQzCOLON_QzHASH_',
... _QzAW22OE5Kz_fn))[-1])()

#> M:#!b"Read-time b# via alias." ;Extra arg for alias with (!)
>>> b'Read-time b# via alias.'
b'Read-time b# via alias.'


#> L# !1 !2 primary ;Note the order!
>>> ['primary', 1, 2]
['primary', 1, 2]

#> .#(en#list "primary" 1 2) ;Inject. Note the order.
>>> ['primary', 1, 2]
['primary', 1, 2]


#> !1 ;! is for a single Extra.
>>> __import__('pickle').loads( # Extra([1])
... b'ccopyreg\n'
... b'_reconstructor\n'
... b'(chissp.reader\n'
... b'Extra\n'
... b'cbuiltins\n'
... b'tuple\n'
... b'(I1\n'
... b'ttR.'
... )
Extra([1])

#> hissp.reader..Extra#(: :? 0 :* (1 2 3)) ; but Extra can have multiple elements.
>>> __import__('pickle').loads( # Extra([':', ':?', 0, ':*', (1, 2, 3)])
... b'ccopyreg\n'
... b'_reconstructor\n'
... b'(chissp.reader\n'
... b'Extra\n'
... b'cbuiltins\n'
... b'tuple\n'
... b'(V:\n'
... b'V:?\n'
... b'I0\n'
... b'V:*\n'
... b'(I1\n'
... b'I2\n'
... b'I3\n'
... b'tttR.'
... )
Extra([':', ':?', 0, ':*', (1, 2, 3)])

#> !!!1 2 3 ;Extras can have extras.
>>> __import__('pickle').loads( # Extra([1, 2, 3])
... b'ccopyreg\n'
... b'_reconstructor\n'
... b'(chissp.reader\n'
... b'Extra\n'
... b'cbuiltins\n'
... b'tuple\n'
... b'(I1\n'
... b'I2\n'
... b'I3\n'
... b'ttR.'
... )
Extra([1, 2, 3])


#> L#!: !:* !(0 1 2) !:? !3 primary ;Unpacking works like calls.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]

#> L#!0 !: !:* !(1 2 3)primary ;Same effect.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]

#> L#hissp.reader..Extra#(0 : :* (1 2 3))primary ;Same effect.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]


#> (setattr _macro_ 'E\# hissp.reader..Extra)
>>> setattr(
... _macro_,
... 'EQzHASH_',
... __import__('hissp.reader',fromlist='?').Extra)


#> L# !0 E#(1 2) !3 primary ;Same effect.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]

#> L#E#(0 : :* (1 2 3))primary ;Same effect.
>>> ['primary', 0, 1, 2, 3]
['primary', 0, 1, 2, 3]


;; Kwargs also work like calls.
#> builtins..dict#()
>>> {}
{}

#> builtins..dict#!: !spam !1 !foo !2 !:** !.#(dict : eggs 3 bar 4)()
>>> {'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}
{'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}

#> builtins..dict#E#(: spam 1 foo 2 :** .#(dict : eggs 3 bar 4))()
>>> {'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}
{'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}

#> builtins..dict#!: !!spam 1 !!foo 2 !!:** .#(dict : eggs 3 bar 4)()
>>> {'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}
{'spam': 1, 'foo': 2, 'eggs': 3, 'bar': 4}


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


;; The compiler will evaluate tuples no matter how the reader produces them.
#> builtins..tuple#L# !"Hello" !"World!" print
>>> print(
... ('Hello'),
... ('World!'))
Hello World!
2 changes: 1 addition & 1 deletion docs/macro_tutorial.rst
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ Now we're shorter than Python:
But we're also less general.
We can change the expression,
but we've hardcoded the parameters to it.
The fixed parameter name is fine as long unless it shadows a `nonlocal <nonlocal>` we need,
The fixed parameter name is fine unless it shadows a `nonlocal <nonlocal>` we need,
but what if we needed two parameters?
Could we make a macro for that?

Expand Down
120 changes: 60 additions & 60 deletions docs/primer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -757,7 +757,7 @@ The ``:*`` can likewise act as a separator starting the keyword-only arguments,
and can likewise be paired with ``:?``.

The normal parameters in between these can be passed in either as positional arguments
or as keyword arguments.
or as keyword arguments (kwargs).

The ``:*`` can instead pair with a parameter name,
which collects the remainder of the positional arguments into a tuple.
Expand Down Expand Up @@ -1367,14 +1367,13 @@ Unfortunately, there are some objects even pickle can't handle.

Hissp had to give up with an error this time.

Qualified Reader Macros
+++++++++++++++++++++++
Reader Tags
+++++++++++

Besides a few built-ins,
reader macros in Lissp consist of a symbol ending with a ``#``,
reader macros in Lissp consist of a special symbol ending with ``#``\ s,
called a *tag*,
followed by another form,
called its *primary*.
followed by additional argument forms.

A function named by a `qualified identifier`_ is invoked on the form,
and the reader embeds the resulting object into the output Hissp:
Expand Down Expand Up @@ -1444,12 +1443,64 @@ then there is no run-time overhead for the alternative notation,
because it's compiled to ``(81)``,
just like there's no run-time overhead for using a hex literal instead of decimal in Python.

Sometimes tags can be unqualified.
Three tags are built into the reader:
Multiary Tags
+++++++++++++

Reader tags may take multiple arguments.
You indicate how many with the number of trailing ``#``\ s.

.. code-block:: REPL

#> fractions..Fraction# .#"2/3" ; Two thirds.
>>> __import__('pickle').loads( # Fraction(2, 3)
... b'cfractions\n'
... b'Fraction\n'
... b'(V2/3\n'
... b'tR.'
... )
Fraction(2, 3)

#> fractions..Fraction## 2 3 ; Notice the extra #.
>>> __import__('pickle').loads( # Fraction(2, 3)
... b'cfractions\n'
... b'Fraction\n'
... b'(V2/3\n'
... b'tR.'
... )
Fraction(2, 3)

Reader tags may also take keyword arguments,
made with a helper kwarg tag ending in ``=#``,
which can be helpful quick refinements for functions with optional arguments,
without the need to create a new reader macro for each specialization.

.. code-block:: REPL

#> builtins..int# .#"21" ; Normal base ten
>>> (21)
21

#> builtins..int## base=#6 .#"21" ; base 6, via base=# kwarg tag
>>> (13)
13

The helper tags ``*=#`` and ``**=#`` unpack the argument at that position,
either as positional arguments or keyword arguments, respectively.

Unqualified Tags
++++++++++++++++

Sometimes tags have no qualifier.
Three such tags are built into the reader:
inject ``.#``, discard ``_#``, and gensym ``$#``.

The reader will also check the current module's ``_macro_`` namespace (if it has one)
for attributes ending in ``#`` (i.e. ``QzHASH_``)
when it encounters an unqualified tag.
The ``#`` is only in an attribute name to distinguish them from normal compile-time macros,
not to indicate arity.
It is possible to use a tag name containing extra ``#``\ s,
or ending in ``=#`` if escaped with a ``\``.

Discard
+++++++
Expand All @@ -1470,7 +1521,6 @@ Templates
+++++++++

Besides ``'``, which we've already seen,
and ``!``, which we'll cover later,
Lissp has three other built-in reader macros that don't require a ``#``:

* ````` template quote
Expand Down Expand Up @@ -1593,7 +1643,7 @@ Gensyms
+++++++

The built-in tag ``$#`` creates a *generated symbol*
(gensym) based on the given primary symbol.
(gensym) based on the given symbol.
Within a template, the same gensym name always makes the same gensym:

.. code-block:: REPL
Expand Down Expand Up @@ -1640,56 +1690,6 @@ which can sometimes happen when they are very short.

By default, the hash is a prefix, but you can mark some other location for it using a $.

Extra
+++++

The final built-in reader macro ``!``
is used to pass extra arguments to other reader macros.
None of Lissp's built-in reader macros use it
(although some of the `bundled macros <hissp.macros>` do),
but extras can be helpful quick refinements for functions with optional arguments,
without the need to create a new reader macro for each specialization.

.. code-block:: REPL

#> builtins..int#.#"21" ; normal base ten
>>> (21)
21

#> builtins..int#!6 .#"21" ; base six via optional base arg
>>> (13)
13

A reader macro can have more than one extra.

Note that since extras are often optional arguments,
they're passed in *after* the reader macro's primary argument,
even though they're written first.

.. code-block:: REPL

#> builtins..range# !0 !-1 20
>>> __import__('pickle').loads( # range(20, 0, -1)
... b'cbuiltins\n'
... b'range\n'
... b'(I20\n'
... b'I0\n'
... b'I-1\n'
... b'tR.'
... )
range(20, 0, -1)

Pass in keyword arguments by pairing with a name after ``:``,
like calls. ``:*`` and ``:**`` unpacking also work here.

.. code-block:: REPL

#> builtins..int# !: !base !6 .#"21"
>>> (13)
13

See the section on Extras in the `lissp_whirlwind_tour` for more examples.

Macros
======

Expand Down
Loading
Loading