;#!/usr/bin/clisp -M ~sds/bin/clisp.mem -C
;;;
;;; Convert *.c to CLISP's ffi
;;;
;;; Copyright (C) 1999-2000 by Sam Steingold
;;; This is Free Software, covered by the GNU GPL (v2)
;;; See http://www.gnu.org/copyleft/gpl.html
;;;
;;; $Id: h2lisp.lisp,v 2.7 2001/11/02 22:31:15 sds Exp $
;;; $Source: /cvsroot/clocc/clocc/src/cllib/h2lisp.lisp,v $

(eval-when (compile load eval)
  (require :cllib-base (translate-logical-pathname "clocc:src;cllib;base"))
  ;; `text-stream'
  ;; (require :cllib-html (translate-logical-pathname "cllib:html"))
  )

(in-package :cllib)

(export '(h2lisp))

;;;
;;; C parsing
;;;

(defstruct c-dim "C dimension." dim)
(defstruct c-cmt "C comment." data)

(defun read-standalone-char (stream char) (declare (ignore stream)) char)

(defun read-c-object (stream char)
  (declare (stream stream) (character char))
  (ecase char
    (#\{ (read-delimited-list #\} stream t))
    (#\[ (make-c-dim :dim (car (read-delimited-list #\] stream t))))
    (#\#
     (let ((com (read stream t nil t)))
       (ecase com
         (|include|
          (let ((ec (ecase (peek-char t stream t nil t) (#\" #\") (#\< #\>))))
          (concatenate
           'string "#include "
           (loop :for cc :of-type character = (read-char stream t nil t)
                 :collect cc :until (char= cc ec))))))))
    ;;     (loop :for line :of-type simple-string =
    ;;           (concatenate 'string "#" (read-line stream))
    ;;           :then (read-line stream)
    ;;           :until (char= #\\ (schar line (1- (length line))))
    ;;           :collect line)
    (#\/
     (case (peek-char nil stream nil nil t)
       (#\*
        (read-char stream)
        (make-c-cmt :data
                    (concatenate
                     'string "/*"
                     (loop :for c1 = (read-char stream t nil t)
                           :and c2 = #\Null :then c1
                           :until (and (char= c2 #\*) (char= c1 #\/))
                           :collect c1)
                     "/")))
       (#\/ (make-c-cmt :data (concatenate 'string "/" (read-line stream))))
       (t #\/)))))

(defun make-c-readtable (&optional (rt (copy-readtable)))
  "Make the readtable for parsing C."
  (set-macro-character #\/ #'read-c-object nil rt)
  (set-macro-character #\| #'read-standalone-char nil rt)
  (set-macro-character #\# #'read-c-object nil rt)
  (set-syntax-from-char #\; #\a rt)
  (set-macro-character #\; #'read-standalone-char nil rt)
  (set-syntax-from-char #\# #\a rt)
  (set-macro-character #\# #'read-c-object nil rt)
  (set-syntax-from-char #\: #\a rt)
  (set-macro-character #\: #'read-standalone-char nil rt)
  (set-syntax-from-char #\, #\a rt)
  (set-macro-character #\, #'read-standalone-char nil rt)
  (set-macro-character #\* #'read-standalone-char nil rt)
  (set-macro-character #\{ #'read-c-object nil rt)
  (set-macro-character #\} (get-macro-character #\)) nil rt)
  (set-macro-character #\[ #'read-c-object nil rt)
  (set-macro-character #\] (get-macro-character #\)) nil rt)
  (setf (readtable-case rt) :preserve)
  rt)

(defparameter *c-readtable* (make-c-readtable) "The readtable for C parsing.")

;(defun uncomment-split (lst obj)
;  "Read object from TS, remove comments, split on OBJ."
;  (declare (list lst))
;  (nsplit-list (delete-if #'c-cmt-p lst) :obj obj))

(defun read-statement (ts)
  (declare (type text-stream ts))
  (loop :for zz = (read-next ts :skip #'c-cmt-p) :until (eql zz #\;)
        :collect zz))

(defparameter *c-types* '(int uint char) "Known C types.")
(defparameter *c-un-types* nil "UnKnown C types.")

(defun c-see-type (sym)
  (assert (and sym (not (string-equal (string sym) "void"))) ()
          "An attempt to define VOID or NIL: ~s" sym)
  (unless (member sym *c-types* :test #'eq)
    (pushnew sym *c-un-types* :test #'eq)))
(defun c-def-type (sym)
  ;(assert (not (member sym *c-types* :test #'eq)) () "redefining type ~s" sym)
  (push sym *c-types*)
  (setq *c-un-types* (delete sym *c-un-types* :test #'eq)))

(defun c-convert-decl (lst)
  "Convert declaration (type obj [dims]||(args)) to CLISP."
  (labels ((voidp (sy) (string-equal (string sy) "void"))
           (objp (ll) (or (symbolp ll)
                          (and (consp ll)
                               (or (eql (car ll) #\*)
                                   (null (cdr ll))))))
           (cc (ll)
             (format t " --> ~s~%" ll)
             (etypecase (car ll)
               (c-dim `(c-array ,(cc (cdr ll)) ,(c-dim-dim (car ll))))
               (cons (mapcar #'cc ll))
               (null nil)
               (c-cmt (cc (cdr ll)))
               (symbol
                (cond ((voidp (car ll))
                       (if (eql #\* (cadr ll))
                           'c-pointer nil))
                      (t (c-see-type (car ll))
                         (let* ((ptr (eql #\* (cadr ll))))
                           (list (c-convert-decl (if ptr (cddr ll) (cdr ll)))
                                 (if ptr `(c-ptr ,(car ll)) (car ll))))))))))
    (let* ((fu (find-if #'objp lst :from-end t)) (ta (member fu lst))
           (ld (ldiff lst ta))
           (tail (map-in (lambda (el) (nsplit-list el :obj #\,)) (cdr ta))))
      (typecase fu
        (symbol (unless (voidp fu) (list fu (cc (nconc tail ld)))))
        (cons `(,(car (last fu)) (c-function (:arguments ,(cc tail))
                                  (:return-type ,(cc ld)))))))))

(defun c-number (obj)
  (typecase obj
    (symbol (let* ((st (symbol-name obj)) (len (length st)))
              (cond ((string-beg-with "0x" st len)
                     (unintern obj)
                     (parse-integer st :radix 16 :start 2))
                    ((let ((ch (char st (1- len))))
                       (and (char-equal #\l ch)
                            (prog2 (setf (char st (1- len)) #\0)
                                (every #'digit-char-p st)
                              (setf (char st (1- len)) ch))))
                     (unintern obj)
                     (parse-integer st :end (1- len)))
                    (obj))))
    (t obj)))

(defun c-eval (lst)
  (cond ((null (cdr lst)) (c-number (car lst)))
        ((eq (second lst) '<<)
         (ash (c-number (first lst)) (c-number (third lst))))))

;;;
;;; H --> LISP
;;;

(defgeneric h2lisp (in out)
  (:documentation "Convert C header to lisp.
Return the number of forms processed.")
  (:method ((in string) (out t)) (h2lisp (pathname in) out))
  (:method ((in cons) (out t))
    (reduce #'+ in :key (lambda (in0) (h2lisp in0 out))))
  (:method ((in t) (out string)) (h2lisp in (pathname out)))
  (:method ((in t) (out pathname))
    (with-open-file (fout out :direction :output :if-exists :supersede
                          :if-does-not-exist :create)
      (h2lisp in fout)))
  (:method ((in pathname) (out t))
    (with-open-file (fin in :direction :input)
      (h2lisp fin out))))

(defmethod h2lisp ((in stream) (out stream))
  (format out "~%;;; reading from ~a~2%" in)
  (unless (eq out *standard-output*)
    (format t "~%;;; reading from ~a~2%" in))
  (loop :with *readtable* = *c-readtable* :and numf = 0
        ;; :initially (setq *c-un-types* nil)
        :for line :of-type simple-string =
        (string-trim
         +whitespace+
         (or (read-line in nil nil)
             (return (progn (format out "~%;;; ~:d form~:p in ~a~2%" numf in)
                            (unless (eq out *standard-output*)
                              (format t "~%;;; ~:d form~:p in ~a~2%" numf in))
                            (when *c-un-types*
                              (format t "~% *** Undefined types:~
~{~<~%~20t ~1,79:; ~a~>~^,~}~%" *c-un-types*))
                            numf))))
        :for len = (length line)
        :with depth = 0 ; #if nesting
        :with ts :of-type text-stream = (make-text-stream :sock in)
        :do                     ; process statement
        (cond ((zerop len) (terpri out))
              ((< len 3) (format out ";;; ~a~%" line))
              ((string-beg-with "/*" line len)
               ;; comment, assume start/end on own line
               (format out ";;; ~a~%" line)
               (loop :until (search "*/" line :test #'char=)
                     :do (setq line (read-line in))
                     (format out ";;; ~a~%" line)))
              ((string-beg-with-cs "#if" line len)
               (incf depth)
               (format out ";;; ~a~%" line))
              ((string-beg-with-cs "#endif" line len)
               (decf depth)
               (format out ";;; ~a~%" line))
              ((string-beg-with-cs "#define" line len) (incf numf)
               ;; only 1-line simple defines!
               (format out ";;; ~a~%" line)
               (multiple-value-bind (var pos)
                   (read-from-string line nil +eof+ :start 7)
                 (let ((val (c-number (read-from-string
                                       line nil +eof+ :start pos))))
                   (typecase val
                     (symbol
                      (format out "(define-symbol-macro ~s ~s)~%" var val))
                     ((or number string)
                      (format out "(defconstant ~s ~s)~%" var val))))))
              ((string-beg-with-cs "#include" line len) (incf numf)
               (format out "(c-lines \"~a~~%\")~%" line))
              ((string-beg-with-cs "typedef struct" line len) (incf numf)
               ;; (format out ";;; ~a~%" line)
               (multiple-value-bind (t0 t1)
                   (values-list (string-tokens line :start 15 :max 2))
                 (c-def-type t1)
                 (c-see-type t0)
                 (format out "(def-c-type ~s ~s)~%" t0 t1)))
              ((string-beg-with-cs "typedef union" line len) (incf numf)
               ;; (format out ";;; ~a~%" line)
               (multiple-value-bind (t0 t1)
                   (values-list (string-tokens line :start 14 :max 2))
                 (c-def-type t1)
                 (c-see-type t0)
                 (format out "(def-c-type ~s ~s)~%" t0 t1)))
              ((string-beg-with-cs "typedef enum" line len) (incf numf)
               (setf (ts-buff ts) line (ts-posn ts) 12)
               (let* ((rr (read-next ts :skip #'c-cmt-p))
                      (en (nsplit-list (delete-if #'c-cmt-p rr) :obj #\,))
                      (tt (read-next ts :skip #'c-cmt-p)))
                 (c-def-type tt)
                 (dolist (el en)
                   (when (stringp (car el))
                     (format out "(c-lines \"~a~%\")~%" (car el))
                     (setf (car el) (cadr el) (cdr el) (cddr el)))
                   (when (cdr el)
                     (assert (eq '= (cadr el)))
                     (setf (cadr el) (c-eval (cddr el)) (cddr el) nil)))
                 (map-in (lambda (el) (if (cdr el) el (car el))) en)
                 ;; (format out "~s~%" (cons 'def-c-enum (cons tt en)))
                 (format out "(def-c-enum ~s~{~%  ~s~})~%" tt en)))
              ((string-beg-with-cs "typedef" line len) (incf numf)
               ;; (format out ";;; ~a~%" line)
               (setf (ts-buff ts) line (ts-posn ts) 7)
               (let ((ll (read-statement ts)))
                 (format out "(def-c-type ~s)~%" ll)
                 (format out "~s~%" (cons 'def-c-type (c-convert-decl ll)))))
              ((string-beg-with-cs "struct" line len) (incf numf)
               (setf (ts-buff ts) line (ts-posn ts) 6)
               (let ((tt (read-next ts :skip #'c-cmt-p))
                     (ll (map-in #'c-convert-decl
                                 (nsplit-list (read-next ts :skip #'c-cmt-p)
                                              :obj #\;))))
                 (c-def-type tt)
                 (format out "(def-c-struct ~s~{~%  ~s~})~%" tt ll)))
              ((string-beg-with-cs "extern \"C\" {" line len)
               (format out ";;; ~a~%" line))
              ((incf numf)  ; function declaration
               (setf (ts-buff ts) line (ts-posn ts) 0)
               (let* ((ll (read-statement ts))
                      (args (mapcar #'c-convert-decl
                                    (nsplit-list (last ll) :obj #\,)))
                      (fun (c-convert-decl (nbutlast ll))))
                 (format out "(def-c-call-out ~s
  (:arguments~{~<~%~12t ~1,79:; ~s~>~})~%  (:return-type~{ ~s~}))~%"
                         (car fun) args (cdr fun)))))))

;(h2lisp *standard-input* *standard-output*)
;(LET ((*readtable* (copy-readtable)))
;  (eval (read-from-string "(read-next ts :skip #'c-cmt-p)")))

(provide :cllib-h2lisp)
;;; file h2lisp.lisp ends here
