Make struct* use static struct field information
This PR is largely based on #732 Fixes #3265 Closes #732
This commit is contained in:
parent
da5f77e277
commit
a5b69b6594
|
@ -808,6 +808,7 @@ not provided, it defaults to @racket[equal?].
|
||||||
A @racket[match] pattern form that matches an instance of a structure
|
A @racket[match] pattern form that matches an instance of a structure
|
||||||
type named @racket[struct-id], where the field @racket[field] in the
|
type named @racket[struct-id], where the field @racket[field] in the
|
||||||
instance matches the corresponding @racket[pat].
|
instance matches the corresponding @racket[pat].
|
||||||
|
The fields do not include those from super types.
|
||||||
|
|
||||||
Any field of @racket[struct-id] may be omitted, and such fields can
|
Any field of @racket[struct-id] may be omitted, and such fields can
|
||||||
occur in any order.
|
occur in any order.
|
||||||
|
@ -815,11 +816,16 @@ not provided, it defaults to @racket[equal?].
|
||||||
@examples[
|
@examples[
|
||||||
#:eval match-eval
|
#:eval match-eval
|
||||||
(eval:no-prompt
|
(eval:no-prompt
|
||||||
(struct tree (val left right)))
|
(struct tree (val left right))
|
||||||
|
(struct tree* tree (val)))
|
||||||
(match (tree 0 (tree 1 #f #f) #f)
|
(match (tree 0 (tree 1 #f #f) #f)
|
||||||
[(struct* tree ([val a]
|
[(struct* tree ([val a]
|
||||||
[left (struct* tree ([right #f] [val b]))]))
|
[left (struct* tree ([right #f] [val b]))]))
|
||||||
(list a b)])
|
(list a b)])
|
||||||
|
(match (tree* 0 #f #f 42)
|
||||||
|
[(and (struct* tree* ([val a]))
|
||||||
|
(struct* tree ([val b])))
|
||||||
|
(list a b)])
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
(require (for-syntax scheme/base)
|
(require (for-syntax scheme/base)
|
||||||
"match-tests.rkt" "match-exn-tests.rkt" "other-plt-tests.rkt" "other-tests.rkt"
|
"match-tests.rkt" "match-exn-tests.rkt" "other-plt-tests.rkt" "other-tests.rkt"
|
||||||
"examples.rkt"
|
"examples.rkt"
|
||||||
rackunit rackunit/text-ui)
|
rackunit rackunit/text-ui
|
||||||
|
(only-in racket/base local-require))
|
||||||
|
|
||||||
(require mzlib/plt-match)
|
(require mzlib/plt-match)
|
||||||
|
|
||||||
|
@ -300,6 +301,10 @@
|
||||||
|
|
||||||
))
|
))
|
||||||
|
|
||||||
|
(module test-struct*-struct-info racket/base
|
||||||
|
(struct foo (a))
|
||||||
|
(provide (rename-out [foo bar])))
|
||||||
|
|
||||||
(define struct*-tests
|
(define struct*-tests
|
||||||
(test-suite
|
(test-suite
|
||||||
"Tests of struct*"
|
"Tests of struct*"
|
||||||
|
@ -381,7 +386,24 @@
|
||||||
[val b]))]))
|
[val b]))]))
|
||||||
(make-tree 0 (make-tree 1 #f #f) #f))
|
(make-tree 0 (make-tree 1 #f #f) #f))
|
||||||
(check = 0 a)
|
(check = 0 a)
|
||||||
(check = 1 b)))))
|
(check = 1 b)))
|
||||||
|
(test-case "also from documentation"
|
||||||
|
(let ()
|
||||||
|
(define-struct tree (val left right))
|
||||||
|
(define-struct (tree* tree) (val))
|
||||||
|
(match-define
|
||||||
|
(and (struct* tree* ([val a]))
|
||||||
|
(struct* tree ([val b])))
|
||||||
|
(tree* 0 #f #f 42))
|
||||||
|
(check = 42 a)
|
||||||
|
(check = 0 b)))
|
||||||
|
(test-case "hygiene"
|
||||||
|
(let ()
|
||||||
|
(local-require 'test-struct*-struct-info)
|
||||||
|
(match-define
|
||||||
|
(struct* bar ([a x]))
|
||||||
|
(bar 1))
|
||||||
|
(check = x 1)))))
|
||||||
|
|
||||||
(define plt-match-tests
|
(define plt-match-tests
|
||||||
(test-suite "Tests for plt-match.rkt"
|
(test-suite "Tests for plt-match.rkt"
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
racket/string
|
racket/string
|
||||||
racket/struct-info
|
racket/struct-info
|
||||||
setup/path-to-relative
|
setup/path-to-relative
|
||||||
|
"../../private/struct-util.rkt"
|
||||||
"application-arity-checking.rkt"
|
"application-arity-checking.rkt"
|
||||||
"arr-i-parse.rkt"
|
"arr-i-parse.rkt"
|
||||||
(prefix-in a: "helpers.rkt")
|
(prefix-in a: "helpers.rkt")
|
||||||
|
@ -1085,20 +1086,6 @@
|
||||||
(loop (cdr l1)
|
(loop (cdr l1)
|
||||||
(+ i 1)))])))
|
(+ i 1)))])))
|
||||||
|
|
||||||
(define (predicate->struct-name orig-stx stx)
|
|
||||||
(if stx
|
|
||||||
(let ([m (regexp-match #rx"^(.*)[?]$" (format "~a" (syntax-e stx)))])
|
|
||||||
(cond
|
|
||||||
[m (cadr m)]
|
|
||||||
[else (raise-syntax-error
|
|
||||||
who
|
|
||||||
"unable to cope with a struct supertype whose predicate doesn't end with `?'"
|
|
||||||
orig-stx)]))
|
|
||||||
(raise-syntax-error
|
|
||||||
who
|
|
||||||
"unable to cope with a struct whose predicate is unknown"
|
|
||||||
orig-stx)))
|
|
||||||
|
|
||||||
;; get-field-names/no-field-info :: string?
|
;; get-field-names/no-field-info :: string?
|
||||||
;; (listof identifier?)
|
;; (listof identifier?)
|
||||||
;; (or/c identifier? boolean?)
|
;; (or/c identifier? boolean?)
|
||||||
|
@ -1137,7 +1124,7 @@
|
||||||
(define predicate (list-ref struct-info-list 2))
|
(define predicate (list-ref struct-info-list 2))
|
||||||
(define accessors (list-ref struct-info-list 3))
|
(define accessors (list-ref struct-info-list 3))
|
||||||
(define super-info (list-ref struct-info-list 5))
|
(define super-info (list-ref struct-info-list 5))
|
||||||
(define struct-name (predicate->struct-name provide-stx predicate))
|
(define struct-name (predicate->struct-name who provide-stx predicate))
|
||||||
(define immediate-field-names
|
(define immediate-field-names
|
||||||
(if (struct-field-info? the-struct-info)
|
(if (struct-field-info? the-struct-info)
|
||||||
(struct-field-info-list the-struct-info)
|
(struct-field-info-list the-struct-info)
|
||||||
|
|
|
@ -2,8 +2,24 @@
|
||||||
(require racket/match/match-expander
|
(require racket/match/match-expander
|
||||||
(for-syntax racket/base
|
(for-syntax racket/base
|
||||||
racket/struct-info
|
racket/struct-info
|
||||||
syntax/id-table
|
racket/list
|
||||||
racket/list))
|
"../private/struct-util.rkt"))
|
||||||
|
|
||||||
|
(define-for-syntax (extract-field-names orig-stx the-struct-info)
|
||||||
|
(define accessors (list-ref the-struct-info 3))
|
||||||
|
(define parent (list-ref the-struct-info 5))
|
||||||
|
(define num-fields (length accessors))
|
||||||
|
(define num-super-fields
|
||||||
|
(if (identifier? parent)
|
||||||
|
(length (cadddr (syntax-local-value parent)))
|
||||||
|
0))
|
||||||
|
(define num-own-fields (- num-fields num-super-fields))
|
||||||
|
(define own-accessors (take accessors num-own-fields))
|
||||||
|
(define struct-name (predicate->struct-name 'struct* orig-stx (list-ref the-struct-info 2)))
|
||||||
|
(for/list ([accessor (in-list own-accessors)])
|
||||||
|
;; add1 for hyphen
|
||||||
|
(string->symbol (substring (symbol->string (syntax-e accessor))
|
||||||
|
(add1 (string-length struct-name))))))
|
||||||
|
|
||||||
(define-match-expander
|
(define-match-expander
|
||||||
struct*
|
struct*
|
||||||
|
@ -17,57 +33,44 @@
|
||||||
[v (if (identifier? #'struct-name)
|
[v (if (identifier? #'struct-name)
|
||||||
(syntax-local-value #'struct-name fail)
|
(syntax-local-value #'struct-name fail)
|
||||||
(fail))]
|
(fail))]
|
||||||
[field-acc->pattern (make-free-id-table)])
|
[field->pattern (make-hash)])
|
||||||
(unless (struct-info? v) (fail))
|
(unless (struct-info? v) (fail))
|
||||||
; Check each pattern and capture the field-accessor name
|
(define the-struct-info (extract-struct-info v))
|
||||||
(for-each (lambda (an)
|
|
||||||
(syntax-case an ()
|
;; own-fields and all-accessors are in the reverse order
|
||||||
[(field pat)
|
(define all-accessors (list-ref the-struct-info 3))
|
||||||
(unless (identifier? #'field)
|
(define own-fields
|
||||||
(raise-syntax-error
|
(if (struct-field-info? v)
|
||||||
'struct* "not an identifier for field name"
|
(struct-field-info-list v)
|
||||||
stx #'field))
|
(extract-field-names stx the-struct-info)))
|
||||||
(let ([field-acc
|
;; Use hash instead of set so that we don't need to require racket/set
|
||||||
(datum->syntax #'field
|
(define field-set (for/hash ([field own-fields]) (values field #t)))
|
||||||
(string->symbol
|
|
||||||
(format "~a-~a"
|
;; Check that all field names are valid
|
||||||
(syntax-e #'struct-name)
|
(for ([an (in-list (syntax->list #'(field+pat ...)))])
|
||||||
(syntax-e #'field)))
|
(syntax-case an ()
|
||||||
#'field)])
|
[(field pat)
|
||||||
(when (free-id-table-ref field-acc->pattern field-acc #f)
|
(let ([fail-field (λ (msg) (raise-syntax-error 'struct* msg stx #'field))])
|
||||||
(raise-syntax-error 'struct* "Field name appears twice" stx #'field))
|
(unless (identifier? #'field)
|
||||||
(free-id-table-set! field-acc->pattern field-acc #'pat))]
|
(fail-field "not an identifier for field name"))
|
||||||
[_
|
(define name (syntax-e #'field))
|
||||||
(raise-syntax-error
|
(unless (hash-has-key? field-set name)
|
||||||
'struct* "expected a field pattern of the form (<field-id> <pat>)"
|
(fail-field "field name not associated with given structure type"))
|
||||||
stx an)]))
|
(when (hash-has-key? field->pattern name)
|
||||||
(syntax->list #'(field+pat ...)))
|
(fail-field "field name appears twice"))
|
||||||
(let* (; Get the structure info
|
(hash-set! field->pattern name #'pat))]
|
||||||
[acc (fourth (extract-struct-info v))]
|
[_ (raise-syntax-error
|
||||||
;; the accessors come in reverse order
|
'struct* "expected a field pattern of the form (<field-id> <pat>)"
|
||||||
[acc (reverse acc)]
|
stx an)]))
|
||||||
;; remove the first element, if it's #f
|
|
||||||
[acc (cond [(empty? acc) acc]
|
;; pats is in the reverse order
|
||||||
[(not (first acc)) (rest acc)]
|
(define pats
|
||||||
[else acc])]
|
(for/list ([field (in-sequences (in-list own-fields)
|
||||||
; Order the patterns in the order of the accessors
|
(in-cycle '(#f)))]
|
||||||
[pats-in-order
|
[accessor (in-list all-accessors)]
|
||||||
(for/list ([field-acc (in-list acc)])
|
#:when accessor)
|
||||||
(begin0
|
(hash-ref field->pattern field (syntax/loc stx _))))
|
||||||
(free-id-table-ref
|
(quasisyntax/loc stx (struct struct-name #,(reverse pats))))])))
|
||||||
field-acc->pattern field-acc
|
|
||||||
(syntax/loc stx _))
|
|
||||||
; Use up pattern
|
|
||||||
(free-id-table-remove! field-acc->pattern field-acc)))])
|
|
||||||
; Check that all patterns were used
|
|
||||||
(free-id-table-for-each
|
|
||||||
field-acc->pattern
|
|
||||||
(lambda (field-acc pat)
|
|
||||||
(when pat
|
|
||||||
(raise-syntax-error 'struct* "field name not associated with given structure type"
|
|
||||||
stx field-acc))))
|
|
||||||
(quasisyntax/loc stx
|
|
||||||
(struct struct-name #,pats-in-order))))])))
|
|
||||||
|
|
||||||
(provide struct* ==)
|
(provide struct* ==)
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,8 @@
|
||||||
"stx.rkt" "stxcase-scheme.rkt" "qq-and-or.rkt" "cond.rkt"
|
"stx.rkt" "stxcase-scheme.rkt" "qq-and-or.rkt" "cond.rkt"
|
||||||
"define-et-al.rkt"
|
"define-et-al.rkt"
|
||||||
"stxloc.rkt" "qqstx.rkt"
|
"stxloc.rkt" "qqstx.rkt"
|
||||||
"struct-info.rkt"))
|
"struct-info.rkt"
|
||||||
|
"struct-util.rkt"))
|
||||||
|
|
||||||
(#%provide define-struct*
|
(#%provide define-struct*
|
||||||
define-struct/derived
|
define-struct/derived
|
||||||
|
@ -899,16 +900,6 @@
|
||||||
[(null? xs) xs]
|
[(null? xs) xs]
|
||||||
[else (cons (car xs) (take (cdr xs) (sub1 n)))]))
|
[else (cons (car xs) (take (cdr xs) (sub1 n)))]))
|
||||||
|
|
||||||
;; modified from racket/collects/racket/contract/private/provide.rkt
|
|
||||||
(define-for-syntax (predicate->struct-name orig-stx stx)
|
|
||||||
(cond
|
|
||||||
[(regexp-match #rx"^(.*)[?]$" (format "~a" (syntax-e stx))) => cadr]
|
|
||||||
[else
|
|
||||||
(raise-syntax-error
|
|
||||||
#f
|
|
||||||
"unable to cope with a struct type whose predicate doesn't end with `?'"
|
|
||||||
orig-stx)]))
|
|
||||||
|
|
||||||
(define-for-syntax (find-accessor/no-field-info the-struct-info fld stx)
|
(define-for-syntax (find-accessor/no-field-info the-struct-info fld stx)
|
||||||
(define accessors (list-ref the-struct-info 3))
|
(define accessors (list-ref the-struct-info 3))
|
||||||
(define parent (list-ref the-struct-info 5))
|
(define parent (list-ref the-struct-info 5))
|
||||||
|
@ -920,7 +911,7 @@
|
||||||
0))
|
0))
|
||||||
(define num-own-fields (- num-fields num-super-fields))
|
(define num-own-fields (- num-fields num-super-fields))
|
||||||
(define own-accessors (take accessors num-own-fields))
|
(define own-accessors (take accessors num-own-fields))
|
||||||
(define struct-name (predicate->struct-name stx (list-ref the-struct-info 2)))
|
(define struct-name (predicate->struct-name #f stx (list-ref the-struct-info 2)))
|
||||||
(define accessor-name (string->symbol (format "~a-~a" struct-name (syntax-e fld))))
|
(define accessor-name (string->symbol (format "~a-~a" struct-name (syntax-e fld))))
|
||||||
(or (findf (λ (a) (eq? accessor-name (syntax-e a))) own-accessors)
|
(or (findf (λ (a) (eq? accessor-name (syntax-e a))) own-accessors)
|
||||||
(raise-syntax-error
|
(raise-syntax-error
|
||||||
|
|
22
racket/collects/racket/private/struct-util.rkt
Normal file
22
racket/collects/racket/private/struct-util.rkt
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
(module struct-util '#%kernel
|
||||||
|
(#%require "define.rkt"
|
||||||
|
"cond.rkt")
|
||||||
|
|
||||||
|
(#%provide predicate->struct-name)
|
||||||
|
|
||||||
|
;; predicate->struct-name : any/c syntax? (or/c identifier? #f) -> string?
|
||||||
|
;; Infers struct name from a predicate identifier. This is used as a fallback
|
||||||
|
;; method to extract field names when struct-field-info is not available.
|
||||||
|
(define (predicate->struct-name who orig-stx stx)
|
||||||
|
(if stx
|
||||||
|
(cond
|
||||||
|
[(regexp-match #rx"^(.*)[?]$" (symbol->string (syntax-e stx))) => cadr]
|
||||||
|
[else
|
||||||
|
(raise-syntax-error
|
||||||
|
who
|
||||||
|
"unable to cope with a struct type whose predicate doesn't end with `?'"
|
||||||
|
orig-stx)])
|
||||||
|
(raise-syntax-error
|
||||||
|
who
|
||||||
|
"unable to cope with a struct whose predicate is unknown"
|
||||||
|
orig-stx))))
|
Loading…
Reference in New Issue
Block a user