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

Compose macro #214

Merged
merged 11 commits into from
May 28, 2023
Merged
309 changes: 171 additions & 138 deletions src/hissp/macros.lissp
Original file line number Diff line number Diff line change
Expand Up @@ -2015,65 +2015,147 @@ except ModuleNotFoundError:pass"
Builds a function whose arguments are pushed to a stack, operates on
the stack according to the program, and finally pops its result.

Expressions are often terse enough to be used one-off inline.
The mini-language supports higher-order function manipulation
including composition, partial application, and point-free data flow.

The language is applied right-to-left, like function calls.
Magic characters are

``^`` DEPTH
Suffix to increase arity. Assume depth 1 otherwise.
``,`` -data
Suffix interprets callable as data.
``%`` -kwargs
Suffix interprets top element as ``**kwargs``.
``^`` -depth
Suffix increases arity. Assume depth 1 otherwise. Can be repeated.
Write after other suffixes.
``/`` DROP
Pops (at depth) and discards.
``&`` PICK
Copies (at depth) and pushes.
``@`` ROLL (default depth 2)
Pops (at depth) and pushes.
``]`` MARK (no depth)
Pushes a sentinel gensym for PACK.
Callables are data when there's a mark.
``[`` PACK (no depth)
Pops to MARK and pushes as tuple.
``]`` MARK (default depth 0)
Inserts a sentinel gensym for PACK (at depth).
``[`` PACK
Pops to the first sentinel and pushes as tuple.
With depth, looks tuple up on the next element.
``*`` SPLAT
Pops (at depth) and pushes its elements.
Pops (at depth) and pushes elements.
``:`` NOP (no depth)
No effect. Used as a separator when no other magic applies.

They can be escaped with a backtick (:literal:`\``).

Other elements are either callables or data, and read as Lissp.
Data elements just put themselves on the stack.
Callables pop args to their depth and push their result.
Data elements just push themselves on the stack (default depth 0).

.. code-block:: REPL

#> (define average ^#truediv^sum@len&)
#> (^#:2)
>>> (lambda *_QzNo73_args:
... # hissp.macros.._macro_.let
... (lambda _QzNo73_stack=__import__('builtins').list(
... _QzNo73_args):(
... _QzNo73_stack.reverse(),
... _QzNo73_stack.append(
... (2)),
... (),
... _QzNo73_stack.pop())[-1])())()
2

Callables (default depth 1) pop args to their depth and push their
result. Combine with a datum for partial application.

.. code-block:: REPL

#> (define decrement ^#sub^@1)
>>> # define
... __import__('builtins').globals().update(
... average=(lambda *_QzNo73_args:
... # hissp.macros.._macro_.let
... (lambda _QzNo73_stack=__import__('builtins').list(
... _QzNo73_args):(
... _QzNo73_stack.reverse(),
... _QzNo73_stack.append(
... (lambda X,Y:X[-1-Y])(
... _QzNo73_stack,
... (0))),
... _QzNo73_stack.append(
... len(
... _QzNo73_stack.pop())),
... _QzNo73_stack.append(
... _QzNo73_stack.pop(
... (-2))),
... _QzNo73_stack.append(
... sum(
... _QzNo73_stack.pop())),
... _QzNo73_stack.append(
... truediv(
... _QzNo73_stack.pop(),
... _QzNo73_stack.pop())),
... _QzNo73_stack.pop())[-1])()))
... decrement=(lambda *_QzNo73_args:
... # hissp.macros.._macro_.let
... (lambda _QzNo73_stack=__import__('builtins').list(
... _QzNo73_args):(
... _QzNo73_stack.reverse(),
... _QzNo73_stack.append(
... (1)),
... _QzNo73_stack.append(
... _QzNo73_stack.pop(
... (-2))),
... _QzNo73_stack.append(
... sub(
... _QzNo73_stack.pop(
... (-1)),
... _QzNo73_stack.pop(
... (-1)))),
... _QzNo73_stack.pop())[-1])()))

#> (decrement 5)
>>> decrement(
... (5))
4

Increasing the depth of data to 1 implies a lookup on the next
element. Methods always need a self, so they can be converted to
attribute lookups at the default depth of 1. Combine them to drill
into complex data structures.

.. code-block:: REPL

