Generator support for `cross' pattern and syntax errors for non-terminals

with no productions.

svn: r11588
This commit is contained in:
Casey Klein 2008-09-09 15:16:45 +00:00
parent 3ce9fd1953
commit 823338e262
6 changed files with 228 additions and 146 deletions

View File

@ -516,24 +516,24 @@
(make-rhs '(+ exp ctxt) '())
(make-rhs 'hole '())))))
(list
(make-nt 'exp-exp
(list (make-rhs 'hole '())
(make-rhs `(+ (cross exp-exp) exp) '())
(make-rhs `(+ exp (cross exp-exp)) '())))
(make-nt 'exp-ctxt
(list (make-rhs `(+ (cross exp-ctxt) exp) '())
(make-rhs `(+ ctxt (cross exp-exp)) '())
(make-rhs `(+ (cross exp-exp) ctxt) '())
(make-rhs `(+ exp (cross exp-ctxt)) '())))
(make-nt 'ctxt-exp
(list (make-rhs `(+ (cross ctxt-exp) exp) '())
(make-rhs `(+ exp (cross ctxt-exp)) '())))
(make-nt 'ctxt-ctxt
(list (make-rhs 'hole '())
(make-rhs `(+ (cross ctxt-ctxt) exp) '())
(make-rhs `(+ ctxt (cross ctxt-exp)) '())
(make-rhs `(+ (cross ctxt-exp) ctxt) '())
(make-rhs `(+ exp (cross ctxt-ctxt)) '())))))
(make-rhs `(+ exp (cross ctxt-ctxt)) '())))
(make-nt 'ctxt-exp
(list (make-rhs `(+ (cross ctxt-exp) exp) '())
(make-rhs `(+ exp (cross ctxt-exp)) '())))
(make-nt 'exp-ctxt
(list (make-rhs `(+ (cross exp-ctxt) exp) '())
(make-rhs `(+ ctxt (cross exp-exp)) '())
(make-rhs `(+ (cross exp-exp) ctxt) '())
(make-rhs `(+ exp (cross exp-ctxt)) '())))
(make-nt 'exp-exp
(list (make-rhs 'hole '())
(make-rhs `(+ (cross exp-exp) exp) '())
(make-rhs `(+ exp (cross exp-exp)) '())))))
(run-test
'compatible-context-language2
@ -542,15 +542,7 @@
(list (make-nt 'm (list (make-rhs '(m m) '()) (make-rhs '(+ m m) '()) (make-rhs 'v '())))
(make-nt 'v (list (make-rhs 'number '()) (make-rhs '(lambda (x) m) '())))))
(list
(make-nt 'm-m
(list
(make-rhs 'hole '())
(make-rhs (list (list 'cross 'm-m) 'm) '())
(make-rhs (list 'm (list 'cross 'm-m)) '())
(make-rhs (list '+ (list 'cross 'm-m) 'm) '())
(make-rhs (list '+ 'm (list 'cross 'm-m)) '())
(make-rhs (list 'cross 'm-v) '())))
(make-nt 'm-v (list (make-rhs (list 'lambda (list 'x) (list 'cross 'm-m)) '())))
(make-nt 'v-v (list (make-rhs 'hole '()) (make-rhs (list 'lambda (list 'x) (list 'cross 'v-m)) '())))
(make-nt 'v-m
(list
(make-rhs (list (list 'cross 'v-m) 'm) '())
@ -558,7 +550,15 @@
(make-rhs (list '+ (list 'cross 'v-m) 'm) '())
(make-rhs (list '+ 'm (list 'cross 'v-m)) '())
(make-rhs (list 'cross 'v-v) '())))
(make-nt 'v-v (list (make-rhs 'hole '()) (make-rhs (list 'lambda (list 'x) (list 'cross 'v-m)) '())))))
(make-nt 'm-v (list (make-rhs (list 'lambda (list 'x) (list 'cross 'm-m)) '())))
(make-nt 'm-m
(list
(make-rhs 'hole '())
(make-rhs (list (list 'cross 'm-m) 'm) '())
(make-rhs (list 'm (list 'cross 'm-m)) '())
(make-rhs (list '+ (list 'cross 'm-m) 'm) '())
(make-rhs (list '+ 'm (list 'cross 'm-m)) '())
(make-rhs (list 'cross 'm-v) '())))))
(run-test
'compatible-context-language3
@ -568,13 +568,26 @@
(make-nt 'seven (list (make-rhs 7 '())))))
`(,(make-nt
'm-m
`(,(make-rhs 'hole '()) ,(make-rhs `((cross m-m) seven m) '()) ,(make-rhs `(m (cross m-seven) m) '()) ,(make-rhs `(m seven (cross m-m)) '())))
,(make-nt 'm-seven `())
`(,(make-rhs 'hole '()) ,(make-rhs `((cross m-m) seven m) '()) ,(make-rhs `(m seven (cross m-m)) '())))
,(make-nt
'seven-m
`(,(make-rhs `((cross seven-m) seven m) '()) ,(make-rhs `(m (cross seven-seven) m) '()) ,(make-rhs `(m seven (cross seven-m)) '())))
,(make-nt 'seven-seven `(,(make-rhs 'hole '())))))
(run-test
'compatible-context-language4
(build-compatible-context-language
(mk-hasheq '((a . ()) (b . ()) (c . ())))
(list (make-nt 'a (list (make-rhs 'b '())))
(make-nt 'b (list (make-rhs 'c '())))
(make-nt 'c (list (make-rhs 3 '())))))
(list (make-nt 'c-c (list (make-rhs 'hole '())))
(make-nt 'c-b (list (make-rhs '(cross c-c) '())))
(make-nt 'c-a (list (make-rhs '(cross c-b) '())))
(make-nt 'b-b (list (make-rhs 'hole '())))
(make-nt 'b-a (list (make-rhs '(cross b-b) '())))
(make-nt 'a-a (list (make-rhs 'hole '())))))
#;
(test-xab '(in-hole (cross exp) (+ number number))
'(+ (+ 1 2) 3)

View File

@ -147,7 +147,7 @@ before the pattern compiler is invoked.
(when may-be-list? (add-to-ht list-ht))
(unless (or may-be-non-list? may-be-list?)
(error 'compile-language
"unable to determine whether pattern matches lists, non-lists, or both: ~s"
"internal error: unable to determine whether pattern matches lists, non-lists, or both: ~s"
(rhs-pattern rhs))))))
(nt-rhs nt)))
lang))]
@ -283,22 +283,52 @@ before the pattern compiler is invoked.
;; build-compatible-context-language : lang -> lang
(define (build-compatible-context-language clang-ht lang)
(apply
append
(map
(lambda (nt1)
(map
(lambda (nt2)
(let ([compat-nt (build-compatible-contexts/nt clang-ht (nt-name nt1) nt2)])
(if (eq? (nt-name nt1) (nt-name nt2))
(make-nt (nt-name compat-nt)
(cons
(make-rhs 'hole '())
(nt-rhs compat-nt)))
compat-nt)))
lang))
lang)))
(remove-empty-compatible-contexts
(apply
append
(map
(lambda (nt1)
(map
(lambda (nt2)
(let ([compat-nt (build-compatible-contexts/nt clang-ht (nt-name nt1) nt2)])
(if (eq? (nt-name nt1) (nt-name nt2))
(make-nt (nt-name compat-nt)
(cons
(make-rhs 'hole '())
(nt-rhs compat-nt)))
compat-nt)))
lang))
lang))))
;; remove-empty-compatible-contexts : lang -> lang
;; Removes the empty compatible context non-terminals and the
;; rhss that reference them.
(define (remove-empty-compatible-contexts lang)
(define (has-cross? pattern crosses)
(match pattern
[`(cross ,(? symbol? nt)) (memq nt crosses)]
[(list-rest p '... rest) (has-cross? rest crosses)]
[(cons first rest) (or (has-cross? first crosses)
(has-cross? rest crosses))]
[_ #f]))
(define (delete-empty nts)
(for/fold ([deleted null] [kept null]) ([nt nts])
(if (null? (nt-rhs nt))
(values (cons nt deleted) kept)
(values deleted (cons nt kept)))))
(define (delete-references deleted-names remaining-nts)
(map (λ (nt)
(make-nt (nt-name nt)
(filter (λ (rhs) (not (has-cross? (rhs-pattern rhs) deleted-names)))
(nt-rhs nt))))
remaining-nts))
(let loop ([nts lang])
(let-values ([(deleted kept) (delete-empty nts)])
(if (null? deleted)
kept
(loop (delete-references (map nt-name deleted) kept))))))
;; build-compatible-contexts : clang-ht prefix nt -> nt
;; constructs the compatible closure evaluation context from nt.
(define (build-compatible-contexts/nt clang-ht prefix nt)

View File

@ -1128,27 +1128,37 @@
stx
name)]))])))
;; check-rhss-not-empty : syntax (listof syntax) -> void
(define-for-syntax (check-rhss-not-empty def-lang-stx nt-def-stxs)
(for-each
(λ (nt-def-stx)
(when (null? (cdr (syntax-e nt-def-stx)))
(raise-syntax-error 'define-language "non-terminal with no productions" def-lang-stx nt-def-stx)))
nt-def-stxs))
(define-syntax (define-language stx)
(syntax-case stx ()
[(_ name (names rhs ...) ...)
(identifier? (syntax name))
(with-syntax ([((nt-names orig) ...) (pull-out-names 'define-language stx #'(names ...))])
(with-syntax ([(subst-names ...) (generate-temporaries (syntax->list #'(nt-names ...)))])
(syntax/loc stx
(begin
(define-syntax name
(make-set!-transformer
(make-language-id
(case-lambda
[(stx)
(syntax-case stx (set!)
[(set! x e) (raise-syntax-error 'define-language "cannot set! identifier" stx #'e)]
[(x e (... ...)) #'(define-language-name e (... ...))]
[x
(identifier? #'x)
#'define-language-name])])
'(nt-names ...))))
(define define-language-name (language name (names rhs ...) ...))))))]))
(begin
(check-rhss-not-empty stx (cddr (syntax-e stx)))
(with-syntax ([((nt-names orig) ...) (pull-out-names 'define-language stx #'(names ...))])
(with-syntax ([(subst-names ...) (generate-temporaries (syntax->list #'(nt-names ...)))])
(syntax/loc stx
(begin
(define-syntax name
(make-set!-transformer
(make-language-id
(case-lambda
[(stx)
(syntax-case stx (set!)
[(set! x e) (raise-syntax-error 'define-language "cannot set! identifier" stx #'e)]
[(x e (... ...)) #'(define-language-name e (... ...))]
[x
(identifier? #'x)
#'define-language-name])])
'(nt-names ...))))
(define define-language-name (language name (names rhs ...) ...)))))))]))
(define-struct binds (source binds))
@ -1328,6 +1338,7 @@
(raise-syntax-error 'define-extended-langauge "expected an identifier" stx #'name))
(unless (identifier? (syntax orig-lang))
(raise-syntax-error 'define-extended-langauge "expected an identifier" stx #'orig-lang))
(check-rhss-not-empty stx (cdddr (syntax-e stx)))
(let ([old-names (language-id-nts #'orig-lang 'define-extended-language)])
(with-syntax ([((new-nt-names orig) ...) (append (pull-out-names 'define-language stx #'(names ...))
(map (λ (x) #`(#,x #f)) old-names))])

View File

@ -21,20 +21,20 @@
(e x (e e) (λ (x) e))
(x variable))
(test (to-table (find-base-cases lc))
'((e . (1 2 2)) (x . (0)))))
'((e . (1 2 2)) (e-e . (0 2 2 1)) (x . (0)) (x-e . (1 2 2 2 2)) (x-x . (0)))))
(let ()
(define-language lc
(define-language lang
(e (e e)))
(test (to-table (find-base-cases lc))
'((e . (inf)))))
(test (to-table (find-base-cases lang))
'((e . (inf)) (e-e . (0 inf inf)))))
(let ()
(define-language lc
(define-language lang
(a 1 2 3)
(b a (a_1 b_!_1)))
(test (to-table (find-base-cases lc))
'((a . (0 0 0)) (b . (1 2)))))
(test (to-table (find-base-cases lang))
'((a . (0 0 0)) (a-a . (0)) (a-b . (1)) (b . (1 2)) (b-b . (0)))))
(let ()
(define-language lc
@ -46,7 +46,9 @@
number)
(x variable))
(test (to-table (find-base-cases lc))
'((e . (2 2 1 1)) (v . (2 0)) (x . (0)))))
'((e . (2 2 1 1)) (e-e . (0 2 2 2 2 2)) (e-v . (1))
(v . (2 0)) (v-e . (2 2 2 2 1)) (v-v . (0 2))
(x . (0)) (x-e . (2 2 2 2 1 3)) (x-v . (2 2)) (x-x . (0)))))
(let ()
(define-language lang
@ -278,8 +280,8 @@
(let ()
(define-language lang (x variable literal))
(test (is-nt? lang 'x) #t)
(test (is-nt? lang 'y) #f))
(test ((is-nt? lang) 'x) #t)
(test ((is-nt? lang) 'y) #f))
(let ()
(define-language lang
@ -454,6 +456,17 @@
(thunk))
(get-output-string port)))
;; `cross' pattern
(let ()
(define-language lang
(e x (e e) v)
(v (λ (x) e))
(x variable-not-otherwise-mentioned))
(test (generate lang (cross e) 3 0
(decisions #:nt (patterns fourth first first second first first first)
#:var (list (λ _ 'x) (λ _ 'y))))
(term (λ (x) (hole y)))))
(let ()
(define-language lang
(d 5)
@ -500,7 +513,9 @@
_
(list (struct class (c_1)) (struct mismatch (i_1 '..._!_1)) 'x_1)))
(car (parse-pattern pattern)))
(test (unparse-pattern (parse-pattern pattern)) pattern)))
(test (unparse-pattern (parse-pattern pattern)) pattern)
(test (parse-pattern '(cross e)) '(cross e-e))
(test (parse-pattern '(cross e) #t) '(cross e))))
(let ()
(define-syntax test-class-reassignments

View File

@ -137,25 +137,23 @@ To do a better job of not generating programs with free variables,
(define lang-chars (unique-chars lang-lits))
(define base-table (find-base-cases lang))
(define (generate-nt nt fvt-id bound-vars size in-hole state)
(let loop ([nts (compiled-lang-lang lang)])
(cond
[(null? nts) (error 'generate-nt "didn't find non-terminal ~s" nt)]
[(eq? (nt-name (car nts)) nt)
(let*-values
([(rhs)
((next-non-terminal-decision)
(if (zero? size) (min-prods (car nts) base-table) (nt-rhs (car nts)))
bound-vars size)]
[(bound-vars) (append (extract-bound-vars fvt-id state) bound-vars)]
[(nt-state) (make-state (map fvt-entry (rhs-var-info rhs)) #hash())]
[(term _)
(generate/pred
(rhs-pattern rhs)
(λ (pat) (((generate-pat bound-vars (max 0 (sub1 size))) pat in-hole) nt-state))
(λ (_ env) (mismatches-satisfied? env)))])
(values term (extend-found-vars fvt-id term state)))]
[else (loop (cdr nts))])))
(define (generate-nt name fvt-id bound-vars size in-hole state)
(let*-values
([(nt) (findf (λ (nt) (eq? name (nt-name nt)))
(append (compiled-lang-lang lang)
(compiled-lang-cclang lang)))]
[(rhs)
((next-non-terminal-decision)
(if (zero? size) (min-prods nt base-table) (nt-rhs nt))
bound-vars size)]
[(bound-vars) (append (extract-bound-vars fvt-id state) bound-vars)]
[(nt-state) (make-state (map fvt-entry (rhs-var-info rhs)) #hash())]
[(term _)
(generate/pred
(rhs-pattern rhs)
(λ (pat) (((generate-pat bound-vars (max 0 (sub1 size))) pat in-hole) nt-state))
(λ (_ env) (mismatches-satisfied? env)))])
(values term (extend-found-vars fvt-id term state))))
(define (generate-sequence ellipsis generate state length)
(define (split-environment env)
@ -213,9 +211,13 @@ To do a better job of not generating programs with free variables,
(define (fvt-entry binds)
(make-found-vars (binds-binds binds) (binds-source binds) '() #f))
(define (((generate-pat bound-vars size) pat in-hole [fvt-id pat]) state)
(define (((generate-pat bound-vars size) pat in-hole) state)
(define recur (generate-pat bound-vars size))
(define (recur/pat pat) ((recur pat in-hole) state))
(define (generate-nt/built-in undecorated decorated)
(if ((is-nt? lang) undecorated)
(generate-nt undecorated decorated bound-vars size in-hole state)
(recur/pat undecorated)))
(match pat
[`number (values ((next-number-decision) random-numbers) state)]
@ -249,21 +251,22 @@ To do a better job of not generating programs with free variables,
[`any
(let-values ([(lang nt) ((next-any-decision) lang)])
(values (generate* lang nt size attempt decisions@) state))]
[(? (λ (p) (is-nt? lang p)))
(generate-nt pat fvt-id bound-vars size in-hole state)]
[(? (is-nt? lang))
(generate-nt pat pat bound-vars size in-hole state)]
[(and (? symbol?) (app symbol->string (regexp named-nt-rx (list _ nt))))
(let* ([undecorated (string->symbol nt)]
[none (gensym)]
[prior (hash-ref (state-env state) pat none)])
(if (eq? prior none)
(let-values
([(term state) ((recur undecorated in-hole pat) state)])
(let-values ([(term state) (generate-nt/built-in undecorated pat)])
(values term (set-env state pat term)))
(values prior state)))]
[(struct mismatch (name group))
(let ([nt (string->symbol (cadr (regexp-match mismatch-nt-rx (symbol->string group))))])
(let-values ([(term state) ((recur nt in-hole name) state)])
(let ([undecorated (string->symbol (cadr (regexp-match mismatch-nt-rx (symbol->string group))))])
(let-values ([(term state) (generate-nt/built-in undecorated name)])
(values term (set-env state pat term))))]
[`(cross ,(? symbol? cross-nt))
(generate-nt cross-nt #f bound-vars size in-hole state)]
[(or (? symbol?) (? number?) (? string?) (? boolean?) (? null?)) (values pat state)]
[(list-rest (and (struct ellipsis (name sub-pat class vars)) ellipsis) rest)
(let*-values ([(length) (let ([prior (hash-ref (state-env state) class #f)])
@ -323,55 +326,58 @@ To do a better job of not generating programs with free variables,
term))
;; find-base-cases : compiled-language -> hash-table
(define (find-base-cases lang)
(define nt-table (make-hasheq))
(define changed? #f)
(define (nt-get nt) (hash-ref nt-table nt 'inf))
(define (nt-set nt new)
(let ([old (nt-get nt)])
(unless (equal? new old)
(set! changed? #t)
(hash-set! nt-table nt new))))
(define (process-nt nt)
(nt-set (nt-name nt) (apply min/f (map process-rhs (nt-rhs nt)))))
(define (process-rhs rhs)
(let ([nts (rhs->nts (rhs-pattern rhs))])
(if (null? nts)
0
(add1/f (apply max/f (map nt-get nts))))))
;; rhs->path : pattern -> (listof symbol)
;; determines all of the non-terminals in a pattern
(define (rhs->nts pat)
(let ([nts '()])
(let loop ([pat pat])
(match pat
[(? symbol? pat)
(when (is-nt? lang (symbol->nt pat))
(set! nts (cons (symbol->nt pat) nts)))]
[`() (void)]
[`(,a ,'... . ,b)
(loop a)
(loop b)]
[`(,a . ,b)
(loop a)
(loop b)]
[_ (void)]))
nts))
(define (find-base-cases lang)
(define nt-table (make-hasheq))
(define changed? #f)
(define (nt-get nt) (hash-ref nt-table nt 'inf))
(define (nt-set nt new)
(let ([old (nt-get nt)])
(unless (equal? new old)
(set! changed? #t)
(hash-set! nt-table nt new))))
(define (process-nt nt)
(nt-set (nt-name nt) (apply min/f (map process-rhs (nt-rhs nt)))))
(define (process-rhs rhs)
(let ([nts (rhs->nts (rhs-pattern rhs))])
(if (null? nts)
0
(add1/f (apply max/f (map nt-get nts))))))
;; rhs->path : pattern -> (listof symbol)
;; determines all of the non-terminals in a pattern
(define (rhs->nts pat)
(let ([nts '()])
(let loop ([pat pat])
(match pat
[(? symbol? pat)
(when ((is-nt? lang) (symbol->nt pat))
(set! nts (cons (symbol->nt pat) nts)))]
[`(cross ,(? symbol? x-nt))
(set! nts (cons x-nt nts))]
[`() (void)]
[`(,a ,'... . ,b)
(loop a)
(loop b)]
[`(,a . ,b)
(loop a)
(loop b)]
[_ (void)]))
nts))
(let ([nts (append (compiled-lang-lang lang) (compiled-lang-cclang lang))])
(let loop ()
(set! changed? #f)
(for-each process-nt (compiled-lang-lang lang))
(for-each process-nt nts)
(when changed?
(loop)))
(let ([ht (make-hash)])
(for-each
(λ (nt) (hash-set! ht (nt-name nt) (map process-rhs (nt-rhs nt))))
(compiled-lang-lang lang))
ht))
nts)
ht)))
(define min/f
(case-lambda
@ -394,7 +400,7 @@ To do a better job of not generating programs with free variables,
(define (add1/f a) (if (eq? a 'inf) 'inf (+ a 1)))
;; is-nt? : compiled-lang any -> boolean
(define (is-nt? lang x)
(define ((is-nt? lang) x)
(and (hash-ref (compiled-lang-ht lang) x #f) #t))
(define named-nt-rx #rx"^([^_]+)_[^_]*$")
@ -419,7 +425,7 @@ To do a better job of not generating programs with free variables,
;; parse-pattern : pattern -> parsed-pattern
;; Turns "pat ...", "pat ..._id", and "pat ..._!_id" into ellipsis structs
;; and "nt_!_id" into mismatch structs.
(define (parse-pattern pattern)
(define (parse-pattern pattern [cross? #f])
(define (recur pat vars)
(match pat
[(and (? symbol?) (app symbol->string (regexp named-nt-rx)))
@ -450,6 +456,9 @@ To do a better job of not generating programs with free variables,
[(vars) (append (list* class mismatch sub-pat-vars) vars)]
[(rest-parsed vars) (recur rest vars)])
(values (cons seq rest-parsed) vars))]
[(and (? (λ (_) (not cross?))) `(cross ,(and (? symbol?) nt)))
(let ([nt-str (symbol->string nt)])
(values `(cross ,(string->symbol (string-append nt-str "-" nt-str))) vars))]
[(cons first rest)
(let-values ([(first-parsed vars) (recur first vars)])
(let-values ([(rest-parsed vars) (recur rest vars)])
@ -460,16 +469,15 @@ To do a better job of not generating programs with free variables,
;; parse-language: compiled-lang -> compiled-lang
(define (parse-language lang)
(define ((parse-nt cross?) nt)
(make-nt (nt-name nt) (map (parse-rhs cross?) (nt-rhs nt))))
(define ((parse-rhs cross?) rhs)
(make-rhs (reassign-classes (parse-pattern (rhs-pattern rhs) cross?))
(rhs-var-info rhs)))
(struct-copy
compiled-lang lang
[lang
(map (λ (nt)
(make-nt (nt-name nt)
(map (λ (rhs)
(make-rhs (reassign-classes (parse-pattern (rhs-pattern rhs)))
(rhs-var-info rhs)))
(nt-rhs nt))))
(compiled-lang-lang lang))]))
[lang (map (parse-nt #f) (compiled-lang-lang lang))]
[cclang (map (parse-nt #t) (compiled-lang-cclang lang))]))
;; unparse-pattern: parsed-pattern -> pattern
(define unparse-pattern

View File

@ -690,6 +690,11 @@
(test-syn-err (define-language bad-lang1 (e name)) #rx"name")
(test-syn-err (define-language bad-lang2 (name x)) #rx"name")
(test-syn-err (define-language bad-lang3 (x_y x)) #rx"x_y")
(test-syn-err (define-language bad-lang4 (a 1 2) (b)) #rx"no productions")
(test-syn-err (let ()
(define-language good-lang (a 1 2))
(define-extended-language bad-lang5 good-lang (a) (b 2)))
#rx"no productions")
;; expect union with duplicate names to fail
(test (with-handlers ((exn? (λ (x) 'passed)))