www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit c42aef881cf49d4cd56506af45a15954fa6939a1
parent 9a7e7422eb6d1d953c1804e116b957fc8d207baf
Author: Georges Dupéron <georges.duperon@gmail.com>
Date:   Mon, 23 Jan 2017 16:51:14 +0100

Support for define/syntax-parse, including when there are multiple defines. Search for the most up-to-date list of current-pvars is done by performing a dichotomy over a set of sequentially numbered current-pvars-paramNNN. Closes FB case 180.

Diffstat:
Mcurrent-pvars.rkt | 192++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Mparse/private/runtime.rkt | 3++-
Mscribblings/stxparse-info.scrbl | 2++
Mtest/test-current-pvars.rkt | 245++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
4 files changed, 390 insertions(+), 52 deletions(-)

diff --git a/current-pvars.rkt b/current-pvars.rkt @@ -1,24 +1,166 @@ -#lang racket/base -(require racket/stxparam - (for-syntax racket/base - racket/contract)) - -(provide (for-syntax (rename-out [get-current-pvars current-pvars])) - with-pvars) - -(define-syntax-parameter current-pvars '()) - -(define-syntax (with-pvars stx) - (syntax-case stx () - [(_ (pvar ...) . body) - (andmap identifier? (syntax->list #'(pvar ...))) - (with-syntax ([(reverse-pvar ...) (reverse (syntax->list #'(pvar ...)))]) - #'(syntax-parameterize - ([current-pvars (list* (quote-syntax reverse-pvar) ... - (syntax-parameter-value #'current-pvars))]) - . body))])) - -(begin-for-syntax - (define/contract (get-current-pvars) - (-> (listof identifier?)) - (syntax-parameter-value #'current-pvars))) -\ No newline at end of file +(module current-pvars '#%kernel + (#%provide (for-syntax current-pvars) + with-pvars + define-pvars) + + (#%require racket/private/small-scheme + (for-syntax '#%kernel + racket/private/qq-and-or + racket/private/stx)) + + ;; This is a poor man's syntax parameter. Since the implementation of + ;; racket/stxparam depends on syntax-case, and we want to add current-pvars to + ;; syntax-case, we cannot use syntax parameters, lest we create a cyclic + ;; dependency. Instead, we implement here a simplified "syntax parameter". + ; Like racket/stxparam, it relies on nested bindings of the same identifier, + ;; and on syntax-local-get-shadower to access the most nested binding. + + ;; Since define/with-syntax and define/syntax-parse need to add new ids to + ;; the list, they redefine current-pvars-param, shadowing the outer binding. + ;; Unfortunately, if a let form contains two uses of define/with-syntax, this + ;; would result in two redefinitions of current-pvars-param, which would cause + ;; a "duplicate definition" error. Instead of shadowing the outer bindings, we + ;; therefore store the list of bound syntax pattern variables in a new, fresh + ;; identifier. When accessing the list, (current-pvars) then checks all such + ;; identifiers. The identifiers have the form current-pvars-paramNNN and are + ;; numbered sequentially, each new "shadowing" identifier using the number + ;; following the latest visible identifier. + ;; When it is safe to shadow identifiers (i.e. for with-pvars, but not for + ;; define-pvars), current-pvars-index-lower-bound is also shadowed. + ;; When current-pvars-index-lower-bound is bound, it contains the index of the + ;; latest current-pvars-paramNNN at that point. + ;; When accessing the latest current-pvars-paramNNN, a dichotomy search is + ;; performed between current-pvars-index-lower-bound and an upper bound + ;; computed by trying to access lower-bound + 2ᵏ, with increasing values of k, + ;; until an unbound identifier is found. + + ;; (poor-man-parameterof exact-nonnegative-integer?) + (define-syntaxes (current-pvars-index-lower-bound) 0) + ;; (poor-man-parameterof (listof identifier?)) + (define-syntaxes (current-pvars-param0) '()) + + (begin-for-syntax + ;; (-> identifier? (or/c #f (listof identifier?))) + (define-values (try-current-pvars) + (λ (id) + (syntax-local-value + (syntax-local-get-shadower id + #t) + ;; Default value if we are outside of any with-pvars. + (λ () #f)))) + + ;; (-> exact-nonnegative-integer? identifier?) + (define-values (nth-current-pvars-id) + (λ (n) + (syntax-local-introduce + (datum->syntax (quote-syntax here) + (string->symbol + (format "current-pvars-param~a" n)))))) + + ;; (-> exact-nonnegative-integer? (or/c #f (listof identifier?))) + (define-values (try-nth-current-pvars) + (λ (n) + (try-current-pvars (nth-current-pvars-id n)))) + + ;; (-> exact-nonnegative-integer? exact-nonnegative-integer? + ;; exact-nonnegative-integer?) + ;; Doubles the value of n until (+ start n) is not a valid index + ;; in the current-pvars-param pseudo-array + (define-values (double-max) + (λ (start n) + (if (try-nth-current-pvars (+ start n)) + (double-max start (* n 2)) + (+ start n)))) + + + ;; (-> exact-nonnegative-integer? exact-nonnegative-integer? + ;; exact-nonnegative-integer?) + ;; Preconditions: upper > lower ∧ upper - lower = 2ᵏ ∧ k ∈ ℕ + ;; Returns the last valid index in the current-pvars-param pseudo-array, + ;; by dichotomy between + (define-values (dichotomy) + (λ (lower upper) + (if (= (- upper lower) 1) + (if (try-nth-current-pvars upper) + upper + lower) + (let ([mid (/ (+ upper lower) 2)]) + (if (try-nth-current-pvars mid) + (dichotomy mid upper) + (dichotomy lower mid)))))) + + ;; (-> exact-nonnegative-integer?) + (define-values (find-last-current-pvars) + (λ () + (let ([lower-bound (syntax-local-value + (syntax-local-get-shadower + (syntax-local-introduce + (quote-syntax current-pvars-index-lower-bound)) + #t))]) + (if (not (try-nth-current-pvars (+ lower-bound 1))) + ;; Short path for the common case where there are no uses + ;; of define/with-syntax or define/syntax-parse in the most nested + ;; syntax-case, with-syntax or syntax-parse + lower-bound + ;; Find an upper bound by repeatedly doubling an offset (starting + ;; with 1) from the lower bound, then perform a dichotomy between + ;; these two bounds. + (dichotomy lower-bound + (double-max lower-bound 1)))))) + + ;; (-> (listof identifier?)) + (define-values (current-pvars) + (λ () + (try-nth-current-pvars (find-last-current-pvars))))) + + ;; (with-pvars [pvar ...] . body) + (define-syntaxes (with-pvars) + (lambda (stx) + (if (not (and (stx-pair? stx) + (identifier? (stx-car stx)) + (stx-pair? (stx-cdr stx)) + (syntax->list (stx-car (stx-cdr stx))) + (andmap identifier? + (syntax->list (stx-car (stx-cdr stx)))))) + (raise-syntax-error 'with-pvars "bad syntax" stx) + (void)) + (let* ([pvars (syntax->list (stx-car (stx-cdr stx)))] + [quoted-pvars (map (λ (v) `(quote-syntax ,v)) pvars)] + [body (stx-cdr (stx-cdr stx))] + [old-pvars-index (find-last-current-pvars)] + [old-pvars (try-nth-current-pvars old-pvars-index)] + [binding (syntax-local-identifier-as-binding + (nth-current-pvars-id (+ old-pvars-index 1)))] + [lower-bound-binding + (syntax-local-identifier-as-binding + (syntax-local-introduce + (quote-syntax current-pvars-index-lower-bound)))]) + (datum->syntax + (quote-syntax here) + `(letrec-syntaxes+values + ([(,binding) (list* ,@quoted-pvars + (try-nth-current-pvars ,old-pvars-index))] + [(,lower-bound-binding) ,(+ old-pvars-index 1)]) + () + . ,body))))) + + (define-syntaxes (define-pvars) + (lambda (stx) + (if (not (and (stx-pair? stx) + (identifier? (stx-car stx)) + (syntax->list (stx-cdr stx)) + (andmap identifier? + (syntax->list (stx-cdr stx))))) + (raise-syntax-error 'with-pvars "bad syntax" stx) + (void)) + (let* ([pvars (syntax->list (stx-cdr stx))] + [quoted-pvars (map (λ (v) `(quote-syntax ,v)) pvars)] + [old-pvars-index (find-last-current-pvars)] + [old-pvars (try-nth-current-pvars old-pvars-index)] + [binding (syntax-local-identifier-as-binding + (nth-current-pvars-id (+ old-pvars-index 1)))]) + (datum->syntax + (quote-syntax here) + `(define-syntaxes (,binding) + (list* ,@quoted-pvars + (try-nth-current-pvars ,old-pvars-index)))))))) +\ No newline at end of file diff --git a/parse/private/runtime.rkt b/parse/private/runtime.rkt @@ -139,7 +139,8 @@ residual.rkt. 'name 'depth 'syntax?)) ... (define-syntax name (make-syntax-mapping 'depth (quote-syntax stmp))) - ...)))])) + ... + (define-pvars name ...))))])) (define-syntax-rule (phase-of-enclosing-module) (variable-reference->module-base-phase diff --git a/scribblings/stxparse-info.scrbl b/scribblings/stxparse-info.scrbl @@ -7,6 +7,8 @@ @racketmodname[syntax/parse]} @author[@author+email["Georges Dupéron" "georges.duperon@gmail.com"]] +Source code: @url{https://github.com/jsmaniac/stxparse-info} + @defmodule[stxparse-info/parse] The module @racketmodname[stxparse-info/parse] is a patched version of diff --git a/test/test-current-pvars.rkt b/test/test-current-pvars.rkt @@ -4,38 +4,232 @@ racket/stxparam rackunit) +;; Test utilities (define-syntax (list-pvars stx) #`'#,(current-pvars)) -(check-equal? (list-pvars) - '()) - -(check-equal? (syntax-parse #'(1 2 3 a b c) - [(x y:nat ... {~parse w (list-pvars)} z ...) - (syntax->datum #`[w #,(list-pvars)])]) - '([y x] [z w y x])) - -(check-equal? (list-pvars) - '()) - ;; Check that the identifier has the right scopes (define-syntax (ref-nth-pvar stx) (syntax-case stx () [(_ n) (number? (syntax-e #'n)) - #`#'#,(let ([pvar (list-ref (current-pvars) (syntax-e #'n))]) + #`#'#,(let ([pvar (if (>= (syntax-e #'n) (length (current-pvars))) + #'too-big! + (list-ref (current-pvars) (syntax-e #'n)))]) (datum->syntax pvar (syntax-e pvar) stx))])) -(check-equal? (syntax-parse #'1 - [x - (syntax->datum (ref-nth-pvar 0))]) - 1) - -(check-equal? (syntax-parse #'1 - [x - (cons (syntax->datum (ref-nth-pvar 0)) - (syntax-parse #'2 - [x - (list (syntax->datum (ref-nth-pvar 0)) - (syntax->datum (ref-nth-pvar 1)))]))]) - '(1 2 1)) -\ No newline at end of file + +;; First check that (current-pvars) returns the empty list before anything +;; is done: + +(check-equal? (list-pvars) + '()) + +;; Simple case: +(check-equal? (syntax-parse #'(1 2 3 a b c) + [(x y ...) + (list-pvars)]) + '(y x)) + +;; Mixed definitions from user code and from a macro +(begin + (define-syntax (mixed stx) + (syntax-case stx () + [(_ val def body) + #'(let () + (define/syntax-parse x #'val) + def + body)])) + + (check-equal? (mixed 1 (define/syntax-parse y #'2) + (mixed 3 (define/syntax-parse y #'4) + (list-pvars))) + '(y x y x)) + + (check-equal? (mixed 1 (define/syntax-parse y #'2) + (mixed 3 (define/syntax-parse y #'4) + (list (syntax->datum (ref-nth-pvar 0)) + (syntax->datum (ref-nth-pvar 1)) + (syntax->datum (ref-nth-pvar 2)) + (syntax->datum (ref-nth-pvar 3))))) + '(4 3 2 1))) + +;; Tests for syntax-parse +(begin + (check-equal? (syntax-parse #'(1 2 3 a b c) + [(x y:nat ... {~parse w (list-pvars)} z ...) + (syntax->datum #`[w #,(list-pvars)])]) + '([y x] [z w y x])) + + (check-equal? (list-pvars) + '()) + + (check-equal? (syntax-parse #'1 + [x + (syntax->datum (ref-nth-pvar 0))]) + 1) + + (check-equal? (syntax-parse #'1 + [x + (cons (syntax->datum (ref-nth-pvar 0)) + (syntax-parse #'2 + [x + (list (syntax->datum (ref-nth-pvar 0)) + (syntax->datum (ref-nth-pvar 1)))]))]) + '(1 2 1))) + +;; tests for define/syntax-parse +(begin + (check-equal? (syntax-parse #'1 + [x + #:with y #'2 + (define/syntax-parse z #'3) + (list-pvars)]) + '(z y x)) + + (check-equal? (syntax-parse #'1 + [x + #:with y #'2 + (define/syntax-parse z #'3) + (list (syntax->datum (ref-nth-pvar 0)) + (syntax->datum (ref-nth-pvar 1)) + (syntax->datum (ref-nth-pvar 2)))]) + '(3 2 1)) + + (check-equal? (syntax-parse #'1 + [x + #:with y #'2 + (define/syntax-parse x #'3) + (list-pvars)]) + '(x y x)) + + (check-equal? (syntax-parse #'1 + [x + #:with y #'2 + (define/syntax-parse x #'3) + (list (syntax->datum (ref-nth-pvar 0)) + (syntax->datum (ref-nth-pvar 1)) + (syntax->datum (ref-nth-pvar 2)))]) + '(3 2 1)) + + (check-equal? (syntax-parse #'1 + [x + #:with y #'2 + (define/syntax-parse x #'3) + (define/syntax-parse y #'4) + (list (syntax->datum (ref-nth-pvar 0)) + (syntax->datum (ref-nth-pvar 1)) + (syntax->datum (ref-nth-pvar 2)) + (syntax->datum (ref-nth-pvar 3)))]) + '(4 3 2 1)) + + (check-equal? (syntax-parse #'1 + [x + #:with y #'2 + (define/syntax-parse x #'3) + (define/syntax-parse y #'4) + (define/syntax-parse z #'5) + (list (syntax->datum (ref-nth-pvar 0)) + (syntax->datum (ref-nth-pvar 1)) + (syntax->datum (ref-nth-pvar 2)) + (syntax->datum (ref-nth-pvar 3)) + (syntax->datum (ref-nth-pvar 4)))]) + '(5 4 3 2 1)) + + (check-equal? (syntax-parse #'(1 2 3) + [(x y z) + (define/syntax-parse x #'4) + (define/syntax-parse y #'5) + (list (syntax->datum (ref-nth-pvar 0)) + (syntax->datum (ref-nth-pvar 1)) + (syntax->datum (ref-nth-pvar 2)) + (syntax->datum (ref-nth-pvar 3)) + (syntax->datum (ref-nth-pvar 4)))]) + '(5 4 3 2 1)) + + (check-equal? (syntax-parse #'(1 2 3) + [(x y z) + (define/syntax-parse x #'4) + (define/syntax-parse y #'5) + (list-pvars)]) + '(y x z y x)) + + ;; Test with nested let, less variables in the nested let + (check-equal? (let () + (define/syntax-parse w #'1) + (define/syntax-parse x #'2) + (define/syntax-parse y #'3) + (define/syntax-parse z #'4) + (list (list-pvars) + (let () + (define/syntax-parse w #'5) + (define/syntax-parse x #'6) + (list-pvars)) + (list-pvars))) + '((z y x w) (x w z y x w) (z y x w))) + + ;; Test with nested let, more variables in the nested let + (check-equal? (let () + (define/syntax-parse w #'1) + (define/syntax-parse x #'2) + (list (list-pvars) + (let () + (define/syntax-parse w #'3) + (define/syntax-parse x #'4) + (define/syntax-parse y #'5) + (define/syntax-parse z #'6) + (list-pvars)) + (list-pvars))) + '((x w) (z y x w x w) (x w))) + + (check-equal? (let () + (define/syntax-parse w #'1) + (define/syntax-parse x #'2) + (define/syntax-parse y #'3) + (define/syntax-parse z #'4) + (list (list-pvars) + (syntax-parse #'5 + [k + (define/syntax-parse w #'5) + (define/syntax-parse x #'6) + (list-pvars)]) + (list-pvars))) + '((z y x w) (x w k z y x w) (z y x w))) + + (check-equal? (let () + (define/syntax-parse w #'1) + (define/syntax-parse x #'2) + (list (list-pvars) + (syntax-parse #'5 + [k + (define/syntax-parse w #'3) + (define/syntax-parse x #'4) + (define/syntax-parse y #'5) + (define/syntax-parse z #'6) + (list-pvars)]) + (list-pvars))) + '((x w) (z y x w k x w) (x w))) + + (check-equal? (let () + (define/syntax-parse w #'1) + (define/syntax-parse x #'2) + (list (list-pvars) + (syntax-parse #'5 + [k + (define/syntax-parse w #'3) + (define/syntax-parse x #'4) + (define/syntax-parse y #'5) + (define/syntax-parse z #'6) + (list (list-pvars) + (syntax-parse #'5 + [k + (define/syntax-parse x #'4) + (define/syntax-parse y #'4) + (list-pvars)]) + (list-pvars))]) + (list-pvars))) + '((x w) + ((z y x w k x w) + (y x k z y x w k x w) + (z y x w k x w)) + (x w))))