;;; This is a -*-Lisp-*- file.
;;; **********************************************************************
;;; This code was written as part of the Spice Lisp project at
;;; Carnegie-Mellon University, and has been placed in the public domain.
;;; Spice Lisp is currently incomplete and under active development.
;;; If you want to use this code or any part of Spice Lisp, please contact
;;; Scott Fahlman (FAHLMAN@CMUC).
;;; **********************************************************************
;;; Spice Lisp Reader
;;; Written by David Dill
;;; Package system interface by Lee Schumacher.
;;; Runs in the standard Spice Lisp environment.
;;; ****************************************************************
;;; Spice Lisp specific hacks.
;;; This macro can be replaced by the obvious system function.
(defmacro reader-fast-int-char (char)
;;no type checking. Assumes no funny font, bits.
`(%sp-make-immediate-type ,char %character-type))
;;;Random global variables
(defvar *read-default-float-format* 'single-float "Float format for 1.0E1")
(defvar *readtable* () "Variable bound to current readtable.")
;;;Readtable implementation: the readtable is a structure with three
;;;components: the CHARACTER-ATTRIBUTE-TABLE is a vector of 128 integers
;;;for describing the character type. Conceptually, there are 4 distinct
;;;"primary" character attributes (WHITESPACE, TERMINATING-MACRO, ESCAPE,
;;;and CONSTITUENT -- non-terminating macros have the attribute
;;;CONSTITUENT, and the symbol reader is implemented as a non-terminating
;;;macro), and a number of "secondary" attributes that are used by the
;;;function READ-QUALIFIED-TOKEN, which apply only when the primary
;;;attribute is CONSTITUENT. In order to make the READ-QUALIFIED-TOKEN
;;;fast, all this information is stored in the character attribute table by
;;;having different varieties of constituents. In order to conform with
;;;the white pages, the primary attributes should be moved by
;;;SET-SYNTAX-FROM-CHARACTER and SET-MACRO-CHARACTER, while the secondary
;;;attributes are constant properties of the characters (as long as they
;;;are constituents).
;;;The CHARACTER-MACRO-TABLE is a vector of 128 functions. One of these
;;;functions called with appropriate arguments whenever any non-WHITESPACE
;;;character is encountered inside READ-PRESERVING-WHITESPACE. These
;;;functions are used to implement user-defined read-macros, system
;;;read-macros, and the number-symbol reader. Finally, there is a
;;;DISPATCH-TABLES entry, which is an alist from dispatch characters to
;;;vectors of 128 functions, for use in defining dispatching macros (like
;;;#-macro).
(defvar std-lisp-readtable ()
"Standard lisp readtable. This is for recovery from broken
read-tables, and should not normally be user-visible.")
(defstruct (readtable (:conc-name ||)
(:predicate readtablep))
(character-attribute-table (make-character-attribute-table)
:type simple-vector)
(character-macro-table (make-character-macro-table)
:type simple-vector)
(dispatch-tables () :type list))
;;;Constants for character attributes. These are all as in the manual.
(eval-when (compile load eval)
(setq whitespace 0
terminating-macro 1
escape 2
constituent 3
constituent-dot 4
constituent-expt 5
constituent-slash 6
constituent-digit 7
constituent-sign 8
sharp-sign 9
multiple-escape 10
package-delimiter 11
;;fake attribute for use in read-unqualified-token
delimiter 12))
(defvar *old-package* ()
"Value of *package* at the start of the last read or Nil.")
;;; In case we get an error trying to parse a symbol, we want to rebind the
;;; above stuff so it's cool.
(proclaim '(special *package* *keyword-package* *read-base*))
;;;macros and functions for character tables.
(defmacro get-cat-entry (char rt)
;;only give this side-effect-free args.
`(elt (the simple-vector (character-attribute-table ,rt))
(char-int ,char)))
(defun set-cat-entry (char newvalue &optional (rt *readtable*))
(setf (elt (the simple-vector (character-attribute-table rt))
(char-int char))
newvalue))
(defmacro get-cmt-entry (char rt)
`(elt (the simple-vector (character-macro-table ,rt))
(char-int ,char)))
(defun set-cmt-entry (char newvalue &optional (rt *readtable*))
(setf (elt (the simple-vector (character-macro-table rt))
(char-int char))
newvalue))
(defun make-character-attribute-table ()
(make-array 128 :element-type t :initial-element #,constituent))
(defun make-character-macro-table ()
(make-array 128 :element-type t
:initial-element #'undefined-macro-char))
(defun undefined-macro-char (ignore char)
(error "Undefined read-macro character ~S" char))
;;;The character attribute table is a 128-long vector of integers.
(defmacro test-attribute (char whichclass rt)
`(= (get-cat-entry ,char ,rt) ,whichclass)))
;;;Predicates for testing character attributes
(defmacro whitespacep (char &optional (rt '*readtable*))
`(test-attribute ,char #,whitespace ,rt))
(defmacro constituentp (char &optional (rt '*readtable*))
`(>= (get-cat-entry ,char ,rt) #,constituent))
(defmacro terminating-macrop (char &optional (rt '*readtable*))
`(test-attribute ,char #,terminating-macro ,rt))
(defmacro escapep (char &optional (rt '*readtable*))
`(test-attribute ,char #,escape ,rt))
(defmacro multiple-escape-p (char &optional (rt '*readtable*))
`(test-attribute ,char #,multiple-escape ,rt))
(defmacro token-delimiterp (char &optional (rt '*readtable*))
;;depends on actual attribute numbering above.
`(<= (get-cat-entry ,char ,rt) #,terminating-macro))
(defvar secondary-attribute-table ())
(defun set-secondary-attribute (char attribute)
(setf (elt (the simple-vector secondary-attribute-table) (char-int char))
attribute))
(defun init-secondary-attribute-table ()
(setq secondary-attribute-table
(make-array 128 :element-type t
:initial-element #,constituent))
(set-secondary-attribute #\: #,package-delimiter)
(set-secondary-attribute #\| #,multiple-escape) ; |) [For EMACS]
(set-secondary-attribute #\. #,constituent-dot)
(set-secondary-attribute #\+ #,constituent-sign)
(set-secondary-attribute #\- #,constituent-sign)
(set-secondary-attribute #\/ #,constituent-slash)
(do ((i (char-int #\0) (1+ i)))
((> i (char-int #\9)))
(set-secondary-attribute (int-char i) #,constituent-digit))
(set-secondary-attribute #\E #,constituent-expt)
(set-secondary-attribute #\F #,constituent-expt)
(set-secondary-attribute #\D #,constituent-expt)
(set-secondary-attribute #\S #,constituent-expt)
(set-secondary-attribute #\L #,constituent-expt)
(set-secondary-attribute #\e #,constituent-expt)
(set-secondary-attribute #\f #,constituent-expt)
(set-secondary-attribute #\d #,constituent-expt)
(set-secondary-attribute #\s #,constituent-expt)
(set-secondary-attribute #\l #,constituent-expt))
(defmacro get-secondary-attribute (char)
`(elt (the simple-vector secondary-attribute-table)
(char-int ,char)))
(defun copy-readtable (&optional (from-readtable *readtable*) to-readtable)
(if (null from-readtable) (setq from-readtable std-lisp-readtable))
(if (null to-readtable) (setq to-readtable (make-readtable)))
;;physically clobber contents of internal tables.
(replace (character-attribute-table to-readtable)
(character-attribute-table from-readtable))
(replace (character-macro-table to-readtable)
(character-macro-table from-readtable))
(setf (dispatch-tables to-readtable)
(mapcar #'(lambda (pair) (cons (car pair)
(copy-seq (cdr pair))))
(dispatch-tables from-readtable)))
to-readtable)
(defun set-syntax-from-char (to-char from-char &optional
(to-readtable *readtable*)
(from-readtable ()))
(if (null from-readtable) (setq from-readtable std-lisp-readtable))
;;copy from-char entries to to-char entries, but make sure that if
;;from char is a constituent you don't copy non-movable secondary
;;attributes (constituent types), and that said attributes magically
;;appear if you transform a non-constituent to a constituent.
(let ((att (get-cat-entry from-char from-readtable)))
(if (constituentp att) (setq att (get-secondary-attribute to-char)))
(set-cat-entry to-char att to-readtable)
(set-cmt-entry to-char
(get-cmt-entry from-char from-readtable)
to-readtable)))
(defun set-macro-character (char function &optional
(non-terminatingp nil) (rt *readtable*))
(if non-terminatingp
(set-cat-entry char (get-secondary-attribute char) rt)
(set-cat-entry char #,terminating-macro rt))
(set-cmt-entry char function rt))
(defun get-macro-character (char &optional (rt *readtable*))
;;check macro syntax, return associated function if it's there.
;;returns a value for all constituents.
(cond ((constituentp char)
(values (get-cmt-entry char rt) t))
((terminating-macrop char)
(values (get-cmt-entry char rt) nil))
(t nil)))
;;;These definitions support internal programming conventions.
(defconstant eof-object '(*eof*))
(defmacro eofp (char) `(eq ,char eof-object))
(defun flush-whitespace (stream)
;;This flushes whitespace chars, returning the last char it read (a non-white
;;one). It always gets an error on end-of-file.
(prepare-for-fast-read-char stream
(do ((attribute-table (character-attribute-table *readtable*))
(char (fast-read-char t) (fast-read-char t)))
((/= (svref attribute-table (char-int char)) #,whitespace)
(done-with-fast-read-char)
char))))
;;;Temporary initialization hack.
(defun init-std-lisp-readtable ()
(setq std-lisp-readtable (make-readtable))
;;all characters default to "constituent" in make-readtable
;;*** un-constituent-ize some of these ***
(let ((*readtable* std-lisp-readtable))
(set-cat-entry #\tab #,whitespace)
(set-cat-entry #\linefeed #,whitespace)
(set-cat-entry #\space #,whitespace)
(set-cat-entry #\page #,whitespace)
(set-cat-entry #\return #,whitespace)
(set-cat-entry #\\ #,escape)
(set-cmt-entry #\\ #'read-token)
(set-cat-entry #\rubout #,whitespace)
(set-cmt-entry #\: #'read-token)
(set-cmt-entry #\| #'read-token) ; |) [for EMACS]
;;macro definitions
(set-macro-character #\" #'read-string)
;;* # macro
(set-macro-character #\' #'read-quote)
(set-macro-character #\( #'read-list)
(set-macro-character #\) #'read-right-paren)
(set-macro-character #\; #'read-comment)
;;* backquote
;;all constituents
(do ((ichar 0 (1+ ichar))
(char))
((= ichar #O200))
(setq char (int-char ichar))
(when (constituentp char std-lisp-readtable)
(set-cat-entry char (get-secondary-attribute char))
(set-cmt-entry char #'read-token)))))
;;;read-buffer implementation.
(defvar read-buffer)
(defvar read-buffer-length)
(defvar inch-ptr)
(defvar ouch-ptr)
(defmacro reset-read-buffer ()
;;turn read-buffer into an empty read-buffer.
;;ouch-ptr always points to next char to write
`(progn
;;next is in case interrupt processor has re-bound read-buffer to nil.
(unless (or (boundp 'read-buffer) read-buffer) (init-read-buffer))
(setq ouch-ptr 0)
;;inch-ptr always points to next char to read
(setq inch-ptr 0)))
(defun init-read-buffer ()
(setq read-buffer (make-string 512)) ;initial bufsize
(setq read-buffer-length 512)
(reset-read-buffer))
(defmacro ouch-read-buffer (char)
`(progn
(if (>= ouch-ptr read-buffer-length)
;;buffer overflow -- double the size
(grow-read-buffer))
(setf (elt (the simple-string read-buffer) ouch-ptr) ,char)
(setq ouch-ptr (1+ ouch-ptr))))
;; macro to move ouch-ptr back one.
(defmacro ouch-unread-buffer ()
'(if (> ouch-ptr inch-ptr) (setq ouch-ptr (1- ouch-ptr))))
(defun grow-read-buffer ()
(let ((rbl (length (the simple-string read-buffer))))
(setq read-buffer
(concatenate 'simple-string
(the simple-string read-buffer)
(the simple-string (make-string rbl))))
(setq read-buffer-length (* 2 rbl))))
(defun inchpeek-read-buffer ()
(if (>= inch-ptr ouch-ptr)
eof-object
(elt (the simple-string read-buffer) inch-ptr)))
(defun inch-read-buffer ()
(cond ((>= inch-ptr ouch-ptr) eof-object)
(t (prog1 (elt (the simple-string read-buffer) inch-ptr)
(setq inch-ptr (1+ inch-ptr))))))
(defmacro unread-buffer ()
`(decf inch-ptr))
(defun read-unwind-read-buffer ()
;;keep contents, but make next (inch..) return first char.
(setq inch-ptr 0))
(defun read-buffer-to-string ()
(subseq (the simple-string read-buffer) 0 ouch-ptr))
;;;Actual reader.
(defvar *real-eof-errorp* ()
"Value checked by reader if recursivep is true.")
(defvar *real-eof-value* ()
"Eof-value used for eof-value if recursivep is true.")
(defvar right-paren-whitespace t
"Flag that READ uses to tell when it's ok to treat right parens as
whitespace.")
;; Alist for sharp-equal. Used to keep track of objects with labels assigned
;; that have been completly read.
(defvar sharp-equal-alist ())
;; Alist for sharp-sharp. Assoc's a number with a symbol produced by gensym.
;; Used by sharp-sharp as an unforgeable label, instead of the number.
(defvar sharp-sharp-alist ())
(proclaim '(special *standard-input*))
;; Read-preserving-whitespace behaves just like read only it makes sure
;; to leave terminating whitespace in the stream.
(defun read-preserving-whitespace
(&optional (stream *standard-input*) (eof-errorp t) (eof-value ())
(recursivep ()))
"Reads from stream and returns the object read, preserving the whitespace
that followed the object."
(let ((*real-eof-value* *real-eof-value*)
(*real-eof-errorp* *real-eof-errorp*)
(sharp-equal-alist sharp-equal-alist)
(sharp-sharp-alist sharp-sharp-alist))
(if recursivep
(setq eof-errorp *real-eof-errorp*
eof-value *real-eof-value*)
(setq *real-eof-value* eof-value
*real-eof-errorp* eof-errorp
;; The scope of these two lists is the top level read, so they
;; have to be reset here.
sharp-equal-alist nil
sharp-sharp-alist nil))
(progn
;;loop for repeating when a macro returns nothing.
(do ((char (read-char stream nil eof-object)
(read-char stream nil eof-object)))
(())
(cond ((eofp char)
(if eof-errorp
(error "Unexpected end-of-file encountered.")
(return eof-value)))
((whitespacep char))
(t
(let* ((macrofun (get-cmt-entry char *readtable*))
(result (multiple-value-list
(funcall macrofun stream char))))
;;repeat if macro returned nothing.
(if result (return (car result))))))))))
(defun read-maybe-nothing (stream char)
;;returns nil or a list with one thing, depending.
;;for functions that want comments to return so they can look
;;past them. Assumes char is not whitespace.
(let ((retval (multiple-value-list
(funcall (get-cmt-entry char *readtable*) stream char))))
(if retval (rplacd retval nil))))
(defun read (&optional (stream *standard-input*) (eof-errorp t)
(eof-value ()) (recursivep ()))
"Reads in the next object in the stream, which defaults to
*standard-input*. For details see the I/O chapter of
the manual."
(prog1
(read-preserving-whitespace stream eof-errorp eof-value recursivep)
(let ((whitechar (read-char stream nil eof-object)))
(if (and (not (eofp whitechar))
(or (not (whitespacep whitechar))
recursivep))
(unread-char whitechar stream)))))
(defun read-delimited-list (endchar &optional (input-stream *standard-input*))
(do ((char (flush-whitespace input-stream)
(flush-whitespace input-stream))
(retlist ()))
((char= char endchar) (nreverse retlist))
(setq retlist (nconc (read-maybe-nothing input-stream char) retlist))))
;;;Standard ReadMacro definitions to implement the reader.
(defun read-quote (stream ignore) (list 'quote (read stream () () t)))
(defun read-comment (stream ignore)
(prepare-for-fast-read-char stream
(do ((char (fast-read-char nil nil)
(fast-read-char nil nil)))
((or (not char) (char= char #\newline))
(done-with-fast-read-char))))
;;don't return anything
(values))
(defun read-list (stream ignore)
(let* ((thelist (list nil))
(listtail thelist))
(do ((firstchar (flush-whitespace stream) (flush-whitespace stream)))
((char= firstchar #\) ) (cdr thelist))
(when (char= firstchar #\.)
(let ((nextchar (read-char stream t)))
(cond ((token-delimiterp nextchar)
(cond ((eq listtail thelist)
(error "Nothing appears before . in list."))
((whitespacep nextchar)
(setq nextchar (flush-whitespace stream))))
(rplacd listtail
;;return list containing last thing.
(car (read-after-dot stream nextchar)))
(return (cdr thelist)))
;;put back nextchar so we can read it normally.
(t (unread-char nextchar stream)))))
;;next thing is not an isolated dot.
(let ((listobj (read-maybe-nothing stream firstchar)))
;;allows the possibility that a comment was read.
(when listobj
(rplacd listtail listobj)
(setq listtail listobj))))))
(defun read-after-dot (stream firstchar)
;;firstchar is non-whitespace!
(let ((lastobj ()))
(do ((char firstchar (flush-whitespace stream)))
((char= char #\) )
(error "Nothing appears after . in list."))
;;see if there's something there.
(setq lastobj (read-maybe-nothing stream char))
(when lastobj (return t)))
;;at least one thing appears after the dot.
;;check for more than one thing following dot.
(do ((lastchar (flush-whitespace stream)
(flush-whitespace stream)))
((char= lastchar #\) ) lastobj) ;success!
;;try reading virtual whitespace
(if (read-maybe-nothing stream lastchar)
(error "More than one object follows . in list.")))))
(defun read-string (stream closech)
;;this accumulates chars until it sees same char that invoked it.
;;for a very long string, this could end up bloating the read buffer.
(reset-read-buffer)
(prepare-for-fast-read-char stream
(do ((char (fast-read-char t) (fast-read-char t)))
((char= char closech)
(done-with-fast-read-char))
(if (escapep char) (setq char (fast-read-char t)))
(ouch-read-buffer char)))
(read-buffer-to-string))
(defun read-right-paren (ignore ignore)
(if right-paren-whitespace
(values)
(error "Unmatched right parenthesis.")))
(defun internal-read-extended-token (stream firstchar
&aux (escape-appearedp nil))
;;read the string up to the next delimiter. Leaves resulting token
;;in read-buffer, returns a flag that is true if an escape (\\)
;;appeared, meaning that it has to be a symbol.
;;needs to have package hacks added.
(reset-read-buffer)
(do ((char firstchar (read-char stream nil eof-object)))
;;for now, treat #\: as a constituent:
;; does this cond need same fix as the top-level read did ??
((cond ((eofp char) t)
((token-delimiterp char)
(unread-char char stream)
t)
(t nil))
escape-appearedp)
(cond ((escapep char)
;;it can't be a number, even if it's 1\23.
(setq escape-appearedp t)
;;read next char here, so it won't be upper-casified.
(let ((nextchar (read-char stream nil eof-object)))
(if (eofp nextchar)
(error "End-of-file after escape character.")
(ouch-read-buffer nextchar))))
(t (ouch-read-buffer (fast-char-upcase char))))))
(defmacro char-class (char attable)
`(let ((att (svref ,attable (char-int ,char))))
(if (<= att #,terminating-macro)
#,delimiter
att)))
(defmacro backup-char (char stream)
`(if ,char (unread-char ,char ,stream)))
(defvar *read-suppress* nil)
(defun read-token (stream firstchar)
"This function is just an fsm that recognizes numbers and symbols."
;;check explicitly whether firstchar has entry for non-terminating
;;in character-attribute-table and read-dot-number-symbol in CMT.
;;Report an error if these are violated (if we called this, we want
;;something that is a legitimate token!).
;;read in the longest possible string satisfying the bnf for
;;"unqualified-token". Leave the result in the READ-BUFFER.
;;Return next char after token (last char read).
(if *read-suppress*
(internal-read-extended-token stream firstchar)
(let ((attribute-table (character-attribute-table *readtable*))
(package *package*)
(colons 0))
(reset-read-buffer)
(prog ((char firstchar))
(case (char-class char attribute-table)
(#,constituent-sign (go SIGN))
(#,constituent-digit (go LEFTDIGIT))
(#,constituent-dot (go FRONTDOT))
(#,escape (go ESCAPE))
(#,package-delimiter (go COLON))
(#,multiple-escape (go MULT-ESCAPE))
;;can't have eof, whitespace, or terminating macro as first char!
(t (go SYMBOL)))
SIGN
;;saw "sign"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (go RETURN-SYMBOL))
(case (char-class char attribute-table)
(#,constituent-digit (go LEFTDIGIT))
(#,constituent-dot (go SIGNDOT))
(#,escape (go ESCAPE))
(#,package-delimiter (go COLON))
(#,multiple-escape (go MULT-ESCAPE))
(#,delimiter (unread-char char stream) (go RETURN-SYMBOL))
(t (go SYMBOL)))
LEFTDIGIT
;;saw "[sign] {digit}+"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (return (make-integer)))
(case (char-class char attribute-table)
(#,constituent-digit (go LEFTDIGIT))
(#,constituent-dot (go MIDDLEDOT))
(#,constituent-expt (go EXPONENT))
(#,constituent-slash (go RATIO))
(#,delimiter (unread-char char stream) (return (make-integer)))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
MIDDLEDOT
;;saw "[sign] {digit}+ dot"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (return (make-integer)))
(case (char-class char attribute-table)
(#,constituent-digit (go RIGHTDIGIT))
(#,constituent-expt (go EXPONENT))
(#,delimiter (unread-char char stream) (return (make-integer)))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
RIGHTDIGIT
;;saw "[sign] {digit}* dot {digit}+"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (return (make-float)))
(case (char-class char attribute-table)
(#,constituent-digit (go RIGHTDIGIT))
(#,constituent-expt (go EXPONENT))
(#,delimiter (unread-char char stream) (return (make-float)))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
SIGNDOT
;;saw "[sign] dot"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (go RETURN-SYMBOL))
(case (char-class char attribute-table)
(#,constituent-digit (go RIGHTDIGIT))
(#,delimiter (unread-char char stream) (go RETURN-SYMBOL))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(t (go SYMBOL)))
FRONTDOT
;;saw "dot"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (error "Dot context error."))
(case (char-class char attribute-table)
(#,constituent-digit (go RIGHTDIGIT))
(#,constituent-dot (go DOTS))
(#,delimiter (error "Dot context error."))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
EXPONENT
(ouch-read-buffer (fast-char-upcase char))
(setq char (read-char stream nil nil))
(unless char (go RETURN-SYMBOL))
(case (char-class char attribute-table)
(#,constituent-sign (go EXPTSIGN))
(#,constituent-digit (go EXPTDIGIT))
(#,delimiter (unread-char char stream) (go RETURN-SYMBOL))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
EXPTSIGN
;;we got to EXPONENT, and saw a sign character.
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (go RETURN-SYMBOL))
(case (char-class char attribute-table)
(#,constituent-digit (go EXPTDIGIT))
(#,delimiter (unread-char char stream) (go RETURN-SYMBOL))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
EXPTDIGIT
;;got to EXPONENT, saw "[sign] {digit}+"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (return (make-float)))
(case (char-class char attribute-table)
(#,constituent-digit (go EXPTDIGIT))
(#,delimiter (unread-char char stream) (return (make-float)))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
RATIO
;;saw "[sign] {digit}+ slash"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (go RETURN-SYMBOL))
(case (char-class char attribute-table)
(#,constituent-digit (go RATIODIGIT))
(#,delimiter (unread-char char stream) (go RETURN-SYMBOL))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
RATIODIGIT
;;saw "[sign] {digit}+ slash {digit}+"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (return (make-ratio)))
(case (char-class char attribute-table)
(#,constituent-digit (go RATIODIGIT))
(#,delimiter (unread-char char stream) (return (make-ratio)))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
DOTS
;;saw "dot {dot}+"
(ouch-read-buffer char)
(setq char (read-char stream nil nil))
(unless char (error "Too many dots."))
(case (char-class char attribute-table)
(#,constituent-dot (go DOTS))
(#,delimiter (unread-char char stream) (error "Too many dots."))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
SYMBOL
;;not a dot, dots, or number.
(prepare-for-fast-read-char stream
(prog ()
SYMBOL-LOOP
(ouch-read-buffer (fast-char-upcase char))
(setq char (fast-read-char nil nil))
(unless char (go RETURN-SYMBOL))
(case (char-class char attribute-table)
(#,escape (done-with-fast-read-char)
(go ESCAPE))
(#,delimiter (done-with-fast-read-char)
(unread-char char stream)
(go RETURN-SYMBOL))
(#,multiple-escape (done-with-fast-read-char)
(go MULT-ESCAPE))
(#,package-delimiter (done-with-fast-read-char)
(go COLON))
(t (go SYMBOL-LOOP)))))
ESCAPE
;;saw an escape.
;;don't put the escape in the read-buffer.
;;read-next char, put in buffer (no case conversion).
(let ((nextchar (read-char stream nil nil)))
(if nextchar
(ouch-read-buffer nextchar)
(error "End-of-file after escape character.")))
(setq char (read-char stream nil nil))
(unless char (go RETURN-SYMBOL))
(case (char-class char attribute-table)
(#,delimiter (unread-char char stream) (go RETURN-SYMBOL))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
MULT-ESCAPE
(do ((char (read-char stream t) (read-char stream t)))
((multiple-escape-p char))
(if (escapep char) (setq char (read-char stream t)))
(ouch-read-buffer char))
(setq char (read-char stream nil nil))
(unless char (go RETURN-SYMBOL))
(case (char-class char attribute-table)
(#,delimiter (unread-char char stream) (go RETURN-SYMBOL))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go COLON))
(t (go SYMBOL)))
COLON
(cond ((zerop colons)
(setq colons 1)
(setq package (find-package (read-buffer-to-string)))
(unless package (error "Package ~S not found."
(read-buffer-to-string))))
(t (error "Too many colons in ~S" (read-buffer-to-string))))
(reset-read-buffer)
(setq char (read-char stream nil nil))
(unless char (error "End of file encountered after reading a colon."))
(case (char-class char attribute-table)
(#,delimiter (unread-char char stream)
(error "Illegal terminating character after a colon, ~S"
char))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (go INTERN))
(t (go SYMBOL)))
INTERN
(setq colons 2)
(setq char (read-char stream nil nil))
(unless char (error "End of file encountered after reading a colon."))
(case (char-class char attribute-table)
(#,delimiter (unread-char char stream)
(error "Illegal terminating character after a colon, ~S"
char))
(#,escape (go ESCAPE))
(#,multiple-escape (go MULT-ESCAPE))
(#,package-delimiter (error "To many colons after ~S:"
(package-name package)))
(t (go SYMBOL)))
RETURN-SYMBOL
(if (or (zerop colons) (= colons 2) (eq package *keyword-package*))
(return (intern (read-buffer-to-string) package))
(multiple-value-bind (symbol test)
(find-symbol (read-buffer-to-string) package)
(cond ((eq test :external) (return symbol))
((null test)
(error
"Symbol ~S not found in package ~S."
(read-buffer-to-string) package))
(t (cerror "use symbol anyway."
"The symbol ~S is not external in the ~
package ~S" (read-buffer-to-string) package)
(return symbol)))))))))
(defun read-extended-token (stream &optional (*readtable* *readtable*))
;;for semi-external use: returns 2 values: the string for the token,
;;and a flag for whether there was an escape char.
(let ((escape-appearedp
(internal-read-extended-token stream (read-char stream t))))
(values (read-buffer-to-string) escape-appearedp)))
;;; Number reading functions.
(defmacro digit* nil
`(do ((ch char (inch-read-buffer)))
((or (eofp ch) (not (digit-char-p ch))) (setq char ch))
;;report if at least one digit is seen:
(setq one-digit t)))
(defmacro exponent-letterp (letter)
`(memq ,letter '(#\E #\S #\F #\L #\D #\e #\s #\f #\l #\d)))
(defun make-integer ()
(let ((base (if (boundp '*read-base*)
(if (and (fixnump *read-base*)
(<= 1 *read-base* 36))
*read-base*
(error "~A not a valid number for *read-base*."
*read-base*))
10.))
(negative-number nil) (number 0) (char ()))
(read-unwind-read-buffer)
(if (cond ((char= (setq char (inch-read-buffer)) #\-)
(setq negative-number t))
((char= char #\+) t))
(setq char (inch-read-buffer)))
;; Read (almost) normally until the number becomes a bignum.
(do ((ch char (inch-read-buffer)))
;;there can be a dot at the end.
((cond ((or (char= ch #\.) (eofp ch)) t)
((bignump number)
;; Figure out the number of digits to read each time,
;; and the number to multiply the precious result by.
(let* ((digits-per (do ((fix (truncate most-positive-fixnum
base)
(truncate fix base))
(digits 0 (1+ digits)))
((zerop fix) digits)))
(base-power (expt base digits-per)))
(unread-buffer)
;; Read the bignum "a fixnum at a time."
(do ()
((do ((ch (inch-read-buffer) (inch-read-buffer))
(digit digits-per (1- digit))
(num 0 (+ (* num base) (digit-char-p ch base))))
((cond ((or (eofp ch) (char= ch #\.))
;; Exits.
(setq number
(+ (* number
(expt base
(- digits-per digit)))
num)))
((zerop digit)
(setq number (+ (* number base-power)
num))))
(if (zerop digit) nil t))))
(unread-buffer))
t))))
(setq number (+ (* number 10) (digit-char-p ch base))))
(if negative-number (- number) number)))
(defun make-float ()
;;assume that the contents of read-buffer are a legal float, with nothing
;;else after it.
(read-unwind-read-buffer)
(prog ((negative-fraction nil) (number 0) (divisor 1)
(negative-exponent nil) (exponent 0)
(float-char ()) (char (inch-read-buffer)))
(if (cond ((char= char #\+) t)
((char= char #\-) (setq negative-fraction t)))
;;flush it
(setq char (inch-read-buffer)))
;;read digits before the dot
(do* ((ch char (inch-read-buffer))
(dig (digit-char-p ch) (digit-char-p ch)))
((not dig) (setq char ch))
(setq number (+ (* number 10) dig)))
;;deal with the dot, if it's there.
(when (char= char #\.)
(setq char (inch-read-buffer))
;;read digits after the dot.
(do* ((ch char (inch-read-buffer))
(dig (and (not (eofp ch)) (digit-char-p ch))
(and (not (eofp ch)) (digit-char-p ch))))
((not dig) (setq char ch))
(setq divisor (* divisor 10))
(setq number (+ (* number 10) dig))))
;;is there an exponent letter?
(cond ((eofp char)
;;if not, we've read the whole number.
(let ((num (/ (coerce number *read-default-float-format*) divisor)))
(return (if negative-fraction (- num) num))))
((exponent-letterp char)
(setq float-char char)
;;build exponent
(setq char (inch-read-buffer))
;;check leading sign
(if (cond ((char= char #\+) t)
((char= char #\-) (setq negative-exponent t)))
;;flush sign
(setq char (inch-read-buffer)))
;;read digits for exponent
(do* ((ch char (inch-read-buffer))
(dig (and (not (eofp ch)) (digit-char-p ch))
(and (not (eofp ch)) (digit-char-p ch))))
((not dig)
(setq exponent (if negative-exponent (- exponent) exponent)))
(setq exponent (+ (* exponent 10) dig)))
;;generate and return the float, depending on float-char:
(let ((float-format (case float-char
(#\E *read-default-float-format*)
(#\S 'short-float)
(#\F 'single-float)
(#\D 'double-float)
(#\L 'long-float))))
(let ((num (* (/ (coerce number float-format)
(coerce divisor float-format))
(expt 10 exponent))))
(return (if negative-fraction (- num) num)))))
;;should never happen:
(t (error "Internal error in floating point reader.")))))
(defun make-ratio ()
;;assume read-buffer contains a legal ratio. Build the number from
;;the string.
;;look for optional "+" or "-".
(let ((numerator 0) (denominator 0) (char ()) (negative-number nil))
(read-unwind-read-buffer)
(setq char (inch-read-buffer))
(cond ((char= char #\+)
(setq char (inch-read-buffer)))
((char= char #\-)
(setq char (inch-read-buffer))
(setq negative-number t)))
;;get numerator
(do* ((ch char (inch-read-buffer))
(dig (digit-char-p ch) (digit-char-p ch)))
((not dig))
(setq numerator (+ (* numerator 10) dig)))
;;get denominator
(do* ((ch (inch-read-buffer) (inch-read-buffer))
(dig ()))
((or (eofp ch) (not (setq dig (digit-char-p ch)))))
(setq denominator (+ (* denominator 10) dig)))
(let ((num (/ numerator denominator)))
(if negative-number (- num) num))))
;;;dispatching macro cruft
(defun make-char-dispatch-table ()
(make-array 128 :initial-element #'dispatch-char-error))
(defun dispatch-char-error (ignore sub-char ignore)
(error "No dispatch function defined for ~S." sub-char))
(defun make-dispatch-macro-character (char &optional
(non-terminating-p nil)
(rt *readtable*))
(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 "Dispatch character already exists"))
(t
(setf (dispatch-tables rt)
(push (cons char (make-char-dispatch-table)) dalist))))))
(defun set-dispatch-macro-character
(disp-char sub-char function &optional (rt *readtable*))
;;get the dispatch char for macro (error if not there), diddle
;;entry for sub-char.
(let ((dpair (find disp-char (dispatch-tables rt)
:test #'char= :key #'car)))
(if dpair
(setf (elt (the simple-vector (cdr dpair))
(char-int sub-char))
function)
(error "~S is not a dispatch char." disp-char))))
(defun get-dispatch-macro-character (disp-char sub-char
&optional (rt *readtable*))
(let ((dpair (find disp-char (dispatch-tables rt)
:test #'char= :key #'car)))
(if dpair
(elt (the simple-vector (cdr dpair))
(char-int sub-char))
(error "~S is not a dispatch char." disp-char))))
(defun read-dispatch-char (stream char)
;;read some digits
(let ((numargp nil)
(numarg 0)
(sub-char ()))
(do* ((ch (read-char stream nil eof-object)
(read-char stream nil eof-object))
(dig ()))
((or (eofp ch)
(not (setq dig (digit-char-p ch))))
;;take care of the extra char.
(if (eofp ch)
(error "End-of-file inside dispatch character.")
(setq sub-char ch)))
(setq numargp t)
(setq numarg (+ (* numarg 10) dig)))
;;look up the function and call it.
(let ((dpair (find char (dispatch-tables *readtable*)
:test #'char= :key #'car)))
(if dpair
(funcall (elt (the simple-vector (cdr dpair))
(char-int sub-char))
stream sub-char (if numargp numarg nil))
(error "No dispatch table for dispatch char.")))))
;;; Read-From-String.
(defvar read-from-string-spares ()
"A resource of string streams for Read-From-String.")
(defun read-from-string (string &optional eof-error-p eof-value
&key (start 0) (end (length string))
preserve-whitespace)
(declare (string string))
(unless read-from-string-spares
(push (internal-make-string-input-stream nil nil nil) read-from-string-spares))
(let ((stream (pop read-from-string-spares)))
(setf (string-input-stream-string stream) (coerce string 'simple-string))
(setf (string-input-stream-current stream) start)
(setf (string-input-stream-end stream) end)
(unwind-protect
(values (if preserve-whitespace
(read-preserving-whitespace stream eof-error-p eof-value)
(read stream eof-error-p eof-value))
(string-input-stream-current stream))
(push stream read-from-string-spares))))
;;; Parse-Integer.
(defun parse-integer (string &rest keywords)
(with-keywords keywords
((:start start 0)
(:end end (length string))
(:radix radix 10)
(:junk-allowed junk-allowed nil))
;; Skip over whitespace.
(do ((index start (1+ index)))
((not (whitespacep (char string index)))
;; Record the sign, if any.
(let ((char (char string index))
(minusp))
(cond ((char= char #\-)
(setq minusp t)
(incf index))
((char= char #\+)
(incf index)))
;; Zoom through the string, parsing an integer.
(do ((index index (1+ index))
(result 0))
((= index end) (values (if minusp (- result) result) index))
(let* ((char (char string index))
(weight (digit-char-p char radix)))
(cond (weight
(setq result (+ weight (* result radix))))
(junk-allowed
(return (values (if minusp (- result) result) index)))
((whitespacep char)
(do ((jndex (1+ index) (1+ jndex)))
((= jndex end) (setq index (1- end))) ; Hackish...
(unless (whitespacep (char string jndex))
(error "There's junk in this string: ~S." string))))
(t
(error "There's junk in this string: ~S." string))
))))))))
;;; Reader initialization code.
(defun reader-init ()
(init-read-buffer)
(init-secondary-attribute-table)
(init-std-lisp-readtable))