;; -*- Mode: emacs-lisp -*- ;; ;; Customizable, CL-like reader for version 19 Emacs Lisp. ;; ;; Copyright (C) 1993 by Guido Bosch ;; This file is written in GNU Emacs Lisp, but not (yet) part of GNU Emacs. ;; The software contained in this file is free software; you can ;; redistribute it and/or modify it under the terms of the GNU General ;; Public License as published by the Free Software Foundation; either ;; version 2, or (at your option) any later version. ;; GNU Emacs is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. ;; ;; Please send bugs and comments to the author. ;; ;; ;; This program is still under development. Neither the author nor ;; his employer accepts responsibility to anyone for the consequences of ;; using it or for whether it serves any particular purpose or works ;; at all. ;; Introduction ;; ------------ ;; ;; This package replaces the standard Emacs Lisp reader (implemented ;; in C) by a flexible and customizable Common Lisp like one ;; (implemented entirely in Emacs Lisp). During reading of elisp ;; source files, it is about 50% slower than the built-in reader, but ;; there is no difference for byte compiled files - they dont contain ;; any syntactic sugar and are loaded with the built in subroutine ;; `load'. ;; The user level functions for defining read tables, character and ;; dispatch macros are implemented according to the Commom Lisp ;; specification by Steels (2nd edition), but the macro functions ;; itself are implemented in a slightly different way, because the ;; character reading is done in an Emacs buffer, and not by using the ;; primitive funtions `read-char' and `unread-char', as a real CL ;; does. To get 100% compatibility with CL, the above functions (or ;; their equivalents) have to be implemented as subroutines. ;; Another difference to real CL reading is that basic token (symbols ;; numbers, strings, and a few more) are still read by the ;; original elisp reader. This is necessary to get a reasonable ;; performance. As a consquence, the syntax of basic tokens can't be ;; customized. ;; Most of the built-in character syntax has been replaced by lisp ;; character macros: parentheses and brackets, simple and double ;; quotes, semicolon comments and the dot. New syntax features are: ;; Backquote-Comma-Atsign Macro: `(,el ,@list) ;; ;; (the clumsy elisp syntax (` ((, el) (,@ list))) is also supported, ;; but with one restriction: the blank behind the quote characters is ;; mandatory for the old syntax. The cl reader needs it as a landmark ;; to distinguish between old and new syntax. An example: ;; ;; With blanks, both readers read the same: ;; (` (, (head)) (,@ (tail))) -std-read-> (` (, (head)) (,@ (tail))) ;; (` (, (head)) (,@ (tail))) -cl-read-> (` (, (head)) (,@ (tail))) ;; ;; Without blanks, the form is interpreted differently by the two readers: ;; (`(,(head)) (,@(tail))) -std-read-> (` (, (head)) (,@ (tail))) ;; (`(,(head)) (,@(tail))) -cl-read-> ((` ((, ((head)))) ((,@ ((tail))))) ;; ;; ;; Dispatch Character Macro" `#' ;; ;; #' function quoting ;; #\ character syntax ;; #.
read time evaluation ;; #p, #P paths ;; #+, #- conditional reading ;; #=, ## tags for shared structure reading ;; ;; Other read macros can be added easyly (see the definition of the ;; above ones in this file using the function `set-macro-character') ;; The Cl reader is mostly downward compatile, (exception: backquote ;; comma macro, see above). E.g., this file, which is written entirely ;; in the old Emacs Lisp dialect, can be read and compiled with the ;; cl-reader being activated. ;; Installation: ;; ------------- ;; ;; The package is built on top of Dave Gillespie's cl.el package ;; (version 2.02 or later). The old one (from Ceazar Quiroz, still ;; shiped with the Emacs 19 disributions) will not do. ;; ;; To use the cl-read package automatically when reading in a buffer, ;; it has to be installed using the emacs-lisp-mode-hook: ;; ;; (add-hook 'emacs-lisp-mode-hook 'cl-read-install) ;; ;; (defun cl-read-install () ;; (save-excursion ;; (goto-char (point-min)) ;; (let ((case-fold-search t)) ;; (cond ((re-search-forward ;; "read-syntax: *common-lisp" ;; (save-excursion ;; (end-of-line) ;; (point)) ;; t) ;; (require 'cl-read) ;; (setq cl-read-active t)))))) ;; ;; As most of the Emacs Lisp files are written ;; using the standard syntax, the cl reader is only loaded and ;; activated on elisp files with the "Read-Syntax" property set to ;; "Common-Lisp" (in the property line): ;; ;; -*- Read-Syntax: Common-Lisp -*- ;; ;; Note that both property name ("Read-Syntax") and value ;; ("Common-Lisp") are not case sensitive. There can also be other ;; properties in this line: ;; ;; -*- Mode: Emacs-Lisp; Read-Syntax: Common-Lisp -*- ;; ;; The `cl-read-install' hook function tests for the presence of the ;; correct Read-Syntax property and loads the cl-read package if ;; necessary. This replaces the follwing standard elisp ;; functions: ;; ;; - read ;; - read-from-string ;; - eval-current-buffer ;; - eval-buffer ;; - eval-region ;; ;; There may be other built-in functions that need to be replaced ;; (load, e.g). The behavior of the new reader function depends on ;; the value of the buffer local variable `cl-read-active': if it is ;; nil, they just call the original functions, otherwise they call the ;; cl reader. If the cl reader is active in a buffer, the string "CL" ;; appears behind the mode name in the buffer's mode line. ;; ;; ;; TO DO List: ;; ----------- ;; - Provide a replacement for load so that uncompiled cl syntax ;; source file can be loaded, too. - some have written load in elisp. ;; - Do we really need the (require 'cl) dependency? ;; - More read macros. ;; - Refine the error signaling mechanism. ; Change History ; ; !Log: cl-read.el,v ! ; Revision 1.8 1993/08/10 13:43:34 bosch ; Hook function `cl-read-install' for automatic installation added. ; Buffer local variable `cl-read-active' added: together with the above ; hook it allows the file specific activation of the cl reader. ; ; Revision 1.7 1993/08/10 10:35:21 bosch ; Functions `read*' and `read-from-string*' renamed into `reader:read' ; and `reader:read-from-string'. Whitespace character skipping after ; recursive reader calls removed (Emacs 19 should not need this). ; Functions `cl-reader-install' and `cl-reader-uninstall' updated. ; Introduction text and function comments added. ; ; Revision 1.6 1993/08/09 15:36:05 bosch Function `read*' now nearly ; elisp compatible (no functions as streams, yet -- I don't think I ; will ever implement this, it would be far too slow). Elisp ; compatible function `read-from-string*' added. Replacements for ; `eval-current-buffer', `eval-buffer' and `eval-region' added. ; Renamed feature `cl-dg' in `cl', as Dave Gillespie's cl.el package ; is rather stable now. Function `cl-reader-install' and ; `cl-reader-uninstall' modified. ; ; Revision 1.5 1993/08/09 10:23:35 bosch ; Functions `copy-readtable' and `set-syntax-from-character' added. ; Variable `reader:internal-standard-readtable' added. Standard ; readtable initialization modified. Whitespace skipping placed back ; inside the read loop. ; ; Revision 1.4 1993/05/14 13:00:48 bosch ; Included patches from Daniel LaLiberte. ; ; Revision 1.3 1993/05/11 09:57:39 bosch ; `read*' renamed in `reader:read-from-buffer'. `read*' now can read ; from strings. ; ; Revision 1.2 1993/05/09 16:30:50 bosch ; (require 'cl-read) added. ; Calling of `{before,after}-read-hook' modified. ; ; Revision 1.1 1993/03/29 19:37:21 bosch ; Initial revision ; ; (require 'cl) (provide 'cl-read) ;; load before compiling ;(require 'cl-read) (autoload 'compiled-function-p "bytecomp") (defvar cl-read-active nil "Buffer local variable that enables Common Lisp style syntax reading.") (make-variable-buffer-local 'cl-read-active) (setq-default cl-read-active nil) (or (assq 'cl-read-active minor-mode-alist) (setq minor-mode-alist (cons '(cl-read-active " CL") minor-mode-alist))) ;; The readtable (defvar reader:readtable-size 256 "The size of a readtable." ;; Actually, the readtable is a vector of size (1+ ;; reader:readtable-size), because the last element contains the ;; symbol `readtable', used for defining `readtablep. ) ;; An entry of the readtable must have one of the following forms: ;; ;; 1. A symbol, one of {illegal, constituent, whitespace}. It means ;; the character's reader class. ;; ;; 2. A function (i.e., a symbol with a function definition, a byte ;; compiled function or an uncompiled lambda expression). It means the ;; character is a macro character. ;; ;; 3. A vector of length `reader:readtable-size'. Elements of this vector ;; may be `nil' or a function (see 2.). It means the charater is a ;; dispatch character, and the vector its dispatch fucntion table. (defun* copy-readtable (&optional (from-readtable *readtable*) (to-readtable (make-vector (1+ reader:readtable-size) 'illegal))) "Return a copy of FROM-READTABLE \(default: *readtable*\). If the FROM-READTABLE argument is provided as `nil', make a copy of a standard \(CL-like\) readtable. If TO-READTABLE is provided, modify and return it, otherwise create a new readtable object." (if (null from-readtable) (setq from-readtable reader:internal-standard-readtable)) (loop for i to reader:readtable-size as from-syntax = (aref from-readtable i) do (setf (aref to-readtable i) (if (vectorp from-syntax) (copy-sequence from-syntax) from-syntax)) finally return to-readtable)) (defmacro reader:get-readtable-entry (char readtable) (` (aref (, readtable) (, char)))) (defun set-macro-character (char function &optional readtable) "Makes CHAR to be a macro character with FUNCTION as handler. When CHAR is seen by reader:read-from-buffer, it calls FUNCTION. Returns always t. Optional argument READTABLE is the readtable to set the macro character in (default: *readtable*)." (or readtable (setq readtable *readtable*)) (or (reader:functionp function) (error "Not valid character macro function: %s" function)) (setf (reader:get-readtable-entry char readtable) function) t) (put 'set-macro-character 'edebug-form-spec '(&define sexp function-form &optional sexp)) (put 'set-macro-character 'lisp-indent-function 1) (defun get-macro-character (char &optional readtable) "Return the function associated with the character CHAR in READTABLE \(default: *readtable*.\). If char isn't a macro charater in READTABLE, return nil." (or readtable (setq readtable *readtable*)) (let ((entry (reader:get-readtable-entry char readtable))) (if (reader:functionp entry) entry))) (defun set-syntax-from-character (to-char from-char &optional to-readtable from-readtable) "Make the syntax of TO-CHAR be the same as the syntax of FROM-CHAR. Optional TO-READTABLE and FROM-READTABLE are the corresponding tables to use. TO-READTABLE defaults to the current readtable \(*readtable*\), and FROM-READTABLE to nil, meaning to use the syntaxes from the standard Lisp Readtable." (or to-readtable (setq to-readtable *readtable*)) (or from-readtable (setq from-readtable reader:internal-standard-readtable)) (let ((from-syntax (reader:get-readtable-entry from-char from-readtable))) (if (vectorp from-syntax) ;; dispatch macro character table (setq from-syntax (copy-sequence from-syntax))) (setf (reader:get-readtable-entry to-char to-readtable) from-syntax)) t) ;; Dispatch macro character (defun make-dispatch-macro-character (char &optional readtable) "Let CHAR be a dispatch macro character in READTABLE (default: *readtable*)." (or readtable (setq readtable *readtable*)) (setf (reader:get-readtable-entry char readtable) ;; create a dispatch character table (make-vector reader:readtable-size nil))) (defun set-dispatch-macro-character (disp-char sub-char function &optional readtable) "Make reading CHAR1 followed by CHAR2 be handled by FUNCTION. Optional argument READTABLE (default: *readtable*). CHAR1 must first be made a dispatch char with `make-dispatch-macro-character'." (or readtable (setq readtable *readtable*)) (let ((disp-table (reader:get-readtable-entry disp-char readtable))) ;; check whether disp-char is a valid dispatch character (or (vectorp disp-table) (error "`%c' not a dispatch macro character." disp-char)) ;; check whether function is a valid function (or (reader:functionp function) (error "Not valid dispatch character macro function: %s" function)) (setf (aref disp-table sub-char) function))) (put 'set-dispatch-macro-character 'edebug-form-spec '(&define sexp sexp function-form &optional def-form)) (put 'set-dispatch-macro-character 'lisp-indent-function 2) (defun get-dispatch-macro-character (disp-char sub-char &optional readtable) "Return the macro character function for SUB-CHAR unser DISP-CHAR in READTABLE (default: *readtable*), or nil if there is no such function." (or readtable (setq readtable *readtable*)) (let ((disp-table (reader:get-readtable-entry disp-char readtable))) (and (vectorp disp-table) (reader:functionp (aref disp-table sub-char)) (aref disp-table sub-char)))) (defun reader:functionp (function) "Check whether FUNCTION is a valid function object to be used as (dispatch) macro character function." (or (and (symbolp function) (fboundp function)) (compiled-function-p function) (and (consp function) (eq (first function) 'lambda)))) ;; The basic reader loop ;; shared and circular structure reading (defvar reader:shared-structure-references nil) (defvar reader:shared-structure-labels nil) (defconst before-read-hook nil) (defconst after-read-hook nil) ;; Set the hooks to `read-char' in order to step through the reader: ;; (add-hook 'before-read-hook '(lambda () (message "before") (read-char))) ;; (add-hook 'after-read-hook '(lambda () (message "after") (read-char))) ;; *** Documenting internal things is fine, but you should probably leave ;; them as comments to save space. (defmacro reader:encapsulate-recursive-call (reader-call) "Encapsulate READER-CALL, a form that contains a recursive call to the reader, for usage inside the main reader loop. The macro wraps two hooks around READER-CALL: `before-read-hook' and `after-read-hook'. If READER-CALL returns normally, the macro exits immediately from the surrounding loop with the value of READER-CALL as result. If it exits non-locally (with tag `reader-ignore'), it just returns the value of READER-CALL, in which case the surrounding reader loop continues its execution. In both cases, `before-read-hook' and `after-read-hook' are called before and after executing READER-CALL." (` (prog2 (run-hooks 'before-read-hook) ;; this catch allows to ignore the return, in the case that reader:read-from-buffer ;; should continue looping (e.g. skipping over comments) (catch 'reader-ignore ;; this only works inside a block (e.g., in a loop): ;; go outside (return (prog1 (, reader-call) ;; this occurence of the after hook fires if the ;; reader-call returns normally ... (run-hooks 'after-read-hook)))) ;; ... and that one if it was thrown to the tag 'reader-ignore (run-hooks 'after-read-hook)))) (defvar reader:tmp-buffer (get-buffer-create " *CL Read*")) ;; save a pointer to the original `read-from-string' function (or (fboundp 'reader:original-read-from-string) (fset 'reader:original-read-from-string (symbol-function 'read-from-string))) (defun reader:read-from-string (string &optional start end) "Read one Lisp expression which is represented as text by STRING. Returns a cons: (OBJECT-READ . FINAL-STRING-INDEX). START and END optionally delimit a substring of STRING from which to read; they default to 0 and (length STRING) respectively. This is the cl-read replacement of the standard elisp function `read-from-string'." ;; It doesnt really make sense to have read-from-string depend on ;; what the current buffer happens to be. (if nil ;; (not cl-read-active) (reader:original-read-from-string string start end) (or start (setq start 0)) (or end (setq end (length string))) (save-excursion (set-buffer reader:tmp-buffer) (auto-save-mode -1) (erase-buffer) (insert (substring string 0 end)) (goto-char (1+ start)) (cons (reader:read-from-buffer reader:tmp-buffer nil) (1- (point)))))) ;; (read-from-string "abc (car 'a) bc" 4) ;; (reader:read-from-string "abc (car 'a) bc" 4) ;; (read-from-string "abc (car 'a) bc" 2 11) ;; (reader:read-from-string "abc (car 'a) bc" 2 11) ;; (reader:read-from-string "`(car ,first ,@rest)") ;; (read-from-string ";`(car ,first ,@rest)") ;; (reader:read-from-string ";`(car ,first ,@rest)") ;; save a pointer to the original read function (or (fboundp 'reader:original-read) (fset 'reader:original-read (symbol-function 'read))) (defun reader:read (&optional stream recursive-p) "Read one Lisp expression as text from STREAM, return as Lisp object. If STREAM is nil, use the value of `standard-input' \(which see\). STREAM or the value of `standard-input' may be: a buffer \(read from point and advance it\) a marker \(read from where it points and advance it\) a string \(takes text from string, starting at the beginning\) t \(read text line using minibuffer and use it\). This is the cl-read replacement of the standard elisp function `read'. The only incompatibility is that functions as stream arguments are not supported." (if (not cl-read-active) (reader:original-read stream) (if (null stream) ; read from standard-input (setq stream standard-input)) (if (eq stream 't) ; read from minibuffer (setq stream (read-from-minibuffer "Common Lisp Expression: "))) (cond ((bufferp stream) ; read from buffer (reader:read-from-buffer stream recursive-p)) ((markerp stream) ; read from marker (save-excursion (set-buffer (marker-buffer stream)) (goto-char (marker-position stream)) (reader:read-from-buffer (current-buffer) recursive-p))) ((stringp stream) ; read from string (save-excursion (set-buffer reader:tmp-buffer) (auto-save-mode -1) (erase-buffer) (insert stream) (goto-char (point-min)) (reader:read-from-buffer reader:tmp-buffer recursive-p))) (t (error "CL reader error: Not a valid stream: %s" stream))))) ;; (reader:read "#'car") ;; (read) ;; (reader:read) ;; (let ((standard-input nil)) (reader:read)) ;; (reader:read (current-buffer)) 'hello ;; (reader:read (save-excursion (backward-sexp 1) (point-marker))) (defun reader:read-from-buffer (&optional stream recursive-p) (or (bufferp stream) (error "Sorry, can only read on buffers")) (if (not recursive-p) (let (reader:shared-structure-references reader:shared-structure-labels) (reader:restore-shared-structure (reader:read-from-buffer stream 't))) (loop for char = (following-char) for entry = (reader:get-readtable-entry char *readtable*) if (eobp) do (error "CL read error: End of file during reading") do (cond ((eq entry 'illegal) (error "CL read error: `%c' has illegal character syntax" char)) ;; skipping whitespace characters must be done inside this ;; loop as character macro subroutines may return without ;; leaving the loop using (throw 'reader-ignore ...) ((eq entry 'whitespace) (forward-char 1) ;; skip all whitespace (while (eq 'whitespace (reader:get-readtable-entry (following-char) *readtable*)) (forward-char 1))) ;; for every token starting with a constituent character ;; call the built-in reader (symbols, numbers, strings, ;; characters with ? syntax) ((eq entry 'constituent) (reader:encapsulate-recursive-call (reader:read-constituent stream))) ((vectorp entry) ;; Dispatch macro character. The dispatch macro character ;; function is contained in the vector `entry', at the ;; place indicated by , the first non-digit ;; character following the : ;; * (reader:encapsulate-recursive-call (loop initially do (forward-char 1) for sub-char = (prog1 (following-char) (forward-char 1)) while (memq sub-char '(?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9)) collect sub-char into digit-args finally (return (funcall ;; no test is done here whether a non-nil ;; contents is a correct dispatch character ;; function to apply. (or (aref entry sub-char) (error "Cl reader error: undefined subsequent dispatch \ character `%c'" sub-char)) stream sub-char (string-to-int (apply 'concat (mapcar 'char-to-string digit-args)))))))) (t ;; must be a macro character. In this case, `entry' is ;; the function to be called (reader:encapsulate-recursive-call (progn (forward-char 1) (funcall entry stream char)))))))) ;; ?\" ;; '[aaaa (a . b) bbbb] `(a ,b ,@l) (` a (, b) (,@ l)) (put 'reader:encapsulate-recursive-call 'edebug-form-spec '(form)) (put 'reader:encapsulate-recursive-call 'lisp-indent-function 0) '(defun reader:read-constituent (stream) ;; For Emacs 19, just read it. (reader:original-read stream)) (defun reader:read-constituent (stream) (prog1 (reader:original-read stream) ;;; For Emacs 18, backing up is necessary because the `read' ;;; function reads one character too far after reading a symbol or number. ;;; This doesnt apply to reading chars (e.g. ?n). ;; This still loses for escaped chars. (if (not (eq (reader:get-readtable-entry (preceding-char) *readtable*) 'constituent)) (forward-char -1)))) ;; Creation and initialization of an internal standard readtable. (defconst reader:internal-standard-readtable (loop with raw-readtable = (make-vector (1+ reader:readtable-size) 'illegal) initially do (setf (aref raw-readtable reader:readtable-size) 'readtable) for entry in '((constituent ?! ?@ ?$ ?% ?& ?* ?_ ?- ?+ ?= ?/ ?\\ ?0 ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?: ?~ ?> ?< ?a ?b ?c ?d ?e ?f ?g ?h ?i ?j ?k ?l ?m ?n ?o ?p ?q ?r ?s ?t ?u ?v ?w ?x ?y ?z ?A ?B ?C ?D ?E ?F ?G ?H ?I ?J ?K ?L ?M ?N ?O ?P ?Q ?R ?S ?T ?U ?V ?W ?X ?Y ?Z) (whitespace ? ?\t ?\n ?\r ?\f) ;; The following CL character classes are only useful for ;; token parsing. We don't need them, as token parsing is ;; left to the built-in reader. ;; (single-escape ?\\) ;; (multiple-escape ?|) ) do (loop for char in (rest entry) do (setf (reader:get-readtable-entry char raw-readtable) (first entry))) finally return raw-readtable) "The original (CL-like) standard readtable. If you ever modify this readtable, you won't be able to recover a standard readtable using \(copy-readtable nil\)") ;; Variables used non-locally in the standard readmacros (defvar reader:context) (defvar reader:stack) (defvar recursive-p) ; need reader: prefix. ;; Changing the setting for a char here needs to be done ;; for both the internal standard readtable and the *readtable*. ;; Is (set-syntax-from-character char char *readtable*) sufficient? ;; Could we instead set-macro-character for *readtable* in the source below, ;; and then copy *readtable* to reader:internal-standard-readtable? ;; Chars and strings ;; This is defined to distinguish chars from constituents ;; since chars read without reading too far. (set-macro-character ?\? (function (lambda (stream char) (forward-char -1) (reader:original-read stream))) reader:internal-standard-readtable) ;; This is defined to distinguish strings from constituents ;; since backing up after reading a string is simpler. (set-macro-character ?\" (function (lambda (stream char) (forward-char -1) (prog1 (reader:original-read stream) ;; This is not needed with Emacs 19. See above. (if (/= (preceding-char) ?\") (forward-char -1))))) reader:internal-standard-readtable) ;; Lists and dotted pairs (set-macro-character ?\( (function (lambda (stream char) (catch 'read-list (let ((reader:context 'list) reader:stack ) ;; read list elements up to a `.' (catch 'dotted-pair (while t (setq reader:stack (cons (reader:read-from-buffer stream 't) reader:stack)))) ;; In dotted pair. Read one more element (setq reader:stack (cons (reader:read-from-buffer stream 't) reader:stack) ;; signal it to the closing paren reader:context 'dotted-pair) ;; this *must* be the closing paren that throws away from here (reader:read-from-buffer stream 't) ;; otherwise an error is signalled (error "illegal dotted pair read syntax"))))) reader:internal-standard-readtable) (set-macro-character ?\) (function (lambda (stream char) (cond ((eq reader:context 'list) (throw 'read-list (nreverse reader:stack))) ((eq reader:context 'dotted-pair) ;(throw 'read-list (apply 'list* (nreverse reader:stack))) (throw 'read-list (nconc (nreverse (cdr reader:stack)) (car reader:stack))) ) (t (error "CL read error: `)' doesn't end a list"))))) reader:internal-standard-readtable) (set-macro-character ?\. (function (lambda (stream char) (and (eq reader:context 'dotted-pair) (error "CL read error: no more than one `.' allowed in list")) (throw 'dotted-pair nil))) reader:internal-standard-readtable) ;; '(#\a . #\b) ;; '(a . (b . c)) ;; Vectors: [a b] (set-macro-character ?\[ (function (lambda (stream char) (let ((reader:context 'vector)) (catch 'read-vector (let ((reader:context 'vector) reader:stack) (while t (push (reader:read-from-buffer stream 't) reader:stack))))))) reader:internal-standard-readtable) (set-macro-character ?\] (function (lambda (stream char) (if (eq reader:context 'vector) (throw 'read-vector (apply 'vector (nreverse reader:stack))) (error "CL read error: `]' doesn't end a vector")))) reader:internal-standard-readtable) ;; Quote and backquote comma macro (set-macro-character ?\' (function (lambda (stream char) (list 'quote (reader:read-from-buffer stream 't)))) reader:internal-standard-readtable) (set-macro-character ?\` (function (lambda (stream char) (if (= (following-char) ?\ ) ;; old backquote syntax. This is ambigous, because ;; (`(sexp)) is a valid form in both syntaxes, but ;; unfortunately not the same. ;; old syntax: read -> (` (sexp)) ;; new syntax: read -> ((` (sexp))) '\` (list '\` (reader:read-from-buffer stream 't))))) reader:internal-standard-readtable) (set-macro-character ?\, (function (lambda (stream char) (cond ((eq (following-char) ?\ ) ;; old syntax '\,) ((eq (following-char) ?\@) (forward-char 1) (cond ((eq (following-char) ?\ ) '\,\@) ((list '\,\@ (reader:read-from-buffer stream 't))))) ((list '\, (reader:read-from-buffer stream 't)))))) reader:internal-standard-readtable) ;; 'a '(a b c) ;; `(,a ,@b c) ;; the old syntax is also supported: ;; (` ((, a) (,@ b) c)) ;; Single character comment: ; (set-macro-character ?\; (function (lambda (stream char) (skip-chars-forward "^\n\r") (throw 'reader-ignore nil))) reader:internal-standard-readtable) ;; Standard CL dispatch character # (make-dispatch-macro-character ?\# reader:internal-standard-readtable) ;; Function quoting: #' (set-dispatch-macro-character ?\# ?\' (function (lambda (stream char n) (or (= n 0) (error "Cl reader error: numeric infix argument not allowed %d" n)) (list (if (featurep 'cl) 'function* 'function) (reader:read-from-buffer stream 't)))) reader:internal-standard-readtable) ;; Character syntax: #\ ;; Not yet implemented: #\Control-a #\M-C-a etc. (set-dispatch-macro-character ?# ?\\ (function (lambda (stream char n) (or (= n 0) (error "Cl reader error: numeric infix argument not allowed %d" n)) (let ((next (following-char)) name) (if (not (and (<= ?a next) (<= next ?z))) (progn (forward-char 1) next) (setq next (reader:read-from-buffer stream t)) (cond ((symbolp next) (setq name (symbol-name next))) ((integerp next) (setq name (int-to-string next)))) (if (= 1 (length name)) (string-to-char name) (case next (linefeed ?\n) (newline ?\r) (space ?\ ) (rubout ?\b) (page ?\f) (tab ?\t) (return ?\C-m) (t (error "CL read error: unknown character specification `%s'" next)))))))) reader:internal-standard-readtable) ;; '(#\# #\> #\< #\a #\A #\tab #\return #\space) ;; Read and load time evaluation: #. ;; Not yet implemented: #, (set-dispatch-macro-character ?\# ?\. (function (lambda (stream char n) (or (= n 0) (error "Cl reader error: numeric infix argument not allowed %d" n)) ;; This eval will see all internal vars of reader, ;; e.g. stream, recursive-p. Anything that might be bound. (eval (reader:read-from-buffer stream t)))) reader:internal-standard-readtable) ;; '(#.(current-buffer) #.(get-buffer "*scratch*")) ;; Path names (kind of): #p, #P, (set-dispatch-macro-character ?\# ?\P (function (lambda (stream char n) (or (= n 0) (error "Cl reader error: numeric infix argument not allowed %d" n)) (let ((string (reader:read-from-buffer stream 't))) (or (stringp string) (error "Cl reader error: Pathname must be a string: %s" string)) (expand-file-name string)))) reader:internal-standard-readtable) (set-dispatch-macro-character ?\# ?\p (get-dispatch-macro-character ?\# ?\P reader:internal-standard-readtable) reader:internal-standard-readtable) ;; #P"~/.emacs" ;; #p"~root/home" ;; Feature reading: #+, #- ;; Not yet implemented: #+, #- (set-dispatch-macro-character ?\# ?\+ (function (lambda (stream char n) (or (= n 0) (error "Cl reader error: numeric infix argument not allowed %d" n)) (let ((feature (reader:read-from-buffer stream 't)) (object (reader:read-from-buffer stream 't))) (if (featurep feature) object (throw 'reader-ignore nil))))) reader:internal-standard-readtable) (set-dispatch-macro-character ?\# ?\- (function (lambda (stream char n) (or (= n 0) (error "Cl reader error: numeric infix argument not allowed %d" n)) (let ((feature (reader:read-from-buffer stream 't)) (object (reader:read-from-buffer stream 't))) (if (featurep feature) (throw 'reader-ignore nil) object)))) reader:internal-standard-readtable) ;; (#+cl loop #+cl do #-cl while #-cl t (body)) ;; Circular and shared structure reading: #=, ## (set-dispatch-macro-character ?\# ?\= (function (lambda (stream char n) (if (memq n reader:shared-structure-labels) (error "Cl reader error: label defined twice") (push n reader:shared-structure-labels)) (let* ((string (int-to-string n)) (ref (or (find string reader:shared-structure-references :test 'string=) (first (push (make-symbol string) reader:shared-structure-references))))) (setf (symbol-value ref) ;; this is also the return value (reader:read-from-buffer stream 't))))) reader:internal-standard-readtable) (set-dispatch-macro-character ?\# ?\# (function (lambda (stream char n) ;; using the non-local variable `recursive-p' (from the reader ;; main loop) doesn't seems very clever. Should do this ;; differently ... (if (not recursive-p) (error "Cl reader error: references at top level not allowed")) (let* ((string (int-to-string n)) (ref (or (find string reader:shared-structure-references :test 'string=) (first (push (make-symbol string) reader:shared-structure-references))))) ;; the value of reading a #n# form is a reference symbol ;; whose symbol value will be the shared structure ref))) reader:internal-standard-readtable) (defun reader:restore-shared-structure (obj) (cond ((consp obj) (if (memq (car obj) reader:shared-structure-references) (setf (car obj) (symbol-value (car obj))) (reader:restore-shared-structure (car obj))) (if (memq (cdr obj) reader:shared-structure-references) (setf (cdr obj) (symbol-value (cdr obj))) (reader:restore-shared-structure (cdr obj)))) ((vectorp obj) (loop for i below (length obj) do (if;; substructure is a reference (memq (aref obj i) reader:shared-structure-references) ;; replace it by the pointer in the cdr of the ref (setf (aref obj i) (symbol-value (aref obj i))) (reader:restore-shared-structure (aref obj i)))))) obj) ;; #1=(a b #3=[#2=c]) ;; (#1=[#\return #\a] #1# #1#) ;; (#1=[a b c] #1# #1#) ;; #1=(a b . #1#) ;; Now make the current readtable (defvar *readtable* (copy-readtable nil) "The current readtable.") ;; Replace built-in functions that call the (built-in) reader: (or (fboundp 'reader:original-eval-current-buffer) (fset 'reader:original-eval-current-buffer (symbol-function 'eval-current-buffer))) (defun reader:eval-current-buffer (&optional printflag) "Evaluate the current buffer as Lisp code. Programs can pass argument PRINTFLAG which controls printing of output: nil means discard it\; anything else is stream for print. This is the cl-read replacement of the standard elisp function `eval-current-buffer'." ;; The standard eval-current-buffer doesn't use eval-region. (interactive) (if (not cl-read-active) (reader:original-eval-current-buffer printflag) (reader:eval-buffer (current-buffer)))) (or (fboundp 'reader:original-eval-buffer) (fset 'reader:original-eval-buffer (if (fboundp 'eval-buffer) ;; only in Emacs 19. (symbol-function 'eval-buffer) 'eval-buffer))) (defun reader:eval-buffer (bufname &optional printflag) "Execute BUFFER as Lisp code. Programs can pass argument PRINTFLAG which controls printing of output: nil means discard it; anything else is stream for print. This is the cl-read replacement of the standard elisp function `eval-buffer'." (interactive "bBuffer: ") (if (not cl-read-active) (reader:original-eval-buffer bufname printflag) (save-excursion (set-buffer (or (get-buffer bufname) (error "No such buffer: %s" bufname))) (reader:eval-region (point-min) (point-max) printflag)))) (or (fboundp 'reader:original-eval-region) (fset 'reader:original-eval-region (symbol-function 'eval-region))) ;; (borrowed from Daniel LaLiberte's edebug) (defun reader:eval-region (start end &optional output) "Execute the region as Lisp code. When called from programs, expects two arguments, giving starting and ending indices in the current buffer of the text to be executed. Programs can pass third argument PRINTFLAG which controls output: nil means discard it; anything else is stream for printing it. If there is no error, point does not move. If there is an error, point remains at the end of the last character read from the buffer. arguments: (b e &optional printflag) This is the cl-read replacement of the standard elisp function `eval-region'." ;; One other difference concerns inserting whitespace after the expression. (interactive "r") (if (not cl-read-active) (reader:original-eval-region start end output) (let ((pnt (point)) (buf (current-buffer)) (inside-buf (current-buffer)) ;; Mark the end because it may move. (end-marker (set-marker (make-marker) end)) form val) (goto-char start) (reader:skip-whitespace) (while (< (point) end-marker) (setq form (reader:read-from-buffer inside-buf)) ;; Evaluate normally - after restoring the current-buffer. (let ((current-buffer (current-buffer))) (set-buffer inside-buf) (setq val (eval form)) ;; *** All the local vars above are visible. ;; Remember current buffer for next time. (setq inside-buf (current-buffer)) (set-buffer current-buffer)) (if output (let ((standard-output (or output t))) (setq values (cons val values)) (if (eq standard-output t) (prin1 val) (princ "\n") (prin1 val) (princ "\n") ))) (goto-char (min (max end-marker (point)) (progn (reader:skip-whitespace) (point))))) (if (null output) ;; like save-excursion recovery, but only if no error (progn ;; but mark is not restored (set-buffer buf) (goto-char pnt))) ;; return always nil nil))) (defun reader:skip-whitespace () ;; Leave point before the next token, skipping white space and comments. (skip-chars-forward " \t\r\n\f") (while (= (following-char) ?\;) (skip-chars-forward "^\n\r") ; skip the comment (skip-chars-forward " \t\r\n\f"))) ;; installing/uninstalling the cl reader (defun cl-reader-install () (interactive) (fset 'read 'reader:read) (fset 'read-from-string 'reader:read-from-string) (fset 'eval-current-buffer 'reader:eval-current-buffer) (fset 'eval-buffer 'reader:eval-buffer) (fset 'eval-region 'reader:eval-region)) (defun cl-reader-uninstall () (interactive) (fset 'read (symbol-function 'reader:original-read)) (fset 'read-from-string (symbol-function 'reader:original-read-from-string)) (fset 'eval-current-buffer (symbol-function 'reader:original-eval-current-buffer)) (fset 'eval-buffer (symbol-function 'reader:original-eval-buffer)) (fset 'eval-region (symbol-function 'reader:original-eval-region))) ;; now install the replacement functions: (cl-reader-install) ; Example: ; ; After having called `cl-reader-install', this function can be compiled ; (M-x elisp-compile-defun) with the syntax used here, but ; `eval-last-sexp' and `eval-defun' don't work always (???) ; ; ; ;(defun test (el list) ; (mapcar #'list ; `(,el ,@list #\a #\0 #\return ))) ; ; You also can call the reader explicitly as follows: ; ;(read (current-buffer)) ;; <- set point after "))", then type C-x C-e ;(defun test (el list) ; (mapcar #'list ; `(,el ,@list #\a #\0 #\return ))) ;;; also, the `eval-print-last-sexp' function (LF in *scratch*) seems to work ;;; partially: ; ;#'car ;car ;'(#\a #\b) ;(97 98) ; ;;; but there is an error on that form: ;(setq l '(a b c)) ;(a b c) ;`(,l ,@l) ;((a b c) a b c) ;) ;; end cl-read.el