Skip to content

Commit

Permalink
Merge pull request #214 from gilch/compose-macro
Browse files Browse the repository at this point in the history
Compose macro
  • Loading branch information
gilch committed May 28, 2023
2 parents b733a43 + 3ce5f78 commit 92b210b
Showing 1 changed file with 171 additions and 138 deletions.
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

0 comments on commit 92b210b

Please sign in to comment.