|
| 1 | +;;;; Local definitions (like Racket) |
| 2 | +;;; |
| 3 | +;;; Like Racket (rather than Scheme) bindings can occur anywhere in a |
| 4 | +;;; BINDING form and an appropriately nested structure results. |
| 5 | +;;; |
| 6 | +;;; Bindings are only considered in the immediate children of BINDING |
| 7 | +;;; to avoid needing a code-walker. |
| 8 | +;;; |
| 9 | +;;; Within BINDING |
| 10 | +;;; - (bind var val) binds a variable; |
| 11 | +;;; - (bind (f ...) ...) binds a function (punning syntax like Scheme); |
| 12 | +;;; - (bind/values (...) form ...) binds multiple values -- if there |
| 13 | +;;; is a single form it should return as many values as there are |
| 14 | +;;; variables, otherwise there should be as many forms as values; |
| 15 | +;;; - (bind/destructuring dsll form) binds with destructuring; |
| 16 | +;;; |
| 17 | +;;; Successive bindings of the same kind (for BIND, not the other two) |
| 18 | +;;; are coalesced. |
| 19 | +;;; |
| 20 | + |
| 21 | +;;; Try to make this work as a module |
| 22 | +;;; |
| 23 | +(eval-when (:compile-toplevel :load-toplevel :execute) |
| 24 | + (unless (and (member "ORG.TFEB.HAX.COLLECTING" *modules* :test #'string=) |
| 25 | + (member "ORG.TFEB.HAX.ITERATE" *modules* :test #'string=)) |
| 26 | + #+org.tfeb.tools.require-module |
| 27 | + (org.tfeb.tools.require-module:require-modules |
| 28 | + '(:org.tfeb.hax.collecting |
| 29 | + :org.tfeb.hax.iterate)) |
| 30 | + #-org.tfeb.tools.require-module |
| 31 | + (error "doomed"))) |
| 32 | + |
| 33 | +(defpackage :org.tfeb.hax.binding |
| 34 | + (:use :cl |
| 35 | + :org.tfeb.hax.collecting |
| 36 | + :org.tfeb.hax.iterate) |
| 37 | + (:export |
| 38 | + #:binding |
| 39 | + #:bind |
| 40 | + #:bind/values |
| 41 | + #:bind/destructuring)) |
| 42 | + |
| 43 | +(in-package :org.tfeb.hax.binding) |
| 44 | + |
| 45 | +(provide :org.tfeb.hax.binding) |
| 46 | + |
| 47 | +;;; At top-level all these should be errors |
| 48 | +;;; |
| 49 | + |
| 50 | +(defmacro bind (name &body forms) |
| 51 | + (declare (ignore forms)) |
| 52 | + (error "Trying to bind ~S outside binding" name)) |
| 53 | + |
| 54 | +(defmacro bind/values (vars &body forms) |
| 55 | + (declare (ignore forms)) |
| 56 | + (error "Trying to bind/values ~S outside binding" vars)) |
| 57 | + |
| 58 | +(defmacro bind/destructuring (dsll form) |
| 59 | + (declare (ignore form)) |
| 60 | + (error "Tryng to bind/destructuring ~A outside binding" dsll)) |
| 61 | + |
| 62 | +(defun parse-binding-form (form) |
| 63 | + ;; Return what sort of binding form this is, or NIL, and the |
| 64 | + ;; corresponding binding, or NIL |
| 65 | + (if (consp form) |
| 66 | + (case (first form) |
| 67 | + ((bind) |
| 68 | + (unless (>= (length form) 2) |
| 69 | + (error "hopless bind form ~S" form)) |
| 70 | + (typecase (first (rest form)) |
| 71 | + (symbol |
| 72 | + (unless (<= (length form) 3) |
| 73 | + (error "too many expressions in ~S" form)) |
| 74 | + (values 'variable (rest form))) |
| 75 | + (cons |
| 76 | + (values 'function `(,(first (first (rest form))) |
| 77 | + ,(rest (first (rest form))) |
| 78 | + ,@(rest (rest form))))) |
| 79 | + (t |
| 80 | + (error "mutant bind form ~S" form)))) |
| 81 | + ((bind/values) |
| 82 | + (unless (>= (length form) 2) |
| 83 | + (error "hopeless bind/values form ~S" form)) |
| 84 | + (let ((vars (first (rest form))) |
| 85 | + (forms (rest (rest form)))) |
| 86 | + (unless (and (listp vars) |
| 87 | + (every #'symbolp vars)) |
| 88 | + (error "not all variables are in ~S" form)) |
| 89 | + (values 'values |
| 90 | + (if (= (length form) 3) |
| 91 | + (rest form) |
| 92 | + `(,vars (values ,@forms)))))) |
| 93 | + ((bind/destructuring) |
| 94 | + (unless (= (length form) 3) |
| 95 | + (error "hopeless bind/destructing form ~S" form)) |
| 96 | + (let ((dsll (first (rest form)))) |
| 97 | + (unless (listp dsll) |
| 98 | + (error "destructuring lambda list isn't in ~S" form)) |
| 99 | + (values 'destructuring (rest form)))) |
| 100 | + (otherwise |
| 101 | + (values nil nil))) |
| 102 | + (values nil nil))) |
| 103 | + |
| 104 | +(defun walk-binding-body (body) |
| 105 | + ;; Walk the body of a BINDING form. This is just unavoidably hairy. |
| 106 | + (collecting |
| 107 | + (iterate wbb ((tail body) |
| 108 | + (variable-bindings '()) |
| 109 | + (function-bindings '())) |
| 110 | + (cond |
| 111 | + (tail |
| 112 | + (destructuring-bind (this . rest) tail |
| 113 | + (multiple-value-bind (what binding) (parse-binding-form this) |
| 114 | + (ecase what |
| 115 | + ((variable) |
| 116 | + (cond |
| 117 | + (variable-bindings ;been collecting vars |
| 118 | + (wbb rest (cons binding variable-bindings) |
| 119 | + function-bindings)) |
| 120 | + (function-bindings ;been collecting fns |
| 121 | + (collect `(labels ,(reverse function-bindings) |
| 122 | + ,@(walk-binding-body tail)))) |
| 123 | + (t ;not collecting |
| 124 | + (wbb rest (cons binding variable-bindings) '())))) |
| 125 | + ((function) |
| 126 | + (cond |
| 127 | + (function-bindings |
| 128 | + (wbb rest '() (cons binding function-bindings))) |
| 129 | + (variable-bindings |
| 130 | + (collect `(let* ,(reverse variable-bindings) |
| 131 | + ,@(walk-binding-body tail)))) |
| 132 | + (t |
| 133 | + (wbb rest '() (cons binding function-bindings))))) |
| 134 | + ((values) |
| 135 | + (cond |
| 136 | + (variable-bindings |
| 137 | + (collect |
| 138 | + `(let* ,(reverse variable-bindings) |
| 139 | + (multiple-value-bind ,(first binding) ,(second binding) |
| 140 | + ,@(walk-binding-body rest))))) |
| 141 | + (function-bindings |
| 142 | + (collect |
| 143 | + `(labels ,(reverse function-bindings) |
| 144 | + (multiple-value-bind ,(first binding) ,(second binding) |
| 145 | + ,@(walk-binding-body rest))))) |
| 146 | + (t |
| 147 | + (collect |
| 148 | + `(multiple-value-bind ,(first binding) ,(second binding) |
| 149 | + ,@(walk-binding-body rest)))))) |
| 150 | + ((destructuring) |
| 151 | + (cond |
| 152 | + (variable-bindings |
| 153 | + (collect |
| 154 | + `(let* ,(reverse variable-bindings) |
| 155 | + (destructuring-bind ,(first binding) ,(second binding) |
| 156 | + ,@(walk-binding-body rest))))) |
| 157 | + (function-bindings |
| 158 | + (collect |
| 159 | + `(labels ,(reverse function-bindings) |
| 160 | + (destructuring-bind ,(first binding) ,(second binding) |
| 161 | + ,@(walk-binding-body rest))))) |
| 162 | + (t |
| 163 | + (collect |
| 164 | + `(destructuring-bind ,(first binding) ,(second binding) |
| 165 | + ,@(walk-binding-body rest)))))) |
| 166 | + ((nil) |
| 167 | + (cond |
| 168 | + (variable-bindings |
| 169 | + (collect |
| 170 | + `(let* ,(reverse variable-bindings) |
| 171 | + ,this |
| 172 | + ,@(walk-binding-body rest)))) |
| 173 | + (function-bindings |
| 174 | + (collect |
| 175 | + `(labels ,(reverse function-bindings) |
| 176 | + ,this |
| 177 | + ,@(walk-binding-body rest)))) |
| 178 | + (t |
| 179 | + (collect this) |
| 180 | + (wbb rest '() '())))))))) |
| 181 | + (variable-bindings |
| 182 | + ;; hit end of body with pending variables: this only matters |
| 183 | + ;; for side-effect |
| 184 | + (collect `(let* ,(reverse variable-bindings)))) |
| 185 | + (function-bindings |
| 186 | + ;; Pending functions, matters even less |
| 187 | + (collect `(labels ,(reverse function-bindings)))))))) |
| 188 | + |
| 189 | +(defmacro binding (&body forms) |
| 190 | + ;; The macro itself |
| 191 | + (let ((expanded (walk-binding-body forms))) |
| 192 | + (if (= (length expanded) 1) |
| 193 | + (first expanded) |
| 194 | + `(progn ,@expanded)))) |
| 195 | + |
| 196 | +;;; Rudimentary sanity tests |
| 197 | +;;; |
| 198 | +(dolist (form/expansion |
| 199 | + '(((binding |
| 200 | + (bind a 1) |
| 201 | + (bind b 2) |
| 202 | + (values a b)) |
| 203 | + (let* ((a 1) (b 2)) |
| 204 | + (values a b))) |
| 205 | + ((binding |
| 206 | + 1 |
| 207 | + (bind b 2) |
| 208 | + b) |
| 209 | + (progn |
| 210 | + 1 |
| 211 | + (let* ((b 2)) |
| 212 | + b))) |
| 213 | + ((binding |
| 214 | + (bind (f &rest args) args) |
| 215 | + (f 1 3)) |
| 216 | + (labels ((f (&rest args) args)) |
| 217 | + (f 1 3))) |
| 218 | + ((binding |
| 219 | + (bind/values (a b) (values 1 2)) |
| 220 | + (values a b)) |
| 221 | + (multiple-value-bind (a b) (values 1 2) |
| 222 | + (values a b))) |
| 223 | + ((binding |
| 224 | + (bind/values (a b) 1 2) |
| 225 | + (values a b)) |
| 226 | + (multiple-value-bind (a b) (values 1 2) |
| 227 | + (values a b))) |
| 228 | + ((binding |
| 229 | + (bind/destructuring (a &rest b) (list 1 2)) |
| 230 | + (values a b)) |
| 231 | + (destructuring-bind (a &rest b) (list 1 2) |
| 232 | + (values a b))))) |
| 233 | + (destructuring-bind (form expansion) form/expansion |
| 234 | + (unless (equal (macroexpand-1 form) expansion) |
| 235 | + (warn "~S expanded to ~S, not ~S" |
| 236 | + form (macroexpand-1 form) expansion)))) |
0 commit comments