(setf (fdocumentation '*readtable* 'variable)
"Variable bound to current readtable.")
-;;; a standard Lisp readtable. This is for recovery from broken
-;;; read-tables (and for WITH-STANDARD-IO-SYNTAX), and should not
-;;; normally be user-visible.
-(defvar *standard-readtable*)
+;;; A standard Lisp readtable (once cold-init is through). This is for
+;;; recovery from broken read-tables (and for
+;;; WITH-STANDARD-IO-SYNTAX), and should not normally be user-visible.
+(defvar *standard-readtable* nil)
(defvar *old-package* nil
#!+sb-doc
(elt *constituent-trait-table* (char-code char))
+char-attr-constituent+))
\f
-;;;; readtable operations
+;;;; Readtable Operations
+
+(defun assert-not-standard-readtable (readtable operation)
+ (when (eq readtable *standard-readtable*)
+ (cerror "Frob it anyway!" 'standard-readtable-modified-error
+ :operation operation)))
+
+(defun readtable-case (readtable)
+ (%readtable-case readtable))
+
+(defun (setf readtable-case) (case readtable)
+ (assert-not-standard-readtable readtable '(setf readtable-case))
+ (setf (%readtable-case readtable) case))
(defun shallow-replace/eql-hash-table (to from)
(maphash (lambda (k v) (setf (gethash k to) v)) from))
-(defun copy-readtable (&optional (from-readtable *readtable*)
- to-readtable)
+(defun copy-readtable (&optional (from-readtable *readtable*) to-readtable)
+ (assert-not-standard-readtable to-readtable 'copy-readtable)
(let ((really-from-readtable (or from-readtable *standard-readtable*))
(really-to-readtable (or to-readtable (make-readtable))))
(replace (character-attribute-array really-to-readtable)
really-to-readtable))
(defun set-syntax-from-char (to-char from-char &optional
- (to-readtable *readtable*) (from-readtable ()))
+ (to-readtable *readtable*) (from-readtable nil))
#!+sb-doc
"Causes the syntax of TO-CHAR to be the same as FROM-CHAR in the optional
readtable (defaults to the current readtable). The FROM-TABLE defaults to the
standard Lisp readtable when NIL."
+ (assert-not-standard-readtable to-readtable 'set-syntax-from-char)
(let ((really-from-readtable (or from-readtable *standard-readtable*)))
(let ((att (get-cat-entry from-char really-from-readtable))
(mac (get-raw-cmt-entry from-char really-from-readtable))
:test #'char= :key #'car)))
(set-cat-entry to-char att to-readtable)
(set-cmt-entry to-char mac to-readtable)
- (when from-dpair
- (cond
- (to-dpair
- (let ((table (cdr to-dpair)))
- (clrhash table)
- (shallow-replace/eql-hash-table table (cdr from-dpair))))
- (t
- (let ((pair (cons to-char (make-hash-table))))
- (shallow-replace/eql-hash-table (cdr pair) (cdr from-dpair))
+ (cond ((and (not from-dpair) (not to-dpair)))
+ ((and (not from-dpair) to-dpair)
(setf (dispatch-tables to-readtable)
- (push pair (dispatch-tables to-readtable)))))))))
+ (remove to-dpair (dispatch-tables to-readtable))))
+ (to-dpair
+ (let ((table (cdr to-dpair)))
+ (clrhash table)
+ (shallow-replace/eql-hash-table table (cdr from-dpair))))
+ (t
+ (let ((pair (cons to-char (make-hash-table))))
+ (shallow-replace/eql-hash-table (cdr pair) (cdr from-dpair))
+ (setf (dispatch-tables to-readtable)
+ (push pair (dispatch-tables to-readtable))))))))
t)
(defun set-macro-character (char function &optional
(non-terminatingp nil)
- (readtable *readtable*))
+ (rt-designator *readtable*))
#!+sb-doc
"Causes CHAR to be a macro character which invokes FUNCTION when seen
by the reader. The NON-TERMINATINGP flag can be used to make the macro
character non-terminating, i.e. embeddable in a symbol name."
- (let ((designated-readtable (or readtable *standard-readtable*)))
+ (let ((designated-readtable (or rt-designator *standard-readtable*))
+ (function (%coerce-callable-to-fun function)))
+ (assert-not-standard-readtable designated-readtable 'set-macro-character)
(set-cat-entry char (if non-terminatingp
+char-attr-constituent+
+char-attr-terminating-macro+)
(set-cmt-entry char function designated-readtable)
t)) ; (ANSI-specified return value)
-(defun get-macro-character (char &optional (readtable *readtable*))
+(defun get-macro-character (char &optional (rt-designator *readtable*))
#!+sb-doc
"Return the function associated with the specified CHAR which is a macro
character, or NIL if there is no such function. As a second value, return
T if CHAR is a macro character which is non-terminating, i.e. which can
be embedded in a symbol name."
- (let* ((designated-readtable (or readtable *standard-readtable*))
+ (let* ((designated-readtable (or rt-designator *standard-readtable*))
;; the first return value: a FUNCTION if CHAR is a macro
;; character, or NIL otherwise
(fun-value (get-raw-cmt-entry char designated-readtable)))
(values fun-value
;; NON-TERMINATING-P return value:
(if fun-value
- (or (constituentp char)
- (not (terminating-macrop char)))
+ (or (constituentp char designated-readtable)
+ (not (terminating-macrop char designated-readtable)))
;; ANSI's definition of GET-MACRO-CHARACTER says this
;; value is NIL when CHAR is not a macro character.
;; I.e. this value means not just "non-terminating
;; character?" but "non-terminating macro character?".
nil))))
+
+
+(defun make-char-dispatch-table ()
+ (make-hash-table))
+
+(defun make-dispatch-macro-character (char &optional
+ (non-terminating-p nil)
+ (rt *readtable*))
+ #!+sb-doc
+ "Cause CHAR to become a dispatching macro character in readtable (which
+ defaults to the current readtable). If NON-TERMINATING-P, the char will
+ be non-terminating."
+ ;; Checks already for standard readtable modification.
+ (set-macro-character char #'read-dispatch-char non-terminating-p rt)
+ (let* ((dalist (dispatch-tables rt))
+ (dtable (cdr (find char dalist :test #'char= :key #'car))))
+ (cond (dtable
+ (error "The dispatch character ~S already exists." char))
+ (t
+ (setf (dispatch-tables rt)
+ (push (cons char (make-char-dispatch-table)) dalist)))))
+ t)
+
+(defun set-dispatch-macro-character (disp-char sub-char function
+ &optional (rt-designator *readtable*))
+ #!+sb-doc
+ "Cause FUNCTION to be called whenever the reader reads DISP-CHAR
+ followed by SUB-CHAR."
+ ;; Get the dispatch char for macro (error if not there), diddle
+ ;; entry for sub-char.
+ (let* ((sub-char (char-upcase sub-char))
+ (readtable (or rt-designator *standard-readtable*)))
+ (assert-not-standard-readtable readtable 'set-dispatch-macro-character)
+ (when (digit-char-p sub-char)
+ (error "SUB-CHAR must not be a decimal digit: ~S" sub-char))
+ (let ((dpair (find disp-char (dispatch-tables readtable)
+ :test #'char= :key #'car)))
+ (if dpair
+ (setf (gethash sub-char (cdr dpair)) (coerce function 'function))
+ (error "~S is not a dispatch char." disp-char))))
+ t)
+
+(defun get-dispatch-macro-character (disp-char sub-char
+ &optional (rt-designator *readtable*))
+ #!+sb-doc
+ "Return the macro character function for SUB-CHAR under DISP-CHAR
+ or NIL if there is no associated function."
+ (let* ((sub-char (char-upcase sub-char))
+ (readtable (or rt-designator *standard-readtable*))
+ (dpair (find disp-char (dispatch-tables readtable)
+ :test #'char= :key #'car)))
+ (if dpair
+ (values (gethash sub-char (cdr dpair)))
+ (error "~S is not a dispatch char." disp-char))))
+
\f
;;;; definitions to support internal programming conventions
\f
;;;; temporary initialization hack
+;; Install the (easy) standard macro-chars into *READTABLE*.
(defun !cold-init-standard-readtable ()
- (setq *standard-readtable* (make-readtable))
+ (/show0 "entering !cold-init-standard-readtable")
;; All characters get boring defaults in MAKE-READTABLE. Now we
;; override the boring defaults on characters which need more
;; interesting behavior.
- (let ((*readtable* *standard-readtable*))
-
- (flet ((whitespaceify (char)
- (set-cmt-entry char nil)
- (set-cat-entry char +char-attr-whitespace+)))
- (whitespaceify (code-char tab-char-code))
- (whitespaceify #\Newline)
- (whitespaceify #\Space)
- (whitespaceify (code-char form-feed-char-code))
- (whitespaceify (code-char return-char-code)))
-
- (set-cat-entry #\\ +char-attr-single-escape+)
- (set-cmt-entry #\\ nil)
-
- (set-cat-entry #\| +char-attr-multiple-escape+)
- (set-cmt-entry #\| nil)
-
- ;; Easy macro-character definitions are in this source file.
- (set-macro-character #\" #'read-string)
- (set-macro-character #\' #'read-quote)
- (set-macro-character #\( #'read-list)
- (set-macro-character #\) #'read-right-paren)
- (set-macro-character #\; #'read-comment)
- ;; (The hairier macro-character definitions, for #\# and #\`, are
- ;; defined elsewhere, in their own source files.)
-
- ;; all constituents
- (do ((ichar 0 (1+ ichar))
- (char))
- ((= ichar base-char-code-limit))
- (setq char (code-char ichar))
- (when (constituentp char *standard-readtable*)
- (set-cmt-entry char nil)))))
+ (flet ((whitespaceify (char)
+ (set-cmt-entry char nil)
+ (set-cat-entry char +char-attr-whitespace+)))
+ (whitespaceify (code-char tab-char-code))
+ (whitespaceify #\Newline)
+ (whitespaceify #\Space)
+ (whitespaceify (code-char form-feed-char-code))
+ (whitespaceify (code-char return-char-code)))
+
+ (set-cat-entry #\\ +char-attr-single-escape+)
+ (set-cmt-entry #\\ nil)
+
+ (set-cat-entry #\| +char-attr-multiple-escape+)
+ (set-cmt-entry #\| nil)
+
+ ;; Easy macro-character definitions are in this source file.
+ (set-macro-character #\" #'read-string)
+ (set-macro-character #\' #'read-quote)
+ (set-macro-character #\( #'read-list)
+ (set-macro-character #\) #'read-right-paren)
+ (set-macro-character #\; #'read-comment)
+ ;; (The hairier macro-character definitions, for #\# and #\`, are
+ ;; defined elsewhere, in their own source files.)
+
+ ;; all constituents
+ (do ((ichar 0 (1+ ichar))
+ (char))
+ ((= ichar base-char-code-limit))
+ (setq char (code-char ichar))
+ (when (constituentp char)
+ (set-cmt-entry char nil)))
+
+ (/show0 "leaving !cold-init-standard-readtable"))
\f
;;;; implementation of the read buffer
(defvar *read-buffer*)
-(defvar *read-buffer-length*)
-;;; FIXME: Is it really helpful to have *READ-BUFFER-LENGTH* be a
-;;; separate variable instead of just calculating it on the fly as
-;;; (LENGTH *READ-BUFFER*)?
(defvar *inch-ptr*) ; *OUCH-PTR* always points to next char to write.
(defvar *ouch-ptr*) ; *INCH-PTR* always points to next char to read.
-(declaim (type index *read-buffer-length* *inch-ptr* *ouch-ptr*))
+(declaim (type index *inch-ptr* *ouch-ptr*))
(declaim (type (simple-array character (*)) *read-buffer*))
(declaim (inline reset-read-buffer))
(declaim (inline ouch-read-buffer))
(defun ouch-read-buffer (char)
;; When buffer overflow
- (when (>= *ouch-ptr* *read-buffer-length*)
+ (let ((op *ouch-ptr*))
+ (declare (optimize (sb!c::insert-array-bounds-checks 0)))
+ (when (>= op (length *read-buffer*))
;; Size should be doubled.
- (grow-read-buffer))
- (setf (elt *read-buffer* *ouch-ptr*) char)
- (setq *ouch-ptr* (1+ *ouch-ptr*)))
+ (grow-read-buffer))
+ (setf (elt *read-buffer* op) char)
+ (setq *ouch-ptr* (1+ op))))
(defun grow-read-buffer ()
(let* ((rbl (length *read-buffer*))
(new-length (* 2 rbl))
(new-buffer (make-string new-length)))
- (setq *read-buffer* (replace new-buffer *read-buffer*))
- (setq *read-buffer-length* new-length)))
+ (setq *read-buffer* (replace new-buffer *read-buffer*))))
(defun inch-read-buffer ()
(if (>= *inch-ptr* *ouch-ptr*)
(defun read-buffer-to-string ()
(subseq *read-buffer* 0 *ouch-ptr*))
-(defmacro with-reader ((&optional recursive-p) &body body)
- #!+sb-doc
- "If RECURSIVE-P is NIL, bind *READER-BUFFER* and its subservient
-variables to allow for nested and thread safe reading."
- `(if ,recursive-p
- (progn ,@body)
- (let* ((*read-buffer* (make-string 128))
- (*read-buffer-length* 128)
- (*ouch-ptr* 0)
- (*inch-ptr* 0))
- ,@body)))
+(defmacro with-read-buffer (() &body body)
+ `(let* ((*read-buffer* (make-string 128))
+ (*ouch-ptr* 0)
+ (*inch-ptr* 0))
+ ,@body))
+
+(declaim (inline read-buffer-boundp))
+(defun read-buffer-boundp ()
+ (and (boundp '*read-buffer*)
+ (boundp '*ouch-ptr*)
+ (boundp '*inch-ptr*)))
+
+(defun check-for-recursive-read (stream recursive-p operator-name)
+ (when (and recursive-p (not (read-buffer-boundp)))
+ (simple-reader-error
+ stream
+ "~A was invoked with RECURSIVE-P being true outside ~
+ of a recursive read operation."
+ `(,operator-name))))
\f
;;;; READ-PRESERVING-WHITESPACE, READ-DELIMITED-LIST, and READ
(declaim (special *standard-input*))
-;;; READ-PRESERVING-WHITESPACE behaves just like READ, only it makes
-;;; sure to leave terminating whitespace in the stream. (This is a
-;;; COMMON-LISP exported symbol.)
-(defun read-preserving-whitespace (&optional (stream *standard-input*)
- (eof-error-p t)
- (eof-value nil)
- (recursivep nil))
- #!+sb-doc
- "Read from STREAM and return the value read, preserving any whitespace
- that followed the object."
- (if recursivep
+;;; Like READ-PRESERVING-WHITESPACE, but doesn't check the read buffer
+;;; for being set up properly.
+(defun %read-preserving-whitespace (stream eof-error-p eof-value recursive-p)
+ (if recursive-p
;; a loop for repeating when a macro returns nothing
(loop
(let ((char (read-char stream eof-error-p *eof-object*)))
;; Repeat if macro returned nothing.
(when result
(return (unless *read-suppress* (car result)))))))))
- (with-reader ()
- (let ((*sharp-equal-alist* nil))
- (read-preserving-whitespace stream eof-error-p eof-value t)))))
+ (let ((*sharp-equal-alist* nil))
+ (with-read-buffer ()
+ (%read-preserving-whitespace stream eof-error-p eof-value t)))))
+
+;;; READ-PRESERVING-WHITESPACE behaves just like READ, only it makes
+;;; sure to leave terminating whitespace in the stream. (This is a
+;;; COMMON-LISP exported symbol.)
+(defun read-preserving-whitespace (&optional (stream *standard-input*)
+ (eof-error-p t)
+ (eof-value nil)
+ (recursive-p nil))
+ #!+sb-doc
+ "Read from STREAM and return the value read, preserving any whitespace
+ that followed the object."
+ (check-for-recursive-read stream recursive-p 'read-preserving-whitespace)
+ (%read-preserving-whitespace stream eof-error-p eof-value recursive-p))
;;; Return NIL or a list with one thing, depending.
;;;
(defun read (&optional (stream *standard-input*)
(eof-error-p t)
- (eof-value ())
- (recursivep ()))
+ (eof-value nil)
+ (recursive-p nil))
#!+sb-doc
"Read the next Lisp value from STREAM, and return it."
- (let ((result (read-preserving-whitespace stream
- eof-error-p
- eof-value
- recursivep)))
+ (check-for-recursive-read stream recursive-p 'read)
+ (let ((result (%read-preserving-whitespace stream eof-error-p eof-value
+ recursive-p)))
;; This function generally discards trailing whitespace. If you
;; don't want to discard trailing whitespace, call
;; CL:READ-PRESERVING-WHITESPACE instead.
- (unless (or (eql result eof-value) recursivep)
+ (unless (or (eql result eof-value) recursive-p)
(let ((next-char (read-char stream nil nil)))
(unless (or (null next-char)
(whitespace[2]p next-char))
#!+sb-doc
"Read Lisp values from INPUT-STREAM until the next character after a
value's representation is ENDCHAR, and return the objects as a list."
- (with-reader (recursive-p)
- (do ((char (flush-whitespace input-stream)
- (flush-whitespace input-stream))
- (retlist ()))
- ((char= char endchar) (unless *read-suppress* (nreverse retlist)))
- (setq retlist (nconc (read-maybe-nothing input-stream char) retlist)))))
+ (check-for-recursive-read input-stream recursive-p 'read-delimited-list)
+ (flet ((%read-delimited-list (endchar input-stream)
+ (do ((char (flush-whitespace input-stream)
+ (flush-whitespace input-stream))
+ (retlist ()))
+ ((char= char endchar)
+ (unless *read-suppress* (nreverse retlist)))
+ (setq retlist (nconc (read-maybe-nothing input-stream char)
+ retlist)))))
+ (declare (inline %read-delimited-list))
+ (if recursive-p
+ (%read-delimited-list endchar input-stream)
+ (with-read-buffer ()
+ (%read-delimited-list endchar input-stream)))))
\f
;;;; basic readmacro definitions
;;;;
(cond (all-lower (raise-em))
(all-upper (lower-em))))))))))))
+(defvar *reader-package* nil)
+
(defun read-token (stream firstchar)
#!+sb-doc
- "This function is just an fsm that recognizes numbers and symbols."
+ "Default readmacro function. Handles numbers, symbols, and SBCL's
+extended <package-name>::<form-in-package> syntax."
;; Check explicitly whether FIRSTCHAR has an entry for
;; NON-TERMINATING in CHARACTER-ATTRIBUTE-TABLE and
;; READ-DOT-NUMBER-SYMBOL in CMT. Report an error if these are
(case (char-class char attribute-array attribute-hash-table)
(#.+char-attr-delimiter+
(unread-char char stream)
- (simple-reader-error stream
- "illegal terminating character after a colon: ~S"
- char))
+ (if package-designator
+ (let* ((*reader-package* (%find-package-or-lose package-designator)))
+ (return (read stream t nil t)))
+ (simple-reader-error stream
+ "illegal terminating character after a double-colon: ~S"
+ char)))
(#.+char-attr-single-escape+ (go SINGLE-ESCAPE))
(#.+char-attr-multiple-escape+ (go MULT-ESCAPE))
(#.+char-attr-package-delimiter+
RETURN-SYMBOL
(casify-read-buffer escapes)
(let ((found (if package-designator
- (find-package package-designator)
- (sane-package))))
- (unless found
- (error 'simple-reader-package-error :stream stream
- :format-arguments (list package-designator)
- :format-control "package ~S not found"))
-
+ (or (find-package package-designator)
+ (error 'simple-reader-package-error
+ :package package-designator
+ :stream stream
+ :format-control "Package ~A does not exist."
+ :format-arguments (list package-designator)))
+ (or *reader-package* (sane-package)))))
(if (or (zerop colons) (= colons 2) (eq found *keyword-package*))
(return (intern* *read-buffer* *ouch-ptr* found))
(multiple-value-bind (symbol test)
(when (eq test :external) (return symbol))
(let ((name (read-buffer-to-string)))
(with-simple-restart (continue "Use symbol anyway.")
- (error 'simple-reader-package-error :stream stream
+ (error 'simple-reader-package-error
+ :package found
+ :stream stream
:format-arguments (list name (package-name found))
:format-control
(if test
(the index (* num base))))))))
(setq number (+ num (* number base-power)))))))
+(defun truncate-exponent (exponent number divisor)
+ "Truncate exponent if it's too large for a float"
+ ;; Work with base-2 logarithms to avoid conversions to floats,
+ ;; and convert to base-10 conservatively at the end.
+ ;; Use the least positive float, because denormalized exponent
+ ;; can be larger than normalized.
+ (let* ((max-exponent
+ #!-long-float
+ (+ sb!vm:double-float-digits sb!vm:double-float-bias))
+ (number-magnitude (integer-length number))
+ (divisor-magnitude (1- (integer-length divisor)))
+ (magnitude (- number-magnitude divisor-magnitude)))
+ (if (minusp exponent)
+ (max exponent (ceiling (- (+ max-exponent magnitude))
+ #.(floor (log 10 2))))
+ (min exponent (floor (- max-exponent magnitude)
+ #.(floor (log 10 2)))))))
+
(defun make-float (stream)
;; Assume that the contents of *read-buffer* are a legal float, with nothing
;; else after it.
(#\F 'single-float)
(#\D 'double-float)
(#\L 'long-float)))
+ (exponent (truncate-exponent exponent number divisor))
(result (make-float-aux (* (expt 10 exponent) number)
divisor float-format stream)))
(return-from make-float
(type-error (c)
(error 'reader-impossible-number-error
:error c :stream stream
- :format-control "failed to build float"))))
+ :format-control "failed to build float from ~a"
+ :format-arguments (list (read-buffer-to-string))))))
(defun make-ratio (stream)
;; Assume *READ-BUFFER* contains a legal ratio. Build the number from
:format-control "failed to build ratio")))))
(if negative-number (- num) num))))
\f
-;;;; cruft for dispatch macros
-
-(defun make-char-dispatch-table ()
- (make-hash-table))
+;;;; General reader for dispatch macros
(defun dispatch-char-error (stream sub-char ignore)
(declare (ignore ignore))
"no dispatch function defined for ~S"
sub-char)))
-(defun make-dispatch-macro-character (char &optional
- (non-terminating-p nil)
- (rt *readtable*))
- #!+sb-doc
- "Cause CHAR to become a dispatching macro character in readtable (which
- defaults to the current readtable). If NON-TERMINATING-P, the char will
- be non-terminating."
- (set-macro-character char #'read-dispatch-char non-terminating-p rt)
- (let* ((dalist (dispatch-tables rt))
- (dtable (cdr (find char dalist :test #'char= :key #'car))))
- (cond (dtable
- (error "The dispatch character ~S already exists." char))
- (t
- (setf (dispatch-tables rt)
- (push (cons char (make-char-dispatch-table)) dalist)))))
- t)
-
-(defun set-dispatch-macro-character (disp-char sub-char function
- &optional (rt *readtable*))
- #!+sb-doc
- "Cause FUNCTION to be called whenever the reader reads DISP-CHAR
- followed by SUB-CHAR."
- ;; Get the dispatch char for macro (error if not there), diddle
- ;; entry for sub-char.
- (when (digit-char-p sub-char)
- (error "SUB-CHAR must not be a decimal digit: ~S" sub-char))
- (let* ((sub-char (char-upcase sub-char))
- (rt (or rt *standard-readtable*))
- (dpair (find disp-char (dispatch-tables rt)
- :test #'char= :key #'car)))
- (if dpair
- (setf (gethash sub-char (cdr dpair)) (coerce function 'function))
- (error "~S is not a dispatch char." disp-char))
- t))
-
-(defun get-dispatch-macro-character (disp-char sub-char
- &optional (rt *readtable*))
- #!+sb-doc
- "Return the macro character function for SUB-CHAR under DISP-CHAR
- or NIL if there is no associated function."
- (let* ((sub-char (char-upcase sub-char))
- (rt (or rt *standard-readtable*))
- (dpair (find disp-char (dispatch-tables rt)
- :test #'char= :key #'car)))
- (if dpair
- (values (gethash sub-char (cdr dpair)))
- (error "~S is not a dispatch char." disp-char))))
-
(defun read-dispatch-char (stream char)
;; Read some digits.
(let ((numargp nil)
\f
;;;; READ-FROM-STRING
-(defun read-from-string (string &optional (eof-error-p t) eof-value
- &key (start 0) end
- preserve-whitespace)
- #!+sb-doc
- "The characters of string are successively given to the lisp reader
- and the lisp object built by the reader is returned. Macro chars
- will take effect."
- (declare (string string))
+(defun maybe-note-read-from-string-signature-issue (eof-error-p)
+ ;; The interface is so unintuitive that we explicitly check for the common
+ ;; error.
+ (when (member eof-error-p '(:start :end :preserve-whitespace))
+ (style-warn "~@<~S as EOF-ERROR-P argument to ~S: probable error. ~
+ Two optional arguments must be provided before the ~
+ first keyword argument.~:@>"
+ eof-error-p 'read-from-string)
+ t))
+
+(declaim (ftype (sfunction (string t t index (or null index) t) (values t index))
+ %read-from-string))
+(defun %read-from-string (string eof-error-p eof-value start end preserve-whitespace)
(with-array-data ((string string :offset-var offset)
(start start)
(end end)
:check-fill-pointer t)
(let ((stream (make-string-input-stream string start end)))
(values (if preserve-whitespace
- (read-preserving-whitespace stream eof-error-p eof-value)
+ (%read-preserving-whitespace stream eof-error-p eof-value nil)
(read stream eof-error-p eof-value))
(- (string-input-stream-current stream) offset)))))
+
+(defun read-from-string (string &optional (eof-error-p t) eof-value
+ &key (start 0) end preserve-whitespace)
+ #!+sb-doc
+ "The characters of string are successively given to the lisp reader
+ and the lisp object built by the reader is returned. Macro chars
+ will take effect."
+ (declare (string string))
+ (maybe-note-read-from-string-signature-issue eof-error-p)
+ (%read-from-string string eof-error-p eof-value start end preserve-whitespace))
+
+(define-compiler-macro read-from-string (&whole form string &rest args)
+ ;; Check this at compile-time, and rewrite it so we're silent at runtime.
+ (destructuring-bind (&optional (eof-error-p t) eof-value &rest keys)
+ args
+ (cond ((maybe-note-read-from-string-signature-issue eof-error-p)
+ `(read-from-string ,string t ,eof-value ,@keys))
+ (t
+ (let* ((start (gensym "START"))
+ (end (gensym "END"))
+ (preserve-whitespace (gensym "PRESERVE-WHITESPACE"))
+ bind seen ignore)
+ (do ()
+ ((not (cdr keys))
+ ;; Odd number of keys, punt.
+ (when keys (return-from read-from-string form)))
+ (let* ((key (pop keys))
+ (value (pop keys))
+ (var (case key
+ (:start start)
+ (:end end)
+ (:preserve-whitespace preserve-whitespace)
+ (otherwise
+ (return-from read-from-string form)))))
+ (when (member key seen)
+ (setf var (gensym "IGNORE"))
+ (push var ignore))
+ (push key seen)
+ (push (list var value) bind)))
+ (dolist (default (list (list start 0)
+ (list end nil)
+ (list preserve-whitespace nil)))
+ (unless (assoc (car default) bind)
+ (push default bind)))
+ (once-only ((string string))
+ `(let ,(nreverse bind)
+ ,@(when ignore `((declare (ignore ,@ignore))))
+ (%read-from-string ,string ,eof-error-p ,eof-value
+ ,start ,end ,preserve-whitespace))))))))
\f
;;;; PARSE-INTEGER