#lang mzscheme
(require (for-syntax scheme/match
scheme/pretty
scheme/provide-transform
scheme/struct-info
(only srfi/1/list append-map)
srfi/26/cut
(planet untyped/unlib:3/debug)
(planet untyped/unlib:3/syntax)
"persistent-struct-info.ss"
"era/era.ss"
"sql/sql-syntax-util.ss")
mzlib/kw
(only scheme/base keyword-apply)
scheme/serialize
(only srfi/1/list iota filter)
"base.ss"
"persistent-struct.ss"
"persistent-struct-util.ss"
"era/era.ss"
(prefix q: "sql/sql-lang.ss"))
(define-for-syntax (make-attribute-ids stx before after entity-id attr-ids)
(list* (make-id stx before entity-id '-id after)
(make-id stx before entity-id '-revision after)
(map (cut make-id stx before entity-id '- <> after)
(syntax->list attr-ids))))
(define-syntax (define-persistent-struct stx)
(define name-stx #f) (define id-stx #f) (define struct-type-stx #f) (define constructor-stx #f) (define predicate-stx #f) (define constructor/defaults-stx #f) (define copy-struct-stx #f) (define deserialize-info-stx #f)
(define property-stxs null) (define entity-arg-stxs null)
(define attr-name-stxs null) (define attr-id-stxs null) (define attr-kw-stxs null) (define column-name-stxs null) (define accessor-stxs null) (define mutator-stxs null)
(define attr-type-stxs null) (define attr-kws null)
(define (parse-id+attrs stx)
(syntax-case stx ()
[(name attrs kw-arg ...)
(begin (set! name-stx #'name)
(set! id-stx (make-id #'name 'entity: #'name))
(set! struct-type-stx (make-id #'name 'struct: #'name))
(set! constructor-stx (make-id #'name 'make- #'name))
(set! predicate-stx (make-id #'name #'name '?))
(set! constructor/defaults-stx (make-id #'name 'make- #'name '/defaults))
(set! copy-struct-stx (make-id #'name 'copy- #'name))
(set! deserialize-info-stx (make-id #'name 'deserialize-info: #'name '-v0))
(set! attr-id-stxs (list (make-id #'name 'attr: #'name '-revision)
(make-id #'name 'attr: #'name '-id)))
(set! attr-kw-stxs (list #`(quote #,(datum->syntax-object #f (string->keyword "revision")))
#`(quote #,(datum->syntax-object #f (string->keyword "id")))))
(set! accessor-stxs (list (make-id #'name #'name '-revision)
(make-id #'name #'name '-id)))
(set! mutator-stxs (list (make-id #'name 'set- #'name '-revision!)
(make-id #'name 'set- #'name '-id!)))
(for-each parse-attr (syntax->list #'attrs))
(parse-entity-kws #'(kw-arg ...)))]))
(define (parse-attr stx)
(define (parse-attr-kws stx)
(syntax-case stx ()
[(kw other ...) (parse-attr-kw #'kw #'(other ...))]
[_ (finish-attr)]))
(define (parse-attr-kw kw-stx other-stx)
(match (syntax-object->datum kw-stx)
['#:column-name
(syntax-case other-stx ()
[(val other ...)
(begin (set! column-name-stxs (cons #'val column-name-stxs))
(parse-attr-kws #'(other ...)))])]))
(define (finish-attr)
(when (< (length column-name-stxs) (length attr-name-stxs))
(set! column-name-stxs (cons #`(quote #,(car attr-name-stxs)) column-name-stxs))))
(syntax-case stx ()
[(name type arg ...)
(begin (set! attr-name-stxs (cons #'name attr-name-stxs))
(set! attr-id-stxs (cons (make-id name-stx 'attr: name-stx '- #'name) attr-id-stxs))
(set! attr-type-stxs (cons #'type attr-type-stxs))
(set! attr-kw-stxs (cons #`(quote #,(datum->syntax-object #f (string->keyword (symbol->string (syntax-object->datum #'name))))) attr-kw-stxs))
(set! accessor-stxs (cons (make-id name-stx name-stx '- #'name) accessor-stxs))
(set! mutator-stxs (cons (make-id name-stx 'set- name-stx '- #'name '!) mutator-stxs))
(parse-attr-kws #'(arg ...)))]))
(define (parse-entity-kws stx)
(syntax-case stx ()
[(kw other ...) (parse-entity-kw #'kw #'(other ...))]
[() (finish-entity)]))
(define (parse-entity-kw kw-stx other-stx)
(match (syntax-object->datum kw-stx)
['#:property
(syntax-case other-stx ()
[(prop-id prop-val other ...)
(identifier? #'prop-id)
(begin (set! property-stxs (cons #'(cons prop-id prop-val) property-stxs))
(parse-entity-kws #'(other ...)))])]
[_
(syntax-case other-stx ()
[(val other ...)
(begin (set! entity-arg-stxs (list* #'val #`(quote #,kw-stx) entity-arg-stxs))
(parse-entity-kws #'(other ...)))])]))
(define (finish-entity)
(with-syntax ([name name-stx]
[entity id-stx]
[struct-type struct-type-stx]
[constructor constructor-stx]
[predicate predicate-stx]
[constructor/defaults constructor/defaults-stx]
[copy-struct copy-struct-stx]
[deserialize-info deserialize-info-stx]
[(attr-name ...) (reverse attr-name-stxs)]
[(attr-id ...) (reverse attr-id-stxs)]
[(attr-kw ...) (reverse attr-kw-stxs)]
[(attr-type ...) (reverse attr-type-stxs)]
[(column-name ...) (reverse column-name-stxs)]
[(accessor ...) (reverse accessor-stxs)]
[(mutator ...) (reverse mutator-stxs)]
[(property ...) (reverse property-stxs)]
[(entity-arg ...) (reverse entity-arg-stxs)]
[(attr-name* ...) (list* #'id #'revision (reverse attr-name-stxs))])
(quasisyntax/loc name-stx
(begin (begin (define-values (entity struct-type constructor predicate)
(let ([entity-args (list entity-arg ...)])
(keyword-apply make-persistent-struct-type
(append (list #:column-names #:properties)
(filter (lambda (x)
(keyword? x))
entity-args))
(append (list (list column-name ...)
(let ([properties (list property ...)])
(if (reserved-properties? properties)
(raise-exn exn:fail:snooze
(format "~a: cannot specify prop:entity or prop:serialize as an argument to define-persistent-struct." 'name))
(cons (cons prop:serializable
(make-serialize-info
(lambda (struct)
(list->vector (struct-attributes struct)))
(quote-syntax deserialize-info)
#t
(or (current-load-relative-directory) (current-directory))))
properties))))
(filter (lambda (x)
(not (keyword? x)))
entity-args))
(list 'name
(list 'attr-name ...)
(list attr-type ...)))))
(define-values (attr-id ...)
(apply values (entity-attributes entity)))
(define-values (accessor ...)
(apply values (map attribute-accessor (entity-attributes entity))))
(define-values (mutator ...)
(apply values (map attribute-mutator (entity-attributes entity))))
(define/kw (constructor/defaults #:key #,@(append-map (lambda (attr name)
(list #`[#,name (type-default (attribute-type #,attr))]))
(syntax->list #'(attr-id ...))
(syntax->list #'(attr-name* ...))))
((entity-constructor entity) attr-name* ...))
(define/kw (copy-struct original #:key #,@(append-map (lambda (accessor name)
(list #`[#,name (#,accessor original)]))
(syntax->list #'(accessor ...))
(syntax->list #'(attr-name* ...))))
((entity-constructor entity) attr-name* ...))
(define deserialize-info
(make-deserialize-info
(entity-constructor entity)
(lambda ()
(values constructor/defaults
copy-struct))))
(define default-alias
(q:alias 'name entity))
(define-syntaxes (name)
(let ([certify (syntax-local-certifier #t)])
(persistent-struct-info-set!
(lambda ()
(list (certify #'struct-type)
(certify #'constructor)
(certify #'predicate)
(reverse (list (certify #'accessor) ...))
(reverse (list (certify #'mutator) ...))
(certify #'persistent-struct)))
(certify #'name)
(certify #'struct-type)
(certify #'entity)
(certify #'constructor)
(certify #'constructor/defaults)
(certify #'copy-struct)
(certify #'predicate)
(certify #'default-alias)
(list (certify #'attr-id) ...)
(list (certify #'accessor) ...)
(list (certify #'mutator) ...)
(list 'attr-name* ...)))))))))
(syntax-case stx ()
[(_ arg ...)
(parse-id+attrs #'(arg ...))]))
(define-syntax (provide-persistent-struct stx)
(syntax-case stx ()
[(_ id (attr-id ...))
(with-syntax ([transformer (make-id #'id #'id)]
[struct-type (make-id #'id 'struct: #'id)]
[constructor (make-id #'id 'make- #'id)]
[constructor/defaults (make-id #'id 'make- #'id '/defaults)]
[copy (make-id #'id 'copy- #'id)]
[deserialize-info (make-id #'id 'deserialize-info: #'id '-v0)]
[predicate (make-id #'id #'id '?)]
[entity (make-id #'id 'entity: #'id)]
[(attribute ...) (make-attribute-ids #'id 'attr: '|| #'id #'(attr-id ...))]
[(accessor ...) (make-attribute-ids #'id '|| '|| #'id #'(attr-id ...))]
[(mutator ...) (make-attribute-ids #'id 'set- '! #'id #'(attr-id ...))])
#'(provide entity
attribute ...
id
struct-type
constructor
constructor/defaults
copy
deserialize-info
predicate
accessor ...
mutator ...))]))
(define-syntax persistent-struct-out
(make-provide-transformer
(lambda (stx modes)
(define (create-export id-stx)
(make-export id-stx (syntax-object->datum id-stx) 0 #f id-stx))
(syntax-case stx ()
[(_ id (attr-id ...))
(let ([struct-type (make-id #'id 'struct: #'id)]
[constructor (make-id #'id 'make- #'id)]
[constructor/defaults (make-id #'id 'make- #'id '/defaults)]
[copy (make-id #'id 'copy- #'id)]
[deserialize-info (make-id #'id 'deserialize-info: #'id '-v0)]
[predicate (make-id #'id #'id '?)]
[entity (make-id #'id 'entity: #'id)]
[attributes (make-attribute-ids #'id 'attr: '|| #'id #'(attr-id ...))]
[accessors (make-attribute-ids #'id '|| '|| #'id #'(attr-id ...))]
[mutators (make-attribute-ids #'id 'set- '! #'id #'(attr-id ...))])
(map create-export
(append (list #'id
struct-type
constructor
constructor/defaults
copy
deserialize-info
predicate
entity)
attributes
accessors
mutators)))]))))
(define-syntax (define/provide-persistent-struct stx)
(syntax-case stx ()
[(_ name ([attr-id attr-arg ...] ...) entity-arg ...)
#'(begin (define-persistent-struct name ([attr-id attr-arg ...] ...) entity-arg ...)
(provide-persistent-struct name (attr-id ...)))]))
(define (reserved-properties? prop-alist)
(ormap (lambda (prop)
(or (eq? prop prop:entity)
(eq? prop prop:serializable)))
(map car prop-alist)))
(provide define-persistent-struct
provide-persistent-struct
persistent-struct-out
define/provide-persistent-struct)