;;;; hyperquirks.lisp (in-package #:hyperquirks) ;;; MACROS (defmacro with-env ((&rest bindings) &body body) "Execute BODY in context where environment variables are bound to particular values. When BODY finishes executing, or exits early, the environment variables are restored to their original values. EXAMPLE: (with-env ((\"VAR1\" (get-value-for-var)) (\"VAR2\" \"SOME_VAL\")) (print (uiop:getenv \"VAR1\")) (print (uiop:getenv \"VAR2\")))" (let ((bindings (loop :for binding :in bindings :collect (list* (gensym "VAR") (gensym "VAL") binding)))) `(let* ,(loop :for (oldval cacheval envvar newval) :in bindings :collect `(,oldval (or (uiop:getenv ,envvar) "")) :collect `(,cacheval ,newval)) (setf ,@(loop :for (oldval cacheval envvar newval) :in bindings :collect `(uiop:getenv ,envvar) :collect cacheval)) (unwind-protect (progn ,@body) ,@(loop :for (oldval cacheval envvar newval) :in (reverse bindings) :collect `(when ,oldval (setf (uiop:getenv ,envvar) ,oldval))))))) (defmacro let+ (bindings &body body) "General purpose binding. Normal let bindings, destructuring-binds, and multiple-value-binds all in the same form. (let+ ((x 10) ((y z . w) (list 1 2 3 4 5)) (p q r (values :oh :my :goodness))) (list x y x w p q r)) ;; returns (10 1 2 (3 4 5) :oh :my :goodness) " (flet ((destructuring-binding-p (binding) (and (consp (first binding)) (every #'symbolp (first binding)))) (mvb-binding-p (binding) (and (every #'symbolp (butlast binding)) (consp (first (last binding)))))) (loop :with body = `(progn ,@body) :for binding :in (reverse bindings) :when (destructuring-binding-p binding) :do (setf body `(destructuring-bind ,(first binding) ,(second binding) ,body)) :when (mvb-binding-p binding) :do (setf body `(multiple-value-bind ,(butlast binding) ,(first (last binding)) ,body)) :finally (return body)))) (defmacro imperative (&body body) "Evaluate expressins in BODY in sequence. Expressions that look like (:= VAR1 EXPR1 ...) will expand into a LET* form whose bindings are valid for the rest of the BODY E.g. (imperative (format t \"Welcome to IMPERATIVE\") (terpri) (:= x 10 z (+ x 20)) (format t \"X = ~a, Z = ~a~%\" x z) (:= y (+ z 20)) (format t \"Y = ~a~%\" y) (list x y z)) would evaluate to: Welcome to IMPERATIVE ;; <-- printed to stdout X = 10, Z = 30 Y = 50 (10 50 30) ;; <-- return value IMPERATIVE introduces an implicit, anonymous BLOCK, and hence can be returned from. " (labels ((binding-form-p (form) (and (consp form) (keywordp (first form)) (eql := (first form)))) (collect-bindings (bindings) (loop for (var expr . more) on bindings by #'cddr collect (list var expr))) (expander (body) (cond ((null body) body) ((binding-form-p (first body)) (list (list* 'let* (collect-bindings (rest (first body))) (expander (rest body))))) (t (cons (first body) (expander (rest body))))))) `(block () ,@(expander body)))) (defmacro $> (input-form &rest functions) (loop :with sub = input-form :for next-form :in piped-to-forms :when (symbolp next-form) :do (setf sub `(funcall ,next-form ,sub)) :when (consp next-form) :do (setf sub `(,(first next-form) ,sub ,@(rest next-form))) :finally (return sub ))) (defmacro binding-cond (&body clauses) "Like cond except the first form of every clause is a binding form alá IMPERATIVE. E.g. (binding-cond ((:= x (and (zerop (random 2)) 10) y 11) (list :x x :y y)) (t 12)) That would bind x and y in the first clause, check both both are non nil, and if they are, return the evaluated body, in this case (list :x x :y y) Otherwise 12 would be returned." (let ((imperative-body (loop for (bindings . body) in clauses for vars = (unless (eq t bindings) (loop for (var _ . more) on (rest bindings) by #'cddr collect var)) collect bindings collect `(when (and ,@vars) (return (progn ,@body)))))) `(imperative ,@imperative-body))) (defmacro defun-case (name &rest clauses) "Clauses look like (VARLIST . BODY) E.g. (defun-case foobar (() 10) ((x y) (+ x y)) ((foo) (* foo 2)) ((a b c d) (list a b c d)))" (let* ((rest-args (gensym "variable-pattern-")) (clauses (loop for (arglist . body) in clauses collect `(,(length arglist) (destructuring-bind ,arglist ,rest-args ,@body))))) `(defun ,name (&rest ,rest-args) (case (length ,rest-args) ,@clauses))))