#lang scheme/base
(require "depend.ss"
(for-syntax scheme/base
(only-in "depend.ss"
string-join
syntax-map
syntax-identifier-append
arg->identifier
args->identifiers
)
)
"query.ss"
)
(define converter-registry (make-assoc-registry))
(define (converter-set! isa? converter)
(registry-set! converter-registry isa? converter))
(define (converter-del! isa?)
(registry-del! converter-registry isa?))
(define (converter-ref isa? (default #f))
(registry-ref converter-registry isa? default))
(converter-set! identity identity)
(define-struct not-passed-in ())
(define-syntax scalar-converter!
(syntax-rules ()
((~ (isa? convert) ...)
(lambda (v)
(cond ((isa? v) (convert v))
...
(else (error 'invalid "~s" v)))))
))
(define-syntax define-scalar-converter!
(syntax-rules ()
((~ type? (isa? convert) ...)
(converter-set! type?
(scalar-converter! (type? identity) (isa? convert) ...)))
))
(define-scalar-converter! number?
(string? string->number))
(define-scalar-converter! string?)
(define-scalar-converter! bytes?
(string? string->bytes/utf-8))
(define-struct list-of (isa? min max))
(define (listof: isa? (min 0) (max +inf.0))
(make-list-of isa? min max))
(define (convert-one converter v)
(let ((res (converter v)))
(if (not res)
(error 'invalid-conversion "~s" v)
res)))
(define (convert-listof converter v min max)
(define (helper lst)
(if (<= min (length lst) max)
(map (lambda (v)
(convert-one converter v))
lst)
(error 'convert-outside-range "[~a,~a]" min max)))
(helper (cond ((list? v) v)
((vector? v) (vector->list v))
(else (list v)))))
(define (run-convert isa? v)
(if (list-of? isa?)
(let ((isa? (list-of-isa? isa?))
(min (list-of-min isa?))
(max (list-of-max isa?)))
(if-it (converter-ref isa?)
(convert-listof it v min max)
(error 'convert-unknown-type "~s" isa?)))
(if-it (converter-ref isa?)
(convert-one it v)
(error 'convert-unknown-type "~s" isa?))))
(define-struct convert-spec (key isa? default)
#:property prop:procedure
(lambda ($s v)
(if (no-value? v)
(if (not-passed-in? (convert-spec-default $s))
(error 'required "~a" (convert-spec-key $s))
(convert-spec-default $s))
(run-convert (convert-spec-isa? $s) v))))
(define (build-convert-spec key
(isa? identity)
(default (make-not-passed-in)))
(define (ensure-isa?-exists! isa?)
(if (list-of? isa?)
(ensure-isa?-exists! (list-of-isa? isa?))
(unless (converter-ref isa?)
(error 'unknown-isa? "~s" isa?))))
(ensure-isa?-exists! isa?)
(make-convert-spec key isa? default))
(define-syntax convert-spec!
(syntax-rules ()
((~ (key isa? default))
(build-convert-spec 'key isa? default))
((~ (key isa?))
(build-convert-spec 'key isa?))
((~ key)
(build-convert-spec 'key))
))
(define (convert-from-hash converters hash)
(map (lambda (converter)
(converter (hash-ref hash (symbol->string (convert-spec-key converter)) #f)))
converters))
(define use-webcall? (make-parameter #f))
(define-struct args-converter (specs)
#:property prop:procedure
(lambda ($s v)
(let ((specs (args-converter-specs $s)))
(if (hash? v)
(convert-from-hash specs v)
(map (lambda (spec v)
(spec v))
specs
v)))))
(define-syntax args-converter!
(syntax-rules ()
((~ (spec ...))
(make-args-converter (list (convert-spec! spec) ...)))
))
(define-struct composite-converter (maker specs)
#:property prop:procedure
(lambda ($s v)
(if (hash? v)
(apply (composite-converter-maker $s)
(convert-from-hash (composite-converter-specs $s) v))
(error 'invalid-argument "~s" v))))
(define-struct (struct-convert-spec convert-spec) (ref))
(define-syntax struct-convert-spec!
(syntax-rules ()
((~ spec ref)
(let ((s (convert-spec! spec)))
(make-struct-convert-spec (convert-spec-key s)
(convert-spec-isa? s)
(convert-spec-default s)
ref)))
))
(define-syntax (struct-converter! stx)
(syntax-case stx ()
((~ name (spec ...))
(with-syntax ((maker (syntax-identifier-append 'make- #'name))
(isa? (syntax-identifier-append #'name '?))
((ref ...) (syntax-map (lambda (spec)
(syntax-identifier-append
#'name
'-
(arg->identifier spec)))
#'(spec ...))))
#'(make-composite-converter maker
(list (struct-convert-spec! spec ref) ...))))
))
(define-syntax (define-struct-converter! stx)
(syntax-case stx ()
((~ name (spec ...))
(with-syntax ((isa? (syntax-identifier-append #'name '?)))
#'(converter-set! isa?
(struct-converter! name (spec ...)))))
))
(define (unconvert v (scalar-convert identity))
(define (helper converter)
(if (and converter
(composite-converter? (cdr converter)))
(make-immutable-hash (map (lambda (spec)
(cons (symbol->string (convert-spec-key spec))
(unconvert ((struct-convert-spec-ref spec) v)
scalar-convert)))
(composite-converter-specs (cdr converter))))
(scalar-convert v)))
(cond ((list? v)
(map (lambda (v)
(unconvert v scalar-convert)) v))
((hash? v)
(make-immutable-hash (hash-map v (lambda (k v)
(cons k (unconvert v scalar-convert))))))
(else
(helper (assf (lambda (test) (test v))
(registry-table converter-registry))))))
(define-struct webcall (args inner)
#:property prop:procedure
(lambda ($s . args) (if (use-webcall?)
(if (and (pair? args)
(hash? (car args)))
(apply (webcall-inner $s) ((webcall-args $s) (car args)))
(apply (webcall-inner $s) ((webcall-args $s) args)))
(apply (webcall-inner $s) args))))
(define-syntax (call! stx)
(syntax-case stx ()
((~ (arg ...) exp ... exp2)
(with-syntax (((key ...) (args->identifiers #'(arg ...))))
#'(make-webcall (args-converter! (arg ...))
(lambda (key ...) exp ... exp2))))
))
(define-syntax define-call!
(syntax-rules ()
((~ (name arg ...) exp exp2 ...)
(define name (call! (arg ...) exp exp2 ...)))
))
(provide define-call!
call!
define-struct-converter!
define-scalar-converter!
)
(provide/contract
(struct webcall ((args procedure?)
(inner procedure?)))
(use-webcall? (parameter/c boolean?))
(struct convert-spec ((key symbol?)
(isa? procedure?)
(default any/c)))
(struct args-converter ((specs (listof convert-spec?))))
(struct list-of ((isa? procedure?)
(min exact-nonnegative-integer?)
(max exact-nonnegative-integer?)))
(converter-ref (->* (procedure?)
(any/c)
any))
(converter-set! (-> procedure? procedure? any))
(unconvert (->* (any/c)
(procedure?)
any))
)