226 lines
8.5 KiB
Common Lisp
226 lines
8.5 KiB
Common Lisp
;;;; -*- Mode: Lisp; indent-tabs-mode: nil -*-
|
|
|
|
(in-package :it.bese.fiveam)
|
|
|
|
(defmacro dolist* ((iterator list &optional return-value) &body body)
|
|
"Like DOLIST but destructuring-binds the elements of LIST.
|
|
|
|
If ITERATOR is a symbol then dolist* is just like dolist EXCEPT
|
|
that it creates a fresh binding."
|
|
(if (listp iterator)
|
|
(let ((i (gensym "DOLIST*-I-")))
|
|
`(dolist (,i ,list ,return-value)
|
|
(destructuring-bind ,iterator ,i
|
|
,@body)))
|
|
`(dolist (,iterator ,list ,return-value)
|
|
(let ((,iterator ,iterator))
|
|
,@body))))
|
|
|
|
(defun make-collector (&optional initial-value)
|
|
"Create a collector function.
|
|
|
|
A Collector function will collect, into a list, all the values
|
|
passed to it in the order in which they were passed. If the
|
|
callector function is called without arguments it returns the
|
|
current list of values."
|
|
(let ((value initial-value)
|
|
(cdr (last initial-value)))
|
|
(lambda (&rest items)
|
|
(if items
|
|
(progn
|
|
(if value
|
|
(if cdr
|
|
(setf (cdr cdr) items
|
|
cdr (last items))
|
|
(setf cdr (last items)))
|
|
(setf value items
|
|
cdr (last items)))
|
|
items)
|
|
value))))
|
|
|
|
(defun partitionx (list &rest lambdas)
|
|
(let ((collectors (mapcar (lambda (l)
|
|
(cons (if (and (symbolp l)
|
|
(member l (list :otherwise t)
|
|
:test #'string=))
|
|
(constantly t)
|
|
l)
|
|
(make-collector)))
|
|
lambdas)))
|
|
(dolist (item list)
|
|
(block item
|
|
(dolist* ((test-func . collector-func) collectors)
|
|
(when (funcall test-func item)
|
|
(funcall collector-func item)
|
|
(return-from item)))))
|
|
(mapcar #'funcall (mapcar #'cdr collectors))))
|
|
|
|
;;;; ** Anaphoric conditionals
|
|
|
|
(defmacro if-bind (var test &body then/else)
|
|
"Anaphoric IF control structure.
|
|
|
|
VAR (a symbol) will be bound to the primary value of TEST. If
|
|
TEST returns a true value then THEN will be executed, otherwise
|
|
ELSE will be executed."
|
|
(assert (first then/else)
|
|
(then/else)
|
|
"IF-BIND missing THEN clause.")
|
|
(destructuring-bind (then &optional else)
|
|
then/else
|
|
`(let ((,var ,test))
|
|
(if ,var ,then ,else))))
|
|
|
|
(defmacro aif (test then &optional else)
|
|
"Just like IF-BIND but the var is always IT."
|
|
`(if-bind it ,test ,then ,else))
|
|
|
|
;;;; ** Simple list matching based on code from Paul Graham's On Lisp.
|
|
|
|
(defmacro acond2 (&rest clauses)
|
|
(if (null clauses)
|
|
nil
|
|
(with-gensyms (val foundp)
|
|
(destructuring-bind ((test &rest progn) &rest others)
|
|
clauses
|
|
`(multiple-value-bind (,val ,foundp)
|
|
,test
|
|
(if (or ,val ,foundp)
|
|
(let ((it ,val))
|
|
(declare (ignorable it))
|
|
,@progn)
|
|
(acond2 ,@others)))))))
|
|
|
|
(defun varsymp (x)
|
|
(and (symbolp x)
|
|
(let ((name (symbol-name x)))
|
|
(and (>= (length name) 2)
|
|
(char= (char name 0) #\?)))))
|
|
|
|
(defun binding (x binds)
|
|
(labels ((recbind (x binds)
|
|
(aif (assoc x binds)
|
|
(or (recbind (cdr it) binds)
|
|
it))))
|
|
(let ((b (recbind x binds)))
|
|
(values (cdr b) b))))
|
|
|
|
(defun list-match (x y &optional binds)
|
|
(acond2
|
|
((or (eql x y) (eql x '_) (eql y '_))
|
|
(values binds t))
|
|
((binding x binds) (list-match it y binds))
|
|
((binding y binds) (list-match x it binds))
|
|
((varsymp x) (values (cons (cons x y) binds) t))
|
|
((varsymp y) (values (cons (cons y x) binds) t))
|
|
((and (consp x) (consp y) (list-match (car x) (car y) binds))
|
|
(list-match (cdr x) (cdr y) it))
|
|
(t (values nil nil))))
|
|
|
|
(defun vars (match-spec)
|
|
(let ((vars nil))
|
|
(labels ((find-vars (spec)
|
|
(cond
|
|
((null spec) nil)
|
|
((varsymp spec) (push spec vars))
|
|
((consp spec)
|
|
(find-vars (car spec))
|
|
(find-vars (cdr spec))))))
|
|
(find-vars match-spec))
|
|
(delete-duplicates vars)))
|
|
|
|
(defmacro list-match-case (target &body clauses)
|
|
(if clauses
|
|
(destructuring-bind ((test &rest progn) &rest others)
|
|
clauses
|
|
(with-gensyms (tgt binds success)
|
|
`(let ((,tgt ,target))
|
|
(multiple-value-bind (,binds ,success)
|
|
(list-match ,tgt ',test)
|
|
(declare (ignorable ,binds))
|
|
(if ,success
|
|
(let ,(mapcar (lambda (var)
|
|
`(,var (cdr (assoc ',var ,binds))))
|
|
(vars test))
|
|
(declare (ignorable ,@(vars test)))
|
|
,@progn)
|
|
(list-match-case ,tgt ,@others))))))
|
|
nil))
|
|
|
|
;;;; * def-special-environment
|
|
|
|
(defun check-required (name vars required)
|
|
(dolist (var required)
|
|
(assert (member var vars)
|
|
(var)
|
|
"Unrecognized symbol ~S in ~S." var name)))
|
|
|
|
(defmacro def-special-environment (name (&key accessor binder binder*)
|
|
&rest vars)
|
|
"Define two macros for dealing with groups or related special variables.
|
|
|
|
ACCESSOR is defined as a macro: (defmacro ACCESSOR (VARS &rest
|
|
BODY)). Each element of VARS will be bound to the
|
|
current (dynamic) value of the special variable.
|
|
|
|
BINDER is defined as a macro for introducing (and binding new)
|
|
special variables. It is basically a readable LET form with the
|
|
prorpe declarations appended to the body. The first argument to
|
|
BINDER must be a form suitable as the first argument to LET.
|
|
|
|
ACCESSOR defaults to a new symbol in the same package as NAME
|
|
which is the concatenation of \"WITH-\" NAME. BINDER is built as
|
|
\"BIND-\" and BINDER* is BINDER \"*\"."
|
|
(unless accessor
|
|
(setf accessor (format-symbol (symbol-package name) "~A-~A" '#:with name)))
|
|
(unless binder
|
|
(setf binder (format-symbol (symbol-package name) "~A-~A" '#:bind name)))
|
|
(unless binder*
|
|
(setf binder* (format-symbol (symbol-package binder) "~A~A" binder '#:*)))
|
|
`(eval-when (:compile-toplevel :load-toplevel :execute)
|
|
(flet ()
|
|
(defmacro ,binder (requested-vars &body body)
|
|
(check-required ',name ',vars (mapcar #'car requested-vars))
|
|
`(let ,requested-vars
|
|
(declare (special ,@(mapcar #'car requested-vars)))
|
|
,@body))
|
|
(defmacro ,binder* (requested-vars &body body)
|
|
(check-required ',name ',vars (mapcar #'car requested-vars))
|
|
`(let* ,requested-vars
|
|
(declare (special ,@(mapcar #'car requested-vars)))
|
|
,@body))
|
|
(defmacro ,accessor (requested-vars &body body)
|
|
(check-required ',name ',vars requested-vars)
|
|
`(locally (declare (special ,@requested-vars))
|
|
,@body))
|
|
',name)))
|
|
|
|
;; Copyright (c) 2002-2006, Edward Marco Baringer
|
|
;; All rights reserved.
|
|
;;
|
|
;; Redistribution and use in source and binary forms, with or without
|
|
;; modification, are permitted provided that the following conditions are
|
|
;; met:
|
|
;;
|
|
;; - Redistributions of source code must retain the above copyright
|
|
;; notice, this list of conditions and the following disclaimer.
|
|
;;
|
|
;; - Redistributions in binary form must reproduce the above copyright
|
|
;; notice, this list of conditions and the following disclaimer in the
|
|
;; documentation and/or other materials provided with the distribution.
|
|
;;
|
|
;; - Neither the name of Edward Marco Baringer, nor BESE, nor the names
|
|
;; of its contributors may be used to endorse or promote products
|
|
;; derived from this software without specific prior written permission.
|
|
;;
|
|
;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
;; "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
;; LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
;; A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
;; OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
;; SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
;; LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
;; DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
;; THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
;; (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
;; OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE
|