From 6eb147ff66fb54800e1b37730dc3de51868d8ea5 Mon Sep 17 00:00:00 2001 From: Kaz Kylheku Date: Thu, 3 Aug 2023 06:36:48 -0700 Subject: opip: new special handling of (let ...). * stdlib/op.tl (sys:opip-single-let-p, sys:opip-let-p): New functions. (sys:opip-expand): Restructure from collect loop to car/cdr recursive form, because the new let operators in opip need access to the rest of the pipeline. Implement let operators. * tests/012/op.tl: New tests. * txr.1: Documented. --- stdlib/op.tl | 43 +++++++++++++++---- tests/012/op.tl | 6 +++ txr.1 | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 166 insertions(+), 10 deletions(-) diff --git a/stdlib/op.tl b/stdlib/op.tl index 31d0dc37..2a41fc5a 100644 --- a/stdlib/op.tl +++ b/stdlib/op.tl @@ -216,16 +216,41 @@ (defmacro aret (arg) ^(ap identity* ,arg)) +(defun sys:opip-single-let-p (c) + (tree-case c + ((op sym) + (and (eq op 'let) + (atom sym))) + (t nil))) + +(defun sys:opip-let-p (c) + (tree-case c + ((op (sym t) . rest) + (and (eq op 'let) + (atom sym) + (listp rest))) + (t nil))) + (defun sys:opip-expand (e clauses) - (collect-each ((c clauses)) - (if (atom c) - c - (let ((sym (car c))) - (if (member sym '(dwim uref qref op do lop ldo ap ip ado ido ret aret)) - c - (let ((opdo (if (or (special-operator-p (car c)) - (macro-form-p c e)) 'do 'op))) - ^(,opdo ,*c))))))) + (tree-case clauses + (nil nil) + ((c . rest) + (if (atom c) + (cons c (sys:opip-expand e rest)) + (let ((sym (car c))) + (cond + ((memq sym '(dwim uref qref op do lop ldo ap ip ado ido ret aret)) + (cons c (sys:opip-expand e rest))) + ((sys:opip-single-let-p c) + (tree-bind (t sym) c + (sys:opip-expand e ^((let (,sym @1)) ,*rest)))) + ((sys:opip-let-p c) + (tree-bind (t . vars) c + ^((do let* ,vars + [(opip ,*(sys:opip-expand e rest)) @1])))) + (t (let ((opdo (if (or (special-operator-p (car c)) + (macro-form-p c e)) 'do 'op))) + (cons ^(,opdo ,*c) (sys:opip-expand e rest)))))))))) (defmacro opip (:env e . clauses) ^[chain ,*(sys:opip-expand e clauses)]) diff --git a/tests/012/op.tl b/tests/012/op.tl index aafe0a28..5501cec4 100644 --- a/tests/012/op.tl +++ b/tests/012/op.tl @@ -99,3 +99,9 @@ (partition-if (op neq 1 (- @2 @1))) (find-max-key @1 : len)) 80) + +(mtest + (flow 1 (+ 1) (let x) (+ 2) (let y) (+ 3) (list x y @1)) (2 4 7) + (flow 10 (+ 1) (let (x @1) (y (* x 2))) (+ x y)) 44 + (flow 10 (+ 1) (let ((x @1) (y (* @1 2))) (+ x y))) 33 + (flow 10 (+ 1) (let ((x @1) (y (* @1 2))))) nil) diff --git a/txr.1 b/txr.1 index d5e365ca..5716c4a5 100644 --- a/txr.1 +++ b/txr.1 @@ -59065,6 +59065,19 @@ notation denotes the following transformation applied to each argument: .(method ...) -> .(method ...) atom -> atom + ;; forms headed by let are treated specially + + (let sym) -> ;; described below + + (let (s0 i0) + (s1 i1) + ....) -> ;; described below + + (let ((s0 i0) + (s1 i1)) -> ;; described below + body) + + ;; other compound forms are transformed like this: (function ...) -> (op function ...) @@ -59089,6 +59102,78 @@ or the respective dot notations, forms which invoke any of the .code do family of operators, as well as any atom forms. +When a +.code let +or +.code let* +expression occurs in +.code opip +syntax, it denotes a special syntax which is treated as follows. +.RS +.IP 1. +The simple form +.mono +.meti (let << sym ) +.onom +where +.meta sym +is a symbol is transformed into the +.mono +.meti (let >> ( sym @1)) +.onom +syntax, which is then handled via the following case (2). +.IP 2. +The form +.mono +.meti (let >> {( sym << init )}+) +.onom +specifies an implicit function which binds the specified variables. +The variables are bound sequentially as if by +.codn let* , +even though the operator is +.codn let . +Note also that the bindings are not enclosed in a list. An example +of the syntax is +.code "(let (x @1) (y (+ x @2)))" +which specifies a function of two arguments, inside of which +.code x +will be bound to the first argument, and +.code y +will be bound to the value of +.code x +plus the second argument. +The remaining elements of the +.code opip +are incorporated into the body of this function. The value of +the first argument, +.codn @1 , +is injected into the +.code opip +remaining opip chain, and that chain is processed in a scope +in which the variables bound by +.code let +are visible. +.IP 3. +All other +.code let +forms not matching the above syntax are treated like all special +operators. They become a +.code do +element of the +.code opip +pipeline. For instance +.code "(let ((x @1)) (+ x 1))" +denotes a one-argument function which binds +.code x +to its first argument, then produces the value +.code "(+ x 1)" +which is passed to the next stage of the +.code opip +chain. The remaining chain is not evaluated in the scope of the +.code x +variable. +.RE +.IP Note: the .code opip and @@ -59104,7 +59189,7 @@ which follows from a documented property of the .code chain function. -.TP* Example: +.TP* Examples: Take each element from the list .code "(1 2 3 4)" and multiply it by three, then add 1. @@ -59138,6 +59223,46 @@ respectively, whereas .code "[iff oddp list]" is passed through untransformed. +The following demonstrates the single variable +.codn let : + +.mono + (let ((pipe (opip (+ 1) (let x) + (+ 2) (let y) + (+ 3) + (list x y)))) + [pipe 1]) + -> (2 4 7) +.onom + +The +.code x +variable intercepts the value coming from +.code "(+ 1)" +and binds +.code x +to that value. When the +.code opip +function is invoked with the argument +.codn 1 , +that value is +.codn 2 . +That value also continues to the +.code "(+ 2)" +element which yields +.codn 4 , +which is similarly captured by variable +.codn y . +The final +.code list +expression lists the values of +.code x +and +.codn y , +as well as, implicitly, the value +.code @1 +coming from the previous element, + .coNP Macro @ flow .synb .mets (flow < form << opip-arg *) -- cgit v1.2.3