;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*- Author: Peter Norvig ;;; File: misc.lisp; Date: 8-Sep-95 (in-package :cl-user) ;;;; MISC. FUNCTIONS: UTILITY FUNCTIONS ;;;; DYLAN-SPECIFIC UTILITY FUNCTIONS ;;; The following functions with a / are like their slash-less counterparts, ;;; except they work for arguments with comments and for |()|. (defun first/ (exp) (first (strip-nil exp))) (defun rest/ (exp) (rest (strip-nil exp))) (defun second/ (exp) (second (strip-nil exp))) (defun null/ (exp) (null (strip-nil exp))) (defun assoc/ (item a-list) (find (strip item) (strip a-list) :key #'(lambda (x) (first-atom (strip x))))) (defun strip-nil (exp) "Strip comment, and convert |()| to ()." (if (eq (strip exp) '|()|) '() (strip exp))) (defun strip (exp) "Strip off the comment." (if (comment? exp) (com-code exp) exp)) (defmacro ifd (pred then &optional else) ;; Dylan if: false for nil or #f `(if (not (false? ,pred)) ,then ,else)) (defmacro once (var &body body) ;; Called once-only on Lisp Machines, return (Dylan) code built by body, ;; binding (in Dylan) any variables if they have non-trivial values (assert (symbolp var)) (let ((temp (gensym (string var)))) `(if (or (constantp ,var) (atom ,var)) (progn ,@body) (list 'let ',temp ,var (let ((,var ',temp)) ,@body))))) (defun maybe-begin (args) "Take a list of args (a body) and wrap a BEGIN around it if necessary." (case (length args) (0 '|\#f|) (1 (if (starts-with (first/ args) 'let) `(begin ,@args) (first/ args))) (t `(begin ,@args)))) ;;;; GENERAL LISP UTILITY FUNCTIONS (defun op (exp) (first/ exp)) (defun args (exp) (rest/ exp)) (defun last1 (x) (first (last x))) (defun nconc1 (list element) (nconc list (list element))) (defun mklist (x) "Return x if is a list, otherwise (list x)." (if (listp x) x (list x))) (defun mksymbol (&rest parts) "Concatenate the parts and intern as a symbol." (intern (format nil "~{~A~}" parts))) (defun first-atom (x) "The first (leftmost) atom in a nested list." (if (atom x) x (first-atom (first/ x)))) (defun length=1 (x) "Is this a list of length 1?" (and (consp x) (null (rest/ x)))) (defun length>1 (x) "Is this a list of length greater than 1?" (and (consp x) (rest/ x))) (defun dotted? (exp) ;; Is this a dotted list -- one with a non-null last tail? (and (consp exp) (not (null (rest/ (last exp)))))) (defun starts-with (sequence item) "Is the first argument a sequence that starts with this item?" (setq sequence (strip sequence)) (and (typecase sequence (list (not (null sequence))) (vector (> (length sequence) 0))) (eql (elt sequence 0) item))) (defun ends-with (sequence item) "Is the first argument a sequence that ends with this item?" (and (typecase sequence (list (not (null sequence))) (vector (> (length sequence) 0))) (eql (elt sequence (- (length sequence) 1)) item))) (defun bracketed-with (sequence item) "Is the first argument a sequence that starts and ends with this item?" (and (starts-with sequence item) (ends-with sequence item))) (defun expand-files (files) "Return a list of files matching the specification." (mapcan #'directory (mklist files))) (defun find-anywhere (item tree) "Does item appear anywhere in tree?" (or (equal item tree) (and (consp tree) (or (find-anywhere item (car tree)) (find-anywhere item (cdr tree)))))) ;;;; DESTRUCTURING BIND, AND VARIANTS (defmacro safe-destructuring-bind (form exp &body body) ;; This is similar to destructuring-bind, except ;; (1) Missing args are silently ignored ;; (2) No & keywords, except &optional (abbreviated &opt). Dot at end ok. (*ing-bind-fn form exp body #'(lambda (x) (declare (ignore x)) 'identity))) (defmacro converting-bind (form exp &body body) ;; Like safe-destructuring-bind, except ;; (3) variables in FORM are converted according to their name (*ing-bind-fn form exp body #'(lambda (arg) (case arg ((f pred) 'cvt-fn) ((name ignore asis) 'identity) ((body) 'cvt-body) ((type class) 'cvt-type-exp) ((keys) 'cvt-keys) ; Which does NOT convert; just handles :test-not ((stdin) '(lambda (x) (if (null/ x) '*standard-input* (cvt-exp x)))) ((stdout) '(lambda (x) (if (null/ x) '*standard-output* (cvt-exp x)))) (otherwise (if (ends-with (string arg) #\*) 'cvt-exps 'cvt-exp)))))) (defun *ing-bind-fn (form exp body converter) (let ((var (gensym)) (vars nil)) (loop (let ((v (if (atom form) form (first form)))) (cond ((null/ form) (RETURN)) ((member v '(&opt &optional)) nil) ((or (not (symbolp v)) (member v lambda-list-keywords)) (error "Don't support ~A" v)) ((and (atom form) (not (null form))) (push `(,v (,(funcall converter v) ,var)) vars)) (t (push `(,v (,(funcall converter v) (pop ,var))) vars))) (if (atom form) (RETURN) (pop form)))) `(let* ((,var ,exp) ,@(nreverse vars)) ,@body)))