From ca5d274696152841eac3c4fe82032dddd5899a0b Mon Sep 17 00:00:00 2001 From: gilch Date: Mon, 22 May 2023 21:07:03 -0600 Subject: [PATCH 1/6] Composition macro first draft --- src/hissp/macros.lissp | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/hissp/macros.lissp b/src/hissp/macros.lissp index 41a1d4bf..487f3c02 100644 --- a/src/hissp/macros.lissp +++ b/src/hissp/macros.lissp @@ -1983,6 +1983,49 @@ except ModuleNotFoundError:pass" ,key -1))))) +(defmacro ^\# s + "``^#`` 'composition' concatenative mini-language functions + + Builds a function whose arguments are pushed to a stack, operates on + the stack according to the program, and finally pops its result. + + The language is applied right-to-left, like function calls. + Magic chars are + ``^`` depth increaser suffix. Assume depth 1 otherwise. + ``/`` pop + ``&`` pick + ``@`` roll (default depth 2) + ``*`` splat + ``:`` nop (no depth) + They can be escaped with a backtick. + + All other words use the following program: Read as Lissp, if it + resolves to a callable, call it with args from the stack (up to depth) + and append the result. + " + (let (reader (hissp..reader.Lissp : ns (.get hissp.compiler..NS))) + `(lambda (: :* $#args) + (let ($#stack (list $#args)) + (.reverse $#stack) + ,@(i#starmap + XY#(case X (let (obj (next (.reads reader (.replace X "`" "")))) + `(.append ,'$#stack + (let ($#G ,obj) + (if-else (callable $#G) + ($#G ,@(XY#.#"X*(Y+1)" `((.pop ,'$#stack)) + (len Y))) + $#G)))) + .#"/" `(.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)))) + .#"*" `(.extend ,'$#stack + (reversed (tuple (.pop ,'$#stack + ,(op#sub -1 (len Y)))))) + : ()) + (reversed (re..findall "([/&@*:]|(?:[^^`/&@*:]|`[/&@*:])+)(\^*)" + (hissp..demunge s)))) + (.pop $#stack))))) + (defmacro _spy (expr file) `(let ($#e ,expr) (print (pprint..pformat ',expr : sort_dicts 0) From 3bcd3f36e05ecce2dc923c9454f59d2a700ae9eb Mon Sep 17 00:00:00 2001 From: gilch Date: Tue, 23 May 2023 19:32:20 -0600 Subject: [PATCH 2/6] Add mark/pack magic --- src/hissp/macros.lissp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hissp/macros.lissp b/src/hissp/macros.lissp index 487f3c02..9f254920 100644 --- a/src/hissp/macros.lissp +++ b/src/hissp/macros.lissp @@ -1995,6 +1995,8 @@ except ModuleNotFoundError:pass" ``/`` pop ``&`` pick ``@`` roll (default depth 2) + ``]`` mark (no depth) + ``[`` pack (no depth) ``*`` splat ``:`` nop (no depth) They can be escaped with a backtick. @@ -2018,11 +2020,13 @@ except ModuleNotFoundError:pass" .#"/" `(.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)))) + .#"[" `(.append ,'$#stack (tuple (iter ,'$#stack.pop ','$#\]))) + .#"]" `(.append ,'$#stack ','$#\]) .#"*" `(.extend ,'$#stack (reversed (tuple (.pop ,'$#stack ,(op#sub -1 (len Y)))))) : ()) - (reversed (re..findall "([/&@*:]|(?:[^^`/&@*:]|`[/&@*:])+)(\^*)" + (reversed (re..findall "([/&@*:[\]]|(?:[^^`/&@*:[\]]|`[/&@*:[\]])+)(\^*)" (hissp..demunge s)))) (.pop $#stack))))) From 64872c16d2de093e1862109d9ea8a6dccba79984 Mon Sep 17 00:00:00 2001 From: gilch Date: Tue, 23 May 2023 20:28:12 -0600 Subject: [PATCH 3/6] Determine callability at read time Now need a way to quote callables. --- src/hissp/macros.lissp | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/hissp/macros.lissp b/src/hissp/macros.lissp index 9f254920..91668a7d 100644 --- a/src/hissp/macros.lissp +++ b/src/hissp/macros.lissp @@ -2012,11 +2012,17 @@ except ModuleNotFoundError:pass" ,@(i#starmap XY#(case X (let (obj (next (.reads reader (.replace X "`" "")))) `(.append ,'$#stack - (let ($#G ,obj) - (if-else (callable $#G) - ($#G ,@(XY#.#"X*(Y+1)" `((.pop ,'$#stack)) - (len Y))) - $#G)))) + ,(if-else (|| (not (op#contains (@ tuple str) + (type obj))) + (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)))) From e97e62bc3a813eb73e51ed86bf7cc3f09ebd8eee Mon Sep 17 00:00:00 2001 From: gilch Date: Tue, 23 May 2023 20:44:00 -0600 Subject: [PATCH 4/6] Treat all []-quoted callables as data --- src/hissp/macros.lissp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/hissp/macros.lissp b/src/hissp/macros.lissp index 91668a7d..d76043d9 100644 --- a/src/hissp/macros.lissp +++ b/src/hissp/macros.lissp @@ -2005,14 +2005,16 @@ except ModuleNotFoundError:pass" resolves to a callable, call it with args from the stack (up to depth) and append the result. " - (let (reader (hissp..reader.Lissp : ns (.get hissp.compiler..NS))) + (let (reader (hissp..reader.Lissp : ns (.get hissp.compiler..NS)) + marks (list)) `(lambda (: :* $#args) (let ($#stack (list $#args)) (.reverse $#stack) ,@(i#starmap XY#(case X (let (obj (next (.reads reader (.replace X "`" "")))) `(.append ,'$#stack - ,(if-else (|| (not (op#contains (@ tuple str) + ,(if-else (|| marks + (not (op#contains (@ tuple str) (type obj))) (hissp.reader..is_lissp_string obj) (&& (op#is_ str (type obj)) @@ -2026,8 +2028,11 @@ except ModuleNotFoundError:pass" .#"/" `(.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)))) - .#"[" `(.append ,'$#stack (tuple (iter ,'$#stack.pop ','$#\]))) - .#"]" `(.append ,'$#stack ','$#\]) + .#"[" (progn (.pop marks) + `(.append ,'$#stack + (tuple (iter ,'$#stack.pop ','$#\])))) + .#"]" (progn (.append marks "]") + `(.append ,'$#stack ','$#\])) .#"*" `(.extend ,'$#stack (reversed (tuple (.pop ,'$#stack ,(op#sub -1 (len Y)))))) From f7435ae9ad3af5ee5ab31c2b949c9ca977216fec Mon Sep 17 00:00:00 2001 From: gilch Date: Tue, 23 May 2023 22:16:32 -0600 Subject: [PATCH 5/6] Add prelude example --- src/hissp/macros.lissp | 194 +++++++++++++++++++++++------------------ 1 file changed, 110 insertions(+), 84 deletions(-) diff --git a/src/hissp/macros.lissp b/src/hissp/macros.lissp index d76043d9..634a43c5 100644 --- a/src/hissp/macros.lissp +++ b/src/hissp/macros.lissp @@ -1811,90 +1811,116 @@ Hidden doctest adds bundled macros for REPL-consistent behavior. ;;;; Import (defmacro prelude (: ns `(globals)) - "Hissp's bundled micro prelude. - - Brings Hissp up to a minimal standard of usability without adding any - dependencies in the compiled output. - - Mainly intended for single-file scripts that can't have dependencies, - or similarly constrained environments (e.g. embedded, readerless). - There, the first form should be ``(hissp.._macro_.prelude)``, - which is also implied in ``$ lissp -c`` commands. - - Larger projects with access to functional and macro libraries need not - use this prelude at all. - - The prelude has several effects: - - * Imports `functools.partial` and `functools.reduce`. - Star imports from `itertools` and `operator`:: - - from functools import partial,reduce - from itertools import *;from operator import * - - .. _engarde: - - * Defines ``engarde``, which calls a function with exception handler:: - - def engarde(xs,h,f,/,*a,**kw): - try:return f(*a,**kw) - except xs as e:return h(e) - - ``engarde`` with handlers can stack above in a single form. - - See `lissp_whirlwind_tour` (§15) for usage examples. - - .. _enter: - - * Defines ``enter``, which calls a function with context manager:: - - def enter(c,f,/,*a): - with c as C:return f(*a,C) - - ``enter`` with context managers can stack above in a single form. - - See `lissp_whirlwind_tour` (§17) for usage examples. - - .. _Ensue: - - * Defines the ``Ensue`` class; trampolined continuation generators:: - - class Ensue(__import__('collections.abc').abc.Generator): - send=lambda s,v:s.g.send(v);throw=lambda s,*x:s.g.throw(*x);F=0;X=();Y=[] - def __init__(s,p):s.p,s.g,s.n=p,s._(s),s.Y - def _(s,k,v=None): - while isinstance(s:=k,__class__) and not setattr(s,'sent',v): - try:k,y=s.p(s),s.Y;v=(yield from y)if s.F or y is s.n else(yield y) - except s.X as e:v=e - return k - - ``Ensue`` takes a step function and returns a generator. The step - function recieves the previous Ensue step and must return the next - one to continue. Returning a different type raises a `StopIteration` - with that object. Set the ``Y`` attribute on the current step to - [Y]ield a value this step. Set the ``F`` attribute to a true value - to yield values [F]rom the ``Y`` iterable instead. Set the ``X`` - attribute to an e[X]ception class or tuple to catch any targeted - exceptions on the next step. Each step keeps a ``sent`` attribute, - which is the value sent to the generator this step, or the exception - caught this step instead. - - See `lissp_whirlwind_tour` (§§16–17) for usage examples. - - See also: `types.coroutine`, `collections.abc.Generator`. - - * Adds the bundled macros, but only if available - (macros are typically only used at compile time), - so its compiled expansion does not require Hissp to be installed. - (This replaces ``_macro_`` if you already had one.):: - - _macro_=__import__('types').SimpleNamespace() - try:exec('from {}._macro_ import *',vars(_macro_)) - except ModuleNotFoundError:pass - - The REPL has the bundled macros loaded by default, but not the prelude. - Invoke ``(prelude)`` to get the rest. - " + <<# + ;; Hissp's bundled micro prelude. + ;; + ;; Brings Hissp up to a minimal standard of usability without adding any + ;; dependencies in the compiled output. + ;; + ;; Mainly intended for single-file scripts that can't have dependencies, + ;; or similarly constrained environments (e.g. embedded, readerless). + ;; There, the first form should be ``(hissp.._macro_.prelude)``, + ;; which is also implied in ``$ lissp -c`` commands. + ;; + ;; Larger projects with access to functional and macro libraries need not + ;; use this prelude at all. + ;; + ;; The prelude has several effects: + ;; + ;; * Imports `functools.partial` and `functools.reduce`. + ;; Star imports from `itertools` and `operator`:: + ;; + ;; from functools import partial,reduce + ;; from itertools import *;from operator import * + ;; + ;; .. _engarde: + ;; + ;; * Defines ``engarde``, which calls a function with exception handler:: + ;; + ;; def engarde(xs,h,f,/,*a,**kw): + ;; try:return f(*a,**kw) + ;; except xs as e:return h(e) + ;; + ;; ``engarde`` with handlers can stack above in a single form. + ;; + ;; See `lissp_whirlwind_tour` (§15) for usage examples. + ;; + ;; .. _enter: + ;; + ;; * Defines ``enter``, which calls a function with context manager:: + ;; + ;; def enter(c,f,/,*a): + ;; with c as C:return f(*a,C) + ;; + ;; ``enter`` with context managers can stack above in a single form. + ;; + ;; See `lissp_whirlwind_tour` (§17) for usage examples. + ;; + ;; .. _Ensue: + ;; + ;; * Defines the ``Ensue`` class; trampolined continuation generators:: + ;; + ;; class Ensue(__import__('collections.abc').abc.Generator): + ;; send=lambda s,v:s.g.send(v);throw=lambda s,*x:s.g.throw(*x);F=0;X=();Y=[] + ;; def __init__(s,p):s.p,s.g,s.n=p,s._(s),s.Y + ;; def _(s,k,v=None): + ;; while isinstance(s:=k,__class__) and not setattr(s,'sent',v): + ;; try:k,y=s.p(s),s.Y;v=(yield from y)if s.F or y is s.n else(yield y) + ;; except s.X as e:v=e + ;; return k + ;; + ;; ``Ensue`` takes a step function and returns a generator. The step + ;; function recieves the previous Ensue step and must return the next + ;; one to continue. Returning a different type raises a `StopIteration` + ;; with that object. Set the ``Y`` attribute on the current step to + ;; [Y]ield a value this step. Set the ``F`` attribute to a true value + ;; to yield values [F]rom the ``Y`` iterable instead. Set the ``X`` + ;; attribute to an e[X]ception class or tuple to catch any targeted + ;; exceptions on the next step. Each step keeps a ``sent`` attribute, + ;; which is the value sent to the generator this step, or the exception + ;; caught this step instead. + ;; + ;; See `lissp_whirlwind_tour` (§§16–17) for usage examples. + ;; + ;; See also: `types.coroutine`, `collections.abc.Generator`. + ;; + ;; * Adds the bundled macros, but only if available + ;; (macros are typically only used at compile time), + ;; so its compiled expansion does not require Hissp to be installed. + ;; (This replaces ``_macro_`` if you already had one.):: + ;; + ;; _macro_=__import__('types').SimpleNamespace() + ;; try:exec('from {}._macro_ import *',vars(_macro_)) + ;; except ModuleNotFoundError:pass + ;; + ;; The REPL has the bundled macros loaded by default, but not the prelude. + ;; Invoke ``(prelude)`` to get the rest. + ;; + ;; .. code-block:: REPL + ;; + ;; #> (prelude) + ;; >>> # prelude + ;; ... __import__('builtins').exec( + ;; ... ('from functools import partial,reduce\n' + ;; ... 'from itertools import *;from operator import *\n' + ;; ... 'def engarde(xs,h,f,/,*a,**kw):\n' + ;; ... ' try:return f(*a,**kw)\n' + ;; ... ' except xs as e:return h(e)\n' + ;; ... 'def enter(c,f,/,*a):\n' + ;; ... ' with c as C:return f(*a,C)\n' + ;; ... "class Ensue(__import__('collections.abc').abc.Generator):\n" + ;; ... ' send=lambda s,v:s.g.send(v);throw=lambda s,*x:s.g.throw(*x);F=0;X=();Y=[]\n' + ;; ... ' def __init__(s,p):s.p,s.g,s.n=p,s._(s),s.Y\n' + ;; ... ' def _(s,k,v=None):\n' + ;; ... " while isinstance(s:=k,__class__) and not setattr(s,'sent',v):\n" + ;; ... ' try:k,y=s.p(s),s.Y;v=(yield from y)if s.F or y is s.n else(yield y)\n' + ;; ... ' except s.X as e:v=e\n' + ;; ... ' return k\n' + ;; ... "_macro_=__import__('types').SimpleNamespace()\n" + ;; ... "try:exec('from hissp.macros._macro_ import *',vars(_macro_))\n" + ;; ... 'except ModuleNotFoundError:pass'), + ;; ... __import__('builtins').globals()) + ;; `(exec ',(.format #"\ from functools import partial,reduce from itertools import *;from operator import * From 79150b1fcd5eb00fcd9847c394a942a18e17649f Mon Sep 17 00:00:00 2001 From: gilch Date: Tue, 23 May 2023 22:10:57 -0600 Subject: [PATCH 6/6] Fill in ^# docstring --- src/hissp/macros.lissp | 183 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 169 insertions(+), 14 deletions(-) diff --git a/src/hissp/macros.lissp b/src/hissp/macros.lissp index 634a43c5..41d70378 100644 --- a/src/hissp/macros.lissp +++ b/src/hissp/macros.lissp @@ -2015,21 +2015,176 @@ 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 language is applied right-to-left, like function calls. - Magic chars are - ``^`` depth increaser suffix. Assume depth 1 otherwise. - ``/`` pop - ``&`` pick - ``@`` roll (default depth 2) - ``]`` mark (no depth) - ``[`` pack (no depth) - ``*`` splat - ``:`` nop (no depth) - They can be escaped with a backtick. - - All other words use the following program: Read as Lissp, if it - resolves to a callable, call it with args from the stack (up to depth) - and append the result. + Magic characters are + + ``^`` DEPTH + Suffix to increase arity. Assume depth 1 otherwise. + ``/`` 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. + ``*`` SPLAT + Pops (at depth) and pushes its 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. + + .. code-block:: REPL + + #> (define average ^#truediv^sum@len&) + >>> # 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])())) + + #> (define geomean ^#pow^reduce^*[mul]@truediv^1:len&) + >>> # define + ... __import__('builtins').globals().update( + ... geomean=(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( + ... (1)), + ... _QzNo73_stack.append( + ... truediv( + ... _QzNo73_stack.pop(), + ... _QzNo73_stack.pop())), + ... _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())), + ... _QzNo73_stack.append( + ... pow( + ... _QzNo73_stack.pop(), + ... _QzNo73_stack.pop())), + ... _QzNo73_stack.pop())[-1])())) + + #> (geomean '(1 10)) + >>> geomean( + ... ((1), + ... (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))