;;; -*- Mode: Lisp; Syntax: Common-Lisp; -*- Author: Peter Norvig ;;; File: convert.lisp; Date: 28-Aug-95 (in-package :cl-user) ;;;; LTD: CONVERT FROM COMMON LISP TO DYLAN - TOP-LEVEL FUNCTION (defun ltd-files (files &key (width 79) (output (make-pathname :type "dylan"))) "Convert a list of Common Lisp files to Dylan." (let ((*print-right-margin* width) (*package* *package*)) (dolist (file (expand-files files)) (with-open-file (out (merge-pathnames output file) :direction :output :if-exists :supersede) (with-open-file (in file :direction :input) (format t "Converting ~A~%" file) (restart-case (loop until (eq *eof* (ltd-exp in out))) (nil () :report (lambda (s) (format s "Skip file ~A" file))))))) (report-unimplemented-functions))) (defun ltd-exp (in out) "Read a Lisp expression from stream IN and write Dylan to stream OUT." (restart-case (let ((exp (ltd-read in))) (unless (eq exp *eof*) (dpp-exp (cvt-exp exp) :stream out) (format out ";~%~%") (clrhash *file-position-table*)) exp) (nil () :report "Skip to the next expression in this file." '|Input expression skipped due to translation error.|))) ;;;; MACROS FOR DEFINING TRANSLATION TABLES ;;; We support three tables, keyed on Lisp symbols, whose values are ;;; the equivalents in Dylan (or a function to compute the equivalent). (defun get-cvt-constant (cl) (when (symbolp cl) (get cl 'cvt-constant))) (defun get-cvt-fn (cl) (when (symbolp cl) (get cl 'cvt-fn))) (defun get-cvt-type (cl) (when (symbolp cl) (get cl 'cvt-type))) (defmacro ltd-constant (cl dylan) ;; Define a translation between a Lisp and Dylan constant (assert (symbolp cl)) `(setf (get ',cl 'cvt-constant) ,(if (symbolp dylan) `',dylan `#'(lambda () ,dylan)))) (defmacro ltd-type (cl dylan) ;; Define a translation from a Lisp to a Dylan type (assert (symbolp cl)) `(setf (get ',cl 'cvt-type) ',dylan)) (defmacro ltd-fn (cl dylan) "Store, under the function symbol in Lisp, a function to convert to Dylan." ;; The function will be passed EXP, the complete expression to be converted. ;; This is either of the form (f x y z) or #'f (if (symbolp cl) (setf cl `(,cl . args))) ; Coerce to canonical form `(progn (setf (get ',(op cl) 'cvt-fn) #'(lambda (exp) ,(cond ((symbolp dylan) `(if (call? exp) (cons ',dylan (cvt-exps (args exp))) ',dylan)) ((starts-with dylan 'function) `(,(second/ dylan) exp)) ((starts-with dylan 'cl?) `(encapsulate-let (converting-bind ,(args cl) (args exp) ,dylan))) (t `(cond ((call? exp) (encapsulate-let (converting-bind ,(args cl) (args exp) ,@(when (find-anywhere 'ignore (args cl)) '((declare (ignore ignore)))) ,dylan))) ,@(if (or (dotted? cl) (find-anywhere '&opt cl)) `((t (cvt-erroneous ; ??? could do better exp (second/ exp) "Can't convert complex function ~A." (second/ exp)))) `((t (let ((dylan-args '(:args ,@(args cl))) (dylan-body (cvt-exp ',cl))) (list 'method dylan-args dylan-body)))))))))) ',(first-atom cl))) (defmacro ltd-unimplemented-functions (&rest fns) "These functions are not yet implemented." `(map nil #'(lambda (fn) (case (get-cvt-fn fn) ((nil) (setf (get fn 'cvt-fn) 'not-yet-implemented)) ((not-yet-implemented)) (t (warn "~A is already implemented!" fn)))) ',fns)) (defmacro ltd-unimplemented-types (&rest types) "These types are not yet implemented." `(map nil #'(lambda (type) (setf (get type 'cvt-type) (add-comment (format nil "Type ~A unimplemented" type) '))) ',types)) (defvar *unimplemented* (make-hash-table :test #'eq)) (defun incf-unimplemented (fn) (incf (gethash fn *unimplemented* 0))) (defun not-yet-implemented (exp) ;; Warn, then just convert each arg and make a function call (let ((fn (if (call? exp) (op exp) (second/ exp)))) (incf-unimplemented fn) (if (call? exp) `(,(cvt-erroneous exp fn "Function ~A not yet implemented." fn) ,@(cvt-exps (args exp))) (cvt-erroneous exp fn "Function ~A not yet implemented." fn)))) (defun report-unimplemented-functions () (let ((result nil)) (maphash #'(lambda (k v) (push (list v k) result)) *unimplemented*) (format t "~%Counts of unimplmented functions:~%") (loop for (n fn) in (sort result #'> :key #'first) do (format t "~4D ~A~%" n fn)) (clrhash *unimplemented*))) ;;;; CONVERTING BASIC EXPRESSIONS (defun cvt-exp (exp) "Convert a CL expression to Dylan." (cond ((and (symbolp exp) (not (keywordp exp)) (or (get-cvt-constant exp) (constantp exp))) (cvt-constant exp)) ((comment? exp) (setf (com-code exp) (cvt-exp (com-code exp))) exp) ((atom exp) exp) ((get-cvt-fn (op exp)) (funcall (get-cvt-fn (op exp)) exp)) ((and (symbolp (op exp)) (macro-function (op exp))) (cvt-macro exp)) (t `(,(cvt-fn `(function ,(op exp))) ,@(cvt-exps (args exp)))))) (defun cvt-exps (exps) "Like (mapcar #'cvt-exp exps), but handles dotted lists." (if (atom exps) exps (cons (cvt-exp (first exps)) (cvt-exps (rest/ exps))))) (defun cvt-constant (var) "Convert a constant's name from CL to Dylan." ;; Use the entries from the cvt-constant table. ;; If it is of the form *xxx* or +xxx+, strip the ** or ++. ;; Otherwise, just tack a $ at the beginning. (let ((str (symbol-name var)) (con (get-cvt-constant var))) (cond ((and con (symbolp con)) con) ((and con (functionp con)) (funcall con)) ((starts-with str #\$) var) ((bracketed-with str #\*) (mksymbol '$ (subseq str 1 (- (length str))))) ((bracketed-with str #\+) (mksymbol '$ (subseq str 1 (- (length str))))) (t (mksymbol '$ var))))) (defun extract-declarations (body) "Return three values: doc string, list of (declare)s, body." (let ((doc nil) (declarations nil)) (loop (cond ((starts-with (first/ body) 'declare) (push (pop body) declarations)) ((and (null doc) (length>1 body) (stringp (first/ body))) (setf doc (pop body))) (t (RETURN)))) (values doc (nreverse declarations) body))) (defun extract-just-declarations (body) (multiple-value-bind (doc declares bod) (extract-declarations body) (declare (ignore doc bod)) declares)) (defun extract-values-declaration (declarations) "Some programs use (declare (values type1 type2)). Get it if there." (dolist (declaration declarations) (dolist (decl (rest/ declaration)) (when (starts-with decl 'values) (return-from extract-values-declaration `(:return ,@(rest/ decl))))))) (defun cvt-body (body &key (name nil)) "Convert a body. Handle doc, declares, return-from. Returns a list of forms." (labels ((strip-begin (exp) (if (and (starts-with exp 'begin) (= (length (strip exp)) 2)) (strip-begin (second/ exp)) exp))) (multiple-value-bind (doc decls body) (extract-declarations body) (declare (ignore decls)) (let ((forms (handle-return-froms (mapcar #'(lambda (exp) (strip-begin (cvt-exp exp))) (or body '(|\#f|))) name))) (if doc (cons (add-comment doc (first/ forms)) (rest/ forms)) forms))))) (defun encapsulate-let (exp) ;; In Dylan, a LET can appear only at the top level of a body. ;; So wrap a LET in a BEGIN, and strip the BEGIN from wihin cvt-body (if (starts-with exp 'let) `(begin ,exp) exp)) ;;;; MISC (defun add-type-declaration (variable declarations) (let ((decl (get-type-declaration variable declarations))) (if decl `(|::| ,variable ,decl) variable))) (defun get-type-declaration (variable declarations) "If there is a type declaration for variable, get it." (dolist (declaration declarations) (dolist (decl (rest/ declaration)) ;; Handle (type fixnum ... variable ...) (when (and (starts-with decl 'type) (member variable (cddr decl))) (RETURN-FROM get-type-declaration (cvt-type (second/ decl)))) ;; Handle (fixnum ... variable ...)) (when (and (consp decl) (get-cvt-type (first/ decl)) (member variable (rest/ decl))) (RETURN-FROM get-type-declaration (cvt-type (first/ decl))))))) (defun cvt-erroneous (exp replacement &rest format-args) "Can't convert exp; just return replacement, but print warnings." (let ((*print-length* 3) (*print-level* 2)) (safe-destructuring-bind (start . end) (or (gethash exp *file-position-table*) '(? . ?)) (apply #'warn format-args) (warn " at ~D to ~D in ~A.~%" start end exp)) (if (get-option :errors-inline) (add-comment (concatenate 'string "LTD: " (apply #'format nil format-args)) replacement) replacement))) (defun false? (x) ;; Is X a Lisp or Dylan expression for EMPTY LIST or FALSE? (member (strip x) '(nil |\#f| |()|))) (defun call? (exp) "Is this a function call, e.g. (f x), as opposed to #'f or 'f?" (and (consp (strip exp)) (not (member (first/ (strip exp)) '(quote function)))))