#> (^#.__class__.__name__:'spam^ (dict : spam 'eggs))
>>> (lambda *_QzNo73_args:
... # hissp.macros.._macro_.let
... (lambda _QzNo73_stack=__import__('builtins').list(
... _QzNo73_args):(
... _QzNo73_stack.reverse(),
... _QzNo73_stack.append(
... __import__('operator').getitem(
... _QzNo73_stack.pop(),
... 'spam')),
... (),
... _QzNo73_stack.append(
... __import__('operator').attrgetter(
... '__class__.__name__')(
... _QzNo73_stack.pop())),
... _QzNo73_stack.pop())[-1])())(
... dict(
... spam='eggs'))
'str'

The callable or data type is determined at read time. Literals are
always data. but an element that reads as `tuple` or `str` type may be
ambiguous, in which case they are presumed callable, unless it ends
with a ``,``.

.. code-block:: REPL

#> (define prod ^#reduce^mul,)
>>> # define
... __import__('builtins').globals().update(
... prod=(lambda *_QzNo73_args:
... # hissp.macros.._macro_.let
... (lambda _QzNo73_stack=__import__('builtins').list(
... _QzNo73_args):(
... _QzNo73_stack.reverse(),
... _QzNo73_stack.append(
... mul),
... _QzNo73_stack.append(
... reduce(
... _QzNo73_stack.pop(
... (-1)),
... _QzNo73_stack.pop(
... (-1)))),
... _QzNo73_stack.pop())[-1])()))

#> (en#prod 1 2 3)
>>> (lambda *_QzNo60_xs:
... prod(
... _QzNo60_xs))(
... (1),
... (2),
... (3))
6

#> (define geomean ^#pow^reduce^*[mul]@truediv^1:len&)
#> (define geomean ^#pow^prod@truediv^1:len&)
>>> # define
... __import__('builtins').globals().update(
... geomean=(lambda *_QzNo73_args:
Expand All @@ -2087,39 +2169,30 @@ except ModuleNotFoundError:pass"
... (0))),
... _QzNo73_stack.append(
... len(
... _QzNo73_stack.pop())),
... _QzNo73_stack.pop(
... (-1)))),
... (),
... _QzNo73_stack.append(
... (1)),
... _QzNo73_stack.append(
... truediv(
... _QzNo73_stack.pop(),
... _QzNo73_stack.pop())),
... _QzNo73_stack.pop(
... (-1)),
... _QzNo73_stack.pop(
... (-1)))),
... _QzNo73_stack.append(
... _QzNo73_stack.pop(
... (-2))),
... _QzNo73_stack.append(
... '_QzNo73_QzRSQB_'),
... _QzNo73_stack.append(
... mul),
... _QzNo73_stack.append(
... __import__('builtins').tuple(
... __import__('builtins').iter(
... _QzNo73_stack.pop,
... '_QzNo73_QzRSQB_'))),
... _QzNo73_stack.extend(
... __import__('builtins').reversed(
... __import__('builtins').tuple(
... _QzNo73_stack.pop(
... (-1))))),
... _QzNo73_stack.append(
... reduce(
... _QzNo73_stack.pop(),
... _QzNo73_stack.pop())),
... prod(
... _QzNo73_stack.pop(
... (-1)))),
... _QzNo73_stack.append(
... pow(
... _QzNo73_stack.pop(),
... _QzNo73_stack.pop())),
... _QzNo73_stack.pop(
... (-1)),
... _QzNo73_stack.pop(
... (-1)))),
... _QzNo73_stack.pop())[-1])()))

#> (geomean '(1 10))
Expand All @@ -2128,98 +2201,58 @@ except ModuleNotFoundError:pass"
... (10),))
3.1622776601683795

#> (average '(.1 10))
>>> average(
... ((0.1),
... (10),))
5.05

#> (geomean '(.1 10))
>>> geomean(
... ((0.1),
... (10),))
1.0

#> (en#average 4 5 6)
>>> (lambda *_QzNo60_xs:
... average(
... _QzNo60_xs))(
... (4),
... (5),
... (6))
5.0

#> (define decrement ^#sub^@1)
>>> # define
... __import__('builtins').globals().update(
... decrement=(lambda *_QzNo73_args:
... # hissp.macros.._macro_.let
... (lambda _QzNo73_stack=__import__('builtins').list(
... _QzNo73_args):(
... _QzNo73_stack.reverse(),
... _QzNo73_stack.append(
... (1)),
... _QzNo73_stack.append(
... _QzNo73_stack.pop(
... (-2))),
... _QzNo73_stack.append(
... sub(
... _QzNo73_stack.pop(),
... _QzNo73_stack.pop())),
... _QzNo73_stack.pop())[-1])()))

