(in-package "SB!IMPL")
+(defvar *core-pathname* nil
+ #!+sb-doc
+ "The absolute pathname of the running SBCL core.")
+
+(defvar *runtime-pathname* nil
+ #!+sb-doc
+ "The absolute pathname of the running SBCL runtime.")
+
;;; something not EQ to anything we might legitimately READ
(defparameter *eof-object* (make-symbol "EOF-OBJECT"))
-;;; a type used for indexing into arrays, and for related quantities
-;;; like lengths of lists
+(eval-when (:compile-toplevel :load-toplevel :execute)
+ (defconstant max-hash sb!xc:most-positive-fixnum))
+
+(def!type hash ()
+ `(integer 0 ,max-hash))
+
+;;; a type used for indexing into sequences, and for related
+;;; quantities like lengths of lists and other sequences.
;;;
-;;; It's intentionally limited to one less than the
-;;; ARRAY-DIMENSION-LIMIT for efficiency reasons, because in SBCL
-;;; ARRAY-DIMENSION-LIMIT is MOST-POSITIVE-FIXNUM, and staying below
-;;; that lets the system know it can increment a value of this type
-;;; without having to worry about using a bignum to represent the
-;;; result.
+;;; A more correct value for the exclusive upper bound for indexing
+;;; would be (1- ARRAY-DIMENSION-LIMIT) since ARRAY-DIMENSION-LIMIT is
+;;; the exclusive maximum *size* of one array dimension (As specified
+;;; in CLHS entries for MAKE-ARRAY and "valid array dimensions"). The
+;;; current value is maintained to avoid breaking existing code that
+;;; also uses that type for upper bounds on indices (e.g. sequence
+;;; length).
;;;
-;;; (It should be safe to use ARRAY-DIMENSION-LIMIT as an exclusive
-;;; bound because ANSI specifies it as an exclusive bound.)
+;;; In SBCL, ARRAY-DIMENSION-LIMIT is arranged to be a little smaller
+;;; than MOST-POSITIVE-FIXNUM, for implementation (see comment above
+;;; ARRAY-DIMENSION-LIMIT) and efficiency reasons: staying below
+;;; MOST-POSITIVE-FIXNUM lets the system know it can increment a value
+;;; of type INDEX without having to worry about using a bignum to
+;;; represent the result.
(def!type index () `(integer 0 (,sb!xc:array-dimension-limit)))
;;; like INDEX, but only up to half the maximum. Used by hash-table
(eq (car clause) 'ignore))))
(cdr decl))))
decls))
-
;;; just like DOLIST, but with one-dimensional arrays
(defmacro dovector ((elt vector &optional result) &body body)
(multiple-value-bind (forms decls) (parse-body body :doc-string-allowed nil)
;;; if the table is a synchronized table.
(defmacro dohash (((key-var value-var) table &key result locked) &body body)
(multiple-value-bind (forms decls) (parse-body body :doc-string-allowed nil)
- (let* ((gen (gensym))
- (n-more (gensym))
- (n-table (gensym))
- (iter-form `(with-hash-table-iterator (,gen ,n-table)
+ (with-unique-names (gen n-more n-table)
+ (let ((iter-form `(with-hash-table-iterator (,gen ,n-table)
(loop
(multiple-value-bind (,n-more ,key-var ,value-var) (,gen)
,@decls
(unless ,n-more (return ,result))
,@forms)))))
- `(let ((,n-table ,table))
- ,(if locked
- `(with-locked-hash-table (,n-table)
- ,iter-form)
- iter-form)))))
+ `(let ((,n-table ,table))
+ ,(if locked
+ `(with-locked-system-table (,n-table)
+ ,iter-form)
+ iter-form))))))
+
+;;; Executes BODY for all entries of PLIST with KEY and VALUE bound to
+;;; the respective keys and values.
+(defmacro doplist ((key val) plist &body body)
+ (with-unique-names (tail)
+ `(let ((,tail ,plist) ,key ,val)
+ (loop (when (null ,tail) (return nil))
+ (setq ,key (pop ,tail))
+ (when (null ,tail)
+ (error "malformed plist, odd number of elements"))
+ (setq ,val (pop ,tail))
+ (progn ,@body)))))
+
\f
;;;; hash cache utility
;;; the specified name. (:INIT-WRAPPER is set to COLD-INIT-FORMS
;;; in type system definitions so that caches will be created
;;; before top level forms run.)
+(defvar *cache-vector-symbols* nil)
+
+(defun drop-all-hash-caches ()
+ (dolist (name *cache-vector-symbols*)
+ (set name nil)))
+
(defmacro define-hash-cache (name args &key hash-function hash-bits default
(init-wrapper 'progn)
(values 1))
- (let* ((var-name (symbolicate "*" name "-CACHE-VECTOR*"))
+ (let* ((var-name (symbolicate "**" name "-CACHE-VECTOR**"))
+ (probes-name (when *profile-hash-cache*
+ (symbolicate "**" name "-CACHE-PROBES**")))
+ (misses-name (when *profile-hash-cache*
+ (symbolicate "**" name "-CACHE-MISSES**")))
(nargs (length args))
(size (ash 1 hash-bits))
(default-values (if (and (consp default) (eq (car default) 'values))
(cdr default)
(list default)))
- (args-and-values (gensym))
+ (args-and-values (sb!xc:gensym "ARGS-AND-VALUES"))
(args-and-values-size (+ nargs values))
- (n-index (gensym))
- (n-cache (gensym)))
-
+ (n-index (sb!xc:gensym "INDEX"))
+ (n-cache (sb!xc:gensym "CACHE")))
+ (declare (ignorable probes-name misses-name))
(unless (= (length default-values) values)
(error "The number of default values ~S differs from :VALUES ~W."
default values))
(values-refs)
(values-names))
(dotimes (i values)
- (let ((name (gensym)))
+ (let ((name (sb!xc:gensym "VALUE")))
(values-names name)
(values-refs `(svref ,args-and-values (+ ,nargs ,i)))
(sets `(setf (svref ,args-and-values (+ ,nargs ,i)) ,name))))
(incf n)))
(when *profile-hash-cache*
- (let ((n-probe (symbolicate "*" name "-CACHE-PROBES*"))
- (n-miss (symbolicate "*" name "-CACHE-MISSES*")))
- (inits `(setq ,n-probe 0))
- (inits `(setq ,n-miss 0))
- (forms `(defvar ,n-probe))
- (forms `(defvar ,n-miss))
- (forms `(declaim (fixnum ,n-miss ,n-probe)))))
+ (inits `(setq ,probes-name 0))
+ (inits `(setq ,misses-name 0))
+ (forms `(declaim (fixnum ,probes-name ,misses-name))))
(let ((fun-name (symbolicate name "-CACHE-LOOKUP")))
(inlines fun-name)
(forms
`(defun ,fun-name ,(arg-vars)
,@(when *profile-hash-cache*
- `((incf ,(symbolicate "*" name "-CACHE-PROBES*"))))
- (let* ((,n-index (,hash-function ,@(arg-vars)))
- (,n-cache ,var-name)
- (,args-and-values (svref ,n-cache ,n-index)))
- (cond ((and ,args-and-values
- ,@(tests))
- (values ,@(values-refs)))
- (t
+ `((incf ,probes-name)))
+ (flet ((miss ()
,@(when *profile-hash-cache*
- `((incf ,(symbolicate "*" name "-CACHE-MISSES*"))))
- ,default))))))
+ `((incf ,misses-name)))
+ (return-from ,fun-name ,default)))
+ (let* ((,n-index (,hash-function ,@(arg-vars)))
+ (,n-cache (or ,var-name (miss)))
+ (,args-and-values (svref ,n-cache ,n-index)))
+ (cond ((and (not (eql 0 ,args-and-values))
+ ,@(tests))
+ (values ,@(values-refs)))
+ (t
+ (miss))))))))
(let ((fun-name (symbolicate name "-CACHE-ENTER")))
(inlines fun-name)
(forms
`(defun ,fun-name (,@(arg-vars) ,@(values-names))
(let ((,n-index (,hash-function ,@(arg-vars)))
- (,n-cache ,var-name)
+ (,n-cache (or ,var-name
+ (setq ,var-name (make-array ,size :initial-element 0))))
(,args-and-values (make-array ,args-and-values-size)))
,@(sets)
(setf (svref ,n-cache ,n-index) ,args-and-values))
(let ((fun-name (symbolicate name "-CACHE-CLEAR")))
(forms
`(defun ,fun-name ()
- (fill ,var-name nil)))
- (forms `(,fun-name)))
+ (setq ,var-name nil))))
- (inits `(unless (boundp ',var-name)
- (setq ,var-name (make-array ,size :initial-element nil))))
+ ;; Needed for cold init!
+ (inits `(setq ,var-name nil))
#!+sb-show (inits `(setq *hash-caches-initialized-p* t))
`(progn
- (defvar ,var-name)
- (declaim (type (simple-vector ,size) ,var-name))
+ (pushnew ',var-name *cache-vector-symbols*)
+ (defglobal ,var-name nil)
+ ,@(when *profile-hash-cache*
+ `((defglobal ,probes-name 0)
+ (defglobal ,misses-name 0)))
+ (declaim (type (or null (simple-vector ,size)) ,var-name))
#!-sb-fluid (declaim (inline ,@(inlines)))
(,init-wrapper ,@(inits))
,@(forms)
(let ((default-values (if (and (consp default) (eq (car default) 'values))
(cdr default)
(list default)))
- (arg-names (mapcar #'car args)))
- (collect ((values-names))
- (dotimes (i values)
- (values-names (gensym)))
- (multiple-value-bind (body decls doc) (parse-body body-decls-doc)
- `(progn
- (define-hash-cache ,name ,args ,@options)
- (defun ,name ,arg-names
- ,@decls
- ,doc
- (cond #!+sb-show
- ((not (boundp '*hash-caches-initialized-p*))
- ;; This shouldn't happen, but it did happen to me
- ;; when revising the type system, and it's a lot
- ;; easier to figure out what what's going on with
- ;; that kind of problem if the system can be kept
- ;; alive until cold boot is complete. The recovery
- ;; mechanism should definitely be conditional on
- ;; some debugging feature (e.g. SB-SHOW) because
- ;; it's big, duplicating all the BODY code. -- WHN
- (/show0 ,name " too early in cold init, uncached")
- (/show0 ,(first arg-names) "=..")
- (/hexstr ,(first arg-names))
- ,@body)
- (t
- (multiple-value-bind ,(values-names)
- (,(symbolicate name "-CACHE-LOOKUP") ,@arg-names)
- (if (and ,@(mapcar (lambda (val def)
- `(eq ,val ,def))
- (values-names) default-values))
- (multiple-value-bind ,(values-names)
- (progn ,@body)
- (,(symbolicate name "-CACHE-ENTER") ,@arg-names
- ,@(values-names))
- (values ,@(values-names)))
- (values ,@(values-names))))))))))))
+ (arg-names (mapcar #'car args))
+ (values-names (make-gensym-list values)))
+ (multiple-value-bind (body decls doc) (parse-body body-decls-doc)
+ `(progn
+ (define-hash-cache ,name ,args ,@options)
+ (defun ,name ,arg-names
+ ,@decls
+ ,doc
+ (cond #!+sb-show
+ ((not (boundp '*hash-caches-initialized-p*))
+ ;; This shouldn't happen, but it did happen to me
+ ;; when revising the type system, and it's a lot
+ ;; easier to figure out what what's going on with
+ ;; that kind of problem if the system can be kept
+ ;; alive until cold boot is complete. The recovery
+ ;; mechanism should definitely be conditional on some
+ ;; debugging feature (e.g. SB-SHOW) because it's big,
+ ;; duplicating all the BODY code. -- WHN
+ (/show0 ,name " too early in cold init, uncached")
+ (/show0 ,(first arg-names) "=..")
+ (/hexstr ,(first arg-names))
+ ,@body)
+ (t
+ (multiple-value-bind ,values-names
+ (,(symbolicate name "-CACHE-LOOKUP") ,@arg-names)
+ (if (and ,@(mapcar (lambda (val def)
+ `(eq ,val ,def))
+ values-names default-values))
+ (multiple-value-bind ,values-names
+ (progn ,@body)
+ (,(symbolicate name "-CACHE-ENTER") ,@arg-names
+ ,@values-names)
+ (values ,@values-names))
+ (values ,@values-names))))))))))
(defmacro define-cached-synonym
(name &optional (original (symbolicate "%" name)))
;;; our equality tests, because MEMBER and friends refer to EQLity.
;;; So:
(defun equal-but-no-car-recursion (x y)
- (cond
- ((eql x y) t)
- ((consp x)
- (and (consp y)
- (eql (car x) (car y))
- (equal-but-no-car-recursion (cdr x) (cdr y))))
- (t nil)))
+ (do () (())
+ (cond ((eql x y) (return t))
+ ((and (consp x)
+ (consp y)
+ (eql (pop x) (pop y))))
+ (t
+ (return)))))
\f
;;;; package idioms
(char= #\* (aref name 0))
(char= #\* (aref name (1- (length name))))))))
-;;; This function is to be called just before a change which would affect the
-;;; symbol value. We don't absolutely have to call this function before such
-;;; changes, since such changes to constants are given as undefined behavior,
-;;; it's nice to do so. To circumvent this you need code like this:
-;;;
-;;; (defvar foo)
-;;; (defun set-foo (x) (setq foo x))
-;;; (defconstant foo 42)
-;;; (set-foo 13)
-;;; foo => 13, (constantp 'foo) => t
-;;;
-;;; ...in which case you frankly deserve to lose.
-(defun about-to-modify-symbol-value (symbol action &optional (new-value nil valuep))
- (declare (symbol symbol))
- (multiple-value-bind (what continue)
- (when (eq :constant (info :variable :kind symbol))
- (cond ((eq symbol t)
- (values "Veritas aeterna. (can't ~@?)" nil))
- ((eq symbol nil)
- (values "Nihil ex nihil. (can't ~@?)" nil))
- ((keywordp symbol)
- (values "Can't ~@?." nil))
- (t
- (values "Constant modification: attempt to ~@?." t))))
- (when what
- (if continue
- (cerror "Modify the constant." what action symbol)
- (error what action symbol)))
- (when valuep
- ;; :VARIABLE :TYPE is in the db only if it is declared, so no need to
- ;; check.
- (let ((type (info :variable :type symbol)))
- (unless (sb!kernel::%%typep new-value type)
- (let ((spec (type-specifier type)))
- (error 'simple-type-error
- :format-control "Cannot ~@? to ~S (not of type ~S.)"
- :format-arguments (list action symbol new-value spec)
- :datum new-value
- :expected-type spec))))))
- (values))
-
;;; If COLD-FSET occurs not at top level, just treat it as an ordinary
;;; assignment instead of doing cold static linking. That way things like
;;; (FLET ((FROB (X) ..))
(unless (proper-list-of-length-p spec 2)
(error "malformed ONCE-ONLY binding spec: ~S" spec))
(let* ((name (first spec))
- (exp-temp (gensym (symbol-name name))))
+ (exp-temp (gensym "ONCE-ONLY")))
`(let ((,exp-temp ,(second spec))
- (,name (gensym "ONCE-ONLY-")))
+ (,name (sb!xc:gensym ,(symbol-name name))))
`(let ((,,name ,,exp-temp))
,,(frob (rest specs) body))))))))
\f
;;; guts of complex systems anyway, I replaced it too.)
(defmacro aver (expr)
`(unless ,expr
- (%failed-aver ,(format nil "~A" expr))))
+ (%failed-aver ',expr)))
-(defun %failed-aver (expr-as-string)
+(defun %failed-aver (expr)
;; hackish way to tell we're in a cold sbcl and output the
- ;; message before signallign error, as it may be this is too
+ ;; message before signalling error, as it may be this is too
;; early in the cold init.
(when (find-package "SB!C")
(fresh-line)
(write-line "failed AVER:")
- (write-line expr-as-string)
+ (write expr)
(terpri))
- (bug "~@<failed AVER: ~2I~_~S~:>" expr-as-string))
+ (bug "~@<failed AVER: ~2I~_~A~:>" expr))
(defun bug (format-control &rest format-arguments)
(error 'bug
;;; If X is a symbol, see whether it is present in *FEATURES*. Also
;;; handle arbitrary combinations of atoms using NOT, AND, OR.
(defun featurep (x)
- (etypecase x
+ (typecase x
(cons
(case (car x)
((:not not)
((:or or) (some #'featurep (cdr x)))
(t
(error "unknown operator in feature expression: ~S." x))))
- (symbol (not (null (memq x *features*))))))
+ (symbol (not (null (memq x *features*))))
+ (t
+ (error "invalid feature expression: ~S" x))))
+
\f
;;;; utilities for two-VALUES predicates
(let ((first? t)
maybe-print-space
(reversed-prints nil)
- (stream (gensym "STREAM")))
+ (stream (sb!xc:gensym "STREAM")))
(flet ((sref (slot-name)
`(,(symbolicate conc-name slot-name) structure)))
(dolist (slot-desc slot-descs)
(translate-logical-pathname possibly-logical-pathname)
possibly-logical-pathname))
-(defun deprecation-warning (bad-name &optional good-name)
- (warn "using deprecated ~S~@[, should use ~S instead~]"
- bad-name
- good-name))
+;;;; Deprecating stuff
+
+(defun normalize-deprecation-replacements (replacements)
+ (if (or (not (listp replacements))
+ (eq 'setf (car replacements)))
+ (list replacements)
+ replacements))
+
+(defun deprecation-error (since name replacements)
+ (error 'deprecation-error
+ :name name
+ :replacements (normalize-deprecation-replacements replacements)
+ :since since))
+
+(defun deprecation-warning (state since name replacements
+ &key (runtime-error (neq :early state)))
+ (warn (ecase state
+ (:early 'early-deprecation-warning)
+ (:late 'late-deprecation-warning)
+ (:final 'final-deprecation-warning))
+ :name name
+ :replacements (normalize-deprecation-replacements replacements)
+ :since since
+ :runtime-error runtime-error))
+
+(defun deprecated-function (since name replacements)
+ (lambda (&rest deprecated-function-args)
+ (declare (ignore deprecated-function-args))
+ (deprecation-error since name replacements)))
+
+(defun deprecation-compiler-macro (state since name replacements)
+ (lambda (form env)
+ (declare (ignore env))
+ (deprecation-warning state since name replacements)
+ form))
+
+;;; STATE is one of
+;;;
+;;; :EARLY, for a compile-time style-warning.
+;;; :LATE, for a compile-time full warning.
+;;; :FINAL, for a compile-time full warning and runtime error.
+;;;
+;;; Suggested duration of each stage is one year, but some things can move faster,
+;;; and some widely used legacy APIs might need to move slower. Internals we don't
+;;; usually add deprecation notes for, but sometimes an internal API actually has
+;;; several external users, in which case we try to be nice about it.
+;;;
+;;; When you deprecate something, note it here till it is fully gone: makes it
+;;; easier to keep things progressing orderly. Also add the relevant section
+;;; (or update it when deprecation proceeds) in the manual, in
+;;; deprecated.texinfo.
+;;;
+;;; EARLY:
+;;; - SB-THREAD::GET-MUTEX, since 1.0.37.33 (04/2010) -> Late: 01/2013
+;;; ^- initially deprecated without compile-time warning, hence the schedule
+;;; - SB-THREAD::SPINLOCK (type), since 1.0.53.11 (08/2011) -> Late: 08/2012
+;;; - SB-THREAD::MAKE-SPINLOCK, since 1.0.53.11 (08/2011) -> Late: 08/2012
+;;; - SB-THREAD::WITH-SPINLOCK, since 1.0.53.11 (08/2011) -> Late: 08/2012
+;;; - SB-THREAD::WITH-RECURSIVE-SPINLOCK, since 1.0.53.11 (08/2011) -> Late: 08/2012
+;;; - SB-THREAD::GET-SPINLOCK, since 1.0.53.11 (08/2011) -> Late: 08/2012
+;;; - SB-THREAD::RELEASE-SPINLOCK, since 1.0.53.11 (08/2011) -> Late: 08/2012
+;;; - SB-THREAD::SPINLOCK-VALUE, since 1.0.53.11 (08/2011) -> Late: 08/2012
+;;; - SB-THREAD::SPINLOCK-NAME, since 1.0.53.11 (08/2011) -> Late: 08/2012
+;;; - SETF SB-THREAD::SPINLOCK-NAME, since 1.0.53.11 (08/2011) -> Late: 08/2012
+;;; - SB-C::MERGE-TAIL-CALLS (policy), since 1.0.53.74 (11/2011) -> Late: 11/2012
+;;; - SB-EXT:QUIT, since 1.0.56.55 (05/2012) -> Late: 05/2013
+;;; - SB-UNIX:UNIX-EXIT, since 1.0.56.55 (05/2012) -> Late: 05/2013
+;;; - SB-DEBUG:*SHOW-ENTRY-POINT-DETAILS*, since 1.1.4.9 (02/2013) -> Late: 02/2014
+;;;
+;;; LATE:
+;;; - SB-SYS:OUTPUT-RAW-BYTES, since 1.0.8.16 (06/2007) -> Final: anytime
+;;; Note: make sure CLX doesn't use it anymore!
+;;; - SB-C::STACK-ALLOCATE-DYNAMIC-EXTENT (policy), since 1.0.19.7 -> Final: anytime
+;;; - SB-C::STACK-ALLOCATE-VECTOR (policy), since 1.0.19.7 -> Final: anytime
+;;; - SB-C::STACK-ALLOCATE-VALUE-CELLS (policy), since 1.0.19.7 -> Final: anytime
+;;; - SB-INTROSPECT:FUNCTION-ARGLIST, since 1.0.24.5 (01/2009) -> Final: anytime
+;;; - SB-THREAD:JOIN-THREAD-ERROR-THREAD, since 1.0.29.17 (06/2009) -> Final: 09/2012
+;;; - SB-THREAD:INTERRUPT-THREAD-ERROR-THREAD since 1.0.29.17 (06/2009) -> Final: 06/2012
+
+(defmacro define-deprecated-function (state since name replacements lambda-list &body body)
+ (let* ((replacements (normalize-deprecation-replacements replacements))
+ (doc
+ (let ((*package* (find-package :keyword))
+ (*print-pretty* nil))
+ (apply #'format nil
+ "~S has been deprecated as of SBCL ~A.~
+ ~#[~;~2%Use ~S instead.~;~2%~
+ Use ~S or ~S instead.~:;~2%~
+ Use~@{~#[~; or~] ~S~^,~} instead.~]"
+ name since replacements))))
+ `(progn
+ ,(ecase state
+ ((:early :late)
+ `(progn
+ (defun ,name ,lambda-list
+ ,doc
+ ,@body)))
+ ((:final)
+ `(progn
+ (declaim (ftype (function * nil) ,name))
+ (setf (fdefinition ',name)
+ (deprecated-function ',name ',replacements ,since))
+ (setf (documentation ',name 'function) ,doc))))
+ (setf (compiler-macro-function ',name)
+ (deprecation-compiler-macro ,state ,since ',name ',replacements)))))
+
+(defun check-deprecated-variable (name)
+ (let ((info (info :variable :deprecated name)))
+ (when info
+ (deprecation-warning (car info) (cdr info) name nil))))
+
+(defmacro define-deprecated-variable (state since name &key (value nil valuep) replacement)
+ `(progn
+ (setf (info :variable :deprecated ',name) (cons ,state ,since))
+ ,@(when (member state '(:early :late))
+ `((defvar ,name ,@(when valuep (list value))
+ ,(let ((*package* (find-package :keyword)))
+ (format nil
+ "~@<~S has been deprecated as of SBCL ~A~@[, use ~S instead~].~:>"
+ name since replacement)))))))
;;; Anaphoric macros
(defmacro awhen (test &body body)
(*print-length* (or (true *print-length*) 12)))
(funcall function))))
+;;; Returns a list of members of LIST. Useful for dealing with circular lists.
+;;; For a dotted list returns a secondary value of T -- in which case the
+;;; primary return value does not include the dotted tail.
+;;; If the maximum length is reached, return a secondary value of :MAYBE.
+(defun list-members (list &key max-length)
+ (when list
+ (do ((tail (cdr list) (cdr tail))
+ (members (list (car list)) (cons (car tail) members))
+ (count 0 (1+ count)))
+ ((or (not (consp tail)) (eq tail list)
+ (and max-length (>= count max-length)))
+ (values members (or (not (listp tail))
+ (and (>= count max-length) :maybe)))))))
+
;;; Default evaluator mode (interpeter / compiler)
(declaim (type (member :compile #!+sb-eval :interpret) *evaluator-mode*))
(if (eql x 0.0l0)
(make-unportable-float :long-float-negative-zero)
0.0l0))))
+
+;;; Signalling an error when trying to print an error condition is
+;;; generally a PITA, so whatever the failure encountered when
+;;; wondering about FILE-POSITION within a condition printer, 'tis
+;;; better silently to give up than to try to complain.
+(defun file-position-or-nil-for-error (stream &optional (pos nil posp))
+ ;; Arguably FILE-POSITION shouldn't be signalling errors at all; but
+ ;; "NIL if this cannot be determined" in the ANSI spec doesn't seem
+ ;; absolutely unambiguously to prohibit errors when, e.g., STREAM
+ ;; has been closed so that FILE-POSITION is a nonsense question. So
+ ;; my (WHN) impression is that the conservative approach is to
+ ;; IGNORE-ERRORS. (I encountered this failure from within a homebrew
+ ;; defsystemish operation where the ERROR-STREAM had been CL:CLOSEd,
+ ;; I think by nonlocally exiting through a WITH-OPEN-FILE, by the
+ ;; time an error was reported.)
+ (if posp
+ (ignore-errors (file-position stream pos))
+ (ignore-errors (file-position stream))))
+
+(defun stream-error-position-info (stream &optional position)
+ (unless (interactive-stream-p stream)
+ (let ((now (file-position-or-nil-for-error stream))
+ (pos position))
+ (when (and (not pos) now (plusp now))
+ ;; FILE-POSITION is the next character -- error is at the previous one.
+ (setf pos (1- now)))
+ (let (lineno colno)
+ (when (and pos
+ (< pos sb!xc:array-dimension-limit)
+ (file-position stream :start))
+ (let ((string
+ (make-string pos :element-type (stream-element-type stream))))
+ (when (= pos (read-sequence string stream))
+ ;; Lines count from 1, columns from 0. It's stupid and traditional.
+ (setq lineno (1+ (count #\Newline string))
+ colno (- pos (or (position #\Newline string :from-end t) 0)))))
+ (file-position-or-nil-for-error stream now))
+ (remove-if-not #'second
+ (list (list :line lineno)
+ (list :column colno)
+ (list :file-position pos)))))))
+
+(declaim (inline schwartzian-stable-sort-list))
+(defun schwartzian-stable-sort-list (list comparator &key key)
+ (if (null key)
+ (stable-sort (copy-list list) comparator)
+ (let* ((key (if (functionp key)
+ key
+ (symbol-function key)))
+ (wrapped (mapcar (lambda (x)
+ (cons x (funcall key x)))
+ list))
+ (sorted (stable-sort wrapped comparator :key #'cdr)))
+ (map-into sorted #'car sorted))))