├── README ├── website └── source │ ├── resources │ ├── navigation.md │ ├── guide-header.md │ ├── guide-footer.md │ ├── shared.md │ ├── header.md │ └── footer.md │ ├── images │ └── index.mmd │ ├── index.mmd │ ├── user-guide.css │ └── user-guide.mmd ├── README.md ├── unit-tests ├── package.lisp ├── plists.lisp ├── utilities.lisp ├── arrays.lisp ├── structures.lisp ├── regex.lisp ├── functions.lisp ├── classes.lisp └── test-bind.lisp ├── .gitignore ├── .boring ├── metabang-bind.asd ├── lift-standard.config ├── COPYING ├── dev ├── bind-re-allegro.lisp ├── packages.lisp ├── bind-cl-ppcre.lisp ├── macros.lisp ├── bind.lisp └── binding-forms.lisp └── metabang-bind-test.asd /README: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/source/resources/navigation.md: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # metabang-bind 2 | bind is let and much much more 3 | 4 | 5 | # NOTE 6 | 7 | This is unmaintained code. Gary King is no longer an active Lisper. Good luck out there. If you'd like to take it over, let me know. 8 | -------------------------------------------------------------------------------- /website/source/resources/guide-header.md: -------------------------------------------------------------------------------- 1 | {set-property html yes} 2 | {set-property style-sheet user-guide} 3 | {set-property author "Gary Warren King"} 4 | 5 | 8 | 9 | {include shared.md} 10 | -------------------------------------------------------------------------------- /unit-tests/package.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | 3 | (defpackage #:metabang-bind-test 4 | (:use #:common-lisp #:lift #:metabang-bind) 5 | (:import-from #:metabang-bind 6 | #:bind-fix-nils-destructured 7 | #:bind-get-vars-from-lambda-list)) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # really this is private to my build process 2 | make/ 3 | common-lisp.net 4 | .vcs 5 | GNUmakefile 6 | init-lisp.lisp 7 | website/changelog.xml 8 | 9 | 10 | lift.tar.gz 11 | website/output/ 12 | test-results*/ 13 | lift-local.config 14 | *.dribble 15 | *.fasl 16 | -------------------------------------------------------------------------------- /website/source/resources/guide-footer.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /unit-tests/plists.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang-bind-test) 2 | 3 | (deftestsuite test-plists (metabang-bind-test) 4 | ()) 5 | 6 | (addtest (test-plists) 7 | basic-access 8 | (ensure-same 9 | (bind (((:plist a (b _) (c _ 2) (dd d)) '(:b #\b :a #\a :d #\d))) 10 | (list a b c dd)) 11 | '(#\a #\b 2 #\d) :test 'equalp)) 12 | -------------------------------------------------------------------------------- /unit-tests/utilities.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang-bind-test) 2 | 3 | (defun collect-tree (tree &key transform) 4 | "Maps FN over every atom in TREE." 5 | (bind ((transform (or transform #'identity)) 6 | ((:labels doit (x)) 7 | (cond 8 | ;; ((null x) nil) 9 | ((atom x) (funcall transform x)) 10 | (t 11 | (cons 12 | (doit (car x)) 13 | (when (cdr x) (doit (cdr x)))))))) 14 | (doit tree))) 15 | 16 | (defun remove-gensyms (tree) 17 | (collect-tree tree :transform (lambda (x) (when (or (not (symbolp x)) 18 | (symbol-package x)) 19 | x)))) 20 | -------------------------------------------------------------------------------- /unit-tests/arrays.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang-bind-test) 2 | 3 | (deftestsuite test-arrays (metabang-bind-test) 4 | ()) 5 | 6 | (addtest (test-arrays) 7 | basic-access 8 | (ensure-same 9 | (bind ((#(a b c) #(1 2 3))) 10 | (list a b c)) 11 | '(1 2 3) :test 'equal)) 12 | 13 | (addtest (test-arrays) 14 | two-dimensional 15 | (ensure-same 16 | (bind ((#2a((a b c) (d e f)) #2a((1 2 3) (4 5 6)))) 17 | (list a b c d e f)) 18 | '(1 2 3 4 5 6) :test 'equal)) 19 | 20 | (addtest (test-arrays) 21 | basic-access-nils 22 | (ensure-same 23 | (bind ((#(a nil c) #(1 2 3))) 24 | (list a c)) 25 | '(1 3) :test 'equal)) 26 | -------------------------------------------------------------------------------- /website/source/resources/shared.md: -------------------------------------------------------------------------------- 1 | [darcs]: http://www.darcs.net/ 2 | [asdf-install]: http://common-lisp.net/project/asdf-install 3 | [gwking]: http://www.metabang.com/ 4 | 5 | [Home]: index.html 6 | [user-guide]: user-guide.html 7 | 8 | [bundler-cliki]: http://www.cliki.net/bundler 9 | [ASDF-Extension]: http://www.cliki.net/asdf-extension 10 | [gwking-mail]: mailto:gwking@metabang.com 11 | 12 | [CL-Markdown]: http://common-lisp.net/project/cl-markdown/ 13 | [asdf-install]: http://www.cliki.net/asdf-install 14 | [metabang-bind-cliki]: http://www.cliki.net/metabang-bind 15 | 16 | [metabang-bind-devel]: http://common-lisp.net/cgi-bin/mailman/listinfo/metabang-bind-devel 17 | -------------------------------------------------------------------------------- /.boring: -------------------------------------------------------------------------------- 1 | # Boring file regexps: 2 | \.hi$ 3 | \.o$ 4 | \.o\.cmd$ 5 | \.ko$ 6 | \.ko\.cmd$ 7 | \.mod\.c$ 8 | (^|/)\.tmp_versions($|/) 9 | (^|/)CVS($|/) 10 | (^|/)RCS($|/) 11 | ~$ 12 | #(^|/)\.[^/] 13 | (^|/)_darcs($|/) 14 | \.bak$ 15 | \.BAK$ 16 | \.orig$ 17 | (^|/)vssver\.scc$ 18 | \.swp$ 19 | (^|/)MT($|/) 20 | (^|/)\{arch\}($|/) 21 | (^|/).arch-ids($|/) 22 | (^|/), 23 | \.class$ 24 | \.prof$ 25 | (^|/)\.DS_Store$ 26 | (^|/)BitKeeper($|/) 27 | (^|/)ChangeSet($|/) 28 | (^|/)\.svn($|/) 29 | \.py[co]$ 30 | \# 31 | \.cvsignore$ 32 | (^|/)Thumbs\.db$ 33 | (^|/)test-results($|/) 34 | \.dribble 35 | (^|/)make($|/) 36 | (^|/)benchmark-data($|/) 37 | (^|/)test-report-.*($|/) 38 | (^)init-lisp.lisp$ 39 | common-lisp\.net$ 40 | -------------------------------------------------------------------------------- /metabang-bind.asd: -------------------------------------------------------------------------------- 1 | (defpackage #:metabang.bind-system (:use #:cl #:asdf)) 2 | (in-package #:metabang.bind-system) 3 | 4 | (defsystem metabang-bind 5 | :version "0.8.0" 6 | :author "Gary Warren King " 7 | :licence "MIT License" 8 | :description "Bind is a macro that generalizes multiple-value-bind, let, let*, destructuring-bind, structure and slot accessors, and a whole lot more." 9 | :components ((:module 10 | "dev" 11 | :serial t 12 | :components 13 | ((:file "packages") 14 | (:file "macros") 15 | (:file "bind") 16 | (:file "binding-forms") 17 | #+allegro 18 | (:file "bind-re-allegro" :depends-on ("bind"))))) 19 | :in-order-to ((test-op (load-op metabang-bind-test))) 20 | :perform (test-op :after (op c) 21 | (funcall 22 | (intern (symbol-name '#:run-tests) :lift) 23 | :config :generic)) 24 | :depends-on ()) 25 | 26 | (defmethod operation-done-p 27 | ((o test-op) (c (eql (find-system 'metabang-bind)))) 28 | (values nil)) 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /website/source/resources/header.md: -------------------------------------------------------------------------------- 1 | {set-property html yes} 2 | {set-property style-sheet "http://common-lisp.net/project/cl-containers/shared/style-200.css"} 3 | {set-property author "Gary Warren King"} 4 | 5 | {include shared.md} 6 | {include shared-links.md} 7 | 8 | 16 | 17 | [tr]: test-report.html 18 | 19 | [user-guide]: user-guide.html 20 | 21 | [tarball]: http://common-lisp.net/project/cl-containers/bundler/bundler_latest.tar.gz 22 | [metabang-bind-tar]: http://common-lisp.net/project/metabang-bind/metabang-bind_latest.tar.gz 23 | 24 | [devel-list]: http://common-lisp.net/cgi-bin/mailman/listinfo/metabang-bind-devel 25 | [cliki-home]: http://www.cliki.net//metabang-bind 26 | [tarball]: http://common-lisp.net/project/metabang-bind/metabang-bind.tar.gz 27 | -------------------------------------------------------------------------------- /lift-standard.config: -------------------------------------------------------------------------------- 1 | ;;; configuration for LIFT tests 2 | 3 | ;; settings 4 | (:if-dribble-exists :supersede) 5 | (:dribble "metabang-bind.dribble") 6 | (:print-length 10) 7 | (:print-level 5) 8 | (:print-test-case-names t) 9 | 10 | ;; suites to run 11 | (metabang-bind-test) 12 | 13 | ;; report properties 14 | (:report-property :title "Metabang-Bind | Test results") 15 | (:report-property :relative-to metabang-bind-test) 16 | 17 | (:report-property :style-sheet "test-style.css") 18 | (:report-property :if-exists :supersede) 19 | (:report-property :format :html) 20 | (:report-property :full-pathname "test-results/test-report") 21 | (:report-property :unique-name t) 22 | (:build-report) 23 | 24 | (:report-property :unique-name t) 25 | (:report-property :format :describe) 26 | (:report-property :full-pathname "test-results/test-report.txt") 27 | (:build-report) 28 | 29 | (:report-property :format :save) 30 | (:report-property :full-pathname "test-results/test-report.sav") 31 | (:build-report) 32 | 33 | (:report-property :format :describe) 34 | (:report-property :full-pathname *standard-output*) 35 | (:build-report) 36 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Copyright (c) 2004-2008 Gary Warren King (gwking@metabang.com) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 16 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /dev/bind-re-allegro.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang.bind.developer) 2 | 3 | (defmethod bind-generate-bindings ((kind (eql :re)) variable-form value-form) 4 | ;; (:re "re" vars) 5 | (bind (((regex &rest vars) variable-form) 6 | (gok (gensym "ok")) 7 | (gblock (gensym "block")) 8 | ((:values vars ignores) (bind-fix-nils vars))) 9 | `(let* ((,gok nil)) 10 | (block ,gblock 11 | (flet ((doit (,@vars) 12 | ,@(when ignores `((declare (ignore ,@ignores)))) 13 | ,metabang-bind::+decl-marker+ 14 | (return-from ,gblock ,metabang-bind::+code-marker+))) 15 | (excl:re-let ,regex ,(first value-form) 16 | ,(loop for var in vars for i from 1 collect 17 | `(,var ,i)) 18 | (setf ,gok t) 19 | (doit ,@vars)) 20 | (unless ,gok 21 | (doit ,@(make-list (length vars) :initial-element nil)))))))) 22 | 23 | #+(or) 24 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 25 | fname lname date month year) "Frank Zappa 21.12.1940")) 26 | (list fname lname date month year)) 27 | 28 | (defmethod bind-generate-bindings ((kind (eql :regex)) variable-form value-form) 29 | ;; (:regex "re" vars) 30 | (bind (((regex &rest vars) variable-form)) 31 | `(excl:re-let ,regex ,(first value-form) 32 | ,(loop for var in vars for i from 1 collect 33 | `(,var ,i))))) 34 | -------------------------------------------------------------------------------- /metabang-bind-test.asd: -------------------------------------------------------------------------------- 1 | (in-package common-lisp-user) 2 | 3 | (defpackage #:metabang-bind-test-system 4 | (:use #:common-lisp #:asdf)) 5 | (in-package #:metabang-bind-test-system) 6 | 7 | (defsystem metabang-bind-test 8 | :version "0.1" 9 | :author "Gary Warren King " 10 | :maintainer "Gary Warren King " 11 | :licence "MIT Style License" 12 | :description "Tests for metabang-bind" 13 | :components ((:module "setup" 14 | :pathname "unit-tests/" 15 | :components ((:file "package") 16 | (:file "utilities" :depends-on ("package")) 17 | (:file "test-bind" 18 | :depends-on ("package")))) 19 | (:module "tests" 20 | :pathname "unit-tests/" 21 | :depends-on ("setup") 22 | :components ((:file "structures") 23 | (:file "classes") 24 | (:file "plists") 25 | (:file "arrays") 26 | (:file "functions") 27 | #+allegro 28 | (:file "regex")))) 29 | :depends-on (:metabang-bind :lift)) 30 | 31 | #+asdf-system-connections 32 | (asdf:defsystem-connection bind-and-cl-ppcre-test 33 | :requires (metabang-bind-test cl-ppcre) 34 | :components ((:module 35 | "bind-and-cl-ppcre" 36 | :pathname "unit-tests/" 37 | :components ((:file "regex"))))) 38 | 39 | 40 | -------------------------------------------------------------------------------- /website/source/resources/footer.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /dev/packages.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:common-lisp-user) 2 | 3 | (defpackage #:metabang.bind 4 | (:use #:common-lisp) 5 | (:nicknames #:bind #:metabang-bind) 6 | (:intern 7 | #:bind-generate-bindings 8 | #:bind-filter-declarations 9 | #:bind-macro-helper 10 | #:bind-fix-nils) 11 | (:export 12 | #:bind 13 | #:fluid-bind 14 | 15 | #:binding-forms 16 | #:binding-form-synonyms 17 | #:binding-form-groups 18 | #:binding-form-docstring 19 | #:binding-form ;for documentation 20 | 21 | #:*bind-all-declarations* 22 | #:*bind-non-var-declarations* 23 | #:*bind-lambda-list-markers* 24 | 25 | #:bind-error 26 | #:bind-keyword/optional-nil-with-default-error 27 | #:bind-missing-value-form-warning 28 | #:bind-too-many-value-forms-error 29 | 30 | #:*unused-declarations-behavior* 31 | #:bind-unused-declarations-condition 32 | #:bind-unused-declarations-warning 33 | #:bind-unused-declarations-error 34 | 35 | #:*unused-declarations-behavior* 36 | #:bind-unused-declarations-condition 37 | #:bind-unused-declarations-warning 38 | #:bind-unused-declarations-error 39 | 40 | #:lambda-bind)) 41 | 42 | (defpackage #:metabang.bind.developer 43 | (:use #:common-lisp #:metabang-bind) 44 | (:import-from #:metabang-bind 45 | #:bind-generate-bindings 46 | #:bind-filter-declarations 47 | #:bind-macro-helper 48 | #:bind-fix-nils) 49 | (:export 50 | #:bind-generate-bindings 51 | #:bind-filter-declarations 52 | #:bind-macro-helper 53 | #:bind-fix-nils 54 | #:defbinding-form)) 55 | 56 | -------------------------------------------------------------------------------- /unit-tests/structures.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang-bind-test) 2 | 3 | (defstruct (metabang-bind-test-1) 4 | a 5 | b 6 | c) 7 | 8 | (defstruct (metabang-bind-test-2 (:conc-name bind-test-)) 9 | d 10 | e) 11 | 12 | (deftestsuite test-structures (metabang-bind-test) 13 | ()) 14 | 15 | (addtest (test-structures) 16 | basic-access 17 | (ensure-same 18 | (bind (((:struct metabang-bind-test-1- a c) 19 | (make-metabang-bind-test-1 :a 1 :b 2 :c 3))) 20 | (list a c)) 21 | '(1 3) :test 'equal)) 22 | 23 | (addtest (test-structures) 24 | no-capture 25 | (let ((values 4)) 26 | (bind (((:struct metabang-bind-test-1- a c) 27 | (make-metabang-bind-test-1 :a 1 :b 2 :c 3))) 28 | (ensure-same '(4 1 3) (list values a c) :test 'equal)))) 29 | 30 | (addtest (test-structures) 31 | changed-variable-name 32 | (ensure-same 33 | (bind (((:struct metabang-bind-test-1- (my-a a) c) 34 | (make-metabang-bind-test-1 :a 1 :b 2 :c 3))) 35 | (list c my-a)) 36 | '(3 1) :test 'equal)) 37 | 38 | (addtest (test-structures) 39 | changed-variable-name-2 40 | (ensure-same 41 | (bind (((:structure metabang-bind-test-1- (my-a a) c) 42 | (make-metabang-bind-test-1 :a 1 :b 2 :c 3))) 43 | (list c my-a)) 44 | '(3 1) :test 'equal)) 45 | 46 | (addtest (test-structures) 47 | nested-read-only 48 | (let ((c1 (make-metabang-bind-test-1 :a 1 :b 2 :c 3)) 49 | (c2 (make-metabang-bind-test-1 :a 4 :b 5 :c 6))) 50 | (ensure-same 51 | (bind (((:structure metabang-bind-test-1- (my-a a) c) c1) 52 | ((:structure metabang-bind-test-1- a b (second-c c)) c2)) 53 | (list my-a c a b second-c)) 54 | '(1 3 4 5 6) :test 'equal))) 55 | 56 | (addtest (test-structures) 57 | read-write-nested 58 | (let ((c1 (make-metabang-bind-test-1 :a 1 :b 2 :c 3)) 59 | (c2 (make-metabang-bind-test-1 :a 4 :b 5 :c 6))) 60 | (bind (((:structure/rw metabang-bind-test-1- (my-a a) c) c1) 61 | ((:structure/rw metabang-bind-test-1- a b (second-c c)) c2)) 62 | (setf my-a :a second-c :c b :b)) 63 | (ensure-same (metabang-bind-test-1-a c1) :a) 64 | (ensure-same (metabang-bind-test-1-b c2) :b) 65 | (ensure-same (metabang-bind-test-1-c c2) :c))) 66 | -------------------------------------------------------------------------------- /unit-tests/regex.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang-bind-test) 2 | 3 | #+(or) 4 | (run-tests :suite 'test-regex) 5 | 6 | 7 | (deftestsuite test-regex (metabang-bind-test) 8 | () 9 | (:equality-test #'equalp)) 10 | 11 | (addtest (test-regex) 12 | simple-bind 13 | (ensure-same 14 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 15 | fname lname date month year) "Frank Zappa 21.12.1940")) 16 | (list fname lname date month year)) 17 | (list "Frank" "Zappa" "21" "12" "1940"))) 18 | 19 | (addtest (test-regex) 20 | nils-are-ignored-1 21 | (ensure-same 22 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 23 | fname lname nil month year) "Frank Zappa 21.12.1940")) 24 | (list fname lname month year)) 25 | (list "Frank" "Zappa" "12" "1940"))) 26 | 27 | (addtest (test-regex) 28 | nils-are-ignored-2 29 | (ensure-same 30 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 31 | nil lname nil month year) "Frank Zappa 21.12.1940")) 32 | (list lname month year)) 33 | (list "Zappa" "12" "1940"))) 34 | 35 | #+(or) 36 | (addtest (test-regex) 37 | nils-are-ignored-1 38 | (let ((result 39 | (lambda () 40 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 41 | fname lname nil month year) "Frank Zappa 21.12.1940")) 42 | (list lname month year))))) 43 | (ensure-same (funcall result) 44 | (list "Zappa" "12" "1940")) 45 | (ensure-warning 46 | (compile 47 | nil 48 | (lambda () 49 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 50 | fname lname nil month year) "Frank Zappa 21.12.1940")) 51 | (list lname month year))))))) 52 | 53 | (addtest (test-regex) 54 | executes-when-no-bindings 55 | (ensure-same 56 | (bind (((:re "(a|b)+" first) "cccc")) 57 | (list "still seen" first)) 58 | (list "still seen" nil))) 59 | 60 | #+(or) 61 | (addtest (test-regex) 62 | you-can-use-doit 63 | (ensure-same 64 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 65 | fname lname nil month year) 66 | "Frank Zappa 21.12.1940")) 67 | (flet ((doit (&rest vars) 68 | (reverse vars))) 69 | (doit fname lname month year))) 70 | (reverse (list "Frank" "Zappa" "12" "1940")))) 71 | 72 | #+(or) 73 | (defun xxx () 74 | (let ((result 75 | '(lambda () 76 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 77 | nil lname nil month year) "Frank Zappa 21.12.1940")) 78 | (list lname month year))))) 79 | (compile nil result))) 80 | -------------------------------------------------------------------------------- /unit-tests/functions.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang-bind-test) 2 | 3 | (deftestsuite test-flet (metabang-bind-test) 4 | ()) 5 | 6 | (addtest (test-flet) 7 | basic-access 8 | (bind (((:flet doit (x)) 9 | (setf x (* 2 x)) 10 | (setf x (+ x 3)) 11 | x)) 12 | (ensure-same (doit 1) 5) 13 | (ensure-same (doit 2) 7))) 14 | 15 | (addtest (test-flet) 16 | declarations 17 | (bind (((:flet doit (x)) 18 | (declare (type fixnum x)) 19 | (setf x (* 2 x)) 20 | (setf x (+ x 3)) 21 | x)) 22 | (ensure-same (doit 1) 5) 23 | (ensure-same (doit 2) 7))) 24 | 25 | (addtest (test-flet) 26 | docstring 27 | (bind (((:flet doit (x)) 28 | "if I knew how to get the docstring out of flet, I'd test it." 29 | (setf x (* 2 x)) 30 | (setf x (+ x 3)) 31 | x)) 32 | (ensure-same (doit 1) 5) 33 | (ensure-same (doit 2) 7))) 34 | 35 | (addtest (test-flet) 36 | docstring-and-declarations-1 37 | (bind (((:flet doit (x)) 38 | "whatever" 39 | (declare (type fixnum x)) 40 | (setf x (* 2 x)) 41 | (setf x (+ x 3)) 42 | x)) 43 | (ensure-same (doit 1) 5) 44 | (ensure-same (doit 2) 7))) 45 | 46 | (addtest (test-flet) 47 | docstring-and-declarations-2 48 | (bind (((:flet constant (x)) 49 | (declare (ignore x)) 50 | 42)) 51 | (ensure-same (constant 1) 42))) 52 | 53 | 54 | (deftestsuite test-labels (metabang-bind-test) 55 | ()) 56 | 57 | (addtest (test-labels) 58 | basic-access 59 | (bind (((:labels my-oddp (x)) 60 | (cond ((<= x 0) nil) 61 | ((= x 1) t) 62 | (t (my-oddp (- x 2)))))) 63 | (ensure (my-oddp 1)) 64 | (ensure (my-oddp 7)) 65 | (ensure-null (my-oddp 2)))) 66 | 67 | (addtest (test-labels) 68 | declarations 69 | (bind (((:labels doit (x)) 70 | (declare (type fixnum x)) 71 | (setf x (* 2 x)) 72 | (setf x (+ x 3)) 73 | x)) 74 | (ensure-same (doit 1) 5) 75 | (ensure-same (doit 2) 7))) 76 | 77 | (addtest (test-labels) 78 | docstring 79 | (bind (((:labels doit (x)) 80 | "if I knew how to get the docstring out of flet, I'd test it." 81 | (setf x (* 2 x)) 82 | (setf x (+ x 3)) 83 | x)) 84 | (ensure-same (doit 1) 5) 85 | (ensure-same (doit 2) 7))) 86 | 87 | (addtest (test-labels) 88 | docstring-and-declarations-1 89 | (bind (((:labels doit (x)) 90 | "whatever" 91 | (declare (type fixnum x)) 92 | (setf x (* 2 x)) 93 | (setf x (+ x 3)) 94 | x)) 95 | (ensure-same (doit 1) 5) 96 | (ensure-same (doit 2) 7))) 97 | 98 | (addtest (test-labels) 99 | docstring-and-declarations-2 100 | (bind (((:labels constant (x)) 101 | (declare (ignore x)) 102 | 42)) 103 | (ensure-same (constant 1) 42))) 104 | -------------------------------------------------------------------------------- /website/source/images/index.mmd: -------------------------------------------------------------------------------- 1 | {include header.md} 2 | {set-property title "metabang-bind - Sticking it the to metal..."} 3 | 4 |
5 | 22 |
23 | 24 | ### What it is 25 | 26 | Bind extends the idea of of `let` and destructing to provide 27 | a uniform syntax for all your accessor needs. It combines 28 | _let_, _destructuring-bind_, `with-slots`, `with-accessors`, 29 | structure editing, property or association-lists, and 30 | _multiple-value-bind_ and a whole lot more into a single 31 | form. The [user guide][user-guide] has all the details but 32 | here is example to whet your appetite: 33 | 34 | (bind ((a 2) 35 | ((b &rest args &key (c 2) &allow-other-keys) '(:a :c 5 :d 10 :e 54)) 36 | ((:values d e) (truncate 4.5))) 37 | (list a b c d e args)) 38 | ==> (2 :A 5 4 0.5 (:C 5 :D 10 :E 54)) 39 | 40 | Bind is especially handy when you have more than one layer of 41 | `multiple-value-bind` or `destructuring-bind`. Since `bind` is a 42 | single form, you don't end up too far off to the right in 43 | editor land. 44 | 45 | Bind is released under the [MIT license][mit-license]. 46 | 47 | {anchor mailing-lists} 48 | 49 | ### Mailing Lists 50 | 51 | Use the developer [mailing list][metabang-bind-devel] for any questions or comments regarding bind. 52 | 53 | {anchor downloads} 54 | 55 | ### Where is it 56 | 57 | metabang.com is switching from [darcs][] to [git][] for source control; the current metabang-bind repository is on [github][github-metabang-bind] and you can clone it using: 58 | 59 | git clone git://github.com/gwkkwg/metabang-bind 60 | 61 | metabang-bind is also [ASDF installable][asdf-install]. Its 62 | CLiki home is right [where][cliki-home] you'd expect. 63 | 64 | There's also a handy [gzipped tar file][tarball]. 65 | 66 | {anchor news} 67 | 68 | ### What is happening 69 | 70 | 10 April 2010 - moved to github; added flet support 71 | 72 | 28 May 2009 - added `:structure/rw` binding form; updated 73 | webpage to link to the user's guide 74 | 75 | 1 Dec 2007 - Added support for [array 76 | destructuring][array-bindings] (Thanks to Tamas Papp for the 77 | idea) 78 | 79 | 15 Nov 2007 - New user guide; bind handles structures and 80 | property lists and is now extensible! 81 | 82 | 13 Nov 2005 - Initial webpage n' stuff. 83 | 84 |
85 |
86 | 87 | {include footer.md} 88 | 89 | 90 | -------------------------------------------------------------------------------- /website/source/index.mmd: -------------------------------------------------------------------------------- 1 | {include resources/header.md} 2 | {set-property title "metabang-bind - Sticking it the to metal..."} 3 | 4 |
5 | 22 |
23 | 24 | ### What it is 25 | 26 | Bind extends the idea of of `let` and destructing to provide 27 | a uniform syntax for all your accessor needs. It combines 28 | _let_, _destructuring-bind_, `with-slots`, `with-accessors`, 29 | structure editing, property or association-lists, and 30 | _multiple-value-bind_ and a whole lot more into a single 31 | form. The [user guide][user-guide] has all the details but 32 | here is example to whet your appetite: 33 | 34 | (bind ((a 2) 35 | ((b &rest args &key (c 2) &allow-other-keys) '(:a :c 5 :d 10 :e 54)) 36 | ((:values d e) (truncate 4.5))) 37 | (list a b c d e args)) 38 | ==> (2 :A 5 4 0.5 (:C 5 :D 10 :E 54)) 39 | 40 | Bind is especially handy when you have more than one layer of 41 | `multiple-value-bind` or `destructuring-bind`. Since `bind` is a 42 | single form, you don't end up too far off to the right in 43 | editor land. 44 | 45 | Bind is released under the [MIT license][mit-license]. 46 | 47 | {anchor mailing-lists} 48 | 49 | ### Mailing Lists 50 | 51 | Use the developer [mailing list][metabang-bind-devel] for any questions or comments regarding bind. 52 | 53 | {anchor downloads} 54 | 55 | ### Where is it 56 | 57 | metabang.com is switching from [darcs][] to [git][] for source control; the current metabang-bind repository is on [github][github-metabang-bind] and you can clone it using: 58 | 59 | git clone git://github.com/gwkkwg/metabang-bind 60 | 61 | metabang-bind is also [ASDF installable][asdf-install]. Its 62 | CLiki home is right [where][cliki-home] you'd expect. 63 | 64 | There's also a handy [gzipped tar file][tarball]. 65 | 66 | {anchor news} 67 | 68 | ### What is happening 69 | 70 | 10 April 2010 - moved to github; added flet support 71 | 72 | 28 May 2009 - added `:structure/rw` binding form; updated 73 | webpage to link to the user's guide 74 | 75 | 1 Dec 2007 - Added support for [array 76 | destructuring][array-bindings] (Thanks to Tamas Papp for the 77 | idea) 78 | 79 | 15 Nov 2007 - New user guide; bind handles structures and 80 | property lists and is now extensible! 81 | 82 | 13 Nov 2005 - Initial webpage n' stuff. 83 | 84 |
85 |
86 | 87 | {include resources/footer.md} 88 | 89 | 90 | -------------------------------------------------------------------------------- /dev/bind-cl-ppcre.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang.bind.developer) 2 | 3 | #+wrong 4 | (defmethod bind-generate-bindings ((kind (eql :re)) variable-form value-form) 5 | ;; (:re "re" vars) 6 | (bind (((regex &rest vars) variable-form) 7 | (gok (gensym "ok")) 8 | (gblock (gensym "block")) 9 | ((:values vars ignores) (bind-fix-nils vars))) 10 | `((let ((,gok nil)) 11 | (block ,gblock 12 | (flet ((doit (,@vars) 13 | ,@(when ignores `((declare (ignore ,@ignores)))) 14 | (return-from ,gblock 15 | (progn ,@(bind-macro-helper 16 | remaining-bindings declarations body))))) 17 | (cl-ppcre:register-groups-bind 18 | ,vars (,regex ,(first value-form) :sharedp t) 19 | ,(bind-filter-declarations 20 | declarations variable-form) 21 | (setf ,gok t) 22 | (doit ,@vars)) 23 | (unless ,gok 24 | (doit ,@(make-list (length vars) :initial-element nil))))))))) 25 | 26 | ;; simple but doesn't execute inner code if no bindings found 27 | ;; which isn't very bind-like 28 | (defmethod bind-generate-bindings ((kind (eql :regex)) variable-form value-form) 29 | ;; (:re "re" vars) 30 | (bind (((regex &rest vars) variable-form)) 31 | `((cl-ppcre:register-groups-bind ,vars (,regex ,(first value-form) :sharedp t))))) 32 | 33 | #+(or) 34 | ;; doesn't handle ignores 35 | (defmethod bind-generate-bindings 36 | ((kind (eql :re)) variable-form value-form 37 | body declarations remaining-bindings) 38 | ;; (:re "re" vars) 39 | (bind (((regex &rest vars) variable-form) 40 | (gok (gensym "ok")) 41 | (gblock (gensym "block"))) 42 | `((let ((,gok nil)) 43 | (block ,gblock 44 | (flet ((doit (,@vars) 45 | (return-from ,gblock 46 | ,@(bind-macro-helper 47 | remaining-bindings declarations body)))) 48 | (cl-ppcre:register-groups-bind 49 | ,vars (,regex ,(first value-form) :sharedp t) 50 | ,(bind-filter-declarations 51 | declarations variable-form) 52 | (setf ,gok t) 53 | (doit ,@vars)) 54 | (unless ,gok 55 | (doit ,@(make-list (length vars) :initial-element nil))))))))) 56 | 57 | #+(or) 58 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 59 | fname lname date month year) "Frank Zappa 21.12.1940")) 60 | (list fname lname date month year)) 61 | 62 | #+(or) 63 | (macroexpand-1 64 | '(bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 65 | fname lname date month year) "Frank Zappa 21.12.1940")) 66 | (list fname lname date month year))) 67 | 68 | #+(or) 69 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 70 | fname lname nil month year) "Frank Zappa 21.12.1940")) 71 | (list fname lname month year)) 72 | 73 | #+(or) 74 | (bind (((:re "(a|b)+" first) "cccc")) 75 | (format t "This will still be printed: ~A" first)) 76 | -------------------------------------------------------------------------------- /unit-tests/classes.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang-bind-test) 2 | 3 | (defclass metabang-bind-class-1 () 4 | ((a :initarg :a :accessor a) 5 | (b :initarg :b :accessor b) 6 | (c :initarg :c :accessor c))) 7 | 8 | (defclass metabang-bind-class-2 (metabang-bind-class-1) 9 | ((d :initarg :d :accessor the-d) 10 | (e :initarg :e :accessor e))) 11 | 12 | (deftestsuite test-classes (metabang-bind-test) 13 | ()) 14 | 15 | (addtest (test-classes) 16 | basic-slots 17 | (ensure-same 18 | (bind (((:slots-read-only a c) 19 | (make-instance 'metabang-bind-class-1 :a 1 :b 2 :c 3))) 20 | (list a c)) 21 | '(1 3) :test 'equal)) 22 | 23 | (addtest (test-classes) 24 | slots-new-variable-names 25 | (ensure-same 26 | (bind (((:slots-read-only a (my-c c) (the-b b)) 27 | (make-instance 'metabang-bind-class-1 :a 1 :b 2 :c 3))) 28 | (list a the-b my-c)) 29 | '(1 2 3) :test 'equal)) 30 | 31 | (addtest (test-classes) 32 | writable-slots 33 | (ensure-same 34 | (bind ((instance (make-instance 'metabang-bind-class-1 :a 1 :b 2 :c 3)) 35 | ((:slots a (my-c c) (the-b b)) instance)) 36 | (setf a :changed) 37 | (list (slot-value instance 'a) the-b my-c)) 38 | '(:changed 2 3) :test 'equal)) 39 | 40 | (addtest (test-classes) 41 | slots-r/o-1 42 | (ensure-same 43 | (bind (((:slots-r/o a c) 44 | (make-instance 'metabang-bind-class-1 :a 1 :b 2 :c 3))) 45 | (list a c)) 46 | '(1 3) :test 'equal)) 47 | 48 | (addtest (test-classes) 49 | basic-accessors-r/o-1 50 | (ensure-same 51 | (bind (((:accessors-read-only a c e) 52 | (make-instance 'metabang-bind-class-2 :a 1 :b 2 :c 3 :d 4 :e 5))) 53 | (list e c a)) 54 | '(5 3 1) :test 'equal)) 55 | 56 | (addtest (test-classes) 57 | basic-accessors-r/o-2 58 | (bind ((obj (make-instance 'metabang-bind-class-2 :a 1 :b 2 :c 3 :d 4 :e 5)) 59 | ((:accessors-read-only a c e) obj)) 60 | (setf a :a c :c) 61 | (ensure-same (list a c e) '(:a :c 5) :test 'equal) 62 | (ensure-same 63 | (list (e obj) (c obj) (a obj)) 64 | '(5 3 1) :test 'equal))) 65 | 66 | (addtest (test-classes) 67 | accessors-new-variable-names-r/o 68 | (ensure-same 69 | (bind (((:accessors-r/o (my-a a) (my-c c) (d the-d)) 70 | (make-instance 'metabang-bind-class-2 :a 1 :b 2 :c 3 :d 4 :e 5))) 71 | (list d my-c my-a)) 72 | '(4 3 1) :test 'equal)) 73 | 74 | (addtest (test-classes) 75 | basic-accessors-1 76 | (ensure-same 77 | (bind ((obj (make-instance 'metabang-bind-class-2 :a 1 :b 2 :c 3 :d 4 :e 5)) 78 | ((:accessors a c e) obj)) 79 | (setf a :a c :c) 80 | (list (e obj) (c obj) (a obj))) 81 | '(5 :c :a) :test 'equal)) 82 | 83 | (addtest (test-classes) 84 | accessors-new-variable-names 85 | (ensure-same 86 | (bind ((obj (make-instance 'metabang-bind-class-2 :a 1 :b 2 :c 3 :d 4 :e 5)) 87 | ((:writable-accessors (my-a a) (my-c c) (d the-d)) 88 | obj)) 89 | (setf my-a 42) 90 | (list d my-c my-a (a obj))) 91 | '(4 3 42 42) :test 'equal)) 92 | -------------------------------------------------------------------------------- /website/source/user-guide.css: -------------------------------------------------------------------------------- 1 | /* @group toc */ 2 | 3 | .table-of-contents { 4 | font-size: 90%; 5 | margin-bottom: 1em; 6 | padding-bottom: 1em; 7 | } 8 | 9 | .table-of-contents h2, h3, h4 { 10 | padding-top: 0; 11 | padding-bottom: 0; 12 | margin-top: 0; 13 | margin-bottom: 1px; 14 | } 15 | 16 | .table-of-contents h2 { 17 | font-size: inherit; 18 | font-style: inherit; 19 | position: relative; 20 | left: 2em; 21 | } 22 | 23 | .table-of-contents h3 { 24 | font-size: inherit; 25 | font-style: inherit; 26 | position: relative; 27 | left: 4em; 28 | } 29 | 30 | .table-of-contents h4 { 31 | font-size: inherit; 32 | font-style: inherit; 33 | position: relative; 34 | left: 6em; 35 | } 36 | 37 | .table-of-contents h5 { 38 | font-size: inherit; 39 | font-style: inherit; 40 | position: relative; 41 | left: 8px; 42 | } 43 | 44 | /* @end */ 45 | 46 | /* @group anchors */ 47 | 48 | a.none { 49 | text-decoration: none; 50 | color:black } 51 | 52 | a.none:visited { text-decoration: none; color:black } 53 | 54 | a.none:active { text-decoration: none; color:black } 55 | 56 | a.none:hover { text-decoration: none; color:black } 57 | 58 | a { 59 | text-decoration: none; 60 | } 61 | 62 | a:visited { 63 | text-decoration: none; 64 | } 65 | 66 | a:active { 67 | text-decoration: underline; 68 | } 69 | 70 | a:hover { 71 | text-decoration: underline; 72 | } 73 | 74 | /* @end */ 75 | 76 | /* @group Reference */ 77 | 78 | .reference { 79 | padding-bottom: 1em; 80 | } 81 | 82 | .reference h3 { 83 | margin-top: 2em; 84 | font-size: 110%; 85 | border-bottom: 1px solid silver; 86 | border-top: 4px solid gray; 87 | padding-top: 3px; 88 | padding-bottom: 3px; 89 | } 90 | 91 | 92 | 93 | /* @end */ 94 | 95 | body { 96 | font-family: Georgia, "Times New Roman", Times, serif; 97 | margin-right: 0.75in; 98 | margin-left: 0.75in; 99 | margin-bottom: 0.25in; 100 | } 101 | 102 | h1, h2, h3, h4 { 103 | font-family: "Lucida Grande", Lucida, Verdana, sans-serif; 104 | } 105 | 106 | h2 { 107 | } 108 | 109 | h3, h4 { 110 | font-style: italic; 111 | } 112 | 113 | .hidden { 114 | visibility: hidden; 115 | } 116 | 117 | .documentation { 118 | margin-right: 1em; 119 | margin-left: 1em; 120 | } 121 | 122 | .function { 123 | 124 | } 125 | 126 | .documentation.header { 127 | display: block; 128 | position: relative; 129 | border-top-style: solid; 130 | border-top-width: 1pt; 131 | padding-top: 4px; 132 | margin-top: 2em; 133 | } 134 | 135 | .documentation.contents { 136 | top: 10px; 137 | position: relative; 138 | } 139 | 140 | .documentation-name { 141 | font-weight: bold; 142 | float: left; 143 | padding-right: 10px; 144 | width: 125px; 145 | } 146 | 147 | .documentation-kind { 148 | float: right; 149 | font-style: italic; 150 | color: gray; 151 | padding-left: 10px; 152 | } 153 | 154 | .documentation-arguments { 155 | float: left; 156 | width: 350px; 157 | font-style: italic; 158 | } 159 | 160 | .documentation p { 161 | clear: both; 162 | margin-right: 1em; 163 | margin-left: 1em; 164 | } 165 | 166 | pre { 167 | background-color: #ffc8ff; 168 | overflow: auto; 169 | padding-bottom: 5px; 170 | margin-right: 1cm; 171 | margin-left: 1cm; 172 | font-family: "Courier New", Courier, mono; 173 | } 174 | 175 | .note { 176 | border: 2px inset gray; 177 | padding: 0.5em; 178 | margin-right: 2em; 179 | margin-left: 2em; 180 | } 181 | #navigation li { 182 | display: inline; 183 | border-right-style: dotted; 184 | border-right-width: 1px; 185 | border-left-style: dotted; 186 | border-left-width: 1px; 187 | border-collapse: collapse; 188 | padding-right: 0.25em; 189 | padding-left: 0.25em; 190 | margin-right: 1em; 191 | } 192 | 193 | #navigation { 194 | text-align: center; 195 | } 196 | 197 | #footer { 198 | margin-top: 2em; 199 | padding-bottom: 2em; 200 | padding-top: 1em; 201 | border-top-style: inset; 202 | border-top-width: 2px; 203 | font-size: 80%; 204 | line-height: 110%; 205 | } 206 | 207 | #timestamp { 208 | font-size: 90%; 209 | text-align: right; 210 | float: right; 211 | } 212 | 213 | #license-note { 214 | float: left; 215 | } 216 | 217 | #copyright { 218 | float: left; 219 | } 220 | 221 | -------------------------------------------------------------------------------- /dev/macros.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang.bind) 2 | 3 | #| 4 | 5 | use 6 | 7 | (defmethod documentation (object doc-type) 8 | body...) 9 | 10 | instead 11 | 12 | (documentation :plist 'binding-form) 13 | 14 | |# 15 | 16 | (defmethod documentation (what (doc-type (eql 'metabang.bind:binding-form))) 17 | (binding-form-docstring what)) 18 | 19 | (defun binding-form-docstring (name) 20 | "Returns the docstring for a binding form named `name`." 21 | (let* ((docstrings (get 'bind :docstrings)) 22 | (forms (get 'bind :binding-forms)) 23 | (canonical-name (first (assoc name forms))) 24 | ) 25 | (and canonical-name 26 | (assoc canonical-name docstrings)))) 27 | 28 | (defun (setf binding-form-docstring) (docstring name/s) 29 | (when (atom name/s) 30 | (setf name/s (list name/s))) 31 | (let* ((docstrings (get 'bind :docstrings)) 32 | (forms (get 'bind :binding-forms)) 33 | (canonical-name (first name/s)) 34 | (current-docstring-pair (assoc canonical-name docstrings))) 35 | (loop for name in name/s do 36 | (let ((names-pair (assoc name forms))) 37 | (if names-pair 38 | (setf (cdr names-pair) name/s) 39 | (push (cons name name/s) forms)))) 40 | (if current-docstring-pair 41 | (setf (cdr current-docstring-pair) docstring) 42 | (push (cons canonical-name docstring) docstrings)) 43 | (setf (get 'bind :docstrings) docstrings) 44 | (setf (get 'bind :binding-forms) forms) 45 | docstring)) 46 | 47 | (defmacro defbinding-form ((name/s &key docstring remove-nils-p 48 | description (use-values-p t) 49 | (accept-multiple-forms-p nil)) &body body) 50 | "Describe how `bind` should expand particular binding-forms. 51 | 52 | `defbinding-form` links a name or type with an expansion. These 53 | definitions are used by `bind` at macro-expansion time to generate 54 | the code that actually does the bindings for you. For example: 55 | 56 | (defbinding-form (symbol :use-values-p nil) 57 | (if (keywordp kind) 58 | (error \"Don't have a binding form for ~s\" kind) 59 | `(let (,@(if values 60 | `((,variables ,values)) 61 | `(,variables)))))) 62 | 63 | This binding form tells to expand clauses whose first element is 64 | a symbol using `let`. (It also gets `bind` to signal an error if 65 | the first element is a keyword that doesn't have a defined binding 66 | form.) 67 | " 68 | (declare (ignorable remove-nils-p description)) 69 | (let* ((multiple-names? (consp name/s)) 70 | (main-method-name nil) 71 | (force-keyword? (or multiple-names? 72 | (eq (symbol-package name/s) 73 | (load-time-value (find-package :keyword))))) 74 | (gnew-form (gensym "new-form"))) 75 | (cond (multiple-names? 76 | (setf main-method-name (gensym (symbol-name '#:binding-generator)))) 77 | (t 78 | (setf main-method-name 'bind-generate-bindings))) 79 | (flet ((form-keyword (name) 80 | (intern (symbol-name name) 81 | (load-time-value (find-package :keyword))))) 82 | (when force-keyword? 83 | (setf name/s (if multiple-names? 84 | (mapcar #'form-keyword name/s) 85 | (form-keyword name/s)))) 86 | `(let () 87 | (setf (binding-form-docstring ',name/s) ,docstring) 88 | ,@(loop for name in (if multiple-names? name/s (list name/s)) 89 | when (keywordp name) collect 90 | `(defmethod binding-form-accepts-multiple-forms-p 91 | ((binding-form (eql ,name))) 92 | ,accept-multiple-forms-p)) 93 | (,(if multiple-names? 'defun 'defmethod) ,main-method-name 94 | (,@(unless multiple-names? 95 | (if force-keyword? 96 | `((kind (eql ,name/s))) 97 | `((kind ,name/s)))) 98 | variable-form value-form) 99 | ;;?? Can (symbolp (first body)) ever be true? 100 | ,(if use-values-p 101 | `(let* ((gvalues (next-value "values-")) 102 | (,gnew-form (funcall (lambda (variables values) ,@body) 103 | variable-form gvalues))) 104 | (destructuring-bind (TAG . REST) 105 | ,gnew-form 106 | ;;?? CASE 107 | (if (or (eq TAG 'let) (eq TAG 'let*)) 108 | (destructuring-bind (let-bindings . after-bindings) 109 | REST 110 | (values `(let* ((,gvalues ,,(if accept-multiple-forms-p 111 | `value-form 112 | `(first value-form))) 113 | ,@let-bindings) 114 | (declare (ignorable ,gvalues)) 115 | ,@after-bindings) 116 | nil)) 117 | (values `(let* ((,gvalues ,,(if accept-multiple-forms-p 118 | `value-form 119 | `(first value-form)))) 120 | (declare (ignorable ,gvalues)) 121 | ,,gnew-form) 122 | t)))) 123 | `(let ((,gnew-form (funcall (lambda (variables values) ,@body) 124 | variable-form ,(if accept-multiple-forms-p 125 | `value-form 126 | `(first value-form))))) 127 | (values ,gnew-form nil)))) 128 | ,@(when multiple-names? 129 | (loop for name in name/s collect 130 | `(defmethod bind-generate-bindings ((kind (eql ,name)) variable-form value-form) 131 | (,main-method-name variable-form value-form)))))))) 132 | 133 | (defun next-value (x) 134 | (gensym x)) 135 | 136 | (defmacro lambda-bind ((&rest instrs) &body body) 137 | "Use `bind' to allow restructuring of argument to lambda expressions. 138 | 139 | This lets you funcall and destructure simultaneously. For example 140 | 141 | (let ((fn (lambda-bind ((a b) c) (cons a c)))) 142 | (funcall fn '(1 2) 3)) 143 | ;; => (1 . 3) 144 | 145 | Via eschulte (see git://gist.github.com/902174.git). 146 | " 147 | #+(or) 148 | (declare (indent 1)) 149 | (let* ((evald-instrs instrs) 150 | (syms (mapcar (lambda (_) 151 | (declare (ignore _)) 152 | (gensym)) 153 | evald-instrs))) 154 | `(lambda ,syms (bind ,(mapcar #'list evald-instrs syms) ,@body)))) 155 | 156 | -------------------------------------------------------------------------------- /unit-tests/test-bind.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang-bind-test) 2 | 3 | (deftestsuite metabang-bind-test () ()) 4 | 5 | (deftestsuite test-bind-fix-nils-destructured (metabang-bind-test) 6 | ()) 7 | 8 | (addtest (test-bind-fix-nils-destructured) 9 | simple-list 10 | (ensure-same (bind-fix-nils-destructured '(a b c)) (values '(a b c) nil) 11 | :test #'equal)) 12 | 13 | (addtest (test-bind-fix-nils-destructured) 14 | simple-list-with-nil 15 | (multiple-value-bind (vars ignores) 16 | (bind-fix-nils-destructured '(a nil c)) 17 | (ensure-same (first vars) 'a) 18 | (ensure-same (third vars) 'c) 19 | (ensure-same (second vars) (first ignores)))) 20 | 21 | (addtest (test-bind-fix-nils-destructured) 22 | simple-list-with-_ 23 | (multiple-value-bind (vars ignores) 24 | (bind-fix-nils-destructured '(a _ c)) 25 | (ensure-same (first vars) 'a) 26 | (ensure-same (third vars) 'c) 27 | (ensure-same (second vars) (first ignores)))) 28 | 29 | (addtest (test-bind-fix-nils-destructured) 30 | simple-list-with-_-2 31 | (multiple-value-bind (vars ignores) 32 | (bind-fix-nils-destructured '(a _ c _ _)) 33 | (ensure-same (first vars) 'a) 34 | (ensure-same (third vars) 'c) 35 | (ensure (member (second vars) ignores)) 36 | (ensure (member (fourth vars) ignores)) 37 | (ensure (member (fifth vars) ignores)))) 38 | 39 | (addtest (test-bind-fix-nils-destructured) 40 | dotted-list 41 | (multiple-value-bind (vars ignores) 42 | (bind-fix-nils-destructured '(a . b)) 43 | (ensure-same (car vars) 'a) 44 | (ensure-same (cdr vars) 'b) 45 | (ensure-same ignores nil))) 46 | 47 | (addtest (test-bind-fix-nils-destructured) 48 | dotted-list-with-nil-1 49 | (multiple-value-bind (vars ignores) 50 | (bind-fix-nils-destructured '(nil . b)) 51 | (ensure-same (car vars) (first ignores)) 52 | (ensure-same (cdr vars) 'b) 53 | (ensure-same (length ignores) 1))) 54 | 55 | (addtest (test-bind-fix-nils-destructured) 56 | keyword-list 57 | (multiple-value-bind (vars ignores) 58 | (bind-fix-nils-destructured '(a b &key (c 1) d (e x y))) 59 | (ensure-same (length vars) 6) 60 | (ensure-same (length ignores) 0) 61 | (ensure-same vars '(a b &key (c 1) d (e x y)) :test #'equal))) 62 | 63 | (addtest (test-bind-fix-nils-destructured) 64 | keyword-list-with-nil-non-keyword 65 | (multiple-value-bind (vars ignores) 66 | (bind-fix-nils-destructured '(nil b &key (c 1) d (e x y))) 67 | (ensure-same (length ignores) 1) 68 | (ensure-same (rest vars) '(b &key (c 1) d (e x y)) :test #'equal) 69 | (ensure-same (first vars) (first ignores)))) 70 | 71 | (addtest (test-bind-fix-nils-destructured) 72 | keyword-list-with-nil-keyword 73 | (multiple-value-bind (vars ignores) 74 | (bind-fix-nils-destructured '(a b &key (c 1) nil (e x y))) 75 | (ensure-same (length ignores) 1) 76 | (ensure-same (subseq vars 0 3) '(a b &key) :test #'equal) 77 | (ensure-same (fifth vars) (first ignores)) 78 | (ensure-same (fourth vars) '(c 1) :test 'equal))) 79 | 80 | (addtest (test-bind-fix-nils-destructured) 81 | keyword-list-with-nil-default 82 | (multiple-value-bind (vars ignores) 83 | (bind-fix-nils-destructured '(a b &key (c nil c?))) 84 | (ensure-same (length ignores) 0) 85 | (ensure-same (subseq vars 0 3) '(a b &key) :test #'equal) 86 | (ensure-same (fourth vars) '(c nil c?) :test 'equal))) 87 | 88 | #+Ignore 89 | ;;?? not yet 90 | (addtest (test-bind-fix-nils-destructured) 91 | keyword-list-with-bad-nil-keyword-syntax 92 | (ensure-condition 'bind-keyword/optional-nil-with-default-error 93 | (bind-fix-nils-destructured '(a b &key (nil 1) d (e x y))))) 94 | 95 | (addtest (test-bind-fix-nils-destructured) 96 | keyword-list-with-allow-other-keys 97 | (multiple-value-bind (vars ignores) 98 | (bind-fix-nils-destructured '(a b &key (c 1) d (e x y) 99 | &allow-other-keys)) 100 | (ensure-same (length ignores) 0) 101 | (ensure-same vars '(a b &key (c 1) d (e x y) 102 | &allow-other-keys) :test #'equal))) 103 | 104 | ;;;; 105 | 106 | (deftestsuite test-bind-style-warnings (metabang-bind-test) 107 | ()) 108 | 109 | (addtest (test-bind-style-warnings) 110 | missing-value-1 111 | (ensure-condition metabang-bind:bind-missing-value-form-warning 112 | (macroexpand '(bind (((:values a b))) (list a b))))) 113 | 114 | (addtest (test-bind-style-warnings) 115 | missing-value-2 116 | (ensure-no-warning 117 | (macroexpand '(bind (((:values a b) (foo))) (list a b))))) 118 | 119 | (addtest (test-bind-style-warnings) 120 | missing-value-3 121 | (ensure-no-warning 122 | (macroexpand '(bind (a) (list a))))) 123 | 124 | (addtest (test-bind-style-warnings) 125 | missing-value-4 126 | (ensure-no-warning 127 | (macroexpand '(bind ((a nil)) (list a))))) 128 | 129 | (addtest (test-bind-style-warnings) 130 | two-many-value-forms-error 131 | (ensure-cases (form) 132 | '((a b c) 133 | ((:values a b) 1 2 3)) 134 | (ensure-condition metabang-bind:bind-too-many-value-forms-error 135 | (macroexpand `(bind (,form) (list a)))))) 136 | 137 | (addtest (test-bind-style-warnings) 138 | two-many-value-forms-warnings-with-flet 139 | (ensure-no-warning 140 | (macroexpand `(bind (((:flet x (a)) (setf a (* 2 a)) (list a))) (x 2))))) 141 | 142 | ;;;; 143 | 144 | (deftestsuite test-ignore-underscores (metabang-bind-test) 145 | () 146 | (:equality-test (lambda (a b) 147 | (equalp (remove-gensyms a) (remove-gensyms b))))) 148 | 149 | (addtest (test-ignore-underscores) 150 | test-simple-destructuring 151 | (ensure-same 152 | (macroexpand '(bind (((nil a b) (foo))) 153 | (list a b))) 154 | (macroexpand '(bind (((_ a b) (foo))) 155 | (list a b))))) 156 | 157 | (addtest (test-ignore-underscores) 158 | test-multiple-values 159 | (ensure-same 160 | (macroexpand '(bind (((:values a nil b) (foo))) 161 | (list a b))) 162 | (macroexpand '(bind (((:values a _ b) (foo))) 163 | (list a b))))) 164 | 165 | (addtest (test-ignore-underscores) 166 | test-array 167 | (ensure-same 168 | (macroexpand '(bind ((#(a nil b) (foo))) 169 | (list a b))) 170 | (macroexpand '(bind ((#(a _ b) (foo))) 171 | (list a b))))) 172 | 173 | ;;; 174 | 175 | (deftestsuite test-for-unused-declarations (metabang-bind-test) 176 | ()) 177 | 178 | (addtest (test-for-unused-declarations) 179 | test-error-1 180 | (let ((bind:*unused-declarations-behavior* :error)) 181 | (ensure-condition bind::bind-unused-declarations-error 182 | (eval '(bind:bind ((a 2) (b 3)) 183 | (declare (type fixnum a b c) (optimize (speed 3))) 184 | a b))))) 185 | 186 | (addtest (test-for-unused-declarations) 187 | test-error-2 188 | (let ((bind:*unused-declarations-behavior* :error)) 189 | (ensure-condition bind:bind-unused-declarations-error 190 | (eval '(bind:bind (((:values _ b ) (values 1 2))) 191 | (declare (type fixnum b) (ignorable b) 192 | (simple-vector d) (optimize (speed 3))) 193 | b))))) 194 | 195 | (addtest (test-for-unused-declarations) 196 | test-warning-1 197 | (let ((bind:*unused-declarations-behavior* :warn)) 198 | (ensure-condition bind::bind-unused-declarations-warning 199 | (eval '(bind:bind ((a 2) (b 3)) 200 | (declare (type fixnum a b c) (optimize (speed 3))) 201 | a b))))) 202 | 203 | (addtest (test-for-unused-declarations) 204 | test-warning-2 205 | (let ((bind:*unused-declarations-behavior* :warn)) 206 | (ensure-condition bind::bind-unused-declarations-warning 207 | (eval '(bind:bind (((:values _ b ) (values 1 2))) 208 | (declare (type fixnum b) (ignorable b) 209 | (simple-vector d) (optimize (speed 3))) 210 | b))))) 211 | 212 | (addtest (test-for-unused-declarations) 213 | test-no-warning-1 214 | (let ((bind:*unused-declarations-behavior* nil)) 215 | (ensure-no-warning 216 | (eval '(bind:bind (((:values _ b ) (values 1 2))) 217 | (declare (type fixnum b) (ignorable b) 218 | (simple-vector d) (optimize (speed 3))) 219 | b))))) 220 | 221 | ;;; 222 | 223 | #| 224 | 225 | (defun x (a b) 226 | (declare (fixnum a b)) 227 | (+ a b)) 228 | 229 | (defun x (c) 230 | (bind (((:structure/rw c- a b) c)) 231 | (declare (fixnum a b)) 232 | (declare (optimize (speed 3) (safety 0))) 233 | (+ a b))) 234 | 235 | (disassemble 'x) 236 | 237 | (bind (((:structure/rw foo- a b c) (bar))) 238 | (declare (type fixnum a) (double b)) 239 | (declare (optimize (speed 3))) 240 | ) 241 | 242 | |# 243 | -------------------------------------------------------------------------------- /website/source/user-guide.mmd: -------------------------------------------------------------------------------- 1 | {comment http://metabang.com/unclogit/?p=154} 2 | 3 | {include resources/guide-header.md} 4 | {set-property title "metabang-bind user guide"} 5 | 6 | {set-property html yes} 7 | {set-property title "metabang-bind | Guide to the perplexed"} 8 | {set-property style-sheet user-guide} 9 | {set-property docs-package metabang-bind} 10 | 11 | # metabang-bind user guide 12 | 13 | {table-of-contents :start 2 :depth 3} 14 | 15 | ## Introduction 16 | 17 | `bind` combines _let_, _destructuring-bind_, _multiple-value-bind_ 18 | *and** a whole lot more into a single form. It has two goals: 19 | 20 | 1. reduce the number of nesting levels 21 | 22 | 2. make it easier to understand all of the different forms of 23 | destructuring and variable binding by unifying the multiple forms of 24 | syntax and reducing special cases. 25 | 26 | `bind` is extensible. It handles the traditional multiple-values, 27 | destructuring, and let-forms as well as property-lists, classes, and 28 | structures. Even better, you can create your own binding forms to make 29 | your code cleaner and easier to follow (for others _and_ yourself!). 30 | 31 | Simple bindings are as in _let*_. Destructuring is done if the first 32 | item in a binding is a list. Multiple value binding is done if the 33 | first item in a binding is a list and the first item in the list is 34 | the keyword ':values'. 35 | 36 | 37 | {remark 38 | ### Summary 39 | 40 | 41 | 42 | 43 | 44 | 45 |
symbol`x` or `(x 45)``let`array`(#(a b) #(4 5))``let`
46 | } 47 | 48 | ## Some examples 49 | 50 | Bind mimics let in its general syntax: 51 | 52 | (bind (&rest bindings) ) 53 | 54 | where each `binding` can either be an symbol or a list. If the binding is an atom, then this atom will be bound to nil within the body (just as in let). If it is a list, then it will be interpreted depending on its first form. 55 | 56 | (bind (a 57 | (...)) 58 | ...) 59 | 60 | ### Bind as a replacement for let 61 | 62 | You can use `bind` as a direct replacement for `let*`: 63 | 64 | (bind ((a 2) b) 65 | (list a b)) 66 | => (2 nil) 67 | 68 | As in `let*`, atoms are initially bound to `nil`. 69 | 70 | ### Bind with multiple-values and destructuring 71 | 72 | Suppose we define two silly functions: 73 | 74 | (defun return-values (x y) 75 | (values x y)) 76 | 77 | (defun return-list (x y) 78 | (list x y)) 79 | 80 | How could we use bind for these: 81 | 82 | (bind (((:values a b) (return-values 1 2)) 83 | ((c d) (return-list 3 4))) 84 | (list a b c d)) 85 | => (1 2 3 4) 86 | 87 | Note that `bind` makes it a little easier to ignore variables you don't care about. 88 | Suppose I've got a function `ijara` that returns 3 values and I happen to need only 89 | the second two. Using `destructuring-bind`, I'd write: 90 | 91 | (destructuring-bind (foo value-1 value-2) 92 | (ijira) 93 | (declare (ignore foo)) 94 | ...) 95 | 96 | With `bind`, you use `nil` or `_` in place of a variable name and it will make up 97 | temporary variables names and add the necessary declarations for you. 98 | 99 | (bind (((_ value-1 value-2) (ijira))) 100 | ...) 101 | 102 | 103 | {anchor property-list-bindings} 104 | 105 | ### Bind with property lists 106 | 107 | A property-list or `plist` is a list of alternating keywords and values. Each keyword specifies a property name; each value specifies the value of that name. 108 | 109 | (setf plist 110 | '(:start 368421722 :end 368494926 :flavor :lemon 111 | :content :ragged) 112 | 113 | You can use `getf` to find the current value of a property in a list (and `setf` to change them). The optional third argument to `getf` is used to specify a default value in case the list doesn't have a binding for the requested property already. 114 | 115 | (let ((start (getf plist :start 0)) 116 | (end (getf plist :end)) 117 | (fuzz (getf plist :fuzziness 'no))) 118 | (list start end fuzz)) 119 | => (368421722 368494926 no) 120 | 121 | The binding form for property-lists is as follows: 122 | 123 | (:plist property-spec*) 124 | 125 | where each property-spec is an atom or a list of up to three elements: 126 | 127 | * atoms bind a variable with that name to 128 | a property with the same name (converting the name to a keyword in order to do the lookup). 129 | 130 | * lists with a single element are treated like atoms. 131 | 132 | * lists with two elements 133 | specify the variable in the first and the name of the 134 | property in the second. 135 | 136 | * Lists with three elements use 137 | the third element to specify a default value (if the 138 | second element is #\_, then the property name is taken 139 | to be the same as the variable name). 140 | 141 | Putting this altogether we can code the above let statement as: 142 | 143 | (bind (((:plist (start _ 0) end (fuzz fuzziness 'no)) 144 | plist)) 145 | => (list start end fuzz)) 146 | 147 | (which takes some getting used to but has the advantage of brevity). 148 | 149 | {anchor structure-bindings} 150 | 151 | ### Bind with structures 152 | 153 | Structure fields are accessed using a concatenation of the structure's 154 | `conc-name` and the name of the field. Bind therefore needs to know 155 | two things: the conc-name and the field-names. The binding-form looks 156 | like 157 | 158 | (:structure structure-spec*) 159 | 160 | where each `structure-spec` is an atom or list with two elements: 161 | 162 | * an atom specifies both the name of the variable to which the structure field is bound and the field-name in the structure. 163 | 164 | * a list has the variable name as its first item and the structure field name as its second. 165 | 166 | So if we have a structure like: 167 | 168 | (defstruct minimal-trout 169 | a b c) 170 | 171 | (setf trout (make-minimal-trout :a 2 :b 3 :c 'yes)) 172 | 173 | We can bind these fields using: 174 | 175 | (bind (((:structure minimal-trout- (my-name a) b c) 176 | trout)) 177 | (list my-name b c)) 178 | => (2 3 yes) 179 | 180 | {anchor class-bindings} 181 | 182 | ### Bind with classes 183 | 184 | You can read the slot of an instance with an accessor (if one exists) or by using slot-value{footnote Note that if an accessor exists, it will generally be much faster than slot-value because CLOS is able to cache information about the accessor and the instance.}. Bind also provides two slot-binding mechanisms: `:slots` and `:accessors`. Both look the same: 185 | 186 | (:slots slot-spec*) 187 | (:accessors accessor-spec*) 188 | 189 | Where both slot-spec and accessor-spec can be atoms or lists with two elements. 190 | 191 | * an atom tells bind to use it as the name of the new variable _and_ to treat this name as the name of the slot or the name of the accessor, respectively. 192 | 193 | * If the specification is a list, then bind will use the first item as the variable's name and the second item as the slot-name or accessor. 194 | 195 | Support we had a class like: 196 | 197 | (defclass wicked-cool-class () 198 | ((a :initarg :a :accessor its-a) 199 | (b :initarg :b :accessor b) 200 | (c :initarg :c :accessor just-c))) 201 | 202 | If we don't mind using the slot-names as variable names, then we can use the simplest form of `:slots`: 203 | 204 | (bind (((:slots a b c) 205 | (make-instance 'wicked-cool-class 206 | :a 1 :b 2 :c 3))) 207 | (list a b c)) 208 | ==> (1 2 3) 209 | 210 | We can also change the names within the context of our bind form: 211 | 212 | (bind (((:slots a b (dance-count c)) 213 | (make-instance 'wicked-cool-class 214 | :a 1 :b 2 :c 3))) 215 | (list a b dance-count)) 216 | ==> (1 2 3) 217 | 218 | Similarly, we can use `:accessors` with variable names that are the same as the accessor names... 219 | 220 | (bind (((:accessors its-a b just-c) 221 | (make-instance 'wicked-cool-class 222 | :a 1 :b 2 :c 3))) 223 | (list its-a b just-c)) 224 | ==> (1 2 3) 225 | 226 | or that are different: 227 | 228 | (bind (((:accessors (a its-a) b (c just-c)) 229 | (make-instance 'wicked-cool-class 230 | :a 1 :b 2 :c 3))) 231 | (list a b c)) 232 | ==> (1 2 3) 233 | 234 | {anchor array-bindings} 235 | 236 | ### Bind with arrays 237 | 238 | Tamas Papp had the idea of letting `bind` handle arrays too. For example, 239 | 240 | (bind ((#(a b c) #(1 2 3))) 241 | (list a b c)) 242 | ==> (1 2 3) 243 | 244 | One quick method definition and a few unit-tests later and bind does! 245 | 246 | ### Bind with regular expressions 247 | 248 | If you have CL-PPCRE or run with Allegro Common Lisp, you 249 | can use `bind` with regular expressions too. The syntax is 250 | 251 | (:re expression &rest vars) string) 252 | 253 | and will bind each grouped item in the expression to the 254 | corresponding var. For example: 255 | 256 | (bind (((:re "(\\w+)\\s+(\\w+)\\s+(\\d{1,2})\\.(\\d{1,2})\\.(\\d{4})" 257 | fname lname nil month year) "Frank Zappa 21.12.1940")) 258 | (list fname lname month year)) 259 | 260 | The body of bind form will be evaluated even if the expression 261 | does not match. 262 | 263 | ### Bind with `flet` and `labels` 264 | 265 | Bind can even be used as a replacement for `flet` and `labels`. 266 | The syntax is 267 | 268 | (:flet function-name (arguments*)) definition) 269 | 270 | (:labels function-name (arguments*)) definition) 271 | 272 | for example: 273 | 274 | (bind (((:flet square (x)) (* x x))) 275 | (square 4)) 276 | ==> 16 277 | 278 | (bind (((:labels my-oddp (x)) 279 | (cond ((<= x 0) nil) 280 | ((= x 1) t) 281 | (t (my-oddp (- x 2)))))) 282 | (my-oddp 7)) 283 | ==> t 284 | 285 | Note that bind currently expands each binding-form into a new context. In 286 | particular, this means that 287 | 288 | (bind (((:flet x (a)) (* a 2)) 289 | ((:flet y (b)) (+ b 2))) 290 | ...) 291 | 292 | expands as 293 | 294 | (flet ((x (a) (progn (* a 2)))) 295 | (flet ((y (b) (progn (+ b 2)))) 296 | ...)) 297 | 298 | rather than 299 | 300 | (flet ((x (a) (progn (* a 2))) 301 | (y (b) (progn (+ b 2)))) 302 | ...) 303 | 304 | Generally speaking, this shouldn't make much of a difference. 305 | 306 | ## `bind` and declarations 307 | 308 | `bind` handles declarations transparently by splitting them 309 | up and moving them to the correct place in the expansion. For 310 | example 311 | 312 | (bind (((:values a b) (foo x)) 313 | (#(d e) (bar y))) 314 | (declare (type fixnum a d) 315 | (optimize (speed 3))) 316 | (list a b d e)) 317 | 318 | becomes 319 | 320 | (multiple-value-bind (a b) 321 | (foo x) 322 | (declare (type fixnum a) (optimize (speed 3))) 323 | (let ((#:values-258889 (bar y))) 324 | (let* ((d (row-major-aref #:values-258889 0)) 325 | (e (row-major-aref #:values-258889 1))) 326 | (declare (optimize (speed 3))) 327 | (list a b d e)))) 328 | 329 | because `bind` knows to keep the variable declarations 330 | (like `type`) with their variables and to repeat 331 | other declarations (like `optimize`) at each level. 332 | 333 | `bind` keeps track of variables declarations that are not used. 334 | The configuration variable `*unused-declarations-behavior*` controls 335 | what `bind` does: 336 | 337 | {docs *unused-declarations-behavior*} 338 | 339 | ## More bindings 340 | 341 | Since bind is extensible and I'm fallible, there are probably 342 | things bind can do that haven't made it into this guide. Use 343 | the following commands to see what bind can do: 344 | 345 | {docs binding-forms} 346 | 347 | {docs binding-form-docstring} 348 | 349 | {docs binding-form-groups} 350 | 351 | {docs binding-form-synonyms} 352 | 353 | 354 | ## `lambda-bind` 355 | 356 | Eric Schulte contributed `lambda-bind` (note, he called it 357 | `lambdab` but I dislike abbreviations so...): 358 | 359 | {docs lambda-bind} 360 | 361 | 362 | ## Extending bind yourself 363 | 364 | Bind's syntax is extensible: the work for each 365 | binding-specification is handled by a generic function. This 366 | means that you can evolve bind to fit your program for 367 | whatever sort of data-structure makes sense for you. To make 368 | a binding form, you can either define a method for `bind-generate-bindings` 369 | or you can use the `defbinding-form` macro. 370 | 371 | {docs bind-generate-bindings} 372 | 373 | {docs defbinding-form} 374 | 375 | There are many more examples included in the source code. 376 | 377 | {include resources/guide-footer.md} 378 | -------------------------------------------------------------------------------- /dev/bind.lisp: -------------------------------------------------------------------------------- 1 | ;;;-*- Mode: Lisp; Package: bind -*- 2 | 3 | #| simple-header 4 | 5 | See the file COPYING for details 6 | 7 | |# 8 | 9 | (in-package #:metabang.bind) 10 | 11 | (defconstant +code-marker+ :XXX) 12 | (defconstant +decl-marker+ :YYY) 13 | 14 | (defgeneric binding-form-accepts-multiple-forms-p (binding-form) 15 | (:documentation "Returns true if a binding form can accept multiple forms 16 | (e.g., :flet)")) 17 | 18 | (defmethod binding-form-accepts-multiple-forms-p ((binding-form t)) 19 | nil) 20 | 21 | (defparameter *unused-declarations-behavior* 22 | :print-warning 23 | "Tells bind how to behave when it encounters an unused declaration. 24 | 25 | The possible options are 26 | 27 | * :print-warning (the current default) - print a warning about the problem 28 | and signal a `bind-unused-declarations-condition` 29 | 30 | * :warn - signal a `bind-unused-declarations-warning` warning 31 | 32 | * :error - signal a `bind-unused-declarations-error` error") 33 | 34 | (defparameter *bind-all-declarations* 35 | '(dynamic-extent ignore optimize ftype inline 36 | special ignorable notinline type)) 37 | 38 | (defparameter *bind-non-var-declarations* 39 | '(optimize ftype inline notinline 40 | #+allegro 41 | :explain)) 42 | 43 | (defparameter *bind-simple-var-declarations* 44 | (remove 'type 45 | (set-difference *bind-all-declarations* *bind-non-var-declarations*))) 46 | 47 | (defparameter *bind-lambda-list-markers* 48 | '(&key &body &rest &args &optional)) 49 | 50 | (define-condition simple-style-warning (style-warning simple-warning) 51 | ()) 52 | 53 | (defun simple-style-warning (message &rest args) 54 | (warn 'simple-style-warning :format-control message :format-arguments args)) 55 | 56 | (define-condition bind-missing-value-form-warning (simple-style-warning) 57 | ((variable-form :initform nil 58 | :initarg :variable-form 59 | :reader variable-form)) 60 | (:report (lambda (c s) 61 | (format s "Missing value form for ~s" (variable-form c))))) 62 | 63 | (define-condition bind-too-many-value-forms-error (error) 64 | ((variable-form :initform nil 65 | :initarg :variable-form 66 | :reader variable-form) 67 | (value-form :initform nil 68 | :initarg :value-form 69 | :reader value-form)) 70 | (:report (lambda (c s) 71 | (format s "Two many value forms for ~s" (variable-form c))))) 72 | 73 | (define-condition bind-error (error) 74 | ((binding 75 | :initform nil 76 | :initarg :binding 77 | :reader binding))) 78 | 79 | (define-condition bind-keyword/optional-nil-with-default-error (bind-error) 80 | ((bad-variable 81 | :initform nil 82 | :initarg :bad-variable 83 | :reader bad-variable)) 84 | (:report (lambda (c s) 85 | (format s "Bad binding '~S' in '~A'; cannot use a default value for &key or &optional arguments." 86 | (bad-variable c) (binding c))))) 87 | 88 | (define-condition bind-unused-declarations-condition () 89 | ((unused-declarations :initform (error "must supply unused-declarations") 90 | :initarg :unused-declarations 91 | :reader unused-declarations)) 92 | (:report (lambda (c s) 93 | (format s "Unused declarations in bind: ~{~s~^, ~}" (unused-declarations c))))) 94 | 95 | (define-condition bind-unused-declarations-warning (bind-unused-declarations-condition 96 | simple-style-warning) 97 | ()) 98 | 99 | (define-condition bind-unused-declarations-error (bind-unused-declarations-condition 100 | error) 101 | ()) 102 | 103 | (defun binding-forms () 104 | "Return a list of all binding-forms that bind supports in alphabetical order." 105 | (let* ((forms (get 'bind :binding-forms))) 106 | (sort (loop for form in forms collect (car form)) 'string-lessp))) 107 | 108 | (defun binding-form-groups () 109 | "Return a list of the available binding-forms grouped into their synonyms." 110 | (let ((binding-forms (get 'bind :binding-forms)) 111 | (canonical-names 112 | (sort 113 | (delete-duplicates 114 | (mapcar #'second (get 'bind :binding-forms))) 115 | #'string-lessp))) 116 | (loop for form in canonical-names collect 117 | (cdr (assoc form binding-forms))))) 118 | 119 | (defun binding-form-synonyms (name) 120 | "Return a list of synonyms for the binding-form `name`. 121 | 122 | For example 123 | 124 | > (binding-form-synonyms :accessors) 125 | (:accessors :writable-accessors) 126 | 127 | " 128 | (let* ((forms (get 'bind :binding-forms)) 129 | (datum (assoc name forms))) 130 | (and datum 131 | (rest datum)))) 132 | 133 | (defvar *all-declarations*) 134 | 135 | (defmacro bind ((&rest bindings) &body body) 136 | "Bind is a replacement for let*, destructuring-bind, multiple-value-bind and more. 137 | 138 | An example is probably the best way to describe its syntax: 139 | 140 | \(bind \(\(a 2\) 141 | \(\(b &rest args &key \(c 2\) &allow-other-keys\) '\(:a :c 5 :d 10 :e 54\)\) 142 | \(\(:values d e\) \(truncate 4.5\)\) 143 | \(\(:structure xxx- slot1 slot2\) \(make-xxx\)\) 144 | \(\(:flet name \(arg1 arg2\)\) \(+ arg1 arg2\)\)\) 145 | \(list a b c d e args\)\) 146 | 147 | Simple bindings are as in let*. Destructuring is done if the first item 148 | in a binding is a list. Multiple value binding is done if the first item 149 | in a binding is a list and the first item in the list is ':values'. Other 150 | forms have their own syntax. For example, :structure first has the conc 151 | name and then slot names whereas :flet has the function name and a list 152 | of arguments and then the function body (in an implicit progn)." 153 | (let (declarations) 154 | (loop while (and (consp (car body)) (eq (caar body) 'declare)) do 155 | (push (first body) declarations) 156 | (setf body (rest body))) 157 | (if bindings 158 | (let ((*all-declarations* (bind-expand-declarations (nreverse declarations)))) 159 | (prog1 160 | (bind-macro-helper bindings body) 161 | (check-for-unused-variable-declarations *all-declarations*))) 162 | `(locally 163 | ,@declarations 164 | ,@body)))) 165 | 166 | (defun check-for-unused-variable-declarations (declarations) 167 | (when declarations 168 | (case *unused-declarations-behavior* 169 | (:warn 170 | (warn 'bind-unused-declarations-warning :unused-declarations declarations)) 171 | (:error 172 | (error 'bind-unused-declarations-error :unused-declarations declarations)) 173 | (t 174 | (format *error-output* "~&;;; warning: unused declarations found in form: ~{~s~^, ~}." 175 | declarations) 176 | (signal 'bind-unused-declarations-condition :unused-declarations declarations))))) 177 | 178 | (defun bind-macro-helper (bindings body) 179 | (if bindings 180 | (let ((binding (first bindings)) 181 | (remaining-bindings (rest bindings)) 182 | variable-form value-form atomp binding-form) 183 | (if (consp binding) 184 | (setf variable-form (first binding) 185 | value-form (rest binding) ;; (second binding) 186 | atomp (if (consp variable-form) nil (null value-form))) 187 | (setf variable-form binding 188 | atomp t)) 189 | (unless (or atomp value-form) 190 | (warn 'bind-missing-value-form-warning :variable-form variable-form)) 191 | (setf binding-form (and (consp variable-form) 192 | (and (symbolp (first variable-form)) 193 | (eq (symbol-package (first variable-form)) 194 | (load-time-value (find-package :keyword))) 195 | (first variable-form)))) 196 | (when (and (consp value-form) 197 | (cdr value-form) 198 | (or (null binding-form) 199 | (not (binding-form-accepts-multiple-forms-p binding-form)))) 200 | (error 'bind-too-many-value-forms-error 201 | :variable-form variable-form :value-form value-form)) 202 | (let* ((body (bind-macro-helper remaining-bindings body)) 203 | (variables (if binding-form (rest variable-form) variable-form)) 204 | (decls (bind-filter-declarations variables))) 205 | (multiple-value-bind (form double-indent) 206 | (if binding-form 207 | ;; e.g., (:values ...) 208 | (bind-generate-bindings (first variable-form) (rest variable-form) value-form) 209 | ;; e.g., #(a b c) 210 | (bind-generate-bindings variable-form variable-form value-form)) 211 | (cond ((or (tree-find form +code-marker+) 212 | (tree-find form +decl-marker+)) 213 | (setf form (subst body +code-marker+ form)) 214 | (setf form (subst decls +decl-marker+ form))) 215 | (double-indent 216 | `(,@(butlast form) (,@(first (last form)) ,@decls ,body))) 217 | ((merge-binding-forms-p form body) 218 | (destructuring-bind (head1 form1-bindings . form1-code) 219 | form 220 | (destructuring-bind (_ form2-bindings . form2-code) 221 | body 222 | (declare (ignore _)) 223 | `(,head1 (,@form1-bindings ,@form2-bindings) 224 | ,@decls 225 | ,@form1-code 226 | ,@form2-code)))) 227 | (t 228 | `(,@form ,@decls ,body)))))) 229 | `(progn ,@body))) 230 | 231 | ;;;; 232 | 233 | (defun var-ignorable-p (var) 234 | (or (null var) 235 | (and (symbolp var) (string= (symbol-name var) (symbol-name '_))))) 236 | 237 | (defun mint-ignorable-variable () 238 | (gensym (symbol-name '#:bind-ignore-))) 239 | 240 | (defun bind-fix-nils (var-list) 241 | (let (vars ignores) 242 | (loop for v in var-list do 243 | (cond ((var-ignorable-p v) 244 | (let ((ignore (mint-ignorable-variable))) 245 | (push ignore vars) 246 | (push ignore ignores))) 247 | (t (push v vars)))) 248 | (values (nreverse vars) ignores))) 249 | 250 | (defun bind-fix-nils-destructured (var-list) 251 | (let ((ignores nil)) 252 | (flet ((maybe-handle-1 (x) 253 | (if (var-ignorable-p x) 254 | (let ((ignore (mint-ignorable-variable))) 255 | (push ignore ignores) 256 | ignore) 257 | x))) 258 | (labels ((do-it (it key?) 259 | (cond ((null it) 260 | nil) 261 | ((atom it) 262 | (maybe-handle-1 it)) 263 | ((dotted-pair-p it) 264 | (cons (do-it (car it) key?) (do-it (cdr it) key?))) 265 | ((eq (first it) '&key) 266 | (loop for x in it collect (do-it x t))) 267 | (key? 268 | it) 269 | (t 270 | (cons (do-it (car it) key?) 271 | (do-it (cdr it) key?)))))) 272 | (values (do-it var-list nil) ignores))))) 273 | 274 | (defun dotted-pair-p (putative-pair) 275 | "Returns true if and only if `putative-pair` is a dotted-list. I.e., if `putative-pair` is a cons cell with a non-nil cdr." 276 | (and (consp putative-pair) 277 | (cdr putative-pair) 278 | (not (consp (cdr putative-pair))))) 279 | 280 | (defmethod bind-collect-variables (kind variable-form) 281 | (declare (ignore kind)) 282 | variable-form) 283 | 284 | (defun bind-get-vars-from-lambda-list (lambda-list) 285 | (let ((result nil)) 286 | (labels ((do-it (thing) 287 | (cond ((arrayp thing) 288 | (loop for i below (array-total-size thing) 289 | for var = (row-major-aref thing i) do (do-it var))) 290 | ((atom thing) 291 | (unless (or (member thing *bind-lambda-list-markers*) 292 | (var-ignorable-p thing)) 293 | (push thing result))) 294 | ((dotted-pair-p thing) 295 | (do-it (car thing)) 296 | (do-it (cdr thing))) 297 | (t 298 | (do-it (car thing)) 299 | (do-it (cdr thing)))))) 300 | (do-it lambda-list)) 301 | (nreverse result))) 302 | 303 | (defun bind-expand-declarations (declarations) 304 | (loop for declaration in declarations append 305 | (loop for decl in (rest declaration) append 306 | (cond ((member (first decl) *bind-non-var-declarations*) 307 | (list decl)) 308 | ((member (first decl) *bind-simple-var-declarations*) 309 | (loop for var in (rest decl) collect 310 | `(,(first decl) ,var))) 311 | (t 312 | ;; a type spec 313 | (when (eq (first decl) 'type) 314 | (setf decl (rest decl))) 315 | (loop for var in (rest decl) collect 316 | `(type ,(first decl) ,var))))))) 317 | 318 | (defun bind-filter-declarations (var-names) 319 | (setf var-names (if (consp var-names) var-names (list var-names))) 320 | (setf var-names (bind-get-vars-from-lambda-list var-names)) 321 | ;; each declaration is separate 322 | (let ((declaration 323 | (loop for declaration in *all-declarations* 324 | when (or (member (first declaration) 325 | *bind-non-var-declarations*) 326 | (and (member (first declaration) 327 | *bind-simple-var-declarations*) 328 | (member 329 | (if (atom (second declaration)) 330 | (second declaration) 331 | ;; ... (function foo) ...) 332 | (second (second declaration))) 333 | var-names)) 334 | ;; type 335 | (member (third declaration) var-names)) collect 336 | (progn 337 | (setf *all-declarations* (remove declaration *all-declarations*)) 338 | declaration)))) 339 | (when declaration 340 | `((declare ,@declaration))))) 341 | 342 | (defun merge-binding-forms-p (form1 form2) 343 | (and (consp form1) (consp form2) 344 | (let ((tag1 (first form1)) 345 | (tag2 (first form2))) 346 | (and (symbolp tag1) 347 | (symbolp tag2) 348 | (string-equal (symbol-name tag1) (symbol-name tag2)) 349 | (or (string-equal (symbol-name tag1) "let") 350 | (string-equal (symbol-name tag1) "let*") 351 | (string-equal (symbol-name tag1) "labels")))))) 352 | 353 | (defun map-tree (fn object) 354 | "apply `fn` to every leaf of `object`." 355 | (cond ((consp object) 356 | (map-tree fn (car object)) 357 | (map-tree fn (cdr object))) 358 | (object 359 | (funcall fn object)))) 360 | 361 | (defun tree-find (tree it &key (test #'eq) (key #'identity)) 362 | (flet ((isit (atom) 363 | (when key (setf atom (funcall key atom))) 364 | (when (funcall test it atom) (return-from tree-find t)))) 365 | (declare (dynamic-extent #'isit)) 366 | (map-tree #'isit tree))) 367 | 368 | ;;; fluid-bind 369 | 370 | (defmacro fluid-bind ((&rest bindings) &body body) 371 | "Fluid-bind is an extension of bind that handles setting and resetting places. For example, suppose that an object of class foo has a slot named bar whose value is currently 3. The following code would evaluate the inner body with bar bound to 17 and restore it when the inner body is exited. 372 | 373 | \(fluid-bind \(\(\(bar foo\) 17\)\) 374 | \(print \(bar foo\)\)\) 375 | \(print \(bar foo\)\) 376 | ==> \(prints 17, then 3\) 377 | 378 | This is similar to dynamic-binding but _much_ less robust." 379 | ;; does not handle declarations correctly 380 | (let ((setup-forms nil) 381 | (cleanup-forms nil) 382 | (gensyms nil)) 383 | (loop for binding in bindings collect 384 | (destructuring-bind 385 | (setup-form cleanup-form) 386 | (cond ((consp binding) 387 | (destructuring-bind (var value) binding 388 | (let ((g (gensym))) 389 | (push g gensyms) 390 | (cond ((atom var) 391 | `((:bind (,var ,value)) nil) 392 | #+(or) 393 | ;; lexical or special? 394 | (if (boundp var) 395 | `((:bind (,var ,value)) nil) 396 | `((:setf (setf ,g ,var ,var ,value)) 397 | (setf ,var ,g)))) 398 | ((and (fboundp (first var)) 399 | (not (eq (first var) 'values))) 400 | ;; putative place 401 | `((:setf (setf ,g ,var ,var ,value)) 402 | (setf ,var ,g))) 403 | (t 404 | `((:bind (,var ,value)) nil)))))) 405 | (t 406 | `((:bind (,binding nil)) nil))) 407 | (push setup-form setup-forms) 408 | (push cleanup-form cleanup-forms))) 409 | (let ((result body)) 410 | (mapc (lambda (setup cleanup) 411 | (setf result 412 | (ecase (first setup) 413 | (:setf `((unwind-protect 414 | (progn 415 | ,(second setup) 416 | ,@result) 417 | ,cleanup))) 418 | (:bind `((bind (,(second setup)) 419 | ,@result))))) 420 | result) 421 | setup-forms cleanup-forms) 422 | `(let ,gensyms 423 | (declare (ignorable ,@gensyms)) 424 | ,@result)))) 425 | 426 | #| 427 | (let ((a 2)) 428 | (fluid-bind ((a 3)) 429 | (print a)) 430 | (print a)) 431 | 432 | (fluid-bind (((population (current-world-state)) t)) 433 | (print (population (current-world-state)))) 434 | 435 | (fluid-bind ((a 3) 436 | (*last-world* t) 437 | (*foo* nil)) 438 | (declare (fixnum a)) 439 | (print (list *last-world* *foo* a)) 440 | (error "Ouch")) 441 | 442 | (defvar *foo* 3) 443 | 444 | (unwind-protect 445 | (bind ((#:g1 *last-world*)) 446 | (setf *last-world* t) 447 | (unwind-protect 448 | (bind ((#:2 *foo*)) 449 | (setf *foo* nil) 450 | (bind ((a 3)) 451 | (list *last-world* *foo* a))) 452 | (setf *foo #:2))) 453 | (set *last-world* #:g1)) 454 | 455 | (fluid-bind (a b) 456 | (+ a a)) 457 | |# 458 | 459 | 460 | -------------------------------------------------------------------------------- /dev/binding-forms.lisp: -------------------------------------------------------------------------------- 1 | (in-package #:metabang.bind) 2 | 3 | (defgeneric bind-generate-bindings (kind variable-form value-form) 4 | (:documentation "Handle the expansion for a particular binding-form. 5 | 6 | `kind` specifies the binding form. It can be a type (e.g., symbol or array) 7 | or a keyword (e.g., :flet or :plist). `variable-form` and `value-form` are 8 | taken from the binding-form given to `bind`. E.g., if you have a bind like 9 | 10 | (bind (((:values a b c) (foo)) 11 | (x 2)) 12 | (declare (optimize (speed 3)) (type simple-array a)) 13 | ...) 14 | 15 | then `kind` will be :values, `variable-form` will be the list `(a b c)` and 16 | `value-form` will be the expression `(foo)`. `bind-generate-bindings` 17 | uses these variables as data to construct the generated code.")) 18 | 19 | (defbinding-form (array 20 | :use-values-p t) 21 | (let* ((dimensions (array-dimensions variables)) 22 | (array-size (array-total-size variables)) 23 | (accessor (if (cdr dimensions) 'row-major-aref 'aref))) 24 | `(let* (,@(loop for i below array-size 25 | for var = (row-major-aref variables i) 26 | unless (var-ignorable-p var) collect 27 | `(,var (,accessor ,values ,i))))))) 28 | 29 | (defbinding-form (symbol 30 | :use-values-p nil) 31 | (if (keywordp kind) 32 | (error "Don't have a binding form for ~s" kind) 33 | `(let* (,@(if values 34 | `((,variables ,values)) 35 | `(,variables)))))) 36 | 37 | (defbinding-form (:flet 38 | :docstring "Local functions are defined using 39 | 40 | \(:flet \(\) \) 41 | 42 | When the function definition occurs in a progn. For example: 43 | 44 | \(bind \(\(\(:flet double-list \(x\)\) \(setf x \(* 2 x\)\) \(list x x\)\)\) 45 | \(double-list 45\)\) 46 | ==> (90 90) 47 | 48 | " 49 | :use-values-p nil 50 | :accept-multiple-forms-p t) 51 | (destructuring-bind (name args) variables 52 | (let* (declaration body docstring) 53 | (when (typep (first values) 'string) 54 | (setf docstring (first values) 55 | values (rest values))) 56 | (when (and (listp (first values)) (eq (caar values) 'declare)) 57 | (setf declaration (first values) 58 | values (rest values))) 59 | (setf body values) 60 | `(flet ((,name ,args 61 | ,@(when docstring `(,docstring)) 62 | ,@(when declaration `(,declaration)) 63 | (progn ,@body))))))) 64 | 65 | 66 | (defbinding-form ((:dynamic-flet :dflet) 67 | :docstring "Local functions are defined using 68 | 69 | \(:dynamic-flet \(\) \) 70 | 71 | Where the function definition occurs in a progn. For example: 72 | 73 | \(bind \(\(\(:flet double-list \(x\)\) \(setf x \(* 2 x\)\) \(list x x\)\)\) 74 | \(double-list 45\)\) 75 | ==> (90 90) 76 | 77 | The functions are automatically declared dynamic-extent 78 | " 79 | :use-values-p nil 80 | :accept-multiple-forms-p t) 81 | (destructuring-bind (name args) variables 82 | (let* (declaration body docstring) 83 | (when (typep (first values) 'string) 84 | (setf docstring (first values) 85 | values (rest values))) 86 | (when (and (listp (first values)) (eq (caar values) 'declare)) 87 | (setf declaration (first values) 88 | values (rest values))) 89 | (setf body values) 90 | `(flet ((,name ,args 91 | ,@(when docstring `(,docstring)) 92 | ,@(when declaration `(,declaration)) 93 | (progn ,@body))) 94 | (declare (dynamic-extent (function ,name))))))) 95 | 96 | 97 | (defbinding-form (:labels 98 | :docstring "Local functions are defined using 99 | 100 | \(:labels \(\) \) 101 | 102 | When the function definition occurs in a progn. For example: 103 | 104 | \(bind \(\(\(:flet double-list \(x\)\) \(setf x \(* 2 x\)\) \(list x x\)\)\) 105 | \(double-list 45\)\) 106 | ==> (90 90) 107 | 108 | " 109 | :use-values-p nil 110 | :accept-multiple-forms-p t) 111 | (destructuring-bind (name args) variables 112 | (let* (declaration body docstring) 113 | (when (typep (first values) 'string) 114 | (setf docstring (first values) 115 | values (rest values))) 116 | (when (and (listp (first values)) (eq (caar values) 'declare)) 117 | (setf declaration (first values) 118 | values (rest values))) 119 | (setf body values) 120 | `(labels ((,name ,args 121 | ,@(when docstring `(,docstring)) 122 | ,@(when declaration `(,declaration)) 123 | (progn ,@body))))))) 124 | 125 | 126 | (defbinding-form ((:dynamic-labels :flabels) 127 | :docstring "Local functions are defined using 128 | 129 | \(:dynamic-labels \(\) \) 130 | 131 | When the function definition occurs in a progn. For example: 132 | 133 | \(bind \(\(\(:flet double-list \(x\)\) \(setf x \(* 2 x\)\) \(list x x\)\)\) 134 | \(double-list 45\)\) 135 | ==> (90 90) 136 | 137 | The functions are automatically declared dynamic-extent 138 | 139 | " 140 | :use-values-p nil 141 | :accept-multiple-forms-p t) 142 | (destructuring-bind (name args) variables 143 | (let* (declaration body docstring) 144 | (when (typep (first values) 'string) 145 | (setf docstring (first values) 146 | values (rest values))) 147 | (when (and (listp (first values)) (eq (caar values) 'declare)) 148 | (setf declaration (first values) 149 | values (rest values))) 150 | (setf body values) 151 | `(labels ((,name ,args 152 | ,@(when docstring `(,docstring)) 153 | ,@(when declaration `(,declaration)) 154 | (progn ,@body))) 155 | (declare (dynamic-extent (function ,name))))))) 156 | 157 | 158 | (defbinding-form (cons 159 | :use-values-p nil) 160 | (multiple-value-bind (vars ignores) 161 | (bind-fix-nils-destructured variables) 162 | `(destructuring-bind ,vars ,values 163 | ,@(when ignores `((declare (ignore ,@ignores))))))) 164 | 165 | (defbinding-form ((:values :mv-bind :multiple-value-bind) 166 | :docstring "Expands into a multiple-value-bind" 167 | :use-values-p nil) 168 | (multiple-value-bind (vars ignores) 169 | (bind-fix-nils variables) 170 | `(multiple-value-bind ,vars ,values 171 | ,@(when ignores `((declare (ignore ,@ignores))))))) 172 | 173 | (defbinding-form ((:struct :structure) 174 | :docstring 175 | "Structure fields are accessed using a concatenation 176 | of the structure's `conc-name` and the name of the field. Bind 177 | therefore needs to know two things: the conc-name and the 178 | field-names. The binding-form looks like 179 | 180 | (:structure structure-spec*) 181 | 182 | where each `structure-spec` is an atom or list with two elements: 183 | 184 | * an atom specifies both the name of the variable to which the 185 | structure field is bound and the field-name in the structure. 186 | 187 | * a list has the variable name as its first item and the structure 188 | field name as its second. 189 | ") 190 | (let ((conc-name (first variables)) 191 | (vars (rest variables))) 192 | (assert conc-name) 193 | (assert vars) 194 | `(let* ,(loop for var in vars collect 195 | (let ((var-var (or (and (consp var) (first var)) 196 | var)) 197 | (var-conc (or (and (consp var) (second var)) 198 | var))) 199 | `(,var-var (,(let ((*package* (symbol-package conc-name)) *read-eval*) 200 | (read-from-string (format nil "~a~a" conc-name var-conc))) 201 | ,values))))))) 202 | 203 | (defbinding-form ((:structure/rw) 204 | :docstring 205 | "Structure fields are accessed using a concatenation 206 | of the structure's `conc-name` and the name of the field. Bind 207 | therefore needs to know two things: the conc-name and the 208 | field-names. The binding-form looks like 209 | 210 | (:structure structure-spec*) 211 | 212 | where each `structure-spec` is an atom or list with two elements: 213 | 214 | * an atom specifies both the name of the variable to which the 215 | structure field is bound and the field-name in the structure. 216 | 217 | * a list has the variable name as its first item and the structure 218 | field name as its second. 219 | 220 | The expansion uses symbol-macrolet to convert variables references to 221 | structure references. Declarations are handled using `the`. 222 | ") 223 | (let ((conc-name (first variables)) 224 | (vars (rest variables))) 225 | (assert conc-name) 226 | (assert vars) 227 | `(symbol-macrolet 228 | ,(loop for var in vars collect 229 | (let* ((var-var (or (and (consp var) (first var)) 230 | var)) 231 | (var-conc (or (and (consp var) (second var)) 232 | var)) 233 | 234 | (var-name (let ((*package* (symbol-package conc-name)) *read-eval*) 235 | (read-from-string (format nil "~a~a" conc-name var-conc)))) 236 | 237 | (type-declaration (find-type-declaration var-var *all-declarations*))) 238 | `(,var-var ,(if type-declaration 239 | `(the ,type-declaration (,var-name ,values)) 240 | `(,var-name ,values)))))))) 241 | 242 | (defun find-type-declaration (var declarations) 243 | ;; declarations looks like ((declare (type fixnum a) (optimize ...) ...) 244 | ;; or ((type fixnum a) ...?) 245 | (let* ((declarations (if (eq (first (first declarations)) 'declare) 246 | (rest (first declarations)) 247 | declarations)) 248 | (result (find-if (lambda (declaration) 249 | (and (eq (first declaration) 'type) 250 | (member var (cddr declaration)))) 251 | declarations))) 252 | (when result 253 | (second result)))) 254 | 255 | #| 256 | (defbinding-form (:function 257 | :docstring "" 258 | :use-values-p nil) 259 | (destructuring-bind (name args) variables 260 | `(labels ((,name ,args (progn ,values)))))) 261 | 262 | (bind (((:function foo (x a)) (list a x)) 263 | ((:function bar (a)) (foo a a))) 264 | (bar 3)) 265 | 266 | (bind (((:function fib (x)) 267 | (cond ((< x 2) 1) 268 | (t (+ (fib (- x 1)) (fib (- x 2))))))) 269 | (fib 5)) 270 | 271 | 1 1 2 3 5 272 | 273 | ;;; fails, need to combine like forms... 274 | (bind (((:function ep (x)) 275 | ;;; failure, need to use rest instead of second in bind-macro-helper 276 | (progn 277 | (print (list :e x)) 278 | (if (= x 0) t (not (op (1- x)))))) 279 | ((:function op (x)) 280 | (progn 281 | (print (list :o x)) 282 | (if (= x 1) t (not (ep (1- x))))))) 283 | (ep 5)) 284 | 285 | (cond ((< x 2) 1) 286 | (t (+ (fib (- x 1)) (fib (- x 2))))))) 287 | (fib 5)) 288 | 289 | |# 290 | 291 | (defbinding-form ((:alist :assoc) 292 | :docstring 293 | "The binding form for association-list is as follows: 294 | 295 | (:alist assoc-spec*) 296 | 297 | where each assoc-spec is an atom or a list of up to three elements: 298 | 299 | * atoms bind a variable with that name to an item with the same name. 300 | 301 | * lists with a single element are treated like atoms. 302 | 303 | * lists with two elements specify the variable in the first and the 304 | name of the accessor in the second. 305 | 306 | * Lists with three elements use the third element to specify a default 307 | value (if the second element is #\_, then the accessor name is taken 308 | to be the same as the variable name). 309 | 310 | Note that the variables are bound to the `cdr` of the item in the list 311 | rather than the `(item . value)` pair.") 312 | `(let* ,(loop for spec in variables collect 313 | (let* ((spec (if (consp spec) spec (list spec))) 314 | (var-name (first spec)) 315 | var-key var-default) 316 | (case (length spec) 317 | (1 (setf var-key (first spec))) 318 | (2 (setf var-key (second spec))) 319 | (3 (setf var-key (second spec) 320 | var-default (third spec))) 321 | (t 322 | (error "bad properly list variable specification: ~s" 323 | spec))) 324 | (when (string= (symbol-name var-key) "_") 325 | (setf var-key var-name)) 326 | `(,var-name (or (cdr (assoc ',var-key ,values)) 327 | ,@(when var-default `(,var-default)))))))) 328 | 329 | ;;;; 330 | 331 | (defbinding-form ((:read-only-slots :slots-read-only :slots-r/o) 332 | :docstring 333 | "The `:read-only-slots` binding form is short hand for the `with-slots` macro except that it provides only read access to the class. 334 | 335 | The syntax is (:read-only-slots slot-spec*) 336 | 337 | Where `slot-spec` can be an atom or a list with two elements. 338 | 339 | * an atom tells bind to use it as the name of the new variable _and_ 340 | to treat this name as the name of the slot. 341 | 342 | * If the specification is a list, then bind will use the first item for 343 | the variable's name and the second item for the slot-name. 344 | 345 | See [slots][slots-binding-spec] for a 346 | variant that provides only read-write access to the class." 347 | ) 348 | `(let* (,@(loop for var in variables collect 349 | (let ((var-var (or (and (consp var) (first var)) 350 | var)) 351 | (var-slot (or (and (consp var) (second var)) 352 | var))) 353 | `(,var-var (slot-value ,values ',var-slot))))))) 354 | 355 | (defbinding-form (:slots 356 | :docstring 357 | "The `:slots` binding form is short hand for the `with-slots` macro. 358 | 359 | The syntax is (:slots slot-spec*) 360 | 361 | Where `slot-spec` can be an atom or a list with two elements. 362 | 363 | * an atom tells bind to use it as the name of the new variable _and_ 364 | to treat this name as the name of the slot. 365 | 366 | * If the specification is a list, then bind will use the first item for 367 | the variable's name and the second item for the slot-name. 368 | 369 | See [read-only-slots][read-only-slots-binding-spec] for a 370 | variant that provides only read-write access to the class." 371 | ) 372 | `(with-slots 373 | (,@(loop for var in variables collect 374 | (let ((var-var (or (and (consp var) (first var)) 375 | var)) 376 | (var-accessor (or (and (consp var) (second var)) 377 | var))) 378 | `(,var-var ,var-accessor)))) 379 | ,values)) 380 | 381 | ;;;; 382 | 383 | (defbinding-form ((:read-only-accessors 384 | :accessors-read-only 385 | :accessors-r/o) 386 | :docstring "The `:read-only-accessors` binding form is short hand for `with-accessors` macro that provides only read access to the class. 387 | 388 | The syntax is (:read-only-accessors accessor-spec*) 389 | 390 | Where `accessor-spec` can be an atom or a list with two elements. 391 | 392 | * an atom tells bind to use it as the name of the new variable _and_ 393 | to treat this name as the name of the accessor. 394 | 395 | * If the specification is a list, then bind will use the first item for 396 | the variable's name and the second item for the accessor name. 397 | 398 | See [accessors][accessors-binding-spec] for a 399 | variant that provides only read-write access to the class." 400 | ) 401 | `(let* ,(loop for var in variables collect 402 | (let ((var-var (or (and (consp var) (first var)) 403 | var)) 404 | (var-accessor (or (and (consp var) (second var)) 405 | var))) 406 | `(,var-var (,var-accessor ,values)))))) 407 | 408 | (defbinding-form ((:accessors :writable-accessors) 409 | :docstring "The `:accessors` binding form is short hand for the `with-accessors` macro. 410 | 411 | The syntax is (:accessors accessor-spec*) 412 | 413 | Where `accessor-spec` can be an atom or a list with two elements. 414 | 415 | * an atom tells bind to use it as the name of the new variable _and_ 416 | to treat this name as the name of the accessor. 417 | 418 | * If the specification is a list, then bind will use the first item for 419 | the variable's name and the second item for the accessor name. 420 | 421 | See [read-only-accessors][read-only-accessors-binding-spec] for a 422 | variant that provides only read-only access to the class." 423 | ) 424 | `(with-accessors 425 | (,@(loop for var in variables collect 426 | (let ((var-var (or (and (consp var) (first var)) 427 | var)) 428 | (var-accessor (or (and (consp var) (second var)) 429 | var))) 430 | `(,var-var ,var-accessor)))) 431 | ,values)) 432 | 433 | (defbinding-form ((:plist :property-list :properties) 434 | :docstring 435 | "The binding form for property-lists is as follows: 436 | 437 | (:plist property-spec*) 438 | 439 | where each property-spec is an atom or a list of up to three elements: 440 | 441 | * atoms bind a variable with that name to 442 | a property with the same name (converting the name to a keyword in order to do the lookup). 443 | 444 | * lists with a single element are treated like atoms. 445 | 446 | * lists with two elements 447 | specify the variable in the first and the name of the 448 | property in the second. 449 | 450 | * Lists with three elements use 451 | the third element to specify a default value (if the 452 | second element is #\_, then the property name is taken 453 | to be the same as the variable name). 454 | 455 | Putting this altogether we can code the above let statement as: 456 | 457 | (setf plist 458 | '(:start 368421722 :end 368494926 :flavor :lemon 459 | :content :ragged)) 460 | 461 | (bind (((:plist (start _ 0) end (fuzz fuzziness 'no)) plist)) 462 | (list start end fuzz)) 463 | ==> (368421722 368494926 no) 464 | 465 | (which takes some getting used to but has the advantage of brevity). 466 | ") 467 | (handle-plist variables values t)) 468 | 469 | (defbinding-form (:plist- 470 | :docstring "The `:plist-` binding-form is exactly like that of [plist][binding-form-plist] except that the name is not converted to a keyword. 471 | 472 | This allows for the case when your property list uses symbols other than 473 | keywords as keys. For example: 474 | 475 | \(bind \(\(\(:plist- a b \(c _ 34\)\) '\(a 5 b 2\)\)\) 476 | \(list a b c\)\) 477 | ==> \(5 2 34\) 478 | 479 | " 480 | ) 481 | (handle-plist variables values nil)) 482 | 483 | (defun handle-plist (variables values form-keywords?) 484 | `(let* ,(loop for spec in variables collect 485 | (let* ((spec (if (consp spec) spec (list spec))) 486 | (var-name (first spec)) 487 | var-key var-default) 488 | (case (length spec) 489 | (1 (setf var-key (first spec))) 490 | (2 (setf var-key (second spec))) 491 | (3 (setf var-key (second spec) 492 | var-default (third spec))) 493 | (t 494 | (error "bad properly list variable specification: ~s" 495 | spec))) 496 | (when (string= (symbol-name var-key) "_") 497 | (setf var-key var-name)) 498 | (when form-keywords? 499 | (setf var-key (intern (symbol-name var-key) (find-package :keyword)))) 500 | `(,var-name (getf ,values 501 | ,(if form-keywords? 502 | var-key `',var-key) 503 | ,@(when var-default 504 | `(,var-default)))))))) 505 | 506 | #+(or) 507 | (bind (((:plist a (b _) (c _ 2) (dd d)) '(:b "B" :a "A" :d "D"))) 508 | (list a b c dd)) 509 | 510 | #+(or) 511 | (bind (((:plist- a (b _) (c _ 2) (dd d)) '(b "B" a "A" d "D"))) 512 | (list a b c dd)) 513 | 514 | (defbinding-form (:file :use-values-p nil 515 | :accept-multiple-forms-p t) 516 | "The binding form for a file is as follows: 517 | 518 | ((:file stream-var) file-name | (file-name arguments*)) 519 | 520 | E.g., 521 | 522 | (bind (((:file s) (\"/tmp/foo.tmp\" :direction :output :if-does-not-exist :create))) 523 | ...) 524 | 525 | " 526 | ;; thanks to https://github.com/hyotang666 for the idea and initial code! 527 | `(with-open-file ,(append variables (if (null (cdr values)) values (car values))))) 528 | --------------------------------------------------------------------------------