#lang scribble/manual
@require[@for-label[phc-toolkit/stx
                    (only-meta-in 0 phc-toolkit/meta-struct)
                    (only-meta-in 1 phc-toolkit/untyped/meta-struct)
                    racket/base
                    racket/struct-info]]

@title{meta operations on structs}
@author{@author+email["Suzanne Soy" "racket@suzanne.soy"]}

@section{Typed macros and procedures}

@defmodule[phc-toolkit/meta-struct
           #:use-sources
           [(submod (lib "phc-toolkit/meta-struct.rkt") typed)]]

@defform[(struct-predicate s)
         #:grammar [[s meta-struct?]]]{
 Expands to a predicate for the given @racket[struct], with the
 type @racket[(-> any/c boolean? : s)].}

@defform[(struct-constructor s)
         #:grammar [[s meta-struct?]]]{
 This macro expands to the constructor function for the given @racket[struct],
 with the type @racket[(-> _arg … s)] where each @racket[_arg] corresponds to an
 argument expected by the @racket[struct]'s constructor.}

@defform*[{(struct-accessor s i)
           (struct-accessor s field)}
          #:grammar [[s meta-struct?]
                     [i (expr/c exact-nonnegative-integer?)]
                     [field identifier?]]]{
 This macro expands to the @racket[i]-th accessor function for the given
 @racket[struct], with the type @racket[(-> s _t)] where @racket[_t] is the
 @racket[struct]'s @racket[_i]-th field's type.

 If the second argument is an identifier, then this macro concatenates the
 identifiers @racket[s] and @racket[field] with a @racket[-] in between, and
 expands to the resulting @racket[_s-field]. The lexical context of
 @racket[_s-field] is the same as the lexical context of @racket[s]. In some
 rare cases this might not resolve to the accessor for @racket[field] on
 @racket[s]. Passing an @racket[exact-nonnegative-integer?] as the second
 argument should be more reliable.}

@defproc[#:kind "phase 1 procedure"
         (struct-type-is-immutable? [st Struct-TypeTop])
         boolean?]{
 Returns @racket[#t] if the given struct type can be determined
 to have only immutable fields. Returns @racket[#f] otherwise.}

@defproc[(struct-instance-is-immutable? [v struct?])
         boolean?]{
 Returns @racket[#t] if @racket[v] can be determined to be an instance of an
 immutable struct. Returns @racket[#f] otherwise. Note that when given an
 instance of an opaque struct @racket[struct-instance-is-immutable?] cannot
 access the struct info, and therefore returns @racket[#f].}

@include-section{meta-struct-untyped.scrbl}

@section{Untyped for-syntax utilities}

@defmodule[phc-toolkit/untyped/meta-struct
           #:use-sources
           [(submod (lib "phc-toolkit/meta-struct.rkt") untyped)]]

@defproc[(meta-struct? [v any/c]) boolean?]{
 Returns @racket[#t] if @racket[v] can be used by the
 functions provided by this module, and @racket[#f]
 otherwise. More precisely, @racket[v] must be an 
 @racket[identifier] whose @racket[syntax-local-value] is a
 @racket[struct-info?].

 @history[#:changed "1.0" "This function is provided at phase 1."]}

@defstruct[meta-struct-info ([type-descriptor (or/c identifier? #f)]
                             [constructor (or/c identifier? #f)]
                             [predicate (or/c identifier? #f)]
                             [accessors (list*of identifier?
                                                 (or/c (list/c #f) null?))]
                             [mutators (list*of (or/c identifier? #f)
                                                (or/c (list/c #f) null?))]
                             [super-type (or/c identifier? #f)])]{
 Encapsulates the result of @racket[extract-struct-info] in
 a structure with named fields, instead of an obscure
 six-element list. The precise contents of each field is
 described in 
 @secref["structinfo" #:doc '(lib "scribblings/reference/reference.scrbl")].

 @history[#:changed "1.0" "The identifiers are provided at phase 1."]}

@defproc[(get-meta-struct-info [s meta-struct?]
                               [#:srcloc srcloc (or/c #f syntax?) #f])
         meta-struct-info?]{
 Returns the @racket[meta-struct-info] for the given
 identifier. The optional @racket[#:srcloc] keyword argument
 gives the source location for error messages in case the
 given identifier is not a @racket[meta-struct?].

 @history[#:changed "1.0" "This function is provided at phase 1."]}
 
@defproc[(meta-struct-subtype? [sub meta-struct?] [super meta-struct?])
         boolean?]{
 Returns @racket[#t] if the @racket[struct] associated to
 the identifier @racket[sub] is a subtype of the 
 @racket[struct] associated to the identifier 
 @racket[super], and @racket[#f] otherwise or if the current
 inspector is not strong enough to know.

 @history[#:changed "1.0" "This function is provided at phase 1."]}

@defproc[#:kind "phase 1 procedure"
         (struct-type-id-is-immutable? [id identifier?])
         boolean?]{
 Returns @racket[#t] if the struct with the given @racket[id] can be determined
 to have only immutable fields. Returns @racket[#f] otherwise.}

@(require (for-syntax racket/base
                      racket/syntax
                      racket/struct
                      racket/vector))

@(define-for-syntax (strip-loc e)
   (cond [(syntax? e) (datum->syntax e (strip-loc (syntax-e e)) #f)]
         [(pair? e) (cons (strip-loc (car e)) (strip-loc (cdr e)))]
         [(vector? e) (vector-map strip-loc e)]
         [(box? e) (box (strip-loc (unbox e)))]
         [(prefab-struct-key e)
          => (λ (k) (apply make-prefab-struct
                           k
                           (strip-loc (struct->list e))))]
         [else e]))

@(define-syntax (shorthand stx)
   (syntax-case stx ()
     [(_ base expresion-type)
      (with-syntax ([loc (datum->syntax #'base #'base #f)]
                    [name (format-id #'base "meta-struct-~a" #'base)]
                    [accessor (format-id #'base "meta-struct-info-~a" #'base)]
                    [tmpl (format-id #'base "!struct-~a" #'base)])
        #`(deftogether
            [(defproc (name [s meta-struct?]
                            [#:srcloc srcloc (or/c #f syntax?) #f])
               (expressionof
                (→ s #,(strip-loc #'expresion-type))))
             (defform #:kind "template metafunction"
               (tmpl #,(strip-loc #'s) #,(strip-loc #'maybe-srcloc))
               #:grammar ([s meta-struct?]
                          [maybe-srcloc (code:line)
                           #||#         (code:line #:srcloc srcloc)]))]
            @list{
       @;{}   Shorthand for @racket[(accessor (get-meta-struct-info s))]
       @;{}   (with the optional @racket[#:srcloc] passed to
       @;{}   @racket[get-meta-struct-info]). The precise contents of the
       @;{}   returned value field is described in 
       @;{}   @secref["structinfo"
                      #:doc '(lib "scribblings/reference/reference.scrbl")].
       @;{}
       @;{}   @history[#:changed "1.0"
                       "This function is provided at phase 1."]}))]))

@(shorthand type-descriptor (or/c identifier? #f))
@(shorthand constructor (or/c identifier? #f))
@(shorthand predicate (or/c identifier? #f))
@(shorthand accessors (list*of identifier?
                               (or/c (list/c #f) null?)))
@(shorthand mutators (list*of (or/c identifier? #f)
                              (or/c (list/c #f) null?)))
@(shorthand super-type (or/c identifier? #f))