#lang racket
(require lang/prim
syntax/parse)
(require (for-syntax syntax/parse racket/bool))
(require "tfield.rkt")
(define-syntax (define/web stx)
(define-syntax-class cmp-type (pattern (~and header
(~or (~datum constant) (~datum structure) (~datum check)
(~datum oneof) (~datum listof) (~datum listof+)
(~datum function)))))
(syntax-parse stx
[(define/web id:identifier t:cmp-type x ...)
#`(define id (web-spec t.header x ...))]
[(define/web id:identifier l:str t:cmp-type x ...)
#'(define id (list l (web-spec t.header x ...)))]
[(define/web id:identifier s)
#'(define id (web-spec s))]
[(define/web id:identifier l:str s)
#'(define id (list l (web-spec s)))]))
(define-syntax (web-spec stx)
(define-syntax-class all-types
(pattern (~and header
(~or (~datum number) (~datum boolean) (~datum string)
(~datum string+) (~datum symbol)
(~datum constant) (~datum structure) (~datum check)
(~datum oneof) (~datum listof) (~datum listof+)
(~datum function)))))
(define-syntax-class cmp-type
(pattern (~and header
(~or (~datum constant) (~datum structure) (~datum check)
(~datum oneof) (~datum listof) (~datum listof+)
(~datum function)))))
(define-syntax-class lab-spec/sp
#:attributes [output]
#:description "a pair of a label and a type specification"
[pattern x:all-types #:attr output
(raise-syntax-error #f (format "expected a pair of a label and a type specification surrounded by parens instead of ~s" (syntax->datum #'x))
#'x )]
[pattern (lab:str spc) #:attr output #'(list lab (web-spec spc))]
[pattern x:identifier
#:fail-when (symbol=? '-> (syntax->datum #'x)) "problem?"
#:attr output #'x]
[pattern (~and bad (a b c ...+)) #:attr output
(raise-syntax-error #f (format "expected a pair of a label and a type specification surrounded by parens instead of ~a things"
(length (syntax->datum #'bad)))
#'bad)]
[pattern (~and z
(~fail #:when (symbol? (syntax->datum #'z))))
#:attr output
(raise-syntax-error #f (format "expected a pair of a label and a type specification surrounded by parens instead of ~s" (syntax->datum #'z))
#'z )])
(syntax-parse stx
[(web-spec ((~datum constant) v)) #'(list 'constant v)]
[(web-spec (~datum number)) #'(quote number)]
[(web-spec (~datum boolean)) #'(quote boolean)]
[(web-spec (~datum string)) #'(quote string)]
[(web-spec (~datum string+)) #'(quote string+)]
[(web-spec (~datum symbol)) #'(quote symbol)]
[(web-spec ((~datum function) txt:str (f:identifier
p:lab-spec/sp ...
(~datum ->)
r:lab-spec/sp )))
#`(list 'function txt (list (first-order->higher-order f)
p.output ... '-> r.output))]
[(web-spec ((~datum structure) ~! constr x:lab-spec/sp ...+))
#'(list 'structure (first-order->higher-order constr)
x.output ...)]
[(web-spec ((~datum oneof) ~! x:lab-spec/sp ...+))
#`(list 'oneof x.output ...)]
[(web-spec ((~datum listof) ~! x:lab-spec/sp))
#'(list 'listof x.output)]
[(web-spec ((~datum listof+) ~! x:lab-spec/sp))
#'(list 'listof+ x.output)]
[(web-spec x:identifier) #'x]
[(web-spec ((~datum constant) z ...)) #'(list 'constant z ...)]
[(web-spec ((~datum structure) z ...)) #'(list 'structure z ...)]
[(web-spec (t:cmp-type z))
(raise-syntax-error #f "problem 1" #'z)]
[(web-spec (~and (~seq t:cmp-type z ...) (~seq x y ...)))
(raise-syntax-error #f
(format
"~a should be preceded by an open parens"
(syntax->datum #'t.header))
#'(x y ...)
#'x)]
[(web-spec ((~datum oneof) z ...)) #'(list 'oneof z ...)]
[(web-spec (x y ...)) (raise-syntax-error
#f "unexpected start of specification" #'x)]
[(web-spec x) (raise-syntax-error #f "unexpected expression" #'x)]))
(define (parse/lab-spec lab+spec)
((parse/web-spec (second lab+spec)) (first lab+spec)))
(define (parse/web-spec spec)
(match spec
['number new-tfield/number]
['boolean new-tfield/boolean]
['string new-tfield/string]
['string+ (λ(lab) (new-tfield/string lab #f #t))]
['symbol new-tfield/symbol]
[(list 'constant v) (λ(s) (new-tfield/const s v))]
[(list 'function txt (list func params ... '-> rspec))
(λ(s) (new-tfield/function s txt func (map parse/lab-spec params)
(parse/lab-spec rspec)))]
[(list 'structure constr params ...)
(λ(s) (new-tfield/struct s constr (map parse/lab-spec params)))]
[(list 'oneof ops ...)
(λ(s) (new-tfield/oneof s (map parse/lab-spec ops)))]
[(list 'listof t)
(λ(s) (new-tfield/listof s (parse/lab-spec t)))]
[(list 'listof+ t)
(λ(s) (new-tfield/listof s (parse/lab-spec t) empty #t))]))
(provide parse/web-spec web-spec)
(provide define/web)