;;; Copyright (c) 2002 Oscar Waddell and R. Kent Dybvig

;;; Permission is hereby granted, free of charge, to any person obtaining a
;;; copy of this software and associated documentation files (the "Software"),
;;; to deal in the Software without restriction, including without limitation
;;; the rights to use, copy, modify, merge, publish, distribute, sublicense,
;;; and/or sell copies of the Software, and to permit persons to whom the
;;; Software is furnished to do so, subject to the following conditions:

;;; The above copyright notice and this permission notice shall be included in
;;; all copies or substantial portions of the Software.

;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
;;; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
;;; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
;;; THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
;;; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
;;; FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
;;; DEALINGS IN THE SOFTWARE.

;;; Last modified: October 2010

;;; Acknowledgements
;;;  - Michael Lenaghan of frogware, Inc., contributed to the define-class
;;;    interface handling code.

;;; Possible names:
;;;   Chez Scheme OOP System (Chez SOOP)

#|
define-class:
  definition -> (define-class (class-name class-formal*)
                              (base-name base-actual*)
                  clause*)
  clause -> (implements interface*)
          | (ivars ivar*)
          | (init init-expr*)
          | (methods method*)
          | (constructor id)
          | (predicate id)
          | (prefix string)

  ivar -> (modifier* ivar-name ivar-expression)
  modifier -> mutability | visibility
  mutability -> mutable | immutable
  visibility -> public | private
  method -> (method-name formals method-body+)
  formals -> (var*) | var | (var+ . var)

notes:
  - at most one of each kind of clause may be present
  - at most one of each kind of modifier may be present
  - multiple methods of the same name but different formals can be present

products:
  - class-name is bound to class information in the expand-time environment
  - make-class-name (or specified constructor name) is bound to creation procedure
  - class-name? (or specified predicate name) is bound to predicate procedure
  - new (not inherited) method names are bound to method-dispatch procedures
  - for each public ivar, <prefix>-ivar is bound to an accessor procedure,
    where <prefix> is the specified prefix or "class-name-"
  - for each public, mutable ivar, <prefix>-ivar-set! is bound to a mutator
    procedure, where <prefix> is the specified prefix or "class-name-"

define-interface:
  definition -> (define-interface interface-name method*)
              | (define-interface interface-name base-name method*)
  
  method -> (method-name formals)

products:
  - interface-name is bound to interface information in the expand-time environment
  - new (not inherited) method names are bound to method-dispatch procedures
|#

#| Todo:
  - try using record-constructor, record-accessor, and record-mutator!
    instead of $record, $object-ref, and $object-set!
  - add a nongenerative clause ala define-record-type
    - squawk if any methods or interfaces
    - default to generative
  - need more tests:
    - init-expr checks
    - profile to see if all paths are covered by test suite
  - consider: don't degenerate to record definition facility
    - flush implicit nongenerative option
    - flush visibility keywords
    - flush getter/setter
    => less complex but less useful
  - consider: alternative version
    - <class-name> and method definitions are only products
    - (make-class-name arg ...) replaced by (make <class-name> arg ...)
    - (class-name? arg) replaced by (isa? <class-name> arg)
    - accessors and mutators replaced by
      (open-instance <class-name> (ivar ...) expr expr ...)
    - upside: prettier, less namespace clutter
    - downside: can't use module system directly to control visibility of
      makers, predicates, accessors, and mutators separately
    - downside: maker, predicate, accessors, mutators aren't
      first-class procedures
  - consider: inherited ivars
    - (inherited-ivar ivar ...)
    - inheritable / noninheritable ivar modifiers
  - (frogware) Error messages for incorrect syntax should be better. (You often
    get a generic "invalid syntax" message and have to hunt to figure
    out what caused it.)
  - consider exposing query-interface somehow
    - (interface I x) => version of x to which I's methods are applicable
    - (interface->instance (interface I x)) => x
  - interfaces and inheritance
    - either require parent for define-interface or hide <root-interface>
    - consider allowing multiple inheritance (including zero parents) for interfaces
|#

#|
reaching into Chez Scheme's internals for:
  #!base-rtd
  $make-record-type
  $record-type-field-offsets
  $record-type-interfaces
  $record
  $object-ref
  $object-set!
|#

#!chezscheme

