2875 lines
135 KiB
Racket
2875 lines
135 KiB
Racket
#lang racket/base
|
||
|
||
(require "matcher.rkt"
|
||
"struct.rkt"
|
||
"term.rkt"
|
||
"fresh.rkt"
|
||
"loc-wrapper.rkt"
|
||
"error.rkt"
|
||
racket/trace
|
||
racket/contract
|
||
racket/list
|
||
mzlib/etc)
|
||
|
||
(require (for-syntax syntax/name
|
||
"loc-wrapper-ct.rkt"
|
||
"rewrite-side-conditions.rkt"
|
||
"term-fn.rkt"
|
||
"underscore-allowed.rkt"
|
||
syntax/boundmap
|
||
syntax/id-table
|
||
scheme/base
|
||
racket/list
|
||
racket/match
|
||
racket/syntax
|
||
syntax/parse
|
||
syntax/parse/experimental/contract
|
||
syntax/name))
|
||
|
||
(define (language-nts lang)
|
||
(hash-map (compiled-lang-ht lang) (λ (x y) x)))
|
||
|
||
(define-for-syntax (prune-syntax stx)
|
||
(datum->syntax
|
||
(identifier-prune-lexical-context #'whatever '(#%app #%datum))
|
||
(let loop ([stx stx])
|
||
(syntax-case stx ()
|
||
[(a . b)
|
||
(datum->syntax (identifier-prune-lexical-context #'whatever '(#%app))
|
||
(cons (loop #'a) (loop #'b))
|
||
stx)]
|
||
[x
|
||
(identifier? #'x)
|
||
(identifier-prune-lexical-context #'x)]
|
||
[() (datum->syntax #f '() stx)]
|
||
[_ (datum->syntax (identifier-prune-lexical-context #'whatever '(#%datum))
|
||
(syntax->datum stx) stx)]))))
|
||
|
||
(define-for-syntax (term-matcher orig-stx make-matcher)
|
||
(syntax-case orig-stx ()
|
||
[(form-name lang [pattern rhs] ...)
|
||
(begin
|
||
(unless (identifier? #'lang)
|
||
(raise-syntax-error (syntax-e #'form-name) "expected an identifier in the language position" orig-stx #'lang))
|
||
(let ([lang-nts (language-id-nts #'lang (syntax-e #'form-name))])
|
||
(with-syntax ([(((names ...) (names/ellipses ...)) ...)
|
||
(map (λ (x) (call-with-values
|
||
(λ () (extract-names lang-nts (syntax-e #'form-name) #t x))
|
||
list))
|
||
(syntax->list (syntax (pattern ...))))]
|
||
[(side-conditions-rewritten ...)
|
||
(map (λ (x) (rewrite-side-conditions/check-errs lang-nts (syntax-e #'form-name) #t x))
|
||
(syntax->list (syntax (pattern ...))))]
|
||
[(cp-x ...) (generate-temporaries #'(pattern ...))]
|
||
[make-matcher make-matcher])
|
||
#'(make-matcher
|
||
'form-name lang
|
||
(list 'pattern ...)
|
||
(list (compile-pattern lang `side-conditions-rewritten #t) ...)
|
||
(list (λ (match)
|
||
(term-let/error-name
|
||
form-name
|
||
([names/ellipses (lookup-binding (mtch-bindings match) 'names)] ...)
|
||
rhs)) ...)))))]))
|
||
|
||
(define-syntax (term-match/single stx)
|
||
(term-matcher stx #'term-match/single/proc))
|
||
(define-syntax (term-match stx)
|
||
(term-matcher stx #'term-match/proc))
|
||
|
||
(define ((term-match/proc form-name lang ps cps rhss) term)
|
||
(append-map
|
||
(λ (cp rhs)
|
||
(let ([matches (match-pattern cp term)])
|
||
(if matches
|
||
(map rhs matches)
|
||
'())))
|
||
cps rhss))
|
||
|
||
(define ((term-match/single/proc form-name lang ps0 cps rhss) term)
|
||
(let loop ([ps ps0] [cps cps] [rhss rhss])
|
||
(if (null? ps)
|
||
(redex-error form-name
|
||
(if (null? (cdr ps0))
|
||
(format "term ~s does not match pattern ~s" term (car ps0))
|
||
(format "no patterns matched ~s" term)))
|
||
(let ([match (match-pattern (car cps) term)])
|
||
(if match
|
||
(begin
|
||
(unless (null? (cdr match))
|
||
(redex-error
|
||
form-name
|
||
"pattern ~s matched term ~s multiple ways"
|
||
(car ps)
|
||
term))
|
||
((car rhss) (car match)))
|
||
(loop (cdr ps) (cdr cps) (cdr rhss)))))))
|
||
|
||
(define-syntaxes (redex-let redex-let*)
|
||
(let ()
|
||
(define-syntax-class binding
|
||
#:description "binding clause"
|
||
(pattern (lhs:expr rhs:expr)))
|
||
(define-syntax-class (bindings extract)
|
||
#:description (if extract
|
||
"sequence of disjoint binding clauses"
|
||
"sequence of binding clauses")
|
||
(pattern (b:binding ...)
|
||
#:fail-when (and extract
|
||
(check-duplicate-identifier
|
||
(apply append (map extract (syntax->list #'(b.lhs ...))))))
|
||
"duplicate pattern variable"
|
||
#:with (lhs ...) #'(b.lhs ...)
|
||
#:with (rhs ...) #'(b.rhs ...)))
|
||
|
||
(define (redex-let stx)
|
||
(define-values (form-name nts)
|
||
(syntax-case stx ()
|
||
[(name lang . _)
|
||
(values (syntax-e #'name)
|
||
(language-id-nts #'lang (syntax-e #'name)))]))
|
||
(define (pattern-variables pattern)
|
||
(let-values ([(names _) (extract-names nts form-name #t pattern)])
|
||
names))
|
||
(syntax-parse stx
|
||
[(name lang (~var bs (bindings pattern-variables)) body ...+)
|
||
(with-syntax ([(t ...) (generate-temporaries #'bs)])
|
||
#`(let ([t bs.rhs] ...)
|
||
#,(nested-lets #'lang #'([bs.lhs t] ...) #'(body ...) #'name)))]))
|
||
|
||
(define (redex-let* stx)
|
||
(syntax-parse stx
|
||
[(name lang (~var bs (bindings #f)) body ...+)
|
||
(nested-lets #'lang #'bs #'(body ...) #'name)]))
|
||
|
||
(define (nested-lets lang bindings bodies name)
|
||
(syntax-case bindings ()
|
||
[()
|
||
#`(let () #,@bodies)]
|
||
[([lhs rhs] . bindings)
|
||
(with-syntax ([rest-lets (nested-lets lang #'bindings bodies name)])
|
||
#`(#,(term-matcher #`(#,name #,lang [lhs rest-lets])
|
||
#'term-match/single/proc)
|
||
rhs))]))
|
||
|
||
(values redex-let redex-let*)))
|
||
|
||
(define-syntax (compatible-closure stx)
|
||
(syntax-case stx ()
|
||
[(_ red lang nt)
|
||
(identifier? (syntax nt))
|
||
(with-syntax ([side-conditions-rewritten
|
||
(rewrite-side-conditions/check-errs (language-id-nts #'lang 'compatible-closure)
|
||
'compatible-closure
|
||
#t
|
||
(syntax (cross nt)))])
|
||
(syntax (do-context-closure red lang `side-conditions-rewritten 'compatible-closure)))]
|
||
[(_ red lang nt)
|
||
(raise-syntax-error 'compatible-closure "expected a non-terminal as last argument" stx (syntax nt))]))
|
||
|
||
(define-syntax (context-closure stx)
|
||
(syntax-case stx ()
|
||
[(_ red lang pattern)
|
||
(with-syntax ([side-conditions-rewritten
|
||
(rewrite-side-conditions/check-errs (language-id-nts #'lang 'context-closure)
|
||
'context-closure
|
||
#t
|
||
(syntax pattern))])
|
||
(syntax
|
||
(do-context-closure
|
||
red
|
||
lang
|
||
`side-conditions-rewritten
|
||
'context-closure)))]))
|
||
|
||
(define (do-context-closure red lang pat name)
|
||
(unless (reduction-relation? red)
|
||
(error name "expected <reduction-relation> as first argument, got ~e" red))
|
||
(unless (compiled-lang? lang)
|
||
(error name "expected <lang> as second argument, got ~e" lang))
|
||
(let ([cp (compile-pattern
|
||
lang
|
||
`(in-hole (name ctxt ,pat)
|
||
(name exp any))
|
||
#f)])
|
||
(build-reduction-relation
|
||
#f
|
||
lang
|
||
(map
|
||
(λ (make-proc)
|
||
(make-rewrite-proc
|
||
(λ (lang)
|
||
(let ([f (make-proc lang)])
|
||
(λ (main-exp exp extend acc)
|
||
(let loop ([ms (or (match-pattern cp exp) '())]
|
||
[acc acc])
|
||
(cond
|
||
[(null? ms) acc]
|
||
[else
|
||
(let* ([mtch (car ms)]
|
||
[bindings (mtch-bindings mtch)])
|
||
(loop (cdr ms)
|
||
(f main-exp
|
||
(lookup-binding bindings 'exp)
|
||
(λ (x) (extend (plug (lookup-binding bindings 'ctxt) x)))
|
||
acc)))])))))
|
||
(rewrite-proc-name make-proc)
|
||
(rewrite-proc-lhs make-proc)
|
||
(rewrite-proc-lhs-src make-proc)
|
||
(rewrite-proc-id make-proc)))
|
||
(reduction-relation-make-procs red))
|
||
(reduction-relation-rule-names red)
|
||
(reduction-relation-lws red)
|
||
`any)))
|
||
|
||
(define-syntax (--> stx) (raise-syntax-error '--> "used outside of reduction-relation"))
|
||
(define-syntax (fresh stx) (raise-syntax-error 'fresh "used outside of reduction-relation"))
|
||
(define-syntax (with stx) (raise-syntax-error 'with "used outside of reduction-relation"))
|
||
|
||
(define (apply-reduction-relation/tagged p v)
|
||
(let loop ([procs (reduction-relation-procs p)]
|
||
[acc '()])
|
||
(cond
|
||
[(null? procs) acc]
|
||
[else
|
||
(loop (cdr procs)
|
||
((car procs) v acc))])))
|
||
|
||
(define (apply-reduction-relation/tag-with-names p v) (map cdr (apply-reduction-relation/tagged p v)))
|
||
(define (apply-reduction-relation p v) (map caddr (apply-reduction-relation/tagged p v)))
|
||
|
||
(define-for-syntax (extract-pattern-binds lhs)
|
||
(let loop ([lhs lhs])
|
||
(syntax-case* lhs (name) (lambda (a b) (eq? (syntax-e a) (syntax-e b)))
|
||
[(name id expr)
|
||
(identifier? #'id)
|
||
(cons (cons #'id #'expr) (loop #'expr))]
|
||
;; FIXME: should follow the grammar of patterns!
|
||
[(a . b)
|
||
(append (loop #'a) (loop #'b))]
|
||
[_else null])))
|
||
|
||
(define-for-syntax (extract-term-let-binds lhs)
|
||
(let loop ([lhs lhs])
|
||
(syntax-case* lhs (term-let) (lambda (a b) (eq? (syntax-e a) (syntax-e b)))
|
||
[(term-let ((x e1) ...) e2 ...)
|
||
(append (map cons
|
||
(syntax->list #'(x ...))
|
||
(syntax->list #'(e1 ...)))
|
||
(loop #'(e2 ...)))]
|
||
;; FIXME: should follow the grammar of patterns!
|
||
[(a . b)
|
||
(append (loop #'a) (loop #'b))]
|
||
[_else null])))
|
||
|
||
(define-syntax (-reduction-relation stx)
|
||
(syntax-case stx ()
|
||
[(_ lang args ...)
|
||
(with-syntax ([orig-stx stx])
|
||
(syntax/loc stx (do-reduction-relation orig-stx reduction-relation empty-reduction-relation #f lang args ...)))]))
|
||
|
||
(define-syntax (extend-reduction-relation stx)
|
||
(syntax-case stx ()
|
||
[(_ orig-reduction-relation lang args ...)
|
||
(with-syntax ([orig-stx stx])
|
||
(syntax/loc stx (do-reduction-relation orig-stx extend-reduction-relation orig-reduction-relation #t lang args ...)))]))
|
||
|
||
(define-for-syntax (where-keyword? id)
|
||
(or (free-identifier=? id #'where)
|
||
(free-identifier=? id #'where/hidden)))
|
||
|
||
(define-for-syntax (split-by-mode xs mode)
|
||
(for/fold ([ins '()] [outs '()])
|
||
([x (reverse xs)]
|
||
[m (reverse mode)])
|
||
(match m
|
||
['I (values (cons x ins) outs)]
|
||
['O (values ins (cons x outs))])))
|
||
|
||
(define-for-syntax (generate-binding-constraints names names/ellipses bindings syn-err-name)
|
||
(define (id/depth stx)
|
||
(syntax-case stx ()
|
||
[(s (... ...))
|
||
(let ([r (id/depth #'s)])
|
||
(make-id/depth (id/depth-id r) (add1 (id/depth-depth r))))]
|
||
[s (make-id/depth #'s 0)]))
|
||
(define temporaries (generate-temporaries names))
|
||
(values
|
||
(for/fold ([cs '()])
|
||
([n names]
|
||
[w/e names/ellipses]
|
||
[x temporaries])
|
||
(cond [(hash-ref bindings (syntax-e n) #f)
|
||
=> (λ (b)
|
||
(let ([b-id/depth (id/depth b)]
|
||
[n-id/depth (id/depth w/e)])
|
||
(if (= (id/depth-depth b-id/depth) (id/depth-depth n-id/depth))
|
||
(cons #`(equal? #,x (term #,b)) cs)
|
||
(raise-ellipsis-depth-error
|
||
syn-err-name
|
||
(id/depth-id n-id/depth) (id/depth-depth n-id/depth)
|
||
(id/depth-id b-id/depth) (id/depth-depth b-id/depth)))))]
|
||
[else cs]))
|
||
temporaries
|
||
(for/fold ([extended bindings])
|
||
([name names]
|
||
[w/ellipses names/ellipses])
|
||
(hash-set extended (syntax-e name) w/ellipses))))
|
||
|
||
(define-for-syntax (ellipsis? stx)
|
||
(and (identifier? stx)
|
||
(free-identifier=? stx (quote-syntax ...))))
|
||
|
||
;; the withs, freshs, and side-conditions come in backwards order
|
||
(define-for-syntax (bind-withs orig-name main lang lang-nts stx where-mode body names w/ellipses)
|
||
(with-disappeared-uses
|
||
(let loop ([stx stx]
|
||
[to-not-be-in main]
|
||
[env (make-immutable-hash
|
||
(map (λ (x e) (cons (syntax-e x) e))
|
||
names w/ellipses))])
|
||
(syntax-case stx (fresh)
|
||
[() body]
|
||
[((-where x e) y ...)
|
||
(where-keyword? #'-where)
|
||
(let-values ([(names names/ellipses) (extract-names lang-nts 'reduction-relation #t #'x)])
|
||
(define-values (binding-constraints temporaries env+)
|
||
(generate-binding-constraints names names/ellipses env orig-name))
|
||
(with-syntax ([(binding-constraints ...) binding-constraints]
|
||
[side-conditions-rewritten (rewrite-side-conditions/check-errs
|
||
lang-nts
|
||
'reduction-relation
|
||
#f
|
||
#'x)]
|
||
[(names ...) names]
|
||
[(names/ellipses ...) names/ellipses]
|
||
[(x ...) temporaries])
|
||
(let ([rest-body (loop #'(y ...) #`(list x ... #,to-not-be-in) env+)])
|
||
#`(let* ([mtchs (match-pattern (compile-pattern #,lang `side-conditions-rewritten #t) (term e))]
|
||
[result (λ (mtch)
|
||
(let ([bindings (mtch-bindings mtch)])
|
||
(let ([x (lookup-binding bindings 'names)] ...)
|
||
(and binding-constraints ...
|
||
(term-let ([names/ellipses x] ...)
|
||
#,rest-body)))))])
|
||
(if mtchs
|
||
#,
|
||
(case where-mode
|
||
[(flatten)
|
||
#`(for/fold ([r '()]) ([m mtchs])
|
||
(let ([s (result m)])
|
||
(if s (append s r) r)))]
|
||
[(predicate)
|
||
#`(ormap result mtchs)]
|
||
[else (error 'unknown-where-mode "~s" where-mode)])
|
||
#f)))))]
|
||
[((-side-condition s ...) y ...)
|
||
(or (free-identifier=? #'-side-condition #'side-condition)
|
||
(free-identifier=? #'-side-condition #'side-condition/hidden))
|
||
#`(and s ... #,(loop #'(y ...) to-not-be-in env))]
|
||
[((fresh x) y ...)
|
||
(identifier? #'x)
|
||
#`(term-let ([x (variable-not-in #,to-not-be-in 'x)])
|
||
#,(loop #'(y ...) #`(list (term x) #,to-not-be-in) env))]
|
||
[((fresh x name) y ...)
|
||
(identifier? #'x)
|
||
#`(term-let ([x (let ([the-name (term name)])
|
||
(verify-name-ok '#,orig-name the-name)
|
||
(variable-not-in #,to-not-be-in the-name))])
|
||
#,(loop #'(y ...) #`(list (term x) #,to-not-be-in) env))]
|
||
[((fresh (y) (x ...)) z ...)
|
||
#`(term-let ([(y #,'...)
|
||
(variables-not-in #,to-not-be-in
|
||
(map (λ (_ignore_) 'y)
|
||
(term (x ...))))])
|
||
#,(loop #'(z ...) #`(list (term (y #,'...)) #,to-not-be-in) env))]
|
||
[((fresh (y) (x ...) names) z ...)
|
||
#`(term-let ([(y #,'...)
|
||
(let ([the-names (term names)]
|
||
[len-counter (term (x ...))])
|
||
(verify-names-ok '#,orig-name the-names len-counter)
|
||
(variables-not-in #,to-not-be-in the-names))])
|
||
#,(loop #'(z ...) #`(list (term (y #,'...)) #,to-not-be-in) env))]
|
||
[((form-name . pats) . after)
|
||
(judgment-form-id? #'form-name)
|
||
(let*-values ([(premise) (syntax-case stx () [(p . _) #'p])]
|
||
[(rest-clauses under-ellipsis?)
|
||
(syntax-case #'after ()
|
||
[(maybe-ellipsis . more)
|
||
(ellipsis? #'maybe-ellipsis)
|
||
(values #'more #t)]
|
||
[_ (values #'after #f)])]
|
||
[(judgment-form) (syntax-local-value/record #'form-name (λ (_) #t))]
|
||
[(mode) (judgment-form-mode judgment-form)]
|
||
[(judgment-proc) (judgment-form-proc judgment-form)]
|
||
[(input-template output-pre-pattern)
|
||
(let-values ([(in out) (split-by-mode (syntax->list #'pats) mode)])
|
||
(if under-ellipsis?
|
||
(let ([ellipsis (syntax/loc premise (... ...))])
|
||
(values #`(#,in #,ellipsis) #`(#,out #,ellipsis)))
|
||
(values in out)))]
|
||
[(output-pattern)
|
||
(rewrite-side-conditions/check-errs lang-nts orig-name #t output-pre-pattern)]
|
||
[(output-names output-names/ellipses)
|
||
(extract-names lang-nts orig-name #t output-pre-pattern)]
|
||
[(binding-constraints temporaries env+)
|
||
(generate-binding-constraints output-names output-names/ellipses env orig-name)]
|
||
[(rest-body) (loop rest-clauses #`(list judgment-output #,to-not-be-in) env+)]
|
||
[(call)
|
||
(let ([input (quasisyntax/loc premise (term #,input-template))])
|
||
(define (make-traced input)
|
||
(quasisyntax/loc premise
|
||
(call-judgment-form 'form-name #,judgment-proc '#,mode #,input)))
|
||
(if under-ellipsis?
|
||
#`(repeated-premise-outputs #,input (λ (x) #,(make-traced #'x)))
|
||
(make-traced input)))])
|
||
(with-syntax ([(output-name ...) output-names]
|
||
[(output-name/ellipsis ...) output-names/ellipses]
|
||
[(temp ...) temporaries]
|
||
[(binding-constraint ...) binding-constraints])
|
||
#`(begin
|
||
(void #,(defined-check judgment-proc "judgment form" #:external #'form-name))
|
||
(for/fold ([outputs '()]) ([sub-output #,call])
|
||
(define mtchs
|
||
(match-pattern (compile-pattern #,lang `#,output-pattern #t) sub-output))
|
||
(if mtchs
|
||
(for/fold ([outputs outputs]) ([mtch mtchs])
|
||
(let ([temp (lookup-binding (mtch-bindings mtch) 'output-name)] ...)
|
||
(define mtch-outputs
|
||
(and binding-constraint ...
|
||
(term-let ([output-name/ellipsis temp] ...)
|
||
#,rest-body)))
|
||
(if mtch-outputs
|
||
(append mtch-outputs outputs)
|
||
outputs)))
|
||
outputs)))))]))))
|
||
|
||
(define (repeated-premise-outputs inputs premise)
|
||
(if (null? inputs)
|
||
'(())
|
||
(let ([output (premise (car inputs))])
|
||
(if (null? output)
|
||
'()
|
||
(for*/list ([o output] [os (repeated-premise-outputs (cdr inputs) premise)])
|
||
(cons o os))))))
|
||
|
||
(define (call-judgment-form form-name form-proc mode input)
|
||
(define traced (current-traced-metafunctions))
|
||
(if (or (eq? 'all traced) (memq form-name traced))
|
||
(let ([outputs #f])
|
||
(define spacers
|
||
(for/fold ([s '()]) ([m mode])
|
||
(case m [(I) s] [(O) (cons '_ s)])))
|
||
(define (assemble inputs outputs)
|
||
(let loop ([ms mode] [is inputs] [os outputs])
|
||
(if (null? ms)
|
||
'()
|
||
(case (car ms)
|
||
[(I) (cons (car is) (loop (cdr ms) (cdr is) os))]
|
||
[(O) (cons (car os) (loop (cdr ms) is (cdr os)))]))))
|
||
(define (wrapped . _)
|
||
(set! outputs (form-proc input))
|
||
(for/list ([output outputs])
|
||
(cons form-name (assemble input output))))
|
||
(apply trace-call form-name wrapped (assemble input spacers))
|
||
outputs)
|
||
(form-proc input)))
|
||
|
||
(define-for-syntax (name-pattern-lws pat)
|
||
(map (λ (x) (cons (to-lw/proc (car x)) (to-lw/proc (cdr x))))
|
||
(extract-pattern-binds pat)))
|
||
|
||
(define-for-syntax (check-judgment-arity judgment)
|
||
(syntax-case judgment ()
|
||
[(form-name pat ...)
|
||
(judgment-form-id? #'form-name)
|
||
(let ([expected (length (judgment-form-mode (syntax-local-value #'form-name)))]
|
||
[actual (length (syntax->list #'(pat ...)))])
|
||
(unless (= actual expected)
|
||
(raise-syntax-error
|
||
#f
|
||
(format "mode specifies a ~a-ary relation but use supplied ~a term~a"
|
||
expected actual (if (= actual 1) "" "s"))
|
||
judgment)))]))
|
||
|
||
(define-syntax-set (do-reduction-relation)
|
||
(define (do-reduction-relation/proc stx)
|
||
(syntax-case stx ()
|
||
[(_ orig-stx id orig-reduction-relation allow-zero-rules? lang . w/domain-args)
|
||
(identifier? #'lang)
|
||
(prune-syntax
|
||
(let-values ([(domain-pattern main-arrow args)
|
||
(parse-keywords #'orig-stx #'id #'w/domain-args)])
|
||
(with-syntax ([(rules ...) (before-with args)]
|
||
[(shortcuts ...) (after-with args)])
|
||
(with-syntax ([(lws ...) (map rule->lws (syntax->list #'(rules ...)))])
|
||
(reduction-relation/helper
|
||
#'orig-stx
|
||
(syntax-e #'id)
|
||
#'orig-reduction-relation
|
||
(syntax lang)
|
||
(syntax->list (syntax (rules ...)))
|
||
(syntax->list (syntax (shortcuts ...)))
|
||
#'(list lws ...)
|
||
(syntax-e #'allow-zero-rules?)
|
||
domain-pattern
|
||
main-arrow)))))]
|
||
[(_ orig-stx id orig-reduction-relation allow-zero-rules? lang args ...)
|
||
(raise-syntax-error (syntax-e #'id)
|
||
"expected an identifier for the language name"
|
||
#'lang)]))
|
||
|
||
(define default-arrow #'-->)
|
||
|
||
(define (parse-keywords stx id args)
|
||
(let ([domain-contract #'any]
|
||
[main-arrow default-arrow])
|
||
|
||
;; ensure no duplicate keywords
|
||
(let ([ht (make-hash)]
|
||
[known-keywords '(#:arrow #:domain)]) ;; #:arrow not yet implemented
|
||
(for-each (λ (kwd/stx) ;; (not necc a keyword)
|
||
(let ([kwd (syntax-e kwd/stx)])
|
||
(when (keyword? kwd)
|
||
(unless (member kwd known-keywords)
|
||
(raise-syntax-error (syntax-e id)
|
||
"unknown keyword"
|
||
stx
|
||
kwd/stx))
|
||
(when (hash-ref ht kwd #f)
|
||
(raise-syntax-error (syntax-e id)
|
||
"duplicate keywords"
|
||
stx
|
||
kwd/stx
|
||
(list (hash-ref ht kwd))))
|
||
(hash-set! ht kwd kwd/stx))))
|
||
(syntax->list args)))
|
||
|
||
(let loop ([args args])
|
||
(syntax-case args ()
|
||
[(#:domain pat args ...)
|
||
(begin (set! domain-contract #'pat)
|
||
(loop #'(args ...)))]
|
||
[(#:domain)
|
||
(raise-syntax-error (syntax-e id)
|
||
"expected a domain after #:domain"
|
||
stx)]
|
||
[(#:arrow arrow . args)
|
||
(identifier? #'arrow)
|
||
(begin (set! main-arrow #'arrow)
|
||
(loop #'args))]
|
||
[(#:arrow arrow . args)
|
||
(raise-syntax-error (syntax-e id)
|
||
"expected an arrow after #:arrow, not a compound expression"
|
||
stx
|
||
#'arrow)]
|
||
[(#:arrow)
|
||
(raise-syntax-error (syntax-e id)
|
||
"expected an arrow after #:arrow"
|
||
stx)]
|
||
[_
|
||
(begin
|
||
(values domain-contract main-arrow args))]))))
|
||
|
||
|
||
(define (before-with stx)
|
||
(let loop ([lst (syntax->list stx)])
|
||
(cond
|
||
[(null? lst) null]
|
||
[else
|
||
(let ([fst (car lst)])
|
||
(syntax-case (car lst) (with)
|
||
[with null]
|
||
[else (cons (car lst) (loop (cdr lst)))]))])))
|
||
|
||
(define (after-with stx)
|
||
(let loop ([lst (syntax->list stx)])
|
||
(cond
|
||
[(null? lst) null]
|
||
[else
|
||
(let ([fst (car lst)])
|
||
(syntax-case (car lst) (with)
|
||
[with (cdr lst)]
|
||
[else (loop (cdr lst))]))])))
|
||
|
||
(define (name-pattern-lws/rr pat)
|
||
(for/list ([lw-pair (name-pattern-lws pat)])
|
||
(match lw-pair
|
||
[(cons l r) #`(cons #,l #,r)])))
|
||
|
||
(define (rule->lws rule)
|
||
(syntax-case rule ()
|
||
[(arrow lhs rhs stuff ...)
|
||
(let-values ([(label computed-label scs/withs fvars)
|
||
(let loop ([stuffs (syntax->list #'(stuff ...))]
|
||
[label #f]
|
||
[computed-label #f]
|
||
[scs/withs null]
|
||
[fvars null])
|
||
(cond
|
||
[(null? stuffs) (values label computed-label (reverse scs/withs) (reverse fvars))]
|
||
[else
|
||
(syntax-case (car stuffs) (where where/hidden
|
||
side-condition side-condition/hidden
|
||
fresh variable-not-in
|
||
computed-name
|
||
judgment-holds)
|
||
[(fresh xs ...)
|
||
(loop (cdr stuffs)
|
||
label
|
||
computed-label
|
||
scs/withs
|
||
(append
|
||
(reverse (map (λ (x)
|
||
(to-lw/proc
|
||
(syntax-case x ()
|
||
[x
|
||
(identifier? #'x)
|
||
#'x]
|
||
[(x whatever)
|
||
(identifier? #'x)
|
||
#'x]
|
||
[((y dots) (x dots2))
|
||
(datum->syntax
|
||
#f
|
||
`(,(syntax->datum #'y) ...)
|
||
#'y)]
|
||
[((y dots) (x dots2) whatever)
|
||
(datum->syntax
|
||
#f
|
||
`(,(syntax->datum #'y) ...)
|
||
#'y)])))
|
||
(syntax->list #'(xs ...))))
|
||
fvars))]
|
||
[(where x e)
|
||
(loop (cdr stuffs)
|
||
label
|
||
computed-label
|
||
(cons #`(cons #,(to-lw/proc #'x) #,(to-lw/proc #'e))
|
||
(append (name-pattern-lws/rr #'x) scs/withs))
|
||
fvars)]
|
||
[(where/hidden x e)
|
||
(loop (cdr stuffs) label computed-label scs/withs fvars)]
|
||
[(side-condition sc)
|
||
(loop (cdr stuffs)
|
||
label
|
||
computed-label
|
||
(cons (to-lw/uq/proc #'sc) scs/withs)
|
||
fvars)]
|
||
[(side-condition/hidden sc)
|
||
(loop (cdr stuffs) label computed-label scs/withs fvars)]
|
||
[x
|
||
(identifier? #'x)
|
||
(loop (cdr stuffs)
|
||
#''x
|
||
computed-label
|
||
scs/withs
|
||
fvars)]
|
||
[x
|
||
(string? (syntax-e #'x))
|
||
(loop (cdr stuffs)
|
||
#'(string->symbol x)
|
||
computed-label
|
||
scs/withs
|
||
fvars)]
|
||
[(computed-name e)
|
||
(loop (cdr stuffs)
|
||
label
|
||
#'e
|
||
scs/withs
|
||
fvars)]
|
||
[(judgment-holds (form-name . pieces))
|
||
(judgment-form-id? #'form-name)
|
||
(loop (cdr stuffs)
|
||
label
|
||
computed-label
|
||
(let*-values ([(mode) (judgment-form-mode (syntax-local-value #'form-name))]
|
||
[(_ outs) (split-by-mode (syntax->list #'pieces) mode)])
|
||
(cons (to-lw/proc #'(form-name . pieces))
|
||
(for/fold ([binds scs/withs]) ([out outs])
|
||
(append (name-pattern-lws/rr out) binds))))
|
||
fvars)])]))])
|
||
(with-syntax ([(scs/withs ...) scs/withs]
|
||
[(fvars ...) fvars]
|
||
[((bind-id . bind-pat) ...)
|
||
(extract-pattern-binds #'lhs)]
|
||
[((tl-id . tl-pat) ...)
|
||
(extract-term-let-binds #'rhs)])
|
||
#`(make-rule-pict 'arrow
|
||
#,(to-lw/proc #'lhs)
|
||
#,(to-lw/proc #'rhs)
|
||
#,label
|
||
#,(and computed-label
|
||
(to-lw/proc #`,#,computed-label))
|
||
(list scs/withs ...
|
||
#,@(map (λ (bind-id bind-pat)
|
||
#`(cons #,(to-lw/proc bind-id)
|
||
#,(to-lw/proc bind-pat)))
|
||
(syntax->list #'(bind-id ...))
|
||
(syntax->list #'(bind-pat ...)))
|
||
#,@(map (λ (tl-id tl-pat)
|
||
#`(cons #,(to-lw/proc tl-id)
|
||
#,(to-lw/uq/proc tl-pat)))
|
||
(syntax->list #'(tl-id ...))
|
||
(syntax->list #'(tl-pat ...))))
|
||
(list fvars ...))))]))
|
||
|
||
(define (reduction-relation/helper stx orig-name orig-red-expr lang-id rules shortcuts
|
||
lws
|
||
allow-zero-rules?
|
||
domain-pattern
|
||
main-arrow)
|
||
(let ([ht (make-module-identifier-mapping)]
|
||
[all-top-levels '()]
|
||
[withs (make-module-identifier-mapping)])
|
||
(for-each (λ (shortcut)
|
||
(syntax-case shortcut ()
|
||
[((rhs-arrow rhs-from rhs-to)
|
||
(lhs-arrow a b))
|
||
(not (identifier? #'a))
|
||
(raise-syntax-error
|
||
orig-name
|
||
"malformed shortcut, expected identifier"
|
||
shortcut #'a)]
|
||
[((rhs-arrow rhs-from rhs-to)
|
||
(lhs-arrow a b))
|
||
(not (identifier? #'b))
|
||
(raise-syntax-error
|
||
orig-name
|
||
"malformed shortcut, expected identifier"
|
||
shortcut #'b)]
|
||
[((rhs-arrow rhs-from rhs-to)
|
||
(lhs-arrow lhs-from lhs-to))
|
||
(begin
|
||
(table-cons! withs #'lhs-arrow #'rhs-arrow)
|
||
(table-cons! ht (syntax rhs-arrow) shortcut))]
|
||
[((a b c) d)
|
||
(raise-syntax-error
|
||
orig-name
|
||
"malformed shortcut, expected right-hand side to have three sub-expressions"
|
||
stx (syntax d))]
|
||
[(a b)
|
||
(raise-syntax-error
|
||
orig-name
|
||
"malformed shortcut, expected left-hand side to have three sub-expressions"
|
||
stx (syntax a))]
|
||
[(a b c d ...)
|
||
(raise-syntax-error orig-name
|
||
"malformed shortcut, expected only two subparts for a shortcut definition, found an extra one"
|
||
stx
|
||
(syntax c))]
|
||
[_ (raise-syntax-error orig-name
|
||
"malformed shortcut"
|
||
stx shortcut)]))
|
||
shortcuts)
|
||
|
||
(for-each (λ (rule)
|
||
(syntax-case rule ()
|
||
[(arrow . rst)
|
||
(begin
|
||
(set! all-top-levels (cons #'arrow all-top-levels))
|
||
(table-cons! ht (syntax arrow) rule))]))
|
||
rules)
|
||
|
||
;; signal a syntax error if there are shortcuts defined, but no rules that use them
|
||
(unless (null? shortcuts)
|
||
(unless (module-identifier-mapping-get ht main-arrow (λ () #f))
|
||
(raise-syntax-error orig-name
|
||
(format "no ~a rules" (syntax-e main-arrow))
|
||
stx)))
|
||
|
||
(for-each (λ (tl)
|
||
(let loop ([id tl])
|
||
(unless (free-identifier=? main-arrow id)
|
||
(let ([nexts
|
||
(module-identifier-mapping-get
|
||
withs id
|
||
(λ ()
|
||
(raise-syntax-error
|
||
orig-name
|
||
(format "the ~s relation is not defined"
|
||
(syntax->datum id))
|
||
stx
|
||
id)))])
|
||
(for-each loop nexts)))))
|
||
all-top-levels)
|
||
|
||
(let ([name-table (make-hasheq)]
|
||
[lang-nts (language-id-nts lang-id orig-name)])
|
||
(hash-set! name-table #f 0)
|
||
;; name table maps symbols for the rule names to their syntax objects and to a counter indicating what
|
||
;; order the names were encountered in. The current value of the counter is stored in the table at key '#f'.
|
||
(with-syntax ([lang-id lang-id]
|
||
[(top-level ...) (get-choices stx orig-name ht lang-id main-arrow
|
||
name-table lang-id allow-zero-rules?)]
|
||
[(rule-names ...)
|
||
(begin
|
||
(hash-remove! name-table #f)
|
||
(map car (sort (hash-map name-table (λ (k v) (list k (list-ref v 1)))) < #:key cadr)))]
|
||
[lws lws]
|
||
|
||
[domain-pattern-side-conditions-rewritten
|
||
(rewrite-side-conditions/check-errs
|
||
lang-nts
|
||
orig-name
|
||
#f
|
||
domain-pattern)])
|
||
|
||
#`(build-reduction-relation
|
||
#,orig-red-expr
|
||
lang-id
|
||
(list top-level ...)
|
||
'(rule-names ...)
|
||
lws
|
||
`domain-pattern-side-conditions-rewritten)))))
|
||
|
||
#|
|
||
;; relation-tree =
|
||
;; leaf
|
||
;; (make-node id[frm] pat[frm] id[to] pat[to] (listof relation-tree))
|
||
(define-struct node (frm-id frm-pat to-id to-pat))
|
||
(define-struct leaf (frm-pat to-pat))
|
||
|#
|
||
;; get-choices : stx[original-syntax-object] bm lang identifier ht[sym->syntax] identifier[language-name] -> (listof relation-tree)
|
||
(define (get-choices stx orig-name bm lang id name-table lang-id allow-zero-rules?)
|
||
(reverse
|
||
(apply
|
||
append
|
||
(map (λ (x) (get-tree stx orig-name bm lang x name-table lang-id allow-zero-rules?))
|
||
(module-identifier-mapping-get
|
||
bm id
|
||
(λ ()
|
||
(if allow-zero-rules?
|
||
'()
|
||
(raise-syntax-error orig-name
|
||
(format "no rules use ~a" (syntax->datum id))
|
||
stx
|
||
(if (equal? id default-arrow) #f id)))))))))
|
||
|
||
(define (get-tree stx orig-name bm lang case-stx name-table lang-id allow-zero-rules?)
|
||
(syntax-case case-stx ()
|
||
[(arrow from to extras ...)
|
||
(list (do-leaf stx
|
||
orig-name
|
||
lang
|
||
name-table
|
||
(syntax from)
|
||
(syntax to)
|
||
(syntax->list (syntax (extras ...)))
|
||
lang-id))]
|
||
[((rhs-arrow rhs-from rhs-to) (lhs-arrow lhs-frm-id lhs-to-id))
|
||
(let* ([lang-nts (language-id-nts lang-id orig-name)]
|
||
[rewrite-side-conds
|
||
(λ (pat) (rewrite-side-conditions/check-errs lang-nts orig-name #t pat))])
|
||
(let-values ([(names names/ellipses) (extract-names lang-nts orig-name #t (syntax rhs-from))])
|
||
(with-syntax ([(names ...) names]
|
||
[(names/ellipses ...) names/ellipses]
|
||
[side-conditions-rewritten (rewrite-side-conds
|
||
(rewrite-node-pat (syntax-e (syntax lhs-frm-id))
|
||
(syntax rhs-from)))]
|
||
[fresh-rhs-from (rewrite-side-conds
|
||
(freshen-names #'rhs-from #'lhs-frm-id lang-nts orig-name))]
|
||
[lang lang])
|
||
(map
|
||
(λ (child-proc)
|
||
#`(do-node-match
|
||
'lhs-frm-id
|
||
'lhs-to-id
|
||
`side-conditions-rewritten
|
||
(λ (bindings rhs-binder)
|
||
(term-let ([lhs-to-id rhs-binder]
|
||
[names/ellipses (lookup-binding bindings 'names)] ...)
|
||
(term rhs-to)))
|
||
#,child-proc
|
||
`fresh-rhs-from))
|
||
(get-choices stx orig-name bm #'lang
|
||
(syntax lhs-arrow)
|
||
name-table lang-id
|
||
allow-zero-rules?)))))]))
|
||
(define (rewrite-node-pat id term)
|
||
(let loop ([t term])
|
||
(syntax-case t (side-condition)
|
||
[(side-condition p c)
|
||
#`(side-condition #,(loop #'p) c)]
|
||
[(p ...)
|
||
(map loop (syntax->list #'(p ...)))]
|
||
[else
|
||
(if (and (identifier? t) (eq? id (syntax-e t)))
|
||
`(name ,id any)
|
||
t)])))
|
||
|
||
(define (freshen-names pat hole-id nts what)
|
||
(define (fresh x)
|
||
(gensym
|
||
(if (or (memq x nts) (memq x underscore-allowed))
|
||
(string-append (symbol->string x) "_")
|
||
x)))
|
||
(let-values ([(bound _) (extract-names nts what #t pat 'binds-anywhere)])
|
||
(let ([renames (make-bound-identifier-mapping)])
|
||
(for-each
|
||
(λ (x)
|
||
(unless (bound-identifier=? x hole-id)
|
||
(bound-identifier-mapping-put! renames x (fresh (syntax-e x)))))
|
||
bound)
|
||
(let recur ([p pat])
|
||
(syntax-case p (side-condition)
|
||
[(side-condition p c)
|
||
#`(side-condition
|
||
#,(recur #'p)
|
||
(term-let (#,@(bound-identifier-mapping-map renames (λ (x y) #`(#,x (term #,y)))))
|
||
c))]
|
||
[(p ...)
|
||
#`(#,@(map recur (syntax->list #'(p ...))))]
|
||
[else
|
||
(if (identifier? p)
|
||
(bound-identifier-mapping-get renames p (λ () p))
|
||
p)])))))
|
||
|
||
(define (do-leaf stx orig-name lang name-table from to extras lang-id)
|
||
(let* ([lang-nts (language-id-nts lang-id orig-name)]
|
||
[rw-sc (λ (pat) (rewrite-side-conditions/check-errs lang-nts orig-name #t pat))])
|
||
(let-values ([(name computed-name sides/withs/freshs) (process-extras stx orig-name name-table extras)])
|
||
(let*-values ([(names names/ellipses) (extract-names lang-nts orig-name #t from)]
|
||
[(body-code)
|
||
(bind-withs orig-name
|
||
#'main-exp
|
||
lang
|
||
lang-nts
|
||
sides/withs/freshs
|
||
'flatten
|
||
#`(list (cons #,(or computed-name #'none)
|
||
(term #,to)))
|
||
names names/ellipses)]
|
||
[(test-case-body-code)
|
||
;; this contains some redundant code
|
||
(bind-withs orig-name
|
||
#'#t
|
||
#'lang-id2
|
||
lang-nts
|
||
sides/withs/freshs
|
||
'predicate
|
||
#'#t
|
||
names names/ellipses)])
|
||
(with-syntax ([side-conditions-rewritten (rw-sc from)]
|
||
[lhs-w/extras (rw-sc #`(side-condition #,from #,test-case-body-code))]
|
||
[lhs-source (format "~a:~a:~a"
|
||
(syntax-source from)
|
||
(syntax-line from)
|
||
(syntax-column from))]
|
||
[name name]
|
||
[lang lang]
|
||
[(names ...) names]
|
||
[(names/ellipses ...) names/ellipses]
|
||
[body-code body-code])
|
||
#`
|
||
(let ([case-id (gensym)])
|
||
(make-rewrite-proc
|
||
(λ (lang-id)
|
||
(let ([cp (compile-pattern lang-id `side-conditions-rewritten #t)])
|
||
(λ (main-exp exp f other-matches)
|
||
(let ([mtchs (match-pattern cp exp)])
|
||
(if mtchs
|
||
(let loop ([mtchs mtchs]
|
||
[acc other-matches])
|
||
(cond
|
||
[(null? mtchs) acc]
|
||
[else
|
||
(let* ([mtch (car mtchs)]
|
||
[bindings (mtch-bindings mtch)]
|
||
[really-matched
|
||
(term-let ([names/ellipses (lookup-binding bindings 'names)] ...)
|
||
body-code)])
|
||
(cond
|
||
[really-matched
|
||
(for-each
|
||
(λ (c)
|
||
(let ([r (coverage-relation c)])
|
||
(when (and (reduction-relation? r)
|
||
(memf (λ (r) (eq? case-id (rewrite-proc-id r)))
|
||
(reduction-relation-make-procs r)))
|
||
(cover-case case-id c))))
|
||
(relation-coverage))
|
||
(loop (cdr mtchs)
|
||
(map/mt (λ (x) (list name
|
||
(if (none? (car x))
|
||
name
|
||
(format "~a" (car x)))
|
||
(f (cdr x))))
|
||
really-matched acc))]
|
||
[else
|
||
(loop (cdr mtchs) acc)]))]))
|
||
other-matches)))))
|
||
name
|
||
(λ (lang-id2) `lhs-w/extras)
|
||
lhs-source
|
||
case-id)))))))
|
||
|
||
(define (process-extras stx orig-name name-table extras)
|
||
(let* ([the-name #f]
|
||
[the-name-stx #f]
|
||
[computed-name-stx #f]
|
||
[sides/withs/freshs
|
||
(let loop ([extras extras])
|
||
(cond
|
||
[(null? extras) '()]
|
||
[else
|
||
(syntax-case (car extras) (fresh computed-name judgment-holds)
|
||
[name
|
||
(or (identifier? (car extras))
|
||
(string? (syntax-e (car extras))))
|
||
(begin
|
||
(let* ([raw-name (syntax-e (car extras))]
|
||
[name-sym
|
||
(if (symbol? raw-name)
|
||
raw-name
|
||
(string->symbol raw-name))])
|
||
(when (hash-ref name-table name-sym #f)
|
||
(raise-syntax-errors orig-name
|
||
"same name on multiple rules"
|
||
stx
|
||
(list (car (hash-ref name-table name-sym))
|
||
(syntax name))))
|
||
(let ([num (hash-ref name-table #f)])
|
||
(hash-set! name-table #f (+ num 1))
|
||
(hash-set! name-table name-sym (list (syntax name) num)))
|
||
|
||
(when the-name
|
||
(raise-syntax-errors orig-name
|
||
"expected only a single name"
|
||
stx
|
||
(list the-name-stx (car extras))))
|
||
(set! the-name (if (symbol? raw-name)
|
||
(symbol->string raw-name)
|
||
raw-name))
|
||
(set! the-name-stx (car extras))
|
||
(loop (cdr extras))))]
|
||
[(fresh var ...)
|
||
(append (map (λ (x)
|
||
(syntax-case x ()
|
||
[x
|
||
(identifier? #'x)
|
||
#'(fresh x)]
|
||
[(x name)
|
||
(identifier? #'x)
|
||
#'(fresh x name)]
|
||
[((ys dots2) (xs dots1))
|
||
(and (eq? (syntax-e #'dots1) (string->symbol "..."))
|
||
(eq? (syntax-e #'dots2) (string->symbol "...")))
|
||
#'(fresh (ys) (xs dots1))]
|
||
[((ys dots2) (xs dots1) names)
|
||
(and (eq? (syntax-e #'dots1) (string->symbol "..."))
|
||
(eq? (syntax-e #'dots2) (string->symbol "...")))
|
||
#'(fresh (ys) (xs dots1) names)]
|
||
[x
|
||
(raise-syntax-error orig-name
|
||
"malformed fresh variable clause"
|
||
stx
|
||
#'x)]))
|
||
(syntax->list #'(var ...)))
|
||
(loop (cdr extras)))]
|
||
[(-side-condition exp ...)
|
||
(or (free-identifier=? #'-side-condition #'side-condition)
|
||
(free-identifier=? #'-side-condition #'side-condition/hidden))
|
||
(cons (car extras) (loop (cdr extras)))]
|
||
[(-where x e)
|
||
(where-keyword? #'-where)
|
||
(cons (car extras) (loop (cdr extras)))]
|
||
[(-where . x)
|
||
(where-keyword? #'-where)
|
||
(raise-syntax-error orig-name "malformed where clause" stx (car extras))]
|
||
[(computed-name e)
|
||
(if computed-name-stx
|
||
(raise-syntax-errors orig-name "expected at most one computed-name clause"
|
||
stx (list computed-name-stx #'e))
|
||
(set! computed-name-stx #'e))
|
||
(loop (cdr extras))]
|
||
[(computed-name . _)
|
||
(raise-syntax-error orig-name "malformed computed-name clause" stx (car extras))]
|
||
[(judgment-holds judgment)
|
||
(begin
|
||
(check-judgment-arity #'judgment)
|
||
(cons #'judgment (loop (cdr extras))))]
|
||
[_
|
||
(raise-syntax-error orig-name "unknown extra" stx (car extras))])]))])
|
||
(values the-name computed-name-stx sides/withs/freshs)))
|
||
|
||
;; table-cons! hash-table sym any -> void
|
||
;; extends ht at key by `cons'ing hd onto whatever is alrady bound to key (or the empty list, if nothing is)
|
||
(define (table-cons! ht key hd)
|
||
(module-identifier-mapping-put! ht key (cons hd (module-identifier-mapping-get ht key (λ () '())))))
|
||
|
||
(define (raise-syntax-errors sym str stx stxs)
|
||
(raise (make-exn:fail:syntax
|
||
(string->immutable-string (format "~a: ~a~a"
|
||
sym
|
||
str
|
||
(if (error-print-source-location)
|
||
(string-append ":" (stxs->list stxs))
|
||
"")))
|
||
(current-continuation-marks)
|
||
stxs)))
|
||
|
||
(define (stxs->list stxs)
|
||
(apply
|
||
string-append
|
||
(let loop ([stxs stxs])
|
||
(cond
|
||
[(null? stxs) '()]
|
||
[else
|
||
(cons (format " ~s" (syntax->datum (car stxs)))
|
||
(loop (cdr stxs)))])))))
|
||
|
||
(define (substitute from to pat)
|
||
(let recur ([p pat])
|
||
(syntax-case p (side-condition)
|
||
[(side-condition p c)
|
||
#`(side-condition #,(recur #'p) c)]
|
||
[(p ...)
|
||
#`(#,@(map recur (syntax->list #'(p ...))))]
|
||
[else
|
||
(if (and (identifier? p) (bound-identifier=? p from))
|
||
to
|
||
p)])))
|
||
|
||
(define (verify-name-ok orig-name the-name)
|
||
(unless (symbol? the-name)
|
||
(error orig-name "expected a single name, got ~s" the-name)))
|
||
|
||
(define (verify-names-ok orig-name the-names len-counter)
|
||
(unless (and (list? the-names)
|
||
(andmap symbol? the-names))
|
||
(error orig-name
|
||
"expected a sequence of names, got ~s"
|
||
the-names))
|
||
(unless (= (length len-counter)
|
||
(length the-names))
|
||
(error orig-name
|
||
"expected the length of the sequence of names to be ~a, got ~s"
|
||
(length len-counter)
|
||
the-names)))
|
||
|
||
(define (union-reduction-relations fst snd . rst)
|
||
(let ([name-ht (make-hasheq)]
|
||
[counter 0]
|
||
[lst (list* fst snd rst)]
|
||
[first-lang (reduction-relation-lang fst)])
|
||
(for-each
|
||
(λ (red)
|
||
(unless (eq? first-lang (reduction-relation-lang red))
|
||
(error 'union-reduction-relations
|
||
"expected all of the reduction relations to use the same language"))
|
||
(for-each (λ (name)
|
||
(when (hash-ref name-ht name #f)
|
||
(error 'union-reduction-relations "multiple rules with the name ~s" name))
|
||
(hash-set! name-ht name counter)
|
||
(set! counter (+ counter 1)))
|
||
(reduction-relation-rule-names red)))
|
||
(reverse lst)) ;; reverse here so the names get put into the hash in the proper (backwards) order
|
||
(make-reduction-relation
|
||
first-lang
|
||
(reverse (apply append (map reduction-relation-make-procs lst)))
|
||
(map car (sort (hash-map name-ht list) < #:key cadr))
|
||
(apply append (map reduction-relation-lws lst))
|
||
(reverse (apply append (map reduction-relation-procs lst))))))
|
||
|
||
(define (do-node-match lhs-frm-id lhs-to-id pat rhs-proc child-make-proc rhs-from)
|
||
(define (subst from to in)
|
||
(let recur ([p in])
|
||
(cond [(eq? from p) to]
|
||
[(pair? p) (map recur p)]
|
||
[else p])))
|
||
;; need call to make-rewrite-proc
|
||
;; also need a test case here to check duplication of names.
|
||
(make-rewrite-proc
|
||
(λ (lang)
|
||
(let ([cp (compile-pattern lang pat #t)]
|
||
[child-proc (child-make-proc lang)])
|
||
(λ (main-exp exp f other-matches)
|
||
(let ([mtchs (match-pattern cp exp)])
|
||
(if mtchs
|
||
(let o-loop ([mtchs mtchs]
|
||
[acc other-matches])
|
||
(cond
|
||
[(null? mtchs) acc]
|
||
[else
|
||
(let ([sub-exp (lookup-binding (mtch-bindings (car mtchs)) lhs-frm-id)])
|
||
(o-loop (cdr mtchs)
|
||
(child-proc main-exp
|
||
sub-exp
|
||
(λ (x) (f (rhs-proc (mtch-bindings (car mtchs)) x)))
|
||
acc)))]))
|
||
other-matches)))))
|
||
(rewrite-proc-name child-make-proc)
|
||
(λ (lang) (subst lhs-frm-id ((rewrite-proc-lhs child-make-proc) lang) rhs-from))
|
||
(rewrite-proc-lhs-src child-make-proc)
|
||
(rewrite-proc-id child-make-proc)))
|
||
|
||
(define relation-coverage (make-parameter null))
|
||
|
||
(define (cover-case id cov)
|
||
(hash-update! (coverage-counts cov) id
|
||
(λ (c) (cons (car c) (add1 (cdr c))))))
|
||
|
||
(define (covered-cases cov)
|
||
(hash-map (coverage-counts cov) (λ (k v) v)))
|
||
|
||
(define-struct coverage (relation counts))
|
||
|
||
(define-syntax (fresh-coverage stx)
|
||
(syntax-case stx ()
|
||
[(name subj-stx)
|
||
(with-syntax ([subj
|
||
(cond [(and (identifier? (syntax subj-stx))
|
||
(let ([tf (syntax-local-value (syntax subj-stx) (λ () #f))])
|
||
(and (term-fn? tf) (term-fn-get-id tf))))
|
||
=> values]
|
||
[else (syntax (let ([r subj-stx])
|
||
(if (reduction-relation? r)
|
||
r
|
||
(raise-type-error 'name "reduction-relation" r))))])])
|
||
(syntax
|
||
(let ([h (make-hasheq)])
|
||
(cond [(metafunc-proc? subj)
|
||
(for-each
|
||
(λ (c) (hash-set! h (metafunc-case-id c) (cons (metafunc-case-src-loc c) 0)))
|
||
(metafunc-proc-cases subj))]
|
||
[(reduction-relation? subj)
|
||
(for-each
|
||
(λ (rwp)
|
||
(hash-set! h (rewrite-proc-id rwp) (cons (or (rewrite-proc-name rwp) (rewrite-proc-lhs-src rwp)) 0)))
|
||
(reduction-relation-make-procs subj))])
|
||
(make-coverage subj h))))]))
|
||
|
||
(define-syntax (test-match stx)
|
||
(syntax-case stx ()
|
||
[(form-name lang-exp pattern)
|
||
(identifier? #'lang-exp)
|
||
(let*-values ([(what) (syntax-e #'form-name)]
|
||
[(nts) (language-id-nts #'lang-exp what)]
|
||
[(ids/depths _) (extract-names nts what #t #'pattern)])
|
||
(with-syntax ([side-condition-rewritten (rewrite-side-conditions/check-errs nts what #t #'pattern)]
|
||
[binders (map syntax-e ids/depths)]
|
||
[name (syntax-local-infer-name stx)])
|
||
(syntax
|
||
(do-test-match lang-exp `side-condition-rewritten 'binders 'name))))]
|
||
[(form-name lang-exp pattern expression)
|
||
(identifier? #'lang-exp)
|
||
(syntax
|
||
((form-name lang-exp pattern) expression))]
|
||
[(_ a b c)
|
||
(raise-syntax-error 'redex-match "expected an identifier (bound to a language) as first argument" stx #'a)]
|
||
[(_ a b)
|
||
(raise-syntax-error 'redex-match "expected an identifier (bound to a language) as first argument" stx #'a)]))
|
||
|
||
(define-struct match (bindings) #:inspector #f)
|
||
|
||
(define (do-test-match lang pat binders context-name)
|
||
(unless (compiled-lang? lang)
|
||
(error 'redex-match "expected first argument to be a language, got ~e" lang))
|
||
(define name (or context-name
|
||
(and (symbol? pat)
|
||
pat)))
|
||
(define cpat (compile-pattern lang pat #t))
|
||
(define redex-match-proc
|
||
(λ (exp)
|
||
(let ([ans (match-pattern cpat exp)])
|
||
(and ans
|
||
(map (λ (m) (make-match (sort-bindings
|
||
(filter (λ (x) (memq (bind-name x) binders))
|
||
(bindings-table (mtch-bindings m))))))
|
||
ans)))))
|
||
(if name
|
||
(procedure-rename redex-match-proc name)
|
||
redex-match-proc))
|
||
|
||
(define (sort-bindings bnds)
|
||
(sort
|
||
bnds
|
||
(λ (x y) (string-ci<=? (symbol->string (bind-name x))
|
||
(symbol->string (bind-name y))))))
|
||
|
||
(define-values (struct:metafunc-proc make-metafunc-proc metafunc-proc? metafunc-proc-ref metafunc-proc-set!)
|
||
(make-struct-type 'metafunc-proc #f 9 0 #f null (current-inspector) 0))
|
||
(define metafunc-proc-pict-info (make-struct-field-accessor metafunc-proc-ref 1))
|
||
(define metafunc-proc-lang (make-struct-field-accessor metafunc-proc-ref 2))
|
||
(define metafunc-proc-multi-arg? (make-struct-field-accessor metafunc-proc-ref 3))
|
||
(define metafunc-proc-name (make-struct-field-accessor metafunc-proc-ref 4))
|
||
(define metafunc-proc-in-dom? (make-struct-field-accessor metafunc-proc-ref 5))
|
||
(define metafunc-proc-dom-pat (make-struct-field-accessor metafunc-proc-ref 6))
|
||
(define metafunc-proc-cases (make-struct-field-accessor metafunc-proc-ref 7))
|
||
(define metafunc-proc-relation? (make-struct-field-accessor metafunc-proc-ref 8))
|
||
|
||
(define-struct metafunction (proc))
|
||
|
||
(define-struct metafunc-case (lhs rhs lhs+ src-loc id))
|
||
|
||
;; Intermediate structures recording clause "extras" for typesetting.
|
||
(define-struct metafunc-extra-side-cond (expr))
|
||
(define-struct metafunc-extra-where (lhs rhs))
|
||
(define-struct metafunc-extra-fresh (vars))
|
||
|
||
(define-syntax (in-domain? stx)
|
||
(syntax-case stx ()
|
||
[(_ (name exp ...))
|
||
(begin
|
||
(unless (identifier? #'name)
|
||
(raise-syntax-error #f "expected an identifier" stx #'name))
|
||
#'(in-domain?/proc (metafunction-form name) (term (exp ...))))]))
|
||
|
||
(define (in-domain?/proc mf exp)
|
||
(let ([mp (metafunction-proc mf)])
|
||
((metafunc-proc-in-dom? mp)
|
||
exp)))
|
||
|
||
(define-for-syntax (definition-nts lang orig-stx syn-error-name)
|
||
(unless (identifier? lang)
|
||
(raise-syntax-error #f "expected an identifier in the language position" orig-stx lang))
|
||
(language-id-nts lang syn-error-name))
|
||
|
||
(define-for-syntax (lhs-lws clauses)
|
||
(with-syntax ([((lhs-for-lw _ ...) ...) clauses])
|
||
(map (λ (x) (to-lw/proc (datum->syntax #f (cdr (syntax-e x)) x)))
|
||
(syntax->list #'(lhs-for-lw ...)))))
|
||
|
||
;
|
||
;
|
||
;
|
||
; ; ;;; ; ;;
|
||
; ;; ;;;; ;; ;;
|
||
; ;;;;;;; ;;;; ;;; ;;;;; ;;;;;;; ;;;;; ;;;; ;;;; ;;;; ;;; ;;;;; ;;;;; ;;;; ;;;; ;;;
|
||
; ;;;;;;;;;;;;; ;;;;; ;;;;;; ;;;;;;;; ;;;; ;;;; ;;;; ;;;;;;;;; ;;;;;; ;;;;;; ;;;; ;;;;;; ;;;;;;;;;
|
||
; ;;;; ;;; ;;;; ;;;; ;; ;;;; ;;;; ;;;;;; ;;;; ;;;; ;;;; ;;;; ;;;;;;; ;;;; ;;;; ;;;;;;;; ;;;; ;;;;
|
||
; ;;;; ;;; ;;;; ;;;;;;; ;;;; ;;;;;;; ;;;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;; ;;;; ;;;;
|
||
; ;;;; ;;; ;;;; ;;;;; ;;;;; ;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;;;;; ;;;;; ;;;; ;;;;;;;; ;;;; ;;;;
|
||
; ;;;; ;;; ;;;; ;;;;;; ;;;;; ;;;;;;;; ;;;; ;;;;;;;;; ;;;; ;;;; ;;;;;; ;;;;; ;;;; ;;;;;; ;;;; ;;;;
|
||
; ;;;; ;;; ;;;; ;;;; ;;;; ;; ;;;; ;;;; ;;; ;;;; ;;;; ;;;; ;;;;; ;;;; ;;;; ;;;; ;;;; ;;;;
|
||
;
|
||
;
|
||
;
|
||
|
||
(define-syntax-set (define-metafunction define-metafunction/extension
|
||
define-relation
|
||
define-judgment-form)
|
||
|
||
(define (define-metafunction/proc stx)
|
||
(syntax-case stx ()
|
||
[(_ . rest)
|
||
(internal-define-metafunction stx #f #'rest #f)]))
|
||
|
||
(define (define-relation/proc stx)
|
||
(syntax-case stx ()
|
||
[(_ . rest)
|
||
;; need to rule out the contracts for this one
|
||
(internal-define-metafunction stx #f #'rest #t)]))
|
||
|
||
(define (define-metafunction/extension/proc stx)
|
||
(syntax-case stx ()
|
||
[(_ prev . rest)
|
||
(identifier? #'prev)
|
||
(internal-define-metafunction stx #'prev #'rest #f)]))
|
||
|
||
(define (internal-define-metafunction orig-stx prev-metafunction stx relation?)
|
||
(not-expression-context orig-stx)
|
||
(syntax-case stx ()
|
||
[(lang . rest)
|
||
(let ([syn-error-name (if relation?
|
||
'define-relation
|
||
(if prev-metafunction
|
||
'define-metafunction/extension
|
||
'define-metafunction))])
|
||
(define lang-nts
|
||
;; keep this near the beginning, so it signals the first error (PR 10062)
|
||
(definition-nts #'lang orig-stx syn-error-name))
|
||
(when (null? (syntax-e #'rest))
|
||
(raise-syntax-error syn-error-name "no clauses" orig-stx))
|
||
(when prev-metafunction
|
||
(syntax-local-value
|
||
prev-metafunction
|
||
(λ ()
|
||
(raise-syntax-error syn-error-name "expected a previously defined metafunction" orig-stx prev-metafunction))))
|
||
(let*-values ([(contract-name dom-ctcs codom-contracts pats)
|
||
(split-out-contract orig-stx syn-error-name #'rest relation?)]
|
||
[(name _) (defined-name (list contract-name) pats orig-stx)])
|
||
(with-syntax ([(((original-names lhs-clauses ...) raw-rhses ...) ...) pats]
|
||
[(lhs-for-lw ...) (lhs-lws pats)])
|
||
(with-syntax ([((rhs stuff ...) ...) (if relation?
|
||
#'((,(and (term raw-rhses) ...)) ...)
|
||
#'((raw-rhses ...) ...))])
|
||
(with-syntax ([(lhs ...) #'((lhs-clauses ...) ...)]
|
||
[name name])
|
||
(when (and prev-metafunction (eq? (syntax-e #'name) (syntax-e prev-metafunction)))
|
||
(raise-syntax-error syn-error-name "the extended and extending metafunctions cannot share a name" orig-stx prev-metafunction))
|
||
(parse-extras #'((stuff ...) ...))
|
||
(let-values ([(lhs-namess lhs-namess/ellipsess)
|
||
(lhss-bound-names (syntax->list (syntax (lhs ...))) lang-nts syn-error-name)])
|
||
(with-syntax ([(rhs/wheres ...)
|
||
(map (λ (sc/b rhs names names/ellipses)
|
||
(bind-withs
|
||
syn-error-name '()
|
||
#'effective-lang lang-nts
|
||
sc/b 'flatten
|
||
#`(list (term #,rhs))
|
||
names names/ellipses))
|
||
(syntax->list #'((stuff ...) ...))
|
||
(syntax->list #'(rhs ...))
|
||
lhs-namess lhs-namess/ellipsess)]
|
||
[(rg-rhs/wheres ...)
|
||
(map (λ (sc/b rhs names names/ellipses)
|
||
(bind-withs
|
||
syn-error-name '()
|
||
#'effective-lang lang-nts
|
||
sc/b 'predicate
|
||
#`#t
|
||
names names/ellipses))
|
||
(syntax->list #'((stuff ...) ...))
|
||
(syntax->list #'(rhs ...))
|
||
lhs-namess lhs-namess/ellipsess)])
|
||
(with-syntax ([(side-conditions-rewritten ...)
|
||
(map (λ (x) (rewrite-side-conditions/check-errs
|
||
lang-nts
|
||
syn-error-name
|
||
#t
|
||
x))
|
||
(syntax->list (syntax (lhs ...))))]
|
||
[(rg-side-conditions-rewritten ...)
|
||
(map (λ (x) (rewrite-side-conditions/check-errs
|
||
lang-nts
|
||
syn-error-name
|
||
#t
|
||
x))
|
||
(syntax->list (syntax ((side-condition lhs rg-rhs/wheres) ...))))]
|
||
[(clause-src ...)
|
||
(map (λ (lhs)
|
||
(format "~a:~a:~a"
|
||
(syntax-source lhs)
|
||
(syntax-line lhs)
|
||
(syntax-column lhs)))
|
||
pats)]
|
||
[dom-side-conditions-rewritten
|
||
(and dom-ctcs
|
||
(rewrite-side-conditions/check-errs
|
||
lang-nts
|
||
syn-error-name
|
||
#f
|
||
dom-ctcs))]
|
||
[(codom-side-conditions-rewritten ...)
|
||
(map (λ (codom-contract)
|
||
(rewrite-side-conditions/check-errs
|
||
lang-nts
|
||
syn-error-name
|
||
#f
|
||
codom-contract))
|
||
codom-contracts)]
|
||
[(rhs-fns ...)
|
||
(map (λ (names names/ellipses rhs/where)
|
||
(with-syntax ([(names ...) names]
|
||
[(names/ellipses ...) names/ellipses]
|
||
[rhs/where rhs/where])
|
||
(syntax
|
||
(λ (name bindings)
|
||
(term-let-fn ((name name))
|
||
(term-let ([names/ellipses (lookup-binding bindings 'names)] ...)
|
||
rhs/where))))))
|
||
lhs-namess lhs-namess/ellipsess
|
||
(syntax->list (syntax (rhs/wheres ...))))]
|
||
[(name2 name-predicate) (generate-temporaries (syntax (name name)))])
|
||
(with-syntax ([defs #`(begin
|
||
(define-values (name2 name-predicate)
|
||
(let ([sc `(side-conditions-rewritten ...)]
|
||
[dsc `dom-side-conditions-rewritten])
|
||
(let ([cases (map (λ (pat rhs-fn rg-lhs src)
|
||
(make-metafunc-case
|
||
(λ (effective-lang) (compile-pattern effective-lang pat #t))
|
||
rhs-fn
|
||
rg-lhs src (gensym)))
|
||
sc
|
||
(list (λ (effective-lang) rhs-fns) ...)
|
||
(list (λ (effective-lang) `rg-side-conditions-rewritten) ...)
|
||
`(clause-src ...))]
|
||
[parent-cases
|
||
#,(if prev-metafunction
|
||
#`(metafunc-proc-cases #,(term-fn-get-id (syntax-local-value prev-metafunction)))
|
||
#'null)])
|
||
(build-metafunction
|
||
lang
|
||
cases
|
||
parent-cases
|
||
(λ (f/dom)
|
||
(make-metafunc-proc
|
||
(let ([name (lambda (x) (f/dom x))]) name)
|
||
(generate-lws #,relation?
|
||
(lhs ...)
|
||
(lhs-for-lw ...)
|
||
((stuff ...) ...)
|
||
#,(if relation?
|
||
#'((raw-rhses ...) ...)
|
||
#'(rhs ...)))
|
||
lang
|
||
#t ;; multi-args?
|
||
'name
|
||
(let ([name (lambda (x) (name-predicate x))]) name)
|
||
dsc
|
||
(append cases parent-cases)
|
||
#,relation?))
|
||
dsc
|
||
`(codom-side-conditions-rewritten ...)
|
||
'name
|
||
#,relation?))))
|
||
(term-define-fn name name2))])
|
||
(syntax-property
|
||
(prune-syntax
|
||
(if (eq? 'top-level (syntax-local-context))
|
||
; Introduce the names before using them, to allow
|
||
; metafunction definition at the top-level.
|
||
(syntax
|
||
(begin
|
||
(define-syntaxes (name2 name-predicate) (values))
|
||
defs))
|
||
(syntax defs)))
|
||
'disappeared-use
|
||
(map syntax-local-introduce
|
||
(syntax->list #'(original-names ...)))))))))))))]))
|
||
|
||
(define (define-judgment-form/proc stx)
|
||
(not-expression-context stx)
|
||
(syntax-case stx ()
|
||
[(def-form-id lang . body)
|
||
(let ([lang #'lang]
|
||
[syn-err-name (syntax-e #'def-form-id)])
|
||
(define nts (definition-nts lang stx syn-err-name))
|
||
(define-values (judgment-form-name dup-form-names mode position-contracts clauses)
|
||
(parse-judgment-form-body #'body syn-err-name stx))
|
||
(define definitions
|
||
#`(begin
|
||
(define-syntax #,judgment-form-name
|
||
(judgment-form '#,judgment-form-name '#,mode #'judgment-form-proc #'#,lang #'judgment-form-lws))
|
||
(define judgment-form-proc
|
||
(compile-judgment-form-proc #,judgment-form-name #,lang #,mode #,clauses #,position-contracts #,stx #,syn-err-name))
|
||
(define judgment-form-lws
|
||
(compiled-judgment-form-lws #,clauses))))
|
||
(syntax-property
|
||
(prune-syntax
|
||
(if (eq? 'top-level (syntax-local-context))
|
||
; Introduce the names before using them, to allow
|
||
; judgment form definition at the top-level.
|
||
#`(begin
|
||
(define-syntaxes (judgment-form-proc judgment-form-lws) (values))
|
||
#,definitions)
|
||
definitions))
|
||
'disappeared-use
|
||
(map syntax-local-introduce dup-form-names)))]))
|
||
|
||
(define (parse-judgment-form-body body syn-err-name full-stx)
|
||
(define-syntax-class pos-mode
|
||
#:literals (I O)
|
||
(pattern I)
|
||
(pattern O))
|
||
(define-syntax-class mode-spec
|
||
#:description "mode specification"
|
||
(pattern (_:id _:pos-mode ...)))
|
||
(define-syntax-class contract-spec
|
||
#:description "contract specification"
|
||
(pattern (_:id _:expr ...)))
|
||
(define (horizontal-line? id)
|
||
(regexp-match? #rx"^-+$" (symbol->string (syntax-e id))))
|
||
(define-syntax-class horizontal-line
|
||
(pattern x:id #:when (horizontal-line? #'x)))
|
||
(define (parse-rules rules)
|
||
(for/list ([rule rules])
|
||
(syntax-parse rule
|
||
[(prem ... _:horizontal-line conc)
|
||
#'(conc prem ...)]
|
||
[_ rule])))
|
||
(define-values (name/mode mode name/contract contract rules)
|
||
(syntax-parse body #:context full-stx
|
||
[((~or (~seq #:mode ~! mode:mode-spec)
|
||
(~seq #:contract ~! contract:contract-spec))
|
||
... . rules:expr)
|
||
(let-values ([(name/mode mode)
|
||
(syntax-parse #'(mode ...)
|
||
[((name . mode)) (values #'name (syntax->list #'mode))]
|
||
[_ (raise-syntax-error
|
||
#f "expected definition to include a mode specification"
|
||
full-stx)])]
|
||
[(name/ctc ctc)
|
||
(syntax-parse #'(contract ...)
|
||
[() (values #f #f)]
|
||
[((name . contract)) (values #'name (syntax->list #'contract))]
|
||
[(_ . dups)
|
||
(raise-syntax-error
|
||
syn-err-name "expected at most one contract specification"
|
||
#f #f (syntax->list #'dups))])])
|
||
(values name/mode mode name/ctc ctc (parse-rules #'rules)))]))
|
||
(check-clauses full-stx syn-err-name rules #t)
|
||
(check-arity-consistency mode contract full-stx)
|
||
(define-values (form-name dup-names)
|
||
(syntax-case rules ()
|
||
[() (raise-syntax-error #f "expected at least one rule" full-stx)]
|
||
[_ (defined-name (list name/mode name/contract) rules full-stx)]))
|
||
(values form-name dup-names mode contract rules))
|
||
|
||
(define (check-arity-consistency mode contracts full-def)
|
||
(when (and contracts (not (= (length mode) (length contracts))))
|
||
(raise-syntax-error
|
||
#f "mode and contract specify different numbers of positions" full-def)))
|
||
|
||
(define (lhss-bound-names lhss nts syn-error-name)
|
||
(let loop ([lhss lhss])
|
||
(if (null? lhss)
|
||
(values null null)
|
||
(let-values ([(namess namess/ellipsess)
|
||
(loop (cdr lhss))]
|
||
[(names names/ellipses)
|
||
(extract-names nts syn-error-name #t (car lhss))])
|
||
(values (cons names namess)
|
||
(cons names/ellipses namess/ellipsess))))))
|
||
|
||
(define (defined-name declared-names clauses orig-stx)
|
||
(with-syntax ([(((used-names _ ...) _ ...) ...) clauses])
|
||
(define-values (the-name other-names)
|
||
(let ([present (filter values declared-names)])
|
||
(if (null? present)
|
||
(values (car (syntax->list #'(used-names ...)))
|
||
(cdr (syntax->list #'(used-names ...))))
|
||
(values (car present)
|
||
(append (cdr present) (syntax->list #'(used-names ...)))))))
|
||
(let loop ([others other-names])
|
||
(cond
|
||
[(null? others) (values the-name other-names)]
|
||
[else
|
||
(unless (eq? (syntax-e the-name) (syntax-e (car others)))
|
||
(raise-syntax-error
|
||
#f
|
||
"expected the same name in both positions"
|
||
orig-stx
|
||
the-name (list (car others))))
|
||
(loop (cdr others))]))))
|
||
|
||
(define (split-out-contract stx syn-error-name rest relation?)
|
||
;; initial test determines if a contract is specified or not
|
||
(cond
|
||
[(pair? (syntax-e (car (syntax->list rest))))
|
||
(values #f #f (list #'any) (check-clauses stx syn-error-name (syntax->list rest) relation?))]
|
||
[else
|
||
(syntax-case rest ()
|
||
[(id separator more ...)
|
||
(identifier? #'id)
|
||
(cond
|
||
[relation?
|
||
(let-values ([(contract clauses)
|
||
(parse-relation-contract #'(separator more ...) syn-error-name stx)])
|
||
(when (null? clauses)
|
||
(raise-syntax-error syn-error-name
|
||
"expected clause definitions to follow domain contract"
|
||
stx))
|
||
(values #'id contract (list #'any) (check-clauses stx syn-error-name clauses #t)))]
|
||
[else
|
||
(unless (eq? ': (syntax-e #'separator))
|
||
(raise-syntax-error syn-error-name "expected a colon to follow the meta-function's name" stx #'separator))
|
||
(let loop ([more (syntax->list #'(more ...))]
|
||
[dom-pats '()])
|
||
(cond
|
||
[(null? more)
|
||
(raise-syntax-error syn-error-name "expected an ->" stx)]
|
||
[(eq? (syntax-e (car more)) '->)
|
||
(define-values (raw-clauses rev-codomains)
|
||
(let loop ([prev (car more)]
|
||
[more (cdr more)]
|
||
[codomains '()])
|
||
(cond
|
||
[(null? more)
|
||
(raise-syntax-error syn-error-name "expected a range contract to follow" stx prev)]
|
||
[else
|
||
(define after-this-one (cdr more))
|
||
(cond
|
||
[(null? after-this-one)
|
||
(values null (cons (car more) codomains))]
|
||
[else
|
||
(define kwd (cadr more))
|
||
(cond
|
||
[(member (syntax-e kwd) '(or ∨ ∪))
|
||
(loop kwd
|
||
(cddr more)
|
||
(cons (car more) codomains))]
|
||
[else
|
||
(values (cdr more)
|
||
(cons (car more) codomains))])])])))
|
||
(let ([doms (reverse dom-pats)]
|
||
[clauses (check-clauses stx syn-error-name raw-clauses relation?)])
|
||
(values #'id doms (reverse rev-codomains) clauses))]
|
||
[else
|
||
(loop (cdr more) (cons (car more) dom-pats))]))])]
|
||
[_
|
||
(raise-syntax-error
|
||
syn-error-name
|
||
(format "expected the name of the ~a, followed by its contract (or no name and no contract)"
|
||
(if relation? "relation" "meta-function"))
|
||
stx
|
||
rest)])]))
|
||
|
||
(define (check-clauses stx syn-error-name rest relation?)
|
||
(syntax-case rest ()
|
||
[([(lhs ...) roc1 roc2 ...] ...)
|
||
rest]
|
||
[([(lhs ...) rhs ...] ...)
|
||
(if relation?
|
||
rest
|
||
(begin
|
||
(for-each
|
||
(λ (clause)
|
||
(syntax-case clause ()
|
||
[(a b) (void)]
|
||
[x (raise-syntax-error syn-error-name "expected a pattern and a right-hand side" stx clause)]))
|
||
rest)
|
||
(raise-syntax-error syn-error-name "error checking failed.3" stx)))]
|
||
[([x roc ...] ...)
|
||
(begin
|
||
(for-each
|
||
(λ (x)
|
||
(syntax-case x ()
|
||
[(lhs ...) (void)]
|
||
[x (raise-syntax-error syn-error-name "expected a function prototype" stx #'x)]))
|
||
(syntax->list #'(x ...)))
|
||
(raise-syntax-error syn-error-name "error checking failed.1" stx))]
|
||
[(x ...)
|
||
(begin
|
||
(for-each
|
||
(λ (x)
|
||
(syntax-case x ()
|
||
[(stuff ...) (void)]
|
||
[x (raise-syntax-error syn-error-name "expected a clause" stx #'x)]))
|
||
(syntax->list #'(x ...)))
|
||
(raise-syntax-error syn-error-name "error checking failed.2" stx))]))
|
||
|
||
(define (parse-extras extras)
|
||
(for-each
|
||
(λ (stuffs)
|
||
(for-each
|
||
(λ (stuff)
|
||
(syntax-case stuff (where side-condition where/hidden side-condition/hidden)
|
||
[(side-condition tl-side-conds ...)
|
||
(void)]
|
||
[(side-condition/hidden tl-side-conds ...)
|
||
(void)]
|
||
[(where x e)
|
||
(void)]
|
||
[(where/hidden x e)
|
||
(void)]
|
||
[(where . args)
|
||
(raise-syntax-error 'define-metafunction
|
||
"malformed where clause"
|
||
stuff)]
|
||
[(where/hidden . args)
|
||
(raise-syntax-error 'define-metafunction
|
||
"malformed where/hidden clause"
|
||
stuff)]
|
||
[_
|
||
(raise-syntax-error 'define-metafunction
|
||
"expected a side-condition or where clause"
|
||
stuff)]))
|
||
(syntax->list stuffs)))
|
||
(syntax->list extras)))
|
||
|
||
(define (parse-relation-contract after-name syn-error-name orig-stx)
|
||
(syntax-case after-name ()
|
||
[(subset . rest-pieces)
|
||
(unless (memq (syntax-e #'subset) '(⊂ ⊆))
|
||
(raise-syntax-error syn-error-name
|
||
"expected ⊂ or ⊆ to follow the relation's name"
|
||
orig-stx #'subset))
|
||
(let ([more (syntax->list #'rest-pieces)])
|
||
(when (null? more)
|
||
(raise-syntax-error syn-error-name
|
||
(format "expected a sequence of patterns separated by x or × to follow ~a"
|
||
(syntax-e #'subset))
|
||
orig-stx
|
||
#'subset))
|
||
(let loop ([more (cdr more)]
|
||
[arg-pats (list (car more))])
|
||
(cond
|
||
[(and (not (null? more)) (memq (syntax-e (car more)) '(x ×)))
|
||
(when (null? (cdr more))
|
||
(raise-syntax-error syn-error-name
|
||
(format "expected a pattern to follow ~a" (syntax-e (car more)))
|
||
orig-stx (car more)))
|
||
(loop (cddr more)
|
||
(cons (cadr more) arg-pats))]
|
||
[else (values (reverse arg-pats) more)])))])))
|
||
|
||
(define-syntax (judgment-holds stx)
|
||
(syntax-case stx ()
|
||
[(j-h judgment)
|
||
#`(not (null? #,(syntax/loc stx (j-h judgment #t))))]
|
||
[(j-h (form-name . pats) tmpl)
|
||
(judgment-form-id? #'form-name)
|
||
(let* ([syn-err-name (syntax-e #'j-h)]
|
||
[lang (judgment-form-lang (syntax-local-value #'form-name))]
|
||
[nts (definition-nts lang stx syn-err-name)]
|
||
[judgment (syntax-case stx () [(_ judgment _) #'judgment])])
|
||
(check-judgment-arity judgment)
|
||
(bind-withs syn-err-name '() lang nts (list judgment)
|
||
'flatten #`(list (term #,#'tmpl)) '() '()))]
|
||
[(_ (not-form-name . _) . _)
|
||
(not (judgment-form-id? #'form-name))
|
||
(raise-syntax-error #f "expected a judgment form name" stx #'not-form-name)]))
|
||
|
||
(define-for-syntax (do-compile-judgment-form-proc name mode clauses contracts nts lang syn-error-name)
|
||
(define (compile-clause clause)
|
||
(syntax-case clause ()
|
||
[((_ . conc-pats) . prems)
|
||
(let-values ([(input-pats output-pats) (split-by-mode (syntax->list #'conc-pats) mode)])
|
||
(define-values (input-names input-names/ellipses)
|
||
(extract-names nts syn-error-name #t input-pats))
|
||
(define ((rewrite-pattern binds?) pat)
|
||
(rewrite-side-conditions/check-errs nts syn-error-name binds? pat))
|
||
(define (contracts-compilation ctcs)
|
||
(and ctcs #`(map (λ (p) (compile-pattern #,lang p #f)) `#,ctcs)))
|
||
(define-values (input-contracts output-contracts)
|
||
(syntax-case contracts ()
|
||
[#f (values #f #f)]
|
||
[(p ...)
|
||
(let-values ([(ins outs) (split-by-mode (syntax->list #'(p ...)) mode)])
|
||
(values (map (rewrite-pattern #f) ins)
|
||
(map (rewrite-pattern #f) outs)))]))
|
||
(define lhs (map (rewrite-pattern #t) input-pats))
|
||
(define body
|
||
(bind-withs syn-error-name '() lang nts (syntax->list #'prems)
|
||
'flatten #`(list (term (#,@output-pats))) input-names input-names/ellipses))
|
||
(with-syntax ([(names ...) input-names]
|
||
[(names/ellipses ...) input-names/ellipses])
|
||
#`(let ([compiled-lhs (compile-pattern #,lang `#,lhs #t)]
|
||
[compiled-input-ctcs #,(contracts-compilation input-contracts)]
|
||
[compiled-output-ctcs #,(contracts-compilation output-contracts)])
|
||
(λ (input)
|
||
(check-judgment-form-contract `#,name input compiled-input-ctcs 'I '#,mode)
|
||
(define mtchs (match-pattern compiled-lhs input))
|
||
(define outputs
|
||
(if mtchs
|
||
(for/fold ([outputs '()]) ([m mtchs])
|
||
(define os
|
||
(term-let ([names/ellipses (lookup-binding (mtch-bindings m) 'names)] ...)
|
||
#,body))
|
||
(if os (append os outputs) outputs))
|
||
'()))
|
||
(for ([output outputs])
|
||
(check-judgment-form-contract `#,name output compiled-output-ctcs 'O '#,mode))
|
||
outputs))))]))
|
||
(with-syntax ([(clause-proc ...) (map compile-clause clauses)])
|
||
#'(λ (input)
|
||
(for/fold ([outputs '()]) ([rule (list clause-proc ...)])
|
||
(append (rule input) outputs)))))
|
||
|
||
(define-for-syntax (in-order-non-hidden extras)
|
||
(reverse
|
||
(filter (λ (extra)
|
||
(syntax-case extra (where/hidden
|
||
side-condition/hidden)
|
||
[(where/hidden pat exp) #f]
|
||
[(side-condition/hidden x) #f]
|
||
[_ #t]))
|
||
(syntax->list extras))))
|
||
|
||
(define-for-syntax (do-compile-judgment-form-lws clauses)
|
||
(syntax-case clauses ()
|
||
[(((_ . conc-body) . prems) ...)
|
||
(let ([rev-premss
|
||
; for consistency with metafunction extras
|
||
(for/list ([prems (syntax->list #'(prems ...))])
|
||
(reverse (syntax->list prems)))]
|
||
[no-rhss (map (λ (_) '()) clauses)])
|
||
#`(generate-lws #t (conc-body ...) #,(lhs-lws clauses) #,rev-premss #,no-rhss))]))
|
||
|
||
(define (check-judgment-form-contract form-name terms contracts mode modes)
|
||
(define description
|
||
(case mode
|
||
[(I) "input"]
|
||
[(O) "output"]))
|
||
(when contracts
|
||
(let loop ([rest-modes modes] [rest-terms terms] [rest-ctcs contracts] [pos 1])
|
||
(unless (null? rest-modes)
|
||
(if (eq? mode (car rest-modes))
|
||
(if (match-pattern (car rest-ctcs) (car rest-terms))
|
||
(loop (cdr rest-modes) (cdr rest-terms) (cdr rest-ctcs) (+ 1 pos))
|
||
(redex-error form-name "~a ~s at position ~s does not match its contract"
|
||
description (car rest-terms) pos))
|
||
(loop (cdr rest-modes) rest-terms rest-ctcs (+ 1 pos)))))))
|
||
|
||
(define-for-syntax (mode-check mode clauses nts syn-err-name)
|
||
(define ((check-template bound-anywhere) temp bound)
|
||
(let check ([t temp])
|
||
(syntax-case t (unquote)
|
||
[(unquote . _)
|
||
(raise-syntax-error syn-err-name "unquote unsupported" t)]
|
||
[x
|
||
(identifier? #'x)
|
||
(unless (cond [(free-id-table-ref bound-anywhere #'x #f)
|
||
(free-id-table-ref bound #'x #f)]
|
||
[(id-binds? nts #t #'x)
|
||
(term-fn? (syntax-local-value #'x (λ () #f)))]
|
||
[else #t])
|
||
(raise-syntax-error syn-err-name "unbound pattern variable" #'x))]
|
||
[(u ...)
|
||
(for-each check (syntax->list #'(u ...)))]
|
||
[_ (void)])))
|
||
(define ((bind kind) pat bound)
|
||
(define-values (ids _)
|
||
(extract-names nts syn-err-name #t pat kind))
|
||
(for/fold ([b bound]) ([x ids])
|
||
(free-id-table-set b x #t)))
|
||
(define (split-body judgment)
|
||
(syntax-case judgment ()
|
||
[(form-name . body)
|
||
(split-by-mode (syntax->list #'body) (judgment-form-mode (syntax-local-value #'form-name)))]))
|
||
(define (drop-ellipses prems)
|
||
(syntax-case prems ()
|
||
[() '()]
|
||
[(prem maybe-ellipsis . remaining)
|
||
(ellipsis? #'maybe-ellipsis)
|
||
(syntax-case #'prem ()
|
||
[(form-name . _)
|
||
(judgment-form-id? #'form-name)
|
||
(cons #'prem (drop-ellipses #'remaining))]
|
||
[_ (raise-syntax-error syn-err-name "ellipses must follow judgment form uses" #'maybe-ellipsis)])]
|
||
[(prem . remaining)
|
||
(cons #'prem (drop-ellipses #'remaining))]))
|
||
(define (fold-clause pat-pos tmpl-pos acc-init clause)
|
||
(syntax-case clause ()
|
||
[(conc . prems)
|
||
(let-values ([(conc-in conc-out) (split-body #'conc)])
|
||
(check-judgment-arity #'conc)
|
||
(define acc-out
|
||
(for/fold ([acc (foldl pat-pos acc-init conc-in)])
|
||
([prem (drop-ellipses #'prems)])
|
||
(syntax-case prem ()
|
||
[(-where pat tmpl)
|
||
(where-keyword? #'-where)
|
||
(begin
|
||
(tmpl-pos #'tmpl acc)
|
||
(pat-pos #'pat acc))]
|
||
[(form-name . _)
|
||
(if (judgment-form-id? #'form-name)
|
||
(let-values ([(prem-in prem-out) (split-body prem)])
|
||
(check-judgment-arity prem)
|
||
(for ([pos prem-in]) (tmpl-pos pos acc))
|
||
(foldl pat-pos acc prem-out))
|
||
(raise-syntax-error syn-err-name "expected judgment form name" #'form-name))]
|
||
[_ (raise-syntax-error syn-err-name "malformed premise" prem)])))
|
||
(for ([pos conc-out]) (tmpl-pos pos acc-out))
|
||
acc-out)]))
|
||
(for ([clause clauses])
|
||
(define do-tmpl
|
||
(check-template
|
||
(fold-clause (bind 'rhs-only) void (make-immutable-free-id-table) clause)))
|
||
(fold-clause (bind 'rhs-only) do-tmpl (make-immutable-free-id-table) clause)))
|
||
|
||
;; Defined as a macro instead of an ordinary phase 1 function so that the
|
||
;; to-lw/proc calls occur after bindings are established for all meta-functions
|
||
;; and relations.
|
||
(define-syntax (generate-lws stx)
|
||
(syntax-case stx ()
|
||
[(_ relation? seq-of-lhs seq-of-lhs-for-lw seq-of-tl-side-cond/binds seq-of-rhs)
|
||
(with-syntax
|
||
([(rhs/lw ...)
|
||
(syntax-case #'relation? ()
|
||
[#t (map (λ (x) #`(list #,@(map to-lw/proc (syntax->list x))))
|
||
(syntax->list #'seq-of-rhs))]
|
||
[#f (map to-lw/proc (syntax->list #'seq-of-rhs))])]
|
||
[(((bind-id/lw . bind-pat/lw) ...) ...)
|
||
;; Also for pict, extract pattern bindings
|
||
(map name-pattern-lws (syntax->list #'seq-of-lhs))]
|
||
[((where/sc/lw ...) ...)
|
||
;; Also for pict, extract where bindings
|
||
(map (λ (hm)
|
||
(map
|
||
(λ (lst)
|
||
(syntax-case lst (unquote side-condition where)
|
||
[(form-name . _)
|
||
(judgment-form-id? #'form-name)
|
||
#`(make-metafunc-extra-side-cond #,(to-lw/proc lst))]
|
||
[(where pat (unquote (f _ _)))
|
||
(and (or (identifier? #'pat)
|
||
(andmap identifier? (syntax->list #'pat)))
|
||
(or (free-identifier=? #'f #'variable-not-in)
|
||
(free-identifier=? #'f #'variables-not-in)))
|
||
(with-syntax ([(ids ...)
|
||
(map to-lw/proc
|
||
(if (identifier? #'pat)
|
||
(list #'pat)
|
||
(syntax->list #'pat)))])
|
||
#`(make-metafunc-extra-fresh
|
||
(list ids ...)))]
|
||
[(where pat exp)
|
||
#`(make-metafunc-extra-where
|
||
#,(to-lw/proc #'pat) #,(to-lw/proc #'exp))]
|
||
[(side-condition x)
|
||
#`(make-metafunc-extra-side-cond
|
||
#,(to-lw/uq/proc #'x))]
|
||
[maybe-ellipsis
|
||
(ellipsis? #'maybe-ellipsis)
|
||
(to-lw/proc #'maybe-ellipsis)]))
|
||
(in-order-non-hidden hm)))
|
||
(syntax->list #'seq-of-tl-side-cond/binds))]
|
||
[(((where-bind-id/lw . where-bind-pat/lw) ...) ...)
|
||
(map (λ (clauses)
|
||
(for/fold ([binds '()]) ([clause (in-order-non-hidden clauses)])
|
||
(syntax-case clause (where)
|
||
[(form-name . pieces)
|
||
(judgment-form-id? #'form-name)
|
||
(let*-values ([(mode) (judgment-form-mode (syntax-local-value #'form-name))]
|
||
[(_ outs) (split-by-mode (syntax->list #'pieces) mode)])
|
||
(for/fold ([binds binds]) ([out outs])
|
||
(append (name-pattern-lws out) binds)))]
|
||
[(where lhs rhs) (append (name-pattern-lws #'lhs) binds)]
|
||
[_ binds])))
|
||
(syntax->list #'seq-of-tl-side-cond/binds))]
|
||
[(((rhs-bind-id/lw . rhs-bind-pat/lw/uq) ...) ...)
|
||
;; Also for pict, extract pattern bindings
|
||
(map (λ (x) (map (λ (x) (cons (to-lw/proc (car x)) (to-lw/uq/proc (cdr x))))
|
||
(extract-term-let-binds x)))
|
||
(syntax->list #'seq-of-rhs))]
|
||
|
||
[(x-lhs-for-lw ...) #'seq-of-lhs-for-lw])
|
||
#'(list (list x-lhs-for-lw
|
||
(list (make-metafunc-extra-where bind-id/lw bind-pat/lw) ...
|
||
(make-metafunc-extra-where where-bind-id/lw where-bind-pat/lw) ...
|
||
(make-metafunc-extra-where rhs-bind-id/lw rhs-bind-pat/lw/uq) ...
|
||
where/sc/lw ...)
|
||
rhs/lw)
|
||
...))]))
|
||
|
||
(define-syntax (compile-judgment-form-proc stx)
|
||
(syntax-case stx ()
|
||
[(_ judgment-form-name lang mode clauses ctcs full-def syn-err-name)
|
||
(let ([nts (definition-nts #'lang #'full-def (syntax-e #'syn-err-name))])
|
||
(mode-check (syntax->datum #'mode) (syntax->list #'clauses) nts (syntax-e #'syn-err-name))
|
||
(do-compile-judgment-form-proc
|
||
(syntax-e #'judgment-form-name)
|
||
(syntax->datum #'mode)
|
||
(syntax->list #'clauses)
|
||
#'ctcs
|
||
nts
|
||
#'lang
|
||
(syntax-e #'syn-err-name)))]))
|
||
|
||
(define-syntax (compiled-judgment-form-lws stx)
|
||
(syntax-case stx ()
|
||
[(_ clauses)
|
||
(do-compile-judgment-form-lws (syntax->list #'clauses))]))
|
||
|
||
(define (build-metafunction lang cases parent-cases wrap dom-contract-pat codom-contract-pats name relation?)
|
||
(let* ([dom-compiled-pattern (and dom-contract-pat (compile-pattern lang dom-contract-pat #f))]
|
||
[codom-compiled-patterns (map (λ (codom-contract-pat) (compile-pattern lang codom-contract-pat #f))
|
||
codom-contract-pats)]
|
||
[all-cases (append cases parent-cases)]
|
||
[lhss-at-lang (map (λ (case) ((metafunc-case-lhs case) lang)) all-cases)]
|
||
[rhss-at-lang (map (λ (case) ((metafunc-case-rhs case) lang)) all-cases)]
|
||
[ids (map metafunc-case-id all-cases)])
|
||
(values
|
||
(wrap
|
||
(letrec ([cache (make-hash)]
|
||
[cache-entries 0]
|
||
[not-in-cache (gensym)]
|
||
[cache-result (λ (arg res case)
|
||
(when (caching-enabled?)
|
||
(when (>= cache-entries cache-size)
|
||
(set! cache (make-hash))
|
||
(set! cache-entries 0))
|
||
(hash-set! cache arg (cons res case))
|
||
(set! cache-entries (add1 cache-entries))))]
|
||
[log-coverage (λ (id)
|
||
(when id
|
||
(for-each
|
||
(λ (c)
|
||
(let ([r (coverage-relation c)])
|
||
(when (and (metafunc-proc? r)
|
||
(findf (λ (c) (eq? id (metafunc-case-id c)))
|
||
(metafunc-proc-cases r)))
|
||
(cover-case id c))))
|
||
(relation-coverage))))]
|
||
[metafunc
|
||
(λ (exp)
|
||
(let ([cache-ref (hash-ref cache exp not-in-cache)])
|
||
(cond
|
||
[(or (not (caching-enabled?)) (eq? cache-ref not-in-cache))
|
||
(when dom-compiled-pattern
|
||
(unless (match-pattern dom-compiled-pattern exp)
|
||
(redex-error name
|
||
"~s is not in my domain"
|
||
`(,name ,@exp))))
|
||
(let loop ([ids ids]
|
||
[lhss lhss-at-lang]
|
||
[rhss rhss-at-lang]
|
||
[num (- (length parent-cases))])
|
||
(cond
|
||
[(null? ids)
|
||
(if relation?
|
||
(begin
|
||
(cache-result exp #f #f)
|
||
#f)
|
||
(redex-error name "no clauses matched for ~s" `(,name . ,exp)))]
|
||
[else
|
||
(let ([pattern (car lhss)]
|
||
[rhs (car rhss)]
|
||
[id (car ids)]
|
||
[continue (λ () (loop (cdr ids) (cdr lhss) (cdr rhss) (+ num 1)))])
|
||
(let ([mtchs (match-pattern pattern exp)])
|
||
(cond
|
||
[(not mtchs) (continue)]
|
||
[relation?
|
||
(let ([ans
|
||
(ormap (λ (mtch) (ormap values (rhs traced-metafunc (mtch-bindings mtch))))
|
||
mtchs)])
|
||
(unless (ormap (λ (codom-compiled-pattern) (match-pattern codom-compiled-pattern ans))
|
||
codom-compiled-patterns)
|
||
(redex-error name "codomain test failed for ~s, call was ~s" ans `(,name ,@exp)))
|
||
(cond
|
||
[ans
|
||
(cache-result exp #t id)
|
||
(log-coverage id)
|
||
#t]
|
||
[else
|
||
(continue)]))]
|
||
[else
|
||
(let ([anss (apply append
|
||
(filter values
|
||
(map (λ (mtch) (rhs traced-metafunc (mtch-bindings mtch)))
|
||
mtchs)))]
|
||
[ht (make-hash)])
|
||
(for-each (λ (ans) (hash-set! ht ans #t)) anss)
|
||
(cond
|
||
[(null? anss)
|
||
(continue)]
|
||
[(not (= 1 (hash-count ht)))
|
||
(redex-error name "~a matched ~s ~a different ways and returned different results"
|
||
(if (< num 0)
|
||
"a clause from an extended metafunction"
|
||
(format "clause #~a (counting from 0)" num))
|
||
`(,name ,@exp)
|
||
(length mtchs))]
|
||
[else
|
||
(let ([ans (car anss)])
|
||
(unless (ormap (λ (codom-compiled-pattern)
|
||
(match-pattern codom-compiled-pattern ans))
|
||
codom-compiled-patterns)
|
||
(redex-error name
|
||
"codomain test failed for ~s, call was ~s"
|
||
ans
|
||
`(,name ,@exp)))
|
||
(cache-result exp ans id)
|
||
(log-coverage id)
|
||
ans)]))])))]))]
|
||
[else
|
||
(log-coverage (cdr cache-ref))
|
||
(car cache-ref)])))]
|
||
[ot (current-trace-print-args)]
|
||
[otr (current-trace-print-results)]
|
||
[traced-metafunc (lambda (exp)
|
||
(if (or (eq? (current-traced-metafunctions) 'all)
|
||
(memq name (current-traced-metafunctions)))
|
||
(parameterize ([current-trace-print-args
|
||
(λ (name args kws kw-args level)
|
||
(if (or (not (caching-enabled?))
|
||
(eq? not-in-cache (hash-ref cache exp not-in-cache)))
|
||
(display " ")
|
||
(display "c"))
|
||
(ot name (car args) kws kw-args level))]
|
||
[current-trace-print-results
|
||
(λ (name results level)
|
||
(display " ")
|
||
(otr name results level))])
|
||
(trace-call name metafunc exp))
|
||
(metafunc exp)))])
|
||
traced-metafunc))
|
||
(if dom-compiled-pattern
|
||
(λ (exp) (and (match-pattern dom-compiled-pattern exp) #t))
|
||
(λ (exp) (and (ormap (λ (lhs) (match-pattern lhs exp)) lhss-at-lang)
|
||
#t))))))
|
||
|
||
(define current-traced-metafunctions (make-parameter '()))
|
||
|
||
(define-syntax (metafunction-form stx)
|
||
(syntax-case stx ()
|
||
[(_ id)
|
||
(identifier? #'id)
|
||
(let ([v (syntax-local-value #'id (lambda () #f))])
|
||
(if (term-fn? v)
|
||
(syntax-property
|
||
#`(make-metafunction #,(term-fn-get-id v))
|
||
'disappeared-use
|
||
(list #'id))
|
||
(raise-syntax-error
|
||
#f
|
||
"not bound as a metafunction"
|
||
stx
|
||
#'id)))]))
|
||
|
||
(define-for-syntax (mode-keyword stx)
|
||
(raise-syntax-error #f "keyword invalid outside of mode specification" stx))
|
||
(define-syntax I mode-keyword)
|
||
(define-syntax O mode-keyword)
|
||
|
||
(define-syntax (::= stx)
|
||
(raise-syntax-error #f "cannot be used outside a language definition" stx))
|
||
|
||
(define-for-syntax (parse-non-terminals nt-defs stx)
|
||
(define (parse-non-terminal def)
|
||
(define (delim? stx)
|
||
(and (identifier? stx) (free-identifier=? stx #'::=)))
|
||
(define-values (left delim right)
|
||
(syntax-case def ()
|
||
[(_ _ ...)
|
||
(let split ([xs def])
|
||
(syntax-case xs (::=)
|
||
[() (values '() #f '())]
|
||
[(x . prods)
|
||
(delim? #'x)
|
||
(values '() #'x (syntax->list #'prods))]
|
||
[(x . xs)
|
||
(let-values ([(l d r) (split #'xs)])
|
||
(values (cons #'x l) d r))]))]
|
||
[_ (raise-syntax-error #f "expected non-terminal definition" stx def)]))
|
||
(define (check-each xs bad? msg)
|
||
(define x (findf bad? xs))
|
||
(when x (raise-syntax-error #f msg stx x)))
|
||
(define-values (names prods)
|
||
(if delim
|
||
(begin
|
||
(when (null? left)
|
||
(raise-syntax-error #f "expected preceding non-terminal names" stx delim))
|
||
(values left right))
|
||
(values (syntax-case (car left) ()
|
||
[(x ...) (syntax->list #'(x ...))]
|
||
[x (list #'x)])
|
||
(cdr left))))
|
||
|
||
(check-each names (λ (x) (not (identifier? x)))
|
||
"expected non-terminal name")
|
||
(check-each names (λ (x) (memq (syntax-e x) (cons 'name underscore-allowed)))
|
||
"cannot use pattern language keyword as a non-terminal name")
|
||
(check-each names (λ (x) (regexp-match? #rx"_" (symbol->string (syntax-e x))))
|
||
"cannot use _ in a non-terminal name")
|
||
|
||
(when (null? prods)
|
||
(raise-syntax-error #f "expected at least one production to follow"
|
||
stx (or delim (car left))))
|
||
(check-each prods delim? "expected production")
|
||
(cons names prods))
|
||
(define parsed (map parse-non-terminal (syntax->list nt-defs)))
|
||
(define defs (make-hash))
|
||
(for ([p parsed])
|
||
(define ns (car p))
|
||
(for ([n ns])
|
||
(define m (hash-ref defs (syntax-e n) #f))
|
||
(if m
|
||
(raise-syntax-error #f "same non-terminal defined twice"
|
||
stx n (list m))
|
||
(hash-set! defs (syntax-e n) n))))
|
||
parsed)
|
||
|
||
(define-syntax (define-language stx)
|
||
(not-expression-context stx)
|
||
(syntax-case stx ()
|
||
[(form-name lang-name . nt-defs)
|
||
(begin
|
||
(unless (identifier? #'lang-name)
|
||
(raise-syntax-error #f "expected an identifier" stx #'lang-name))
|
||
(with-syntax ([(define-language-name) (generate-temporaries #'(lang-name))])
|
||
(let ([non-terms (parse-non-terminals #'nt-defs stx)])
|
||
(with-syntax ([((names prods ...) ...) non-terms]
|
||
[(all-names ...) (apply append (map car non-terms))])
|
||
(syntax/loc stx
|
||
(begin
|
||
(define-syntax lang-name
|
||
(make-set!-transformer
|
||
(make-language-id
|
||
(case-lambda
|
||
[(stx)
|
||
(syntax-case stx (set!)
|
||
[(set! x e) (raise-syntax-error (syntax-e #'form-name) "cannot set! identifier" stx #'e)]
|
||
[(x e (... ...)) #'(define-language-name e (... ...))]
|
||
[x
|
||
(identifier? #'x)
|
||
#'define-language-name])])
|
||
'(all-names ...))))
|
||
(define define-language-name (language form-name lang-name (all-names ...) (names prods ...) ...))))))))]))
|
||
|
||
(define-struct binds (source binds))
|
||
|
||
(define-syntax (language stx)
|
||
(syntax-case stx ()
|
||
[(_ form-name lang-id (all-names ...) (name rhs ...) ...)
|
||
(prune-syntax
|
||
(let ()
|
||
(let ([all-names (syntax->list #'(all-names ...))])
|
||
(with-syntax ([((r-rhs ...) ...)
|
||
(map (lambda (rhss)
|
||
(map (lambda (rhs)
|
||
(rewrite-side-conditions/check-errs
|
||
(map syntax-e all-names)
|
||
(syntax-e #'form-name)
|
||
#f
|
||
rhs))
|
||
(syntax->list rhss)))
|
||
(syntax->list (syntax ((rhs ...) ...))))]
|
||
[((rhs/lw ...) ...)
|
||
(map (lambda (rhss) (map to-lw/proc (syntax->list rhss)))
|
||
(syntax->list (syntax ((rhs ...) ...))))]
|
||
[(refs ...)
|
||
(let loop ([stx (syntax ((rhs ...) ...))])
|
||
(cond
|
||
[(identifier? stx)
|
||
(if (ormap (λ (x) (bound-identifier=? x stx))
|
||
all-names)
|
||
(list stx)
|
||
'())]
|
||
[(syntax? stx)
|
||
(loop (syntax-e stx))]
|
||
[(pair? stx)
|
||
(append (loop (car stx))
|
||
(loop (cdr stx)))]
|
||
[else '()]))])
|
||
(with-syntax ([(the-stx ...) (cdr (syntax-e stx))]
|
||
[(all-names ...) all-names]
|
||
[((uniform-names ...) ...)
|
||
(map (λ (x) (if (identifier? x) (list x) x))
|
||
(syntax->list (syntax (name ...))))]
|
||
[(first-names ...)
|
||
(map (λ (x) (if (identifier? x) x (car (syntax->list x))))
|
||
(syntax->list (syntax (name ...))))]
|
||
[((new-name orig-name) ...)
|
||
(apply
|
||
append
|
||
(map (λ (name-stx)
|
||
(if (identifier? name-stx)
|
||
'()
|
||
(let ([l (syntax->list name-stx)])
|
||
(map (λ (x) (list x (car l)))
|
||
(cdr l)))))
|
||
(syntax->list #'(name ...))))])
|
||
|
||
;; note: when there are multiple names for a single non-terminal,
|
||
;; we build equivalent non-terminals by redirecting all except the
|
||
;; first non-terminal to the first one, and then make the first one
|
||
;; actually have all of the productions. This should produce better
|
||
;; caching behavior and should compile faster than duplicating the
|
||
;; right-hand sides.
|
||
(syntax/loc stx
|
||
(begin
|
||
(let ([all-names 1] ...)
|
||
(begin (void) refs ...))
|
||
(compile-language (list (list '(uniform-names ...) rhs/lw ...) ...)
|
||
(list (make-nt 'first-names (list (make-rhs `r-rhs) ...)) ...
|
||
(make-nt 'new-name (list (make-rhs 'orig-name))) ...)
|
||
'((uniform-names ...) ...)))))))))]))
|
||
|
||
(define-syntax (define-extended-language stx)
|
||
(syntax-case stx ()
|
||
[(_ name orig-lang . nt-defs)
|
||
(begin
|
||
(unless (identifier? (syntax name))
|
||
(raise-syntax-error 'define-extended-language "expected an identifier" stx #'name))
|
||
(unless (identifier? (syntax orig-lang))
|
||
(raise-syntax-error 'define-extended-language "expected an identifier" stx #'orig-lang))
|
||
(let ([old-names (language-id-nts #'orig-lang 'define-extended-language)]
|
||
[non-terms (parse-non-terminals #'nt-defs stx)])
|
||
(with-syntax ([((names prods ...) ...) non-terms]
|
||
[(all-names ...) (apply append old-names (map car non-terms))]
|
||
[(define-language-name) (generate-temporaries #'(name))])
|
||
#'(begin
|
||
(define define-language-name (extend-language orig-lang (all-names ...) (names prods ...) ...))
|
||
(define-syntax name
|
||
(make-set!-transformer
|
||
(make-language-id
|
||
(λ (stx)
|
||
(syntax-case stx (set!)
|
||
[(set! x e) (raise-syntax-error 'define-extended-language "cannot set! identifier" stx #'e)]
|
||
[(x e (... ...)) #'(define-language-name e (... ...))]
|
||
[x
|
||
(identifier? #'x)
|
||
#'define-language-name]))
|
||
'(all-names ...))))))))]))
|
||
|
||
(define-syntax (extend-language stx)
|
||
(syntax-case stx ()
|
||
[(_ lang (all-names ...) (name rhs ...) ...)
|
||
(with-syntax ([((r-rhs ...) ...) (map (lambda (rhss) (map (λ (x) (rewrite-side-conditions/check-errs
|
||
(append (language-id-nts #'lang 'define-extended-language)
|
||
(map syntax-e
|
||
(syntax->list #'(all-names ...))))
|
||
'define-extended-language
|
||
#f
|
||
x))
|
||
(syntax->list rhss)))
|
||
(syntax->list (syntax ((rhs ...) ...))))]
|
||
[((rhs/lw ...) ...) (map (lambda (rhss) (map to-lw/proc (syntax->list rhss)))
|
||
(syntax->list (syntax ((rhs ...) ...))))]
|
||
[((uniform-names ...) ...)
|
||
(map (λ (x) (if (identifier? x) (list x) x))
|
||
(syntax->list (syntax (name ...))))]
|
||
|
||
[((new-name orig-name) ...)
|
||
(apply
|
||
append
|
||
(map (λ (name-stx)
|
||
(if (identifier? name-stx)
|
||
'()
|
||
(let ([l (syntax->list name-stx)])
|
||
(map (λ (x) (list x (car l)))
|
||
(cdr l)))))
|
||
(syntax->list #'(name ...))))])
|
||
(syntax/loc stx
|
||
(do-extend-language lang
|
||
(list (make-nt '(uniform-names ...) (list (make-rhs `r-rhs) ...)) ...)
|
||
(list (list '(uniform-names ...) rhs/lw ...) ...))))]))
|
||
|
||
(define extend-nt-ellipses '(....))
|
||
|
||
;; do-extend-language : compiled-lang (listof (listof nt)) ? -> compiled-lang
|
||
;; note: the nts that come here are an abuse of the `nt' struct; they have
|
||
;; lists of symbols in the nt-name field.
|
||
(define (do-extend-language old-lang new-nts new-pict-infos)
|
||
(unless (compiled-lang? old-lang)
|
||
(error 'define-extended-language "expected a language as first argument, got ~e" old-lang))
|
||
|
||
(let ([old-nts (compiled-lang-lang old-lang)]
|
||
[old-ht (make-hasheq)]
|
||
[new-ht (make-hasheq)])
|
||
|
||
|
||
(for-each (λ (nt)
|
||
(hash-set! old-ht (nt-name nt) nt)
|
||
(hash-set! new-ht (nt-name nt) nt))
|
||
old-nts)
|
||
|
||
(for-each (λ (raw-nt)
|
||
(let* ([names (nt-name raw-nt)]
|
||
[rhs (nt-rhs raw-nt)]
|
||
[primary-names (map (λ (name) (find-primary-nt name old-lang)) names)]
|
||
[main-primary (car primary-names)])
|
||
|
||
;; error checking
|
||
(when (and (ormap not primary-names)
|
||
(ormap symbol? primary-names))
|
||
(error 'define-extended-language "new language extends old non-terminal ~a and also adds new shortcut ~a"
|
||
(ormap (λ (x y) (and (symbol? x) y)) primary-names names)
|
||
(ormap (λ (x y) (and (not x) y)) primary-names names)))
|
||
|
||
;; error checking
|
||
(when (andmap symbol? primary-names)
|
||
(let ([main-orig (car names)])
|
||
(let loop ([primary-names (cdr primary-names)]
|
||
[names (cdr names)])
|
||
(cond
|
||
[(null? primary-names) void]
|
||
[else
|
||
(unless (eq? main-primary (car primary-names))
|
||
(error 'define-extended-language
|
||
(string-append
|
||
"new language does not have the same non-terminal aliases as the old,"
|
||
" non-terminal ~a was not in the same group as ~a in the old language")
|
||
(car names)
|
||
main-orig))
|
||
(loop (cdr primary-names) (cdr names))]))))
|
||
|
||
|
||
;; rebind original nt
|
||
(let ([nt (make-nt (or main-primary (car names)) rhs)])
|
||
(cond
|
||
[(ormap (λ (rhs) (member (rhs-pattern rhs) extend-nt-ellipses))
|
||
(nt-rhs nt))
|
||
(unless (hash-ref old-ht (nt-name nt) #f)
|
||
(error 'define-extended-language
|
||
"the language extends the ~s non-terminal, but that non-terminal is not in the old language"
|
||
(nt-name nt)))
|
||
(hash-set! new-ht
|
||
(nt-name nt)
|
||
(make-nt
|
||
(nt-name nt)
|
||
(append (nt-rhs (hash-ref old-ht (nt-name nt)))
|
||
(filter (λ (rhs) (not (member (rhs-pattern rhs) extend-nt-ellipses)))
|
||
(nt-rhs nt)))))]
|
||
[else
|
||
(hash-set! new-ht (nt-name nt) nt)]))
|
||
|
||
;; add new shortcuts (if necessary)
|
||
(unless main-primary
|
||
(for-each (λ (shortcut-name)
|
||
(hash-set! new-ht
|
||
shortcut-name
|
||
(make-nt shortcut-name (list (make-rhs (car names))))))
|
||
(cdr names)))))
|
||
|
||
new-nts)
|
||
|
||
(compile-language (vector (compiled-lang-pict-builder old-lang)
|
||
new-pict-infos)
|
||
(hash-map new-ht (λ (x y) y))
|
||
(compiled-lang-nt-map old-lang))))
|
||
|
||
;; find-primary-nt : symbol lang -> symbol or #f
|
||
;; returns the primary non-terminal for a given nt, or #f if `nt' isn't bound in the language.
|
||
(define (find-primary-nt nt lang)
|
||
(let ([combined (find-combined-nts nt lang)])
|
||
(and combined
|
||
(car combined))))
|
||
|
||
;; find-combined-nts : symbol lang -> (listof symbol) or #f
|
||
;; returns the combined set of non-terminals for 'nt' from lang
|
||
(define (find-combined-nts nt lang)
|
||
(ormap (λ (nt-line)
|
||
(and (member nt nt-line)
|
||
nt-line))
|
||
(compiled-lang-nt-map lang)))
|
||
|
||
(define (apply-reduction-relation* reductions exp #:cache-all? [cache-all? (current-cache-all?)])
|
||
(let-values ([(results cycle?) (traverse-reduction-graph reductions exp #:cache-all? cache-all?)])
|
||
results))
|
||
|
||
(struct search-success ())
|
||
(struct search-failure (cutoff?))
|
||
|
||
;; traverse-reduction-graph :
|
||
;; reduction-relation term #:goal (-> any boolean?) #:steps number? #:visit (-> any/c void?) -> (or/c search-success? search-failure?)
|
||
;; reduction-relation term #:goal #f #:steps number? #:visit (-> any/c void?) -> (values (listof any/c) boolean?)
|
||
(define (traverse-reduction-graph reductions start #:goal [goal? #f] #:steps [steps +inf.0] #:visit [visit void]
|
||
#:cache-all? [cache-all? (current-cache-all?)])
|
||
(define visited (and cache-all? (make-hash)))
|
||
(let/ec return
|
||
(let ([answers (make-hash)]
|
||
[cycle? #f]
|
||
[cutoff? #f])
|
||
(let loop ([term start]
|
||
;; It would be better to record all visited terms, to avoid traversing
|
||
;; any part of the graph multiple times. Results from
|
||
;; collects/redex/trie-experiment
|
||
;; in commit
|
||
;; 152084d5ce6ef49df3ec25c18e40069950146041
|
||
;; suggest that a hash works better than a trie.
|
||
[path (make-immutable-hash '())]
|
||
[more-steps steps])
|
||
(if (and goal? (goal? term))
|
||
(return (search-success))
|
||
(cond
|
||
[(hash-ref path term #f)
|
||
(set! cycle? #t)]
|
||
[else
|
||
(visit term)
|
||
(let ([nexts (apply-reduction-relation reductions term)])
|
||
(cond
|
||
[(null? nexts)
|
||
(unless goal?
|
||
(hash-set! answers term #t))]
|
||
[else (if (zero? more-steps)
|
||
(set! cutoff? #t)
|
||
(for ([next (in-list (remove-duplicates nexts))])
|
||
(when (or (not visited)
|
||
(not (hash-ref visited next #f)))
|
||
(when visited (hash-set! visited next #t))
|
||
(loop next
|
||
(hash-set path term #t)
|
||
(sub1 more-steps)))))]))])))
|
||
(if goal?
|
||
(search-failure cutoff?)
|
||
(values (sort (hash-map answers (λ (x y) x))
|
||
string<=?
|
||
#:key (λ (x) (format "~s" x)))
|
||
cycle?)))))
|
||
|
||
(define current-cache-all? (make-parameter #f))
|
||
|
||
;; map/mt : (a -> b) (listof a) (listof b) -> (listof b)
|
||
;; map/mt is like map, except
|
||
;; a) it uses the last argument instead of the empty list
|
||
;; b) if `f' returns #f, that is not included in the result
|
||
(define (map/mt f l mt-l)
|
||
(let loop ([l l])
|
||
(cond
|
||
[(null? l) mt-l]
|
||
[else
|
||
(let ([this-one (f (car l))])
|
||
(if this-one
|
||
(cons this-one (loop (cdr l)))
|
||
(loop (cdr l))))])))
|
||
|
||
(define (reduction-relation->rule-names x)
|
||
(reverse (reduction-relation-rule-names x)))
|
||
|
||
|
||
;
|
||
;
|
||
;
|
||
; ; ; ;; ;
|
||
; ;; ;; ;; ;;
|
||
; ;;;;; ;;; ;;;;; ;;;;; ;;;;; ;;;; ;;;; ;;;;; ;;; ;;;;;
|
||
; ;;;;;; ;;;;; ;;;;;; ;;;;;; ;;;;;; ;;;; ;;;; ;;;; ;;;;;; ;;;;; ;;;;;;
|
||
; ;;;; ;;;; ;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;; ;;;;
|
||
; ;;;; ;;;;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;; ;;;;;;; ;;;;
|
||
; ;;;;; ;;;;; ;;;; ;;;;; ;;;; ;;;; ;;;; ;;;; ;;;;; ;;;;; ;;;;
|
||
; ;;;;; ;;;;;; ;;;;;; ;;;;; ;;;;;; ;;;;;;;;; ;;;; ;;;;; ;;;;;; ;;;;;;
|
||
; ;;;; ;;;; ;;;;; ;;;; ;;;;; ;;; ;;;; ;;;; ;;;; ;;;; ;;;;;
|
||
;
|
||
;
|
||
;
|
||
|
||
(define tests 0)
|
||
(define test-failures 0)
|
||
(define (inc-failures) (set! test-failures (+ test-failures 1)))
|
||
(define (inc-tests) (set! tests (+ tests 1)))
|
||
|
||
(define (test-results)
|
||
(cond
|
||
[(= tests 0)
|
||
(printf "No tests run.\n")]
|
||
[(= test-failures 0)
|
||
(cond
|
||
[(= tests 1)
|
||
(printf "One test passed.\n")]
|
||
[(= tests 2)
|
||
(printf "Both tests passed.\n")]
|
||
[else
|
||
(printf "All ~a tests passed.\n" tests)])]
|
||
[else
|
||
(printf "~a test~a failed (out of ~a total).\n"
|
||
test-failures
|
||
(if (= test-failures 1) "" "s")
|
||
tests)])
|
||
(set! tests 0)
|
||
(set! test-failures 0))
|
||
|
||
(define-for-syntax (get-srcloc stx)
|
||
#`(list
|
||
'#,(syntax-source stx)
|
||
'#,(syntax-line stx)
|
||
'#,(syntax-column stx)
|
||
'#,(syntax-position stx)))
|
||
|
||
(define-for-syntax test-equiv-ctc
|
||
#'(-> any/c any/c any/c))
|
||
(define-for-syntax test-equiv-name
|
||
"#:equiv argument")
|
||
(define-for-syntax test-equiv-default
|
||
#'equal?)
|
||
|
||
(define-syntax (test-->> stx)
|
||
(syntax-parse stx
|
||
[(form red:expr
|
||
(~or (~optional (~seq (~and #:cycles-ok (~bind [cycles-ok? #t])))
|
||
#:defaults ([cycles-ok? #f])
|
||
#:name "#:cycles-ok argument")
|
||
(~optional (~seq #:equiv equiv?)
|
||
#:defaults ([equiv?.c test-equiv-default])
|
||
#:name test-equiv-name)
|
||
(~optional (~seq #:pred pred)
|
||
#:defaults ([pred #f])
|
||
#:name "#:pred argument"))
|
||
...
|
||
e1:expr
|
||
e2:expr ...)
|
||
#:declare equiv? (expr/c test-equiv-ctc #:name test-equiv-name)
|
||
#`(test-->>/procs 'test-->> red e1 (list e2 ...) traverse-reduction-graph #,(attribute cycles-ok?) equiv?.c #,(attribute pred) #,(get-srcloc stx))]))
|
||
|
||
(define-syntax (test--> stx)
|
||
(syntax-parse stx
|
||
[(form red:expr
|
||
(~optional (~seq #:equiv equiv?)
|
||
#:defaults ([equiv?.c test-equiv-default]))
|
||
e1:expr
|
||
e2:expr ...)
|
||
#:declare equiv? (expr/c test-equiv-ctc #:name test-equiv-name)
|
||
#`(test-->>/procs 'test--> red e1 (list e2 ...) apply-reduction-relation/dummy-second-value #t equiv?.c #f #,(get-srcloc stx))]))
|
||
|
||
(define (apply-reduction-relation/dummy-second-value red arg #:visit visit)
|
||
(values (apply-reduction-relation red arg) #f))
|
||
|
||
(define (test-->>/procs name red arg expected apply-red cycles-ok? equiv? pred srcinfo)
|
||
(unless (reduction-relation? red)
|
||
(error name "expected a reduction relation as first argument, got ~e" red))
|
||
(when pred
|
||
(unless (and (procedure? pred)
|
||
(procedure-arity-includes? pred 1))
|
||
(error 'test-->> "expected a procedure that accepted one argument for the #:pred, got ~e" pred)))
|
||
(inc-tests)
|
||
(define visit-already-failed? #f)
|
||
(define (visit t)
|
||
(when pred
|
||
(unless visit-already-failed?
|
||
(unless (pred t)
|
||
(set! visit-already-failed? #t)
|
||
(inc-failures)
|
||
(print-failed srcinfo)
|
||
(fprintf (current-error-port) "found a term that failed #:pred: ~v\n" t)))))
|
||
(let-values ([(got got-cycle?) (apply-red red arg #:visit visit)])
|
||
|
||
(cond
|
||
[(and got-cycle?
|
||
(not cycles-ok?))
|
||
(inc-failures)
|
||
(print-failed srcinfo)
|
||
(fprintf (current-error-port) "found a cycle in the reduction graph\n")]
|
||
[else
|
||
(unless visit-already-failed?
|
||
(let* ([⊆ (λ (s1 s2) (andmap (λ (x1) (memf (λ (x) (equiv? x1 x)) s2)) s1))]
|
||
[set-equal? (λ (s1 s2) (and (⊆ s1 s2) (⊆ s2 s1)))])
|
||
(unless (set-equal? expected got)
|
||
(inc-failures)
|
||
(print-failed srcinfo)
|
||
(for-each
|
||
(λ (v2) (fprintf (current-error-port) "expected: ~v\n" v2))
|
||
expected)
|
||
(if (empty? got)
|
||
(fprintf (current-error-port) "got nothing\n")
|
||
(for-each
|
||
(λ (v1) (fprintf (current-error-port) " actual: ~v\n" v1))
|
||
got)))))])))
|
||
|
||
(define-syntax (test-->>∃ stx)
|
||
(syntax-parse stx
|
||
[(form (~optional (~seq #:steps steps) #:defaults ([steps.c #'1000]))
|
||
relation
|
||
start:expr
|
||
goal)
|
||
#:declare relation (expr/c #'reduction-relation?
|
||
#:name "reduction relation expression")
|
||
#:declare goal (expr/c #'(or/c (-> any/c any/c) (not/c procedure?))
|
||
#:name "goal expression")
|
||
#:declare steps (expr/c #'(or/c natural-number/c +inf.0)
|
||
#:name "steps expression")
|
||
#`(test-->>∃/proc relation.c start goal.c steps.c #,(get-srcloc stx))]))
|
||
|
||
(define (test-->>∃/proc relation start goal steps srcinfo)
|
||
(let ([result (traverse-reduction-graph
|
||
relation
|
||
start
|
||
#:goal (if (procedure? goal) goal (λ (x) (equal? goal x)))
|
||
#:steps steps)])
|
||
(inc-tests)
|
||
(when (search-failure? result)
|
||
(print-failed srcinfo)
|
||
(inc-failures)
|
||
(begin
|
||
(if (procedure? goal)
|
||
(fprintf (current-error-port)
|
||
"no term satisfying ~a reachable from ~a" goal start)
|
||
(fprintf (current-error-port)
|
||
"term ~a not reachable from ~a" goal start))
|
||
(when (search-failure-cutoff? result)
|
||
(fprintf (current-error-port) " (within ~a steps)" steps))
|
||
(newline (current-error-port))))))
|
||
|
||
(define-syntax (test-predicate stx)
|
||
(syntax-case stx ()
|
||
[(_ p arg)
|
||
#`(test-predicate/proc p arg #,(get-srcloc stx))]))
|
||
|
||
(define (test-predicate/proc pred arg srcinfo)
|
||
(inc-tests)
|
||
(unless (pred arg)
|
||
(inc-failures)
|
||
(print-failed srcinfo)
|
||
(fprintf (current-error-port) " ~v does not hold for\n ~v\n"
|
||
pred arg)))
|
||
|
||
(define-syntax (test-equal stx)
|
||
(syntax-case stx ()
|
||
[(_ e1 e2)
|
||
#`(test-equal/proc e1 e2 #,(get-srcloc stx))]))
|
||
|
||
(define (test-equal/proc v1 v2 srcinfo)
|
||
(inc-tests)
|
||
(unless (equal? v1 v2)
|
||
(inc-failures)
|
||
(print-failed srcinfo)
|
||
(fprintf (current-error-port) " actual: ~v\n" v1)
|
||
(fprintf (current-error-port) "expected: ~v\n" v2)))
|
||
|
||
(define (print-failed srcinfo)
|
||
(let ([file (list-ref srcinfo 0)]
|
||
[line (list-ref srcinfo 1)]
|
||
[column (list-ref srcinfo 2)]
|
||
[pos (list-ref srcinfo 3)])
|
||
(fprintf (current-error-port)
|
||
"FAILED ~a~a\n"
|
||
(cond
|
||
[(path? file)
|
||
(let-values ([(base name dir) (split-path file)])
|
||
(path->string name))]
|
||
[else ""])
|
||
(cond
|
||
[(and line column)
|
||
(format ":~a.~a" line column)]
|
||
[pos
|
||
(format "::~a" pos)]
|
||
[else #f]))))
|
||
|
||
(provide (rename-out [-reduction-relation reduction-relation])
|
||
--> fresh with ::= I O ;; macro keywords
|
||
reduction-relation->rule-names
|
||
extend-reduction-relation
|
||
reduction-relation?
|
||
union-reduction-relations
|
||
|
||
compatible-closure
|
||
context-closure
|
||
|
||
define-language
|
||
define-extended-language
|
||
|
||
define-metafunction
|
||
define-metafunction/extension
|
||
define-relation
|
||
define-judgment-form
|
||
judgment-holds
|
||
|
||
(rename-out [metafunction-form metafunction])
|
||
metafunction? metafunction-proc
|
||
in-domain?
|
||
current-traced-metafunctions
|
||
metafunc-proc-lang
|
||
metafunc-proc-pict-info
|
||
metafunc-proc-name
|
||
metafunc-proc-multi-arg?
|
||
metafunc-proc-in-dom?
|
||
metafunc-proc-dom-pat
|
||
metafunc-proc-cases
|
||
metafunc-proc-relation?
|
||
metafunc-proc?
|
||
(struct-out metafunc-case)
|
||
|
||
(struct-out metafunc-extra-side-cond)
|
||
(struct-out metafunc-extra-where)
|
||
(struct-out metafunc-extra-fresh)
|
||
|
||
(struct-out binds))
|
||
|
||
(provide test-match
|
||
term-match
|
||
term-match/single
|
||
redex-let
|
||
redex-let*
|
||
make-bindings bindings-table bindings?
|
||
match? match-bindings
|
||
make-bind bind? bind-name bind-exp
|
||
make-match
|
||
|
||
test-equal
|
||
test-->>
|
||
test-->
|
||
test-->>∃ (rename-out [test-->>∃ test-->>E])
|
||
test-predicate
|
||
test-results)
|
||
|
||
|
||
(provide language-nts
|
||
apply-reduction-relation
|
||
apply-reduction-relation/tag-with-names
|
||
apply-reduction-relation/tagged
|
||
apply-reduction-relation*
|
||
current-cache-all?
|
||
variable-not-in
|
||
variables-not-in)
|
||
|
||
(provide relation-coverage
|
||
covered-cases
|
||
(rename-out [fresh-coverage make-coverage])
|
||
coverage?)
|