9 Check Syntax
Check Syntax is a part of the DrRacket collection, but is implemented via the plugin API. See also drracket/check-syntax.
9.1 Check Syntax Button
(require drracket/syncheck-drracket-button) | |
package: drracket |
value
syncheck-drracket-button :
(list/c string? (is-a?/c bitmap%) (-> (is-a?/c top-level-window<%>) any))
9.2 Syntax Properties that Check Syntax Looks For
Check Syntax collects the values of the syntax-propertys named 'disappeared-use, 'disappeared-binding, 'identifiers-as-disappeared-uses?, 'identifier-as-keyword, 'sub-range-binders, and 'mouse-over-tooltips and uses them to add control which arrows are added to the program text. These properties are intended for use when a macro discards or manufactures identifiers that, from the programmers perspective, should be binding each other, or when there are identifiers that are intended to be used more in the spirit of keywords, and thus should be ignored.
For example, here is program with a macro that discards its arguments, but adds properties to the result syntax object so that the two occurrences of a are treated as a binding/bound pair by Check Syntax.
(define-syntax (m stx) (syntax-case stx () [(_ id1 id2) (and (identifier? #'id1) (identifier? #'id2)) (syntax-property (syntax-property #'1 'disappeared-use (list (syntax-local-introduce #'id1))) 'disappeared-binding (list (syntax-local-introduce #'id2)))])) (m a a)
Check Syntax draws arrows only between identifiers that are free-identifier=?. They must be syntax-original? or have the syntax-property 'original-for-check-syntax set to #t. See also current-recorded-disappeared-uses.
#lang racket (require (for-syntax syntax/parse)) (define-syntax (depths stx) (syntax-parse stx [(_ id expr) (define table (make-hash)) (let loop ([stx #'expr] [depth 0]) (cond [(syntax->list stx) => (λ (lst) (for ([ele (in-list lst)]) (loop ele (+ depth 1))))] [(identifier? stx) (hash-set! table (syntax-e stx) depth)])) #`(define-syntax id #,table)])) (define-syntax (depth-of stx) (syntax-parse stx [(_ id1 id2) (datum->syntax #'here (hash-ref (syntax-local-value #'id1) (syntax-e #'id2)))])) (depths my-sexp ((a) b (((((((c))))))))) (depth-of my-sexp a) (depth-of my-sexp b) (depth-of my-sexp c)
Extending these macro to cooperate with Check syntax requires more information to be collected on the definition side and then used at the use side:
#lang racket (require (for-syntax syntax/parse)) (define-syntax (depths stx) (syntax-parse stx [(_ id expr) (define table (make-hash)) (let loop ([stx #'expr] [depth 0]) (cond [(syntax->list stx) => (λ (lst) (for ([ele (in-list lst)]) (loop ele (+ depth 1))))] [(identifier? stx) (hash-set! table (syntax-e stx) (cons (vector (syntax-source stx) (syntax-line stx) (syntax-column stx) (syntax-position stx) (syntax-span stx)) depth))])) #`(define-syntax id #,table)])) (define-syntax (depth-of stx) (syntax-parse stx [(_ id1 id2) (define pr (hash-ref (syntax-local-value #'id1) (syntax-e #'id2))) (define fake-binder (datum->syntax #'id2 (syntax-e #'id2) (car pr) #'id2)) #`(begin (void (let ([#,fake-binder 1]) id2)) #,(cdr pr))])) (depths my-sexp ((a) b (((((((c))))))))) (depth-of my-sexp a) (depth-of my-sexp b) (depth-of my-sexp c)
For each syntax object that appears in the fully expanded program, DrRacket traverses it looking for identifiers and connecting them to likely binding occurrences. When it finds such identifiers it draws an arrow with a large question mark near the head of the arrow. But, if the syntax object has the property 'identifiers-as-disappeared-uses?, then the arrows are the normal arrow color.
(or/c (vector/c identifier? natural? natural? identifier? natural? natural?) (vector/c identifier? natural? natural? (real-in 0 1) (real-in 0 1) identifier? natural? natural? (real-in 0 1) (real-in 0 1)))
Each vector is interpreted as a single arrow. The first identifier in the vector is the start of the arrow and the second identifier in the vector is the destination of the arrow. The two natural numbers that follow each identifier adjust the precise starting and ending ranges for the arrows, however. They are interpreted as offsets into the position of each corresponding identifier, making the arrows start and end on just a portion of the identifier, instead of the entire identifier.
If the vector has 8 elements, then the two real numbers are treated as the precise location where the arrow starts and ends, inside the rectangle that corresponds to the start and end of the identifier. The first real number is for the x direction and the second one is for the y direction. For example, if some identifier has a position and span of 100 and 10, and the offset are 1 and 5, then the rectangle that bounds the corresponding end of the arrow would be from position 101 to 105. This entire range gets highlighted when the mouse moves over it. The arrow itself, however, will start from some specific point inside that editor range, normally in the center and corresponds to the situation where the two real numbers are both 0.5. If, however the two reals are both 1/3, then the arrow will start one third of the way from the top to the bottom and one third of the way from the left to the right.
The property is looked for in expression positions and on binding identifiers.
Here’s an example:
#lang racket/base (require (for-syntax racket/base)) (define-syntax (define/hyphen stx) (syntax-case stx () [(_ id1 id2 rhs-expr) (let () (define first-part (symbol->string (syntax-e #'id1))) (define second-part (symbol->string (syntax-e #'id2))) (define first-len (string-length first-part)) (define second-len (string-length second-part)) (define hyphenated-id (datum->syntax #'id1 (string->symbol (string-append first-part "-" second-part)))) (syntax-property #`(define #,hyphenated-id rhs-expr) 'sub-range-binders (list (vector (syntax-local-introduce hyphenated-id) 0 first-len 0.5 0.5 (syntax-local-introduce #'id1) 0 first-len 0.5 0.5) (vector (syntax-local-introduce hyphenated-id) (+ first-len 1) second-len 0.5 0 (syntax-local-introduce #'id2) 0 second-len 0.5 1))))])) (define/hyphen big generator 11) (+ big-generator big-generator)
After putting this code in the DrRacket window, mouse over the words “big” and “generator” to see arrows pointing to the individual pieces of the identifier big-generator. The four 0.5s in the first vector put the arrows on big in the center of the identifiers; the 0.5 0 and the 0.5 1 in the second vector put the arrows at the top and bottom center for generator.
(vector/c syntax? exact-nonnegative-integer? exact-nonnegative-integer? (or/c string? (-> string?)))
#lang racket (define-syntax (char-span stx) (syntax-case stx () [(_ a) (syntax-property #'a 'mouse-over-tooltips (vector stx (sub1 (syntax-position stx)) (sub1 (+ (syntax-position stx) (syntax-span stx))) (format "this expression\nspans ~a chars" (syntax-span stx))))])) (char-span (+ 1 2))
If the syntax property 'identifier-as-keyword is any value except #f and appears on an identifier, then Check Syntax ignores the identifier, not drawing any arrows to it.
Changed in version 1.3 of package drracket: Looks for 'sub-range-binders
on binding identifiers (not just in expression positions).
Changed in version 1.5: Looks for 'identifier-as-keyword on identifiers.