;;;;
;;;; struct.stk -- Defining STklos Structures
;;;;
;;;; Copyright © 2004-2022 Erick Gallesio - I3S-CNRS/ESSI <eg@essi.fr>
;;;;
;;;;
;;;; This program is free software; you can redistribute it and/or modify
;;;; it under the terms of the GNU General Public License as published by
;;;; the Free Software Foundation; either version 2 of the License, or
;;;; (at your option) any later version.
;;;;
;;;; This program is distributed in the hope that it will be useful,
;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;;;; GNU General Public License for more details.
;;;;
;;;; You should have received a copy of the GNU General Public License
;;;; along with this program; if not, write to the Free Software
;;;; Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
;;;; USA.
;;;;
;;;;           Author: Erick Gallesio [eg@essi.fr]
;;;;    Creation date: 17-May-2004 23:35 (eg)
;;;;


(export define-struct)


#|
<doc EXT-SYNTAX define-struct
 * (define-struct <name> <slot> ...)
 *
 * Defines a structure type whose name is |<name>|. Once a structure type is
 * defined, the following symbols are bound:
 *
 * - |<name>| denotes the structure type.
 *
 * - |make-<name>| is a procedure which takes 0 to |n| parameters (if there
 *   are |n| slots defined). Each parameter is assigned to the corresponding
 *   field (in the definition order).
 *
 * - |<name>?| is a predicate which returns `#t` when applied to an
 *   instance of the |<name>| structure type and `#f` otherwise
 *
 * - |<name>-<slot>| (one for each defined |<slot>|) to read the
 *   content of an instance of the |<name>| structure type. Writting the
 *   content of a slot can be done using a generalized |set!|.
 *
 * @lisp
 * (define-struct point x y)
 * (define p (make-point 1 2))
 * (point? p)    => #t
 * (point? 100)  => #f
 * (point-x p)   => 1
 * (point-y p)   => 2
 * (set! (point-x p) 10)
 * (point-x p)   => 10
 * @end lisp
 *
doc>
|#
(define-macro (define-struct name . slots)
  (define (compute-offset slot slots)
    (let ((sublist (memq slot slots)))
      (- (length slots) (length sublist))))

  (let* ((pred    (string->symbol (format "~a?" name)))
         (arg     (gensym))
         (val     (gensym)))
    `(begin
       ;; Build the structure
       (define ,name (make-struct-type ',name #f ',slots))

       ;; Build the constructor
       (define (,(string->symbol (format "make-~a" name)) . ,arg)
         (apply make-struct ,name ,arg))

       ;; Build the predicate
       (define (,pred ,arg)
         (and (struct? ,arg) (struct-is-a? ,arg ,name)))

       ;; Build the slot readers
       ,@(map (lambda (x)
                (let ((fname (string->symbol (format "~a-~a" name x))))
                  `(define ,fname
                     (lambda (,arg)
                       (%fast-struct-ref ,arg ,name ',fname
                                         ,(compute-offset x slots))))))
              slots)
       ;; Build the slot setters
       ,@(map (lambda (x)
                (let ((fname (string->symbol (format "~a-~a" name x))))
                  `(set! (setter ,fname)
                     (lambda (,arg ,val)
                       (%fast-struct-set! ,arg ,name ',fname
                                          ,(compute-offset x slots) ,val)))))
              slots)

       ;; Build the toplevel result
       (values (void) ',name))))
