summaryrefslogtreecommitdiff
path: root/flatbind.lisp
diff options
context:
space:
mode:
authorcolin <colin@cicadas.surf>2024-05-04 10:19:17 -0700
committercolin <colin@cicadas.surf>2024-05-04 10:19:17 -0700
commit5d3b5870b7f4d3a9b9be9ea840b21398fa77241d (patch)
tree4851a6692f16473863ca1b6b8460d309459d921b /flatbind.lisp
parentb83e27e66b0032c67072a85bed3c87d948f087f8 (diff)
renamed
Diffstat (limited to 'flatbind.lisp')
-rw-r--r--flatbind.lisp168
1 files changed, 168 insertions, 0 deletions
diff --git a/flatbind.lisp b/flatbind.lisp
new file mode 100644
index 0000000..9a76ff3
--- /dev/null
+++ b/flatbind.lisp
@@ -0,0 +1,168 @@
+(defpackage #:flatbind
+ (:use #:cl)
+ (:local-nicknames
+ (#:a #:alexandria-2))
+ (:export #:do>))
+
+(in-package :flatbind)
+
+(defun variablep (thing)
+ (and (symbolp thing) (not (keywordp thing))))
+
+(defun values-binding-p (form)
+ #+testiere
+ (:tests
+ (:is (values-binding-p 'a))
+ (:is (values-binding-p '(a b)))
+ (:is (values-binding-p '()))
+ (:is (not (values-binding-p :x)))
+ (:is (not (values-binding-p '(a b 4))))
+ (:is (not (values-binding-p '(a :b 4)))))
+ (or (variablep form)
+ (and (listp form)
+ (every #'variablep form))))
+
+(defun destructuring-bind-list-p (form)
+ #+testiere
+ (:tests
+ (:is (destructuring-bind-list-p '()))
+ (:is (destructuring-bind-list-p '(a b)))
+ (:is (destructuring-bind-list-p '(a &rest b)))
+ (:is (destructuring-bind-list-p '(a &rest b &key (x 10) y)))
+ (:is (destructuring-bind-list-p '(&optional a b)))
+ (:is (not (destructuring-bind-list-p 'x)))
+ (:is (not (destructuring-bind-list-p '(&rest x &optional y))))
+ (:is (not (destructuring-bind-list-p '(&rest 10 &optional y)))))
+ (and (listp form)
+ (ignore-errors
+ (multiple-value-bind (required optional rest kwargs allow-other aux keyp)
+ (a:parse-ordinary-lambda-list form)
+ (declare (ignorable required optional rest kwargs keyp))
+ (not (or allow-other aux))))))
+
+(defun slot-value-list-p (form)
+ #+testiere
+ (:tests
+ (:is (slot-value-list-p '(a b c)))
+ (:is (slot-value-list-p '((a slot-a))))
+ (:is (slot-value-list-p '()))
+ (:is (not (slot-value-list-p '(1 2))))
+ (:is (not (slot-value-list-p '(:x)))))
+ (and (listp form)
+ (loop :for thing :in form
+ :always (or (variablep thing)
+ (and (listp thing)
+ (= 2 (length thing))
+ (variablep (first thing))
+ (variablep (second thing)))))))
+
+(defun expand-clauses (clauses)
+ (flet ((collapse-progn (expanded)
+ (if (and (listp expanded) (eq 'cl:progn (first expanded)))
+ (rest expanded)
+ (list expanded))))
+ (loop :with expanded := nil
+ :for (tag bindings form) :in (nreverse clauses)
+ :do
+ (setf expanded
+ (ecase tag
+ (:simple
+ (cond ((null expanded) form)
+ ((eq 'cl:progn (first expanded))
+ `(progn ,form ,@(rest expanded)))
+ (t
+ `(progn ,form ,expanded))))
+
+ (:when
+ (cond ((null expanded) form)
+ ((eq 'cl:progn (first expanded))
+ `(when ,form ,@(rest expanded)))
+ (t
+ `(when ,form ,expanded))))
+
+ (:=
+ `(multiple-value-bind
+ ,(if (listp bindings) bindings (list bindings))
+ ,form
+ ,@(collapse-progn expanded)))
+ (:when=
+ `(a:when-let (,bindings ,form)
+ ,@(collapse-progn expanded)))
+
+ (:slots=
+ `(with-slots ,bindings ,form
+ ,@(collapse-progn expanded)))
+
+ (:match=
+ `(destructuring-bind ,bindings ,form
+ ,@(collapse-progn expanded)))))
+ :finally (return
+ (if (and (listp expanded) (eq 'cl:progn (first expanded)))
+ `(block nil ,@(rest expanded))
+ `(block nil ,expanded))))))
+
+
+(defun literals-equal (a b)
+ (and (symbolp a) (symbolp b) (string-equal a b)))
+
+(argot:deflanguage do> (:literals= #'literals-equal)
+ (<start>
+ :match (:seq (:* <clause>) (:eof))
+ :then (expand-clauses (first <start>)))
+
+ (<clause>
+ :match (:or <binding-clause>
+ <destructuring-clause>
+ <with-slots-clause>
+ <predicate-binding-clause>
+ <predicate-clause>
+ <simple-clause>))
+
+ (<binding-clause>
+ :match (:seq <values-bind> (:= :=) (:item))
+ :then (destructuring-bind (bindings _ form) <binding-clause>
+ (declare (ignore _))
+ (list := bindings form)))
+
+ (<values-bind>
+ :match (:item)
+ :if values-binding-p
+ :note "Either a symbol or a list suitable for passing to MULTIPLE-VALUE-BIND")
+
+ (<destructuring-clause>
+ :match (:seq <destructuring-list> (:= :match=) (:item))
+ :then (destructuring-bind (bindings _ form) <destructuring-clause>
+ (declare (ignore _))
+ (list :match= bindings form)))
+
+ (<destructuring-list>
+ :match (:item)
+ :if destructuring-bind-list-p
+ :note "An list that might be passed as the first argument to DESTRUCTURING-BIND.")
+
+ (<with-slots-clause>
+ :match (:seq <slot-value-list> (:= :slots=) (:item))
+ :then (destructuring-bind (bindings _ form) <with-slots-clause>
+ (declare (ignore _))
+ (list :slots= bindings form)))
+
+ (<slot-value-list>
+ :match (:item)
+ :if slot-value-list-p
+ :note "A list that is suitable for passing to with-slots.")
+
+ (<predicate-binding-clause>
+ :match (:seq (:@ var (:item)) (:= :when=) (:@ form (:item)))
+ :if (variablep var)
+ :then (list :when= var form)
+ :note "Bind variable to form, exit early if form evaluated to nil.")
+
+ (<predicate-clause>
+ :match (:seq (:= :when) (:item))
+ :then (list :when nil (second <predicate-clause>)))
+
+ (<simple-clause>
+ :match (:@ simple (:item))
+ :if (not (member simple '(:when= :match= :when := :slots=) :test #'literals-equal))
+ :then (list :simple nil simple)))
+