#> (^#decrement:decrement 5)
>>> (lambda *_QzNo73_args:
... # hissp.macros.._macro_.let
... (lambda _QzNo73_stack=__import__('builtins').list(
... _QzNo73_args):(
... _QzNo73_stack.reverse(),
... _QzNo73_stack.append(
... decrement(
... _QzNo73_stack.pop())),
... (),
... _QzNo73_stack.append(
... decrement(
... _QzNo73_stack.pop())),
... _QzNo73_stack.pop())[-1])())(
... (5))
3

"
(let (reader (hissp..reader.Lissp : ns (.get hissp.compiler..NS))
marks (list))
literal? X#(not (op#contains (# tuple str) (type X)))
control-word? X#(&& (op#is_ (type X) str) (.startswith X ":"))
module-handle? X#(&& (op#is_ (type X) str) (.endswith X "."))
quotation? X#(&& (op#is_ (type X) tuple) (op#eq 'quote (get#0 X)))
method? X#(&& (op#is_ (type X) str) (.startswith X "."))
kwargs? X#(.startswith X "%")
depth X#(.count X "^"))
`(lambda (: :* $#args)
(let ($#stack (list $#args))
(.reverse $#stack)
,@(i#starmap
XY#(case X (let (obj (next (.reads reader (.replace X "`" ""))))
`(.append ,'$#stack
,(if-else (|| marks
(not (op#contains (@ tuple str)
(type obj)))
,(if-else (|| (literal? obj)
(.startswith Y ",")
(hissp.reader..is_lissp_string obj)
(&& (op#is_ str (type obj))
(|| (.startswith obj ":")
(.endswith obj ".")))
(&& (op#is_ tuple (type obj))
(op#eq 'quote (get#0 obj))))
obj
`(,obj ,@(XY#.#"X*(Y+1)" `((.pop ,'$#stack))
(len Y))))))
.#"/" `(.pop ,'$#stack ,(op#sub -1 (len Y)))
.#"&" `(.append ,'$#stack (,'XY#.#"X[-1-Y]" ,'$#stack ,(len Y)))
.#"@" `(.append ,'$#stack (.pop ,'$#stack ,(op#sub -2 (len Y))))
.#"[" (progn (.pop marks)
`(.append ,'$#stack
(tuple (iter ,'$#stack.pop ','$#\]))))
.#"]" (progn (.append marks "]")
`(.append ,'$#stack ','$#\]))
(control-word? obj)
(module-handle? obj)
(quotation? obj))
(if-else (depth Y)
`(op#getitem (.pop ,'$#stack) ,obj)
obj)
(if-else (|| (depth Y) (not (method? obj)))
`(,obj ,@(XYZW#.#"(X+1-Y-Z)*W"
(depth Y)
(method? obj)
(kwargs? Y)
`((.pop ,'$#stack
,(op#sub -1 (kwargs? Y)))))
: ,@(when (kwargs? Y)
`(:** (dict (.pop ,'$#stack)))))
`((op#attrgetter ',.#"obj[1:]")
(.pop ,'$#stack))))))
.#"/" `(.pop ,'$#stack ,(op#sub -1 (depth Y)))
.#"&" `(.append ,'$#stack (,'XY#.#"X[-1-Y]" ,'$#stack ,(depth Y)))
.#"@" `(.append ,'$#stack (.pop ,'$#stack ,(op#sub -2 (depth Y))))
.#"[" `(.append ,'$#stack
(-<>> (tuple (iter ,'$#stack.pop ','$#\]))
,@(when Y `(op#itemgetter
(:<> (,'$#stack.pop))))))
.#"]" `(.insert ,'$#stack
(op#sub (len ,'$#stack) ,(depth Y))
','$#\])
.#"*" `(.extend ,'$#stack
(reversed (tuple (.pop ,'$#stack
,(op#sub -1 (len Y))))))
,(op#sub -1 (depth Y))))))
: ())
(reversed (re..findall "([/&@*:[\]]|(?:[^^`/&@*:[\]]|`[/&@*:[\]])+)(\^*)"
(hissp..demunge s))))
(reversed (re..findall
"([/&@[\]*:]|(?:[^,%^`/&@[\]*:]|`[,%^/&@[\]*:])+)(%?,?\^*)"
(hissp..demunge s))))
(.pop $#stack)))))

(defmacro _spy (expr file)
Expand Down
Loading