#! /usr/bin/env chibi-scheme

;; Note: this evolved as a throw-away script to provide certain core
;; modules, and so is a mess.  Tread carefully.

;; Simple C FFI.  "chibi-ffi file.stub" will read in the C function
;; FFI definitions from file.stub and output the appropriate C
;; wrappers into file.c.  You can then compile that file with:
;;
;;   cc -fPIC -shared file.c -lchibi-scheme
;;
;; (or using whatever flags are appropriate to generate shared libs on
;; your platform) and then the generated .so file can be loaded
;; directly with load, or portably using (include-shared "file") in a
;; module definition (note that include-shared uses no suffix).
;;
;; Passing the -c/--compile option will attempt to compile the .so
;; file in a single step.

;; The goal of this interface is to make access to C types and
;; functions easy, without requiring the user to write any C code.
;; That means the stubber needs to be intelligent about various C
;; calling conventions and idioms, such as return values passed in
;; actual parameters.  Writing C by hand is still possible, and
;; several of the core modules provide C interfaces directly without
;; using the stubber.

;; For bootstrapping purposes we depend only on the core language.
(import (chibi))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; globals

(define *ffi-version* "0.5")
(define *types* '())
(define *type-getters* '())
(define *type-setters* '())
(define *typedefs* '())
(define *funcs* '())
(define *methods* '())
(define *consts* '())
(define *inits* '())
(define *clibs* '())
(define *cflags* '())
(define *frameworks* '())
(define *tags* '())
(define *open-namespaces* '())
(define *c++?* #f)
(define wdir ".")

(define auto-expand-limit (* 10 1024 1024))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; type objects

(define (make-type) (make-vector 20 #f))

(define (type-base type) (vector-ref type 0))
(define (type-free? type) (vector-ref type 1))
(define (type-const? type) (vector-ref type 2))
(define (type-null? type) (vector-ref type 3))
(define (type-pointer? type) (vector-ref type 4))
(define (type-reference? type) (vector-ref type 5))
(define (type-struct? type) (vector-ref type 6))
(define (type-link? type) (vector-ref type 7))
(define (type-result? type) (vector-ref type 8))
(define (type-array type) (vector-ref type 9))
(define (type-value type) (vector-ref type 10))
(define (type-default? type) (vector-ref type 11))
(define (type-template type) (vector-ref type 12))
(define (type-new? type) (vector-ref type 13))
(define (type-error type) (vector-ref type 14))
(define (type-address-of? type) (vector-ref type 15))
(define (type-no-free? type) (vector-ref type 16))
(define (type-as-sexp type) (vector-ref type 17))
(define (type-move? type) (vector-ref type 18))
(define (type-index type) (vector-ref type 19))
(define (type-index-set! type i) (vector-set! type 19 i))

(define (spec->type type . o)
  (let ((res (make-type)))
    (if (pair? o)
        (type-index-set! res (car o)))
    (let lp ((type type))
      (define (next) (if (null? (cddr type)) (cadr type) (cdr type)))
      (case (and (pair? type) (car type))
        ((free)
         (vector-set! res 1 #t)
         (lp (next)))
        ((const)
         (vector-set! res 2 #t)
         (lp (next)))
        ((maybe-null)
         (vector-set! res 3 #t)
         (lp (next)))
        ((pointer)
         (vector-set! res 4 #t)
         (lp (next)))
        ((reference)
         (vector-set! res 5 #t)
         (lp (next)))
        ((struct)
         (vector-set! res 6 #t)
         (lp (next)))
        ((link)
         (vector-set! res 7 #t)
         (lp (next)))
        ((result)
         (vector-set! res 8 #t)
         (lp (next)))
        ((array)
         (vector-set! res 9 (if (pair? (cddr type)) (car (cddr type)) #t))
         (lp (cadr type)))
        ((value)
         (vector-set! res 10 (cadr type))
         (lp (cddr type)))
        ((default)
         (vector-set! res 10 (cadr type))
         (vector-set! res 11 #t)
         (lp (cddr type)))
        ((template)
         (vector-set! res 12 (cadr type))
         (lp (cddr type)))
        ((new)
         (vector-set! res 13 #t)
         (lp (next)))
        ((error)
         (vector-set! res 8 #t)
         (vector-set! res 14 (cadr type))
         (lp (cddr type)))
        ((address-of)
         (vector-set! res 15 #t)
         (lp (next)))
        ((no-free)
         (vector-set! res 16 #t)
         (lp (next)))
        ((as-sexp)
         (vector-set! res 17 #t)
         (lp (next)))
        ((move)
         (vector-set! res 18 #t)
         (lp (next)))
        (else
         (let ((base (if (and (pair? type) (null? (cdr type)))
                         (car type)
                         type)))
           (vector-set! res 0 base)
           res))))))

(define (parse-type type . o)
  (cond
   ((vector? type)
    (if (and (pair? o) (car o))
        (let ((res (vector-copy type)))
          (type-index-set! res (car o))
          res)
        type))
   (else
    (apply spec->type type o))))

(define (type-auto-expand? type)
  (and (pair? (type-array type))
       (memq 'auto-expand (type-array type))))

(define (type-index-string type)
  (if (integer? (type-index type))
      (number->string (type-index type))
      ""))

(define (struct-fields ls)
  (let lp ((ls ls) (res '()))
    (cond ((not (pair? ls)) (reverse res))
          ((symbol? (car ls)) (lp (if (pair? (cdr ls)) (cddr ls) (cdr ls)) res))
          (else (lp (cdr ls) (cons (car ls) res))))))

(define (lookup-type type)
  (or (assq type *types*)
      (assq type *typedefs*)))

(define (type-field-type type field)
  (cond
   ((lookup-type (type-base (parse-type type)))
    => (lambda (x)
         (let lp ((ls (struct-fields (cdr x))))
           (cond
            ((null? ls) #f)
            ((eq? field (caar ls)) (car (cdar ls)))
            (else (lp (cdr ls)))))))
   (else
    #f)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; type predicates

(define *c-int-types* '())
(define *c-enum-types* '())

(define-syntax define-c-int-type
  (syntax-rules ()
    ((define-c-int-type type)
     (if (not (memq 'type *c-int-types*))
         (set! *c-int-types* (cons 'type *c-int-types*))))))

(define-syntax define-c-enum
  ;; TODO: support conversion to/from symbolic names
  (er-macro-transformer
   (lambda (expr rename compare)
     (let ((name (if (pair? (cadr expr)) (car (cadr expr)) (cadr expr)))
           (c-name (if (pair? (cadr expr)) (cadr (cadr expr)) (cadr expr))))
       (set! *c-enum-types*
             `((,name . ,c-name) ,@*c-enum-types*))
       `(,(rename 'define-c-const)
         ,name
         ,@(cddr expr))))))

(define (enum-type? type)
  (assq type *c-enum-types*))

(define (signed-int-type? type)
  (or (memq type '(signed-char short int long s8 s16 s32 s64
                   int8_t int16_t int32_t int64_t
                   pid_t off_t ssize_t blkcnt_t blksize_t))
      (memq type *c-int-types*)
      (enum-type? type)))

(define (unsigned-int-type? type)
  (memq type '(unsigned-char unsigned-short unsigned unsigned-int unsigned-long
               size_t time_t clock_t dev_t ino_t mode_t nlink_t
               uint8_t uint16_t uint32_t uint64_t
               uid_t gid_t sigval_t
               u1 u8 u16 u32 u64)))

(define (int-type? type)
  (or (signed-int-type? type) (unsigned-int-type? type)))

(define (float-type? type)
  (memq type '(float double long-double long-long-double f8 f16 f32 f64)))

(define (string-type? type)
  (or (memq type '(char* string env-string non-null-string))
      (and (vector? type)
           (type-array type)
           (not (type-pointer? type))
           (eq? 'char (type-base type)))))

(define (port-type? type)
  (memq type '(port input-port output-port input-output-port)))

(define (error-type? type)
  (or (type-error type)
      (memq (type-base type)
            '(errno status-bool non-null-string non-null-pointer))))

(define (array-type? type)
  (and (type-array type) (not (eq? 'char (type-base type)))))

(define (basic-type? type)
  (let ((type (parse-type type)))
    (and (not (type-array type))
         (not (void-pointer-type? type))
         (not (lookup-type (type-base type))))))

(define (void-type? type)
  (and (eq? 'void (type-base type)) (not (type-pointer? type))))

(define (void-pointer-type? type)
  (or (and (eq? 'void (type-base type)) (type-pointer? type))
      (eq? 'void* (type-base type))))

(define (uniform-vector-type-code type)
  (case type
    ((u1vector) 'SEXP_U1)
    ((u8vector) 'SEXP_U8)
    ((s8vector) 'SEXP_S8)
    ((u16vector) 'SEXP_U16)
    ((s16vector) 'SEXP_S16)
    ((u32vector) 'SEXP_U32)
    ((s32vector) 'SEXP_S32)
    ((u64vector) 'SEXP_U64)
    ((s64vector) 'SEXP_S64)
    ((f8vector) 'SEXP_F8)
    ((f16vector) 'SEXP_F16)
    ((f32vector) 'SEXP_F32)
    ((f64vector) 'SEXP_F64)
    ((c64vector) 'SEXP_C64)
    ((c128vector) 'SEXP_C128)
    (else #f)))

(define (uniform-vector-type? type)
  (or (eq? type 'uvector)
      (and (uniform-vector-type-code type) #t)))

(define (uniform-vector-ctype type)
  (case type
    ((uvector) "sexp")
    ((u1vector) "char*")
    ((u8vector) "unsigned char*")
    ((s8vector) "signed char*")
    ((u16vector) "unsigned short*")
    ((s16vector) "signed short*")
    ((u32vector) "uint32_t*")
    ((s32vector) "int32_t*")
    ((u64vector) "uint64_t*")
    ((s64vector) "int64_t*")
    ((f8vector) "unsigned char*")
    ((f16vector) "unsigned short*")
    ((f32vector) "float*")
    ((f64vector) "double*")
    ((c64vector) "float*")
    ((c128vector) "double*")
    (else #f)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; function objects

(define (parse-func func . o)
  (if (not (and (>= (length func) 3)
                (or (identifier? (cadr func))
                    (and (list? (cadr func))
                         (<= 1 (length (cadr func)) 3)
                         (every (lambda (x) (or (identifier? x) (not x) (string? x)))
                                (cadr func))))
                (list? (car (cddr func)))))
      (error "bad function definition" func))
  (let* ((method? (and (pair? o) (car o)))
         (ret-type (parse-type (car func)))
         (scheme-name (if (pair? (cadr func)) (car (cadr func)) (cadr func)))
         (c-name (if (pair? (cadr func))
                     (cadr (cadr func))
                     (mangle scheme-name)))
         (stub-name (if (and (pair? (cadr func)) (pair? (cddr (cadr func))))
                        (car (cddr (cadr func)))
                        (generate-stub-name scheme-name)))
         (inline (and (> (length func) 3)
                      (cond ((assq 'inline (cdr (cddr func))) => cadr)
                            (else #f))))
         (assert (or (and (> (length func) 3)
                          (cond ((assq 'assert (cdr (cddr func))) => cdr)
                                (else #f)))
                     '())))
    (let lp ((ls (if (equal? (car (cddr func)) '(void)) '() (car (cddr func))))
             (i 0)
             (results '())
             (c-args '())
             (s-args '()))
      (cond
       ((null? ls)
        (vector scheme-name c-name stub-name ret-type
                (reverse results) (reverse c-args) (reverse s-args)
                method? inline assert))
       (else
        (let ((type (parse-type (car ls) i)))
          (cond
           ((type-result? type)
            (lp (cdr ls) (+ i 1) (cons type results) (cons type c-args) s-args))
           ((and (type-value type) (not (type-default? type)))
            (lp (cdr ls) (+ i 1) results (cons type c-args) s-args))
           (else
            (lp (cdr ls) (+ i 1) results (cons type c-args) (cons type s-args)))
           )))))))

(define (func-scheme-name func) (vector-ref func 0))
(define (func-c-name func) (vector-ref func 1))
(define (func-stub-name func) (vector-ref func 2))
(define (func-ret-type func) (vector-ref func 3))
(define (func-results func) (vector-ref func 4))
(define (func-c-args func) (vector-ref func 5))
(define (func-scheme-args func) (vector-ref func 6))
(define (func-method? func) (vector-ref func 7))
(define (func-inline func) (vector-ref func 8))
(define (func-assert func) (vector-ref func 9))

(define (func-stub-name-set! func x) (vector-set! func 2 x))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; utilities

(define (cat . args)
  (for-each (lambda (x) (if (procedure? x) (x) (display x))) args))

(define (join ls . o)
  (if (pair? ls)
      (let ((sep (if (pair? o) (car o) " ")))
        (let lp ((ls ls))
          (if (pair? (cdr ls))
              (cat (car ls) sep (lambda () (lp (cdr ls))))
              (cat (car ls)))))
      ""))

(define (x->string x)
  (cond ((string? x) x)
        ((symbol? x) (symbol->string x))
        ((number? x) (number->string x))
        (else (error "non-stringable object" x))))

(define (filter pred ls)
  (cond ((null? ls) '())
        ((pred (car ls)) (cons (car ls) (filter pred (cdr ls))))
        (else (filter pred (cdr ls)))))

(define (remove pred ls)
  (cond ((null? ls) '())
        ((pred (car ls)) (remove pred (cdr ls)))
        (else (cons (car ls) (remove pred (cdr ls))))))

(define (strip-extension path)
  (let lp ((i (- (string-length path) 1)))
    (cond ((<= i 0) path)
          ((eq? #\. (string-ref path i)) (substring path 0 i))
          (else (lp (- i 1))))))

(define (string-concatenate-reverse ls)
  (cond ((null? ls) "")
        ((null? (cdr ls)) (car ls))
        (else (string-concatenate (reverse ls)))))

(define (string-replace str c r)
  (let ((len (string-length str)))
    (let lp ((from 0) (i 0) (res '()))
      (define (collect) (if (= i from) res (cons (substring str from i) res)))
      (cond
       ((>= i len) (string-concatenate-reverse (collect)))
       ((eqv? c (string-ref str i)) (lp (+ i 1) (+ i 1) (cons r (collect))))
       (else (lp from (+ i 1) res))))))

(define (string-split str c . o)
  (let ((test?
         (if (procedure? c)
             c
             (lambda (char) (eqv? char c))))
        (start (if (pair? o) (car o) 0))
        (end (string-length str)))
    (let lp ((from start) (i start) (res '()))
      (define (collect) (if (= i from) res (cons (substring str from i) res)))
      (cond
       ((>= i end) (reverse (collect)))
       ((test? (string-ref str i)) (lp (+ i 1) (+ i 1) (collect)))
       (else (lp from (+ i 1) res))))))

(define (string-scan c str . o)
  (let ((end (string-length str)))
    (let lp ((i (if (pair? o) (car o) 0)))
      (cond ((>= i end) #f)
            ((eqv? c (string-ref str i)) i)
            (else (lp (+ i 1)))))))

(define (string-downcase str)
  (list->string (map char-downcase (string->list str))))

(define (with-output-to-string thunk)
  (call-with-output-string
    (lambda (out)
      (let ((old-out (current-output-port)))
        (current-output-port out)
        (thunk)
        (current-output-port old-out)))))

(define (warn msg . args)
  (let ((err (current-error-port)))
    (display "WARNING: " err)
    (display msg err)
    (if (pair? args) (display ":" err))
    (for-each (lambda (x) (display " " err) (write x err)) args)
    (newline err)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; naming

(define (c-char? c)
  (or (char-alphabetic? c) (char-numeric? c) (memv c '(#\_ #\- #\! #\?))))

(define (c-escape str)
  (let ((len (string-length str)))
    (let lp ((from 0) (i 0) (res '()))
      (define (collect) (if (= i from) res (cons (substring str from i) res)))
      (cond
       ((>= i len) (string-concatenate-reverse (collect)))
       ((not (c-char? (string-ref str i)))
        (lp (+ i 1) (+ i 1)
            `("_" ,(number->string (char->integer (string-ref str i)) 16)
              ,@(collect))))
       (else (lp from (+ i 1) res))))))

(define (mangle x)
  (string-replace
   (string-replace (string-replace (c-escape (x->string x)) #\- "_") #\? "_p")
   #\! "_x"))

(define (generate-stub-name sym)
  (string-append "sexp_" (mangle sym) "_stub"))

(define (type-id-name sym)
  (string-append "sexp_" (mangle sym) "_type_obj"))

(define (make-integer x)
  (case x
    ((-1) "SEXP_NEG_ONE")  ((0) "SEXP_ZERO")   ((1) "SEXP_ONE")
    ((2) "SEXP_TWO")       ((3) "SEXP_THREE")  ((4) "SEXP_FOUR")
    ((5) "SEXP_FIVE")      ((6) "SEXP_SIX")    ((7) "SEXP_SEVEN")
    ((8) "SEXP_EIGHT")     ((9) "SEXP_NINE")   ((10) "SEXP_TEN")
    (else (string-append "sexp_make_fixnum(" (x->string x) ")"))))

(define (string-scan-right str ch)
  (let lp ((i (string-cursor-end str)))
    (let ((i2 (string-cursor-prev str i)))
      (cond ((string-cursor<? i2 0) 0)
            ((eqv? ch (string-cursor-ref str i2)) i)
            (else (lp i2))))))

(define (strip-namespace x)
  (string->symbol
   (let* ((x (x->string x))
          (i (string-scan-right x #\:)))
     (if (> i 0)
         (substring-cursor x i)
         x))))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; .stub file interface

(define (ffi-include file)
  (load file (current-environment)))

(define (c-link lib)
  (set! *clibs* (cons lib *clibs*)))

(define (c-framework lib)
  (set! *frameworks* (cons lib *frameworks*)))

(define (c-flags-from-script cmd)
  (eval '(import (chibi process)) (current-environment))
  (let ((string-null?    (lambda (str) (equal? str "")))
        (process->string (eval 'process->string (current-environment))))
    (set! *cflags*
      (append *cflags*
              (filter
               (lambda (x) (not (string-null? x)))
               (string-split (process->string cmd) char-whitespace?))))))

(define (c-declare . args)
  (apply cat args)
  (newline))

(define (c-include header)
  (cat "\n#include \"" header "\"\n"))

(define (c-system-include header)
  (cat "\n#include <" header ">\n"))

(define (c-include-verbatim file)
  (call-with-input-file (if (eqv? #\/ (string-ref file 0))
                            file
                            (string-append wdir "/" file))
    (lambda (in)
      (let lp ()
        (let ((c (read-char in)))
          (cond
           ((not (eof-object? c))
            (write-char c)
            (lp))))))))

(define (c-init x)
  (set! *inits* (cons x *inits*)))

(define (parse-struct-like ls)
  (let lp ((ls ls) (res '()))
    (cond
     ((null? ls)
      (reverse res))
     ((symbol? (car ls))
      (lp (cddr ls) (cons (cadr ls) (cons (car ls) res))))
     ((pair? (car ls))
      (lp (cdr ls) (cons (cons (parse-type (caar ls)) (cdar ls)) res)))
     (else
      (lp (cdr ls) (cons (car ls) res))))))

(define-syntax define-struct-like
  (er-macro-transformer
   (lambda (expr rename compare)
     (set! *types*
           `((,(cadr expr)
              ,@(parse-struct-like (cddr expr)))
             ,@*types*))
     (set! *tags* `(,(type-id-name (cadr expr)) ,@*tags*))
     #f)))

(define-syntax define-c-struct
  (er-macro-transformer
   (lambda (expr rename compare)
     `(define-struct-like ,(cadr expr) type: struct ,@(cddr expr)))))

(define-syntax define-c-class
  (er-macro-transformer
   (lambda (expr rename compare)
     `(define-struct-like ,(cadr expr) type: class ,@(cddr expr)))))

(define-syntax define-c-union
  (er-macro-transformer
   (lambda (expr rename compare)
     `(define-struct-like ,(cadr expr) type: union ,@(cddr expr)))))

(define-syntax define-c-type
  (er-macro-transformer
   (lambda (expr rename compare)
     `(define-struct-like ,(cadr expr) ,@(cddr expr)))))

(define-syntax declare-c-struct
  (er-macro-transformer
   (lambda (expr rename compare)
     `(define-struct-like ,(cadr expr) type: struct imported?: #t))))

(define-syntax declare-c-class
  (er-macro-transformer
   (lambda (expr rename compare)
     `(define-struct-like ,(cadr expr) type: class imported?: #t))))

(define-syntax declare-c-union
  (er-macro-transformer
   (lambda (expr rename compare)
     `(define-struct-like ,(cadr expr) type: union imported?: #t))))

(define-syntax define-c
  (er-macro-transformer
   (lambda (expr rename compare)
     (set! *funcs* (cons (parse-func (cdr expr)) *funcs*))
     #f)))

(define-syntax define-c-const
  (er-macro-transformer
   (lambda (expr rename compare)
     (let ((type (parse-type (cadr expr))))
       (for-each (lambda (x) (set! *consts* (cons (list type x) *consts*)))
                 (cddr expr))))))

;; custom strerror which reports constants as their names
(define-syntax define-c-strerror
  (er-macro-transformer
   (lambda (expr rename compare)
     (let ((name (cadr expr))
           (errnos (cddr expr)))
       `(,(rename 'c-declare)
         ,(string-concatenate
           `("\nchar* " ,(x->string name) "(const int err) {
  static char buf[64];
  switch (err) {
"
             ,@(map (lambda (errno)
                      (let ((e (x->string errno)))
                        (string-append "    case " e ": return \"" e "\";\n")))
                    errnos)

             "  }
  snprintf(buf, sizeof(buf), \"unknown error: %d\", err);
  return buf;
}")))))))

(define-syntax c-typedef
  (er-macro-transformer
   (lambda (expr rename compare)
     (let ((type (parse-type (cadr expr)))
           (name (car (cddr expr))))
       (set! *typedefs* `((,name ,@type) ,@*typedefs*))
       `(,(rename 'cat) "typedef " ,(type-c-name type) " " ',name ";\n")))))

(define (c++)
  (set! *c++?* #t))

(define (ensure-c++ name)
  (cond
   ((not *c++?*)
    (display "WARNING: assuming c++ mode from " (current-error-port))
    (display name (current-error-port))
    (display " - use (c++) to make this explicit\n" (current-error-port))
    (c++))))

(define-syntax c++-namespace
  (er-macro-transformer
   (lambda (expr rename compare)
     (ensure-c++ 'c++-namespace)
     (let ((namespace (cadr expr)))
       (cond
        ((null? (cddr expr))
         (set! *open-namespaces* (cons namespace *open-namespaces*))
         `(,(rename 'cat) "namespace " ',namespace ";\n"))
        (else
         `(,(rename 'begin)
           (,(rename 'cat) "namespace " ',namespace " {\n")
           ,@(cddr expr)
           (,(rename 'cat) "}  // namespace " ',namespace "\n\n"))))))))

(define-syntax c++-using
  (er-macro-transformer
   (lambda (expr rename compare)
     (ensure-c++ 'c++-using)
     `(,(rename 'cat) "using " ',(cadr expr) ";\n"))))

(define-syntax c++-using-namespace
  (er-macro-transformer
   (lambda (expr rename compare)
     (ensure-c++ 'c++-using-namespace)
     `(,(rename 'cat) "using namespace " ',(cadr expr) ";\n"))))

(define-syntax define-c++-method
  (er-macro-transformer
   (lambda (expr rename compare)
     (ensure-c++ 'define-c++-method)
     (let* ((class (cadr expr))
            (ret-type (car (cddr expr)))
            (name (cadr (cddr expr)))
            (meths (map (lambda (x)
                          (parse-func `(,ret-type ,name (,class ,@x)) #t))
                        (cddr (cddr expr)))))
       (set! *methods* (cons (cons name meths) *methods*))))))

(define-syntax define-c++-constructor
  (er-macro-transformer
   (lambda (expr rename compare)
     (ensure-c++ 'define-c++-constructor)
     (set! *funcs*
           (cons (parse-func `((new ,(if (pair? (cadr expr))
                                         (cadr (cadr expr))
                                         (cadr expr)))
                               ,(cadr expr)
                               ,@(cddr expr)))
                 *funcs*))
     #f)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; C code generation

(define (type-predicate type)
  (let ((base (type-base (parse-type type))))
    (cond
     ((int-type? base) "sexp_exact_integerp")
     ((float-type? base) "sexp_flonump")
     ((string-type? base) "sexp_stringp")
     (else
      (case base
        ((bytevector u8vector) "sexp_bytesp")
        ((char) "sexp_charp")
        ((bool boolean status-bool) "sexp_booleanp")
        ((port) "sexp_portp")
        ((input-port) "sexp_iportp")
        ((output-port) "sexp_oportp")
        ((input-output-port) "sexp_ioportp")
        ((fileno fileno-nonblock) "sexp_filenop")
        ((uvector) "sexp_uvectorp")
        ((u1vector) "sexp_u1vectorp")
        ((s8vector) "sexp_s8vectorp")
        ((u16vector) "sexp_u16vectorp")
        ((s16vector) "sexp_s16vectorp")
        ((u32vector) "sexp_u32vectorp")
        ((s32vector) "sexp_s32vectorp")
        ((u64vector) "sexp_u64vectorp")
        ((s64vector) "sexp_s64vectorp")
        ((f8vector) "sexp_f8vectorp")
        ((f16vector) "sexp_f16vectorp")
        ((f32vector) "sexp_f32vectorp")
        ((f64vector) "sexp_f64vectorp")
        ((c64vector) "sexp_c64vectorp")
        ((c128vector) "sexp_c128vectorp")
        (else #f))))))

(define (type-name type)
  (let ((base (type-base (parse-type type))))
    (cond
     ((int-type? base) "integer")
     ((float-type? base) "flonum")
     ((memq base '(bool boolean status-bool)) (if *c++?* "bool" "int"))
     (else base))))

(define (type-id-number type . o)
  (let ((base (type-base type)))
    (cond
     ((int-type? base) "SEXP_FIXNUM")
     ((float-type? base) "SEXP_FLONUM")
     ((string-type? base) "SEXP_STRING")
     ((memq base '(bytevector u8vector)) "SEXP_BYTES")
     ((eq? base 'char) "SEXP_CHAR")
     ((memq base '(bool boolean status-bool)) "SEXP_BOOLEAN")
     ((eq? base 'string) "SEXP_STRING")
     ((eq? base 'symbol) "SEXP_SYMBOL")
     ((eq? base 'pair) "SEXP_PAIR")
     ((eq? base 'port) "SEXP_IPORT")
     ((eq? base 'input-port) "SEXP_IPORT")
     ((eq? base 'output-port) "SEXP_OPORT")
     ((eq? base 'input-output-port) "SEXP_IPORT")
     ((memq base '(fileno fileno-nonblock)) "SEXP_FILENO")
     ((uniform-vector-type? base)
      "SEXP_UNIFORM_VECTOR")
     ((void-pointer-type? type) "SEXP_CPOINTER")
     ((lookup-type base)
      ;; (string-append "sexp_type_tag(" (type-id-name base) ")")
      (let ((i (type-index type)))
        (cond
         ((not i)
          ;;(warn "type-id-number on unknown arg" type)
          (if (and (pair? o) (car o))
              "sexp_unbox_fixnum(sexp_opcode_return_type(self))"
              (string-append "sexp_type_tag(" (type-id-name base) ")")))
         ((< i 3)
          (string-append
           "sexp_unbox_fixnum(sexp_opcode_arg"
           (number->string (+ i 1)) "_type(self))"))
         (else
          (string-append
           "sexp_unbox_fixnum(sexp_vector_ref(sexp_opcode_argn_type(self), "
           (make-integer (- i 3)) "))")))))
     (else "SEXP_OBJECT"))))

(define (type-id-value type . o)
  (cond
   ((eq? 'void (type-base type))
    "SEXP_VOID")
   (else
    (make-integer (apply type-id-number type o)))))

(define (type-id-init-value type)
  (cond
   ((lookup-type (type-base type))
    (make-integer
     (string-append "sexp_type_tag(" (type-id-name (type-base type)) ")")))
   (else
    (type-id-value type))))

(define (c-array-length type)
  (if (memq 'result (type-array type))
      "sexp_unbox_fixnum(res)"
      "-1"))

(define (c-type-free? type)
  (or (type-free? type)
      (type-new? type)
      (and (type-result? type)
           (not (basic-type? type))
           (not (type-no-free? type)))))

(define (c->scheme-converter type val . o)
  (let ((base (type-base type)))
    (cond
     ((and (eq? base 'void) (not (type-pointer? type)))
      (cat "((" val "), SEXP_VOID)"))
     ((or (eq? base 'sexp) (error-type? type))
      (cat val))
     ((memq base '(bool boolean status-bool))
      (cat "sexp_make_boolean(" val ")"))
     ((eq? base 'time_t)
      (cat "sexp_make_integer(ctx, sexp_shift_epoch(" val "))"))
     ((unsigned-int-type? base)
      (cat "sexp_make_unsigned_integer(ctx, " val ")"))
     ((signed-int-type? base)
      (cat "sexp_make_integer(ctx, " val ")"))
     ((float-type? base)
      (cat "sexp_make_flonum(ctx, " val ")"))
     ((eq? base 'char)
      (if (type-array type)
          (cat "sexp_c_string(ctx, " val ", " (c-array-length type) ")")
          (cat "sexp_make_character(" val ")")))
     ((eq? 'env-string base)
      (cat "(p=strchr(" val ", '=') ? "
           "sexp_cons(ctx, str=sexp_c_string(ctx, " val
           ", p - " val "), str=sexp_c_string(ctx, p, -1))"
           " : sexp_cons(ctx, str=" val ", SEXP_FALSE)"))
     ((string-type? base)
      (if (and *c++?* (eq? 'string base))
          (cat "sexp_c_string(ctx, " val ".c_str(), " val ".size())")
          (cat "sexp_c_string(ctx, " val ", " (c-array-length type) ")")))
     ((memq base '(bytevector u8vector))
      (if *c++?*
          (cat "sexp_string_to_bytes(ctx, sexp_c_string(ctx, "
               val ".data(), " val ".size()))")
          (cat "sexp_string_to_bytes(ctx, sexp_c_string(ctx, " val ", "
               (c-array-length type) "))")))
     ((eq? 'input-port base)
      (cat "sexp_make_non_null_input_port(ctx, " val ", SEXP_FALSE)"))
     ((eq? 'output-port base)
      (cat "sexp_make_non_null_output_port(ctx, " val ", SEXP_FALSE)"))
     ((eq? 'input-output-port base)
      (cat "sexp_make_non_null_input_output_port(ctx, " val ", SEXP_FALSE)"))
     ((memq base '(fileno fileno-nonblock))
      (cat "sexp_make_fileno(ctx, sexp_make_fixnum(" val "), SEXP_FALSE)"))
     ((eq? base 'uvector)
      val)
     ((uniform-vector-type? base)
      (cat "sexp_make_cuvector(ctx, " (uniform-vector-type-code base) ", "
           val ", " (if (c-type-free? type) 1 0) ")"))
     (else
      (let ((ctype (lookup-type base))
            (void*? (void-pointer-type? type)))
        (cond
         ((or ctype void*?)
          (cat "sexp_make_cpointer(ctx, "
               (if void*?
                   "SEXP_CPOINTER"
                   ;;(string-append "sexp_type_tag(" (type-id-name base) ")")
                   (type-id-number type #t))
               ", "
               val ", " (or (and (pair? o) (car o)) "SEXP_FALSE") ", "
               (if (c-type-free? type) 1 0)
               ")"))
         (else
          (error "unknown type" base))))))))

(define (scheme->c-converter type val)
  (let* ((type (parse-type type))
         (base (type-base type)))
    (cond
     ((eq? base 'sexp)
      (cat val))
     ((memq base '(bool boolean status-bool))
      (cat "sexp_truep(" val ")"))
     ((eq? base 'time_t)
      (cat "sexp_unshift_epoch(sexp_uint_value(" val "))"))
     ((enum-type? base)
      => (lambda (x) (cat "((" (cdr x) ")sexp_sint_value(" val "))")))
     ((signed-int-type? base)
      (cat "sexp_sint_value(" val ")"))
     ((unsigned-int-type? base)
      (cat "sexp_uint_value(" val ")"))
     ((float-type? base)
      (cat "sexp_flonum_value(" val ")"))
     ((eq? base 'char)
      (cat "sexp_unbox_character(" val ")"))
     ((eq? base 'env-string)
      (cat "sexp_concat_env_string(" val ")"))
     ((string-type? base)
      (cat (if (type-null? type)
            "sexp_string_maybe_null_data"
            "sexp_string_data")
           "(" val ")"))
     ((memq base '(bytevector u8vector))
      (cat (if (type-null? type)
            "sexp_bytes_maybe_null_data"
            "sexp_bytes_data")
           "(" val ")"))
     ((eq? base 'port-or-fileno)
      (cat "(sexp_portp(" val ") ? sexp_port_fileno(" val ")"
           " : sexp_filenop(" val ") ? sexp_fileno_fd(" val ")"
           " : sexp_unbox_fixnum(" val "))"))
     ((port-type? base)
      (cat "sexp_port_stream(" val ")"))
     ((memq base '(fileno fileno-nonblock))
      (cat "(sexp_filenop(" val ") ? sexp_fileno_fd(" val ")"
           " : sexp_unbox_fixnum(" val "))"))
     ((uniform-vector-type? base)
      (cat "((" (uniform-vector-ctype base) ") sexp_uvector_data(" val "))"))
     (else
      (let ((ctype (lookup-type base))
            (void*? (void-pointer-type? type)))
        (cond
         ((or ctype void*?)
          (cat (if (or (type-struct? type) (type-reference? type)) "*" "")
               "(" (type-c-name type) ")"
               (if (type-address-of? type) "&" "")
               (if (type-null? type)
                   "sexp_cpointer_maybe_null_value"
                   "sexp_cpointer_value")
               "(" val ")"))
         (else
          (error "unknown type" base))))))))

(define (base-type-c-name base)
  (case base
    ((string env-string non-null-string bytevector u8vector)
     (if *c++?* "string" "char*"))
    ((fileno fileno-nonblock) "int")
    ((u1 u8 u16 u32 u64 s8 s16 s32 s64 f8 f16 f32 f64)
     (let ((a
            (uniform-vector-ctype
             (string->symbol
              (string-append (x->string base) "vector")))))
       (substring a 0 (- (string-length a) 1))))
    (else
     (if (uniform-vector-type? base)
         (uniform-vector-ctype base)
         (string-replace (symbol->string base) #\- " ")))))

(define (type-struct-type type)
  (let ((type-spec (lookup-type (if (vector? type) (type-base type) type))))
    (cond ((and type-spec (memq 'type: type-spec)) => cadr)
          (else #f))))

(define (type-c-name-derefed type)
  (let* ((type (parse-type type))
         (base (type-base type))
         (type-spec (lookup-type base))
         (struct-type (type-struct-type type)))
    (string-append
     (if (type-const? type) "const " "")
     (if (and struct-type (not *c++?*))
         (string-append (symbol->string struct-type) " ")
         "")
     (base-type-c-name base)
     (if (type-template type)
         (string-append
          "<"
          (string-concatenate (map type-c-name (type-template type)) ", ")
          ">")
         ""))))

(define (type-c-name type)
  (let ((type (parse-type type)))
    (if (type-as-sexp type)
        "sexp"
        (string-append
         (type-c-name-derefed type)
         (if (type-struct-type type) "*" "")
         (if (type-pointer? type) "*" "")))))

(define (type-finalizer-name type)
  (let ((name (type-c-name-derefed type)))
    (string-append "sexp_finalize_" (string-replace name #\: "_"))))

(define (check-type arg type)
  (let* ((type (parse-type type))
         (base (type-base type)))
    (cond
     ((eq? base 'env-string)
      (cat "(sexp_pairp(" arg ") && sexp_stringp(sexp_car(" arg
           ")) && sexp_stringp(sexp_cdr(" arg ")))"))
     ((memq base '(fileno fileno-nonblock))
      (cat "(sexp_filenop(" arg ") || sexp_fixnump(" arg "))"))
     ((string-type? base)
      (cat
       (if (type-null? type) "(" "")
       (type-predicate type) "(" arg ")"
       (lambda () (if (type-null? type) (cat " || sexp_not(" arg "))")))))
     ((or (eq? base 'char) (int-type? base) (float-type? base) (port-type? base)
          (memq base '(bytevector u8vector)) (uniform-vector-type? base))
      (cat (type-predicate type) "(" arg ")"))
     ((or (lookup-type base) (void-pointer-type? type))
      (cat
       (if (type-null? type) "(" "")
       "(sexp_pointerp(" arg  ")"
       " && (sexp_pointer_tag(" arg  ") == "
       (if (void-pointer-type? type)
           "SEXP_CPOINTER"
           (type-id-number type))
       "))"
       (lambda () (if (type-null? type) (cat " || sexp_not(" arg "))")))))
     (else
      (warn "don't know how to check" type)
      (cat "1")))))

(define (write-validator arg type)
  (let* ((type (parse-type type))
         (array (type-array type))
         (base-type (type-base type)))
    (cond
     ((and array (not (string-type? type)))
      (cond
       ((number? array)
        (cat "  if (!sexp_listp(ctx, " arg ")"
             "      || sexp_unbox_fixnum(sexp_length(ctx, " arg ")) != " array ")\n"
             "    return sexp_type_exception(ctx, self, SEXP_PAIR, " arg ");\n")))
      (cat "  for (res=" arg "; sexp_pairp(res); res=sexp_cdr(res))\n"
           "    if (! " (lambda () (check-type "sexp_car(res)" type)) ")\n"
           "      return sexp_xtype_exception(ctx, self, \"not a list of "
           (type-name type) "s\", " arg ");\n")
      (if (not (number? array))
          (cat "  if (! sexp_nullp(res))\n"
               "    return sexp_xtype_exception(ctx, self, \"not a list of "
               (type-name type) "s\", " arg ");\n")))
     ((eq? base-type 'port-or-fileno)
      (cat "  if (! (sexp_portp(" arg ") || sexp_filenop(" arg ") || sexp_fixnump(" arg ")))\n"
           "    return sexp_xtype_exception(ctx, self, \"not a port or file descriptor\"," arg ");\n"))
     ((or (int-type? base-type)
          (float-type? base-type)
          (string-type? base-type)
          (port-type? base-type)
          (uniform-vector-type? base-type)
          (memq base-type '(bytevector u8vector fileno fileno-nonblock))
          (and (not array) (eq? 'char base-type)))
      (cat
       "  if (! " (lambda () (check-type arg type)) ")\n"
       "    return sexp_type_exception(ctx, self, "
       (type-id-number type) ", " arg ");\n"))
     ((or (lookup-type base-type) (void-pointer-type? type))
      (cat
       "  if (! " (lambda () (check-type arg type)) ")\n"
       "    return sexp_type_exception(ctx, self, "
       (type-id-number type) ", " arg ");\n"))
     ((eq? 'sexp base-type))
     ((string-type? type)
      (write-validator arg 'string))
     ((memq base-type '(bool boolean status-bool)))
     (else
      (warn "don't know how to validate" type)))))

(define (write-parameters args)
  (lambda () (for-each (lambda (a) (cat ", sexp arg" (type-index a))) args)))

(define (write-c-parameters args)
  (lambda ()
    (let lp ((ls args) (i 0))
      (cond
       ((pair? ls)
        (if (> i 0)
            (cat ", "))
        (cat (type-c-name (car ls)))
        (cat " arg")
        (cat i)
        (lp (cdr ls) (+ i 1)))))))

(define (take ls n)
  (let lp ((ls ls) (n n) (res '()))
    (if (zero? n) (reverse res) (lp (cdr ls) (- n 1) (cons (car ls) res)))))

(define max-gc-vars 7)

(define (write-gc-vars ls . o)
  (let ((num-gc-vars (length ls)))
    (cond
     ((zero? num-gc-vars))
     ((<= num-gc-vars max-gc-vars)
      (cat "  sexp_gc_var" num-gc-vars "(")
      (display (car ls))
      (for-each (lambda (x) (display ", ") (display x)) (cdr ls))
      (cat ");\n"))
     (else
      (write-gc-vars (take ls max-gc-vars))
      (let lp ((ls (list-tail ls max-gc-vars))
               (i (+ max-gc-vars 1)))
        (cond
         ((pair? ls)
          (cat "  sexp_gc_var(" (car ls) ", __sexp_gc_preserver" i ");\n")
          (lp (cdr ls) (+ i 1)))))))))

(define (write-gc-preserves ls)
  (let ((num-gc-vars (length ls)))
    (cond
     ((zero? num-gc-vars))
     ((<= num-gc-vars max-gc-vars)
      (cat "  sexp_gc_preserve" num-gc-vars "(ctx")
      (for-each (lambda (x) (display ", ") (display x)) ls)
      (cat ");\n"))
     (else
      (write-gc-preserves (take ls max-gc-vars))
      (let lp ((ls (list-tail ls max-gc-vars))
               (i (+ max-gc-vars 1)))
        (cond
         ((pair? ls)
          (cat "  sexp_gc_preserve(ctx, " (car ls)
               ", __sexp_gc_preserver" i ");\n")
          (lp (cdr ls) (+ i 1)))))))))

(define (write-gc-release ls)
  (if (pair? ls)
      (cat "  sexp_gc_release" (min max-gc-vars (length ls)) "(ctx);\n")))

(define (get-array-length func x)
  (let ((len (if (pair? (type-array x))
                 (car (reverse (type-array x)))
                 (type-array x))))
    (cond
     ((number? len)
      len)
     (else
      (and func
           (symbol? len)
           (let* ((str (symbol->string len))
                  (len2 (string-length str)))
             (and (> len2 3)
                  (string=? "arg" (substring str 0 3))
                  (let ((i (string->number (substring str 3 len2))))
                    (if i
                        (let ((y (list-ref (func-c-args func) i)))
                          (or (type-value y) len)))))))))))

(define (write-locals func)
  (define (arg-res x)
    (string-append "res" (type-index-string x)))
  (let* ((ret-type (func-ret-type func))
         (results (func-results func))
         (scheme-args (func-scheme-args func))
         (return-res? (not (error-type? ret-type)))
         (preserve-res? (> (+ (length results)) (if return-res? 0 1)))
         (single-res? (and (= 1 (length results)) (not return-res?)))
         (tmp-string? (any (lambda (a)
                             (and (type-array a)
                                  (string-type? (type-base a))))
                           (cons ret-type results)))
         (gc-vars (map arg-res results))
         (gc-vars (if tmp-string? (cons "str" gc-vars) gc-vars))
         (gc-vars (if preserve-res? (cons "res" gc-vars) gc-vars))
         (sexps (if preserve-res? '() '("res")))
         (ints (if (or return-res?
                       (memq (type-base ret-type)
                             '(status-bool non-null-string non-null-pointer)))
                   '()
                   '("err")))
         (ints (if (or (array-type? ret-type)
                       (any array-type? results)
                       (any array-type? scheme-args))
                   (cons "i" ints)
                   ints)))
    (case (type-base ret-type)
      ((status-bool) (cat "  bool err;\n"))
      ((non-null-string) (cat "  char *err;\n"))
      ((non-null-pointer) (cat "  void *err;\n")))
    (if (type-struct? ret-type)
        (cat "  struct " (type-base ret-type) " struct_res;\n"
             "  struct " (type-base ret-type) "* ptr_res;\n"))
    (cond
     ((pair? ints)
      (cat "  int " (car ints) " = 0"
           (lambda ()
             (for-each (lambda (x) (cat ", " x " = 0")) (cdr ints)))
           ";\n")))
    (if (any (lambda (a) (eq? 'env-string (type-base a)))
             (cons ret-type results))
        (cat "  char *p;\n"))
    (for-each
     (lambda (x)
       (let ((len (get-array-length func x)))
         (cat "  " (if (type-const? x) "const " "")
              (type-c-name (type-base x)) " ")
         (if (or (and (type-array x) (not (number? len))) (type-pointer? x))
             (cat "*"))
         (cat (if (type-auto-expand? x) "buf" "tmp") (type-index-string x))
         (if (number? len)
             (cat "[" len "]"))
         (cond
          ((type-reference? x)
           (cat " = NULL"))
          ((type-error x)
           (cat " = 0")))
         (cat ";\n")
         (if (or (vector? len) (type-auto-expand? x))
             (cat "  int len" (type-index x) ";\n"))
         (if (type-auto-expand? x)
             (cat "  " (type-c-name (type-base x))
                  " *tmp" (type-index-string x) ";\n"))))
     (append (if (or (type-array ret-type) (type-pointer? ret-type))
                 (list ret-type)
                 '())
             results
             (remove type-result? (filter type-array scheme-args))))
    (for-each
     (lambda (arg)
       (cond
        ((and (type-pointer? arg) (basic-type? arg))
         (cat "  " (if (type-const? arg) "const " "")
              (type-c-name (type-base arg))
              " tmp" (type-index arg) ";\n"))))
     scheme-args)
    (cond
     ((pair? sexps)
      (cat "  sexp " (car sexps))
      (for-each (lambda (x) (display ", ") (display x)) (cdr sexps))
      (cat ";\n")))
    ;; Declare the gc vars.
    (write-gc-vars gc-vars)
    ;; Shortcut returns should come before preserving.
    (write-validators (func-scheme-args func))
    (write-additional-checks (func-c-args func))
    (write-assertions func (func-assert func))
    ;; Preserve the gc vars.
    (write-gc-preserves gc-vars)))

(define (write-validators args)
  (for-each
   (lambda (a)
     (write-validator (string-append "arg" (type-index-string a)) a))
   args))

(define (write-additional-checks args)
  (for-each
   (lambda (a)
     (if (port-type? (type-base a))
         (cat "  if (!sexp_stream_portp(arg" (type-index a) "))\n"
              "    return sexp_xtype_exception(ctx, self,"
              " \"not a FILE* backed port\", arg" (type-index a) ");\n")))
   args)
  (for-each
   (lambda (a)
     (if (eq? 'input-port (type-base a))
         (cat "  sexp_check_block_port(ctx, arg" (type-index a) ", 0);\n")))
   args))

(define (string-has-prefix? str prefix)
  (let ((prefix-len (string-length prefix)))
    (and (>= (string-length str) prefix-len)
         (equal? (substring str 0 prefix-len) prefix))))

(define (write-scheme->c expr . o)
  (define (write-numeric-arg x)
    (if (symbol? x)
        (let ((func (and (pair? o) (car o)))
              (sym-name (symbol->string x)))
          (if (and func
                   (string-has-prefix? sym-name "arg")
                   (unsigned-int-type?
                    (type-base
                     (list-ref (func-c-args func)
                               (string->number (substring sym-name 3))))))
              (cat "sexp_uint_value(" x ")")
              (cat "sexp_sint_value(" x ")")))
        (write-scheme->c x)))
  (if (pair? expr)
      (case (car expr)
        ((+ - * / %)
         (let ((expr (if (and (null? (cddr expr)) (memq (car expr) '(- /)))
                         `(,(car expr)
                           ,(if (eq? '- (car expr)) 0 1)
                           ,@(cdr expr))
                         expr)))
           (write-scheme->c (cadr expr))
           (for-each
            (lambda (x)
              (cat " " (car expr) " ")
              (write-numeric-arg x))
            (cddr expr))))
        ((< <= == != >= >)
         (let lp ((ls (cdr expr)))
           (cat "(" (lambda () (write-numeric-arg (car ls))) " " (car expr)
                " " (lambda () (write-numeric-arg (cadr ls)))
                ")")
           (cond
            ((pair? (cddr ls))
             (display " && ")
             (lp (cdr ls))))))
        ((expt)
         (if (and (integer? (cadr expr))
                  (integer? (car (cddr expr))))
             (write-scheme->c (expt (cadr expr) (car (cddr expr))))
             (cat "pow("
                  (lambda () (write-numeric-arg (cadr expr)))
                  ", "
                  (lambda () (write-numeric-arg (car (cddr expr))))
                  ")")))
        (else
         (write (scheme-procedure->c (car expr)))
         (display "(")
         (cond
          ((pair? (cdr expr))
           (write-scheme->c (cadr expr))
           (for-each
            (lambda (x) (cat ", " (lambda () (write-scheme->c x))))
            (cddr expr))))
         (display ")")))
      (write expr)))

(define (extract-irritants expr)
  (reverse
   (let lp ((args (cdr expr))
            (irr '()))
     (cond
      ((null? args) irr)
      ((identifier? (car args)) (lp (cdr args) (cons (car args) irr)))
      ((pair? (car args))
       ;; pass length rather than vector objects to avoid huge error messages
       (if (and (memq (caar args)
                      '(length bytevector-length u8vector-length uvector-length))
                (pair? (cdar args))
                (identifier? (car (cdar args))))
           (lp (cdr args)
               ;; sexp_length returns sexp, the others int
               (cons (if (eq? 'length (caar args))
                         (car args)
                         (list 'sexp_make_fixnum (car args)))
                     irr))
           (lp (cdr args) (lp (cdar args) irr))))
      (else (lp (cdr args) irr))))))

(define (write-assertions func asserts)
  (for-each
   (lambda (assert)
     (let ((irr-ls (extract-irritants assert)))
       (cat "  if (!(" (lambda () (write-scheme->c assert func)) ")) {\n"
            "    return sexp_user_exception_ls(ctx, self, \"assertion failed: \" "
            (call-with-output-string
              (lambda (out)
                (write (call-with-output-string
                         (lambda (out) (write assert out))) out)))
            ", " (length irr-ls)
            (lambda ()
              (for-each
               (lambda (irr)
                 (cat ", ")
                 (write-scheme->c irr))
               irr-ls))
            ");\n"
            "  }\n")))
   asserts))

(define (scheme-procedure->c name)
  (cond
   ((eq? name 'length) 'sexp_length_unboxed)
   ((eq? name 'string-length) 'sexp_string_length)
   ((eq? name 'string-size) 'sexp_string_size)
   ((memq name '(bytevector-length u8vector-length)) 'sexp_bytes_length)
   ((eq? name 'uvector-length) 'sexp_uvector_length)
   ((eq? name 'immutable?) 'sexp_immutablep)
   ((eq? name 'mutable?) 'sexp_mutablep)
   (else name)))

(define (write-value func val)
  (cond
   ((find (lambda (x)
            (and (type-array x)
                 (type-auto-expand? x)
                 (eq? val (get-array-length func x))))
          (func-c-args func))
    => (lambda (x) (cat "len" (type-index x))))
   ((lookup-type val)
    (cat (or (type-struct-type val) "") " " val))
   ((and (pair? val) (list? val))
    (write (scheme-procedure->c (car val)))
    (cat
     "("
     (lambda ()
       (cond
        ((pair? (cdr val))
         (write-value func (cadr val))
         (for-each (lambda (x) (display ", ") (write-value func x)) (cddr val)))))
     ")"))
   (else
    (write val))))

(define (write-actual-parameter func arg)
  (cond
   ((or (type-result? arg) (type-array arg))
    (cat (if (or (type-free? arg) (type-reference? arg)
                 (type-address-of? arg) (basic-type? arg)
                 ;; a non-pointer, non-basic result needs indirection
                 (and (type-result? arg) (not (type-pointer? arg))
                      (not (type-struct-type arg)) (not (basic-type? arg))
                      (not (type-array arg))))
             "&"
             "")
         "tmp" (type-index arg)))
   ((and (not (type-default? arg)) (type-value arg))
    => (lambda (x) (write-value func x)))
   ((and (type-pointer? arg) (basic-type? arg))
    (cat "&tmp" (type-index arg)))
   (else
    (scheme->c-converter
     arg
     (string-append "arg" (type-index-string arg))))))

(define (write-temporaries func)
  (for-each
   (lambda (a)
     (let ((len (and (type-array a) (get-array-length func a))))
       (cond
        ((and (type-array a) (or (vector? len) (type-auto-expand? a)))
         (cat "  len" (type-index a) " = "
              (lambda ()
                (if (number? len) (cat len) (scheme->c-converter 'int len)))
              ";\n"
              "  tmp" (type-index a) " = buf" (type-index a) ";\n")))
       (cond
        ((and (not (type-result? a)) (type-array a) (not (string-type? a)))
         (if (not (number? (type-array a)))
             (if (and *c++?* (type-new? a))
                 (cat "  tmp" (type-index a)
                      " = new " (type-c-name-derefed (type-base a)) "();\n")
                 (cat "  tmp" (type-index a)
                      " = (" (if (type-const? a) "const " "")
                      (type-c-name (type-base a)) "*) "
                      "calloc((sexp_unbox_fixnum(sexp_length(ctx, arg"
                      (type-index a)
                      "))+1), sizeof(tmp" (type-index a) "[0]));\n")))
         (cat "  for (i=0, res=arg" (type-index a)
              "; sexp_pairp(res); res=sexp_cdr(res), i++) {\n"
              "    tmp" (type-index a) "[i] = "
              (lambda () (scheme->c-converter (type-base a) "sexp_car(res)"))
              ";\n"
              "  }\n")
         (if (not (number? (type-array a)))
             (cat "  tmp" (type-index a) "[i] = 0;\n")))
        ((and (type-result? a) (not (basic-type? a))
              (not (type-free? a)) ;;(not (type-pointer? a))
              (not (type-reference? a))
              (not (type-auto-expand? a))
              (or (not (type-array a))
                  (not (integer? len))))
         (if (and *c++?* (type-new? a))
             (cat "  tmp" (type-index a)
                  " = new " (type-c-name-derefed (type-base a)) "();\n")
             (cat "  tmp" (type-index a) " = "
                  (lambda () (cat "(" (type-c-name (type-base a))
                              (if (or (type-pointer? a)
                                      (and (not (int-type? a))
                                           (not (type-struct-type a))))
                                  "*"
                                  "")
                              ")"))
                  " calloc(1, 1 + "
                  (if (and (symbol? len) (not (eq? len 'null)))
                      (lambda () (cat (lambda () (scheme->c-converter 'unsigned-int len))
                                  "*sizeof(tmp" (type-index a) "[0])"))
                      (lambda () (cat "sizeof(tmp" (type-index a) "[0])")))
                  ");\n"
                  ;; (lambda ()
                  ;;   (if (and (symbol? len) (not (eq? len 'null)))
                  ;;       (cat "  tmp" (type-index a) "["
                  ;;            (lambda () (scheme->c-converter 'unsigned-int len))
                  ;;            "*sizeof(tmp" (type-index a) "[0])] = 0;\n")))
                  )))
        ((and (type-result? a) (type-value a))
         (cat "  tmp" (type-index a) " = "
              (lambda () (write-value func (type-value a))) ";\n"))
        ((and (type-pointer? a) (basic-type? a))
         (cat "  tmp" (type-index a) " = "
              (lambda ()
                (scheme->c-converter
                 a
                 (string-append "arg" (type-index-string a))))
              ";\n")))))
   (func-c-args func)))

(define (write-call func)
  (let ((ret-type (func-ret-type func))
        (c-name (func-c-name func))
        (c-args (func-c-args func))
        (link (find type-link? (func-c-args func))))
    (if (any type-auto-expand? (func-c-args func))
        (cat " loop:\n"))
    (cat (cond ;;((void-type? ret-type) "")
               ((error-type? ret-type) "  err = ")
               ((type-array ret-type) "  tmp = ")
               ((type-struct? ret-type) "  struct_res = ")
               (else "  res = ")))
    ((if (or (type-array ret-type)
             (type-struct? ret-type))
         (lambda (t f x) (f))
         c->scheme-converter)
     ret-type
     (lambda ()
       (if (and *c++?* (type-new? ret-type))
           (cat "new "))
       (if (func-method? func)
           (cat "(" (lambda () (write-actual-parameter func (car c-args)))
                ")->" c-name)
           (cat c-name))
       (cat "(")
       (for-each
        (lambda (arg)
          (if (> (type-index arg) (if (func-method? func) 1 0)) (cat ", "))
          (write-actual-parameter func arg))
        (if (func-method? func) (cdr c-args) c-args))
       (cat ")"))
     (and link (string-append "arg" (type-index-string link))))
    (cat ";\n")
    ;; (if (and *c++?* (type-new? ret-type) link)
    ;;     (cat "  sexp_preserve_object(ctx, arg" (type-index-string link) ");\n"))
    (if (type-array ret-type)
        (write-result ret-type)
        (write-result-adjustment ret-type))))

(define (write-result-adjustment result)
  (cond
   ;; new port results are automatically made non-blocking
   ((memq (type-base result) '(input-port output-port input-output-port))
    (let ((res (string-append "res" (type-index-string result))))
      (cat "#ifdef SEXP_USE_GREEN_THREADS\n"
           "  if (sexp_portp(" res "))\n"
           "    fcntl(fileno(sexp_port_stream(" res ")), F_SETFL, O_NONBLOCK "
           " | fcntl(fileno(sexp_port_stream(" res ")), F_GETFL));\n"
           "#endif\n")))
   ;; a file descriptor result can be automatically made non-blocking
   ;; by specifying a result type of fileno-nonblock
   ((memq (type-base result) '(fileno-nonblock))
    (let ((res (string-append "res" (type-index-string result))))
      (cat "#ifdef SEXP_USE_GREEN_THREADS\n"
           "  if (sexp_filenop(" res "))\n"
           "    fcntl(sexp_fileno_fd(" res "), F_SETFL, O_NONBLOCK "
           " | fcntl(sexp_fileno_fd(" res "), F_GETFL));\n"
           "#endif\n")))
   ;; non-pointer struct return types need to be copied to the heap
   ((type-struct? result)
    (cat
     "  ptr_res = (" (type-c-name result) ") malloc(sizeof("
     (type-c-name-derefed result) "));\n"
     "  memcpy(ptr_res, &struct_res, sizeof(" (type-c-name-derefed result) "));\n"
     "  res = sexp_make_cpointer(ctx, sexp_unbox_fixnum(sexp_opcode_return_type(self)), ptr_res, SEXP_FALSE, 0);\n"))
   ))

(define (write-result result . o)
  (let ((res (string-append "res" (type-index-string result)))
        (tmp (string-append "tmp" (type-index-string result))))
    (cond
     ((and (type-array result) (eq? 'char (type-base result)))
      (cat "  " res " = " (lambda () (c->scheme-converter result tmp)) ";\n"))
     ((type-array result)
      (cat "  " res " = SEXP_NULL;\n")
      (let ((auto-expand?
             (and (pair? (type-array result))
                  (memq 'auto-expand (type-array result))))
            (len (if (pair? (type-array result))
                     (car (reverse (type-array result)))
                     (type-array result))))
        (cond
         ((eq? 'null len)
          (cat "  for (i=0; " tmp "[i]; i++) {\n"
               "    sexp_push(ctx, " res ", "
               (if (eq? 'string (type-base result))
                   "str="
                   (lambda () (cat "SEXP_VOID);\n    sexp_car(" res ") = ")))
               (lambda () (c->scheme-converter result (lambda () (cat tmp "[i]"))))
               ");\n"
               "  }\n"
               "  " res " = sexp_nreverse(ctx, " res ");\n"))
         (else
          (cat "  for (i=" (if (and (symbol? len)
                                    (equal? "arg"
                                            (substring (symbol->string len)
                                                        0 3)))
                               (string-append
                                "sexp_unbox_fixnum(" (symbol->string len) ")")
                               len)
               "-1; i>=0; i--) {\n"
               "    sexp_push(ctx, " res ", SEXP_VOID);\n"
               "    sexp_car(" res ") = "
               (lambda () (c->scheme-converter result (lambda () (cat tmp "[i]"))))
               ";\n"
               "  }\n")))))
     (else
      (cat "  " res " = ")
      (apply
       c->scheme-converter
       result
       (string-append "tmp" (type-index-string result))
       o)
      (cat ";\n")))
    (write-result-adjustment result)))

(define (write-results func)
  (let* ((error-res (cond ((error-type? (func-ret-type func))
                           (func-ret-type func))
                          ((find type-error (func-c-args func)))
                          (else #f)))
         (error-return? (eq? error-res (func-ret-type func)))
         (void-res? (eq? 'void (type-base (func-ret-type func))))
         (results (remove type-error (func-results func))))
    (if error-res
        (cat "  if ("
             (if (memq (type-base error-res)
                       '(status-bool non-null-string non-null-pointer))
                 "!"
                 "")
             (if error-return?
                 "err"
                 (string-append "tmp" (type-index-string error-res)))
             ") {\n"
             (cond
              ((find type-auto-expand? (func-c-args func))
               => (lambda (a)
                    (lambda ()
                      (let ((len (get-array-length func a))
                            (i (type-index a)))
                        (cat "  if (len" i " > " auto-expand-limit ") {\n"
                             "    res = sexp_user_exception(ctx, self, "
                             "\"exceeded max auto-expand len in " (func-scheme-name func) "\", SEXP_NULL);\n"
                             "} else {\n")
                        (if (number? len)
                            (cat "  if (len" i " != " len ")\n"
                                 "    free(tmp" i ");\n"))
                        (cat "  len" i " *= 2;\n"
                             "  tmp" i " = "
                             (lambda () (cat "(" (type-c-name (type-base a))
                                         (if (or (type-pointer? a)
                                                 (and (not *c++?*)
                                                      (string-type? a)))
                                             "*"
                                             "")
                                         ")"))
                             " calloc(len" i ", sizeof(tmp" i "[0]));\n"
                             "  goto loop;\n"
                             "}\n")))))
              (error-return?
               ;; TODO: free other results
               "  res = SEXP_FALSE;\n")
              (else
               (lambda ()
                 (cat "  res = sexp_user_exception(ctx, self, "
                      (type-error error-res) "(tmp"
                      (type-index-string error-res)
                      "), SEXP_NULL);\n"))))
             "  } else {\n"))
    (if (null? results)
        (if (and error-res error-return?)
            (cat "  res = SEXP_TRUE;\n"))
        (let ((first-result-link
               ;; the `link' modifier applies to the first result when
               ;; there are multiple results
               (and
                (not (lookup-type (func-ret-type func)))
                (cond
                 ((find type-link? (func-c-args func))
                  => (lambda (a) (string-append "arg" (type-index-string a))))
                 (else #f)))))
          (write-result (car results) first-result-link)
          (for-each write-result (cdr results))))
    (cond
     ((> (length results) (if (or error-res void-res?) 1 0))
      (if (or error-res void-res?)
          (cat "  res = SEXP_NULL;\n")
          (cat "  res = sexp_cons(ctx, res, SEXP_NULL);\n"))
      (for-each
       (lambda (x)
         (if (or error-res void-res?)
             (cat "  sexp_push(ctx, res, res" (type-index x) ");\n")
             (cat "  sexp_push(ctx, res, sexp_car(res));\n"
                  "  sexp_cadr(res) = res" (type-index x) ";\n")))
       (reverse results)))
     ((pair? results)
      (cat "  res = res" (type-index (car results)) ";\n")))
    (if error-res
        (cat "  }\n"))))

(define (write-free type)
  (if (and (type-array type) (not (number? (type-array type))))
      (cat "  free(tmp" (type-index-string type) ");\n")))

(define (write-cleanup func)
  (for-each write-free (func-scheme-args func))
  (for-each
   (lambda (a)
     (cond
      ((type-auto-expand? a)
       (let ((len (get-array-length func a))
             (i (type-index a)))
         (if (number? len)
             (cat "  if (len" i " != " len ")\n"
                  "    free(tmp" i ");\n"))))
      ((memq (type-base a) '(input-port input-output-port))
       (cat "  sexp_maybe_unblock_port(ctx, arg" (type-index a) ");\n"))
      ((and (type-result? a) (not (basic-type? a))
            (not (lookup-type (type-base a)))
            (not (type-free? a)) (not (type-pointer? a))
            (or (not (type-array a))
                (not (integer? (get-array-length func a)))))
       ;; the above is hairy - basically this frees temporary strings
       (cat "  free(tmp" (type-index a) ");\n"))
      ((and (type-move? a) (or (type-pointer? a) (not (basic-type? a))))
       (cat "  sexp_cpointer_value(arg" (type-index a) ") = NULL;  /* moved */\n"
            "  sexp_cpointer_freep(arg" (type-index a) ") = 0;\n"))))
   (func-c-args func))
  (let* ((results (func-results func))
         (return-res? (not (error-type? (func-ret-type func))))
         (preserve-res? (> (+ (length results)) (if return-res? 0 1)))
         (single-res? (and (= 1 (length results)) (not return-res?)))
         (tmp-string? (any (lambda (a)
                             (and (type-array a)
                                  (string-type? (type-base a))))
                           (cons (func-ret-type func)
                                 (func-results func))))
         (gc-vars results)
         (gc-vars (if tmp-string? (cons "str" gc-vars) gc-vars))
         (gc-vars (if preserve-res? (cons "res" gc-vars) gc-vars)))
    (write-gc-release gc-vars)))

(define (write-func-declaration func)
  (cat "sexp " (func-stub-name func)
       " (sexp ctx, sexp self, sexp_sint_t n"
       (write-parameters (func-scheme-args func)) ")"))

(define (write-func func)
  (cond
   ((func-inline func)
    (cat "static "(type-c-name (func-ret-type func)) " " (func-c-name func)
         "(" (write-c-parameters (func-c-args func)) ") {\n"
         "   " (if (void-type? (func-ret-type func)) "" "return ")
         (func-inline func) ";\n"
         "}\n")))
  (write-func-declaration func)
  (cat " {\n")
  (write-locals func)
  (write-temporaries func)
  (write-call func)
  (write-results func)
  (write-cleanup func)
  (cat "  return res;\n"
       "}\n\n"))

(define (adjust-method-name! func i)
  (func-stub-name-set!
   func
   (string-append (func-stub-name func) "__" (number->string i))))

(define (write-primitive-call func args)
  (cat (func-stub-name func)
       "(" (lambda () (join (append '(ctx self n) args) ", ")) ")"))

(define (write-fixed-arity-method meth)
  (define (write-dispatch func)
    (write-primitive-call
     func
     (map (lambda (a) (string-append "arg" (type-index-string a)))
          (func-scheme-args func))))
  (define (write-method-validators func)
    (cond
     ((not (pair? (cdr (func-scheme-args func))))
      (warn "no arguments to distinguish" func)
      (cat "1"))
     (else
      (let lp ((ls (cdr (func-scheme-args func))))
        (check-type (string-append "arg" (type-index-string (car ls))) (car ls))
        (cond
         ((pair? (cdr ls))
          (cat " && ")
          (lp (cdr ls))))))))
  (case (length meth)
    ((0 1)
     (error "invalid method" meth))
    ((2)
     (write-func (cadr meth)))
    (else
     (let ((orig-stub-name (func-stub-name (cadr meth))))
       (do ((ls (cdr meth) (cdr ls)) (i 0 (+ i 1)))
           ((null? ls))
         (adjust-method-name! (car ls) i)
         (write-func (car ls)))
       (let ((new-stub-name (func-stub-name (cadr meth))))
         (func-stub-name-set! (cadr meth) orig-stub-name)
         (write-func-declaration (cadr meth))
         (func-stub-name-set! (cadr meth) new-stub-name)
         (cat " {\n"
              "  sexp orig_self = self;\n")
         (write-validator "arg0" (car (func-scheme-args (cadr meth))))
         (let lp ((ls (cdr meth)) (i 0))
           (cat "  self = sexp_vector_ref(sexp_opcode_methods(orig_self), "
                (make-integer i) ");\n")
           (cond
            ((null? (cdr ls))
             (cat "  return " (lambda () (write-dispatch (car ls))) ";\n"))
            (else
             (cat "  if ("
                  (lambda () (write-method-validators (car ls))) ") {\n"
                  "    return " (lambda () (write-dispatch (car ls))) ";\n"
                  "  }\n" (lambda () (lp (cdr ls) (+ i 1)))))))
         (cat "}\n\n")
         (func-stub-name-set! (cadr meth) orig-stub-name))))))

(define (write-method meth)
  (let ((args (map func-scheme-args (cdr meth))))
    (if (and (> (length args) 1)
             (not (apply = (map length args))))
        (error "methods must have the same arity")))
  (write-fixed-arity-method meth))

(define (parameter-default? x)
  (and (pair? x)
       (member x '((current-input-port)
                   (current-output-port)
                   (current-error-port)))))

(define (write-default x) ;; this is a hack but very convenient
  (lambda ()
    (let ((value (type-value x)))
      (cond
       ((equal? value '(current-input-port))
        (cat "\"current-input-port\""))
       ((equal? value '(current-output-port))
        (cat "\"current-output-port\""))
       ((equal? value '(current-error-port))
        (cat "\"current-error-port\""))
       ((equal? value '(native-endianness))
        (cat "sexp_global(ctx, SEXP_G_ENDIANNESS)"))
       ((equal? value 'NULL)
        (cat "SEXP_FALSE"))
       (else
        (c->scheme-converter x value))))))

(define (write-func-creation var func . o)
  (let ((default (and (pair? (func-scheme-args func))
                      (type-default? (car (reverse (func-scheme-args func))))
                      (car (reverse (func-scheme-args func)))))
        (no-bind? (and (pair? o) (car o))))
    (cat "  " var " = "
         (cond
          (no-bind?
           "sexp_make_foreign(ctx, ")
          ((not default)
           "sexp_define_foreign(ctx, env, ")
          ((parameter-default? (type-value default))
           "sexp_define_foreign_param(ctx, env, ")
          (else
           "sexp_define_foreign_opt(ctx, env, "))
         (lambda () (write (symbol->string (func-scheme-name func))))
         ", " (length (func-scheme-args func))  ", "
         (if no-bind?
             (lambda ()
               (cat (cond ((not default) 0)
                          ((parameter-default? (type-value default)) 3)
                          (else 1))
                    ", \"" (func-stub-name func) "\", "))
             "")
         (func-stub-name func)
         (cond
          (default (lambda () (cat ", " (write-default default))))
          (no-bind? ", SEXP_VOID")
          (else ""))
         ");\n")))

(define (write-func-types var func)
  (cond
   ((or (not (eq? 'sexp (type-base (func-ret-type func))))
        (and (pair? (func-c-args func))
             (any (lambda (a) (not (eq? 'sexp (type-base a))))
                  (func-c-args func))))
    (cat
     "  if (sexp_opcodep(" var ")) {\n"
     "    sexp_opcode_return_type(" var ") = "
     (type-id-init-value (func-ret-type func)) ";\n"
     (lambda ()
       (let lp ((ls (func-c-args func))
                (i 1)
                (vec? #f))
         (cond
          ((null? ls))
          ;; ((type-value (car ls))
          ;;  (lp (cdr ls) i))
          ((eq? 'sexp (type-base (car ls)))
           (lp (cdr ls) (+ i 1) vec?))
          ((<= i 3)
           (cat "    sexp_opcode_arg" i "_type(" var ") = "
                (type-id-init-value (car ls)) ";\n")
           (lp (cdr ls) (+ i 1) vec?))
          (else
           (if (not vec?)
               (cat "    sexp_opcode_argn_type(" var ") = "
                    "sexp_make_vector(ctx, "
                    (make-integer (- (length (func-c-args func)) 3)) ", "
                    (make-integer "SEXP_OBJECT") ");\n"))
           (cat "    sexp_vector_set(sexp_opcode_argn_type(" var "), "
                (make-integer (- i 4)) ", "
                (type-id-init-value (car ls)) ");\n")
           (lp (cdr ls) (+ i 1) #t)))))
     ;; "  } else {\n"
     ;; "    sexp_warn(ctx, \"couldn't generated opcode\", " var ");\n"
     "  }\n")))
  (cond
   ((assq (func-scheme-name func) *type-getters*)
    => (lambda (x)
         (let ((name (cadr x))
               (i (car (cddr x))))
           (cat "  if (sexp_vectorp(sexp_type_getters(" (type-id-name name)
                "))) sexp_vector_set(sexp_type_getters("
                (type-id-name name) "), "
                (make-integer i) ", " var ");\n"))))
   ((assq (func-scheme-name func) *type-setters*)
    => (lambda (x)
         (let ((name (cadr x))
               (i (car (cddr x))))
           (cat "  if (sexp_vectorp(sexp_type_setters(" (type-id-name name)
                "))) sexp_vector_set(sexp_type_setters("
                (type-id-name name) "), "
                (make-integer i) ", " var ");\n"))))))

(define (write-func-binding func . o)
  (let ((var (if (pair? o) (car o) "op")))
    (write-func-creation var func)
    (write-func-types var func)))

(define (write-method-binding meth)
  (write-func-binding (cadr meth))
  (cat "  if (sexp_opcodep(op)) {\n"
       (lambda ()
         (cat "    sexp_opcode_methods(op) = "
              "sexp_make_vector(ctx, " (make-integer (length (cdr meth)))
              ", SEXP_VOID);\n")
         (do ((ls (cdr meth) (cdr ls)) (i 0 (+ i 1)))
             ((null? ls))
           (let ((var (string-append
                       "sexp_vector_ref(sexp_opcode_methods(op), "
                       (make-integer i) ")")))
             (write-func-creation var (car ls) #t)
             (write-func-types var (car ls)))))
       "  }\n"))

(define (write-type orig-type)
  (let* ((name (car orig-type))
         (scheme-name (strip-namespace (type-name name)))
         (type (cdr orig-type))
         (imported? (cond ((member 'imported?: type) => cadr) (else #f))))
    (cond
     (imported?
      (cat "  name = sexp_intern(ctx, \"" scheme-name "\", -1);\n"
           "  " (type-id-name name) " = sexp_env_ref(ctx, env, name, SEXP_FALSE);\n"
           "  if (sexp_not(" (type-id-name name) ")) {\n"
           "    sexp_warn(ctx, \"couldn't import declared type: \", name);\n"
           "  }\n"))
     (else
      (cat "  name = sexp_c_string(ctx, \"" scheme-name "\", -1);\n"
           "  " (type-id-name name)
           " = sexp_register_c_type(ctx, name, "
           (cond ((or (memq 'finalizer: type)
                      (memq 'finalizer-method: type))
                  => (lambda (x)
                       (let ((name (cadr x)))
                         (if name
                             (generate-stub-name
                              (if (pair? name) (car name) name))
                             'NULL))))
                 (*c++?*
                  (type-finalizer-name name))
                 (else
                  "sexp_finalize_c_type"))
           ");\n"
           "  tmp = sexp_string_to_symbol(ctx, name);\n"
           "  sexp_env_define(ctx, env, tmp, " (type-id-name name) ");\n")
      (if (pair? (struct-fields type))
          (let ((len (make-integer (length (struct-fields type)))))
            (cat "  sexp_type_slots(" (type-id-name name) ") = SEXP_NULL;\n"
                 (lambda ()
                   (do ((ls (reverse (struct-fields type)) (cdr ls)))
                       ((not (pair? ls)))
                     (cat "  sexp_push(ctx, sexp_type_slots("
                          (type-id-name name) "), "
                          "sexp_intern(ctx, "
                          (lambda () (write (x->string (cadr (car ls)))))
                          ", -1));\n")))
                 "  sexp_type_getters(" (type-id-name name) ")"
                 " = sexp_make_vector(ctx, " len ", SEXP_FALSE);\n"
                 "  sexp_type_setters(" (type-id-name name) ")"
                 " = sexp_make_vector(ctx, " len ", SEXP_FALSE);\n")))
      (cond
       ((memq 'predicate: type)
        => (lambda (x)
             (let ((pred (cadr x)))
               (cat "  tmp = sexp_make_type_predicate(ctx, name, "
                    (type-id-name name) ");\n"
                    "  name = sexp_intern(ctx, \"" pred "\", "
                    (string-length (x->string pred)) ");\n"
                    "  sexp_env_define(ctx, env, name, tmp);\n")))))))))

(define (type-getter-name type name field)
  (let ((c-name (if (pair? (cadr field)) (cadr (cadr field)) (cadr field))))
    (string-replace
     (string-append "sexp_" (x->string (type-name (parse-type name)))
                    "_get_" (x->string c-name))
     #\: "_")))

(define (verify-accessor field)
  (if (and (pair? field)
           (not (and (= 3 (length field))
                     (memq (cadr field) '(function: method:)))))
      (error "accessor should be a single symbol or (scheme-name function:|method: c-name) but got" field)))

(define (write-type-getter type name field)
  (let* ((get (car (cddr field)))
         (_ (verify-accessor get))
         (c-name (if (pair? (cadr field)) (cadr (cadr field)) (cadr field)))
         (ptr (string-append
               "((" (x->string (or (type-struct-type name) ""))
               " " (x->string name) "*)"
               "sexp_cpointer_value(x))")))
    (cat "sexp " (type-getter-name type name field)
         " (sexp ctx, sexp self, sexp_sint_t n, sexp x) {\n"
         (lambda () (write-validator "x" (parse-type name 0)))
         "  return "
         (lambda ()
           (c->scheme-converter
            (car field)
            (cond
             ((and (pair? get) (eq? 'function: (cadr get)))
              (string-append (car (cddr get)) "(" ptr ")"))
             ((and (pair? get) (eq? 'method: (cadr get)))
              (string-append ptr "->" (car (cddr get)) "()"))
             ((pair? get)
              (error "invalid getter" get))
             (else
              (string-append
               (if (type-struct? (car field)) "&" "")
               ptr "->" (x->string c-name))))
            (and (or (type-struct? (car field)) (type-link? (car field)))
                 "x")))
         ";\n"
         "}\n\n")))

(define (type-setter-name type name field)
  (let ((c-name (if (pair? (cadr field)) (cadr (cadr field)) (cadr field))))
    (string-replace
     (string-append "sexp_" (x->string (type-name (parse-type name)))
                    "_set_" (x->string c-name))
     #\: "_")))

(define (write-type-setter-assignment type name field dst val)
  (let* ((set (cadr (cddr field)))
         (_ (verify-accessor set))
         (c-name (if (pair? (cadr field)) (cadr (cadr field)) (cadr field)))
         (ptr (string-append
               "((" (x->string (or (type-struct-type name) ""))
               " " (x->string name) "*)"
               "sexp_cpointer_value(" (x->string dst) "))")))
    (cond
     ((and (pair? set) (eq? 'function: (cadr set)))
      (lambda ()
        (cat (car (cddr set)) "(" ptr ", "
             (lambda () (scheme->c-converter (car field) val)) ");\n")))
     ((and (pair? set) (eq? 'method: (cadr set)))
      (lambda ()
        (cat ptr "->" (car (cddr set)) "("
             (lambda () (scheme->c-converter (car field) val)) ");\n")))
     ((pair? set)
      (error "invalid setter" set))
     ((type-struct? (car field))
      ;; assign to a nested struct - copy field-by-field
      (let ((field-type
             (cond ((lookup-type (type-name (car field)))
                    => (lambda (x) (cddr (cdr x))))
                   (else (cdr field)))))
        (lambda ()
          (for-each
           (lambda (subfield)
             (let ((subname (x->string (cadr subfield))))
               (cat
                "  "
                ptr "->" (x->string (cadr field))
                "." (x->string (cadr subfield))
                " = "
                (string-append
                 "((" (x->string (or (type-struct-type (type-name (car field)))
                                     ""))
                 " " (mangle (type-name (car field))) "*)"
                 "sexp_cpointer_value(" val "))"
                 "->" (x->string (cadr subfield)))
                ";\n")))
           (struct-fields field-type)))))
     (else
      (lambda ()
        (cat "  " ptr "->" c-name " = "
             (lambda () (scheme->c-converter (car field) val)) ";\n"))))))

(define (write-type-setter type name field)
  (cat "sexp " (type-setter-name type name field)
       " (sexp ctx, sexp self, sexp_sint_t n, sexp x, sexp v) {\n"
       (lambda () (write-validator "x" (parse-type name 0)))
       (lambda () (write-validator "v" (parse-type (car field) 1)))
       (write-type-setter-assignment type name field "x" "v")
       "  return SEXP_VOID;\n"
       "}\n\n"))

(define (write-type-funcs-helper orig-type name type)
  ;; maybe write finalizer
  (cond
   ((or (memq 'finalizer: type) (memq 'finalizer-method: type))
    => (lambda (x)
         (let* ((y (cadr x))
                (scheme-name (if (pair? y) (car y) y))
                (cname (if (pair? y) (cadr y) y))
                (method? (not (memq 'finalizer: type))))
           (cond
            (y
             (cat "sexp " (generate-stub-name scheme-name)
                  " (sexp ctx, sexp self, sexp_sint_t n, sexp x) {\n"
                  "  if (sexp_cpointer_freep(x)) {\n"
                  "    " (if method? "" cname) "("
                  (if method? "(" "")
                  "\n#ifdef __cplusplus\n"
                  "(" (mangle name) "*)"
                  "\n#endif\n"
                  "sexp_cpointer_value(x)"
                  (if method? (string-append ")->" (x->string cname) "()") "")
                  ");\n"
                  ;; TODO: keep track of open/close separately from ownership
                  "    sexp_cpointer_freep(x) = 0;\n"
                  "  }\n"
                  "  return SEXP_VOID;\n"
                  "}\n\n")
             ;; make the finalizer available
             (set! *funcs*
                   (cons (parse-func `(void ,y (,name))) *funcs*))))))))
  ;; maybe write constructor
  (cond
   ((memq 'constructor: type)
    => (lambda (x)
         (let ((make (car (cadr x)))
               (args (cdr (cadr x))))
           (cat "sexp " (generate-stub-name make)
                " (sexp ctx, sexp self, sexp_sint_t n"
                (lambda ()
                  (let lp ((ls args) (i 0))
                    (cond ((pair? ls)
                           (cat ", sexp arg" i)
                           (lp (cdr ls) (+ i 1))))))
                ") {\n"
                "  " (type-c-name name) " r;\n"
                "  sexp_gc_var1(res);\n"
                "  sexp_gc_preserve1(ctx, res);\n"
                ;; TODO: support heap-managed allocations
                ;; "  res = sexp_alloc_tagged(ctx, sexp_sizeof(cpointer)"
                ;; " + sizeof(struct " (type-name name) "), "
                ;; (type-id-name name)
                ;; ");\n"
                ;; "  r = sexp_cpointer_value(res) = "
                ;; "sexp_cpointer_body(res);\n"
                ;; "  res = sexp_alloc_tagged(ctx, sexp_sizeof(cpointer), sexp_type_tag("
                ;; (type-id-name name)
                ;; "));\n"
                "  res = sexp_alloc_tagged(ctx, sexp_sizeof(cpointer), "
                "sexp_unbox_fixnum(sexp_opcode_return_type(self)));\n"
                "  sexp_cpointer_value(res) = calloc(1, sizeof("
                (type-c-name-derefed name) "));\n"
                "  r = (" (type-c-name name) ") sexp_cpointer_value(res);\n"
                "  memset(r, 0, sizeof("
                (type-c-name-derefed name) "));\n"
                "  sexp_freep(res) = 1;\n"
                (lambda ()
                  (let lp ((ls args) (i 0))
                    (cond
                     ((pair? ls)
                      (let* ((a (car ls))
                             (field
                              (find (lambda (f) (and (pair? f) (eq? a (cadr f))))
                                    (cddr x)))
                             (arg (string-append "arg" (number->string i))))
                        (cond
                         ((and field (>= (length field) 4))
                          (cat
                           (write-type-setter-assignment
                            type name field "res" arg)))
                         (field
                          (cat "  r->" (cadr field) " = "
                               (lambda ()
                                 (scheme->c-converter (car field) arg))
                               ";\n")))
                        (lp (cdr ls) (+ i 1)))))))
                "  sexp_gc_release1(ctx);\n"
                "  return res;\n"
                "}\n\n")
           (set! *funcs*
                 (cons (parse-func
                        `(,name ,make
                                ,(map (lambda (a)
                                        (cond
                                         ((find (lambda (x) (eq? a (cadr x)))
                                                (struct-fields type))
                                          => car)
                                         (else 'sexp)))
                                      args)))
                       *funcs*))))))
  ;; write field accessors
  (let lp ((ls (struct-fields type))
           (i 0))
    (cond
     ((not (pair? ls)))
     ((and (pair? (car ls)) (pair? (cdar ls)))
      (let* ((field (car ls))
             (get+set (cddr field)))
        (cond
         ((and (pair? get+set) (car get+set))
          (let ((get-name (if (pair? (car get+set))
                              (caar get+set)
                              (car get+set))))
            (write-type-getter type name field)
            (set! *funcs*
                  (cons (parse-func
                         `(,(car field)
                           (,get-name
                            #f
                            ,(type-getter-name type name field))
                           (,name)))
                        *funcs*))
            (if (type-struct-type name)
                (set! *type-getters*
                      (cons `(,get-name ,name ,i) *type-getters*)))))
         (else "SEXP_FALSE"))
        (cond
         ((and (pair? get+set)
               (pair? (cdr get+set))
               (cadr get+set))
          (let ((set-name (if (pair? (cadr get+set))
                              (car (cadr get+set))
                              (cadr get+set))))
            (write-type-setter type name field)
            (set! *funcs*
                  (cons (parse-func
                         `(,(car field)
                           (,set-name
                            #f
                            ,(type-setter-name type name field))
                           (,name ,(car field))))
                        *funcs*))
            (if (type-struct-type name)
                (set! *type-setters*
                      (cons `(,set-name ,name ,i) *type-setters*)))))))
      (lp (cdr ls) (+ i 1))))))

(define (write-type-funcs orig-type)
  (let* ((name (car orig-type))
         (type (cdr orig-type))
         (imported? (cond ((member 'imported?: type) => cadr) (else #f))))
    (if (not imported?)
        (write-type-funcs-helper orig-type name type))))

(define (write-const const)
  (let ((scheme-name
         (if (pair? (cadr const)) (car (cadr const)) (cadr const)))
        (c-name
         (if (pair? (cadr const)) (cadr (cadr const)) (mangle (cadr const)))))
    (cat "  name = sexp_intern(ctx, \"" scheme-name "\", "
         (string-length (x->string scheme-name)) ");\n"
         "  sexp_env_define(ctx, env, name, tmp="
         (lambda () (c->scheme-converter (car const) c-name)) ");\n")))

(define (write-utilities)
  (define (input-env-string? x)
    (and (eq? 'env-string (type-base x)) (not (type-result? x))))
  (cond
   (*c++?*
    (for-each
     (lambda (t)
       (cond
        ((and (not (memq 'finalizer: (cdr t)))
              (not (memq 'finalizer-method: (cdr t)))
              (type-struct-type (car t)))
         (let ((name (type-c-name-derefed (car t)))
               (finalizer-name (type-finalizer-name (car t))))
           (cat
            "sexp " finalizer-name " ("
            "sexp ctx, sexp self, sexp_sint_t n, sexp obj) {\n"
            "  if (sexp_cpointer_value(obj) && sexp_cpointer_freep(obj))\n"
            "    delete static_cast<" name "*>"
            "(sexp_cpointer_value(obj));\n"
            "  sexp_cpointer_value(obj) = NULL;\n"
            ;; "  if (sexp_cpointer_parent(obj) && sexp_pointerp(sexp_cpointer_parent(obj)))\n"
            ;; "    sexp_release_object(ctx, sexp_cpointer_parent(obj));\n"
            "  return SEXP_VOID;\n"
            "}\n\n")))))
     *types*)))
  (cond
   ((any (lambda (f)
           (or (any input-env-string? (func-results f))
               (any input-env-string? (func-scheme-args f))))
         *funcs*)
    (cat "static char* sexp_concat_env_string (sexp x) {\n"
         "  int klen=sexp_string_size(sexp_car(x)), vlen=sexp_string_size(sexp_cdr(x));\n"
         "  char *res = (char*) calloc(1, klen+vlen+2);\n"
         "  strncpy(res, sexp_string_data(sexp_car(x)), klen);\n"
         "  res[sexp_string_size(sexp_car(x))] = '=';\n"
         "  strncpy(res+sexp_string_size(sexp_car(x)), sexp_string_data(sexp_cdr(x)), vlen);\n"
         "  res[len-1] = '\\0';\n"
         "  return res;\n"
         "}\n\n"))))

(define (write-init)
  (newline)
  (write-utilities)
  (for-each write-func *funcs*)
  (for-each write-method *methods*)
  (for-each write-type-funcs *types*)
  (for-each (lambda (n) (cat "}  // " n "\n")) *open-namespaces*)
  (newline)
  (if *c++?*
      (cat "extern \"C\"\n"))
  (cat "sexp sexp_init_library (sexp ctx, sexp self, sexp_sint_t n, sexp env, const char* version, const sexp_abi_identifier_t abi) {\n"
       (lambda ()
         (for-each
          (lambda (t) (cat "  sexp " t ";\n"))
          *tags*))
       "  sexp_gc_var3(name, tmp, op);\n"
       "  if (!(sexp_version_compatible(ctx, version, sexp_version)\n"
       "        && sexp_abi_compatible(ctx, abi, SEXP_ABI_IDENTIFIER)))\n"
       "    return SEXP_ABI_ERROR;\n"
       "  sexp_gc_preserve3(ctx, name, tmp, op);\n")
  (for-each write-const *consts*)
  (for-each write-type *types*)
  (for-each write-func-binding *funcs*)
  (for-each write-method-binding *methods*)
  (for-each (lambda (x) (cat "  " x "\n")) (reverse *inits*))
  (cat "  sexp_gc_release3(ctx);\n"
       "  return SEXP_VOID;\n"
       "}\n\n"))

(define (generate file)
  (cat "/* Automatically generated by chibi-ffi; version: "
       *ffi-version* " */\n")
  (c-system-include "chibi/eval.h")
  (load file (current-environment))
  (cat "/*\ntypes: " (map car *types*) "\nenums: " *c-enum-types* "\n*/\n")
  (write-init))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; main

(let ((args (command-line)))
  (let lp ((args (if (pair? args) (cdr args) args))
           (compile? #f)
           (cc #f)
           (cflags '())
           (features '()))
    (cond
     ((and (pair? args) (not (equal? "" (car args)))
           (eqv? #\- (string-ref (car args) 0)))
      (case (string->symbol (car args))
        ((-c --compile)
         (lp (cdr args) #t cc cflags features))
        ((-cc --cc)
         (lp (cddr args) compile? (cadr args) cflags features))
        ((-f --flags)
         (if (null? (cdr args))
             (error "--flags requires an argument"))
         (lp (cddr args)
             compile?
             cc
             (append cflags (string-split (cadr args) #\space))
             features))
        ((--features)
         (if (null? (cdr args))
             (error "--features requires an argument"))
         (lp (cddr args)
             compile?
             cc
             cflags
             (append features (map string->symbol (string-split (cadr args) #\,)))))
        (else
         (error "unknown option" (car args)))))
     (else
      (if (pair? features)
          (set! *features* features))
      (let* ((src (if (or (not (pair? args)) (equal? "-" (car args)))
                      "/dev/stdin"
                      (car args)))
             (dest
              (case (length args)
                ((0) "-")
                ((1) (string-append (strip-extension src) ".c"))
                ((2) (cadr args))
                (else
                 (error "usage: chibi-ffi [-c] <file.stub> [<output.c>]")))))
        (if (not (equal? "/dev/stdin" src))
            (let ((slash (string-scan-right src #\/)))
              (if (string-cursor>? slash (string-cursor-start src))
                  (set! wdir (substring-cursor src (string-cursor-start src) slash)))))
        (if (equal? "-" dest)
            (generate src)
            (with-output-to-file dest (lambda () (generate src))))
        (cond
         ((and compile? (not (equal? "-" dest)))
          ;; This has to use `eval' for bootstrapping, since we need
          ;; chibi-ffi to compile to (chibi process) module.
          (let* ((so (string-append (strip-extension src)
                                    *shared-object-extension*))
                 (execute (begin (eval '(import (chibi process))
                                       (current-environment))
                                 (eval 'execute (current-environment))))
                 (base-args (append cflags *cflags*
                                    `("-o" ,so ,dest "-lchibi-scheme")
                                    (map (lambda (x) (string-append "-l" x))
                                         (reverse *clibs*))
                                    (apply append
                                           (map (lambda (x) (list "-framework" x))
                                                (reverse *frameworks*)))))
                 (args
                  (eval
                   `(cond-expand
                     (macosx (append '("-dynamiclib" "-Oz") ',base-args))
                     (else (append '("-fPIC" "-shared" "-Os") ',base-args)))))
                 (cc (or cc (if *c++?* "c++" "cc"))))
            (display ";; " (current-error-port))
            (write (cons cc args) (current-error-port))
            (newline (current-error-port))
            (execute cc (cons cc args))))))))))
