Plaster

common-lisp
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; ;;; WITH-OVERLOAD ;;; ;;; Syntactically replaces usages of the specified operators with their ;;; replacements. By syntactically we mean two things: ;;; ;;; 1) The package of the symbol being replaced doesn't matter, only its name. ;;; ;;; 2) The replacement is done within *sublexical scope* (Let Over Lambda's ;;; terminology). This means that any macros used within BODY (whether ;;; introduced in the global scope, the outer lexical scope or within BODY ;;; itself) are not expanded. Therefore, any usage of the specified operator ;;; within those macros is unaffected. ;;; ;;; SPECS is of the form ({(<op> <replacement>)}*). Every symbol <sym> in the ;;; operator position of a form within BODY that satisfies (string-equal <sym> ;;; <op>), where <op> is one of the specified operators, is replaced with the ;;; corresponding <replacement>. ;;; ;;; TODO: Currently, expanders of MACROLETs within the inner lexical scope are ;;; unaffected by the replacement. This is because Agnostic Lizard doesn't walk ;;; them (and it should, I think). (defmacro with-overload (specs &body body) (let ((last-form nil)) (agnostic-lizard:walk-form `(progn ,@body) nil :on-every-form-pre (lambda (f env) (declare (ignore env)) (setf last-form f)) :on-macroexpanded-form (lambda (f env) (declare (ignore f env)) last-form) :on-function-form (lambda (f env) (declare (ignore env)) (if (consp f) (let ((rep (find (car f) specs :key #'first :test #'string-equal))) (if rep (cons (second rep) (cdr f)) f)) f))))) ;;; OKAY: WITH-OVERLOAD Example 1 (with-overload ((+ -)) (+ 2 2)) ;;; -> (progn (- 2 2)) ;;; ;;; => 0 ;;; OKAY: WITH-OVERLOAD Example 2 (values (multiple-value-list (values (+ 2 2) (+ (+ (+ 0 1) 2) 3))) (multiple-value-list (with-overload ((+ -)) (values (+ 2 2) (+ (+ (+ 0 1) 2) 3))))) ;;; -> ;;; (values ;;; (multiple-value-list ;;; (values (+ 2 2) ;;; (+ (+ (+ 0 1) 2) 3))) ;;; (multiple-value-list ;;; (progn (values (- 2 2) (- (- (- 0 1) 2) 3))))) ;;; ;;; => (4 6), (0 -6) ;;; OKAY: WITH-OVERLOAD Example 3 (defmacro zoz (a b) `(format t "zoz: ~a, ~a!~%" ,a ,b)) (with-overload ((zoz +)) (zoz 1 2)) ;;; -> (progn (+ 1 2)) ;;; ;;; => 3 ;;; ;;; Global macros aren't expanded. ;;; OKAY: WITH-OVERLOAD Example 4 (macrolet ((zoz (a b) `(format t "zoz: ~a, ~a!~%" ,a ,b))) (with-overload ((zoz +)) (zoz 1 2))) ;;; -> ;;; (macrolet ((zoz (a b) ;;; `(format t "zoz: ~a, ~a!~%" ,a ,b))) ;;; (progn (+ 1 2))) ;;; ;;; => 3 ;;; ;;; MACROLETs from the outer lexical scope aren't expanded. ;;; OKAY: WITH-OVERLOAD Example 5 (with-overload ((zoz +)) (macrolet ((zoz (a b) `(format t "zoz: ~a, ~a!~%" ,a ,b))) (zoz 1 2))) ;;; -> ;;; (progn ;;; (macrolet ((zoz (a b) ;;; `(format t "zoz: ~a, ~a!~%" ,a ,b))) ;;; (+ 1 2))) ;;; ;;; => 3 ;;; ;;; MACROLETs from the inner lexical scope aren't expanded. ;;; DONT: WITH-OVERLOAD Example 6 (with-overload ((+ -)) (macrolet ((zoz (a b) `(list (* ,a ,b) ,(+ 1 2)))) (values (zoz 1 2) (+ 1 2)))) ;;; -> ;;; (progn ;;; (macrolet ((zoz (a b) ;;; `(list (* ,a ,b) ,(+ 1 2)))) ;;; (values (zoz 1 2) (- 1 2)))) ;;; ;;; => (2 3), -1 ;;; ;;; Usages of the operator within MACROLET expanders aren't replaced because ;;; Agnostic Lizard doesn't yet walk MACROLET's and DEFMACRO's expanders. ;;; DONT: WITH-OVERLOAD Example 7 (with-overload ((+ -)) (macrolet ((zoz (a b) (+ 1 2) `(list (* ,a ,b) ,(+ 3 4)))) (zoz 1 2))) ;;; -> ;;; (progn ;;; (macrolet ((zoz (a b) ;;; (- 1 2) ;;; `(list (* ,a ,b) ,(+ 3 4)))) ;;; (zoz 1 2))) ;;; ;;; => (2 7) ;;; ;;; This requires my patch to Agnostic Lizard so that it walks MACROLET ;;; expanders. ;;; ;;; It demonstrates that sublexical scope is a tricky beast to use in practice. ;;; Since we don't know what forms the backquote reader macro expands into, we ;;; don't know how exactly inhibiting macro expansion will affect backquote ;;; forms. ;;; ;;; SBCL's backquote implementation for example expands into a form that uses ;;; internal SBCL macros. Commas are represented using an internal macro whose ;;; argument is a literal structure object which encodes the expression that ;;; that is to be unquoted. Since this expression is hidden behind a macro ;;; (regardless of the fact that it further lies within a literal structure), ;;; and we inhibit macro expansion, WITH-OVERLOAD won't rewrite the usages of ;;; the specified operators within these hidden expressions. ;;; ;;; In the example above, when run on SBCL, we can see that the form (+ 1 2) is ;;; rewritten while (+ 3 4) is not as it appears within the backquote. ;;; DONT: WITH-OVERLOAD Example 8 (with-overload ((+ -)) (macrolet ((zoz (a b) `(* ,a ,b))) (values (zoz 1 (+ 2 3)) (+ 1 2)))) ;;; -> ;;; (progn ;;; (macrolet ((zoz (a b) ;;; `(* ,a ,b))) ;;; (values (zoz 1 (- 2 3)) (- 1 2)))) ;;; ;;; => -1, -1 ;;; ;;; This again demonstrates that sublexical scope is tricky to work with. It is ;;; unclear whether we want the (+ 2 3) form to be rewritten, since it appears ;;; as an argument to a *macro*, not a function operator. ;;; ;;; First, Agnostic Lizard assumes that :ON-MACROEXPANDED-FORM returns a form ;;; whose operator position (in case of non-atom forms) has been fully macro ;;; expanded, as if by Common Lisp's MACROEXPAND. Second, our approach of ;;; inhibiting macros is just returning from :ON-MACROEXPANDED-FORM whichever ;;; form was captured last within :ON-EVERY-FORM. These two things together ;;; result in those macros being treated as normal functions, which means that ;;; Agnostic Lizard recursively walks the operator's arguments, rewriting any ;;; usages of the specified operators within the macro's arguments.