Skip to content

Commit

Permalink
Tags use only one # again
Browse files Browse the repository at this point in the history
Pack macros now lack the implicit positional.
  • Loading branch information
gilch committed Dec 23, 2023
1 parent f72893a commit 9bf1162
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 97 deletions.
101 changes: 47 additions & 54 deletions docs/primer.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1371,7 +1371,7 @@ Reader Tags
+++++++++++

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

Expand Down Expand Up @@ -1446,8 +1446,23 @@ just like there's no run-time overhead for using a hex literal instead of decima
Multiary Tags
+++++++++++++

Reader tags may take multiple arguments.
You indicate how many with the number of trailing ``#``\ s.
Reader tags may also take keyword arguments indicated by keyword prefixes,
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
#> base=builtins..int#6 .#"21" ; base six via optional base= kwarg
>>> (13)
13
The special prefixes ``*=`` and ``**=`` unpack the argument at that position,
either as positional arguments or keyword arguments, respectively.
An empty prefix (``=``) indicates an additional positional argument.

.. code-block:: REPL
Expand All @@ -1460,7 +1475,7 @@ You indicate how many with the number of trailing ``#``\ s.
... )
Fraction(2, 3)
#> fractions..Fraction## 2 3 ; Notice the extra #.
#> =fractions..Fraction# 2 3 ; Notice the =.
>>> __import__('pickle').loads( # Fraction(2, 3)
... b'cfractions\n'
... b'Fraction\n'
Expand All @@ -1469,72 +1484,50 @@ You indicate how many with the number of trailing ``#``\ s.
... )
Fraction(2, 3)
Reader tags may also take keyword arguments indicated by keyword prefixes,
which can be helpful quick refinements for functions with optional arguments,
without the need to create a new reader macro for each specialization.
Multiple prefixes are allowed.
They pull from the reader stream in the order written.
(The final argument for the tag itself is always positional.)

.. code-block:: REPL
#> builtins..int#.#"21" ; Normal base ten
>>> (21)
21
Unqualified Tags
++++++++++++++++

#> base=builtins..int##6 .#"21" ; base six via optional base= kwarg
>>> (13)
13
Sometimes tags have no qualifier.
Three such tags are built into the reader:
inject ``.#``, discard ``_#``, and gensym ``$#``.

The special prefixes ``*=`` and ``**=`` unpack the agument at that position,
either as positional arguments or keyword arguments, respectively.
Prefixes pull from the reader stream in the order written.
Each prefix requires another ``#``.
Any leftover ``#``\ s each pull a positional argument after that.
An empty prefix (``=``) indicates a single positional argument.
These can be used to put positional arguments between or before kwargs.
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.
Prefixes should not be included in the attribute name.
It is possible to use a tag name containing ``=``\ s.
but they must each be escaped with a ``\``.

Pack Objects
++++++++++++

