
This adds the option to generate random terms that satisfy judgment-forms and metafunctions. Currently functionality does not include: - patterns/terms using: ellipses, in-hole/hole and relatives, side-conditions, unquotes - define-relation - redex-check integration
411 lines
16 KiB
Racket
411 lines
16 KiB
Racket
#lang racket/base
|
|
|
|
(require (for-syntax racket/base
|
|
"term-fn.rkt"
|
|
syntax/boundmap
|
|
syntax/parse
|
|
racket/syntax
|
|
(only-in racket/list flatten)
|
|
"keyword-macros.rkt"
|
|
"matcher.rkt")
|
|
syntax/datum
|
|
"error.rkt"
|
|
"matcher.rkt")
|
|
|
|
(provide term term-let define-term
|
|
hole in-hole
|
|
term-let/error-name term-let-fn term-define-fn
|
|
term/nts
|
|
(for-syntax term-rewrite
|
|
term-temp->pat
|
|
currently-expanding-term-fn))
|
|
|
|
(define-syntax (hole stx) (raise-syntax-error 'hole "used outside of term"))
|
|
(define-syntax (in-hole stx) (raise-syntax-error 'in-hole "used outside of term"))
|
|
|
|
(define (with-syntax* stx)
|
|
(syntax-case stx ()
|
|
[(_ () e) (syntax e)]
|
|
[(_ (a b ...) e) (syntax (with-syntax (a) (with-syntax* (b ...) e)))]))
|
|
|
|
(define-for-syntax lang-keyword
|
|
(list '#:lang #f))
|
|
|
|
(define-syntax (term stx)
|
|
(syntax-case stx ()
|
|
[(term t . kw-args)
|
|
(with-syntax ([(lang-stx) (parse-kw-args (list lang-keyword)
|
|
(syntax kw-args)
|
|
stx
|
|
(syntax-e #'form))])
|
|
(if (syntax->datum #'lang-stx)
|
|
(let ([lang-nts (language-id-nts #'lang-stx 'term)])
|
|
#`(term/nts t #,lang-nts))
|
|
#'(term/nts t #f)))]))
|
|
|
|
(define-syntax (term/nts stx)
|
|
(syntax-case stx ()
|
|
[(_ arg nts)
|
|
#'(#%expression (term/private arg nts))]))
|
|
|
|
(define-syntax (term/private stx)
|
|
(syntax-case stx ()
|
|
[(_ arg-stx nts-stx)
|
|
(with-disappeared-uses
|
|
(let-values ([(t a-mfs) (term-rewrite/private #'arg-stx #'nts-stx #f)])
|
|
(term-temp->unexpanded-term t a-mfs)))]))
|
|
|
|
(define-for-syntax (term-rewrite t names)
|
|
(let*-values ([(t-t a-mfs) (term-rewrite/private t #`#f names)]
|
|
[(t-pat) (term-temp->pat t-t names)])
|
|
t-pat))
|
|
|
|
(define-syntax (mf-apply stx)
|
|
(syntax-case stx ()
|
|
[(_ mf)
|
|
#'(λ (x) (mf x))]))
|
|
|
|
(define-syntax (mf-map stx)
|
|
(syntax-case stx ()
|
|
[(_ inner-apps)
|
|
#'(λ (l) (map inner-apps l))]))
|
|
|
|
(define-for-syntax currently-expanding-term-fn (make-parameter #f))
|
|
|
|
|
|
;; term-rewrite/private produces expressions from the following grammar:
|
|
;; (which get further processed by term-temp->unexpanded-term or term-temp->pat)
|
|
;;
|
|
;; term-template := `(term-template (,term-binding ...) ,term-datum)
|
|
;; term-binding := `(,t-bind-pat (,mf-apps ,term-datum))
|
|
;; t-bind-pat := id | (ref id) | `(,t-b-seq ...)
|
|
;; t-b-seq := t-bind-pat | ellipsis
|
|
;; mf-apps := `(mf-map ,mf-apps) | `(mf-app ,metafunction-id)
|
|
;; term-datum := `(quasidatum ,d)
|
|
;; d := literal | pattern-variable | `(,d-seq ...) | ;; other (holes, undatum)
|
|
;; d-seq := d | ellipsis
|
|
|
|
;; actually can be attached to anything that matches a variable in the language
|
|
;; is removed by the internal term rewriter
|
|
;; and expands into an error
|
|
;; *bound* things will be caught by the other rewrite/max-depth possibilities
|
|
|
|
(define-for-syntax (term-rewrite/private arg-stx nts-stx names)
|
|
|
|
(define lang-nts (syntax->datum nts-stx))
|
|
(define outer-bindings '())
|
|
(define applied-metafunctions
|
|
(make-free-identifier-mapping))
|
|
|
|
(define (rewrite stx)
|
|
(let-values ([(rewritten _) (rewrite/max-depth stx 0)])
|
|
rewritten))
|
|
|
|
(define (rewrite-application fn args depth)
|
|
(let-values ([(rewritten max-depth) (rewrite/max-depth args depth)])
|
|
(let ([result-id (car (generate-temporaries '(f-results)))])
|
|
(with-syntax ([fn fn])
|
|
(let loop ([func (syntax (mf-apply fn))]
|
|
[args-stx rewritten]
|
|
[res result-id]
|
|
[args-depth (min depth max-depth)])
|
|
(with-syntax ([func func]
|
|
[args args-stx]
|
|
[res res])
|
|
(if (zero? args-depth)
|
|
(begin
|
|
(set! outer-bindings
|
|
(cons (syntax [res (func (quasidatum args))])
|
|
outer-bindings))
|
|
(values result-id (min depth max-depth)))
|
|
(loop (syntax (mf-map func))
|
|
(syntax/loc args-stx (args (... ...)))
|
|
(syntax (res (... ...)))
|
|
(sub1 args-depth)))))))))
|
|
|
|
(define (rewrite/max-depth stx depth)
|
|
(syntax-case stx (unquote unquote-splicing in-hole hole)
|
|
[(metafunc-name arg ...)
|
|
(and (identifier? (syntax metafunc-name))
|
|
(if names
|
|
(not (memq (syntax->datum #'metafunc-name) names))
|
|
#t)
|
|
(term-fn? (syntax-local-value (syntax metafunc-name) (λ () #f))))
|
|
(let ([f (term-fn-get-id (syntax-local-value/record (syntax metafunc-name) (λ (x) #t)))])
|
|
(free-identifier-mapping-put! applied-metafunctions f #t)
|
|
(rewrite-application f (syntax/loc stx (arg ...)) depth))]
|
|
[f
|
|
(and (identifier? (syntax f))
|
|
(if names
|
|
(not (memq (syntax->datum #'f) names))
|
|
#t)
|
|
(term-fn? (syntax-local-value (syntax f) (λ () #f))))
|
|
(raise-syntax-error 'term "metafunction must be in an application" arg-stx stx)]
|
|
[x
|
|
(and (identifier? (syntax x))
|
|
(term-id? (syntax-local-value (syntax x) (λ () #f))))
|
|
(let ([id (syntax-local-value/record (syntax x) (λ (x) #t))])
|
|
(check-id (syntax->datum (term-id-id id)) stx)
|
|
(values (datum->syntax (term-id-id id) (syntax-e (term-id-id id)) (syntax x))
|
|
(term-id-depth id)))]
|
|
[x
|
|
(defined-term-id? #'x)
|
|
(let ([ref (syntax-property
|
|
(defined-term-value (syntax-local-value #'x))
|
|
'disappeared-use
|
|
(syntax-local-introduce #'x))])
|
|
(check-id (syntax->datum #'x) stx)
|
|
(with-syntax ([v #`(begin
|
|
#,(defined-check ref "term" #:external #'x)
|
|
#,ref)])
|
|
(values #`(undatum v) 0)))]
|
|
[(unquote x)
|
|
(values (syntax (undatum x)) 0)]
|
|
[(unquote . x)
|
|
(raise-syntax-error 'term "malformed unquote" arg-stx stx)]
|
|
[(unquote-splicing x)
|
|
(values (syntax (undatum-splicing x)) 0)]
|
|
[(unquote-splicing . x)
|
|
(raise-syntax-error 'term "malformed unquote splicing" arg-stx stx)]
|
|
[(in-hole id body)
|
|
(rewrite-application (syntax (λ (x) (apply plug x))) (syntax/loc stx (id body)) depth)]
|
|
[(in-hole . x)
|
|
(raise-syntax-error 'term "malformed in-hole" arg-stx stx)]
|
|
[hole (values (syntax (undatum the-hole)) 0)]
|
|
[x
|
|
(and (identifier? (syntax x))
|
|
(check-id (syntax->datum #'x) stx))
|
|
(values stx 0)]
|
|
[() (values stx 0)]
|
|
[(x ... . y)
|
|
(not (null? (syntax->list #'(x ...))))
|
|
(let-values ([(x-rewrite max-depth)
|
|
(let i-loop ([xs (syntax->list (syntax (x ...)))])
|
|
(cond
|
|
[(null? xs) (rewrite/max-depth #'y depth)]
|
|
[else
|
|
(let ([new-depth (if (and (not (null? (cdr xs)))
|
|
(identifier? (cadr xs))
|
|
(free-identifier=? (quote-syntax ...)
|
|
(cadr xs)))
|
|
(+ depth 1)
|
|
depth)])
|
|
(let-values ([(fst fst-max-depth)
|
|
(rewrite/max-depth (car xs) new-depth)]
|
|
[(rst rst-max-depth)
|
|
(i-loop (cdr xs))])
|
|
(values (cons fst rst)
|
|
(max fst-max-depth rst-max-depth))))]))])
|
|
(values (datum->syntax stx x-rewrite stx) max-depth))]
|
|
|
|
[_ (values stx 0)]))
|
|
|
|
(define (check-id id stx)
|
|
(when lang-nts
|
|
(define m (regexp-match #rx"^([^_]*)_" (symbol->string id)))
|
|
(when m
|
|
(unless (memq (string->symbol (list-ref m 1)) (append pattern-symbols lang-nts))
|
|
(raise-syntax-error 'term "before underscore must be either a non-terminal or a built-in pattern" arg-stx stx)))))
|
|
|
|
(values
|
|
(with-syntax ([rewritten (rewrite arg-stx)])
|
|
(with-syntax ([(outer-bs ...) (reverse outer-bindings)])
|
|
#'(term-template
|
|
(outer-bs ...)
|
|
(quasidatum rewritten))))
|
|
applied-metafunctions))
|
|
|
|
(define-for-syntax (term-temp->unexpanded-term term-stx applied-mfs)
|
|
(syntax-case term-stx (term-template)
|
|
[(term-template (outer-bs ...) t)
|
|
(let ([outer-bindings (syntax->list #'(outer-bs ...))])
|
|
#`(begin
|
|
#,@(free-identifier-mapping-map
|
|
applied-mfs
|
|
(λ (f _) (defined-check f "metafunction")))
|
|
#,(let loop ([bs outer-bindings])
|
|
(cond
|
|
[(null? bs) (syntax t)]
|
|
[else (with-syntax ([rec (loop (cdr bs))]
|
|
[fst (car bs)])
|
|
(syntax (with-datum (fst)
|
|
rec)))]))))]))
|
|
|
|
(define-for-syntax (term-temp->pat t-t names)
|
|
(syntax-case t-t (term-template)
|
|
[(term-template (term-bindings ...) body-datum)
|
|
(let loop ([t-bs-raw (syntax->list #'(term-bindings ...))]
|
|
[t-bs '()]
|
|
[ns names])
|
|
(cond
|
|
[(null? t-bs-raw)
|
|
(with-syntax ([body-pat (term-datum->pat #'body-datum ns)]
|
|
[(bind-pats ...) (reverse t-bs)])
|
|
#'(term-pattern (bind-pats ...) body-pat))]
|
|
[else
|
|
(with-syntax ([(bind-lhs (bind-mf-sig bind-term-datum)) (car t-bs-raw)])
|
|
(let ([new-names (append ns (bind-lhs-name #'bind-lhs))])
|
|
(with-syntax ([bind-rhs-pat (term-datum->pat #'bind-term-datum new-names)]
|
|
[bind-lhs-pat (d->pat #'bind-lhs new-names)]
|
|
[bind-mf-pat (bind-mf-sig->pat #'bind-mf-sig)])
|
|
(loop (cdr t-bs-raw)
|
|
(cons #'(bind-lhs-pat (bind-mf-pat bind-rhs-pat)) t-bs)
|
|
new-names))))]))]))
|
|
|
|
(define-for-syntax (term-datum->pat t-d names)
|
|
(syntax-case t-d ()
|
|
[(quasidatum d)
|
|
(d->pat #'d names)]))
|
|
|
|
(define-for-syntax (d->pat d names)
|
|
(syntax-case d (... undatum in-hole undatum-splicing)
|
|
[()
|
|
#'(list)]
|
|
[(undatum rest ...) ;; holes are also undatumed
|
|
d]
|
|
[(undatum-splicing rest ...)
|
|
d]
|
|
[(in-hole rest ...)
|
|
d]
|
|
[(r-dat (... ...) rest ...)
|
|
(with-syntax ([r-pat (d->pat #'r-dat names)]
|
|
[(list rest-pats ...) (d->pat #'(rest ...) names)])
|
|
#'(list (repeat r-pat #f #f) rest-pats ...))]
|
|
[(d ds ...)
|
|
(with-syntax ([p (d->pat #'d names)]
|
|
[(list ps ...) (d->pat #'(ds ...) names)])
|
|
#'(list p ps ...))]
|
|
[var
|
|
(and (identifier? #'var)
|
|
(memq (syntax->datum #'var) names))
|
|
#'(name var any)]
|
|
[literal
|
|
#'literal]))
|
|
|
|
(define-for-syntax (bind-lhs-name blhs)
|
|
(define name (filter (λ (n) (not (eq? n '...)))
|
|
(flatten (syntax->datum blhs))))
|
|
(unless (equal? (length name) 1)
|
|
(error 'term-rewrite "term function lhs binding had more than one name: ~s" (syntax->datum blhs)))
|
|
name)
|
|
|
|
(define-for-syntax (bind-mf-sig->pat bmfs)
|
|
(syntax-case bmfs ()
|
|
;; TODO : handle apps at ellipsis depth
|
|
[(mf-apply f)
|
|
(and (identifier? #'mf-apply)
|
|
(eq? (syntax-e #'mf-apply) 'mf-apply))
|
|
#'(metafunc f)]))
|
|
|
|
(define-syntax (term-let-fn stx)
|
|
(syntax-case stx ()
|
|
[(_ ([f . rhs-stuff] ...) body1 body2 ...)
|
|
(with-syntax ([(g ...) (generate-temporaries (syntax (f ...)))]
|
|
[((rhs info) ...)
|
|
(for/list ([rhs-stuff (in-list (syntax->list #'(rhs-stuff ...)))]
|
|
[f (in-list (syntax->list #'(f ...)))])
|
|
(syntax-case rhs-stuff ()
|
|
[(rhs) #'(rhs #f)]
|
|
[(rhs info) #'(rhs info)]
|
|
[else (raise-syntax-error 'term-let-fn
|
|
(format "expected the rhs of a binding for ~a"
|
|
(syntax->datum f))
|
|
stx f)]))])
|
|
(syntax
|
|
(let ([g rhs] ...)
|
|
(let-syntax ([f (make-term-fn #'g info)] ...)
|
|
body1
|
|
body2 ...))))]))
|
|
|
|
(define-syntax (term-define-fn stx)
|
|
(syntax-case stx ()
|
|
[(_ id exp info)
|
|
;; this info is currently used to record the
|
|
;; difference between define-metafunctions
|
|
;; bound identifiers and define-relation bound
|
|
;; ones for use in the unification-based generator
|
|
(with-syntax ([id2 (datum->syntax #'here (syntax-e #'id))])
|
|
(syntax
|
|
(begin
|
|
(define id2 exp)
|
|
(define-syntax id
|
|
(make-term-fn #'id2 info)))))]
|
|
[(_ id exp)
|
|
#'(term-define-fn id exp #f)]))
|
|
|
|
(define-syntax (term-let/error-name stx)
|
|
(syntax-case stx ()
|
|
[(_ error-name ([x1 rhs1] [x rhs] ...) body1 body2 ...)
|
|
(let-values ([(orig-names new-names depths new-x1)
|
|
(let loop ([stx #'x1] [depth 0])
|
|
(define ((combine orig-names new-names depths new-pat)
|
|
orig-names* new-names* depths* new-pat*)
|
|
(values (append orig-names orig-names*)
|
|
(append new-names new-names*)
|
|
(append depths depths*)
|
|
(cons new-pat new-pat*)))
|
|
(syntax-case stx (...)
|
|
[x
|
|
(and (identifier? #'x)
|
|
(not (free-identifier=? (quote-syntax ...) #'x)))
|
|
(let ([new-name (datum->syntax #'here (syntax-e #'x))])
|
|
(values (list #'x)
|
|
(list new-name)
|
|
(list depth)
|
|
new-name))]
|
|
[(x (... ...) . xs)
|
|
(let-values ([(orig-names new-names depths new-pat)
|
|
(call-with-values
|
|
(λ () (loop #'xs depth))
|
|
(call-with-values
|
|
(λ () (loop #'x (add1 depth)))
|
|
combine))])
|
|
(values orig-names new-names depths
|
|
(list* (car new-pat) #'(... ...) (cdr new-pat))))]
|
|
[(x . xs)
|
|
(call-with-values
|
|
(λ () (loop #'xs depth))
|
|
(call-with-values
|
|
(λ () (loop #'x depth))
|
|
combine))]
|
|
[_
|
|
(values '() '() '() stx)]))])
|
|
(with-syntax ([(orig-names ...) orig-names]
|
|
[(new-names ...) new-names]
|
|
[(depths ...) depths]
|
|
[new-x1 new-x1]
|
|
[no-match (syntax/loc (syntax rhs1)
|
|
(error 'error-name "term ~s does not match pattern ~s" rhs1 'x1))])
|
|
(syntax
|
|
(datum-case rhs1 ()
|
|
[new-x1
|
|
;; syntax local value on an id to check if it's bound correctly in
|
|
;; a term
|
|
;; term (term #:lang L (x_1 y_2)) term -> optional argument with lang
|
|
(let-syntax ([orig-names (make-term-id #'new-names depths)] ...)
|
|
(term-let/error-name error-name ((x rhs) ...) body1 body2 ...))]
|
|
[_ no-match]))))]
|
|
[(_ error-name () body1 body2 ...)
|
|
(syntax
|
|
(begin body1 body2 ...))]
|
|
[(_ x)
|
|
(raise-syntax-error 'term-let "expected at least one body" stx)]))
|
|
|
|
(define-syntax (term-let stx)
|
|
(syntax-case stx ()
|
|
[(_ () body1)
|
|
#'body1]
|
|
[(_ ([x rhs] ...) body1 body2 ...)
|
|
(syntax
|
|
(term-let/error-name term-let ((x rhs) ...) body1 body2 ...))]
|
|
[(_ x)
|
|
(raise-syntax-error 'term-let "expected at least one body" stx)]))
|
|
|
|
(define-syntax (define-term stx)
|
|
(syntax-parse stx
|
|
[(_ x:id t:expr)
|
|
(not-expression-context stx)
|
|
#'(begin
|
|
(define term-val (term t))
|
|
(define-syntax x (defined-term #'term-val)))]))
|