;;;; the backquote reader macro ;;;; This software is part of the SBCL system. See the README file for ;;;; more information. ;;;; ;;;; This software is derived from the CMU CL system, which was ;;;; written at Carnegie Mellon University and released into the ;;;; public domain. The software is in the public domain and is ;;;; provided with absolutely no warranty. See the COPYING and CREDITS ;;;; files for more information. (in-package "SB!IMPL") (/show0 "entering backq.lisp") ;;; The flags passed back by BACKQUOTIFY can be interpreted as follows: ;;; ;;; |`,|: [a] => a ;;; NIL: [a] => a ;the NIL flag is used only when a is NIL ;;; T: [a] => a ;the T flag is used when a is self-evaluating ;;; QUOTE: [a] => (QUOTE a) ;;; APPEND: [a] => (APPEND . a) ;;; NCONC: [a] => (NCONC . a) ;;; LIST: [a] => (LIST . a) ;;; LIST*: [a] => (LIST* . a) ;;; ;;; The flags are combined according to the following set of rules: ;;; ([a] means that a should be converted according to the previous table) ;;; ;;; \ car || otherwise | QUOTE or | |`,@| | |`,.| ;;;cdr \ || | T or NIL | | ;;;================================================================================ ;;; |`,| || LIST* ([a] [d]) | LIST* ([a] [d]) | APPEND (a [d]) | NCONC (a [d]) ;;; NIL || LIST ([a]) | QUOTE (a) | a | a ;;;QUOTE or T|| LIST* ([a] [d]) | QUOTE (a . d) | APPEND (a [d]) | NCONC (a [d]) ;;; APPEND || LIST* ([a] [d]) | LIST* ([a] [d]) | APPEND (a . d) | NCONC (a [d]) ;;; NCONC || LIST* ([a] [d]) | LIST* ([a] [d]) | APPEND (a [d]) | NCONC (a . d) ;;; LIST || LIST ([a] . d) | LIST ([a] . d) | APPEND (a [d]) | NCONC (a [d]) ;;; LIST* || LIST* ([a] . d) | LIST* ([a] . d) | APPEND (a [d]) | NCONC (a [d]) ;;; ;;; involves starting over again pretending you had read ".,a)" instead ;;; of ",@a)" (defvar *backquote-count* 0 #!+sb-doc "how deep we are into backquotes") (defvar *bq-comma-flag* '(|,|)) (defvar *bq-at-flag* '(|,@|)) (defvar *bq-dot-flag* '(|,.|)) (defvar *bq-vector-flag* '(|bqv|)) (defvar *bq-error* "Comma not inside a backquote.") (/show0 "backq.lisp 50") ;;; the actual character macro (defun backquote-macro (stream ignore) (declare (ignore ignore)) (let ((*backquote-count* (1+ *backquote-count*))) (multiple-value-bind (flag thing) (backquotify stream (read stream t nil t)) (when (eq flag *bq-at-flag*) (simple-reader-error stream ",@ after backquote in ~S" thing)) (when (eq flag *bq-dot-flag*) (simple-reader-error stream ",. after backquote in ~S" thing)) (backquotify-1 flag thing)))) (/show0 "backq.lisp 64") (defun comma-macro (stream ignore) (declare (ignore ignore)) (unless (> *backquote-count* 0) (when *read-suppress* (return-from comma-macro nil)) (simple-reader-error stream *bq-error*)) (let ((c (read-char stream)) (*backquote-count* (1- *backquote-count*))) (flet ((check (what) (let ((x (peek-char t stream t nil t))) (when (and (char= x #\)) (eq #'read-right-paren (get-macro-character #\)))) ;; Easier to figure out than an "unmatched parenthesis". (simple-reader-error stream "Trailing ~A in backquoted expression." what))))) (cond ((char= c #\@) (check "comma-at") (cons *bq-at-flag* (read stream t nil t))) ((char= c #\.) (check "comma-dot") (cons *bq-dot-flag* (read stream t nil t))) (t (unread-char c stream) (check "comma") (cons *bq-comma-flag* (read stream t nil t))))))) (/show0 "backq.lisp 83") ;;; (defun expandable-backq-expression-p (object) (and (consp object) (let ((flag (car object))) (or (eq flag *bq-at-flag*) (eq flag *bq-dot-flag*))))) (defun backquote-splice (method dflag a d what stream) (cond (dflag (values method (cond ((eq dflag method) (cons a d)) (t (list a (backquotify-1 dflag d)))))) ((expandable-backq-expression-p a) (values method (list a))) ((not (and (atom a) (backq-constant-p a))) ;; COMMA special cases a few constant atoms, which ;; are illegal in splices. (comma a)) (t (simple-reader-error stream "Invalid splice in backquote: ~A~A" what a)))) ;;; This does the expansion from table 2. (defun backquotify (stream code) (cond ((atom code) (cond ((null code) (values nil nil)) ((or (consp code) (symbolp code)) ;; Keywords are self-evaluating. Install after packages. (values 'quote code)) (t (values t code)))) ((or (eq (car code) *bq-at-flag*) (eq (car code) *bq-dot-flag*)) (values (car code) (cdr code))) ((eq (car code) *bq-comma-flag*) (comma (cdr code))) ((eq (car code) *bq-vector-flag*) (multiple-value-bind (dflag d) (backquotify stream (cdr code)) (values 'vector (backquotify-1 dflag d)))) (t (multiple-value-bind (aflag a) (backquotify stream (car code)) (multiple-value-bind (dflag d) (backquotify stream (cdr code)) (when (eq dflag *bq-at-flag*) ;; Get the errors later. (simple-reader-error stream ",@ after dot in ~S" code)) (when (eq dflag *bq-dot-flag*) (simple-reader-error stream ",. after dot in ~S" code)) (cond ((eq aflag *bq-at-flag*) (backquote-splice 'append dflag a d ",@" stream)) ((eq aflag *bq-dot-flag*) (backquote-splice 'nconc dflag a d ",." stream)) ((null dflag) (if (member aflag '(quote t nil)) (values 'quote (list a)) (values 'list (list (backquotify-1 aflag a))))) ((member dflag '(quote t)) (if (member aflag '(quote t nil)) (values 'quote (cons a d )) (values 'list* (list (backquotify-1 aflag a) (backquotify-1 dflag d))))) (t (setq a (backquotify-1 aflag a)) (if (member dflag '(list list*)) (values dflag (cons a d)) (values 'list* (list a (backquotify-1 dflag d))))))))))) (/show0 "backq.lisp 139") (defun backq-constant-p (x) (or (numberp x) (eq x t))) ;;; This handles the cases. (defun comma (code) (cond ((atom code) (cond ((null code) (values nil nil)) ((backq-constant-p code) (values t code)) (t (values *bq-comma-flag* code)))) ((and (eq (car code) 'quote) (not (expandable-backq-expression-p (cadr code)))) (values (car code) (cadr code))) ((member (car code) '(append list list* nconc)) (values (car code) (cdr code))) ((eq (car code) 'cons) (values 'list* (cdr code))) (t (values *bq-comma-flag* code)))) (/show0 "backq.lisp 157") ;;; This handles table 1. (defun backquotify-1 (flag thing) (cond ((or (eq flag *bq-comma-flag*) (member flag '(t nil))) thing) ((eq flag 'quote) (list 'quote thing)) ((eq flag 'list*) (cond ((and (null (cddr thing)) (not (expandable-backq-expression-p (car thing))) (not (expandable-backq-expression-p (cadr thing)))) (cons 'backq-cons thing)) ((expandable-backq-expression-p (car (last thing))) (list 'backq-append (cons 'backq-list (butlast thing)) ;; Can it be optimized further? -- APD, 2001-12-21 (car (last thing)))) (t (cons 'backq-list* thing)))) ((eq flag 'vector) (list 'backq-vector thing)) (t (cons (ecase flag ((list) 'backq-list) ((append) 'backq-append) ((nconc) 'backq-nconc)) thing)))) ;;;; magic BACKQ- versions of builtin functions (/show0 "backq.lisp 184") ;;; Define synonyms for the lisp functions we use, so that by using ;;; them, the backquoted material will be recognizable to the ;;; pretty-printer. (macrolet ((def (b-name name) ;; FIXME: This function should be INLINE so that the lists ;; aren't consed twice, but I ran into an optimizer bug the ;; first time I tried to make this work for BACKQ-LIST. See ;; whether there's still an optimizer bug, and fix it if so, and ;; then make these INLINE. `(defun ,b-name (&rest rest) (declare (truly-dynamic-extent rest)) (apply #',name rest)))) (def backq-list list) (def backq-list* list*) (def backq-append append) (def backq-nconc nconc) (def backq-cons cons)) (/show0 "backq.lisp 204") (defun backq-vector (list) (declare (list list)) (coerce list 'simple-vector)) ;;;; initialization (/show0 "backq.lisp 212") ;;; Install BACKQ stuff in the current *READTABLE*. ;;; ;;; In the target Lisp, we have to wait to do this until the readtable ;;; has been created. In the cross-compilation host Lisp, we can do ;;; this right away. (You may ask: In the cross-compilation host, ;;; which already has its own implementation of the backquote ;;; readmacro, why do we do this at all? Because the cross-compilation ;;; host might -- as SBCL itself does -- express the backquote ;;; expansion in terms of internal, nonportable functions. By ;;; redefining backquote in terms of functions which are guaranteed to ;;; exist on the target Lisp, we ensure that backquote expansions in ;;; code-generating code work properly.) (defun !backq-cold-init () (set-macro-character #\` #'backquote-macro) (set-macro-character #\, #'comma-macro)) #+sb-xc-host (!backq-cold-init) ;;; The pretty-printer needs to know about our special tokens (defvar *backq-tokens* '(backq-comma backq-comma-at backq-comma-dot backq-list backq-list* backq-append backq-nconc backq-cons backq-vector)) ;;; Since our backquote is installed on the host lisp, and since ;;; developers make mistakes with backquotes and commas too, let's ;;; ensure that we can report errors rather than get an undefined ;;; function condition on SIMPLE-READER-ERROR. #+sb-xc-host ; proper definition happens for the target (defun simple-reader-error (stream format-string &rest format-args) (bug "READER-ERROR on stream ~S: ~?" stream format-string format-args)) (/show0 "done with backq.lisp")