From e272233e225e16d16671c516fedb158432ab62dd Mon Sep 17 00:00:00 2001 From: eutro Date: Thu, 17 Jul 2025 01:13:17 +0100 Subject: [PATCH] Add `parallel` binding spec This is to `nest` what `let` is to `let*`, and is precisely the binding structure that Qi needs for tee and friends. --- private/runtime/binding-spec.rkt | 29 +++++++++++++++----- private/syntax/compile/binding-spec.rkt | 36 ++++++++++++++++++++----- scribblings/reference/specifying.scrbl | 18 +++++++++++++ 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/private/runtime/binding-spec.rkt b/private/runtime/binding-spec.rkt index c09cad7..0400f7c 100644 --- a/private/runtime/binding-spec.rkt +++ b/private/runtime/binding-spec.rkt @@ -15,6 +15,7 @@ (struct-out group) ; [] (struct-out nest) (struct-out nest-one) + (struct-out parallel) (struct-out nested) (struct-out suspend) (struct-out fresh-env-expr-ctx) @@ -65,6 +66,7 @@ (struct group [specs] #:transparent) (struct nest [pvar nonterm spec] #:transparent) (struct nest-one [pvar nonterm spec] #:transparent) +(struct parallel [pvar nonterm spec] #:transparent) (struct nested [] #:transparent) (struct suspend [pvar] #:transparent) (struct fresh-env-expr-ctx [spec] #:transparent) @@ -290,7 +292,17 @@ (match-define (nest-ret done-seq st^) res) (set-pvar st^ pv (car done-seq))] - + + [(parallel pv f inner-spec) + (define init-seq (get-pvar st pv)) + + (define res + (start-nest f init-seq st inner-spec local-scopes #:parallel? #t)) + + (match-define (nest-ret done-seq st^) res) + + (set-pvar st^ pv done-seq)] + [(nested) (update-nest-state st @@ -305,7 +317,8 @@ ; seq is (listof (treeof syntax?)) ; inner-spec-st is exp-state? ; inner-spec is spec -(struct nest-call [f seq acc-scopes inner-spec-st inner-spec] #:transparent) +; parallel-scopes is (or/c #f list?) -- list of initial scopes if binding in parallel +(struct nest-call [f seq acc-scopes parallel-scopes inner-spec-st inner-spec] #:transparent) ; seq is (listof (treeof syntax?)) (struct nest-ret [done-seq inner-spec-st^] #:transparent) @@ -343,13 +356,15 @@ ;; TODO: if the meta-DSL supports binding macros in the future, we may need to add a scope ;; at every nest step rather than only the entry point to account for macros defined in one ;; nest step and used in the next without an intervening new scope. -(define (start-nest f init-seq st inner-spec local-scopes) +(define (start-nest f init-seq st inner-spec local-scopes #:parallel? [parallel? #f]) (with-scope sc - (simple-expand-nest (nest-call f init-seq '() st inner-spec) (cons sc local-scopes)))) + (define initial-scopes (cons sc local-scopes)) + (define parallel-scopes (if parallel? initial-scopes #f)) + (simple-expand-nest (nest-call f init-seq '() parallel-scopes st inner-spec) initial-scopes))) ; nest-call? -> nest-ret? (define (simple-expand-nest nest-st new-local-scopes) - (match-define (nest-call f seq acc-scopes inner-spec-st inner-spec) nest-st) + (match-define (nest-call f seq acc-scopes parallel-scopes inner-spec-st inner-spec) nest-st) (define acc-scopes^ (append acc-scopes new-local-scopes)) @@ -360,8 +375,8 @@ ;; Original: (call-expand-function/nest f - (add-scopes stx acc-scopes^) - (nest-call f rest acc-scopes^ inner-spec-st inner-spec))) + (add-scopes stx (or parallel-scopes acc-scopes^)) + (nest-call f rest acc-scopes^ parallel-scopes inner-spec-st inner-spec))) (match-define (nest-ret done-seq inner-spec-st^) nest-st^) (nest-ret (cons stx^ done-seq) inner-spec-st^)] diff --git a/private/syntax/compile/binding-spec.rkt b/private/syntax/compile/binding-spec.rkt index 471889d..25a1bd0 100644 --- a/private/syntax/compile/binding-spec.rkt +++ b/private/syntax/compile/binding-spec.rkt @@ -45,6 +45,7 @@ ;; Elaborated representation; variables are associated with expander-environment information +;; A Pattern VARiable called `id`. (struct pvar [id info]) (struct with-stx [stx]) @@ -60,7 +61,8 @@ (struct export-syntax with-stx [pvar transformer-pvar] #:transparent) (struct export-syntaxes with-stx [pvar transformer-pvar] #:transparent) (struct nest with-stx [pvar spec] #:transparent) -(struct nest-one with-stx [pvar spec] #:transparent) +(struct nest-one with-stx [pvar spec] #:transparent) +(struct parallel with-stx [pvar spec] #:transparent) (struct suspend with-stx [pvar] #:transparent) (struct scope with-stx [spec] #:transparent) (struct group [specs] #:transparent) @@ -80,6 +82,9 @@ [(nest-one stx pv s) (let ([s^ (map-bspec f s)]) (f (nest-one stx pv s^)))] + [(parallel stx pv s) + (let ([s^ (map-bspec f s)]) + (f (parallel stx pv s^)))] [(scope stx s) (let ([s^ (map-bspec f s)]) (f (scope stx s^)))] @@ -94,6 +99,7 @@ (match spec [(or (s* nest [spec s]) (s* nest-one [spec s]) + (s* parallel [spec s]) (s* scope [spec s])) (let ([s^ (fold-bspec f s)]) (f spec (list s^)))] @@ -109,7 +115,7 @@ ; convert surface syntax for a bspec to a structure representation. (define elaborate-bspec (syntax-parser - #:datum-literals (scope bind bind-syntax bind-syntaxes import export export-syntax export-syntaxes re-export nest nest-one host) + #:datum-literals (scope bind bind-syntax bind-syntaxes import export export-syntax export-syntaxes re-export nest nest-one parallel host) [v:nonref-id (elaborate-ref (attribute v))] [(bind ~! v:nonref-id ...+) @@ -192,6 +198,13 @@ (s* nonterm-rep [variant-info (s* nesting-nonterm-info)]) "nesting nonterminal") (elaborate-bspec (attribute spec)))] + [(parallel ~! v:nonref-id spec:bspec-term) + (parallel + this-syntax + (elaborate-pvar (attribute v) + (s* nonterm-rep [variant-info (s* nesting-nonterm-info)]) + "nesting nonterminal") + (elaborate-bspec (attribute spec)))] [(host ~! v:nonref-id) (suspend this-syntax @@ -268,6 +281,7 @@ (s* export [pvar (pvar v _)]) (s* nest [pvar (pvar v _)]) (s* nest-one [pvar (pvar v _)]) + (s* parallel [pvar (pvar v _)]) (s* suspend [pvar (pvar v _)])) (list v)] [(or (s* bind-syntax [pvar (pvar v1 _)] [transformer-pvar (pvar v2 _)]) @@ -331,7 +345,7 @@ ; one-pass-spec: unscoped-spec ; exporting-spec: (seq (* (or (export _) (export-syntax _ _) (export-syntaxes _ _))) (* (re-export _)) refs+subexps) ; unscoped-spec: refs+subexps -; refs+subexps: (* (or (ref _) (nest _ unscoped-spec) (nest-one _ unscoped-spec) (scope scoped-spec))) +; refs+subexps: (* (or (ref _) (nest _ unscoped-spec) (nest-one _ unscoped-spec) (parallel _ unscoped-spec) (scope scoped-spec))) ; scoped-spec: (seq (* (or (bind-syntax _ _) (bind-syntaxes _ _) (bind _))) (? (rec _)) refs+subexps) ; ; The implementation below separately implements refs+subexps for each context in which it occurs to @@ -369,7 +383,8 @@ [(and (s* re-export) (with-stx stx)) (re-export-context-error stx)] [(or (s* nest [spec s]) - (s* nest-one [spec s])) + (s* nest-one [spec s]) + (s* parallel [spec s])) (check-order/unscoped-expression s)] [(s* scope [spec s]) (check-order/scoped-expression s)])) @@ -409,7 +424,8 @@ [(or (s* ref) (s* suspend)) (check-sequence refs+subexps specs)] [(or (s* nest [spec s]) - (s* nest-one [spec s])) + (s* nest-one [spec s]) + (s* parallel [spec s])) (check-order/unscoped-expression s) (check-sequence refs+subexps specs)] [(s* scope [spec s]) @@ -445,7 +461,8 @@ [(or (s* ref) (s* suspend)) (check-sequence refs+subexps specs)] [(or (s* nest [spec s]) - (s* nest-one [spec s])) + (s* nest-one [spec s]) + (s* parallel [spec s])) (check-order/unscoped-expression s) (check-sequence refs+subexps specs)] [(s* scope [spec s]) @@ -504,6 +521,11 @@ [(nonterm-rep (nesting-nonterm-info expander)) (with-syntax ([spec-c (compile-bspec-term/single-pass spec)]) #`(nest-one '#,v #,expander spec-c))])] + [(parallel _ (pvar v info) spec) + (match info + [(nonterm-rep (nesting-nonterm-info expander)) + (with-syntax ([spec-c (compile-bspec-term/single-pass spec)]) + #`(parallel '#,v #,expander spec-c))])] [(scope _ spec) (with-syntax ([spec-c (compile-bspec-term/single-pass spec)]) #'(scope spec-c))] @@ -524,6 +546,7 @@ [(or (ref _) (nest _ _ _) (nest-one _ _ _) + (parallel _ _ _) (scope _ _) (suspend _ _)) no-op] @@ -551,6 +574,7 @@ [(or (ref _) (nest _ _ _) (nest-one _ _ _) + (parallel _ _ _) (scope _ _) (suspend _ _)) (compile-bspec-term/single-pass spec)] diff --git a/scribblings/reference/specifying.scrbl b/scribblings/reference/specifying.scrbl index 6390e99..ab24f5b 100644 --- a/scribblings/reference/specifying.scrbl +++ b/scribblings/reference/specifying.scrbl @@ -278,6 +278,7 @@ When a form production's form is used outside of the context of a syntax-spec DS [spec ...] (nest spec-variable-id binding-spec) (nest-one spec-variable-id binding-spec) + (parallel spec-variable-id binding-spec) (import spec-variable-id ...+) (export spec-variable-id ...+) (export-syntax spec-variable-id spec-variable-id) @@ -371,6 +372,23 @@ When a form production's form is used outside of the context of a syntax-spec DS #:binding (nest-one car-pat (nest-one cdr-pat nested)))) ] } +@item{ + @racket[parallel] is like @racket[nest], but binds in parallel rather than in sequence. That is, while @racket[nest] allows each sequential spec to reference bindings in previous specs, @racket[parallel] does not. If @racket[nest] is @racket[let*], then @racket[parallel] is like @racket[let]. + + Example: + @racketblock[ + (syntax-spec + (binding-class my-var) + (nonterminal my-expr + n:number + x:my-var + (my-let (b:binding-pair ...) body:my-expr) + #:binding (parallel b body)) + (nonterminal/nesting binding-pair (nested) + [x:my-var e:my-expr] + #:binding (scope (bind x) nested))) + ] +} @item{ @racket[(import d)] imports the bindings exported from the sub-expression specified by @racket[d]. @racket[import] must be used inside of a @racket[scope] and must refer to a syntax spec associated with an @tech{exporting nonterminal}.