(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) ( :match (:seq (:* ) (:eof)) :then (expand-clauses (first ))) ( :match (:or )) ( :match (:seq (:= :=) (:item)) :then (destructuring-bind (bindings _ form) (declare (ignore _)) (list := bindings form))) ( :match (:item) :if values-binding-p :note "Either a symbol or a list suitable for passing to MULTIPLE-VALUE-BIND") ( :match (:seq (:= :match=) (:item)) :then (destructuring-bind (bindings _ form) (declare (ignore _)) (list :match= bindings form))) ( :match (:item) :if destructuring-bind-list-p :note "An list that might be passed as the first argument to DESTRUCTURING-BIND.") ( :match (:seq (:= :slots=) (:item)) :then (destructuring-bind (bindings _ form) (declare (ignore _)) (list :slots= bindings form))) ( :match (:item) :if slot-value-list-p :note "A list that is suitable for passing to with-slots.") ( :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.") ( :match (:seq (:= :when) (:item)) :then (list :when nil (second ))) ( :match (:@ simple (:item)) :if (not (member simple '(:when= :match= :when := :slots=) :test #'literals-equal)) :then (list :simple nil simple)))