(provide 'cload.scm)

;;; --------------------------------------------------------------------------------
;;; automatically link a C function into s7 (there are a bunch of examples below)
;;;     (c-define '(double j0 (double)) "m" "math.h")
;;; means link the name m:j0 to the math library function j0 passing a double arg and getting a double result (reals in s7)
;;;
;;; (c-define c-info prefix headers cflags ldflags)
;;;    prefix is some arbitrary prefix (it can be "") that you want prepended to various names.
;;;    headers is a list of headers (as strings) that the c-info relies on, (("math.h") for example).
;;;    cflags are any special C compiler flags that are needed ("-I." in particular).
;;;    ldflags is the similar case for the loader.
;;;    c-info is a list that describes the C entities that you want to tie into s7.
;;;       it can be either one list describing one entity, or a list of such lists.
;;;       Each description has the form: (return-type entity-name-in-C (argument-type...))
;;;       where each entry is a symbol, and C names are used throughout.  So, in the j0
;;;       example above, (double j0 (double)) says we want access to j0, it returns
;;;       a C double, and takes one argument, also a C double.  s7 tries to figure out 
;;;       what the corresponding s7 type is, but in tricky cases, you should tell it
;;;       by replacing the bare type name with a list: (C-type underlying-C-type).  For example,
;;;       the Snd function set_graph_style takes an (enum) argument of type graph_style_t.
;;;       This is actually an int, so we use (graph_style_t int) as the type:
;;;         (void set_graph_style ((graph_style_t int)))
;;;       If the C entity is a constant, then the descriptor list has just two entries,
;;;       the C-type and the entity name: (int F_OK) for example. The entity name can also be a list 
;;;       (an enum listing for example).
;;;       If the C type has a space ("struct tm*" for example), use (symbol "struct tm*") 
;;;       to construct the corresponding symbol.
;;;    The entity is placed in the current s7 environment under the name (string-append prefix ":" name)
;;;    where the ":" is omitted if the prefix is null.  So in the j0 example, we get in s7 the function m:j0.
;;;
;;; some examples:
;;;
;;;  (c-define '((double j0 (double)) 
;;;              (double j1 (double)) 
;;;              (double erf (double)) 
;;;              (double erfc (double))
;;;              (double lgamma (double)))
;;;             "m" "math.h")
;;; 
;;;
;;; (c-define '(char* getenv (char*)))
;;; (c-define '(int setenv (char* char* int)))
;;; (define get-environment-variable (let () (c-define '(char* getenv (char*))) getenv))
;;;
;;; (define file-exists? (let () (c-define '((int F_OK) (int access (char* int))) "" "unistd.h") (lambda (arg) (= (access arg F_OK) 0))))
;;; (define delete-file (let () (c-define '(int unlink (char*)) "" "unistd.h") (lambda (file) (= (unlink file) 0)))) ; 0=success, -1=failure
;;;
;;;
;;; these pick up Snd stuff:
;;;   (c-define '(char* version_info ()) "" "snd.h" "-I.")
;;;   (c-define '(mus_float_t mus_degrees_to_radians (mus_float_t)) "" "snd.h" "-I.")
;;;
;;;   (c-define '(snd_info* any_selected_sound ()) "" "snd.h" "-I.")
;;;   (c-define '(void select_channel (snd_info* int)) "" "snd.h" "-I.")
;;;   (c-define '(((graph_style_t int) (GRAPH_LINES GRAPH_DOTS GRAPH_FILLED GRAPH_DOTS_AND_LINES GRAPH_LOLLIPOPS)) 
;;;               (void set_graph_style ((graph_style_t int)))) 
;;;             "" "snd.h" "-I.")
;;;   
;;;
;;;  (c-define '(char* getcwd (char* size_t)) "" "unistd.h")
;;;    :(let ((str (make-string 32))) (getcwd str 32) str)
;;;    "/home/bil/cl\x00                   "
;;;    so it works in a sense -- there is a memory leak here
;;; 
;;;
;;; (c-define (list '(void* calloc (size_t size_t))
;;;		    '(void* malloc (size_t))
;;;		    '(void free (void*))
;;;		    '(void* realloc(void* size_t))
;;;		    '(void time (time_t*)) ; ignore returned value
;;;		    (list (symbol "struct tm*") 'localtime '(time_t*))
;;;                 (list 'size_t 'strftime (list 'char* 'size_t 'char* (symbol "struct tm*"))))
;;;          "" "time.h")
;;;   > (let ((p (calloc 1 8)) (str (make-string 32))) (time p) (strftime str 32 "%a %d-%b-%Y %H:%M %Z" (localtime p)) (free p) str)
;;;   "Sat 11-Aug-2012 08:55 PDT\x00      "
;;;
;;;
;;; (c-define '((int closedir (DIR*))
;;; 	        (DIR* opendir (char*))
;;; 		(in-C "static char *read_dir(DIR *p) \
;;;                    {                             \
;;;                      struct dirent *dirp;         \
;;;                      dirp = readdir(p);           \
;;;                      if (!dirp) return(NULL);     \
;;;                      else return(dirp->d_name);   \
;;;                     }")
;;; 	        (char* read_dir (DIR*)))
;;;   "" '("sys/types.h" "dirent.h"))
;;; 
;;; (let ((dir (opendir "/home/bil/gtk-snd")))
;;;   (do ((p (read_dir dir) (read_dir dir)))
;;;       ((= (length p) 0))
;;;     (format *stderr* "~A " p))
;;;   (closedir dir))
;;;
;;; (define (memory-usage)
;;;   (with-let *libc*
;;;     (let ((v (rusage.make))) 
;;;       (getrusage RUSAGE_SELF v)
;;;       (let ((mem (rusage.ru_maxrss v))) 
;;;         (free v) 
;;;         (* 1024 mem)))))
;;; --------------------------------------------------------------------------------

(define *cload-cflags* "") 
(define *cload-ldflags* "") 


(define-macro (defvar name value) 
  `(if (not (defined? ',name)) 
       (define ,name ,value)))

(defvar c-define-output-file-counter 0)   ; ugly, but I can't find a way around this (dlopen/dlsym stupidity)


;;; to place the new function in the caller's current environment, we need to pass the environment in explicitly:
(define-macro (c-define . args) 
  `(c-define-1 (curlet) ,@args))


(define* (c-define-1 cur-env function-info (prefix "") (headers ()) (cflags "") (ldflags "") output-name)
  ;; write a C shared library module that links in the functions in function-info
  ;;    function info is either a list: (return-type c-name arg-type) or a list thereof
  ;;    the new functions are placed in cur-env

  (define collides?
    (let ((all-names ()))
      (lambda (name)
	(if (member name all-names eq?) 
	    (format *stderr* "~A twice?~%" name)
	    (set! all-names (cons name all-names)))
	name)))

  (define handlers (list '(integer s7_is_integer s7_integer s7_make_integer s7_int)
			 '(boolean s7_is_boolean s7_boolean s7_make_boolean bool)
			 '(real s7_is_real s7_number_to_real s7_make_real s7_double)

			 ;; '(complex s7_is_complex #f s7_make_complex s7_Complex)
			 ;; the typedef is around line 6116 in s7.c, but we also need s7_complex which requires the s7_Complex type
			 ;; xen.h uses (s7_real_part(a) + s7_imag_part(a) * _Complex_I) instead since c++ won't let use define s7_Complex in s7.h

			 '(string s7_is_string s7_string s7_make_string char*)
			 (list 'character 's7_is_character 's7_character 's7_make_character (symbol "unsigned char"))
			 '(c_pointer s7_is_c_pointer s7_c_pointer s7_make_c_pointer void*)
			 '(s7_pointer #f #f #f s7_pointer)
			 ))

  (define (find-handler handle choice)
    (let ((found (assoc handle handlers eq?)))
      (if (pair? found)
	  (choice found)
	  #t)))
  

  (define (C-type->s7-type type)

    (if (pair? type)                             ; in case the type name does not make its C type obvious: (graph_style_t int)
	(symbol->string (cadr type))
	(let ((type-name (symbol->string type)))
	  (cond ((string-position "**" type-name)     ; any complicated C pointer is uninterpreted
		 'c_pointer)

		((string=? "s7_pointer" type-name)
		 's7_pointer)
		
		((string-position "char*" type-name)  ; but not char** (caught above)
		 'string)

		((or (string-position "*" type-name)  ; float* etc
		     (string-position "pointer" type-name))
		 'c_pointer)

		((string-position "char" type-name)
		 'character)

		((string-position "bool" type-name) 
		 'boolean)
		
		;; ((string-position "complex" type-name)
		;;  'complex)                              ; double complex or complex double (mus_edot_product in clm.c uses the latter)

		((or (string-position "float" type-name) 
		     (string-position "double" type-name)) 
		 'real)

		((or (string-position "int" type-name) 
		     (string-position "long" type-name) ; assuming not "long double" here so we need to look for it first (above)
		     (string-position "short" type-name) 
		     (string-position "size" type-name)
		     (string-position "byte" type-name)) 
		 'integer)

		(#t #t)))))

  (define (C->s7-cast type)
    (find-handler (C-type->s7-type type) (lambda (p) (car (cddddr p)))))
    
  (define (C->s7 type)
    (find-handler (C-type->s7-type type) cadddr))
    
  (define (s7->C type)
    (find-handler (C-type->s7-type type) caddr))

  (define (checker type)
    (find-handler (C-type->s7-type type) cadr))

  (define* (cload->signature type (rtn #f))
    (case (C-type->s7-type type)
      ((real)      (if rtn 'float? 'real?))
      ((integer)   'integer?)
      ((string)    'string?)
      ((boolean)   'boolean?)
      ((character) 'char?)
      ((c_pointer) 'c-pointer?)
      (else #t)))

  (define (signature->pl type)
    (case type
      ((integer?)   #\i)
      ((boolean?)   #\b)
      ((real?)      #\r)
      ((float?)     #\d)
      ((char?)      #\c)
      ((string?)    #\s)
      ((c-pointer?) #\x)
      (else         #\t)))

  (set! c-define-output-file-counter (+ c-define-output-file-counter 1))

  (let* ((file-name (or output-name (format "temp-s7-output-~D" c-define-output-file-counter)))
	 (c-file-name (string-append file-name ".c"))
	 (o-file-name (string-append file-name ".o"))
	 (so-file-name (string-append file-name ".so"))
	 (init-name (if output-name
			(string-append output-name "_init")
			(string-append "init_" (number->string c-define-output-file-counter))))
	 (functions ())
	 (constants ())
	 (macros ())     ; these are protected by #ifdef ... #endif
	 (inits ())      ; C code (a string in s7) inserted in the library initialization function
	 (p #f)
	 (if-funcs ())   ; if-functions (guaranteed to return int, so we can optimize away make-integer etc)
	 (rf-funcs ())  ; rf-functions
	 (sig-symbols (list (cons 'integer? 0) (cons 'boolean? 0) (cons 'real?  0) (cons 'float? 0) 
			    (cons 'char? 0) (cons 'string? 0) (cons 'c-pointer? 0) (cons 't 0)))
	 (signatures (make-hash-table)))

    (define (make-signature rtn args)

      (define (compress sig)
	(if (and (pair? sig)
		 (pair? (cdr sig))
		 (eq? (car sig) (cadr sig)))
	    (compress (cdr sig))
	    sig))

      (let ((sig (list (cload->signature rtn #t)))
	    (cyclic #f))
	(for-each
	 (lambda (arg)
	   (set! sig (cons (cload->signature arg) sig)))
	 args)
	(let ((len (length sig)))
	  (set! sig (compress sig))
	  (set! cyclic (not (= len (length sig)))))
	(set! sig (reverse sig))
	(when (not (signatures sig)) ; it's not in our collection yet
	  (let ((pl (make-string (+ (if cyclic 4 3) (length sig))))
		(loc (if cyclic 4 3)))
	    (set! (pl 0) #\p) 
	    (if cyclic
		(begin (set! (pl 1) #\c) (set! (pl 2) #\l) (set! (pl 3) #\_))
		(begin (set! (pl 1) #\l) (set! (pl 2) #\_)))
	    (for-each 
	     (lambda (typer)
	       (set! (pl loc) (signature->pl typer))
	       (let ((count (or (assoc typer sig-symbols eq?)
				(assoc 't sig-symbols eq?))))
		 (set-cdr! count (+ (cdr count) 1)))
	       (set! loc (+ loc 1)))
	     sig)
	    (set! (signatures sig) pl)))
	sig))

    (define (initialize-c-file)
      ;; C header stuff
      (set! p (open-output-file c-file-name))
      (format p "#include <stdlib.h>~%")
      (format p "#include <stdio.h>~%")
      (format p "#include <string.h>~%")
      (if (string? headers)
	  (format p "#include <~A>~%" headers)
	  (for-each
	   (lambda (header)
	     (format p "#include <~A>~%" header))
	   headers))
      (format p "#include \"s7.h\"~%~%"))
  

    (define* (add-one-function return-type name arg-types doc)
      ;; (format *stderr* "~A ~A ~A~%" return-type name arg-types): double j0 (double) for example
      ;; C function -> scheme
      (let* ((func-name   (symbol->string (collides? name)))
	     (num-args    (length arg-types))
	     (base-name   (string-append (if (> (length prefix) 0) prefix "s7_dl") "_" func-name)) ; not "g" -- collides with glib
	     (scheme-name (string-append prefix (if (> (length prefix) 0) ":" "") func-name)))

	(if (and (= num-args 1) 
		 (eq? (car arg-types) 'void))
	    (set! num-args 0))
	(format p "~%/* -------- ~A -------- */~%" func-name)
	(format p "static s7_pointer ~A(s7_scheme *sc, s7_pointer args)~%" base-name)
	(format p "{~%")
	
	;; get the Scheme args, check their types, assign to local C variables
	(if (positive? num-args)
	    (begin
	      (format p "  s7_pointer arg;~%")
	      (do ((i 0 (+ i 1))
		   (type arg-types (cdr type)))
		  ((= i num-args))
		(format p "  ~A ~A_~D;~%" (if (pair? (car type)) (caar type) (car type)) base-name i))
	      (format p "  arg = args;~%")
	      (do ((i 0 (+ i 1))
		   (type arg-types (cdr type)))
		  ((= i num-args))

		(let* ((nominal-type (if (pair? (car type)) (caar type) (car type)))  ; double in the example
		       (true-type    (if (pair? (car type)) (cadar type) (car type)))
		       (s7-type      (C-type->s7-type true-type)))                    ; real
		  (if (eq? true-type 's7_pointer)
		      (format p "    ~A_~D = s7_car(arg);~%" base-name i)
		      (begin
			(format p "  if (~A(s7_car(arg)))~%" (checker true-type))
			(format p "    ~A_~D = (~A)~A(~As7_car(arg));~%"
				base-name i
				nominal-type
				(s7->C true-type)                                     ; s7_number_to_real which requires 
				(if (member s7-type '(boolean real) eq?)              ;   the extra sc arg
				    "sc, " ""))
			(format p "  else return(s7_wrong_type_arg_error(sc, ~S, ~D, s7_car(arg), ~S));~%"
				func-name 
				(if (= num-args 1) 0 (+ i 1))
				(if (symbol? s7-type) 
				    (symbol->string s7-type) 
				    (error 'bad-arg (format #f "in ~S, ~S is not a symbol~%" name s7-type))))))
		  (if (< i (- num-args 1))
		      (format p "  arg = s7_cdr(arg);~%"))))))
	
	;; return C value to Scheme
	(if (pair? return-type) 
	    (set! return-type (cadr return-type)))
	(let ((return-translator (C->s7 return-type)))
	  (format p "  ")
	  (if (not (eq? return-translator #t))
	      (format p "return("))
	  (if (symbol? return-translator)
	      (format p "~A(sc, (~A)" return-translator (C->s7-cast return-type)))
	  (format p "~A(" func-name)
	  (do ((i 0 (+ i 1)))
	      ((>= i (- num-args 1)))
	    (format p "~A_~D, " base-name i))
	  (if (positive? num-args)
	      (format p "~A_~D" base-name (- num-args 1)))
	  (format p ")")
	  (if (symbol? return-translator)
	      (format p ")"))
	  (if (not (eq? return-translator #t))
	      (format p ");~%")
	      (format p ";~%  return(s7_unspecified(sc));~%"))
	  (format p "}~%"))
	
	;; add optimizer connection
	(when (and (eq? return-type 'double)     ; double (f double) -- s7_rf_t: double f(s7, s7_pointer **p)
		   (eq? (car arg-types) 'double)
		   (or (= num-args 1)
		       (and (= num-args 2)       ; double (f double double)
			    (eq? (cadr arg-types) 'double))))
	  (set! rf-funcs (cons (cons func-name scheme-name) rf-funcs))
	  (if (= num-args 1)	    
	      (format p "static s7_double ~A_rf_r(s7_scheme *sc, s7_pointer **p)~
                          {s7_rf_t f; s7_double x; f = (s7_rf_t)(**p); (*p)++; x = f(sc, p); return(~A(x));}~%" 
		      func-name func-name)
	      (format p "static s7_double ~A_rf_r(s7_scheme *sc, s7_pointer **p)~%  ~
                          {s7_rf_t f; s7_double x, y; f = (s7_rf_t)(**p); (*p)++; x = f(sc, p); f = (s7_rf_t)(**p); (*p)++; y = f(sc, p); return(~A(x, y));}~%" 
		      func-name func-name))
	  (format p "static s7_rf_t ~A_rf(s7_scheme *sc, s7_pointer expr) ~
                      {if (s7_arg_to_rf(sc, s7_cadr(expr))) return(~A_rf_r); return(NULL);}~%" 
		  func-name func-name))

	(when (and (eq? return-type 'int)        ; int (f int|double|void)
		   (member (car arg-types) '(int double void) eq?)
		   (<= num-args 1))
	  (set! if-funcs (cons (cons func-name scheme-name) if-funcs))
	  (case (car arg-types)
	    ((double)
	     (format p "static s7_int ~A_if_r(s7_scheme *sc, s7_pointer **p)~
                         {s7_rf_t f; s7_double x; f = (s7_rf_t)(**p); (*p)++; x = f(sc, p); return(~A(x));}~%" 
		     func-name func-name)
	     (format p "static s7_if_t ~A_if(s7_scheme *sc, s7_pointer expr) ~
                         {if (s7_arg_to_if(sc, s7_cadr(expr))) return(~A_if_r); return(NULL);}~%" 
		     func-name func-name))
	    ((int)
	     (format p "static s7_int ~A_if_i(s7_scheme *sc, s7_pointer **p)~
                         {s7_if_t f; s7_int x; f = (s7_if_t)(**p); (*p)++; x = f(sc, p); return(~A(x));}~%" 
		     func-name (if (string=? func-name "abs") "llabs" func-name))
	     (format p "static s7_if_t ~A_if(s7_scheme *sc, s7_pointer expr) ~
                         {if (s7_arg_to_if(sc, s7_cadr(expr))) return(~A_if_i); return(NULL);}~%" 
		     func-name func-name))
	    ((void)
	     (format p "static s7_int ~A_if_i(s7_scheme *sc, s7_pointer **p) {return(~A());}~%" func-name func-name)
	     (format p "static s7_if_t ~A_if(s7_scheme *sc, s7_pointer expr) {return(~A_if_i);}~%" func-name func-name))))
	  
	(format p "~%")
	(set! functions (cons (list scheme-name base-name 
				    (if (and (string? doc)
					     (> (length doc) 0))
					doc
					(format #f "~A ~A~A" return-type func-name arg-types))
				    num-args 0 
				    (make-signature return-type arg-types))
			      functions))))

    
    (define (add-one-constant type name)
      ;; C constant -> scheme
      (let ((c-type (if (pair? type) (cadr type) type)))
	(if (symbol? name)
	    (set! constants (cons (list c-type (symbol->string (collides? name))) constants))
	    (for-each 
	     (lambda (c)
	       (set! constants (cons (list c-type (symbol->string (collides? c))) constants)))
	     name))))


    (define (add-one-macro type name)
      ;; C macro (with definition check) -> scheme
      (let ((c-type (if (pair? type) (cadr type) type)))
	(if (symbol? name)
	    (set! macros (cons (list c-type (symbol->string (collides? name))) macros))
	    (for-each 
	     (lambda (c)
	       (set! macros (cons (list c-type (symbol->string (collides? c))) macros)))
	     name))))

  
    (define (end-c-file)
      ;; now the init function
      ;;   the new scheme variables and functions are placed in the current environment

      (format p "void ~A(s7_scheme *sc);~%" init-name)
      (format p "void ~A(s7_scheme *sc)~%" init-name)
      (format p "{~%")
      (format p "  s7_pointer cur_env;~%")
      (format p "  s7_pointer ")
      (let ((pls (hash-table-entries signatures))
	    (loc 1))
	(for-each
	 (lambda (s)
	   (format p "~A~A~A" (cdr s) (if (< loc pls) "," ";") (if (< loc pls) " " #\newline))
	   (set! loc (+ loc 1)))
	 signatures))

      (let ((syms ())
	    (names ()))
	(for-each
	 (lambda (q)
	   (when (positive? (cdr q))
	     (set! syms (cons (car q) syms))
	     (set! names (cons (signature->pl (car q)) names))))
	 sig-symbols)
	(when (pair? syms)
	  (format p "  {~%    s7_pointer ~{~C~^, ~};~%" names)
	  (for-each
	   (lambda (name sym)
	     (if (eq? sym 't)
		 (format p "    t = s7_t(sc);~%")
		 (format p "    ~C = s7_make_symbol(sc, ~S);~%" name (symbol->string sym))))
	   names syms))
	(format p "~%")
	(for-each
	 (lambda (sig)
	   (let ((cyclic (char=? ((cdr sig) 1) #\c)))
	     (if cyclic
		 (format p "    ~A = s7_make_circular_signature(sc, ~D, ~D" (cdr sig) (- (length (car sig)) 1) (length (car sig)))
		 (format p "    ~A = s7_make_signature(sc, ~D" (cdr sig) (length (car sig))))
	     (format p "~{~^, ~C~}" (substring (cdr sig) (if cyclic 4 3)))
	     (format p ");~%")))
	 signatures)
	(format p "  }~%~%"))

      (format p "  cur_env = s7_outlet(sc, s7_curlet(sc));~%") ; this must exist because we pass load the env ourselves
      
      ;; send out any special initialization code
      (for-each
       (lambda (init-str)
	 (format p "  ~A~%" init-str))
       (reverse inits))

      ;; "constants" -- actually variables in s7 because we want them to be local to the current environment
      (if (pair? constants)
	  (begin
	    (format p "~%")
	    (for-each
	     (lambda (c)
	       (let* ((type (c 0))
		      (c-name (c 1))
		      (scheme-name (string-append prefix (if (> (length prefix) 0) ":" "") c-name)))
		 (format p "  s7_define(sc, cur_env, s7_make_symbol(sc, ~S), ~A(sc, (~A)~A));~%" 
			 scheme-name
			 (C->s7 type)
			 (C->s7-cast type)
			 c-name)))
	     constants)))
      
      ;; C macros -- need #ifdef name #endif wrapper
      (if (pair? macros)
	  (begin
	    (format p "~%")
	    (for-each
	     (lambda (c)
	       (let* ((type (c 0))
		      (c-name (c 1))
		      (scheme-name (string-append prefix (if (> (length prefix) 0) ":" "") c-name)))
		 (format p "#ifdef ~A~%" c-name)
		 (format p "  s7_define(sc, cur_env, s7_make_symbol(sc, ~S), ~A(sc, (~A)~A));~%" 
			 scheme-name
			 (C->s7 type)
			 (C->s7-cast type)
			 c-name)
		 (format p "#endif~%")))
	     macros)))
      
      ;; functions
      (for-each
       (lambda (f) 
	 (let ((scheme-name (f 0))
	       (base-name   (f 1))
	       (help        (f 2))
	       (num-args    (f 3))
	       (opt-args    (if (> (length f) 4) (f 4) 0))
	       (sig         (and (> (length f) 5) (f 5))))
	   (format p "~%  s7_define(sc, cur_env,~%            s7_make_symbol(sc, ~S),~%" scheme-name)
	   (format p "            s7_make_typed_function(sc, ~S, ~A, ~D, ~D, false, ~S, ~A));~%"
		   scheme-name
		   base-name
		   num-args
		   opt-args
		   help
		   (if (pair? sig) (signatures sig) 'NULL))))
       functions)

      ;; optimizer connection
      (when (pair? rf-funcs)
	(format p "~%  /* rf optimizer connections */~%")
	(for-each
	 (lambda (f)
	   (format p "  s7_rf_set_function(s7_name_to_value(sc, ~S), ~A_rf);~%" (cdr f) (car f)))
	 rf-funcs))

      (when (pair? if-funcs)
	(format p "~%  /* if optimizer connections */~%")
	(for-each
	 (lambda (f)
	   (format p "  s7_if_set_function(s7_name_to_value(sc, ~S), ~A_if);~%" (cdr f) (car f)))
	 if-funcs))
      
      (format p "}~%")
      (close-output-port p)
      
      ;; now we have the module .c file -- make it into a shared object, load it, delete the temp files
      
      (if (provided? 'osx)
	  (begin
	    ;; I assume the caller is also compiled with these flags?
	    (system (format #f "gcc -c ~A ~A" 
			    c-file-name (string-append *cload-cflags* " " cflags)))
	    (system (format #f "gcc ~A -o ~A -dynamic -bundle -undefined suppress -flat_namespace ~A" 
			    o-file-name so-file-name (string-append *cload-ldflags* " " ldflags))))

	  (if (provided? 'freebsd)
	      (begin
		(system (format #f "cc -fPIC -c ~A ~A" 
				c-file-name (string-append *cload-cflags* " " cflags)))
		(system (format #f "cc ~A -shared -o ~A ~A" 
				o-file-name so-file-name (string-append *cload-ldflags* " " ldflags))))

	      (if (provided? 'openbsd)
		  (begin
		    (system (format #f "cc -fPIC -ftrampolines -c ~A ~A" 
				    c-file-name (string-append *cload-cflags* " " cflags)))
		    (system (format #f "cc ~A -shared -o ~A ~A" 
				    o-file-name so-file-name (string-append *cload-ldflags* " " ldflags))))

		  (begin
		    (system (format #f "gcc -fPIC -c ~A ~A" 
				    c-file-name (string-append *cload-cflags* " " cflags)))
		    (system (format #f "gcc ~A -shared -o ~A ~A" 
				    o-file-name so-file-name (string-append *cload-ldflags* " " ldflags))))))))

    (define (check-doc func-data)
      (let ((doc (caddr func-data)))
	(if (and (string? doc)
		 (> (length doc) 0))
	    func-data
	    (append (list (car func-data) (cadr func-data) (car func-data)) (cdddr func-data)))))

    (define (handle-declaration func)
      ;; functions
      (if (>= (length func) 3)
	  (apply add-one-function func)
	  (case (car func)
	    ((in-C)       (format p "~A~%" (cadr func)))
	    ((C-init)     (set! inits (cons (cadr func) inits)))
	    ((C-macro)    (apply add-one-macro (cadr func)))
	    ((C-function) (collides? (caadr func)) (set! functions (cons (check-doc (cadr func)) functions)))
	    (else         (apply add-one-constant func)))))


    ;; this is the body of c-define
    (if (or (not output-name)
	    (not (file-exists? c-file-name))
	    (not (file-exists? so-file-name))
	    (not (provided? 'system-extras))
	    (< (file-mtime so-file-name) (file-mtime c-file-name))   ; they are equal on my linux system
	    (and (file-exists? (port-filename (current-input-port))) ; we're actually loading a file
		 (< (file-mtime so-file-name) (file-mtime (port-filename (current-input-port))))))
	(begin
	  (format *stderr* "writing ~A~%" c-file-name)
	  ;; write a new C file and compile it
	  (initialize-c-file)

	  (if (and (pair? (cdr function-info))
		   (symbol? (cadr function-info)))
	      (handle-declaration function-info)
	      (for-each handle-declaration function-info))
	  
	  (end-c-file)
	  (delete-file o-file-name)))

    ;; load the object file, clean up
    (let ((new-env (sublet cur-env 'init_func (string->symbol init-name))))
      (format *stderr* "loading ~A~%" so-file-name)
      (load so-file-name new-env))))



;;; backwards compatibility
(define define-c-function c-define)


#|
(let ((cd (symbol "complex double"))
      (cd* (symbol "complex double *")))
  (c-define (list cd 'mus_edot_product (list cd cd* 'int))))

;complex double mus_edot_product(complex double freq, complex double *data, mus_long_t size)
|#
