racket/collects/redex/private/reduction-semantics.rkt

2875 lines
135 KiB
Racket
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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?)