;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*- Author: Peter Norvig ;;; File: ltd-table.lisp; Date: /95 (in-package :cl-user) ;;;; Functions called from the (ltd-fn ...) table in tables.lisp ;;;; CLtL2 CH 2: DATA TYPES (defun cvt-type-exp (exp) ;; Convert a CL type specification (with the quote!) to Dylan. (if (starts-with (strip exp) 'quote) (cvt-type (second/ exp)) (cvt-exp exp))) (defun cvt-type (spec) ;; Convert a CL type specification (without the quote!) to Dylan. ;; If the spec is a name that is known from a cvt-type declaration, ;; then look it up. If it is an unknown name, just wrap <> around ;; it. If the spec is an (or ...), do each branch. For example, ;; (or standard-class my-class) converts to (type-union ). (cond ((null spec) nil) ((starts-with spec 'or) `(type-union ,@(mapcar #'cvt-type (args spec)))) ((starts-with spec 'integer) `(limited ;; Or is it :from and :to ?? ,@(when (second/ spec) `(:min ,(second/ spec))) ,@(when (third spec) `(:max ,(third spec))))) ((starts-with spec 'member) `(one-of ,@(mapcar #'kwote (rest/ spec)))) ((not (symbolp spec)) (cvt-erroneous spec spec "Can't convert type specification.")) ((get-cvt-type spec)) ((starts-with spec #\$) spec) (t (mksymbol '< spec '>)))) ;;;; CLtL2 CH 4: TYPE SPECIFIERS (defun cvt-deftype (exp) ;; Converts parameterless types only (safe-destructuring-bind (name arglist body1 . body) (args exp) (if (and (false? arglist) (starts-with 'quote (strip body1)) (false? body)) `(define-constant ,(cvt-type name) ,(cvt-type-exp (second/ (strip body1)))) (cvt-erroneous exp '|\#f| "Can't handle complex deftypes.")))) ;;;; CLtL2 CH 5: PROGRAM STRUCTURE (defun cvt-defun (exp) "Convert a defun or basic defmethod into a define-method." `(,(get-option :defun-as) ,(second/ exp) ,@(cvt-parms-and-body (nthcdr 2 exp) (second/ exp)))) (defun cvt-lambda (exp &optional) `(method ,@(cvt-parms-and-body (rest/ exp)))) (defun cvt-parms-and-body (parms-and-body &optional name) (safe-destructuring-bind (parms . body) parms-and-body (let* ((declarations (extract-just-declarations body)) (vals (extract-values-declaration declarations))) `(,(cvt-parms parms :declarations declarations) ,@(ifd vals (list vals)) ,@(cvt-body body :name name))))) (defun cvt-parms (lambda-list &key body (declarations (extract-just-declarations body))) ;; Convert &*** words, handle specializers and defaults, add in declarations ;; Return &aux bindings as second argument. (when (eq (strip lambda-list) '|()|) (setq lambda-list '())) (let ((key? nil) ; have we seen a &key (or &optional) yet? (aux? nil) ; have we seen an &aux yet? (auxs nil)) (labels ; arg is one of var, (var val), ((key var) val), (var type) ((key (arg) (cond ((atom arg) nil) ((atom (first/ arg)) nil) (t (first/ (first/ arg))))) (var (arg) (cond ((atom arg) arg) ((atom (first/ arg)) (first/ arg)) (t (second/ (first/ arg))))) (val (arg) (if (and (consp arg) key?) (cvt-exp (second/ arg)) nil)) (var-and-typ (arg) (if (and (not key?) (consp arg)) `(|::| ,(var arg) ,(cvt-type (second/ arg))) (add-type-declaration (var arg) declarations))) (var-and-typ-and-val (arg) (if (val arg) `(= ,(var-and-typ arg) ,(val arg)) (var-and-typ arg))) (cvt-arg (arg) (cond (aux? (push (list (var arg) (val arg)) auxs) nil) ((member arg '(&body &whole &environment)) (cvt-erroneous lambda-list arg "Can't handle macros.")) ((eq arg '&aux) (setq aux? t) nil) ((eq arg '&optional) (setq key? t) '|\#key|) ((eq arg '&rest) ' |\#rest|) ((eq arg '&key) (if key? NIL (progn (setq key? t) '|\#key|))) ((eq arg '&allow-other-keys) '|\#all-keys|) ((key arg) `(:args-bare ,(key arg) ,(var-and-typ-and-val arg))) (t (var-and-typ-and-val arg)))) (cvt-args (args) (cond ((atom args) args) (t (let* ((one (cvt-arg (first/ args))) (two (cvt-args (rest/ args)))) (if one (cons one two) two)))))) (values `(:args ,@(cvt-args lambda-list)) (nreverse auxs))))) ;;;; CLtL2 CH 6: PREDICATES (defun cvt-to-binary (exp) "Convert (+ a b c d) to (+ (+ (+ a b) c) d)." (if (not (call? exp)) (if (get-option :only-binary-arithmetic-ops) (second/ exp) ; E.g. #'+ => + (let ((id (case (second/ exp) ((+ -) 0) ((* /) 1)))) `(method (:args |\#rest| args) (reduce ,id (second/ op) args)))) (let ((args (cvt-exps (args exp)))) (case (length args) (0 (ecase (op exp) ((+) 0) ((*) 1) ((- /) (cvt-erroneous exp 0 "Missing arguments to ~A." (op exp))) ((and &) '|\#t|) ((or \|) '|\#f|))) (1 (case (op exp) ((-) (if (numberp (first/ args)) (eval exp) exp)) ((/) `(/ 1 ,(first args))) (t (first/ args)))) (2 (cons (op exp) args)) (otherwise (let ((result (list (op exp) (first/ args) (second/ args)))) (dolist (arg (nthcdr 2 args)) (setf result (list (op exp) result arg))) result)))))) ;;;; CLtL2 CH 7: CONTROL STRUCTURE (defun cvt-fn (exp) "Convert a CL expression, interpreted as a function, to Dylan." ;; If the exp is of the form 'fn or #'fn, use the cvt-fn tables. ;; Otherwise, just convert as a regular expression. (cond ((and (or (starts-with exp 'function) (starts-with exp 'quote)) (consp exp) (length=1 (args exp))) (let ((fn (second/ exp))) (cond ((starts-with fn 'setf) (mksymbol (second/ fn) '-setter)) ((starts-with fn 'lambda) (cvt-lambda fn)) ((not (symbolp fn)) (cvt-exp fn)) ((get-cvt-fn fn) (funcall (get-cvt-fn fn) exp)) (t fn)))) (t (cvt-exp exp)))) (defun cvt-setf (exp) (maybe-begin (loop for (var val) on (args exp) by 'cddr collect `(:= ,(cvt-exp var) ,(cvt-exp val))))) (defun binding-var (binding) (if (consp binding) (first/ binding) binding)) (defun binding-val (binding) (if (consp binding) (second/ binding) '|\#f|)) (defun cvt-let (exp) (safe-destructuring-bind (bindings . body) (args exp) (when (eq bindings '|()|) (setq bindings '())) (let ((declarations (extract-just-declarations body))) (if (let-can-use-serial-binding? bindings) (cvt-let* exp) `(begin (let (:list ,@(mapcar #'(lambda (b) (add-type-declaration (binding-var b) declarations)) bindings)) (values ,@(mapcar #'(lambda (b) (cvt-exp (binding-val b))) bindings)) ,@(cvt-body body))))))) (defun cvt-compiler-let (exp) (cvt-erroneous exp (cvt-let exp) "COMPILER-LET converted to LET")) (defun cvt-let* (exp) "Convert let* to nested lets." (safe-destructuring-bind (vars . body) (rest/ exp) (move-comment vars (let ((code (cvt-body body)) (declarations (extract-just-declarations body))) (when (eq vars '|()|) (setq vars '())) (if (null vars) (maybe-begin code) `(begin ,(dolist (binding (reverse vars) (first/ code)) (move-comment binding (let* ((var (binding-var binding)) (val (cvt-exp (binding-val binding))) (special? (bracketed-with (string var) #\*))) (setf code (if special? `((fluid-bind (= ,(add-type-declaration var declarations) ,val) ,@code)) `((let ,(add-type-declaration var declarations) ,val ,@code))))))))))))) (defun let-can-use-serial-binding? (bindings) ;; If the code is using LET (i.e., parallel binding) and none of the bindings ;; depend on the result of a previous binding, then we can convert it to use ;; serial binding. This makes the resulting Dylan code look a lot nicer. ;; This suggests that Dick Waters is right in using let* for the default. (let ((vars-so-far nil)) (loop for binding in bindings as var = (binding-var binding) as val = (binding-val binding) do (if (some #'(lambda (v) (find-anywhere v val)) vars-so-far) (RETURN-FROM let-can-use-serial-binding? nil) (push var vars-so-far)))) t) (defun cvt-flet (exp) (safe-destructuring-bind (bindings . body) (args exp) ;; Convert (f (args) body) to (f (lambda (args) body)); then use let (flet ((fix-fn (b) `(,(binding-var b) (lambda ,@(rest/ b))))) (cvt-exp `(let* ,(mapcar #'fix-fn bindings) ,@body))))) (defun cvt-labels (exp) (safe-destructuring-bind (bindings . body) (args exp) `(local (:list-bare ,@(mapcar #'(lambda (b) (move-comment b `(:local-method ,(first/ b) ,@(cvt-parms-and-body (rest/ b) (first/ b))))) (strip bindings))) ,@(cvt-body body)))) (defun cvt-symbol-macrolet (exp) ;; This is important, because with-slots macroexpands into symbol-macrolet. ;; Unfortunately, symbol-macrolet is a special form, not a macro, so we have ;; to expand it ourselves. We do a half-way job ??, converting ALL symbols, ;; even ones that are not in an evaluation context. So you lose on things ;; like (symbol-macrolet ((x 1)) (list 'x (let ((x 2)) x))), but do fine on ;; e.g. (symbol-macrolet ((x (slot-value y 'x))) (setf x (* x 2))) (safe-destructuring-bind (bindings . body) (args exp) (maybe-begin (cvt-exps (nsublis (mapcar #'(lambda (b) (cons (first/ b) (second/ b))) bindings) body))))) (defun cvt-if (exp) (destructuring-bind (pred conseq &optional (else nil else?)) (args exp) (let ((then (cvt-exp conseq))) `(if ,(cvt-exp pred) ,@(if (starts-with then 'begin) (args then) (list then)) ,@(cvt-else else else?))))) (defun cvt-else (else else?) ;; ELSE is a LISP expression. Return a list of (:else[if] ...) clauses (cond ((null else?) nil) ((starts-with else 'if) (destructuring-bind (p then &optional (else2 nil else2?)) (args else) `((:elseif ,(cvt-exp p) ,(cvt-exp then)) ,@(cvt-else else2 else2?)))) (t (let ((dylan-else (cvt-exp else))) (if (starts-with dylan-else 'begin) `((:else ,@(args dylan-else))) `((:else ,dylan-else))))))) (defun cvt-cond (exp) ;; Convert cond to either IF or CASE. Worry about (cond ((test))). (cond ((null (args exp)) '|\#f|) ((some #'length=1 (mapcar #'strip (butlast (args exp)))) ; E.g. ((test)) `(begin (let _that |\#f| ,(cvt-cond `(cond ,@(mapcar #'(lambda (c) (if (length=1 (strip c)) `((setq _that ,(first/ c)) _that) c)) (args exp))))))) ((eq (get-option :cond-as) 'if) `(if ,(cvt-exp (first/ (first/ (args exp)))) ,@(cvt-exps (rest/ (first/ (args exp)))) ,@(mapcar #'(lambda (clause) (move-comment clause `(:elseif ,@(cvt-exps clause)))) (butlast (rest/ (args exp)))) ,(let ((final (last1 (rest/ (args exp))))) (move-comment final (cond ((length=1 final) `(:else ,(cvt-exp (first/ final)))) ((eq t (first/ final)) `(:else ,@(cvt-exps (rest/ final)))) (t `(:elseif ,@(cvt-exps final)))))))) (t `(case ,@(mapcar #'(lambda (clause) `(:branch ,@(cvt-exps clause))) (args exp)))))) (defmacro must-be-call (result) `(if (call? exp) ,result (second/ exp))) (defun kwote (x) (list 'quote x)) (defun cvt-case (exp) (cvt-ecase (if (member (first/ (first/ (last exp))) '(t otherwise)) exp (append exp '((otherwise nil)))))) (defun cvt-ecase (exp) (must-be-call (safe-destructuring-bind (keyform . clauses) (args exp) `(select ,keyform ,@(mapcar 'cvt-case-clause clauses))))) (defun cvt-case-clause (clause) `(:branch ,(cond ((member (move-comment clause (first/ clause)) '(otherwise t)) 'otherwise) ((atom (first/ clause)) (kwote (first/ clause))) (t (cons :list (mapcar #'kwote (first/ clause))))) ,@(cvt-body (rest/ clause)))) (defun cvt-typecase (exp) (must-be-call (destructuring-bind (x . clauses) (args exp) `(select (:for-clause ,(cvt-exp x) by instance?) ,@(mapcar #'(lambda (c) (move-comment c `(:branch ,(cvt-exp (first/ c)) ,@(cvt-exps (rest/ c))))) clauses))))) (defun cvt-return-from (exp) (let ((result (cvt-exp (third exp)))) `(,(mksymbol 'return-from- (second/ exp)) ,@(if (starts-with result 'values) (args result) (list result))))) (defun handle-returns (body &optional name) ;; Wrap a BLOCK around body if it uses a return or return-from-name. ;; Returns a list of forms (let ((return-from-name (mksymbol 'return-from- name))) (if (or (find-return return-from-name body 'return)) `((block ,(if name return-from-name 'return) ,@body)) body))) (defun handle-returns1 (exp &optional name) ;; Like handle-returns, but for a single expression, not a list of them (first/ (handle-returns (list exp) name))) (defun handle-return-froms (body name) ;; Wrap a BLOCK around body if it uses a (return-from name) ;; Returns a list of forms (let ((return-from-name (mksymbol 'return-from- name))) (if (find-return return-from-name body) `((block ,(if name return-from-name 'return) ,@body)) body))) (defun find-return (name body &optional name2) (setq body (strip body)) (flet ((match (x) (or (eq x name) (and name2 (eq x name2))))) (cond ((atom body) nil) ((match (first/ body)) t) ((and (starts-with body 'block) (match (second/ body))) nil) (t (or (find-return name (first/ body) name2) (find-return name (rest/ body) name2)))))) (defun cvt-do (exp) ;; Need to fix this for do* (safe-destructuring-bind (vars endtest-and-values . body) (args exp) (setq vars (if (eq vars '|()|) '() (mapcar #'mklist vars))) (if (eq endtest-and-values '|()|) (setf endtest-and-values '())) (let* ((endtest (cvt-exp (move-comment endtest-and-values (first/ endtest-and-values)))) (values (cvt-exps (rest/ endtest-and-values))) (bindings (mapcar #'(lambda (binding) (move-comment binding (first/ binding)) (let* ((var (pop binding)) (init (pop binding)) (then (if binding (pop binding) init))) `(:for-clause ,var = ,init then ,then))) vars))) (handle-returns1 `(for (:list-bare ,@bindings ,@(when endtest-and-values `((:for-clause :until ,endtest)))) ,@(cvt-body body) ,@(when values (if (= (length values) 1) `((:finally ,@values)) `((:finally (values ,@values)))))))))) (defun cvt-dolist (exp) (destructuring-bind ((name list &optional result) . body) (args exp) (handle-returns1 `(for (:for-clause ,name in ,(cvt-exp list)) ,@(cvt-body body) ,@(ifd result `((:finally ,(cvt-exp result)))))))) (defun cvt-dotimes (exp) (destructuring-bind ((name count &optional result) . body) (args exp) (handle-returns1 `(for (:for-clause ,name from 0 below ,(cvt-exp count)) ,@(cvt-body body) ,@(ifd result `((:finally ,(cvt-exp result)))))))) (defun cvt-tagbody (exp) ;; Make these a set of mutually recursive local methods. ;; This way (GO label) translates as (label). ;; I.e. (tagbody ... A *** B ---) translates as ;; (labels ((A () *** (B)) (B () ---)) ... (A)) (if (notany #'symbolp (args exp)) (maybe-begin (cvt-exps (args exp))) (let ((args (args exp))) (flet ((chunk () ; eat a chunk up to next label (let ((p (position-if #'symbolp args))) (if p (prog1 (nconc (subseq args 0 p) (list (list (mksymbol 'go- (elt args p))))) (setf args (subseq args p))) (prog1 args (setf args nil)))))) (let ((fns nil) (body (chunk))) (loop while args do (push `(,(mksymbol 'go- (pop args)) () ,@(chunk)) fns)) (cvt-exp `(labels ,fns ,@body))))))) (defun cvt-multiple-value-call (exp) (cvt-exp `(apply ,(first/ (args exp)) (nconc ,@(loop for arg in (rest/ (args exp)) collect `(multiple-value-list ,arg)))))) (defun cvt-multiple-value-bind (exp) (safe-destructuring-bind (vars form . body) (args exp) `(let ,(cvt-parms vars :body body) ,(cvt-exp form) ,@(cvt-body body)))) (defun cvt-multiple-value-setq (exp) ;; I can't believe "(a, b, c) := values(1, 2, 3)" isn't in the language! (let* ((temps (loop for var in (second/ exp) collect (mksymbol '_ var))) (sets (loop for var in (second/ exp) for temp in temps collect `(:= ,var ,temp)))) `(begin (let (:args |\#rest| ,@temps) ,(cvt-exp (third exp)) ,@sets)))) (defun cvt-tag (tag) (cond ((starts-with tag 'quote) (second/ tag)) ((constantp tag) tag) (t (cvt-erroneous tag (cvt-exp tag) "Can't convert a run-time catch tag.")))) ;;;; CLtL2 CH 8: MACROS (defun cvt-macro (exp) ;; Macroexpand and convert ;; We use this for lots of things: rotatef, psetf, ... ;; If there is no macro, (let ((expansion (safe-macroexpand exp))) (cond ((eq expansion exp) (mapcar #'cvt-exp (strip exp))) (t (cvt-exp expansion))))) (defun safe-macroexpand (exp) (macroexpand (to-normal-lisp exp))) (defun to-normal-lisp (exp) "Convert back to normal lisp: eliminate comments and |()|." (cond ((comment? exp) (to-normal-lisp (com-code exp))) ((eq exp '|()|) '()) ((consp exp) (recons (to-normal-lisp (car exp)) (to-normal-lisp (cdr exp)) exp)) ((stringp exp) exp) ((vectorp exp) (coerce (map-into exp #'to-normal-lisp exp) 'vector)) (t exp))) (defun recons (x y x-y) "Cons x and y, but reuse x-y if it would be the same." (if (and (eql x (car x-y)) (eql y (cdr x-y))) x-y (cons x y))) ;;;; CLtL2 CH 11: PACKAGES (defun cvt-in-package (exp) (when (get-option :obey-in-package) (eval exp) (format nil "~(~A~)" exp))) (defun cvt-export (exp) (must-be-call (safe-destructuring-bind (symbols &opt package) (rest/ exp) (if (and (constantp (strip symbols)) (constantp package)) `(define-module ,(or (eval package) (intern (package-name *package*))) (:clause export ,@(mklist (eval symbols)))) (cvt-erroneous exp exp "Can't handle dynamic EXPORT"))))) (defun cvt-defpackage (exp) (must-be-call (safe-destructuring-bind (name . options) (rest/ exp) `(define-module ,name ,@(loop for option in options when (starts-with option ':use) collect `(:clause use ,@(rest/ option)) when (starts-with option ':export) collect `(:clause export ,@(rest/ option))))))) ;;;; CLtL2 CH 12: NUMBERS (defun cvt-to-binary-compares (exp) "Convert (< a b c d) to (& (& (< a b) (< b c)) (< c d))." (if (not (call? exp)) (if (get-option :only-binary-arithmetic-ops) (second/ exp) ; I.e., #'< => < `(cl-reduce-compares ,(second/ exp))) (let ((op (op exp)) (args (cvt-exps (args exp)))) (case (length args) ((0 1) '|\#t|) ((2) (cons op args)) (otherwise ;; Need to bind non-atomic elements of args (let ((bindings nil)) (flet ((maybe-bind (x) (cond ((consp (strip x)) (let ((var (gensym))) (push (list var x) bindings) var)) (t x)))) (let* ((args `(,(first/ args) ,@(mapcar #'maybe-bind (butlast (rest/ args))) ,(first/ (last args)))) (result (list (op exp) (first/ args) (second/ args)))) (pop args) ; Get rid of first/ arg (loop while (length>1 args) do (setf result `(& ,result ,(list (op exp) (first/ args) (second/ args)))) (pop args)) (wrap-bindings bindings result))))))))) (defun wrap-bindings (bindings code) ;; Wrap code with LETs for the (var val) pairs in bindings. (loop for (var val) in bindings do (setf code `(let ,var ,val ,code))) (if bindings `(begin ,code) code)) (defun cvt-division (exp) ;; Translate, e.g., (floor m &optional n) to (floor/ m n) or (floor m) (case (length (args exp)) (2 `(,(mksymbol (op exp) '/) ,@(cvt-exps (args exp)))) (t `(,(op exp) ,@(cvt-exps (args exp)))))) ;;;; CLtL2 CH 14: SEQUENCES (defun cvt-reduce (exp) (destructuring-bind (f s . keys) (args exp) (let ((function (mkpred keys (cvt-fn f))) (sequence (mkseq (cvt-exp s) keys))) (if (member :initial-value keys) `(reduce ,function ,(getf keys :initial-value) ,sequence) `(reduce1 ,function ,sequence))))) (defun cvt-if-not (exp) ;; Invert the test in a sequence-function-IF-NOT (if (call? exp) (let ((name (string (first/ exp))) (pos (case (op exp) ((subst-if-not nsubst-if-not substitutue-if-not nsubstitute-if-not) 2) (otherwise 1)))) (setf exp (copy-list exp)) (setf (elt exp pos) `(complement ,(elt exp pos))) (setf (first exp) (mksymbol (subseq name 0 (- (length name) 7)))) (cvt-exp exp)) `(method (:args x y |\#rest| r) (apply ,@(cvt-if-not `(,(second/ exp) x y)) r)))) (defun cvt-keys (keys) ;; Leave the key/value pair alone, except ;; replace :test-not f with :test (complement f) (loop for sublist on keys by 'cddr do (when (and (eq (first/ sublist) ':test-not) (length>1 sublist)) (setf (first sublist) ':test) (setf (second sublist) `(complement ,(second/ sublist))))) keys) (defun cl? (exp dylan &optional bad-keys (cl-fn-name (mksymbol 'cl- (op dylan)))) ;; Return DYLAN unless: ;; (1) If exp is of the form #'f, then use cl-fn-name ;; (2) If one of the bad-keys is used, use cl-fn-name applied to args (cond ((not (call? exp)) cl-fn-name) ((or (dotted? exp) (intersection bad-keys (args exp))) (cons cl-fn-name (cvt-exps (args exp)))) (t dylan))) (defun mktest (keys &optional pred binary?) ;; Return `:test test' or (), given a list of Lisp keyword/value pairs, ;; which may contain :test and :key (:test-not is already converted). ;; If binary? is true, apply TEST to key of two args ;; If PRED is given, there can be no :test, but maybe a key. ;; First, handle case where KEYS is not a list (when (listp keys) (let* ((test (or pred (getf keys :test '==))) (key (getf keys :key)) (code (cvt-exp (cond ((and binary? key) `(lambda (x y) (funcall ,test (funcall ,key x) (funcall ,key y)))) (binary? test) (key `(lambda (x y) (funcall ,test x (funcall ,key y)))) (t test))))) (if (eq code '==) nil `(:test ,code))))) (defun mkpred (keys pred) ;; Make a binary comparison predicate, possibly using :key argument from keys ;; pred is already converted to Dylan; keys are not. (when (listp keys) (let ((key (getf keys :key))) (if key `(compose ,pred ,(cvt-fn key)) pred)))) (defun mkseq (sequence keys &optional (start :start) (end :end)) ;; Take a sequence (in Dylan) and a list of key/values (in Lisp) ;; and extract :start and :end if provided. (when (listp keys) (let ((starts (if (getf keys start) `(:start ,(cvt-exp (getf keys start))))) (ends (if (getf keys end) `(:end ,(cvt-exp (getf keys end)))))) (if (or starts ends) `(copy-subsequence ,sequence ,@starts ,@ends) sequence)))) (defun mkcount (keys) ;; If there is a :count keyword, get it. (when (listp keys) (if (getf keys :count) `(:count ,(cvt-exp (getf keys :count)))))) (defun mkeof (eofs) ;; Eofs is of the form (&optional eof-errorp eof-value recursive-p) ;; If eof-errorp is non-nil, we return nothing; otherwise ;; we extract eof-value (if (and (consp eofs) (not (null (first/ eofs)))) `(:on-end-of-stream ,(cvt-exp (second/ eofs))) nil)) ;;;; CLtL2 CH 16: HASH TABLES (defun cvt-make-hash-table (exp) (destructuring-bind (&key test size rehash-size rehash-threshold) (args exp) (declare (ignore rehash-size rehash-threshold)) (let ((type (cond ((member test '('equal '#'equal) :test #'equal) ') ((member test '('eq 'eql '#'eq '#'eql) :test #'equal) ') (t ')))) `(make ,type ,@(ifd test `(:test ,(cvt-fn test))) ,@(ifd size `(:size ,(cvt-exp size))))))) ;;;; CLtL2 CH 17: ARRAYS (defun cvt-make-array (exp) (safe-destructuring-bind (dimensions . keys) (args exp) (let* ((element-type (cvt-type-exp (getf keys :element-type))) (type (cond ((or (integerp dimensions) (starts-with dimensions 'length) (and (starts-with dimensions 'quote) (length=1 (second/ dimensions)))) (cond ((eq element-type ') ') ((getf keys :adjustable) ') (t '))) (t '))) (code `(make ,type ,@(if (eql type ') `(:dimensions ,(cvt-exp dimensions)) `(:size ,(cvt-exp dimensions)))))) (when (getf keys :fill) (setf code `(fill! ,code ,(cvt-exp (getf keys :fill))))) (when (getf keys :fill-pointer) (setf code `(begin (let _vector ,code (:= (size _vector) ,(cvt-exp (getf keys :fill-pointer))) _vector)))) code))) ;;;; CLtL2 CH 19: STRUCTURES (defun cvt-defstruct (exp) ;; See also cvt-defclass (safe-destructuring-bind (name-and-options . slots) (args exp) (let* ((name (first-atom (strip name-and-options))) (options (if (listp (strip name-and-options)) (rest/ (strip name-and-options)))) (supers (mapcar #'cvt-type (rest/ (assoc/ :include options)))) (comment nil)) (when (eq slots '|()|) (setq slots '())) (when (stringp (first/ slots)) (setq comment (pop slots))) (let ((code `(define-class ,(cvt-type name) (:list ,@(or supers '())) ,@(cvt-defstruct-slots slots name options)))) (if comment (make-com :comment comment :code code) code))))) (defun cvt-defstruct-slots (slots struct-name options) ;; Converts a set of defstruct slot definitions to a set of Dylan slots ;; See also cvt-defclass-slots (let* ((option (assoc/ :conc-name options)) (conc-name (if (consp (strip option)) (second/ option) (mksymbol struct-name '-)))) (flet ((convert-defstruct-slot (slot) (move-comment slot (let* ((name (if (listp slot) (first/ slot) slot)) (new-name (mksymbol conc-name name)) (exp new-name) (type (if (listp slot) (getf (cddr slot) :type)))) (when type (setf exp `(|::| ,exp ,(cvt-type type)))) (when (and (consp slot) (>= (length slot) 2)) (setf exp `(= ,exp ,(cvt-exp (second/ slot))))) (push new-name *dotted-functions*) `(:slot ,exp :init-keyword ,(intern (symbol-name new-name) :keyword)))))) (mapcar #'convert-defstruct-slot (strip-nil slots))))) ;;;; CLtL2 CH 22: INPUT/OUTPUT (defun cvt-format (exp) (must-be-call (destructuring-bind (stream string . args) (args exp) (let ((str (cvt-format-string string))) (cond ((and (stringp str) (eq (strip stream) 't)) `(format-out ,str ,@(cvt-exps args))) ((stringp str) `(,(op exp) ,(cvt-exp stream) ,str ,@(cvt-exps args))) (t `(,str ,(cvt-exp stream) ,@(cvt-exps args)))))))) (defun cvt-format-string (control) ;; Handle ~X => %X; ~% => newline; % => %% ;; Notice that #\newline and #\tab are printed as \n and \t (by dpp-string). (if (not (stringp control)) (cvt-exp control) (let* ((length (length control)) (i 0) (unhandled nil) (result (make-array length :element-type 'string-char :adjustable t :fill-pointer 0))) (flet ((emit (ch) (vector-push-extend ch result)) (consume () (if (< i length) (prog1 (aref control i) (incf i))))) (loop for ch = (consume) while ch do (case ch (#\% (emit #\%) (emit #\%)) (#\~ (let ((ch2 (consume))) (if (null ch2) (emit #\~) (case (char-upcase ch2) ((#\D #\B #\O #\X #\C) (emit #\%) (emit ch2)) ((#\A) (emit #\%) (emit #\S)) ((#\S) (emit #\%) (emit #\=)) ((#\~) (emit #\~)) ((#\& #\%) (emit #\newline)) (otherwise (pushnew ch2 unhandled)))))) (otherwise (emit ch))))) (cond ((null unhandled) result) ((get-option :macroexpand-hard-format-strings) (cvt-exp (macroexpand `(formatter ,control)))) (t (cvt-erroneous control result "Unhandled format characters: ~{~~~C~^,~}" (nreverse unhandled))))))) (defun extract-stream (keys &optional (default '*standard-output*)) "Return the STREAM arg from the list of Dylan key/vals, followed by the keys." ;; I.e. (extract-stream '(:pretty t :stream s)) => (s :pretty t) (cons (or (getf (mapcar #'strip keys) :stream) default) (let ((n (position :stream keys :key #'strip))) (if n (append (subseq keys 0 n) (subseq keys (+ n 2))) keys)))) ;;;; CLtL2 CH 23: FILE SYSTEM INTERFACE (defun cvt-with-open-file (exp) (must-be-call (destructuring-bind ((var file . options) . body) (args exp) `(with-open-file (= ,(cvt-exp var) (:args ,(cvt-exp file) ,@(cvt-exps options))) ,@(cvt-body body))))) ;;;; CLtL2 CH 28: COMMON LISP OBJECT SYSTEM (defun cvt-compute-applicable-methods (exp) (converting-bind (f . arg*) (args exp) `(begin (let (:list _a _b) (apply sorted-applicable-methods ,f . ,arg*) (concatenate _a _b))))) (defun cvt-defclass (exp) (safe-destructuring-bind (name supers slots . options) (args exp) (when (null/ supers) (setf supers (ecase (op exp) (defclass '(t)) (define-condition '(condition))))) `(define-class ,(cvt-type name) (:list ,@(mapcar #'cvt-type (or supers '(t)))) ,@(cvt-defclass-slots slots options)))) (defun cvt-defclass-slots (slots options) ;; Converts a set of CLOS slot definitions to a set of Dylan slot definitions (let ((default-initargs (rest/ (assoc/ :default-initargs options)))) (flet ((convert-slot (slot) (move-comment slot (if (symbolp slot) slot (destructuring-bind (name &key reader writer accessor type allocation initarg documentation (initform nil initform-p) (initvalue nil initvalue-p)) slot (let ((getter (or reader accessor name)) ; norvig change to swm's code (setter writer) (default-initarg (getf default-initargs initarg))) (when default-initarg (setq initvalue default-initarg)) (when type (setf getter `(|::| ,getter ,(cvt-type type)))) (when initform-p (setf getter `(= ,getter ,(cvt-exp initform)))) (push getter *dotted-functions*) (add-comment documentation `(:slot ,getter ,@(and setter `(:setter ,setter)) ,@(and initarg `(:init-keyword ,initarg)) ,@(and initvalue-p `(:init-value ,initvalue)) ,@(and allocation `(:allocation ,allocation)))))))))) (mapcar #'convert-slot (strip-nil slots))))) (defun cvt-defgeneric (exp) (safe-destructuring-bind (name parms . options) (args exp) ;; Silently ignores options other than :documentation (add-comment (second/ (find :documentation options :key #'first-atom)) `(define-generic ,name ,(cvt-parms parms))))) (defun cvt-defmethod (exp) "Just like cvt-defun, but handle make-instance, initialize-instance." ;; Just give up on method qualifiers. (safe-destructuring-bind (name parms . body) (args exp) (cond ((typep parms '(and symbol (not null))) (cvt-erroneous exp (cvt-defmethod `(defmethod ,name ,@(rest/ body))) "Defmethod qualifier ~A ignored." name)) ((eq name 'make-instance) (cvt-defmethod `(defmethod make ,parms ,@body))) ((eq name 'initialize-instance) (cvt-defmethod `(defmethod initialize ,parms ,@body))) (t (cvt-defun exp))))) (defun cvt-with-slots (exp) ;; Just expand macro, except if the argument is atomic, avoid local var. (if (atom (strip (third exp))) (cvt-exp `(symbol-macrolet ,(mapcar #'(lambda (v) `(,v (slot-value ,(strip (third exp)) ',v))) (strip (second/ exp))) ,@(cdddr exp))) (cvt-macro exp))) ;;;; CLtL2 CH 29: CONDITIONS (defun cvt-condition-function (exp) ;; Convert a function like error or signal. There are three cases for args: ;; (1) condition-object &rest args ;; (2) format-string &rest args ;; (3) condition-class-name &rest args ;; Dylan supports the first two. We don't always handle the third. (if (not (call? exp)) (second/ exp) ; Close, but doesn't handle (3). ?? (let* ((arg1 (first/ (args exp))) (class-name? (and (starts-with arg1 'quote) (symbolp (second/ arg1)))) (arg1-val (cond ((stringp arg1) (cvt-format-string arg1)) (class-name? (cvt-type arg1)) (t (cvt-exp arg1))))) `(,(op exp) ,arg1-val ,@(cvt-exps (rest/ (args exp)))))))