#lang scheme/base
(require (for-syntax scheme/base
scheme/match
scheme/pretty
scheme/provide-transform
scheme/struct-info
(only-in srfi/1 append-map)
srfi/26
(planet untyped/unlib:3/debug)
(planet untyped/unlib:3/syntax)
"persistent-struct-info.ss"
"era/era.ss"
"sql/sql.ss")
scheme/serialize
"base.ss"
"persistent-struct.ss"
"era/era.ss"
(prefix-in sql: "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 (attr stx)
(syntax-case stx ()
[(_ entity-id attr-id)
(and (identifier? #'entity-id)
(identifier? #'attr-id))
(let ([info (persistent-struct-info-ref #'entity-id)]
[attr-sym (syntax->datum #'attr-id)])
(or (for/or ([attr (in-list (persistent-struct-info-attribute-ids info))]
[sym (in-list (persistent-struct-info-attribute-names info))])
(and (eq? attr-sym sym) attr))
(raise-syntax-error #f "attribute not found" #'attr-id stx)))]))
(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 (datum->syntax #f (string->keyword "revision"))
(datum->syntax #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->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 (datum->syntax #f (string->keyword (symbol->string (syntax->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->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 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))]
[all-properties (if (eq? (syntax-local-context) 'module)
#'(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)
#'properties)])
(quasisyntax/loc name-stx
(begin (begin (define-values (entity struct-type constructor predicate)
(make-persistent-struct-type 'name
(list 'attr-name ...)
(list attr-type ...)
#:column-names
(list column-name ...)
#:properties
(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))
all-properties))
entity-arg ...))
(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 (constructor/defaults #,@(append-map (lambda (kw attr name)
(list kw #`[#,name (type-default (attribute-type #,attr))]))
(syntax->list #'(attr-kw ...))
(syntax->list #'(attr-id ...))
(syntax->list #'(attr-name* ...))))
((entity-constructor entity) attr-name* ...))
(define (copy-struct original #,@(append-map (lambda (kw accessor name)
(list kw #`[#,name (#,accessor original)]))
(syntax->list #'(attr-kw ...))
(syntax->list #'(accessor ...))
(syntax->list #'(attr-name* ...))))
((entity-constructor entity) attr-name* ...))
#,(if (eq? (syntax-local-context) 'module)
#'(begin
(define deserialize-info
(make-deserialize-info
(entity-constructor entity)
(lambda ()
(values constructor/defaults
copy-struct))))
(provide deserialize-info))
#'(begin))
(define default-alias
(sql: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 persistent-struct-extras-out
(make-provide-transformer
(lambda (stx modes)
(define (create-export id-stx)
(make-export id-stx (syntax->datum id-stx) 0 #f id-stx))
(syntax-case stx ()
[(_ id)
(let ([constructor/defaults (make-id #'id 'make- #'id '/defaults)]
[copy (make-id #'id 'copy- #'id)]
[entity (make-id #'id 'entity: #'id)]
[attributes (persistent-struct-info-attribute-ids (persistent-struct-info-ref #'id))])
(map create-export (append (list constructor/defaults copy entity) attributes)))]))))
(define-syntax persistent-struct-out
(make-provide-transformer
(lambda (stx modes)
(syntax-case stx ()
[(_ id)
(append (expand-export #'(struct-out id) modes)
(expand-export #'(persistent-struct-extras-out id) modes))]))))
(define (reserved-properties? prop-alist)
(ormap (lambda (prop)
(or (eq? prop prop:entity)
(eq? prop prop:serializable)))
(map car prop-alist)))
(provide attr
define-persistent-struct
persistent-struct-out
persistent-struct-extras-out)