#lang racket
(require 2htdp/universe)
(require "../semantics/first-order.rkt")
(require racket/stxparam)
(require (for-syntax "../utilities.rkt"))
(require (for-syntax racket/list))
(require (for-syntax syntax/stx))
(provide animate)
(provide (rename-out (big-bang-wrapper big-bang)))
(define-syntax (big-bang-wrapper stx)
(define CDL
'((to_draw draw_width draw_height)
(on_tick tick_rate tick_limit)
(on_key)
(on_pad)
(on_release)
(on_mouse)
(stop_when last_scene)
(check_with)
(should_record)
(on_receive)
(state)
(name)
(register)))
(define MCKWL
'(to_draw on_tick on_key on_pad on_release stop_when check_with should_record state name register on_receive))
(define VCKWL '(init
to_draw draw_width draw_height
on_tick tick_rate tick_limit
on_key
on_pad
on_release
stop_when last_scene
check_with
should_record
state
name
register
on_receive))
(define (convert s)
(case s
[(to_draw) 'to-draw]
[(on_tick) 'on-tick]
[(stop_when) 'stop-when]
[(on_key) 'on-key]
[(on_pad) 'on-pad]
[(on_release) 'on-release]
[(stop_when) 'stop-when]
[(should_record) 'record?]
[(state) 'state]
[(name) 'name]
[(register) 'register]
[(on_receive) 'on-receive]
[else (error 'convert "pyret: internal error: invalid input")]))
(define (check1 sl)
(define (check c)
(syntax-case c ()
[(clause-car clause-cdr)
(identifier? (syntax clause-car))
c]
[(clause-car clause-cdr)
(raise-pyret-error/stx
"big_bang: first element of a clause must be a keyword"
(syntax clause-car))]
[else
(raise-pyret-error/stx
"big-bang-wrapper: invalid clause"
c)]))
(map check sl))
(define (check2 sl)
(define (check-kw clause)
(let ([clause-kw (syntax-e (stx-car clause))])
(if (member clause-kw VCKWL)
clause
(raise-pyret-error/stx
(format "big_bang: ~a clauses are not allowed when using big_bang"
clause-kw)
(stx-car clause)))))
(map check-kw sl))
(define (check3 sl)
(define (filter-proc target-clause)
(lambda (current-clause)
(let ([target-kw (syntax-e (stx-car target-clause))]
[current-kw (syntax-e (stx-car current-clause))])
(equal? target-kw current-kw))))
(define (map-proc clause)
(let ([result (filter (filter-proc clause) sl)])
(if (> (length result) 1)
(let ([clause-kw (syntax-e (stx-car clause))]
[example-clause (stx-car (stx-cdr result))])
(raise-pyret-error/stx
(format "big_bang: found a duplicate ~a clause" clause-kw)
(stx-car example-clause)))
clause)))
(map map-proc sl))
(define (find-deps kw dkws stx)
(let loop ([left-to-find dkws] [stx stx] [found-clauses empty])
(cond
[(empty? left-to-find)
(list stx found-clauses)]
[else
(let ([f (first left-to-find)]
[r (rest left-to-find)])
(let ([result (wrip-out stx f)])
(let ([next-stx (first result)]
[found (first (rest result))])
(if found
(loop r next-stx (append found-clauses (list found)))
(loop r next-stx found-clauses)))))])))
(define (make-clauses sym vals)
(define (get-clause-dependencies kw)
(lambda (l)
(equal? kw (first l))))
(define (convert/stx stx-id)
(datum->syntax #'this
(convert (syntax-e stx-id))
stx-id))
(let ([kw-dl (rest (findf (get-clause-dependencies sym) CDL))]
[clause-list (first vals)]
[generated-clauses (second vals)])
(let* ([main-result (wrip-out clause-list sym)]
[next-cl (first main-result)]
[found (second main-result)])
(let* ([deps-result (find-deps sym kw-dl next-cl)]
[next-cl (first deps-result)]
[found-deps (second deps-result)]
[len-found-deps (length found-deps)])
(cond
[(equal? found #f)
(cond
[(empty? found-deps)
(list next-cl generated-clauses)]
[else
(let* ([example (first found-deps)]
[found-kw (stx-car example)])
(raise-pyret-error/stx
(format
(string-append "found a ~a clause, "
"which requires the ~a clause, "
"but no ~a clause was found")
(syntax-e found-kw)
sym
sym)
found-kw))])]
[else
(let ([main-kw-stx (first found)]
[main-kw-expr-stx (first (rest found))])
(cond
[(empty? found-deps)
(list next-cl
(append generated-clauses
(list
(list (convert/stx main-kw-stx)
main-kw-expr-stx))))]
[(equal? len-found-deps (length kw-dl))
(list next-cl
(append generated-clauses
(list
(cons (convert/stx main-kw-stx)
(cons main-kw-expr-stx
(map (λ (c) (stx-car (stx-cdr c)))
found-deps))))))]
[else
(case sym
[(on_tick)
(if (equal? (syntax-e (stx-car (first found-deps))) 'tick_rate)
(list next-cl
(append generated-clauses
(list
(cons (convert/stx main-kw-stx)
(cons main-kw-expr-stx
(map (λ (c) (stx-car (stx-cdr c)))
found-deps))))))
(raise-pyret-error/stx
"big_bang: found a tick_limit clause, which requires a tick_rate clause"
(stx-car (first found-deps))))]
[else
(raise-pyret-error/stx
(format
(string-append
"big_bang: when using the ~a clause, either all, "
"or none, of the following clauses must be used: "
(foldl (λ (sym str) (string-append str " " (symbol->string sym)))
""
kw-dl))
sym)
main-kw-stx)])]))])))))
(define (wrip-out clause-list target)
(let loop ([seen '()]
[togo clause-list])
(cond
[(stx-null? togo)
(list seen #f)]
[else
(let ([f (stx-car togo)]
[r (stx-cdr togo)])
(syntax-case f ()
[(clause-kw clause-expr)
(identifier? (syntax clause-kw))
(cond
[(equal? target (syntax-e (syntax clause-kw)))
(list (append seen r)
(list (syntax clause-kw) (syntax clause-expr)))]
[else
(loop (append seen (list f)) r)])]
[else
(error 'wrip-out
"pyret: internal error: wrip-out did not get a valid clause")]))])))
(syntax-case stx ()
[(bbw [clause-kw clause-expr] ...)
(let ([clause-list
(check3
(check2
(check1
(stx->list
(syntax ([clause-kw clause-expr] ...))))))])
(let* ([init-result (wrip-out clause-list 'init)]
[next-stx (first init-result)]
[init (second init-result)])
(unless init
(raise-pyret-error/stx
"big_bang: a big_bang requires an `init` clause"
stx))
(let ([init-expr (second init)])
(let* ([result (foldl make-clauses (list next-stx empty) MCKWL)]
[next-stx (first result)]
[generated-clauses (second result)])
(unless (stx-null? next-stx)
(error 'big-bang-wrapper "pyret: internal error: clauses have been left over"))
(with-syntax ([clauses generated-clauses]
[ie init-expr])
(syntax/loc stx
(make-big-bang ie clauses)))))))]
[else
(raise-pyret-error/stx
"pyret: internal error: the parser has generated an invalid big_bang"
stx)]))
(define-syntax (make-big-bang stx)
(syntax-case stx ()
[(_ init ([clause-kw clause-expr ...] ...))
(let ([clause-list (syntax->list (syntax ([clause-kw clause-expr ...] ...)))])
(if (findf (lambda (c) (equal? 'to-draw (first c)))
(map syntax->datum clause-list))
(syntax/loc stx
(syntax-parameterize ([be-first-order #f])
(big-bang init [clause-kw clause-expr ...] ...)))
(raise-pyret-error/stx
"big_bang: expects a to_draw clause"
stx)))]))