Try to avoid using more than about 3 or 4 ``#``\ s in a tag,
because that gets hard to read.
You typically won't need more than that.
For too many homogeneous arguments,
(i.e., with the same semantic type)
consider using ``*=`` applied to a tuple instead.
For too many heterogeneous arguments, consider ``**=``.
For complicated expressions,
consider using inject (``.#``) on tuple expressions instead of using tags.

A tag can be empty if it has at least one prefix,
even the empty prefix (``=``).
An empty tag creates a `Pack` object,
which contains any args and kwargs given.
When a reader tag pulls one, it automatically unpacks it.

`Pack`\ s are used to order and group tag arguments in a hierarchical way,
for improved legibility.
They're another way to avoid using too many ``#``\ s in a row.
They allow you to write the keywords immediately before their values,
instead of up front.
When another reader tag pulls one as a positional argument, it automatically unpacks it.

`Pack`\ s are only meant for reader tags.
They should be consumed immediately at read time,
and are only allowed to survive past that for debugging purposes.

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.
Prefixes should not be included in the attribute name either.
It is possible to use a tag name containing ``=`` or extra ``#``\ s,
but they must be escaped with a ``\``.
`Pack`\ s are used to order and group tag arguments in a hierarchical way,
for improved legibility.
They allow you to write the keywords immediately before their values,
instead of up front.
They're also a way to avoid using too many positional prefixes (``=``\ s) in a row,
which can get hard to read.
You typically won't need more than ``===``.
For too many homogeneous arguments,
(i.e., with the same semantic type)
consider using ``*=`` applied to a tuple instead.
For too many heterogeneous arguments, consider ``**=``.
For complicated expressions,
consider using inject (``.#``) on tuple expressions instead of using tags.

Discard
+++++++
Expand Down
6 changes: 3 additions & 3 deletions docs/style_guide.rst
Original file line number Diff line number Diff line change
Expand Up @@ -376,9 +376,9 @@ a form modifying the previous (e.g. decorating, attaching attributes),
or adding it to a collection may be attached to it.

However, in many of these cases,
the groups could be written as a single top-level form insead,
the groups could be written as a single top-level form instead,
given the appropriate functions or macros.
E.g. `dict.update` (on `globals`), `let`, `@##<QzAT_QzHASH_>`, `attach`, `doto`.
E.g. `dict.update` (on `globals`), `let`, `@#<QzAT_QzHASH_>`, `attach`, `doto`.

Try to avoid blank lines within forms.
You may need them for separating groups whose elements span lines
Expand Down Expand Up @@ -778,7 +778,7 @@ in which case they may be treated as literal values.
Avoid using inline or margin comments as commentary between a tag and its target,
as this can cause errors when they are instead treated as arguments.
(Usually, tags are attached to one argument, so this doesn't come up,
but e.g. the bundled decorator macro `@##<QzAT_QzHASH_>` typically is not.)
but e.g. the bundled decorator macro `@#<QzAT_QzHASH_>` typically is not.)
You may use a discarded string instead ``_#"NB foo"``.
A good syntax highlighter specialized for Lissp may be able
to indicate when a comment token is not discarded,
Expand Down
58 changes: 24 additions & 34 deletions src/hissp/macros.lissp
Original file line number Diff line number Diff line change
Expand Up @@ -823,32 +823,25 @@ Hidden doctest adds bundled macros for REPL-consistent behavior.
;; >>> # hissp.._macro_.alias
;; ... # hissp.macros.._macro_.defmacro
;; ... # hissp.macros.._macro_.let
;; ... (lambda _Qz2D5FNHXZz_fn=(lambda _QzE4JATHEUz_first,*_QzE4JATHEUz_rest,**_QzE4JATHEUz_kwargs:(
;; ... (lambda _Qz2D5FNHXZz_fn=(lambda _QzE4JATHEUz_attr,*_QzE4JATHEUz_args,**_QzE4JATHEUz_kwargs:(
;; ... 'Aliases ``hissp.._macro_`` as ``HQzCOLON_#``.',
;; ... # hissp.macros.._macro_.ifQz_else
;; ... (lambda b,c,a:c()if b else a())(
;; ... _QzE4JATHEUz_rest,
;; ... (lambda X,Y:X or Y)(
;; ... _QzE4JATHEUz_args,
;; ... _QzE4JATHEUz_kwargs),
;; ... (lambda :
;; ... # hissp.macros.._macro_.let
;; ... (lambda _QzE4JATHEUz_rest=__import__('builtins').list(
;; ... _QzE4JATHEUz_rest):
;; ... __import__('builtins').getattr(
;; ... __import__('hissp')._macro_,
;; ... ('{}{}').format(
;; ... _QzE4JATHEUz_first,
;; ... # hissp.macros.._macro_.ifQz_else
;; ... (lambda b,c,a:c()if b else a())(
;; ... 'hissp.._macro_'.endswith(
;; ... '._macro_'),
;; ... (lambda :'QzHASH_'),
;; ... (lambda :('')))))(
;; ... _QzE4JATHEUz_rest.pop(),
;; ... *_QzE4JATHEUz_rest,
;; ... **_QzE4JATHEUz_kwargs))()),
;; ... __import__('builtins').getattr(
;; ... __import__('hissp')._macro_,
;; ... ('{}{}').format(
;; ... _QzE4JATHEUz_attr,
;; ... 'QzHASH_'))(
;; ... *_QzE4JATHEUz_args,
;; ... **_QzE4JATHEUz_kwargs)),
;; ... (lambda :
;; ... ('{}.{}').format(
;; ... 'hissp.._macro_',
;; ... _QzE4JATHEUz_first))))[-1]):(
;; ... _QzE4JATHEUz_attr))))[-1]):(
;; ... __import__('builtins').setattr(
;; ... _Qz2D5FNHXZz_fn,
;; ... '__doc__',
Expand Down Expand Up @@ -883,7 +876,7 @@ Hidden doctest adds bundled macros for REPL-consistent behavior.
;; >>> b'Fully-qualified b# macro at read time.'
;; b'Fully-qualified b# macro at read time.'
;;
;; #> H:##b"Read-time b# via alias."
;; #> =H:#b"Read-time b# via alias."
;; >>> b'Read-time b# via alias.'
;; b'Read-time b# via alias.'
;;
Expand All @@ -893,17 +886,14 @@ Hidden doctest adds bundled macros for REPL-consistent behavior.
;; See also: `prelude`, `attach`.
;;
`(defmacro ,(.format "{}{}" abbreviation '#)
($#first : :* $#rest :** $#kwargs)
($#attr : :* $#args :** $#kwargs)
',(.format "Aliases ``{}`` as ``{}#``." qualifier abbreviation)
(if-else $#rest
(let ($#rest (list $#rest))
((getattr ,qualifier (.format "{}{}"
$#first
(if-else (.endswith ',qualifier ','._macro_)
','#
"")))
(.pop $#rest) : :* $#rest :** $#kwargs))
(.format "{}.{}" ',qualifier $#first))))
(if-else (,'XY#.#"X or Y" $#args $#kwargs)
((getattr ,qualifier ,(if-else (.endswith qualifier '._macro_)
`(.format "{}{}" ,'$#attr ','#)
'$#attr))
: :* $#args :** $#kwargs)
(.format "{}.{}" ',qualifier $#attr))))

(alias i itertools.)
(alias op operator.)
Expand Down Expand Up @@ -963,8 +953,8 @@ Hidden doctest adds bundled macros for REPL-consistent behavior.

.. code-block:: REPL

#> @##str.swapcase
#..@##str.title
#> =@#str.swapcase
#..=@#str.title
#..(define spam 'spam) ; Unlike Python def, not always a function.
>>> # hissp.macros.._macro_.define
... __import__('builtins').globals().update(
Expand Down Expand Up @@ -2828,7 +2818,7 @@ Creates a lambda of arity {X} containing a `^*#<QzHAT_QzSTAR_QzHASH_>`

.. code-block:: REPL

#> (op#add 5 file=spy##sys..stdout(op#mul 7 3))
#> (op#add 5 file=spy#sys..stdout(op#mul 7 3))
>>> __import__('operator').add(
... (5),
... # hissp.._macro_._spy
Expand Down Expand Up @@ -2861,7 +2851,7 @@ Creates a lambda of arity {X} containing a `^*#<QzHAT_QzSTAR_QzHASH_>`

.. code-block:: REPL

#> file=time##sys..stdout(time..sleep .05)
#> file=time#sys..stdout(time..sleep .05)
>>> # hissp.macros.._macro_.let
... (lambda _QzPMWTVFTZz_time=__import__('time').time_ns:
... # hissp.macros.._macro_.letQz_from
Expand Down
11 changes: 5 additions & 6 deletions src/hissp/reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
|['`,]
|[.][#]
# Any atom that ends in ``#``, but not ``.#`` or ``\#``.
|(?:[^\\ \n"();#]|\\.)*(?:[^.\\ \n"();#]|\\.)[#]+
|(?:[^\\ \n"();#]|\\.)*(?:[^.\\ \n"();#]|\\.)[#]
)
|(?P<string>
[#]? # raw?
Expand Down Expand Up @@ -436,12 +436,11 @@ def _get_counter(self) -> int:

def _custom_macro(self, form, tag: str):
assert tag.endswith("#")
arity = tag.replace(R"\#", "").count("#")
arity = tag.replace(R"\=", "").count("=")
*keywords, label = re.findall(r"((?:[^=\\]|\\.)*[=#])", tag)
arity += bool(label[:-1])
assert arity > 0
*keywords, label = re.findall(r"((?:[^=\\]|\\.)*(?:=|#+))", tag)
if len(keywords) > arity:
raise SyntaxError(f"Not enough # for each = in {tag!r}")
label = label[:-arity]
label = label[:-1]
label = force_munge(self.escape(label))
label = re.sub(r"(^\.)", lambda m: force_qz_encode(m[1]), label)
fn: Fn[[str], Fn] = self._fully_qualified if ".." in label else self._local
Expand Down

0 comments on commit 9bf1162

Please sign in to comment.