Style improvements and minor bugfix from sb-fasteval integration.
[sbcl.git] / src / code / late-extensions.lisp
blobaf0a96d05f9814eda9b141c3b26ab998603bc431
1 ;;;; various extensions (including SB-INT "internal extensions")
2 ;;;; available both in the cross-compilation host Lisp and in the
3 ;;;; target SBCL, but which can't be defined on the target until until
4 ;;;; some significant amount of machinery (e.g. error-handling) is
5 ;;;; defined
7 ;;;; This software is part of the SBCL system. See the README file for
8 ;;;; more information.
9 ;;;;
10 ;;;; This software is derived from the CMU CL system, which was
11 ;;;; written at Carnegie Mellon University and released into the
12 ;;;; public domain. The software is in the public domain and is
13 ;;;; provided with absolutely no warranty. See the COPYING and CREDITS
14 ;;;; files for more information.
16 (in-package "SB!IMPL")
18 ;;; Is X a list for which LENGTH is meaningful, i.e. a list which is
19 ;;; not improper and which is not circular?
20 (defun list-with-length-p (x)
21 (values (ignore-errors (list-length x))))
23 ;;; not used in 0.7.8, but possibly useful for defensive programming
24 ;;; in e.g. (COERCE ... 'VECTOR)
25 ;;;(defun list-length-or-die (x)
26 ;;; (or (list-length x)
27 ;;; ;; not clear how to do this best:
28 ;;; ;; * Should this be a TYPE-ERROR? Colloquially that'd make
29 ;;; ;; lots of sense, but since I'm not sure how to express
30 ;;; ;; "noncircular list" as a Lisp type expression, coding
31 ;;; ;; it seems awkward.
32 ;;; ;; * Should the ERROR object include the offending value?
33 ;;; ;; Ordinarily that's helpful, but if the user doesn't have
34 ;;; ;; his printer set up to deal with cyclicity, we might not
35 ;;; ;; be doing him a favor by printing the object here.
36 ;;; ;; -- WHN 2002-10-19
37 ;;; (error "can't calculate length of cyclic list")))
39 ;;; This is used in constructing arg lists for debugger printing,
40 ;;; and when needing to print unbound slots in PCL.
41 (defstruct (unprintable-object
42 (:constructor make-unprintable-object (string))
43 (:print-object (lambda (x s)
44 (print-unreadable-object (x s)
45 (write-string (unprintable-object-string x) s))))
46 (:copier nil))
47 (string nil :read-only t))
49 ;;; Used internally, but it would be nice to provide something
50 ;;; like this for users as well.
51 ;;;
52 ;;; FIXME / IMPORTANT: On backends without interleaved raw slots,
53 ;; if the slot is raw, the address is correct only for
54 ;;; instances of the specified class, not its subclasses!
55 (defmacro define-structure-slot-addressor (name &key structure slot)
56 (let* ((dd (find-defstruct-description structure t))
57 (slotd (or (and dd (find slot (dd-slots dd) :key #'dsd-name))
58 (error "Slot ~S not found in ~S." slot structure)))
59 (index (dsd-index slotd))
60 #!-interleaved-raw-slots (raw-type (dsd-raw-type slotd)))
61 `(progn
62 (declaim (inline ,name))
63 (defun ,name (instance)
64 (declare (type ,structure instance) (optimize speed))
65 (truly-the
66 word
67 (+ (get-lisp-obj-address instance)
68 ,(+ (- sb!vm:instance-pointer-lowtag)
69 #!+interleaved-raw-slots
70 (* (+ sb!vm:instance-slots-offset index) sb!vm:n-word-bytes)
71 #!-interleaved-raw-slots
72 (* (if (eq t raw-type)
73 (+ sb!vm:instance-slots-offset index)
74 (- (1+ (sb!kernel::dd-instance-length dd))
75 sb!vm:instance-slots-offset index
76 (1- (sb!kernel::raw-slot-words raw-type))))
77 sb!vm:n-word-bytes))))))))
79 ;;;; ATOMIC-INCF and ATOMIC-DECF
81 (defun expand-atomic-frob (name specified-place diff env
82 &aux (place (sb!xc:macroexpand specified-place env)))
83 (declare (type (member atomic-incf atomic-decf) name))
84 (flet ((invalid-place ()
85 (error "Invalid first argument to ~S: ~S" name specified-place))
86 (compute-newval (old) ; used only if no atomic inc vop
87 `(logand (,(case name (atomic-incf '+) (atomic-decf '-)) ,old
88 (the sb!vm:signed-word ,diff)) sb!ext:most-positive-word))
89 (compute-delta () ; used only with atomic inc vop
90 `(logand ,(case name
91 (atomic-incf `(the sb!vm:signed-word ,diff))
92 (atomic-decf `(- (the sb!vm:signed-word ,diff))))
93 sb!ext:most-positive-word)))
94 (declare (ignorable #'compute-newval #'compute-delta))
95 (if (and (symbolp place)
96 (eq (info :variable :kind place) :global)
97 (type= (info :variable :type place) (specifier-type 'fixnum)))
98 ;; Global can't be lexically rebound.
99 (return-from expand-atomic-frob
100 `(truly-the fixnum (,(case name
101 (atomic-incf '%atomic-inc-symbol-global-value)
102 (atomic-decf '%atomic-dec-symbol-global-value))
103 ',place (the fixnum ,diff)))))
105 (unless (consp place)
106 (invalid-place))
107 (destructuring-bind (op &rest args) place
108 ;; FIXME: The lexical environment should not be disregarded.
109 ;; CL builtins can't be lexically rebound, but structure accessors can.
110 (case op
111 (aref
112 (unless (singleton-p (cdr args))
113 (invalid-place))
114 (with-unique-names (array)
115 `(let ((,array (the (simple-array word (*)) ,(car args))))
116 #!+(or x86 x86-64 ppc)
117 (%array-atomic-incf/word
118 ,array
119 (%check-bound ,array (array-dimension ,array 0) ,(cadr args))
120 ,(compute-delta))
121 #!-(or x86 x86-64 ppc)
122 ,(with-unique-names (index old-value)
123 `(without-interrupts
124 (let* ((,index ,(cadr args))
125 (,old-value (aref ,array ,index)))
126 (setf (aref ,array ,index) ,(compute-newval old-value))
127 ,old-value))))))
128 ((car cdr first rest)
129 (when (cdr args)
130 (invalid-place))
131 `(truly-the
132 fixnum
133 (,(case op
134 ((first car) (case name
135 (atomic-incf '%atomic-inc-car)
136 (atomic-decf '%atomic-dec-car)))
137 ((rest cdr) (case name
138 (atomic-incf '%atomic-inc-cdr)
139 (atomic-decf '%atomic-dec-cdr))))
140 ,(car args) (the fixnum ,diff))))
142 (when (or (cdr args)
143 ;; Because accessor info is identical for the writer and reader
144 ;; functions, without a SYMBOLP check this would erroneously allow
145 ;; (ATOMIC-INCF ((SETF STRUCT-SLOT) x))
146 (not (symbolp op))
147 (not (structure-instance-accessor-p op)))
148 (invalid-place))
149 (let* ((accessor-info (structure-instance-accessor-p op))
150 (slotd (cdr accessor-info))
151 (type (dsd-type slotd)))
152 (unless (and (eq 'sb!vm:word (dsd-raw-type slotd))
153 (type= (specifier-type type) (specifier-type 'sb!vm:word)))
154 (error "~S requires a slot of type (UNSIGNED-BYTE ~S), not ~S: ~S"
155 name sb!vm:n-word-bits type place))
156 (when (dsd-read-only slotd)
157 (error "Cannot use ~S with structure accessor for a read-only slot: ~S"
158 name place))
159 #!+(or x86 x86-64 ppc)
160 `(truly-the sb!vm:word
161 (%raw-instance-atomic-incf/word
162 (the ,(dd-name (car accessor-info)) ,@args)
163 ,(dsd-index slotd)
164 ,(compute-delta)))
165 ;; No threads outside x86 and x86-64 for now, so this is easy...
166 #!-(or x86 x86-64 ppc)
167 (with-unique-names (structure old-value)
168 `(without-interrupts
169 (let* ((,structure ,@args)
170 (,old-value (,op ,structure)))
171 (setf (,op ,structure) ,(compute-newval old-value))
172 ,old-value)))))))))
174 (def!macro atomic-incf (&environment env place &optional (diff 1))
175 #!+sb-doc
176 #.(format nil
177 "Atomically increments PLACE by DIFF, and returns the value of PLACE before
178 the increment.
180 PLACE must access one of the following:
181 - a DEFSTRUCT slot with declared type (UNSIGNED-BYTE ~D~:*)
182 or AREF of a (SIMPLE-ARRAY (UNSIGNED-BYTE ~D~:*) (*))
183 The type SB-EXT:WORD can be used for these purposes.
184 - CAR or CDR (respectively FIRST or REST) of a CONS.
185 - a variable defined using DEFGLOBAL with a proclaimed type of FIXNUM.
186 Macroexpansion is performed on PLACE before expanding ATOMIC-INCF.
188 Incrementing is done using modular arithmetic,
189 which is well-defined over two different domains:
190 - For structures and arrays, the operation accepts and produces
191 an (UNSIGNED-BYTE ~D~:*), and DIFF must be of type (SIGNED-BYTE ~D).
192 ATOMIC-INCF of #x~x by one results in #x0 being stored in PLACE.
193 - For other places, the domain is FIXNUM, and DIFF must be a FIXNUM.
194 ATOMIC-INCF of #x~x by one results in #x~x
195 being stored in PLACE.
197 DIFF defaults to 1.
199 EXPERIMENTAL: Interface subject to change."
200 sb!vm:n-word-bits most-positive-word
201 sb!xc:most-positive-fixnum sb!xc:most-negative-fixnum)
202 (expand-atomic-frob 'atomic-incf place diff env))
204 (defmacro atomic-decf (&environment env place &optional (diff 1))
205 #!+sb-doc
206 #.(format nil
207 "Atomically decrements PLACE by DIFF, and returns the value of PLACE before
208 the decrement.
210 PLACE must access one of the following:
211 - a DEFSTRUCT slot with declared type (UNSIGNED-BYTE ~D~:*)
212 or AREF of a (SIMPLE-ARRAY (UNSIGNED-BYTE ~D~:*) (*))
213 The type SB-EXT:WORD can be used for these purposes.
214 - CAR or CDR (respectively FIRST or REST) of a CONS.
215 - a variable defined using DEFGLOBAL with a proclaimed type of FIXNUM.
216 Macroexpansion is performed on PLACE before expanding ATOMIC-DECF.
218 Decrementing is done using modular arithmetic,
219 which is well-defined over two different domains:
220 - For structures and arrays, the operation accepts and produces
221 an (UNSIGNED-BYTE ~D~:*), and DIFF must be of type (SIGNED-BYTE ~D).
222 ATOMIC-DECF of #x0 by one results in #x~x being stored in PLACE.
223 - For other places, the domain is FIXNUM, and DIFF must be a FIXNUM.
224 ATOMIC-DECF of #x~x by one results in #x~x
225 being stored in PLACE.
227 DIFF defaults to 1.
229 EXPERIMENTAL: Interface subject to change."
230 sb!vm:n-word-bits most-positive-word
231 sb!xc:most-negative-fixnum sb!xc:most-positive-fixnum)
232 (expand-atomic-frob 'atomic-decf place diff env))
234 ;; Interpreter stubs for ATOMIC-INCF.
235 #!+(or x86 x86-64 ppc)
236 (progn
237 ;; argument types are declared in vm-fndb
238 (defun %array-atomic-incf/word (array index diff)
239 (%array-atomic-incf/word array index diff))
240 (defun %raw-instance-atomic-incf/word (instance index diff)
241 (%raw-instance-atomic-incf/word instance index diff)))
243 (defun spin-loop-hint ()
244 #!+sb-doc
245 "Hints the processor that the current thread is spin-looping."
246 (spin-loop-hint))
248 (defun call-hooks (kind hooks &key (on-error :error))
249 (dolist (hook hooks)
250 (handler-case
251 (funcall hook)
252 (serious-condition (c)
253 (if (eq :warn on-error)
254 (warn "Problem running ~A hook ~S:~% ~A" kind hook c)
255 (with-simple-restart (continue "Skip this ~A hook." kind)
256 (error "Problem running ~A hook ~S:~% ~A" kind hook c)))))))
258 ;;;; DEFGLOBAL
260 (defmacro-mundanely defglobal (name value &optional (doc nil docp))
261 #!+sb-doc
262 "Defines NAME as a global variable that is always bound. VALUE is evaluated
263 and assigned to NAME both at compile- and load-time, but only if NAME is not
264 already bound.
266 Global variables share their values between all threads, and cannot be
267 locally bound, declared special, defined as constants, and neither bound
268 nor defined as symbol macros.
270 See also the declarations SB-EXT:GLOBAL and SB-EXT:ALWAYS-BOUND."
271 (let ((boundp (make-symbol "BOUNDP")))
272 `(progn
273 (eval-when (:compile-toplevel)
274 (let ((,boundp (boundp ',name)))
275 (%compiler-defglobal ',name :always-bound
276 (unless ,boundp ,value) (not ,boundp))))
277 (let ((,boundp (boundp ',name)))
278 (%defglobal ',name (unless ,boundp ,value) ,boundp ',doc ,docp
279 (sb!c:source-location))))))
281 (defmacro-mundanely define-load-time-global (name value &optional (doc nil docp))
282 #!+sb-doc
283 "Defines NAME as a global variable that is always bound. VALUE is evaluated
284 and assigned to NAME at load-time, but only if NAME is not already bound.
286 Attempts to read NAME at compile-time will signal an UNBOUND-VARIABLE error
287 unless it has otherwise been assigned a value.
289 See also DEFGLOBAL which assigns the VALUE at compile-time too."
290 (let ((boundp (make-symbol "BOUNDP")))
291 `(progn
292 (eval-when (:compile-toplevel)
293 (%compiler-defglobal ',name :eventually nil nil))
294 (let ((,boundp (boundp ',name)))
295 (%defglobal ',name (unless ,boundp ,value) ,boundp ',doc ,docp
296 (sb!c:source-location))))))
298 (defun %compiler-defglobal (name always-boundp value assign-it-p)
299 (sb!xc:proclaim `(global ,name))
300 (when assign-it-p
301 #-sb-xc-host
302 (set-symbol-global-value name value)
303 #+sb-xc-host
304 (set name value))
305 (sb!c::process-variable-declaration
306 name 'always-bound
307 ;; don't "weaken" the proclamation if it's in fact always bound now
308 (if (eq (info :variable :always-bound name) :always-bound)
309 :always-bound
310 always-boundp)))
312 (defun %defglobal (name value boundp doc docp source-location)
313 (%compiler-defglobal name :always-bound value (not boundp))
314 (when docp
315 (setf (fdocumentation name 'variable) doc))
316 (sb!c:with-source-location (source-location)
317 (setf (info :source-location :variable name) source-location))
318 name)
320 ;;;; WAIT-FOR -- waiting on arbitrary conditions
322 (defun %%wait-for (test stop-sec stop-usec)
323 (declare (function test))
324 (labels ((try ()
325 (declare (optimize (safety 0)))
326 (awhen (funcall test)
327 (return-from %%wait-for it)))
328 (tick (sec usec)
329 (declare (type fixnum sec usec))
330 ;; TICK is microseconds
331 (+ usec (* 1000000 sec)))
332 (get-tick ()
333 (multiple-value-call #'tick
334 (decode-internal-time (get-internal-real-time)))))
335 (let* ((timeout-tick (when stop-sec (tick stop-sec stop-usec)))
336 (start (get-tick))
337 ;; Rough estimate of how long a single attempt takes.
338 (try-ticks (progn
339 (try) (try) (try)
340 (max 1 (truncate (- (get-tick) start) 3)))))
341 ;; Scale sleeping between attempts:
343 ;; Start by sleeping for as many ticks as an average attempt
344 ;; takes, then doubling for each attempt.
346 ;; Max out at 0.1 seconds, or the 2 x time of a single try,
347 ;; whichever is longer -- with a hard cap of 10 seconds.
349 ;; FIXME: Maybe the API should have a :MAX-SLEEP argument?
350 (loop with max-ticks = (max 100000 (min (* 2 try-ticks)
351 (expt 10 7)))
352 for scale of-type fixnum = 1
353 then (let ((x (logand most-positive-fixnum (* 2 scale))))
354 (if (> scale x)
355 most-positive-fixnum
357 do (try)
358 (let* ((now (get-tick))
359 (sleep-ticks (min (* try-ticks scale) max-ticks))
360 (sleep
361 (if timeout-tick
362 ;; If sleep would take us past the
363 ;; timeout, shorten it so it's just
364 ;; right.
365 (if (>= (+ now sleep-ticks) timeout-tick)
366 (- timeout-tick now)
367 sleep-ticks)
368 sleep-ticks)))
369 (declare (type fixnum sleep))
370 (cond ((plusp sleep)
371 ;; microseconds to seconds and nanoseconds
372 (multiple-value-bind (sec nsec)
373 (truncate (* 1000 sleep) (expt 10 9))
374 (with-interrupts
375 (sb!unix:nanosleep sec nsec))))
377 (return-from %%wait-for nil))))))))
379 (defun %wait-for (test timeout)
380 (declare (function test))
381 (tagbody
382 :restart
383 (multiple-value-bind (to-sec to-usec stop-sec stop-usec deadlinep)
384 (decode-timeout timeout)
385 (declare (ignore to-sec to-usec))
386 (return-from %wait-for
387 (or (%%wait-for test stop-sec stop-usec)
388 (when deadlinep
389 (signal-deadline)
390 (go :restart)))))))
392 (defmacro wait-for (test-form &key timeout)
393 #!+sb-doc
394 "Wait until TEST-FORM evaluates to true, then return its primary value.
395 If TIMEOUT is provided, waits at most approximately TIMEOUT seconds before
396 returning NIL.
398 If WITH-DEADLINE has been used to provide a global deadline, signals a
399 DEADLINE-TIMEOUT if TEST-FORM doesn't evaluate to true before the
400 deadline.
402 Experimental: subject to change without prior notice."
403 `(dx-flet ((wait-for-test () (progn ,test-form)))
404 (%wait-for #'wait-for-test ,timeout)))
406 (defmacro with-progressive-timeout ((name &key seconds)
407 &body body)
408 #!+sb-doc
409 "Binds NAME as a local function for BODY. Each time #'NAME is called, it
410 returns SECONDS minus the time that has elapsed since BODY was entered, or
411 zero if more time than SECONDS has elapsed. If SECONDS is NIL, #'NAME
412 returns NIL each time."
413 (with-unique-names (deadline time-left sec)
414 `(let* ((,sec ,seconds)
415 (,deadline
416 (when ,sec
417 (+ (get-internal-real-time)
418 (round (* ,seconds internal-time-units-per-second))))))
419 (flet ((,name ()
420 (when ,deadline
421 (let ((,time-left (- ,deadline (get-internal-real-time))))
422 (if (plusp ,time-left)
423 (* (coerce ,time-left 'single-float)
424 (load-time-value (/ 1.0f0 internal-time-units-per-second) t))
425 0)))))
426 ,@body))))
428 (defmacro atomic-update (place update-fn &rest arguments &environment env)
429 #!+sb-doc
430 "Updates PLACE atomically to the value returned by calling function
431 designated by UPDATE-FN with ARGUMENTS and the previous value of PLACE.
433 PLACE may be read and UPDATE-FN evaluated and called multiple times before the
434 update succeeds: atomicity in this context means that value of place did not
435 change between the time it was read, and the time it was replaced with the
436 computed value.
438 PLACE can be any place supported by SB-EXT:COMPARE-AND-SWAP.
440 Examples:
442 ;;; Conses T to the head of FOO-LIST.
443 (defstruct foo list)
444 (defvar *foo* (make-foo))
445 (atomic-update (foo-list *foo*) #'cons t)
447 (let ((x (cons :count 0)))
448 (mapc #'sb-thread:join-thread
449 (loop repeat 1000
450 collect (sb-thread:make-thread
451 (lambda ()
452 (loop repeat 1000
453 do (atomic-update (cdr x) #'1+)
454 (sleep 0.00001))))))
455 ;; Guaranteed to be (:COUNT . 1000000) -- if you replace
456 ;; atomic update with (INCF (CDR X)) above, the result becomes
457 ;; unpredictable.
460 (multiple-value-bind (vars vals old new cas-form read-form)
461 (get-cas-expansion place env)
462 `(let* (,@(mapcar 'list vars vals)
463 (,old ,read-form))
464 (loop for ,new = (funcall ,update-fn ,@arguments ,old)
465 until (eq ,old (setf ,old ,cas-form))
466 finally (return ,new)))))
468 (defmacro atomic-push (obj place &environment env)
469 #!+sb-doc
470 "Like PUSH, but atomic. PLACE may be read multiple times before
471 the operation completes -- the write does not occur until such time
472 that no other thread modified PLACE between the read and the write.
474 Works on all CASable places."
475 (multiple-value-bind (vars vals old new cas-form read-form)
476 (get-cas-expansion place env)
477 `(let* (,@(mapcar 'list vars vals)
478 (,old ,read-form)
479 (,new (cons ,obj ,old)))
480 (loop until (eq ,old (setf ,old ,cas-form))
481 do (setf (cdr ,new) ,old)
482 finally (return ,new)))))
484 (defmacro atomic-pop (place &environment env)
485 #!+sb-doc
486 "Like POP, but atomic. PLACE may be read multiple times before
487 the operation completes -- the write does not occur until such time
488 that no other thread modified PLACE between the read and the write.
490 Works on all CASable places."
491 (multiple-value-bind (vars vals old new cas-form read-form)
492 (get-cas-expansion place env)
493 `(let* (,@(mapcar 'list vars vals))
494 (loop for ,old = ,read-form
495 for ,new = (cdr ,old)
496 until (eq ,old (setf ,old ,cas-form))
497 finally (return (car ,old))))))
499 (defun split-version-string (string)
500 (loop with subversion and start = 0
501 with end = (length string)
502 when (setf (values subversion start)
503 (parse-integer string :start start :junk-allowed t))
504 collect it
505 while (and subversion
506 (< start end)
507 (char= (char string start) #\.))
508 do (incf start)))
510 (defun version>= (x y)
511 (unless (or x y)
512 (return-from version>= t))
513 (let ((head-x (or (first x) 0))
514 (head-y (or (first y) 0)))
515 (or (> head-x head-y)
516 (and (= head-x head-y)
517 (version>= (rest x) (rest y))))))
519 (defun assert-version->= (&rest subversions)
520 #!+sb-doc
521 "Asserts that the current SBCL is of version equal to or greater than
522 the version specified in the arguments. A continuable error is signaled
523 otherwise.
525 The arguments specify a sequence of subversion numbers in big endian order.
526 They are compared lexicographically with the runtime version, and versions
527 are treated as though trailed by an unbounded number of 0s.
529 For example, (assert-version->= 1 1 4) asserts that the current SBCL is
530 version 1.1.4[.0.0...] or greater, and (assert-version->= 1) that it is
531 version 1[.0.0...] or greater."
532 (let ((version (split-version-string (lisp-implementation-version))))
533 (unless (version>= version subversions)
534 (cerror "Disregard this version requirement."
535 "SBCL ~A is too old for this program (version ~{~A~^.~} ~
536 or later is required)."
537 (lisp-implementation-version)
538 subversions))))
540 ;;; Signalling an error when trying to print an error condition is
541 ;;; generally a PITA, so whatever the failure encountered when
542 ;;; wondering about FILE-POSITION within a condition printer, 'tis
543 ;;; better silently to give up than to try to complain.
544 (defun file-position-or-nil-for-error (stream &optional (pos nil posp))
545 ;; Arguably FILE-POSITION shouldn't be signalling errors at all; but
546 ;; "NIL if this cannot be determined" in the ANSI spec doesn't seem
547 ;; absolutely unambiguously to prohibit errors when, e.g., STREAM
548 ;; has been closed so that FILE-POSITION is a nonsense question. So
549 ;; my (WHN) impression is that the conservative approach is to
550 ;; IGNORE-ERRORS. (I encountered this failure from within a homebrew
551 ;; defsystemish operation where the ERROR-STREAM had been CL:CLOSEd,
552 ;; I think by nonlocally exiting through a WITH-OPEN-FILE, by the
553 ;; time an error was reported.)
554 (ignore-errors
555 (if posp
556 (file-position stream pos)
557 (file-position stream))))
559 (defun stream-error-position-info (stream &optional position)
560 (when (and (not position) (form-tracking-stream-p stream))
561 (let ((line/col (line/col-from-charpos stream)))
562 (return-from stream-error-position-info
563 `((:line ,(car line/col))
564 (:column ,(cdr line/col))
565 ,@(let ((position (file-position-or-nil-for-error stream)))
566 ;; FIXME: 1- is technically broken for multi-byte external
567 ;; encodings, albeit bug-compatible with the broken code in
568 ;; the general case (below) for non-form-tracking-streams.
569 ;; i.e. If you position to this byte, it might not be the
570 ;; first byte of any character.
571 (when position `((:file-position ,(1- position)))))))))
573 ;; Give up early for interactive streams and non-character stream.
574 (when (or (ignore-errors (interactive-stream-p stream))
575 (not (subtypep (ignore-errors (stream-element-type stream))
576 'character)))
577 (return-from stream-error-position-info))
579 (flet ((read-content (old-position position)
580 "Read the content of STREAM into a buffer in order to count
581 lines and columns."
582 (unless (and old-position position
583 (< position sb!xc:array-dimension-limit))
584 (return-from read-content))
585 (let ((content
586 (make-string position :element-type (stream-element-type stream))))
587 (when (and (file-position-or-nil-for-error stream :start)
588 (eql position (ignore-errors (read-sequence content stream))))
589 (file-position-or-nil-for-error stream old-position)
590 content)))
591 ;; Lines count from 1, columns from 0. It's stupid and
592 ;; traditional.
593 (line (string)
594 (1+ (count #\Newline string)))
595 (column (string position)
596 (- position (or (position #\Newline string :from-end t) 0))))
597 (let* ((stream-position (file-position-or-nil-for-error stream))
598 (position (or position
599 ;; FILE-POSITION is the next character --
600 ;; error is at the previous one.
601 (and stream-position (plusp stream-position)
602 (1- stream-position))))
603 (content (read-content stream-position position)))
604 `(,@(when content `((:line ,(line content))
605 (:column ,(column content position))))
606 ,@(when position `((:file-position ,position)))))))