(library (oop helpers)
  (export
    $make-class $class?
    $class-formals $class-formal-bindings $class-ivar-bindings $class-minfos
    $class-vtable-rtd $class-ctrtd
    $class-vtable-expr $class-interfaces $class-init-proc

    $make-interface $interface?
    $interface-rtd $interface-minfos

    $instance $make-instance $instance?

    root-vtable-rtd

    make-minfo minfo-mname minfo-hidden-mname minfo-arity minfo-formals minfo-flat-formals

    construct-name parse-formals build-generic make-ivar-defn free-id-member
  )

  (import (chezscheme))

  (define-record-type ($class $make-class $class?)
    (nongenerative)
    (fields
      (immutable formals)         ; (formal ...)
      (immutable formal-bindings) ; (((base-formal base-arg) ...) ...)
      (immutable ivar-bindings)   ; (((ivar init) ...) ...)
      (immutable minfos)          ; ((mname hidden-mname arity) ...)
                                  ; same mname may appear more than once

      (immutable vtable-rtd)
      (immutable ctrtd)

      (immutable vtable-expr)
      (immutable interfaces)
      (immutable init-proc)))

  (define-record-type ($interface $make-interface $interface?)
    (nongenerative)
    (fields
      (immutable rtd)
      (immutable minfos)))

  (define-record-type ($instance $make-instance $instance?)
    (nongenerative))

 ;; minfos cannot be records since we insert minfos into the output of
 ;; define-class and define-interface and the identifiers contained within
 ;; these minfos must be marked/unmarked as appropriate by the expander,
 ;; which delves into vectors but not records
  (define make-minfo
    (lambda (mname hidden-name arity formals flat-formals)
      (vector mname hidden-name arity formals flat-formals)))
  (define minfo-mname (lambda (x) (vector-ref x 0)))
  (define minfo-hidden-mname (lambda (x) (vector-ref x 1)))
  (define minfo-arity (lambda (x) (vector-ref x 2)))
  (define minfo-formals (lambda (x) (vector-ref x 3)))
  (define minfo-flat-formals (lambda (x) (vector-ref x 4)))

  (define root-vtable-rtd
    (#%$make-record-type #!base-rtd #!base-rtd
      "root-vtable-rtd"
      '((immutable ptr interfaces))
      #f
      #f
      #f))

  (define construct-name
    (lambda (template-identifier . args)
      (datum->syntax template-identifier
        (string->symbol
          (apply string-append
                 (map (lambda (x)
                        (if (string? x)
                            x
                            (symbol->string (syntax->datum x))))
                      args))))))

  (define parse-formals
    (lambda (fmls)
      (let f ([ids fmls] [n 0])
        (syntax-case ids ()
          [(car . cdr)
           (if (identifier? #'car)
               (f #'cdr (fx+ n 1))
               (syntax-error fmls "invalid method formals"))]
          [() (values n fmls)]
          [else
           (if (identifier? #'ids)
               (values
                 (fx- -1 n)
                 (let f ([ids fmls])
                   (syntax-case ids ()
                     [(x . d) (cons #'x (f #'d))]
                     [x #'(x)])))
               (syntax-error #'fmls "invalid method formals"))]))))

  (define build-generic
    (lambda (minfos offsets)
      (define cull-method
        (lambda (mname minfos offsets)
          (if (null? minfos)
              (values '() '() '() '())
              (let ([offset (car offsets)] [minfo (car minfos)])
                (let-values ([(gminfos goffsets new-minfos new-offsets)
                              (cull-method mname (cdr minfos) (cdr offsets))])
                  (if (bound-identifier=? (minfo-mname minfo) mname)
                      (values
                        (cons minfo gminfos)
                        (cons offset goffsets)
                        new-minfos
                        new-offsets)
                      (values
                        gminfos
                        goffsets
                        (cons (car minfos) new-minfos)
                        (cons (car offsets) new-offsets))))))))
      (if (null? minfos)
          '()
          (let ([generic-name (minfo-mname (car minfos))])
            (let-values ([(gminfos goffsets minfos offsets)
                          (cull-method generic-name minfos offsets)])
              (cons `(,generic-name
                      ,@(map (lambda (minfo offset)
                               `(,(minfo-formals minfo) ,(minfo-flat-formals minfo) ,offset))
                             gminfos
                             goffsets))
                    (build-generic minfos offsets)))))))

  (define (make-ivar-defn ivar mutable? ivar-offset)
    (with-syntax ([ivar ivar] [ivar-offset ivar-offset])
      (if mutable?
          #'(define-syntax ivar
              (identifier-syntax
                [id (#3%$object-ref 'scheme-object ego ivar-offset)]
                [(set! var val) (#3%$object-set! 'scheme-object ego ivar-offset val)]))
          #'(define-syntax ivar
              (make-variable-transformer
                (lambda (x)
                  (syntax-case x (set!)
                    [id (identifier? #'id) #'(#3%$object-ref 'scheme-object ego ivar-offset)]
                    [(set! var val) (syntax-error x "invalid assignment of immutable ivar")])))))))

  (define free-id-member
    (lambda (id ls2)
      (and (not (null? ls2))
           (if (free-identifier=? (car ls2) id)
               ls2
               (free-id-member id (cdr ls2))))))

  (record-writer (type-descriptor $instance)
    (lambda (x p wr)
      (fprintf p "#<instance of ~a>"
        (let ([rtd (record-rtd x)])
          (if (eq? rtd (type-descriptor $instance))
              "<root>"
              (record-type-name rtd))))))

 ; these aren't evaluated if placed in (oop), since (oop) has no
 ; run-time (variable) exports
  (pretty-format 'define-class
    '(_ (fill 0 x ...) 13 (fill 0 x ...) #f clause ...))
  (pretty-format 'methods
    '(_ #f (bracket x (fill 0 x ...) #f e ...) ...))
  (pretty-format 'ivars
    '(_ (bracket fill #f x ...) 6 (bracket fill #f x ...) ...))
  (pretty-format 'define-interface
    '(alt (_ var var #f (bracket x x) ...)
          (_ var #f (bracket x x) ...)))
)

;;; supplies define-class, define-interface, <root>, and <root-interface>
;;; and aux keywords

(library (oop)
  (export <root> <root-interface> define-interface
    define-class ivars public private methods self implements init
    constructor predicate prefix)
  (import (chezscheme) (oop helpers))

  (define-syntax <root>
    (make-compile-time-value
      ($make-class '() '() '() '()                         ; formals formal-bindings ivar-bindings minfos
        root-vtable-rtd
        (type-descriptor $instance)                        ; ctrtd
        #'(type-descriptor $instance)                      ; vtable-expr
        '()                                                ; interfaces
        #'values)))                                        ; init-expr ...

  (define-syntax <root-interface>
    (make-compile-time-value
      ($make-interface (make-record-type "base interface rtd" '()) '())))

  (define-syntax define-interface
    (lambda (x)
      (define build-minfo
        (lambda (mname formals)
          (let-values ([(arity flat-formals) (parse-formals formals)])
           ; discard source information
            (make-minfo mname "ignored" arity formals flat-formals))))
      (syntax-case x ()
        [(_ iname [method-name method-formals] ...)
         (and (identifier? #'iname) (andmap identifier? #'(method-name ...)))
         #'(define-interface iname <root-interface> [method-name method-formals] ...)]
        [(_ iname base-iname [method-name method-formals] ...)
         (and (identifier? #'iname) (identifier? #'base-iname) (andmap identifier? #'(method-name ...)))
         (lambda (r)
           (let ([bi (r #'base-iname)])
             (unless ($interface? bi)
               (syntax-error #'base-iname
                 "define-interface: unrecognized base interface"))
             (let ([base-mnames (with-syntax ([(#(base-mname base-hidden-name base-arity base-formals base-flat-formals) ...) ($interface-minfos bi)])
                                  #'(base-mname ...))])
               (let f ([ls #'(method-name ...)])
                 (unless (null? ls)
                   (when (free-id-member (car ls) base-mnames)
                     (syntax-error (car ls) "conflict with inherited interface method"))
                   (f (cdr ls)))))
             (with-syntax ([(base-minfo ...) ($interface-minfos bi)]
                           [(minfo ...) (map build-minfo #'(method-name ...) #'(method-formals ...))])
               (with-syntax ([iface-rtd
                              (make-record-type ($interface-rtd bi)
                                (symbol->string (syntax->datum #'iname))
                                (syntax->datum (map minfo-mname #'(minfo ...))))])
                 (with-syntax ([((generic-name (generic-formals generic-flat-formals generic-offset) ...) ...)
                                (build-generic
                                  #'(minfo ...)
                                  (let ([ls (#%$record-type-field-offsets #'iface-rtd)])
                                    (list-tail ls (- (length ls) (length #'(minfo ...))))))]
                               [opt3 (= (optimize-level) 3)])
                   #`(begin
                       (define-syntax iname (make-compile-time-value ($make-interface 'iface-rtd #'(base-minfo ... minfo ...))))
                       (define (qi who ego)
                         (define get-interfaces (#3%record-accessor '#,root-vtable-rtd 0))
                         (or (and (or opt3 (#3%record? ego))
                                  (let ([rtd (#3%record-rtd ego)])
                                    (and (or opt3 (#3%record? rtd '#,root-vtable-rtd))
                                      (#3%ormap (lambda (i) (and (#3%record? i 'iface-rtd) i))
                                        (get-interfaces rtd)))))
                             (errorf who "not applicable to ~s" ego)))
                       (define generic-name
                         (let ([who 'generic-name]) ; can't ref generic-name pattern vble inside ... below
                           (case-lambda
                             [(ego . generic-formals)
                              ((#3%$object-ref 'scheme-object (qi who ego) generic-offset)
                               ego . generic-flat-formals)]
                             ...)))
                       ...))))))])))

  (define-syntax define-class
    (lambda (x)
      (define build-minfo
        (lambda (mname formals hidden)
          (let-values ([(arity flat-formals) (parse-formals formals)])
            (make-minfo mname hidden arity formals flat-formals))))
      (define build-method
        (lambda (minfo class-name body base-minfos)
          (define cull-super
            (lambda (mname minfos)
              (if (null? minfos)
                  '()
                  (let ([minfo (car minfos)])
                    (if (free-identifier=? (minfo-mname minfo) mname)
                        (cons minfo (cull-super mname (cdr minfos)))
                        (cull-super mname (cdr minfos)))))))
          (with-syntax ([(formal ...) (minfo-flat-formals minfo)]
                        [body body]
                        [super-definition
                         (with-syntax ([super (datum->syntax class-name 'super)])
                           (if (not (null? base-minfos))
                               (with-syntax ([(#(mname hidden-name arity formals flat-formals) ...)
                                              (cull-super (minfo-mname minfo) base-minfos)])
                                 #'(define super (case-lambda [formals (hidden-name ego . flat-formals)] ...)))
                               (with-syntax ([mname (minfo-mname minfo)] [class-name class-name])
                                 #'(define-syntax super
                                     (lambda (x)
                                       (syntax-error x
                                         (format "no inherited ~s method for ~s in" 'mname 'class-name)))))))])
            #'(lambda (i formal ...)
                (fluid-let-syntax ([ego (identifier-syntax i)])
                  super-definition
                  body)))))
      (define unwrap-minfos
        (lambda (minfos)
         ; need list/vector structure, with arity unwrapped
          (with-syntax ([(#(mname hidden-name arity formals flat-formals) ...) minfos])
            (with-syntax ([(arity ...) (syntax->datum #'(arity ...))])
              #'(#(mname hidden-name arity formals flat-formals) ...)))))
      (define build-interface-vtable
        (lambda (minfos)
          (lambda (iface)
            (with-syntax
              ([irtd ($interface-rtd iface)]
               [(hidden ...)
                (map (lambda (iminfo)
                       (let ([mname (minfo-mname iminfo)] [arity (minfo-arity iminfo)])
                         (let f ([minfos minfos])
                           (cond
                             [(null? minfos) (syntax-error (minfo-mname iminfo) "no suitable implementation for interface method")]
                             [(minfo-match? (car minfos) mname arity) (minfo-hidden-mname (car minfos))]
                             [else (f (cdr minfos))]))))
                     (unwrap-minfos ($interface-minfos iface)))])
              #'(#3%$record 'irtd hidden ...)))))
      (define minfo-match?
        (lambda (minfo mname arity)
          (and (= arity (minfo-arity minfo))
               (free-identifier=? mname (minfo-mname minfo)))))
      (define process-methods
        (lambda (class-name interfaces all-base-minfos minfos bodies)
          (let f ([minfos minfos])
            (unless (null? minfos)
              (let ([mname (minfo-mname (car minfos))] [arity (minfo-arity (car minfos))])
                (let f ([ls (cdr minfos)])
                  (unless (null? ls)
                    (if (minfo-match? (car ls) mname arity)
                        (syntax-error (minfo-mname (car ls)) "duplicate arity for method")
                        (f (cdr ls))))))
              (f (cdr minfos))))
          (let ([base-mnames (map minfo-mname all-base-minfos)])
            (let f ([base-minfos all-base-minfos] [minfos minfos] [bodies bodies] [mlambdas '()] [all-minfos '()])
              (if (null? base-minfos)
                  (let f ([minfos minfos] [bodies bodies] [mlambdas mlambdas] [all-minfos all-minfos] [generics '()])
                    (if (null? minfos)
                        (begin
                         ; We should never create a new generic method that matches the name of
                         ; an interface method. (If the generic method made it this far then we
                         ; know it didn't match the arity of the interface method. If it did it
                         ; would have been filtered out.)
                          (for-each
                            (lambda (generic)
                              (let ([mname (minfo-mname generic)])
                                (for-each
                                  (lambda (i)
                                    (for-each
                                      (lambda (m)
                                        (let ([interface-mname (minfo-mname m)])
                                          (when (free-identifier=? mname interface-mname)
                                            (syntax-error mname "arity not supported by interface method"))))
                                      (unwrap-minfos ($interface-minfos i))))
                                  interfaces)))
                              generics)
                          (list (reverse all-minfos)
                                (reverse mlambdas)
                                (reverse generics)
                               ; We need to build a list of minfos that will actually be included in the class vtable--
                               ; ie, minfos that appear in this class and/or any super-class (but not in any interface).
                               ; That list will match up with the vtable offsets.
                                (let f ([possibly-included-minfos all-minfos] [included-minfos '()])
                                  (if (null? possibly-included-minfos)
                                      included-minfos
                                      (let ([minfo (car possibly-included-minfos)])
                                        (let ([mname (minfo-mname minfo)] [arity (minfo-arity minfo)])
                                          (f (cdr possibly-included-minfos)
                                             (if (ormap
                                                   (lambda (i)
                                                     (ormap (lambda (m) (minfo-match? m mname arity))
                                                            (unwrap-minfos ($interface-minfos i))))
                                                   interfaces)
                                                 included-minfos
                                                 (cons minfo included-minfos)))))))))
                        (let ([minfo (car minfos)])
                          (let ([mname (minfo-mname minfo)] [arity (minfo-arity minfo)])
                            (when (ormap (lambda (base-mname) (free-identifier=? mname base-mname)) base-mnames)
                              (syntax-error mname "arity not supported by base class method"))
                            (f (cdr minfos)
                               (cdr bodies)
                               (cons
                                 (list (minfo-hidden-mname minfo)
                                       (build-method minfo class-name (car bodies) '()))
                                 mlambdas)
                               (cons minfo all-minfos)
                               (if (ormap
                                     (lambda (i)
                                       (ormap (lambda (m) (minfo-match? m mname arity))
                                         (unwrap-minfos ($interface-minfos i))))
                                     interfaces)
                                   generics
                                   (cons minfo generics)))))))
                  (let ([base-minfo (car base-minfos)])
                    (let ([base-mname (minfo-mname base-minfo)] [base-arity (syntax->datum (minfo-arity base-minfo))])
                      (let find-method ([xminfos minfos] [xbodies bodies])
                        (cond
                          [(null? xminfos)
                           (f (cdr base-minfos) minfos bodies mlambdas
                              (cons base-minfo all-minfos))]
                          [(and (= base-arity (minfo-arity (car xminfos)))
                                (free-identifier=? base-mname (minfo-mname (car xminfos))))
                           (f (cdr base-minfos)
                              (remq (car xminfos) minfos)
                              (remq (car xbodies) bodies)
                              (cons (list (minfo-hidden-mname (car xminfos))
                                          (build-method (car xminfos) class-name (car xbodies) all-base-minfos))
                                    mlambdas)
                              (cons (car xminfos) all-minfos))]
                          [else (find-method (cdr xminfos) (cdr xbodies))])))))))))
      (define free-id-union
        (lambda (ls1 ls2)
          (if (null? ls1)
              ls2
              (if (free-id-member (car ls1) ls2)
                  (free-id-union (cdr ls1) ls2)
                  (cons (car ls1) (free-id-union (cdr ls1) ls2))))))

      (module (parse-clauses)
        (define-syntax define-option-parser
          (lambda (x)
            (syntax-case x (else)
              [(_ name [desc (kwd ...) ([var default] ...) pattern guard expr] ...)
               (with-syntax ([(seen? ...) (generate-temporaries #'(desc ...))])
                 #'(begin
                     (define (option-parser x*)
                       (let f ([x* x*] [seen? #f] ... [var default] ... ...)
                          (if (null? x*)
                              (values var ... ...)
                              (syntax-case (car x*) (kwd ... ...)
                                [pattern
                                 guard
                                 (begin
                                   (when seen?
                                     (syntax-error (car x*)
                                       (format "extra ~a" desc)))
                                   (let ([seen? #t])
                                     (let-values ([(var ...) expr])
                                       (f (cdr x*) seen? ... var ... ...))))]
                                ...))))
                     (define-syntax name
                       (lambda (x)
                         (syntax-case x ()
                           [(k x* b1 b2 (... ...))
                            (with-implicit (k var ... ...)
                              #'(let-values ([(var ... ...) (option-parser x*)])
                                  b1 b2 (... ...)))])))))])))
        (define (valid-type? x) (eq? x 'scheme-object))
        (define-option-parser parse-modifiers
          ["ivar mutability modifier" () ([mutable? #t])
           x
           (or (literal-identifier=? #'x #'mutable) (literal-identifier=? #'x #'immutable))
           (literal-identifier=? #'x #'mutable)]
          ["ivar public/private modifier" () ([public? #f])
           x
           (or (literal-identifier=? #'x #'public) (literal-identifier=? #'x #'private))
           (literal-identifier=? #'x #'public)]
          ["ivar type" () ([type 'scheme-object])
           x
           (valid-type? (datum x))
           (datum x)]
          ["" () () x #t (syntax-error #'x "invalid ivar modifier")])
        (define-option-parser parse-clauses
          ["implements clause" (implements) ([iface* '()])
           (implements iface ...)
           (andmap identifier? (syntax->list #'(iface ...)))
           (syntax->list #'(iface ...))]
          ["ivars clause" (ivars) ([public?* '()] [mutable?* '()] [ivar* '()] [ivar-init* '()])
           (ivars (modifier ... ivar ivar-init) ...)
           (andmap identifier? (syntax->list #'(ivar ...)))
           (let-values ([(public?* mutable?*)
                         (let f ([modifier** (syntax->list #'((modifier ...) ...))])
                           (if (null? modifier**)
                               (values '() '())
                               (let-values ([(public?* mutable?*) (f (cdr modifier**))])
                                 (parse-modifiers (syntax->list (car modifier**))
                                   (values (cons public? public?*)
                                           (cons mutable? mutable?*))))))])
             (values public?* mutable?* (syntax->list #'(ivar ...)) (syntax->list #'(ivar-init ...))))]
          ["init clause" (init) ([init-expr* '()])
           (init init-expr ...)
           #t
           (syntax->list #'(init-expr ...))]
          ["methods clause" (methods) ([method-name* '()] [method-formals* '()] [method-body* '()])
           (methods method ...)
           #t
           (let f ([method* (syntax->list #'(method ...))])
             (if (null? method*)
                 (values  '() '() '())
                 (let-values ([(method) (car method*)]
                              [(method-name* method-formals* method-body*) (f (cdr method*))])
                   (syntax-case method ()
                     [(method-name method-formals method-b1 method-b2 ...)
                      (values
                        (cons #'method-name method-name*)
                        (cons #'method-formals method-formals*)
                        (cons #'(let () method-b1 method-b2 ...) method-body*))]
                     [method (syntax-error #'method "invalid method syntax")]))))]
          ["constructor clause" (constructor) ([constructor-id #f])
           (constructor id)
           (identifier? #'id)
           #'id]
          ["predicate clause" (predicate) ([predicate-id #f])
           (predicate id)
           (identifier? #'id)
           #'id]
          ["prefix clause" (prefix) ([prefix-string #f])
           (prefix str)
           (string? (datum str))
           (datum str)]
          ["" () () x #t (syntax-error #'x "invalid define-class clause")]))

      (syntax-case x ()
        [(_ (class-name class-formal ...) (base-name base-arg ...) clause ...)
         (parse-clauses (syntax->list #'(clause ...))
           (lambda (r)
             (let ([bc (r #'base-name)])
               (unless ($class? bc)
                 (syntax-error #'base-name
                   "define-class: unrecognized base class"))
               (let f ([ls ivar*])
                 (define bound-id-member?
                   (lambda (x ls)
                     (and (not (null? ls))
                          (or (bound-identifier=? (car ls) x)
                              (bound-id-member? x (cdr ls))))))
                 (unless (null? ls)
                   (if (bound-id-member? (car ls) (cdr ls))
                       (syntax-error (car ls) "duplicate instance variable")
                       (f (cdr ls)))))
               (with-syntax ([(iface-name ...) (free-id-union
                                                 iface*
                                                 (syntax->list ($class-interfaces bc)))])
                 (with-syntax ([(interface ...)
                                (map (lambda (x)
                                       (let ([iface (r x)])
                                         (unless ($interface? iface) (syntax-error x "unrecognized interface"))
                                         iface))
                                     #'(iface-name ...))])
                   (with-syntax ([(((base-ivar base-ivar-init) ...) ...) ($class-ivar-bindings bc)]
                                 [base-init-proc ($class-init-proc bc)]
                                 [(base-formal-binding ...)
                                  (with-syntax ([(base-formal ...) ($class-formals bc)]
                                                [(base-base-formal-binding ...) ($class-formal-bindings bc)])
                                    (unless (= (length #'(base-arg ...)) (length #'(base-formal ...)))
                                      (syntax-error #'base-name
                                        "incorrect number of arguments to base class"))
                                    #'(((base-formal base-arg) ...) base-base-formal-binding ...))]
                                 [((all-minfo ...) ((new-hidden-mname mlambda) ...) (generic ...) (included-minfo ...))
                                  (process-methods #'class-name #'(interface ...)
                                    (unwrap-minfos ($class-minfos bc))
                                    (map build-minfo
                                      method-name*
                                      method-formals*
                                      (generate-temporaries method-name*))
                                    method-body*)]
                                 [self (datum->syntax #'class-name 'self)])
                     (let ([name (let ([name (datum class-name)])
                                   (if (gensym? name)
                                       name
                                       (symbol->string name)))]
                           [nongenerative? (and (null? #'(all-minfo ...))
                                                (null? #'(interface ...)))]
                           [flds (map list
                                      (map (lambda (x) (if x 'mutable 'immutable)) mutable?*)
                                      (map (lambda (x) 'ptr) ivar*)
                                      (syntax->datum ivar*))])
                       (when (gensym? name)
                         (unless nongenerative?
                           (syntax-error #'class-name
                             "cannot specify gensym class-name with methods or interfaces")))
                       (with-syntax ([ctrtd (#%$make-record-type
                                              #!base-rtd
                                              ($class-ctrtd bc)
                                              name
                                              flds
                                              #f
                                              #f
                                              #f)]
                                     [vtable-rtd (#%$make-record-type
                                                   #!base-rtd
                                                   ($class-vtable-rtd bc)
                                                   "compile-time-vtable-rtd"
                                                   (syntax->datum (map minfo-mname #'(generic ...)))
                                                   #f
                                                   #f
                                                   #f)])
                         (with-syntax ([(ivar ...) ivar*]
                                       [(ivar-init ...) ivar-init*]
                                       [(ivar-defn ...)
                                        (map make-ivar-defn
                                             ivar*
                                             mutable?*
                                             (let ([offsets (#%$record-type-field-offsets #'ctrtd)])
                                               (list-tail offsets (- (length offsets) (length ivar*)))))])
                           (with-syntax ([init-proc
                                          (with-syntax ([(init-expr ...) init-expr*])
                                            #'(lambda (ego)
                                                (base-init-proc ego)
                                                (let ()
                                                  ivar-defn
                                                  ...
                                                  (define-syntax self (identifier-syntax ego))
                                                  init-expr ...
                                                  ego)))]
                                         [vtable-expr
                                          (if nongenerative?
                                              #''ctrtd
                                              (with-syntax ([(vtable-init ...) (map minfo-hidden-mname #'(included-minfo ...))]
                                                            [parent-rtd ($class-vtable-expr bc)]
                                                            [name (datum->syntax #'class-name name)]
                                                            [flds (datum->syntax #'class-name flds)]
                                                            [(iface-elt ...)
                                                             (map (build-interface-vtable #'(all-minfo ...)) #'(interface ...))])
                                                #'(#%$make-record-type
                                                    'vtable-rtd
                                                    parent-rtd
                                                    name
                                                    'flds
                                                    #f
                                                    #f
                                                    #f
                                                    (list iface-elt ...)
                                                    vtable-init ...)))]
                                         [((generic-name (generic-formals generic-flat-formals generic-offset) ...) ...)
                                          (build-generic
                                            #'(generic ...)
                                            (let ([offsets (#%$record-type-field-offsets #'vtable-rtd)] [minfos #'(included-minfo ...)])
                                              (let f ([offsets (list-tail offsets (- (length offsets) (length minfos)))]
                                                      [minfos minfos]
                                                      [generics #'(generic ...)])
                                                (if (null? generics)
                                                    '()
                                                    (if (eq? (car generics) (car minfos))
                                                        (cons (car offsets) (f (cdr offsets) (cdr minfos) (cdr generics)))
                                                        (f (cdr offsets) (cdr minfos) generics))))))]
                                         [opt3 (= (optimize-level) 3)])
                             (with-syntax ([maker-name (or constructor-id (construct-name #'class-name "make-" #'class-name))]
                                           [maker-body
                                            (let f ([ls #'(base-formal-binding ...)])
                                              (syntax-case ls ()
                                                [(((lhs rhs) ...))
                                                 #'(let ((lhs rhs) ...)
                                                     (init-proc (let* ([base-ivar base-ivar-init] ... ... [ivar ivar-init] ...)
                                                                  (#3%$record vtable base-ivar ... ... ivar ...))))]
                                                [(((lhs rhs) ...) m ...)
                                                 (with-syntax ([body (f #'(m ...))])
                                                   #'(let ((lhs rhs) ...) body))]))]
                                           [pred-name (or predicate-id (construct-name #'class-name #'class-name "?"))]
                                           [((accessor-name . accessor) ...)
                                            (let ([offset* (let ([offset* (#%$record-type-field-offsets #'ctrtd)])
                                                             (list-tail offset* (- (length offset*) (length ivar*))))]
                                                  [prefix-string (or prefix-string (format "~a-" (datum class-name)))])
                                              (let f ([ivar* ivar*] [offset* offset*] [public?* public?*])
                                                (if (null? ivar*)
                                                    '()
                                                    (with-syntax ([rest (f (cdr ivar*) (cdr offset*) (cdr public?*))])
                                                      (if (car public?*)
                                                          (with-syntax ([accessor-name (construct-name (car ivar*) prefix-string (car ivar*))]
                                                                        [offset (car offset*)])
                                                            #'((accessor-name .
                                                                (lambda (x)
                                                                  (unless (or opt3 (record? x vtable))
                                                                    (errorf 'accessor-name "not applicable to ~s" x))
                                                                  (#3%$object-ref 'scheme-object x offset)))
                                                               . rest))
                                                          #'rest)))))]
                                           [((mutator-name . mutator) ...)
                                            (let ([offset* (let ([offset* (#%$record-type-field-offsets #'ctrtd)])
                                                             (list-tail offset* (- (length offset*) (length ivar*))))]
                                                  [prefix-string (or prefix-string (format "~a-" (datum class-name)))])
                                              (let f ([ivar* ivar*] [offset* offset*] [public?* public?*] [mutable?* mutable?*])
                                                (if (null? ivar*)
                                                    '()
                                                    (with-syntax ([rest (f (cdr ivar*) (cdr offset*) (cdr public?*) (cdr mutable?*))])
                                                      (if (and (car public?*) (car mutable?*))
                                                          (with-syntax ([mutator-name (construct-name (car ivar*) prefix-string (car ivar*) "-set!")]
                                                                        [offset (car offset*)])
                                                            #'((mutator-name .
                                                                (lambda (x v)
                                                                  (unless (or opt3 (record? x vtable))
                                                                    (errorf 'mutator-name "not applicable to ~s" x))
                                                                  (#3%$object-set! 'scheme-object x offset v)))
                                                               . rest))
                                                          #'rest)))))])
                               #'(begin
                                  ; we use a module here (1) to attach necessary
                                  ; indirect exports to class-name in case class-name
                                  ; is exported from a top-level module and
                                  ; (2) so that we can define vtable after
                                  ; new-hidden-mname ... yet still have the introduced
                                  ; identifier vtable resolve to the correct binding
                                  ; in the method bodies.  we don't wrap the entire
                                  ; define-class output in a module form since we want
                                  ; the pred-name, maker-name, and the generic names
                                  ; to be ordinary top-level variables at top level.
                                   (module ((class-name vtable new-hidden-mname ...) vtable new-hidden-mname ...)
                                     (module (new-hidden-mname ...)
                                      ; local ego for fluid-let-syntax to whack
                                       (define-syntax ego values)
                                       ivar-defn
                                       ...
                                       (define-syntax self (identifier-syntax ego))
                                       (define new-hidden-mname mlambda) ...)
                                    ; counting on letrec* semantics below so that
                                    ; new-hidden-mname ... are defined before referenced
                                    ; in vtable-expr
                                     (define vtable vtable-expr)
                                     (define-syntax class-name
                                       (make-compile-time-value
                                         ($make-class
                                           #'(class-formal ...)
                                           #'(base-formal-binding ...)
                                           #'(([base-ivar base-ivar-init] ...) ... ([ivar ivar-init] ...))
                                           #'(all-minfo ...)
                                           'vtable-rtd
                                           'ctrtd
                                           #'vtable
                                           #'(iface-name ...)
                                           #'init-proc))))
                                   (define (pred-name x) (#3%record? x vtable))
                                   (define (maker-name class-formal ...) maker-body)
                                   (define accessor-name accessor) ...
                                   (define mutator-name mutator) ...
                                   (define generic-name
                                     (let ([who 'generic-name]) ; can't ref generic-name pattern vble inside ... below
                                       (case-lambda
                                         [(ego . generic-formals)
                                          (unless (or opt3 (#3%record? ego vtable))
                                            (errorf who "not applicable to ~s" ego))
                                          ((#3%$object-ref 'scheme-object (#3%record-rtd ego) generic-offset)
                                           ego . generic-flat-formals)]
                                         ...)))
                                   ...))))))))))))])))

  (define-syntax aux
    (syntax-rules ()
      [(_ kwd)
       (define-syntax kwd
         (lambda (x)
           (syntax-error x "misplaced aux keyword")))]))

  (aux ivars)
  (aux public)
  (aux private)
  (aux methods)
  (aux self)
  (aux implements)
  (aux init)
 ; constructor, predicate, and prefix are defined by (chezscheme)
  #;(aux constructor)
  #;(aux predicate)
  #;(aux prefix)
)
