Skip to content

Commit

Permalink
Merge pull request #245 from gilch/kwarg-tags
Browse files Browse the repository at this point in the history
Excise Extra take 2 (Kwarg tags)
  • Loading branch information
gilch authored Dec 24, 2023
2 parents fdd447c + 9f51bee commit e2f5cdc
Show file tree
Hide file tree
Showing 7 changed files with 150 additions and 487 deletions.
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

0 comments on commit e2f5cdc

Please sign in to comment.