utility.lisp 27.7 KB
Newer Older
1
2
3
;;;; -------------------------------------------------------------------------
;;;; General Purpose Utilities for ASDF

4
5
(uiop/package:define-package :uiop/utility
  (:use :uiop/common-lisp :uiop/package)
6
  ;; import and reexport a few things defined in :uiop/common-lisp
7
  (:import-from :uiop/common-lisp #:compatfmt #:loop* #:frob-substrings
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
8
   #+(or clasp ecl) #:use-ecl-byte-compiler-p #+mcl #:probe-posix)
9
  (:export #:compatfmt #:loop* #:frob-substrings #:compatfmt
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
10
   #+(or clasp ecl) #:use-ecl-byte-compiler-p #+mcl #:probe-posix)
11
  (:export
12
   ;; magic helper to define debugging functions:
13
   #:uiop-debug #:load-uiop-debug-utility #:*uiop-debug-utility*
14
15
   #:with-upgradability ;; (un)defining functions in an upgrade-friendly way
   #:undefine-function #:undefine-functions #:defun* #:defgeneric*
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
16
   #:nest #:if-let ;; basic flow control
17
18
   #:while-collecting #:appendf #:length=n-p #:ensure-list ;; lists
   #:remove-plist-keys #:remove-plist-key ;; plists
19
   #:emptyp ;; sequences
20
   #:+non-base-chars-exist-p+ ;; characters
21
   #:+max-character-type-index+ #:character-type-index #:+character-types+
22
   #:base-string-p #:strings-common-element-type #:reduce/strcat #:strcat ;; strings
23
   #:first-char #:last-char #:split-string #:stripln #:+cr+ #:+lf+ #:+crlf+
24
   #:string-prefix-p #:string-enclosed-p #:string-suffix-p
25
   #:standard-case-symbol-name #:find-standard-case-symbol
26
   #:coerce-class ;; CLOS
27
28
   #:stamp< #:stamps< #:stamp*< #:stamp<= ;; stamps
   #:earlier-stamp #:stamps-earliest #:earliest-stamp
29
   #:later-stamp #:stamps-latest #:latest-stamp #:latest-stamp-f
30
   #:list-to-hash-set #:ensure-gethash ;; hash-table
31
   #:ensure-function #:access-at #:access-at-count ;; functions
32
   #:call-function #:call-functions #:register-hook-function
33
34
   #:match-condition-p #:match-any-condition-p ;; conditions
   #:call-with-muffled-conditions #:with-muffled-conditions
35
36
   #:lexicographic< #:lexicographic<=
   #:parse-version #:unparse-version #:version< #:version<= #:version-compatible-p)) ;; version
37
(in-package :uiop/utility)
38

39
40
41
42
43
;;;; Defining functions in a way compatible with hot-upgrade:
;; DEFUN* and DEFGENERIC* use FMAKUNBOUND to delete any previous fdefinition,
;; thus replacing the function without warning or error
;; even if the signature and/or generic-ness of the function has changed.
;; For a generic function, this invalidates any previous DEFMETHOD.
44
45
46
(eval-when (:load-toplevel :compile-toplevel :execute)
  (defun undefine-function (function-spec)
    (cond
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
47
      ((symbolp function-spec)
48
49
50
       ;; undefining the previous function is the portable way
       ;; of overriding any incompatible previous gf,
       ;; but CLISP needs extra help with getting rid of previous methods.
51
       #+clisp
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
52
53
54
55
56
       (let ((f (and (fboundp function-spec) (fdefinition function-spec))))
         (when (typep f 'clos:standard-generic-function)
           (loop :for m :in (clos:generic-function-methods f)
                 :do (remove-method f m))))
       (fmakunbound function-spec))
57
58
      ((and (consp function-spec) (eq (car function-spec) 'setf)
            (consp (cdr function-spec)) (null (cddr function-spec)))
59
       (fmakunbound function-spec))
60
61
      (t (error "bad function spec ~S" function-spec))))
  (defun undefine-functions (function-spec-list)
62
63
64
65
66
67
68
69
70
71
72
    (map () 'undefine-function function-spec-list))
  (macrolet
      ((defdef (def* def)
         `(defmacro ,def* (name formals &rest rest)
            (destructuring-bind (name &key (supersede t))
                (if (or (atom name) (eq (car name) 'setf))
                    (list name :supersede nil)
                    name)
              (declare (ignorable supersede))
              `(progn
                 ;; We usually try to do it only for the functions that need it,
73
                 ;; which happens in asdf/upgrade - however, for ECL, we need this hammer.
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
74
                 ,@(when (or supersede #+(or clasp ecl) t)
75
                     `((undefine-function ',name)))
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
76
                 ,@(when (and #+(or clasp ecl) (symbolp name)) ; fails for setf functions on ecl
77
78
79
                     `((declaim (notinline ,name))))
                 (,',def ,name ,formals ,@rest))))))
    (defdef defgeneric* defgeneric)
80
81
    (defdef defun* defun))
  (defmacro with-upgradability ((&optional) &body body)
82
83
84
85
86
    "Evaluate BODY at compile- load- and run- times, with DEFUN and DEFGENERIC modified
to also declare the functions NOTINLINE and to accept a wrapping the function name
specification into a list with keyword argument SUPERSEDE (which defaults to T if the name
is not wrapped, and NIL if it is wrapped). If SUPERSEDE is true, call UNDEFINE-FUNCTION
to supersede any previous definition."
87
88
89
90
91
92
    `(eval-when (:compile-toplevel :load-toplevel :execute)
       ,@(loop :for form :in body :collect
               (if (consp form)
                   (destructuring-bind (car . cdr) form
                     (case car
                       ((defun) `(defun* ,@cdr))
93
                       ((defgeneric) `(defgeneric* ,@cdr))
94
95
                       (otherwise form)))
                   form)))))
96
97

;;; Magic debugging help. See contrib/debug.lisp
98
(with-upgradability ()
99
  (defvar *uiop-debug-utility*
100
    '(or (ignore-errors
101
102
          (symbol-call :asdf :system-relative-pathname :uiop "contrib/debug.lisp"))
      (symbol-call :uiop/pathname :subpathname (user-homedir-pathname) "cl/asdf/uiop/contrib/debug.lisp"))
103
104
    "form that evaluates to the pathname to your favorite debugging utilities")

105
  (defmacro uiop-debug (&rest keys)
106
    `(eval-when (:compile-toplevel :load-toplevel :execute)
107
       (load-uiop-debug-utility ,@keys)))
108

109
  (defun load-uiop-debug-utility (&key package utility-file)
110
111
112
113
    (let* ((*package* (if package (find-package package) *package*))
           (keyword (read-from-string
                     (format nil ":DBG-~:@(~A~)" (package-name *package*)))))
      (unless (member keyword *features*)
114
        (let* ((utility-file (or utility-file *uiop-debug-utility*))
115
116
               (file (ignore-errors (probe-file (eval utility-file)))))
          (if file (load file)
117
              (error "Failed to locate debug utility file: ~S" utility-file)))))))
118

119
;;; Flow control
120
(with-upgradability ()
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
121
122
123
124
125
  (defmacro nest (&rest things)
    "Macro to do keep code nesting and indentation under control." ;; Thanks to mbaringer
    (reduce #'(lambda (outer inner) `(,@outer ,inner))
            things :from-end t))

126
127
128
129
130
131
132
133
134
135
  (defmacro if-let (bindings &body (then-form &optional else-form)) ;; from alexandria
    ;; bindings can be (var form) or ((var1 form1) ...)
    (let* ((binding-list (if (and (consp bindings) (symbolp (car bindings)))
                             (list bindings)
                             bindings))
           (variables (mapcar #'car binding-list)))
      `(let ,binding-list
         (if (and ,@variables)
             ,then-form
             ,else-form)))))
136
137

;;; List manipulation
138
139
140
(with-upgradability ()
  (defmacro while-collecting ((&rest collectors) &body body)
    "COLLECTORS should be a list of names for collections.  A collector
141
142
143
144
145
146
147
148
149
defines a function that, when applied to an argument inside BODY, will
add its argument to the corresponding collection.  Returns multiple values,
a list for each collection, in order.
   E.g.,
\(while-collecting \(foo bar\)
           \(dolist \(x '\(\(a 1\) \(b 2\) \(c 3\)\)\)
             \(foo \(first x\)\)
             \(bar \(second x\)\)\)\)
Returns two values: \(A B C\) and \(1 2 3\)."
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
    (let ((vars (mapcar #'(lambda (x) (gensym (symbol-name x))) collectors))
          (initial-values (mapcar (constantly nil) collectors)))
      `(let ,(mapcar #'list vars initial-values)
         (flet ,(mapcar #'(lambda (c v) `(,c (x) (push x ,v) (values))) collectors vars)
           ,@body
           (values ,@(mapcar #'(lambda (v) `(reverse ,v)) vars))))))

  (define-modify-macro appendf (&rest args)
    append "Append onto list") ;; only to be used on short lists.

  (defun length=n-p (x n) ;is it that (= (length x) n) ?
    (check-type n (integer 0 *))
    (loop
      :for l = x :then (cdr l)
      :for i :downfrom n :do
        (cond
          ((zerop i) (return (null l)))
167
168
169
170
171
          ((not (consp l)) (return nil)))))

  (defun ensure-list (x)
    (if (listp x) x (list x))))

172

173
;;; remove a key from a plist, i.e. for keyword argument cleanup
174
175
176
177
178
179
(with-upgradability ()
  (defun remove-plist-key (key plist)
    "Remove a single key from a plist"
    (loop* :for (k v) :on plist :by #'cddr
           :unless (eq k key)
           :append (list k v)))
180

181
182
183
184
185
  (defun remove-plist-keys (keys plist)
    "Remove a list of keys from a plist"
    (loop* :for (k v) :on plist :by #'cddr
           :unless (member k keys)
           :append (list k v))))
186
187


188
;;; Sequences
189
190
191
192
(with-upgradability ()
  (defun emptyp (x)
    "Predicate that is true for an empty sequence"
    (or (null x) (and (vectorp x) (zerop (length x))))))
193

194

195
;;; Characters
196
(with-upgradability ()
197
198
199
  ;; base-char != character on ECL, LW, SBCL, Genera.
  ;; NB: We assume a total order on character types.
  ;; If that's not true... this code will need to be updated.
200
  (defparameter +character-types+ ;; assuming a simple hierarchy
201
202
203
204
205
206
    #.(coerce (loop* :for (type next) :on
                     '(;; In SCL, all characters seem to be 16-bit base-char
                       ;; Yet somehow character fails to be a subtype of base-char
                       #-scl base-char
                       ;; LW6 has BASE-CHAR < SIMPLE-CHAR < CHARACTER
                       ;; LW7 has BASE-CHAR < BMP-CHAR < SIMPLE-CHAR = CHARACTER
Elias Pipping's avatar
Elias Pipping committed
207
                       #+lispworks7+ lw:bmp-char
208
                       #+lispworks lw:simple-char
209
210
211
212
213
214
                       character)
                     :unless (and next (subtypep next type))
                     :collect type) 'vector))
  (defparameter +max-character-type-index+ (1- (length +character-types+)))
  (defconstant +non-base-chars-exist-p+ (plusp +max-character-type-index+))
  (when +non-base-chars-exist-p+ (pushnew :non-base-chars-exist-p *features*)))
215
216
217
218
219
220
221
222
223
224
225

(with-upgradability ()
  (defun character-type-index (x)
    (declare (ignorable x))
    #.(case +max-character-type-index+
        (0 0)
        (1 '(etypecase x
             (character (if (typep x 'base-char) 0 1))
             (symbol (if (subtypep x 'base-char) 0 1))))
        (otherwise
         '(or (position-if (etypecase x
226
                             (character #'(lambda (type) (typep x type)))
227
228
229
230
                             (symbol #'(lambda (type) (subtypep x type))))
               +character-types+)
           (error "Not a character or character type: ~S" x))))))

231

232
;;; Strings
233
(with-upgradability ()
234
  (defun base-string-p (string)
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
235
    "Does the STRING only contain BASE-CHARs?"
236
    (declare (ignorable string))
237
    (and #+non-base-chars-exist-p (eq 'base-char (array-element-type string))))
238
239

  (defun strings-common-element-type (strings)
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
240
    "What least subtype of CHARACTER can contain all the elements of all the STRINGS?"
241
    (declare (ignorable strings))
242
243
244
    #.(if +non-base-chars-exist-p+
          `(aref +character-types+
            (loop :with index = 0 :for s :in strings :do
245
246
247
248
249
250
251
252
253
254
255
256
257
258
              (flet ((consider (i)
                       (cond ((= i ,+max-character-type-index+) (return i))
                             ,@(when (> +max-character-type-index+ 1) `(((> i index) (setf index i)))))))
                (cond
                  ((emptyp s)) ;; NIL or empty string
                  ((characterp s) (consider (character-type-index s)))
                  ((stringp s) (let ((string-type-index
                                       (character-type-index (array-element-type s))))
                                 (unless (>= index string-type-index)
                                   (loop :for c :across s :for i = (character-type-index c)
                                         :do (consider i)
                                         ,@(when (> +max-character-type-index+ 1)
                                             `((when (= i string-type-index) (return))))))))
                  (t (error "Invalid string designator ~S for ~S" s 'strings-common-element-type))))
259
260
                  :finally (return index)))
          ''character))
261
262

  (defun reduce/strcat (strings &key key start end)
263
264
    "Reduce a list as if by STRCAT, accepting KEY START and END keywords like REDUCE.
NIL is interpreted as an empty string. A character is interpreted as a string of length one."
265
266
    (when (or start end) (setf strings (subseq strings start end)))
    (when key (setf strings (mapcar key strings)))
267
268
    (loop :with output = (make-string (loop :for s :in strings
                                            :sum (if (characterp s) 1 (length s)))
269
                                      :element-type (strings-common-element-type strings))
270
          :with pos = 0
271
          :for input :in strings
272
273
274
275
          :do (etypecase input
                (null)
                (character (setf (char output pos) input) (incf pos))
                (string (replace output input :start1 pos) (incf pos (length input))))
276
277
          :finally (return output)))

278
  (defun strcat (&rest strings)
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
279
280
    "Concatenate strings.
NIL is interpreted as an empty string, a character as a string of length one."
281
    (reduce/strcat strings))
282

283
  (defun first-char (s)
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
284
    "Return the first character of a non-empty string S, or NIL"
285
    (and (stringp s) (plusp (length s)) (char s 0)))
286

287
  (defun last-char (s)
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
288
    "Return the last character of a non-empty string S, or NIL"
289
    (and (stringp s) (plusp (length s)) (char s (1- (length s)))))
290

291
292
  (defun split-string (string &key max (separator '(#\Space #\Tab)))
    "Split STRING into a list of components separated by
293
294
295
296
any of the characters in the sequence SEPARATOR.
If MAX is specified, then no more than max(1,MAX) components will be returned,
starting the separation from the end, e.g. when called with arguments
 \"a.b.c.d.e\" :max 3 :separator \".\" it will return (\"a.b.c\" \"d\" \"e\")."
297
298
    (block ()
      (let ((list nil) (words 0) (end (length string)))
299
        (when (zerop end) (return nil))
300
301
302
303
304
        (flet ((separatorp (char) (find char separator))
               (done () (return (cons (subseq string 0 end) list))))
          (loop
            :for start = (if (and max (>= words (1- max)))
                             (done)
305
306
307
308
309
                             (position-if #'separatorp string :end end :from-end t))
            :do (when (null start) (done))
                (push (subseq string (1+ start) end) list)
                (incf words)
                (setf end start))))))
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329

  (defun string-prefix-p (prefix string)
    "Does STRING begin with PREFIX?"
    (let* ((x (string prefix))
           (y (string string))
           (lx (length x))
           (ly (length y)))
      (and (<= lx ly) (string= x y :end2 lx))))

  (defun string-suffix-p (string suffix)
    "Does STRING end with SUFFIX?"
    (let* ((x (string string))
           (y (string suffix))
           (lx (length x))
           (ly (length y)))
      (and (<= ly lx) (string= x y :start1 (- lx ly)))))

  (defun string-enclosed-p (prefix string suffix)
    "Does STRING begin with PREFIX and end with SUFFIX?"
    (and (string-prefix-p prefix string)
330
         (string-suffix-p string suffix)))
331

332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
  (defvar +cr+ (coerce #(#\Return) 'string))
  (defvar +lf+ (coerce #(#\Linefeed) 'string))
  (defvar +crlf+ (coerce #(#\Return #\Linefeed) 'string))

  (defun stripln (x)
    "Strip a string X from any ending CR, LF or CRLF.
Return two values, the stripped string and the ending that was stripped,
or the original value and NIL if no stripping took place.
Since our STRCAT accepts NIL as empty string designator,
the two results passed to STRCAT always reconstitute the original string"
    (check-type x string)
    (block nil
      (flet ((c (end) (when (string-suffix-p x end)
                        (return (values (subseq x 0 (- (length x) (length end))) end)))))
        (when x (c +crlf+) (c +lf+) (c +cr+) (values x nil)))))

348
  (defun standard-case-symbol-name (name-designator)
349
350
351
352
    "Given a NAME-DESIGNATOR for a symbol, if it is a symbol, convert it to a string using STRING;
if it is a string, use STRING-UPCASE on an ANSI CL platform, or STRING on a so-called \"modern\"
platform such as Allegro with modern syntax."
    (check-type name-designator (or string symbol))
353
    (cond
354
355
      ((or (symbolp name-designator) #+allegro (eq excl:*current-case-mode* :case-sensitive-lower))
       (string name-designator))
356
357
358
359
      ;; Should we be doing something on CLISP?
      (t (string-upcase name-designator))))

  (defun find-standard-case-symbol (name-designator package-designator &optional (error t))
360
361
    "Find a symbol designated by NAME-DESIGNATOR in a package designated by PACKAGE-DESIGNATOR,
where STANDARD-CASE-SYMBOL-NAME is used to transform them if these designators are strings.
362
If optional ERROR argument is NIL, return NIL instead of an error when the symbol is not found."
363
364
    (find-symbol* (standard-case-symbol-name name-designator)
                  (etypecase package-designator
365
366
                    ((or package symbol) package-designator)
                    (string (standard-case-symbol-name package-designator)))
367
                  error)))
368

Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
369
;;; stamps: a REAL or a boolean where NIL=-infinity, T=+infinity
370
371
(eval-when (#-lispworks :compile-toplevel :load-toplevel :execute)
  (deftype stamp () '(or real boolean)))
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
(with-upgradability ()
  (defun stamp< (x y)
    (etypecase x
      (null (and y t))
      ((eql t) nil)
      (real (etypecase y
              (null nil)
              ((eql t) t)
              (real (< x y))))))
  (defun stamps< (list) (loop :for y :in list :for x = nil :then y :always (stamp< x y)))
  (defun stamp*< (&rest list) (stamps< list))
  (defun stamp<= (x y) (not (stamp< y x)))
  (defun earlier-stamp (x y) (if (stamp< x y) x y))
  (defun stamps-earliest (list) (reduce 'earlier-stamp list :initial-value t))
  (defun earliest-stamp (&rest list) (stamps-earliest list))
  (defun later-stamp (x y) (if (stamp< x y) y x))
  (defun stamps-latest (list) (reduce 'later-stamp list :initial-value nil))
  (defun latest-stamp (&rest list) (stamps-latest list))
  (define-modify-macro latest-stamp-f (&rest stamps) latest-stamp))
391

392

393
;;; Function designators
394
395
396
(with-upgradability ()
  (defun ensure-function (fun &key (package :cl))
    "Coerce the object FUN into a function.
397
398
399
400
401
402

If FUN is a FUNCTION, return it.
If the FUN is a non-sequence literal constant, return constantly that,
i.e. for a boolean keyword character number or pathname.
Otherwise if FUN is a non-literally constant symbol, return its FDEFINITION.
If FUN is a CONS, return the function that applies its CAR
403
404
to the appended list of the rest of its CDR and the arguments,
unless the CAR is LAMBDA, in which case the expression is evaluated.
405
406
If FUN is a string, READ a form from it in the specified PACKAGE (default: CL)
and EVAL that in a (FUNCTION ...) context."
407
408
409
    (etypecase fun
      (function fun)
      ((or boolean keyword character number pathname) (constantly fun))
410
      (hash-table #'(lambda (x) (gethash x fun)))
411
      (symbol (fdefinition fun))
412
413
414
      (cons (if (eq 'lambda (car fun))
                (eval fun)
                #'(lambda (&rest args) (apply (car fun) (append (cdr fun) args)))))
415
416
417
418
419
420
      (string (eval `(function ,(with-standard-io-syntax
                                  (let ((*package* (find-package package)))
                                    (read-from-string fun))))))))

  (defun access-at (object at)
    "Given an OBJECT and an AT specifier, list of successive accessors,
421
422
call each accessor on the result of the previous calls.
An accessor may be an integer, meaning a call to ELT,
423
424
425
426
427
a keyword, meaning a call to GETF,
NIL, meaning identity,
a function or other symbol, meaning itself,
or a list of a function designator and arguments, interpreted as per ENSURE-FUNCTION.
As a degenerate case, the AT specifier may be an atom of a single such accessor
428
instead of a list."
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
    (flet ((access (object accessor)
             (etypecase accessor
               (function (funcall accessor object))
               (integer (elt object accessor))
               (keyword (getf object accessor))
               (null object)
               (symbol (funcall accessor object))
               (cons (funcall (ensure-function accessor) object)))))
      (if (listp at)
          (dolist (accessor at object)
            (setf object (access object accessor)))
          (access object at))))

  (defun access-at-count (at)
    "From an AT specification, extract a COUNT of maximum number
444
of sub-objects to read as per ACCESS-AT"
445
446
447
448
449
    (cond
      ((integerp at)
       (1+ at))
      ((and (consp at) (integerp (first at)))
       (1+ (first at)))))
450

451
  (defun call-function (function-spec &rest arguments)
452
453
    "Call the function designated by FUNCTION-SPEC as per ENSURE-FUNCTION,
with the given ARGUMENTS"
454
    (apply (ensure-function function-spec) arguments))
455

456
  (defun call-functions (function-specs)
457
    "For each function in the list FUNCTION-SPECS, in order, call the function as per CALL-FUNCTION"
458
    (map () 'call-function function-specs))
459

460
  (defun register-hook-function (variable hook &optional call-now-p)
461
462
463
    "Push the HOOK function (a designator as per ENSURE-FUNCTION) onto the hook VARIABLE.
When CALL-NOW-P is true, also call the function immediately."
    (pushnew hook (symbol-value variable) :test 'equal)
464
    (when call-now-p (call-function hook))))
465

466

467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
;;; CLOS
(with-upgradability ()
  (defun coerce-class (class &key (package :cl) (super t) (error 'error))
    "Coerce CLASS to a class that is subclass of SUPER if specified,
or invoke ERROR handler as per CALL-FUNCTION.

A keyword designates the name a symbol, which when found in either PACKAGE, designates a class.
-- for backward compatibility, *PACKAGE* is also accepted for now, but this may go in the future.
A string is read as a symbol while in PACKAGE, the symbol designates a class.

A class object designates itself.
NIL designates itself (no class).
A symbol otherwise designates a class by name."
    (let* ((normalized
             (typecase class
              (keyword (or (find-symbol* class package nil)
                           (find-symbol* class *package* nil)))
              (string (symbol-call :uiop :safe-read-from-string class :package package))
              (t class)))
           (found
             (etypecase normalized
               ((or standard-class built-in-class) normalized)
               ((or null keyword) nil)
               (symbol (find-class normalized nil nil)))))
      (or (and found
               (or (eq super t) (#-cormanlisp subtypep #+cormanlisp cl::subclassp found super))
               found)
494
          (call-function error "Can't coerce ~S to a ~:[class~;subclass of ~:*~S~]" class super)))))
495
496


497
498
499
;;; Hash-tables
(with-upgradability ()
  (defun ensure-gethash (key table default)
500
    "Lookup the TABLE for a KEY as by GETHASH, but if not present,
501
call the (possibly constant) function designated by DEFAULT as per CALL-FUNCTION,
502
503
set the corresponding entry to the result in the table.
Return two values: the entry after its optional computation, and whether it was found"
504
    (multiple-value-bind (value foundp) (gethash key table)
505
506
507
508
509
      (values
       (if foundp
           value
           (setf (gethash key table) (call-function default)))
       foundp)))
510
511
512
513
514
515
516

  (defun list-to-hash-set (list &aux (h (make-hash-table :test 'equal)))
    "Convert a LIST into hash-table that has the same elements when viewed as a set,
up to the given equality TEST"
    (dolist (x list h) (setf (gethash x h) t))))


517
;;; Version handling
518
519
520
(with-upgradability ()
  (defun unparse-version (version-list)
    (format nil "~{~D~^.~}" version-list))
521

522
523
  (defun parse-version (version-string &optional on-error)
    "Parse a VERSION-STRING as a series of natural integers separated by dots.
524
525
526
527
528
529
530
Return a (non-null) list of integers if the string is valid;
otherwise return NIL.

When invalid, ON-ERROR is called as per CALL-FUNCTION before to return NIL,
with format arguments explaining why the version is invalid.
ON-ERROR is also called if the version is not canonical
in that it doesn't print back to itself, but the list is returned anyway."
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
    (block nil
      (unless (stringp version-string)
        (call-function on-error "~S: ~S is not a string" 'parse-version version-string)
        (return))
      (unless (loop :for prev = nil :then c :for c :across version-string
                    :always (or (digit-char-p c)
                                (and (eql c #\.) prev (not (eql prev #\.))))
                    :finally (return (and c (digit-char-p c))))
        (call-function on-error "~S: ~S doesn't follow asdf version numbering convention"
                       'parse-version version-string)
        (return))
      (let* ((version-list
               (mapcar #'parse-integer (split-string version-string :separator ".")))
             (normalized-version (unparse-version version-list)))
        (unless (equal version-string normalized-version)
          (call-function on-error "~S: ~S contains leading zeros" 'parse-version version-string))
        version-list)))

  (defun lexicographic< (< x y)
    (cond ((null y) nil)
          ((null x) t)
          ((funcall < (car x) (car y)) t)
          ((funcall < (car y) (car x)) nil)
          (t (lexicographic< < (cdr x) (cdr y)))))

  (defun lexicographic<= (< x y)
    (not (lexicographic< < y x)))

  (defun version< (version1 version2)
    (let ((v1 (parse-version version1 nil))
          (v2 (parse-version version2 nil)))
      (lexicographic< '< v1 v2)))

  (defun version<= (version1 version2)
    (not (version< version2 version1)))

  (defun version-compatible-p (provided-version required-version)
    "Is the provided version a compatible substitution for the required-version?
569
570
571
If major versions differ, it's not compatible.
If they are equal, then any later version is compatible,
with later being determined by a lexicographical comparison of minor numbers."
572
573
574
    (let ((x (parse-version provided-version nil))
          (y (parse-version required-version nil)))
      (and x y (= (car x) (car y)) (lexicographic<= '< (cdr y) (cdr x))))))
575

576
577
578

;;; Condition control

579
580
581
582
583
584
(with-upgradability ()
  (defparameter +simple-condition-format-control-slot+
    #+abcl 'system::format-control
    #+allegro 'excl::format-control
    #+clisp 'system::$format-control
    #+clozure 'ccl::format-control
585
    #+(or cmucl scl) 'conditions::format-control
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
586
    #+(or clasp ecl mkcl) 'si::format-control
587
588
    #+(or gcl lispworks) 'conditions::format-string
    #+sbcl 'sb-kernel:format-control
589
    #-(or abcl allegro clasp clisp clozure cmucl ecl gcl lispworks mkcl sbcl scl) nil
590
591
592
593
    "Name of the slot for FORMAT-CONTROL in simple-condition")

  (defun match-condition-p (x condition)
    "Compare received CONDITION to some pattern X:
594
595
596
a symbol naming a condition class,
a simple vector of length 2, arguments to find-symbol* with result as above,
or a string describing the format-control of a simple-condition."
597
598
    (etypecase x
      (symbol (typep condition x))
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
599
600
      ((simple-vector 2)
       (ignore-errors (typep condition (find-symbol* (svref x 0) (svref x 1) nil))))
601
602
603
      (function (funcall x condition))
      (string (and (typep condition 'simple-condition)
                   ;; On SBCL, it's always set and the check triggers a warning
604
                   #+(or allegro clozure cmucl lispworks scl)
605
606
607
608
609
610
611
612
                   (slot-boundp condition +simple-condition-format-control-slot+)
                   (ignore-errors (equal (simple-condition-format-control condition) x))))))

  (defun match-any-condition-p (condition conditions)
    "match CONDITION against any of the patterns of CONDITIONS supplied"
    (loop :for x :in conditions :thereis (match-condition-p x condition)))

  (defun call-with-muffled-conditions (thunk conditions)
Francois-Rene Rideau's avatar
Francois-Rene Rideau committed
613
    "calls the THUNK in a context where the CONDITIONS are muffled"
614
615
616
617
    (handler-bind ((t #'(lambda (c) (when (match-any-condition-p c conditions)
                                      (muffle-warning c)))))
      (funcall thunk)))

618
  (defmacro with-muffled-conditions ((conditions) &body body)
619
    "Shorthand syntax for CALL-WITH-MUFFLED-CONDITIONS"
620
    `(call-with-muffled-conditions #'(lambda () ,@body) ,conditions)))
621