From d64d2c1901bc0e369008511e3abc40f089450830 Mon Sep 17 00:00:00 2001 From: Suzanne Soy <ligo@suzanne.soy> Date: Wed, 3 Mar 2021 03:21:48 +0000 Subject: [PATCH] Upgrade for Racket 7.4 --- 7-4/0001-require-paths.patch | 650 ++++++ ...ax-e-and-template-metafunction-stuff.patch | 197 ++ 7-4/base_commit.txt | 1 + .../racket/private/stxcase-scheme.rkt | 75 + .../collects/racket/private/stxcase.rkt | 390 ++++ 7-4/racket/collects/racket/private/stxloc.rkt | 59 + .../collects/racket/private/template.rkt | 705 ++++++ .../collects/racket/private/with-stx.rkt | 99 + 7-4/racket/collects/racket/syntax.rkt | 216 ++ 7-4/racket/collects/syntax/parse.rkt | 33 + 7-4/racket/collects/syntax/parse/debug.rkt | 129 ++ 7-4/racket/collects/syntax/parse/define.rkt | 20 + .../syntax/parse/experimental/contract.rkt | 46 + .../collects/syntax/parse/experimental/eh.rkt | 5 + .../parse/experimental/private/substitute.rkt | 1 + .../syntax/parse/experimental/provide.rkt | 156 ++ .../syntax/parse/experimental/reflect.rkt | 147 ++ .../syntax/parse/experimental/specialize.rkt | 40 + .../syntax/parse/experimental/splicing.rkt | 95 + .../syntax/parse/experimental/template.rkt | 49 + .../syntax/parse/lib/function-header.rkt | 112 + 7-4/racket/collects/syntax/parse/pre.rkt | 49 + .../collects/syntax/parse/private/3d-stx.rkt | 250 +++ .../collects/syntax/parse/private/lib.rkt | 96 + .../collects/syntax/parse/private/litconv.rkt | 284 +++ .../collects/syntax/parse/private/make.rkt | 43 + .../collects/syntax/parse/private/opt.rkt | 456 ++++ .../collects/syntax/parse/private/parse.rkt | 1206 ++++++++++ .../collects/syntax/parse/private/rep.rkt | 1965 +++++++++++++++++ .../syntax/parse/private/residual.rkt | 302 +++ .../syntax/parse/private/runtime-progress.rkt | 258 +++ .../syntax/parse/private/runtime-reflect.rkt | 96 + .../syntax/parse/private/runtime-report.rkt | 816 +++++++ .../collects/syntax/parse/private/runtime.rkt | 235 ++ .../collects/syntax/parse/private/sc.rkt | 32 + .../collects/syntax/parse/private/txlift.rkt | 45 + 7-4/stxparse-info.scrbl | 357 +++ info.rkt | 4 +- my-include.rkt | 6 +- 39 files changed, 9721 insertions(+), 4 deletions(-) create mode 100644 7-4/0001-require-paths.patch create mode 100644 7-4/0002-auto-syntax-e-and-template-metafunction-stuff.patch create mode 100644 7-4/base_commit.txt create mode 100644 7-4/racket/collects/racket/private/stxcase-scheme.rkt create mode 100644 7-4/racket/collects/racket/private/stxcase.rkt create mode 100644 7-4/racket/collects/racket/private/stxloc.rkt create mode 100644 7-4/racket/collects/racket/private/template.rkt create mode 100644 7-4/racket/collects/racket/private/with-stx.rkt create mode 100644 7-4/racket/collects/racket/syntax.rkt create mode 100644 7-4/racket/collects/syntax/parse.rkt create mode 100644 7-4/racket/collects/syntax/parse/debug.rkt create mode 100644 7-4/racket/collects/syntax/parse/define.rkt create mode 100644 7-4/racket/collects/syntax/parse/experimental/contract.rkt create mode 100644 7-4/racket/collects/syntax/parse/experimental/eh.rkt create mode 100644 7-4/racket/collects/syntax/parse/experimental/private/substitute.rkt create mode 100644 7-4/racket/collects/syntax/parse/experimental/provide.rkt create mode 100644 7-4/racket/collects/syntax/parse/experimental/reflect.rkt create mode 100644 7-4/racket/collects/syntax/parse/experimental/specialize.rkt create mode 100644 7-4/racket/collects/syntax/parse/experimental/splicing.rkt create mode 100644 7-4/racket/collects/syntax/parse/experimental/template.rkt create mode 100644 7-4/racket/collects/syntax/parse/lib/function-header.rkt create mode 100644 7-4/racket/collects/syntax/parse/pre.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/3d-stx.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/lib.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/litconv.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/make.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/opt.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/parse.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/rep.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/residual.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/runtime-progress.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/runtime-reflect.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/runtime-report.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/runtime.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/sc.rkt create mode 100644 7-4/racket/collects/syntax/parse/private/txlift.rkt create mode 100644 7-4/stxparse-info.scrbl diff --git a/7-4/0001-require-paths.patch b/7-4/0001-require-paths.patch new file mode 100644 index 0000000..6b8be51 --- /dev/null +++ b/7-4/0001-require-paths.patch @@ -0,0 +1,650 @@ +From f40af8e9288f83b19c8b358981e2a8c7c42874ef Mon Sep 17 00:00:00 2001 +From: Suzanne Soy <ligo@suzanne.soy> +Date: Tue, 2 Mar 2021 21:19:51 +0000 +Subject: [PATCH 1/2] require paths + +--- + .../racket/private/stxcase-scheme.rkt | 7 +-- + racket/collects/racket/private/stxcase.rkt | 12 ++--- + racket/collects/racket/private/stxloc.rkt | 4 +- + racket/collects/racket/private/template.rkt | 49 +++++++++++-------- + racket/collects/racket/private/with-stx.rkt | 6 +-- + racket/collects/racket/syntax.rkt | 3 +- + racket/collects/syntax/parse.rkt | 3 +- + racket/collects/syntax/parse/debug.rkt | 8 +-- + racket/collects/syntax/parse/define.rkt | 4 +- + .../syntax/parse/experimental/contract.rkt | 4 +- + .../collects/syntax/parse/experimental/eh.rkt | 2 +- + .../parse/experimental/private/substitute.rkt | 1 + + .../syntax/parse/experimental/provide.rkt | 6 +-- + .../syntax/parse/experimental/reflect.rkt | 6 +-- + .../syntax/parse/experimental/specialize.rkt | 4 +- + .../syntax/parse/experimental/splicing.rkt | 6 +-- + racket/collects/syntax/parse/private/lib.rkt | 2 +- + .../collects/syntax/parse/private/litconv.rkt | 10 ++-- + racket/collects/syntax/parse/private/opt.rkt | 6 +-- + .../collects/syntax/parse/private/parse.rkt | 18 +++---- + racket/collects/syntax/parse/private/rep.rkt | 14 +++--- + .../syntax/parse/private/residual.rkt | 6 +-- + .../syntax/parse/private/runtime-progress.rkt | 2 +- + .../syntax/parse/private/runtime-reflect.rkt | 30 +++++++++--- + .../syntax/parse/private/runtime-report.rkt | 6 +-- + .../collects/syntax/parse/private/runtime.rkt | 4 +- + racket/collects/syntax/parse/private/sc.rkt | 4 +- + 27 files changed, 128 insertions(+), 99 deletions(-) + create mode 100644 racket/collects/syntax/parse/experimental/private/substitute.rkt + +diff --git racket/collects/racket/private/stxcase-scheme.rkt racket/collects/racket/private/stxcase-scheme.rkt +index 9f1a21abbb..46bddba4e5 100644 +--- racket/collects/racket/private/stxcase-scheme.rkt ++++ racket/collects/racket/private/stxcase-scheme.rkt +@@ -4,8 +4,8 @@ + ;; check-duplicate-identifier, and assembles everything we have so far + + (module stxcase-scheme '#%kernel +- (#%require "small-scheme.rkt" "stx.rkt" "stxcase.rkt" "with-stx.rkt" "stxloc.rkt" +- (for-syntax '#%kernel "small-scheme.rkt" "stx.rkt" "stxcase.rkt" ++ (#%require racket/private/small-scheme racket/private/stx "stxcase.rkt" "with-stx.rkt" "stxloc.rkt" ++ (for-syntax '#%kernel racket/private/small-scheme racket/private/stx "stxcase.rkt" + "stxloc.rkt")) + + (-define (check-duplicate-identifier names) +@@ -68,7 +68,8 @@ + (syntax-arm stx #f #t) + (raise-argument-error 'syntax-protect "syntax?" stx))) + +- (#%provide syntax datum (all-from "with-stx.rkt") (all-from "stxloc.rkt") ++ (#%provide syntax datum (all-from "with-stx.rkt") ++ (all-from "stxloc.rkt") + check-duplicate-identifier syntax-protect + syntax-rules syntax-id-rules + (for-syntax syntax-pattern-variable?))) +diff --git racket/collects/racket/private/stxcase.rkt racket/collects/racket/private/stxcase.rkt +index 69832a27a1..ca8f8f631b 100644 +--- racket/collects/racket/private/stxcase.rkt ++++ racket/collects/racket/private/stxcase.rkt +@@ -2,10 +2,10 @@ + ;; syntax-case and syntax + + (module stxcase '#%kernel +- (#%require "stx.rkt" "small-scheme.rkt" '#%paramz '#%unsafe +- "ellipses.rkt" +- (for-syntax "stx.rkt" "small-scheme.rkt" +- "gen-temp.rkt" "member.rkt" "sc.rkt" '#%kernel)) ++ (#%require racket/private/stx racket/private/small-scheme '#%paramz '#%unsafe ++ racket/private/ellipses ++ (for-syntax racket/private/stx racket/private/small-scheme ++ racket/private/gen-temp racket/private/member racket/private/sc '#%kernel)) + + (-define interp-match + (lambda (pat e literals immediate=?) +@@ -379,6 +379,6 @@ + m))))]))) + x))))))) + +- (#%require "template.rkt") +- (#%provide (all-from "ellipses.rkt") syntax-case** syntax syntax/loc datum ++ (#%require racket/private/template) ++ (#%provide (all-from racket/private/ellipses) syntax-case** syntax syntax/loc datum + (for-syntax syntax-pattern-variable?))) +diff --git racket/collects/racket/private/stxloc.rkt racket/collects/racket/private/stxloc.rkt +index c9e0d54324..e26417c7bc 100644 +--- racket/collects/racket/private/stxloc.rkt ++++ racket/collects/racket/private/stxloc.rkt +@@ -3,8 +3,8 @@ + ;; syntax/loc + + (module stxloc '#%kernel +- (#%require "qq-and-or.rkt" "stxcase.rkt" "define-et-al.rkt" +- (for-syntax '#%kernel "stxcase.rkt" "sc.rkt")) ++ (#%require racket/private/qq-and-or "stxcase.rkt" racket/private/define-et-al ++ (for-syntax '#%kernel "stxcase.rkt" racket/private/sc)) + + (begin-for-syntax + (define-values (transform-to-syntax-case**) +diff --git racket/collects/racket/private/template.rkt racket/collects/racket/private/template.rkt +index 9037109f38..06dfba3033 100644 +--- racket/collects/racket/private/template.rkt ++++ racket/collects/racket/private/template.rkt +@@ -1,25 +1,25 @@ + (module template '#%kernel +-(#%require "stx.rkt" "small-scheme.rkt" "performance-hint.rkt" +- (rename "small-scheme.rkt" define -define) +- (rename "small-scheme.rkt" define-syntax -define-syntax) +- "ellipses.rkt" +- (for-syntax "stx.rkt" "small-scheme.rkt" +- (rename "small-scheme.rkt" define -define) +- (rename "small-scheme.rkt" define-syntax -define-syntax) +- "member.rkt" "sc.rkt" '#%kernel)) ++(#%require racket/private/stx racket/private/small-scheme racket/private/performance-hint ++ (rename racket/private/small-scheme define -define) ++ (rename racket/private/small-scheme define-syntax -define-syntax) ++ racket/private/ellipses ++ (for-syntax racket/private/stx racket/private/small-scheme ++ (rename racket/private/small-scheme define -define) ++ (rename racket/private/small-scheme define-syntax -define-syntax) ++ racket/private/member racket/private/sc '#%kernel)) + (#%provide syntax + syntax/loc + datum + ~? ~@ + ~@! signal-absent-pvar +- (protect +- (for-syntax attribute-mapping +- attribute-mapping? +- attribute-mapping-name +- attribute-mapping-var +- attribute-mapping-depth +- attribute-mapping-check +- metafunction metafunction?))) ++ (for-syntax attribute-mapping ++ attribute-mapping? ++ attribute-mapping-name ++ attribute-mapping-var ++ attribute-mapping-depth ++ attribute-mapping-check ++ (protect metafunction ++ metafunction?))) + + ;; ============================================================ + ;; Syntax of templates +@@ -87,7 +87,14 @@ + ;; (struct attribute-mapping (var name depth check) ...) + ;; check : #f (trusted) or Id, ref to Checker + ;; Checker = ( Any d:Nat b:Boolean Syntax/#f -> (Listof^d (if b Syntax Any)) ) +- (define-values (struct:attribute-mapping attribute-mapping attribute-mapping? ++ (#%require (for-template (only racket/private/template ++ attribute-mapping ++ attribute-mapping? ++ attribute-mapping-var ++ attribute-mapping-name ++ attribute-mapping-depth ++ attribute-mapping-check))) ++ #;(define-values (struct:attribute-mapping attribute-mapping attribute-mapping? + attribute-mapping-ref _attribute-mapping-set!) + (make-struct-type 'attribute-mapping #f 4 0 #f null (current-inspector) + (lambda (self stx) +@@ -106,10 +113,10 @@ + (quote-syntax ,source-name))) + (datum->syntax here-stx code stx)) + (attribute-mapping-var self))))) +- (define (attribute-mapping-var a) (attribute-mapping-ref a 0)) +- (define (attribute-mapping-name a) (attribute-mapping-ref a 1)) +- (define (attribute-mapping-depth a) (attribute-mapping-ref a 2)) +- (define (attribute-mapping-check a) (attribute-mapping-ref a 3)) ++ #;(define (attribute-mapping-var a) (attribute-mapping-ref a 0)) ++ #;(define (attribute-mapping-name a) (attribute-mapping-ref a 1)) ++ #;(define (attribute-mapping-depth a) (attribute-mapping-ref a 2)) ++ #;(define (attribute-mapping-check a) (attribute-mapping-ref a 3)) + + ;; (struct metafunction (var)) + (define-values (struct:metafunction metafunction metafunction? metafunction-ref _mf-set!) +diff --git racket/collects/racket/private/with-stx.rkt racket/collects/racket/private/with-stx.rkt +index b10fc4ec7c..1e22edcd99 100644 +--- racket/collects/racket/private/with-stx.rkt ++++ racket/collects/racket/private/with-stx.rkt +@@ -2,9 +2,9 @@ + ;; with-syntax, generate-temporaries + + (module with-stx '#%kernel +- (#%require "stx.rkt" "small-scheme.rkt" "stxcase.rkt" +- (for-syntax '#%kernel "stx.rkt" "stxcase.rkt" "stxloc.rkt" +- "gen-temp.rkt" "sc.rkt" "qq-and-or.rkt" "cond.rkt")) ++ (#%require racket/private/stx racket/private/small-scheme "stxcase.rkt" ++ (for-syntax '#%kernel racket/private/stx "stxcase.rkt" "stxloc.rkt" ++ racket/private/gen-temp racket/private/sc racket/private/qq-and-or racket/private/cond)) + + (-define (with-syntax-fail stx) + (raise-syntax-error +diff --git racket/collects/racket/syntax.rkt racket/collects/racket/syntax.rkt +index ce0a72d1fc..428c195971 100644 +--- racket/collects/racket/syntax.rkt ++++ racket/collects/racket/syntax.rkt +@@ -1,5 +1,6 @@ + #lang racket/base +-(require (for-syntax racket/base ++(require (only-in "stxloc.rkt" syntax-case) ++ (for-syntax racket/base + racket/private/sc)) + (provide define/with-syntax + +diff --git racket/collects/syntax/parse.rkt racket/collects/syntax/parse.rkt +index 6e81621a07..643de4ddda 100644 +--- racket/collects/syntax/parse.rkt ++++ racket/collects/syntax/parse.rkt +@@ -24,7 +24,8 @@ + [syntax-local-syntax-parse-pattern-introduce + (-> syntax? syntax?)])) + +- (define pattern-expander ++ (require (only-in (for-template syntax/parse) pattern-expander)) ++ #;(define pattern-expander + (let () + (struct pattern-expander (proc) #:transparent + #:omit-define-syntaxes +diff --git racket/collects/syntax/parse/debug.rkt racket/collects/syntax/parse/debug.rkt +index a092ab6263..c62849b212 100644 +--- racket/collects/syntax/parse/debug.rkt ++++ racket/collects/syntax/parse/debug.rkt +@@ -2,20 +2,20 @@ + (require (for-syntax racket/base + syntax/stx + racket/syntax +- "private/rep-data.rkt" ++ syntax/parse/private/rep-data + "private/rep.rkt" +- "private/kws.rkt") ++ syntax/parse/private/kws) + racket/list + racket/pretty + "../parse.rkt" +- (except-in syntax/parse/private/residual ++ (except-in stxparse-info/parse/private/residual + prop:syntax-class + prop:pattern-expander + syntax-local-syntax-parse-pattern-introduce) + "private/runtime.rkt" + "private/runtime-progress.rkt" + "private/runtime-report.rkt" +- "private/kws.rkt") ++ syntax/parse/private/kws) + + ;; No lazy loading for this module's dependencies. + +diff --git racket/collects/syntax/parse/define.rkt racket/collects/syntax/parse/define.rkt +index 80af669b22..6392c75adb 100644 +--- racket/collects/syntax/parse/define.rkt ++++ racket/collects/syntax/parse/define.rkt +@@ -1,10 +1,10 @@ + #lang racket/base + (require (for-syntax racket/base +- syntax/parse ++ stxparse-info/parse + "private/sc.rkt")) + (provide define-simple-macro + define-syntax-parser +- (for-syntax (all-from-out syntax/parse))) ++ (for-syntax (all-from-out stxparse-info/parse))) + + (define-syntax (define-simple-macro stx) + (syntax-parse stx +diff --git racket/collects/syntax/parse/experimental/contract.rkt racket/collects/syntax/parse/experimental/contract.rkt +index 022e590958..b0f932b3ff 100644 +--- racket/collects/syntax/parse/experimental/contract.rkt ++++ racket/collects/syntax/parse/experimental/contract.rkt +@@ -1,8 +1,8 @@ + #lang racket/base +-(require syntax/parse/pre ++(require stxparse-info/parse/pre + "provide.rkt" + syntax/contract +- (only-in syntax/parse/private/residual ;; keep abs. path ++ (only-in stxparse-info/parse/private/residual ;; keep abs. path + this-context-syntax + this-role) + racket/contract/base) +diff --git racket/collects/syntax/parse/experimental/eh.rkt racket/collects/syntax/parse/experimental/eh.rkt +index 305080721f..f8e1b09302 100644 +--- racket/collects/syntax/parse/experimental/eh.rkt ++++ racket/collects/syntax/parse/experimental/eh.rkt +@@ -1,5 +1,5 @@ + #lang racket/base + (require "../private/sc.rkt" +- "../private/keywords.rkt") ++ syntax/parse/private/keywords) + (provide ~eh-var + define-eh-alternative-set) +diff --git racket/collects/syntax/parse/experimental/private/substitute.rkt racket/collects/syntax/parse/experimental/private/substitute.rkt +new file mode 100644 +index 0000000000..7bc35af1c4 +--- /dev/null ++++ racket/collects/syntax/parse/experimental/private/substitute.rkt +@@ -0,0 +1 @@ ++#lang racket/base +diff --git racket/collects/syntax/parse/experimental/provide.rkt racket/collects/syntax/parse/experimental/provide.rkt +index 86a3b6fd88..173d81e428 100644 +--- racket/collects/syntax/parse/experimental/provide.rkt ++++ racket/collects/syntax/parse/experimental/provide.rkt +@@ -4,10 +4,10 @@ + syntax/location + (for-syntax racket/base + racket/syntax +- "../private/minimatch.rkt" +- syntax/parse/pre ++ syntax/parse/private/minimatch ++ stxparse-info/parse/pre + syntax/parse/private/residual-ct ;; keep abs. path +- "../private/kws.rkt" ++ syntax/parse/private/kws + syntax/contract)) + (provide provide-syntax-class/contract + syntax-class/c +diff --git racket/collects/syntax/parse/experimental/reflect.rkt racket/collects/syntax/parse/experimental/reflect.rkt +index 7e4daa3e5a..8f18781e9a 100644 +--- racket/collects/syntax/parse/experimental/reflect.rkt ++++ racket/collects/syntax/parse/experimental/reflect.rkt +@@ -5,10 +5,10 @@ + syntax/parse/private/residual-ct) ;; keep abs.path + racket/contract/base + racket/contract/combinator +- "../private/minimatch.rkt" +- "../private/keywords.rkt" ++ syntax/parse/private/minimatch ++ syntax/parse/private/keywords + "../private/runtime-reflect.rkt" +- "../private/kws.rkt") ++ syntax/parse/private/kws) + (begin-for-syntax + (lazy-require + [syntax/parse/private/rep-data ;; keep abs. path +diff --git racket/collects/syntax/parse/experimental/specialize.rkt racket/collects/syntax/parse/experimental/specialize.rkt +index e32b077825..ad569c1cb0 100644 +--- racket/collects/syntax/parse/experimental/specialize.rkt ++++ racket/collects/syntax/parse/experimental/specialize.rkt +@@ -1,8 +1,8 @@ + #lang racket/base + (require (for-syntax racket/base + racket/syntax +- "../private/kws.rkt" +- "../private/rep-data.rkt" ++ syntax/parse/private/kws ++ syntax/parse/private/rep-data + "../private/rep.rkt") + "../private/runtime.rkt") + (provide define-syntax-class/specialize) +diff --git racket/collects/syntax/parse/experimental/splicing.rkt racket/collects/syntax/parse/experimental/splicing.rkt +index 510b2a7361..56abbd5cd2 100644 +--- racket/collects/syntax/parse/experimental/splicing.rkt ++++ racket/collects/syntax/parse/experimental/splicing.rkt +@@ -1,9 +1,9 @@ + #lang racket/base + (require (for-syntax racket/base +- syntax/parse ++ stxparse-info/parse + racket/lazy-require +- "../private/kws.rkt") +- syntax/parse/private/residual) ;; keep abs. path ++ syntax/parse/private/kws) ++ stxparse-info/parse/private/residual) ;; keep abs. path + (provide define-primitive-splicing-syntax-class) + + (begin-for-syntax +diff --git racket/collects/syntax/parse/private/lib.rkt racket/collects/syntax/parse/private/lib.rkt +index 5eaa74c946..01e110c45b 100644 +--- racket/collects/syntax/parse/private/lib.rkt ++++ racket/collects/syntax/parse/private/lib.rkt +@@ -1,6 +1,6 @@ + #lang racket/base + (require "sc.rkt" +- "keywords.rkt" ++ syntax/parse/private/keywords + (only-in "residual.rkt" state-cons!) + (for-syntax syntax/parse/private/residual-ct) + (for-syntax racket/base)) +diff --git racket/collects/syntax/parse/private/litconv.rkt racket/collects/syntax/parse/private/litconv.rkt +index 772930c301..9e25611d96 100644 +--- racket/collects/syntax/parse/private/litconv.rkt ++++ racket/collects/syntax/parse/private/litconv.rkt +@@ -3,14 +3,14 @@ + racket/lazy-require + "sc.rkt" + "lib.rkt" +- "kws.rkt" ++ syntax/parse/private/kws + racket/syntax) + syntax/parse/private/residual-ct ;; keep abs. path +- syntax/parse/private/residual) ;; keep abs. path ++ stxparse-info/parse/private/residual) ;; keep abs. path + (begin-for-syntax + (lazy-require + [syntax/private/keyword (options-select-value parse-keyword-options)] +- [syntax/parse/private/rep ;; keep abs. path ++ [stxparse-info/parse/private/rep ;; keep abs. path + (parse-kw-formals + check-conventions-rules + check-datum-literals-list +@@ -18,7 +18,7 @@ + ;; FIXME: workaround for phase>0 bug in racket/runtime-path (and thus lazy-require) + ;; Without this, dependencies don't get collected. + (require racket/runtime-path racket/syntax (for-meta 2 '#%kernel)) +-(define-runtime-module-path-index _unused_ 'syntax/parse/private/rep) ++(define-runtime-module-path-index _unused_ 'stxparse-info/parse/private/rep) + + (provide define-conventions + define-literal-set +@@ -215,7 +215,7 @@ change between when define-literal-set is compiled and the comparison + involving L. For example: + + (module M racket +- (require syntax/parse) ++ (require stxparse-info/parse) + (define-literal-set LS (lambda)) + (require (only-in some-other-lang lambda)) + .... LS ....) +diff --git racket/collects/syntax/parse/private/opt.rkt racket/collects/syntax/parse/private/opt.rkt +index 0e7af8e5a8..12714d6be1 100644 +--- racket/collects/syntax/parse/private/opt.rkt ++++ racket/collects/syntax/parse/private/opt.rkt +@@ -2,9 +2,9 @@ + (require racket/syntax + racket/pretty + syntax/parse/private/residual-ct ;; keep abs. path +- "minimatch.rkt" +- "rep-patterns.rkt" +- "kws.rkt") ++ syntax/parse/private/minimatch ++ syntax/parse/private/rep-patterns ++ syntax/parse/private/kws) + (provide (struct-out pk1) + (rename-out [optimize-matrix0 optimize-matrix])) + +diff --git racket/collects/syntax/parse/private/parse.rkt racket/collects/syntax/parse/private/parse.rkt +index 609d0a48b4..e14cc3aea9 100644 +--- racket/collects/syntax/parse/private/parse.rkt ++++ racket/collects/syntax/parse/private/parse.rkt +@@ -4,22 +4,22 @@ + syntax/private/id-table + syntax/keyword + racket/syntax +- "minimatch.rkt" +- "datum-to-expr.rkt" +- "rep-attrs.rkt" +- "rep-data.rkt" +- "rep-patterns.rkt" ++ syntax/parse/private/minimatch ++ syntax/parse/private/datum-to-expr ++ syntax/parse/private/rep-attrs ++ syntax/parse/private/rep-data ++ syntax/parse/private/rep-patterns + "rep.rkt" +- "kws.rkt" ++ syntax/parse/private/kws + "opt.rkt" + "txlift.rkt") +- "keywords.rkt" ++ syntax/parse/private/keywords + racket/syntax + racket/stxparam + syntax/stx +- syntax/parse/private/residual ;; keep abs. path ++ stxparse-info/parse/private/residual ;; keep abs. path + "runtime.rkt" +- syntax/parse/private/runtime-reflect) ;; keep abs. path ++ stxparse-info/parse/private/runtime-reflect) ;; keep abs. path + + ;; ============================================================ + +diff --git racket/collects/syntax/parse/private/rep.rkt racket/collects/syntax/parse/private/rep.rkt +index 1264a0398f..be08bc36c1 100644 +--- racket/collects/syntax/parse/private/rep.rkt ++++ racket/collects/syntax/parse/private/rep.rkt +@@ -1,12 +1,12 @@ + #lang racket/base + (require (for-template racket/base + syntax/parse/private/keywords +- syntax/parse/private/residual ;; keep abs. path +- syntax/parse/private/runtime) ++ stxparse-info/parse/private/residual ;; keep abs. path ++ stxparse-info/parse/private/runtime) + racket/list + racket/contract/base + "make.rkt" +- "minimatch.rkt" ++ syntax/parse/private/minimatch + syntax/apply-transformer + syntax/private/id-table + syntax/stx +@@ -14,11 +14,11 @@ + racket/syntax + racket/struct + "txlift.rkt" +- "rep-attrs.rkt" +- "rep-data.rkt" +- "rep-patterns.rkt" ++ syntax/parse/private/rep-attrs ++ syntax/parse/private/rep-data ++ syntax/parse/private/rep-patterns + syntax/parse/private/residual-ct ;; keep abs. path +- "kws.rkt") ++ syntax/parse/private/kws) + + ;; Error reporting + ;; All entry points should have explicit, mandatory #:context arg +diff --git racket/collects/syntax/parse/private/residual.rkt racket/collects/syntax/parse/private/residual.rkt +index 3f9416c950..528885697b 100644 +--- racket/collects/syntax/parse/private/residual.rkt ++++ racket/collects/syntax/parse/private/residual.rkt +@@ -7,10 +7,10 @@ + ;; ============================================================ + ;; Compile-time + +-(require (for-syntax racket/private/sc "residual-ct.rkt")) +-(provide (for-syntax (all-from-out "residual-ct.rkt"))) ++(require (for-syntax racket/private/sc syntax/parse/private/residual-ct)) ++(provide (for-syntax (all-from-out syntax/parse/private/residual-ct))) + +-(require racket/private/template) ++(require "../../case/template.rkt") + (provide (for-syntax attribute-mapping attribute-mapping?)) + + ;; ============================================================ +diff --git racket/collects/syntax/parse/private/runtime-progress.rkt racket/collects/syntax/parse/private/runtime-progress.rkt +index 4065357c76..ad894d2c70 100644 +--- racket/collects/syntax/parse/private/runtime-progress.rkt ++++ racket/collects/syntax/parse/private/runtime-progress.rkt +@@ -1,6 +1,6 @@ + #lang racket/base + (require racket/list +- "minimatch.rkt") ++ syntax/parse/private/minimatch) + (provide ps-empty + ps-add-car + ps-add-cdr +diff --git racket/collects/syntax/parse/private/runtime-reflect.rkt racket/collects/syntax/parse/private/runtime-reflect.rkt +index e3bc47a4c5..59125cf0ac 100644 +--- racket/collects/syntax/parse/private/runtime-reflect.rkt ++++ racket/collects/syntax/parse/private/runtime-reflect.rkt +@@ -1,7 +1,7 @@ + #lang racket/base + (require "residual.rkt" +- (only-in "residual-ct.rkt" attr-name attr-depth) +- "kws.rkt") ++ (only-in syntax/parse/private/residual-ct attr-name attr-depth) ++ syntax/parse/private/kws) + (provide reflect-parser + (struct-out reified) + (struct-out reified-syntax-class) +@@ -11,10 +11,28 @@ + A Reified is + (reified symbol ParserFunction nat (listof (list symbol nat))) + |# +-(define-struct reified-base (name) #:transparent) +-(define-struct (reified reified-base) (parser arity signature)) +-(define-struct (reified-syntax-class reified) ()) +-(define-struct (reified-splicing-syntax-class reified) ()) ++(require (only-in syntax/parse/private/runtime-reflect ++ reified ++ reified? ++ reified-parser ++ reified-arity ++ reified-signature ++ make-reified ++ struct:reified ++ ++ reified-syntax-class ++ reified-syntax-class? ++ make-reified-syntax-class ++ struct:reified-syntax-class ++ ++ reified-splicing-syntax-class ++ reified-splicing-syntax-class? ++ make-reified-splicing-syntax-class ++ struct:reified-splicing-syntax-class)) ++#;(define-struct reified-base (name) #:transparent) ++#;(define-struct (reified reified-base) (parser arity signature)) ++#;(define-struct (reified-syntax-class reified) ()) ++#;(define-struct (reified-splicing-syntax-class reified) ()) + + (define (reflect-parser obj e-arity e-attrs splicing?) + ;; e-arity represents single call; min and max are same +diff --git racket/collects/syntax/parse/private/runtime-report.rkt racket/collects/syntax/parse/private/runtime-report.rkt +index 59733c4cd2..a1d09e11ad 100644 +--- racket/collects/syntax/parse/private/runtime-report.rkt ++++ racket/collects/syntax/parse/private/runtime-report.rkt +@@ -4,9 +4,9 @@ + syntax/stx + racket/struct + syntax/srcloc +- "minimatch.rkt" +- syntax/parse/private/residual +- "kws.rkt") ++ syntax/parse/private/minimatch ++ stxparse-info/parse/private/residual ++ syntax/parse/private/kws) + (provide call-current-failure-handler + current-failure-handler + invert-failure +diff --git racket/collects/syntax/parse/private/runtime.rkt racket/collects/syntax/parse/private/runtime.rkt +index d100d2c564..41b321499e 100644 +--- racket/collects/syntax/parse/private/runtime.rkt ++++ racket/collects/syntax/parse/private/runtime.rkt +@@ -1,13 +1,13 @@ + #lang racket/base + (require racket/stxparam +- syntax/parse/private/residual ;; keep abs. path ++ stxparse-info/parse/private/residual ;; keep abs. path + (for-syntax racket/base + racket/list + syntax/kerncase + syntax/strip-context + racket/private/sc + racket/syntax +- "rep-data.rkt")) ++ syntax/parse/private/rep-data)) + + (provide with + fail-handler +diff --git racket/collects/syntax/parse/private/sc.rkt racket/collects/syntax/parse/private/sc.rkt +index 27b069059d..d4e5ff695a 100644 +--- racket/collects/syntax/parse/private/sc.rkt ++++ racket/collects/syntax/parse/private/sc.rkt +@@ -1,6 +1,6 @@ + #lang racket/base + (require racket/lazy-require +- "keywords.rkt" ++ syntax/parse/private/keywords + "residual.rkt") + + (lazy-require-syntax +@@ -21,7 +21,7 @@ + syntax-parser + define/syntax-parse + +- (except-out (all-from-out "keywords.rkt") ++ (except-out (all-from-out syntax/parse/private/keywords) + ~reflect + ~splicing-reflect + ~eh-var) +-- +2.30.0 + diff --git a/7-4/0002-auto-syntax-e-and-template-metafunction-stuff.patch b/7-4/0002-auto-syntax-e-and-template-metafunction-stuff.patch new file mode 100644 index 0000000..2c33d5f --- /dev/null +++ b/7-4/0002-auto-syntax-e-and-template-metafunction-stuff.patch @@ -0,0 +1,197 @@ +From 544b1d8c15e32f5ce722d69d3f9ec51b73430b5a Mon Sep 17 00:00:00 2001 +From: Suzanne Soy <ligo@suzanne.soy> +Date: Tue, 2 Mar 2021 21:20:46 +0000 +Subject: [PATCH 2/2] auto-syntax-e and template-metafunction stuff + +--- + racket/collects/racket/private/stxcase.rkt | 14 ++++++++++---- + racket/collects/racket/syntax.rkt | 9 ++++++--- + .../syntax/parse/experimental/template.rkt | 17 +++++++++++++++-- + racket/collects/syntax/parse/private/parse.rkt | 7 ++++++- + .../collects/syntax/parse/private/residual.rkt | 4 +++- + .../collects/syntax/parse/private/runtime.rkt | 12 ++++++++---- + 6 files changed, 48 insertions(+), 15 deletions(-) + +diff --git racket/collects/racket/private/stxcase.rkt racket/collects/racket/private/stxcase.rkt +index ca8f8f631b..cded343601 100644 +--- racket/collects/racket/private/stxcase.rkt ++++ racket/collects/racket/private/stxcase.rkt +@@ -4,8 +4,10 @@ + (module stxcase '#%kernel + (#%require racket/private/stx racket/private/small-scheme '#%paramz '#%unsafe + racket/private/ellipses ++ stxparse-info/current-pvars + (for-syntax racket/private/stx racket/private/small-scheme +- racket/private/gen-temp racket/private/member racket/private/sc '#%kernel)) ++ racket/private/gen-temp racket/private/member racket/private/sc '#%kernel ++ auto-syntax-e/utils)) + + (-define interp-match + (lambda (pat e literals immediate=?) +@@ -346,7 +348,7 @@ + (list + (if s-exp? + (quote-syntax make-s-exp-mapping) +- (quote-syntax make-syntax-mapping)) ++ (quote-syntax make-auto-pvar)) + ;; Tell it the shape of the variable: + (let loop ([var unflat-pattern-var][d 0]) + (if (syntax? var) +@@ -361,9 +363,13 @@ + null + (if fender + (list (quote-syntax if) fender +- answer ++ (list (quote-syntax with-pvars) ++ pattern-vars ++ answer) + do-try-next) +- answer))) ++ (list (quote-syntax with-pvars) ++ pattern-vars ++ answer)))) + do-try-next))]) + (if fender + (list +diff --git racket/collects/racket/syntax.rkt racket/collects/racket/syntax.rkt +index 428c195971..85c41d155f 100644 +--- racket/collects/racket/syntax.rkt ++++ racket/collects/racket/syntax.rkt +@@ -1,7 +1,9 @@ + #lang racket/base + (require (only-in "stxloc.rkt" syntax-case) ++ stxparse-info/current-pvars + (for-syntax racket/base +- racket/private/sc)) ++ racket/private/sc ++ auto-syntax-e/utils)) + (provide define/with-syntax + + current-recorded-disappeared-uses +@@ -44,8 +46,9 @@ + (with-syntax ([pattern rhs]) + (values (pvar-value pvar) ...))) + (define-syntax pvar +- (make-syntax-mapping 'depth (quote-syntax valvar))) +- ...)))])) ++ (make-auto-pvar 'depth (quote-syntax valvar))) ++ ... ++ (define-pvars pvar ...))))])) + ;; Ryan: alternative name: define/syntax-pattern ?? + + ;; auxiliary macro +diff --git racket/collects/syntax/parse/experimental/template.rkt racket/collects/syntax/parse/experimental/template.rkt +index b52fd80e6e..160eccc84b 100644 +--- racket/collects/syntax/parse/experimental/template.rkt ++++ racket/collects/syntax/parse/experimental/template.rkt +@@ -1,5 +1,6 @@ + #lang racket/base +-(require (for-syntax racket/base) ++(require (for-syntax racket/base ++ auto-syntax-e/utils) + (only-in racket/private/template + metafunction)) + (provide (rename-out [syntax template] +@@ -26,10 +27,22 @@ + (define current-template-metafunction-introducer + (make-parameter (lambda (stx) (if (syntax-transforming?) (syntax-local-introduce stx) stx)))) + ++(define old-template-metafunction-introducer ++ (make-parameter #f)) ++ ++(define (syntax-local-template-metafunction-introduce stx) ++ (let ([mark (current-template-metafunction-introducer)] ++ [old-mark (old-template-metafunction-introducer)]) ++ (unless old-mark ++ (error 'syntax-local-template-metafunction-introduce ++ "must be called within the dynamic extent of a template metafunction")) ++ (mark (old-mark stx)))) ++ + (define ((make-hygienic-metafunction transformer) stx) + (define mark (make-syntax-introducer)) + (define old-mark (current-template-metafunction-introducer)) +- (parameterize ((current-template-metafunction-introducer mark)) ++ (parameterize ((current-template-metafunction-introducer mark) ++ (old-template-metafunction-introducer old-mark)) + (define r (call-with-continuation-barrier (lambda () (transformer (mark (old-mark stx)))))) + (unless (syntax? r) + (raise-syntax-error #f "result of template metafunction was not syntax" stx)) +diff --git racket/collects/syntax/parse/private/parse.rkt racket/collects/syntax/parse/private/parse.rkt +index e14cc3aea9..7e5c61dee1 100644 +--- racket/collects/syntax/parse/private/parse.rkt ++++ racket/collects/syntax/parse/private/parse.rkt +@@ -435,7 +435,12 @@ Conventions: + ((body-sequence) + (syntax-case rest () + [(e0 e ...) +- #'(let () e0 e ...)] ++ ;; Should we use a shadower (works on the whole file, unhygienically), ++ ;; or use the context of the syntax-parse identifier? ++ (let ([the-#%intdef-begin (datum->syntax #'ctx '#%intdef-begin)]) ++ (if (syntax-local-value the-#%intdef-begin (λ () #f)) ;; Defined as a macro ++ #`(let () (#,the-#%intdef-begin e0 e ...)) ++ #'(let () e0 e ...)))] + [_ (raise-syntax-error #f "expected non-empty clause body" + #'ctx clause)])) + (else +diff --git racket/collects/syntax/parse/private/residual.rkt racket/collects/syntax/parse/private/residual.rkt +index 528885697b..1624b6394a 100644 +--- racket/collects/syntax/parse/private/residual.rkt ++++ racket/collects/syntax/parse/private/residual.rkt +@@ -18,7 +18,9 @@ + + (require "runtime-progress.rkt" + "3d-stx.rkt" +- syntax/stx) ++ auto-syntax-e ++ syntax/stx ++ stxparse-info/current-pvars) + + (provide (all-from-out "runtime-progress.rkt") + +diff --git racket/collects/syntax/parse/private/runtime.rkt racket/collects/syntax/parse/private/runtime.rkt +index 41b321499e..90d7ea88f4 100644 +--- racket/collects/syntax/parse/private/runtime.rkt ++++ racket/collects/syntax/parse/private/runtime.rkt +@@ -1,11 +1,13 @@ + #lang racket/base + (require racket/stxparam + stxparse-info/parse/private/residual ;; keep abs. path ++ stxparse-info/current-pvars + (for-syntax racket/base + racket/list + syntax/kerncase + syntax/strip-context + racket/private/sc ++ auto-syntax-e/utils + racket/syntax + syntax/parse/private/rep-data)) + +@@ -111,9 +113,10 @@ residual.rkt. + ...) + ([(vtmp) value] ...) + (letrec-syntaxes+values +- ([(name) (make-syntax-mapping 'depth (quote-syntax stmp))] ...) ++ ([(name) (make-auto-pvar 'depth (quote-syntax stmp))] ...) + () +- . body))))])) ++ (with-pvars (name ...) ++ . body)))))])) + + ;; (let-attributes* (([id num] ...) (expr ...)) expr) : expr + ;; Special case: empty attrs need not match number of value exprs. +@@ -147,8 +150,9 @@ residual.rkt. + (attribute-mapping (quote-syntax vtmp) 'name 'depth + (if 'syntax? #f (quote-syntax check-attr-value)))) + ... +- (define-syntax name (make-syntax-mapping 'depth (quote-syntax stmp))) +- ...)))])) ++ (define-syntax name (make-auto-pvar 'depth (quote-syntax stmp))) ++ ... ++ (define-pvars name ...))))])) + + (define-syntax-rule (phase-of-enclosing-module) + (variable-reference->module-base-phase +-- +2.30.0 + diff --git a/7-4/base_commit.txt b/7-4/base_commit.txt new file mode 100644 index 0000000..91d7ffb --- /dev/null +++ b/7-4/base_commit.txt @@ -0,0 +1 @@ +cc06a5e2523ae3049b69d15cd2548eed5a887100 diff --git a/7-4/racket/collects/racket/private/stxcase-scheme.rkt b/7-4/racket/collects/racket/private/stxcase-scheme.rkt new file mode 100644 index 0000000..46bddba --- /dev/null +++ b/7-4/racket/collects/racket/private/stxcase-scheme.rkt @@ -0,0 +1,75 @@ + +;;---------------------------------------------------------------------- +;; #%stxcase-scheme: adds let-syntax, syntax-rules, and +;; check-duplicate-identifier, and assembles everything we have so far + +(module stxcase-scheme '#%kernel + (#%require racket/private/small-scheme racket/private/stx "stxcase.rkt" "with-stx.rkt" "stxloc.rkt" + (for-syntax '#%kernel racket/private/small-scheme racket/private/stx "stxcase.rkt" + "stxloc.rkt")) + + (-define (check-duplicate-identifier names) + (unless (and (list? names) (andmap identifier? names)) + (raise-argument-error 'check-duplicate-identifier "(listof identifier?)" names)) + (let/ec escape + (let ([ht (make-hasheq)]) + (for-each + (lambda (defined-name) + (unless (identifier? defined-name) + (raise-argument-error 'check-duplicate-identifier + "(listof identifier?)" names)) + (let ([l (hash-ref ht (syntax-e defined-name) null)]) + (when (ormap (lambda (i) (bound-identifier=? i defined-name)) l) + (escape defined-name)) + (hash-set! ht (syntax-e defined-name) (cons defined-name l)))) + names) + #f))) + + (begin-for-syntax + (define-values (check-sr-rules) + (lambda (stx kws) + (for-each (lambda (id) + (unless (identifier? id) + (raise-syntax-error + #f + "pattern must start with an identifier, found something else" + stx + id))) + (syntax->list kws))))) + + ;; From Dybvig, mostly: + (-define-syntax syntax-rules + (lambda (stx) + (syntax-case** syntax-rules #t stx () free-identifier=? #f + ((sr (k ...) ((keyword . pattern) template) ...) + (andmap identifier? (syntax->list (syntax (k ...)))) + (begin + (check-sr-rules stx (syntax (keyword ...))) + (syntax/loc stx + (lambda (x) + (syntax-case** sr #t x (k ...) free-identifier=? #f + ((_ . pattern) (syntax-protect (syntax/loc x template))) + ...)))))))) + + (-define-syntax syntax-id-rules + (lambda (x) + (syntax-case** syntax-id-rules #t x () free-identifier=? #f + ((sidr (k ...) (pattern template) ...) + (andmap identifier? (syntax->list (syntax (k ...)))) + (syntax/loc x + (make-set!-transformer + (lambda (x) + (syntax-case** sidr #t x (k ...) free-identifier=? #f + (pattern (syntax-protect (syntax/loc x template))) + ...)))))))) + + (-define (syntax-protect stx) + (if (syntax? stx) + (syntax-arm stx #f #t) + (raise-argument-error 'syntax-protect "syntax?" stx))) + + (#%provide syntax datum (all-from "with-stx.rkt") + (all-from "stxloc.rkt") + check-duplicate-identifier syntax-protect + syntax-rules syntax-id-rules + (for-syntax syntax-pattern-variable?))) diff --git a/7-4/racket/collects/racket/private/stxcase.rkt b/7-4/racket/collects/racket/private/stxcase.rkt new file mode 100644 index 0000000..cded343 --- /dev/null +++ b/7-4/racket/collects/racket/private/stxcase.rkt @@ -0,0 +1,390 @@ +;;---------------------------------------------------------------------- +;; syntax-case and syntax + +(module stxcase '#%kernel + (#%require racket/private/stx racket/private/small-scheme '#%paramz '#%unsafe + racket/private/ellipses + stxparse-info/current-pvars + (for-syntax racket/private/stx racket/private/small-scheme + racket/private/gen-temp racket/private/member racket/private/sc '#%kernel + auto-syntax-e/utils)) + + (-define interp-match + (lambda (pat e literals immediate=?) + (interp-gen-match pat e literals immediate=? #f))) + + (-define interp-s-match + (lambda (pat e literals immediate=?) + (interp-gen-match pat e literals immediate=? #t))) + + (-define interp-gen-match + (lambda (pat e literals immediate=? s-exp?) + (let loop ([pat pat][e e][cap e]) + (cond + [(null? pat) + (if s-exp? + (null? e) + (stx-null? e))] + [(number? pat) + (and (if s-exp? (symbol? e) (identifier? e)) + (immediate=? e (vector-ref (if s-exp? literals (syntax-e literals)) pat)))] + [(not pat) + #t] + [else + (let ([i (vector-ref pat 0)]) + (cond + [(eq? i 'bind) + (let ([e (if s-exp? + e + (if (vector-ref pat 2) + (datum->syntax cap e cap) + e))]) + (if (vector-ref pat 1) + e + (list e)))] + [(eq? i 'pair) + (let ([match-head (vector-ref pat 1)] + [match-tail (vector-ref pat 2)] + [mh-did-var? (vector-ref pat 3)] + [mt-did-var? (vector-ref pat 4)]) + (let ([cap (if (syntax? e) e cap)]) + (and (stx-pair? e) + (let ([h (loop match-head (stx-car e) cap)]) + (and h + (let ([t (loop match-tail (stx-cdr e) cap)]) + (and t + (if mh-did-var? + (if mt-did-var? + (append h t) + h) + t))))))))] + [(eq? i 'quote) + (if s-exp? + (and (equal? (vector-ref pat 1) e) + null) + (and (syntax? e) + (equal? (vector-ref pat 1) (syntax-e e)) + null))] + [(eq? i 'ellipses) + (let ([match-head (vector-ref pat 1)] + [nest-cnt (vector-ref pat 2)] + [last? (vector-ref pat 3)]) + (and (if s-exp? + (list? e) + (stx-list? e)) + (if (zero? nest-cnt) + (andmap (lambda (e) (loop match-head e cap)) + (if s-exp? e (stx->list e))) + (let/ec esc + (let ([l (map (lambda (e) + (let ([m (loop match-head e cap)]) + (if m + m + (esc #f)))) + (if s-exp? e (stx->list e)))]) + (if (null? l) + (let loop ([cnt nest-cnt]) + (cond + [(= 1 cnt) (if last? '() '(()))] + [else (cons '() (loop (sub1 cnt)))])) + ((if last? stx-rotate* stx-rotate) l)))))))] + [(eq? i 'mid-ellipses) + (let ([match-head (vector-ref pat 1)] + [match-tail (vector-ref pat 2)] + [tail-cnt (vector-ref pat 3)] + [prop? (vector-ref pat 4)] + [mh-did-var? (vector-ref pat 5)] + [mt-did-var? (vector-ref pat 6)]) + (let-values ([(pre-items post-items ok?) + (split-stx-list e tail-cnt prop?)] + [(cap) (if (syntax? e) e cap)]) + (and ok? + (let ([h (loop match-head pre-items cap)]) + (and h + (let ([t (loop match-tail post-items cap)]) + (and t + (if mt-did-var? + (if mh-did-var? + (append h t) + t) + h))))))))] + [(eq? i 'veclist) + (and (if s-exp? + (vector? e) + (stx-vector? e #f)) + (loop (vector-ref pat 1) (vector->list (if s-exp? e (syntax-e e))) cap))] + [(eq? i 'vector) + (and (if s-exp? + (and (vector? e) (= (vector-length e) (vector-ref pat 1))) + (stx-vector? e (vector-ref pat 1))) + (let vloop ([p (vector-ref pat 2)][pos 0]) + (cond + [(null? p) null] + [else + (let ([clause (car p)]) + (let ([match-elem (car clause)] + [elem-did-var? (cdr clause)]) + (let ([m (loop match-elem (if s-exp? (vector-ref e pos) (stx-vector-ref e pos)) cap)]) + (and m + (let ([body (vloop (cdr p) (add1 pos))]) + (and body + (if elem-did-var? + (if (null? body) + m + (append m body)) + body)))))))])))] + [(eq? i 'box) + (let ([match-content (vector-ref pat 1)]) + (and (if s-exp? + (box? e) + (stx-box? e)) + (loop match-content (unbox (if s-exp? e (syntax-e e))) cap)))] + [(eq? i 'prefab) + (and (if s-exp? + (equal? (vector-ref pat 1) (prefab-struct-key e)) + (stx-prefab? (vector-ref pat 1) e)) + (loop (vector-ref pat 2) (cdr (vector->list (struct->vector (if s-exp? e (syntax-e e))))) cap))] + [else (error "yikes!" pat)]))])))) + + (-define-syntax syntax-case** + (lambda (x) + (-define l (and (stx-list? x) (cdr (stx->list x)))) + (unless (and (stx-list? x) + (> (length l) 3)) + (raise-syntax-error + #f + "bad form" + x)) + (let ([who (car l)] + [arg-is-stx? (cadr l)] + [expr (caddr l)] + [kws (cadddr l)] + [lit-comp (cadddr (cdr l))] + [s-exp? (syntax-e (cadddr (cddr l)))] + [clauses (cddddr (cddr l))]) + (unless (stx-list? kws) + (raise-syntax-error + (syntax-e who) + "expected a parenthesized sequence of literal identifiers" + kws)) + (for-each + (lambda (lit) + (unless (identifier? lit) + (raise-syntax-error + (syntax-e who) + "literal is not an identifier" + lit))) + (stx->list kws)) + (for-each + (lambda (clause) + (unless (and (stx-list? clause) + (<= 2 (length (stx->list clause)) 3)) + (raise-syntax-error + (syntax-e who) + "expected a clause containing a pattern, an optional guard expression, and an expression" + clause))) + clauses) + (let ([patterns (map stx-car clauses)] + [fenders (map (lambda (clause) + (and (stx-pair? (stx-cdr (stx-cdr clause))) + (stx-car (stx-cdr clause)))) + clauses)] + [answers (map (lambda (clause) + (let ([r (stx-cdr (stx-cdr clause))]) + (if (stx-pair? r) + (stx-car r) + (stx-car (stx-cdr clause))))) + clauses)]) + (let* ([arg (quote-syntax arg)] + [rslt (quote-syntax rslt)] + [pattern-varss (map + (lambda (pattern) + (get-match-vars who pattern pattern (stx->list kws))) + (stx->list patterns))] + [lit-comp-is-mod? (and (identifier? lit-comp) + (free-identifier=? + lit-comp + (quote-syntax free-identifier=?)))]) + (syntax-arm + (datum->syntax + (quote-syntax here) + (list (quote-syntax let) (list (list arg (if (or s-exp? (syntax-e arg-is-stx?)) + expr + (list (quote-syntax datum->syntax) + (list + (quote-syntax quote-syntax) + (datum->syntax + expr + 'here)) + expr)))) + (let loop ([patterns patterns] + [fenders fenders] + [unflat-pattern-varss pattern-varss] + [answers answers]) + (cond + [(null? patterns) + (list + (quote-syntax raise-syntax-error) + #f + "bad syntax" + arg)] + [else + (let ([rest (loop (cdr patterns) (cdr fenders) + (cdr unflat-pattern-varss) (cdr answers))]) + (let ([pattern (car patterns)] + [fender (car fenders)] + [unflat-pattern-vars (car unflat-pattern-varss)] + [answer (car answers)]) + (-define pattern-vars + (map (lambda (var) + (let loop ([var var]) + (if (syntax? var) + var + (loop (car var))))) + unflat-pattern-vars)) + (-define temp-vars + (map + (lambda (p) (gen-temp-id 'sc)) + pattern-vars)) + (-define tail-pattern-var (sub1 (length pattern-vars))) + ;; Here's the result expression for one match: + (let* ([do-try-next (if (car fenders) + (list (quote-syntax try-next)) + rest)] + [mtch (make-match&env + who + pattern + pattern + (stx->list kws) + (not lit-comp-is-mod?) + s-exp?)] + [cant-fail? (if lit-comp-is-mod? + (equal? mtch '(lambda (e) e)) + (equal? mtch '(lambda (e free-identifier=?) e)))] + ;; Avoid generating gigantic matching expressions. + ;; If it's too big, interpret at run time, instead + [interp? (and (not cant-fail?) + (zero? + (let sz ([mtch mtch][fuel 100]) + (cond + [(zero? fuel) 0] + [(pair? mtch) (sz (cdr mtch) + (sz (car mtch) + fuel))] + [(syntax? mtch) (sz (syntax-e mtch) (sub1 fuel))] + [else (sub1 fuel)]))))] + [mtch (if interp? + (let ([interp-box (box null)]) + (let ([pat (make-interp-match pattern (syntax->list kws) interp-box s-exp?)]) + (list 'lambda + '(e) + (list (if s-exp? 'interp-s-match 'interp-match) + (list 'quote pat) + 'e + (if (null? (unbox interp-box)) + #f + (list (if s-exp? 'quote 'quote-syntax) + (list->vector (reverse (unbox interp-box))))) + lit-comp)))) + mtch)] + [m + ;; Do match, bind result to rslt: + (list (quote-syntax let) + (list + (list rslt + (if cant-fail? + arg + (list* (datum->syntax + (quote-syntax here) + mtch + pattern) + arg + (if (or interp? lit-comp-is-mod?) + null + (list lit-comp)))))) + ;; If match succeeded... + (list + (quote-syntax if) + (if cant-fail? + #t + rslt) + ;; Extract each name binding into a temp variable: + (list + (quote-syntax let) + (map (lambda (pattern-var temp-var) + (list + temp-var + (let ([pos (stx-memq-pos pattern-var pattern-vars)]) + (let ([accessor (cond + [(= tail-pattern-var pos) + (cond + [(eq? pos 0) 'tail] + [(eq? pos 1) (quote-syntax unsafe-cdr)] + [else 'tail])] + [(eq? pos 0) (quote-syntax unsafe-car)] + [else #f])]) + (cond + [(eq? accessor 'tail) + (if (zero? pos) + rslt + (list + (quote-syntax unsafe-list-tail) + rslt + pos))] + [accessor (list + accessor + rslt)] + [else (list + (quote-syntax unsafe-list-ref) + rslt + pos)]))))) + pattern-vars temp-vars) + ;; Tell nested `syntax' forms about the + ;; pattern-bound variables: + (list + (quote-syntax letrec-syntaxes+values) + (map (lambda (pattern-var unflat-pattern-var temp-var) + (list (list pattern-var) + (list + (if s-exp? + (quote-syntax make-s-exp-mapping) + (quote-syntax make-auto-pvar)) + ;; Tell it the shape of the variable: + (let loop ([var unflat-pattern-var][d 0]) + (if (syntax? var) + d + (loop (car var) (add1 d)))) + ;; Tell it the variable name: + (list + (quote-syntax quote-syntax) + temp-var)))) + pattern-vars unflat-pattern-vars + temp-vars) + null + (if fender + (list (quote-syntax if) fender + (list (quote-syntax with-pvars) + pattern-vars + answer) + do-try-next) + (list (quote-syntax with-pvars) + pattern-vars + answer)))) + do-try-next))]) + (if fender + (list + (quote-syntax let) + ;; Bind try-next to try next case + (list (list (quote try-next) + (list (quote-syntax lambda) + (list) + rest))) + ;; Try one match + m) + ;; Match try already embed the rest case + m))))]))) + x))))))) + + (#%require racket/private/template) + (#%provide (all-from racket/private/ellipses) syntax-case** syntax syntax/loc datum + (for-syntax syntax-pattern-variable?))) diff --git a/7-4/racket/collects/racket/private/stxloc.rkt b/7-4/racket/collects/racket/private/stxloc.rkt new file mode 100644 index 0000000..e26417c --- /dev/null +++ b/7-4/racket/collects/racket/private/stxloc.rkt @@ -0,0 +1,59 @@ + +;;---------------------------------------------------------------------- +;; syntax/loc + +(module stxloc '#%kernel + (#%require racket/private/qq-and-or "stxcase.rkt" racket/private/define-et-al + (for-syntax '#%kernel "stxcase.rkt" racket/private/sc)) + + (begin-for-syntax + (define-values (transform-to-syntax-case**) + (lambda (stx sc arg-is-stx? expr kws lit-comp s-exp? clauses) + ((λ (ans) (datum->syntax #'here ans stx)) + (list* 'syntax-case** sc arg-is-stx? expr kws lit-comp s-exp? + clauses))))) + + ;; Like regular syntax-case, but with free-identifier=? replacement + (-define-syntax syntax-case* + (lambda (stx) + (syntax-case** #f #t stx () free-identifier=? #f + [(sc stxe kl id=? . clause) + (transform-to-syntax-case** stx #'sc #f #'stxe #'kl #'id=? #f #'clause)]))) + + ;; Regular syntax-case + (-define-syntax syntax-case + (lambda (stx) + (syntax-case** #f #t stx () free-identifier=? #f + [(sc stxe kl . clause) + (transform-to-syntax-case** stx #'sc #f #'stxe #'kl #'free-identifier=? #f + #'clause)]))) + + ;; Like `syntax-case, but on plain datums + (-define-syntax datum-case + (lambda (stx) + (syntax-case** #f #t stx () free-identifier=? #f + [(sc stxe kl . clause) + (transform-to-syntax-case** stx #'sc #f #'stxe #'kl #'eq? #t #'clause)]))) + + (-define-syntax quote-syntax/prune + (lambda (stx) + (syntax-case** #f #t stx () free-identifier=? #f + [(_ id) + (if (symbol? (syntax-e #'id)) + (datum->syntax #'here + (list (quote-syntax quote-syntax) + (identifier-prune-lexical-context (syntax id) + (list + (syntax-e (syntax id)) + '#%top))) + stx + #f + stx) + (raise-syntax-error + #f + "expected an identifier" + stx + #'id))]))) + + (#%provide syntax/loc quote-syntax/prune syntax-case* syntax-case datum-case + ... _ ~? ~@)) diff --git a/7-4/racket/collects/racket/private/template.rkt b/7-4/racket/collects/racket/private/template.rkt new file mode 100644 index 0000000..06dfba3 --- /dev/null +++ b/7-4/racket/collects/racket/private/template.rkt @@ -0,0 +1,705 @@ +(module template '#%kernel +(#%require racket/private/stx racket/private/small-scheme racket/private/performance-hint + (rename racket/private/small-scheme define -define) + (rename racket/private/small-scheme define-syntax -define-syntax) + racket/private/ellipses + (for-syntax racket/private/stx racket/private/small-scheme + (rename racket/private/small-scheme define -define) + (rename racket/private/small-scheme define-syntax -define-syntax) + racket/private/member racket/private/sc '#%kernel)) +(#%provide syntax + syntax/loc + datum + ~? ~@ + ~@! signal-absent-pvar + (for-syntax attribute-mapping + attribute-mapping? + attribute-mapping-name + attribute-mapping-var + attribute-mapping-depth + attribute-mapping-check + (protect metafunction + metafunction?))) + +;; ============================================================ +;; Syntax of templates + +;; A Template (T) is one of: +;; - pattern-variable +;; - constant (including () and non-pvar identifiers) +;; - (metafunction . T) +;; - (H . T) +;; - (H ... . T), (H ... ... . T), etc +;; - (... T) -- escapes inner ..., ~?, ~@ +;; - (~? T T) +;; - #(T*) -- actually, vector->list interpreted as T +;; - #s(prefab-struct-key T*) -- likewise + +;; A HeadTemplate (H) is one of: +;; - T +;; - (~? H) +;; - (~? H H) +;; - (~@ . T) + +(define-syntax ~@! #f) ;; private, escape-ignoring version of ~@, used by unsyntax-splicing + +;; ============================================================ +;; Compile-time + +;; Parse template syntax into a Guide (AST--the name is left over from +;; when the "guide" was a data structure interpreted at run time). + +;; The AST representation is designed to coincide with the run-time +;; support, so compilation is just (datum->syntax #'here guide). The +;; variants listed below are the ones recognized and treated specially +;; by other functions (eg optimize-resyntax, relocate-guide). + +;; A Guide (G) is one of: +;; - (list 't-resyntax Expr Expr G) +;; - (list 't-quote Datum) ;; constant, but not null +;; - (list 't-quote-syntax Syntax) +;; - (list 't-var Id) ;; trusted pattern variable +;; - (list 't-list G ...) +;; - (list 't-list* G ... G) +;; - (list 't-append HG G) +;; - (list 't-orelse G G) +;; - (list 't-subst Expr Expr '({Subst} ...) Expr ...) ;; apply susbstitutions +;; -- where Subst = Nat ;; replace nth car with arg +;; | 'tail Nat ;; replace nth cdr with arg +;; | 'append Nat ;; replace nth car by appending arg +;; | 'recur Nat ;; replace nth car by recurring on it with arg +;; - other expression (must be pair!) + +;; A HeadGuide (HG) is one of: +;; - (list 'h-t G) +;; - other expression (must be pair!) + +(begin-for-syntax + + (define here-stx (quote-syntax here)) + + (define template-logger (make-logger 'template (current-logger))) + + ;; An Attribute is an identifier statically bound to a syntax-mapping + ;; (see sc.rkt) whose valvar is an identifier statically bound to an + ;; attribute-mapping. + + ;; (struct attribute-mapping (var name depth check) ...) + ;; check : #f (trusted) or Id, ref to Checker + ;; Checker = ( Any d:Nat b:Boolean Syntax/#f -> (Listof^d (if b Syntax Any)) ) + (#%require (for-template (only racket/private/template + attribute-mapping + attribute-mapping? + attribute-mapping-var + attribute-mapping-name + attribute-mapping-depth + attribute-mapping-check))) + #;(define-values (struct:attribute-mapping attribute-mapping attribute-mapping? + attribute-mapping-ref _attribute-mapping-set!) + (make-struct-type 'attribute-mapping #f 4 0 #f null (current-inspector) + (lambda (self stx) + (if (attribute-mapping-check self) + (let ([source-name + (or (let loop ([p (syntax-property stx 'disappeared-use)]) + (cond [(identifier? p) p] + [(pair? p) (or (loop (car p)) (loop (cdr p)))] + [else #f])) + (attribute-mapping-name self))]) + (define code + `(,(attribute-mapping-check self) + ,(attribute-mapping-var self) + ,(attribute-mapping-depth self) + #t + (quote-syntax ,source-name))) + (datum->syntax here-stx code stx)) + (attribute-mapping-var self))))) + #;(define (attribute-mapping-var a) (attribute-mapping-ref a 0)) + #;(define (attribute-mapping-name a) (attribute-mapping-ref a 1)) + #;(define (attribute-mapping-depth a) (attribute-mapping-ref a 2)) + #;(define (attribute-mapping-check a) (attribute-mapping-ref a 3)) + + ;; (struct metafunction (var)) + (define-values (struct:metafunction metafunction metafunction? metafunction-ref _mf-set!) + (make-struct-type 'syntax-metafunction #f 1 0 #f null (current-inspector))) + (define (metafunction-var mf) (metafunction-ref mf 0)) + + (define (guide-is? x tag) (and (pair? x) (eq? (car x) tag))) + + (define (ht-guide? x) (guide-is? x 'h-t)) + (define (ht-guide-t x) (cadr x)) + + (define (quote-guide? x) (guide-is? x 't-quote)) + (define (quote-guide-v x) (cadr x)) + (define (quote-syntax-guide? x) (guide-is? x 't-quote-syntax)) + (define (quote-syntax-guide-v x) (cadr x)) + + (define (null-guide? x) (and (guide-is? x 't-list) (null? (cdr x)))) + + (define (datum-guide? x) (or (quote-guide? x) (null-guide? x))) + (define (datum-guide-v x) (if (null-guide? x) null (quote-guide-v x))) + + (define (list-guide? x) (guide-is? x 't-list)) + (define (list-guide-vs x) (cdr x)) + + (define (list*-guide? x) (guide-is? x 't-list*)) + (define (list*-guide-vs x) (cdr x)) + + (define (struct-guide? x) (guide-is? x 't-struct)) + (define (struct-guide-key x) (cadr (cadr x))) + (define (struct-guide-v x) (caddr x)) + + (define (vector-guide? x) (guide-is? x 't-vector)) + (define (vector-guide-v x) (cadr x)) + + (define (box-guide? x) (guide-is? x 't-box)) + (define (box-guide-v x) (cadr x)) + + (define (append-guide gh gt) + (cond [(ht-guide? gh) (cons-guide (ht-guide-t gh) gt)] + [(null-guide? gt) gh] + [else `(t-append ,gh ,gt)])) + + (define (cons-guide g1 g2) + (cond [(and (datum-guide? g1) (datum-guide? g2)) + `(t-quote ,(cons (datum-guide-v g1) (datum-guide-v g2)))] + [(list-guide? g2) (list* 't-list g1 (list-guide-vs g2))] + [(list*-guide? g2) (list* 't-list* g1 (list*-guide-vs g2))] + [else (list 't-list* g1 g2)])) + + (define (const-stx-guide? x) + (cond [(quote-guide? x) #t] + [(quote-syntax-guide? x) #t] + [(list-guide? x) (andmap const-stx-guide? (list-guide-vs x))] + [(list*-guide? x) (andmap const-stx-guide? (list*-guide-vs x))] + [(struct-guide? x) (const-stx-guide? (struct-guide-v x))] + [(vector-guide? x) (const-stx-guide? (vector-guide-v x))] + [(box-guide? x) (const-stx-guide? (box-guide-v x))] + [else #f])) + (define (const-stx-guide-v x) + (cond [(quote-guide? x) (quote-guide-v x)] + [(quote-syntax-guide? x) (quote-syntax-guide-v x)] + [(list-guide? x) (map const-stx-guide-v (list-guide-vs x))] + [(list*-guide? x) (apply list* (map const-stx-guide-v (list*-guide-vs x)))] + [(struct-guide? x) + (apply make-prefab-struct (struct-guide-key x) (const-stx-guide-v (struct-guide-v x)))] + [(vector-guide? x) (list->vector (const-stx-guide-v (vector-guide-v x)))] + [(box-guide? x) (box (const-stx-guide-v (box-guide-v x)))] + [else (error 'const-stx-guide-v "bad guide: ~e" x)])) + + (define (dots-guide hguide frame head at-stx) + (let ([cons? (ht-guide? hguide)] + [hguide (if (ht-guide? hguide) (ht-guide-t hguide) hguide)] + [env (dotsframe-env frame)]) + (cond [(and (guide-is? hguide 't-var) (= (length env) 1) + (eq? (cadr hguide) (caar env))) + ;; (x ...), where x is trusted + (cond [cons? `(t-var ,(cdar env))] + [else `(apply append (t-var ,(cdar env)))])] + [else + `(t-dots ,cons? ,hguide ,(map car env) ,(map cdr env) + (quote ,head) (quote-syntax ,at-stx))]))) + + ;; A Depth is (Listof MapFrame) + + ;; A DotsFrame is (vector (Listof (cons Id Syntax)) (Hash Id => Id) Id Bool) + ;; Each ellipsis in a template has a corresponding DotsFrame of the form + ;; (vector env ht ellipsis-id any-vars?), where + ;; -- env is (list (cons iter-id src-list-expr) ...), where src-list-expr + ;; is a src-list-id either by itself or wrapped in a check + ;; -- ht maps a src-list-id to the corresponding iter-id + ;; -- ellipsis-id is the identifier for the ellipsis (for error reporting) + ;; -- any-vars? is a flag that indicates whether any pattern variables occur + ;; in this frame's subtemplate (for error reporting) + ;; When a pattern variable of depth D is found, it is added to the D current + ;; innermost (ie, topmost) dotsframes (see `lookup`). + (define (new-dotsframe ellipsis-stx) + (vector null (make-hasheq) ellipsis-stx #f)) + (define (dotsframe-env frame) (vector-ref frame 0)) + (define (dotsframe-ref frame src-id) + (hash-ref (vector-ref frame 1) src-id #f)) + (define (dotsframe-add! frame iter-id src-id src-expr) + (vector-set! frame 0 (cons (cons iter-id src-expr) (vector-ref frame 0))) + (hash-set! (vector-ref frame 1) src-id iter-id)) + (define (dotsframe-index-iter frame) (vector-ref frame 2)) + (define (dotsframe-index-iter! frame) + (cond [(vector-ref frame 2) => (lambda (x) x)] + [else (let ([index-var (gentemp)]) + (vector-set! frame 2 index-var) + index-var)])) + (define (dotsframe-ellipsis-id frame) (vector-ref frame 2)) + (define (dotsframe-has-mapvars? frame) (pair? (vector-ref frame 0))) + (define (dotsframe-has-any-vars? frame) (vector-ref frame 3)) + + (define (frames-seen-pvar! frames) + (when (pair? frames) + (unless (vector-ref (car frames) 3) + (vector-set! (car frames) 3 #t) + (frames-seen-pvar! (cdr frames))))) + + (define (ellipsis? x) + (and (identifier? x) (free-identifier=? x (quote-syntax ...)))) + + ;; ---------------------------------------- + ;; Parsing templates + + ;; parse-template : Syntax Syntax Boolean -> (values Guide (Listof Id)) + (define (parse-template ctx t stx?) + ;; wrong-syntax : Syntax Format-String Any ... -> (error) + (define (wrong-syntax x fmt . args) (raise-syntax-error #f (apply format fmt args) ctx x)) + + ;; disappeared-uses : (Listof Id) + (define disappeared-uses null) + ;; disappeared! : Id -> Void + (define (disappeared! id) (set! disappeared-uses (cons id disappeared-uses))) + + ;; parse-t : Stx Nat Boolean -> Guide + (define (parse-t t depth esc?) + (cond [(stx-pair? t) + (if (identifier? (stx-car t)) + (parse-t-pair/command t depth esc?) + (parse-t-pair/dots t depth esc?))] + [else (parse-t-nonpair t depth esc?)])) + + ;; parse-t-pair/command : Stx Nat Boolean -> ... + ;; t is a stxpair w/ id in car; check if it is a "command" (metafun, escape, etc) + (define (parse-t-pair/command t depth esc?) + (cond [esc? + (parse-t-pair/dots t depth esc?)] + [(parse-form t (quote-syntax ...) 1) + => (lambda (t) + (disappeared! (car t)) + (parse-t (cadr t) depth #t))] + [(parse-form t (quote-syntax ~?) 2) + => (lambda (t) + (disappeared! (car t)) + (define t1 (cadr t)) + (define t2 (caddr t)) + (define guide1 (parse-t t1 depth esc?)) + (define guide2 (parse-t t2 depth esc?)) + `(t-orelse ,guide1 ,guide2))] + [(lookup-metafun (stx-car t)) + => (lambda (mf) + (unless stx? (wrong-syntax (stx-car t) "metafunctions are not supported")) + (disappeared! (stx-car t)) + (define guide (parse-t (stx-cdr t) depth esc?)) + `(t-metafun ,(metafunction-var mf) ,guide + (quote-syntax + ,(let ([tstx (and (syntax? t) t)]) + (datum->syntax tstx (cons (stx-car t) #f) tstx tstx)))))] + [else (parse-t-pair/dots t depth esc?)])) + + ;; parse-t-pair/dots : Stx Nat Boolean -> ... + ;; t is a stx pair; check for dots + (define (parse-t-pair/dots t depth esc?) + (define head (stx-car t)) + (define-values (tail frames) ;; first-in-stx = innermost is first in list + (let loop ([tail (stx-cdr t)] [frames null]) + (cond [(and (not esc?) (stx-pair? tail) (ellipsis? (stx-car tail))) + (disappeared! (stx-car tail)) + (loop (stx-cdr tail) (cons (new-dotsframe (stx-car tail)) frames))] + [else (values tail (reverse frames))]))) + (define at-stx (datum->syntax #f '... head)) + (define hguide + (let loop ([frames frames] [hguide (parse-h head (append frames depth) esc?)]) + (cond [(pair? frames) + (define frame (car frames)) + (unless (dotsframe-has-mapvars? frame) + (unless (dotsframe-has-any-vars? frame) + (wrong-syntax head "no pattern variables before ellipsis in template")) + (wrong-syntax (dotsframe-ellipsis-id frame) "too many ellipses in template")) + (loop (cdr frames) (dots-guide hguide frame head at-stx))] + [else hguide]))) + (define tguide (parse-t tail depth esc?)) + (resyntax t (append-guide hguide tguide))) + + ;; parse-t-nonpair : Syntax Nat Boolean -> ... + ;; PRE: t is not a stxpair + (define (parse-t-nonpair t depth esc?) + (define td (if (syntax? t) (syntax-e t) t)) + (cond [(identifier? t) + (cond [(and (not esc?) + (or (free-identifier=? t (quote-syntax ...)) + (free-identifier=? t (quote-syntax ~?)) + (free-identifier=? t (quote-syntax ~@)))) + (wrong-syntax t "illegal use")] + [(lookup-metafun t) + (wrong-syntax t "illegal use of syntax metafunction")] + [(lookup t depth) => (lambda (ref) ref)] + [else (const-guide t)])] + [(vector? td) + (define guide (parse-t (vector->list td) depth esc?)) + (resyntax t `(t-vector ,guide))] + [(prefab-struct-key td) + => (lambda (key) + (define elems (cdr (vector->list (struct->vector td)))) + (define guide (parse-t elems depth esc?)) + (resyntax t `(t-struct (quote ,key) ,guide)))] + [(box? td) + (define guide (parse-t (unbox td) depth esc?)) + (resyntax t `(t-box ,guide))] + [else (const-guide t)])) + + ;; parse-h : Syntax Depth Boolean -> HeadGuide + (define (parse-h h depth esc?) + (cond [(and (not esc?) (parse-form h (quote-syntax ~?) 1)) + => (lambda (h) + (disappeared! (car h)) + (define guide (parse-h (cadr h) depth esc?)) + `(h-orelse ,guide null))] + [(and (not esc?) (parse-form h (quote-syntax ~?) 2)) + => (lambda (h) + (disappeared! (car h)) + (define guide1 (parse-h (cadr h) depth esc?)) + (define guide2 (parse-h (caddr h) depth esc?)) + (if (and (ht-guide? guide1) (ht-guide? guide2)) + `(h-t (t-orelse ,(ht-guide-t guide1) ,(ht-guide-t guide2))) + `(h-orelse ,guide1 ,guide2)))] + [(and (stx-pair? h) + (let ([h-head (stx-car h)]) + (and (identifier? h-head) + (or (and (free-identifier=? h-head (quote-syntax ~@)) (not esc?)) + (free-identifier=? h-head (quote-syntax ~@!)))))) + (disappeared! (stx-car h)) + (define guide (parse-t (stx-cdr h) depth esc?)) + `(h-splice ,guide (quote ,h) (quote-syntax ,(stx-car h)))] + [else + (define guide (parse-t h depth esc?)) + `(h-t ,guide)])) + + ;; lookup : Identifier Depth -> Syntax/#f + ;; If pattern variable with depth>0, insert into depth innermost ellipsis envs. + (define (lookup id depth0) + (define (make-pvar var check pvar-depth) + (define (make-ref var) + (cond [check `(t-check-var (,check ,var 0 #t (quote-syntax ,id)))] + [else `(t-var ,var)])) + (define (make-src-ref var id) + (cond [check `(#%expression (,check ,var 1 #f (quote-syntax ,id)))] + [else var])) + (disappeared! id) + (frames-seen-pvar! depth0) + (make-ref + (let dloop ([depth depth0] [pvar-depth pvar-depth]) ;; ... -> Identifier + ;; Returns variable reference whose value has not been checked yet. + (cond [(zero? pvar-depth) var] + [(null? depth) + (if (null? depth0) + (wrong-syntax id "missing ellipsis with pattern variable in template") + (wrong-syntax id "too few ellipses for pattern variable in template"))] + [else + (define src (dloop (cdr depth) (sub1 pvar-depth))) + (or (dotsframe-ref (car depth) src) + (let ([iter (gentemp)]) + (dotsframe-add! (car depth) iter src (make-src-ref src id)) + iter))])))) + (let ([v (syntax-local-value id (lambda () #f))]) + (cond [(and stx? (syntax-pattern-variable? v)) + (define pvar-depth (syntax-mapping-depth v)) + (define attr + (let ([attr (syntax-local-value (syntax-mapping-valvar v) (lambda () #f))]) + (and (attribute-mapping? attr) attr))) + (define var (if attr (attribute-mapping-var attr) (syntax-mapping-valvar v))) + (define check (and attr (attribute-mapping-check attr))) + (make-pvar var check pvar-depth)] + [(and (not stx?) (s-exp-pattern-variable? v)) + (define pvar-depth (s-exp-mapping-depth v)) + (define var (s-exp-mapping-valvar v)) + (make-pvar var #f pvar-depth)] + [else + ;; id is a constant; check that for all x s.t. id = x.y, x is not an attribute + (for-each + (lambda (pfx) + (let ([pfx-v (syntax-local-value pfx (lambda () #f))]) + (if (and (syntax-pattern-variable? pfx-v) + (let ([valvar (syntax-mapping-valvar pfx-v)]) + (attribute-mapping? (syntax-local-value valvar (lambda () #f))))) + (wrong-syntax id "undefined nested attribute of attribute `~a'" + (syntax-e pfx)) + (void)))) + (dotted-prefixes id)) + #f]))) + + ;; resyntax : Stx Guide -> Guide + (define (resyntax t g) + (cond [(not (and stx? (syntax? t))) g] + [(const-stx-guide? g) + `(t-quote-syntax ,(datum->syntax t (const-stx-guide-v g) t t))] + [#t (optimize-resyntax t g)] + [else `(t-resyntax #f (quote-syntax ,(datum->syntax t 'STX t t)) ,g)])) + + ;; optimize-resyntax : Syntax Guide -> Guide + (define (optimize-resyntax t0 g) + (define HOLE (datum->syntax #f '_)) + (define (finish i rt rs re) + (values (sub1 i) (reverse rs) (reverse re) + (datum->syntax t0 (apply list* (reverse rt)) t0 t0))) + (define (loop-gs list*? gs i rt rs re) + (cond [(null? gs) + (finish i (cons null rt) rs re)] + [(and list*? (null? (cdr gs))) + (loop-g (car gs) i rt rs re)] + [else + (define g0 (car gs)) + (cond [(quote-syntax-guide? g0) + (let ([const (quote-syntax-guide-v g0)]) + (loop-gs list*? (cdr gs) (add1 i) (cons const rt) rs re))] + [(eq? (car g0) 't-subst) ;; (t-subst LOC STX <substs>) + (let ([subt (cadr (list-ref g0 2))] ;; extract from (quote-syntax _) + [subargs (list-tail g0 3)]) + (loop-gs list*? (cdr gs) (add1 i) (cons subt rt) + (list* i 'recur rs) (cons `(list . ,subargs) re)))] + [else (loop-gs list*? (cdr gs) (add1 i) (cons HOLE rt) + (cons i rs) (cons g0 re))])])) + (define (loop-g g i rt rs re) + (cond [(list-guide? g) + (loop-gs #f (list-guide-vs g) i rt rs re)] + [(list*-guide? g) + (loop-gs #t (list*-guide-vs g) i rt rs re)] + [(guide-is? g 't-append) + (loop-g (caddr g) (add1 i) (cons HOLE rt) + (list* i 'append rs) (cons (cadr g) re))] + [(eq? (car g) 't-quote-syntax) + (let ([const (quote-syntax-guide-v g)]) + (finish i (cons const rt) rs re))] + [else (finish i (cons HOLE rt) (list* i 'tail rs) (cons g re))])) + (define-values (npairs substs exprs t*) (loop-g g 0 null null null)) + (cond [(and substs + ;; Tunable condition for choosing whether to create a t-subst. + ;; Avoid creating useless (t-subst loc stx '(tail 0) g). + (<= (length substs) (* 2 npairs))) + #;(log-message template-logger 'debug + (format "OPTIMIZED ~s" (syntax->datum t0)) #f) + `(t-subst #f (quote-syntax ,t*) (quote ,substs) . ,exprs)] + [else + #;(log-message template-logger 'debug + (format "NOT opt ~s" (syntax->datum t0)) #f) + (let ([rep (datum->syntax t0 'STX t0 t0)]) + `(t-resyntax #f (quote-syntax ,rep) ,g))])) + + ;; const-guide : Any -> Guide + (define (const-guide x) + (cond [(and stx? (syntax? x)) `(t-quote-syntax ,x)] + [(null? x) `(t-list)] + [else `(t-quote , x)])) + + (let ([guide (parse-t t null #f)]) + (values guide disappeared-uses))) + + ;; parse-form : Stx Id Nat -> (list[arity+1] Syntax) + (define (parse-form stx form-id arity) + (and (stx-pair? stx) + (let ([stx-h (stx-car stx)] [stx-t (stx-cdr stx)]) + (and (identifier? stx-h) (free-identifier=? stx-h form-id) + (let ([stx-tl (stx->list stx-t)]) + (and (list? stx-tl) + (= (length stx-tl) arity) + (cons stx-h stx-tl))))))) + + ;; lookup-metafun : Identifier -> Metafunction/#f + (define (lookup-metafun id) + (define v (syntax-local-value id (lambda () #f))) + (and (metafunction? v) v)) + + (define (dotted-prefixes id) + (let* ([id-string (symbol->string (syntax-e id))] + [dot-locations + (let loop ([i 0]) + (if (< i (string-length id-string)) + (if (eqv? (string-ref id-string i) #\.) + (cons i (loop (add1 i))) + (loop (add1 i))) + null))]) + (map (lambda (loc) (datum->syntax id (string->symbol (substring id-string 0 loc)))) + dot-locations))) + + (define gentemp-counter 0) + (define (gentemp) + (set! gentemp-counter (add1 gentemp-counter)) + ((make-syntax-introducer) + (datum->syntax #f (string->symbol (format "pv_~s" gentemp-counter))))) + + (define (stx-drop n x) + (if (zero? n) x (stx-drop (sub1 n) (stx-cdr x)))) + + ;; ---------------------------------------- + ;; Relocating (eg, syntax/loc) + + ;; Only relocate if relocation would affect a syntax pair originating + ;; from template structure. For example (x,y are pvars): + ;; (syntax/loc loc-stx (1 2 3)) => relocate + ;; (syntax/loc loc-stx y) => don't relocate + ;; (syntax/loc loc-stx (x ... . y) => relocate iff at least one x! + ;; Deciding whether to relocate after the fact is hard. But with explicit + ;; t-resyntax, it's much easier. + + ;; relocate-guide : Syntax Guide Id -> Guide + (define (relocate-guide ctx g0 loc-id) + (define (loop g) + (define gtag (car g)) + (cond [(guide-is? g 't-resyntax) + `(t-resyntax ,loc-id . ,(cddr g))] + [(quote-syntax-guide? g) + `(t-relocate ,g ,loc-id)] + [(guide-is? g 't-subst) + `(t-subst ,loc-id . ,(cddr g))] + ;; ---- + [(guide-is? g 't-orelse) + `(t-orelse ,(loop (cadr g)) ,(loop (caddr g)))] + ;; ---- + ;; Nothing else should be relocated + [else g])) + (loop g0)) + + ;; ---------------------------------------- + + ;; do-template : Syntax Syntax Id/#f Boolean -> Syntax + (define (do-template ctx tstx loc-id stx?) + (define-values (pre-guide disappeared-uses) + (parse-template ctx tstx stx?)) + (define guide (if loc-id (relocate-guide ctx pre-guide loc-id) pre-guide)) + (define code (syntax-arm (datum->syntax here-stx guide ctx))) + (syntax-property code 'disappeared-use (map syntax-local-introduce disappeared-uses))) + ) + +(define-syntax (syntax stx) + (define s (syntax->list stx)) + (if (and (list? s) (= (length s) 2)) + (do-template stx (cadr s) #f #t) + (raise-syntax-error #f "bad syntax" stx))) + +(define-syntax (syntax/loc stx) + (define s (syntax->list stx)) + (if (and (list? s) (= (length s) 3)) + (let ([loc-id (quote-syntax loc)]) + (define code + `(let ([,loc-id (check-loc (quote ,(car s)) ,(cadr s))]) + ,(do-template stx (caddr s) loc-id #t))) + (syntax-arm (datum->syntax here-stx code stx))) + (raise-syntax-error #f "bad syntax" stx))) + +(define-syntax (datum stx) + (define s (syntax->list stx)) + (if (and (list? s) (= (length s) 2)) + (do-template stx (cadr s) #f #f) + (raise-syntax-error #f "bad syntax" stx))) + +;; check-loc : Symbol Any -> (U Syntax #f) +;; Raise exn if not syntax. Returns same syntax if suitable for srcloc +;; (ie, if at least syntax-source or syntax-position set), #f otherwise. +(define (check-loc who x) + (if (syntax? x) + (if (or (syntax-source x) (syntax-position x)) + x + #f) + (raise-argument-error who "syntax?" x))) + +;; ============================================================ +;; Run-time support + +;; (t-dots cons? hguide iter-vars src-vars head-datum at-stx) : Expr[(Listof Syntax)] +(define-syntax (t-dots stx) + (define s (syntax->list stx)) + (define cons? (syntax-e (list-ref s 1))) + (define head (list-ref s 2)) + (define iter-vars (syntax->list (list-ref s 3))) + (define src-exprs (syntax->list (list-ref s 4))) + (define in-stx (list-ref s 5)) + (define at-stx (list-ref s 6)) + (define code + `(let ,(map list iter-vars src-exprs) + ,(if (> (length iter-vars) 1) `(check-same-length ,in-stx ,at-stx . ,iter-vars) '(void)) + ,(if cons? + `(map (lambda ,iter-vars ,head) . ,iter-vars) + `(apply append (map (lambda ,iter-vars ,head) . ,iter-vars))))) + (datum->syntax here-stx code stx)) + +(define-syntaxes (t-orelse h-orelse) + (let () + (define (orelse-transformer stx) + (define s (syntax->list stx)) + (datum->syntax here-stx + `(t-orelse* (lambda () ,(cadr s)) (lambda () ,(caddr s))))) + (values orelse-transformer orelse-transformer))) + +(#%require (rename '#%kernel t-quote quote) + (rename '#%kernel t-quote-syntax quote-syntax) + (rename '#%kernel t-var #%expression) + (rename '#%kernel t-check-var #%expression) + ;; (rename '#%kernel t-append append) + (rename '#%kernel t-list list) + (rename '#%kernel t-list* list*) + (rename '#%kernel t-escaped #%expression) + (rename '#%kernel t-vector list->vector) + (rename '#%kernel t-box box-immutable) + (rename '#%kernel h-t list)) + +(begin-encourage-inline + +(define (t-append xs ys) (if (null? ys) xs (append xs ys))) +(define (t-resyntax loc stx g) (datum->syntax stx g (or loc stx) stx)) +(define (t-relocate g loc) (datum->syntax g (syntax-e g) (or loc g) g)) +(define (t-orelse* g1 g2) + ((let/ec escape + (with-continuation-mark + absent-pvar-escape-key + (lambda () (escape g2)) + (let ([v (g1)]) (lambda () v)))))) +(define (t-struct key g) (apply make-prefab-struct key g)) +(define (t-metafun mf g stx) + (mf (datum->syntax stx (cons (stx-car stx) g) stx stx))) +(define (h-splice g in-stx at-stx) + (if (stx-list? g) (stx->list g) (error/splice g in-stx at-stx))) + +#| end begin-encourage-inline |#) + +;; t-subst : Syntax/#f Syntax Substs Any ... -> Syntax +;; where Substs = '() | (cons Nat Substs) | (list* (U 'tail 'append 'recur) Nat Substs) +;; There is one arg for each index in substs. See also defn of Guide above. +(define (t-subst loc stx substs . args) + (define (loop/mode s i mode seek substs args) + (cond [(< i seek) (cons (car s) (loop/mode (cdr s) (add1 i) mode seek substs args))] + [(eq? mode #f) (cons (car args) (loop (cdr s) (add1 i) substs (cdr args)))] + [(eq? mode 'tail) (car args)] + [(eq? mode 'append) (append (car args) (loop (cdr s) (add1 i) substs (cdr args)))] + [(eq? mode 'recur) (cons (apply t-subst #f (car s) (car args)) + (loop (cdr s) (add1 i) substs (cdr args)))])) + (define (loop s i substs args) + (cond [(null? substs) s] + [(symbol? (car substs)) + (loop/mode s i (car substs) (cadr substs) (cddr substs) args)] + [else (loop/mode s i #f (car substs) (cdr substs) args)])) + (define v (loop (syntax-e stx) 0 substs args)) + (datum->syntax stx v (or loc stx) stx)) + +(define absent-pvar-escape-key (gensym 'absent-pvar-escape)) + +;; signal-absent-pvar : -> escapes or #f +;; Note: Only escapes if in ~? form. +(define (signal-absent-pvar) + (let ([escape (continuation-mark-set-first #f absent-pvar-escape-key)]) + (if escape (escape) #f))) + +;; error/splice : Any Stx Stx -> (escapes) +(define (error/splice r in-stx at-stx) + (raise-syntax-error 'syntax + (format "splicing template did not produce a syntax list\n got: ~e" r) in-stx at-stx)) + +;; check-same-length : Stx Stx List ... -> Void +(define check-same-length + (case-lambda + [(in at a) (void)] + [(in at a b) + (if (= (length a) (length b)) + (void) + (raise-syntax-error 'syntax "incompatible ellipsis match counts for template" + (list in '...) at))] + [(in at a . bs) + (define alen (length a)) + (for-each (lambda (b) + (if (= alen (length b)) + (void) + (raise-syntax-error 'syntax "incompatible ellipsis match counts for template" + (list in '...) at))) + bs)])) + +) diff --git a/7-4/racket/collects/racket/private/with-stx.rkt b/7-4/racket/collects/racket/private/with-stx.rkt new file mode 100644 index 0000000..1e22edc --- /dev/null +++ b/7-4/racket/collects/racket/private/with-stx.rkt @@ -0,0 +1,99 @@ +;;---------------------------------------------------------------------- +;; with-syntax, generate-temporaries + +(module with-stx '#%kernel + (#%require racket/private/stx racket/private/small-scheme "stxcase.rkt" + (for-syntax '#%kernel racket/private/stx "stxcase.rkt" "stxloc.rkt" + racket/private/gen-temp racket/private/sc racket/private/qq-and-or racket/private/cond)) + + (-define (with-syntax-fail stx) + (raise-syntax-error + 'with-syntax + "binding match failed" + stx)) + + (-define (with-datum-fail stx) + (raise-syntax-error + 'with-datum + "binding match failed" + stx)) + + ;; Partly from Dybvig + (begin-for-syntax + (define-values (gen-with-syntax) + (let ([here-stx (quote-syntax here)]) + (lambda (x s-exp?) + (syntax-case x () + ((_ () e1 e2 ...) + (syntax/loc x (let () e1 e2 ...))) + ((_ ((out in) ...) e1 e2 ...) + (let ([ins (syntax->list (syntax (in ...)))]) + ;; Check for duplicates or other syntax errors: + (get-match-vars (syntax _) x (syntax (out ...)) null) + ;; Generate temps and contexts: + (let ([tmps (map (lambda (x) (gen-temp-id 'ws)) ins)] + [heres (map (lambda (x) + (datum->syntax + x + 'here + x)) + ins)] + [outs (syntax->list (syntax (out ...)))]) + ;; Let-bind RHSs, then build up nested syntax-cases: + (datum->syntax + here-stx + `(let ,(map (lambda (tmp here in) + `[,tmp ,(if s-exp? + in + `(datum->syntax + (quote-syntax ,here) + ,in))]) + tmps heres ins) + ,(let loop ([tmps tmps][outs outs]) + (cond + [(null? tmps) + (syntax (begin e1 e2 ...))] + [else `(syntax-case** #f #t ,(car tmps) () ,(if s-exp? 'eq? 'free-identifier=?) ,s-exp? + [,(car outs) ,(loop (cdr tmps) + (cdr outs))] + [_ (,(if s-exp? 'with-datum-fail 'with-syntax-fail) + ;; Minimize the syntax structure we keep: + (quote-syntax ,(datum->syntax + #f + (syntax->datum (car outs)) + (car outs))))])]))) + x))))))))) + + (-define-syntax with-syntax (lambda (stx) (gen-with-syntax stx #f))) + (-define-syntax with-datum (lambda (stx) (gen-with-syntax stx #t))) + + (-define counter 0) + (-define (append-number s) + (set! counter (add1 counter)) + (string->symbol (format "~a~s" s counter))) + + (-define (generate-temporaries sl) + (unless (stx-list? sl) + (raise-argument-error + 'generate-temporaries + "(or/c list? syntax->list)" + sl)) + (let ([l (stx->list sl)]) + (map (lambda (x) + ((make-syntax-introducer) + (cond + [(symbol? x) + (datum->syntax #f (append-number x))] + [(string? x) + (datum->syntax #f (append-number x))] + [(keyword? x) + (datum->syntax #f (append-number (keyword->string x)))] + [(identifier? x) + (datum->syntax #f (append-number (syntax-e x)))] + [(and (syntax? x) (keyword? (syntax-e x))) + (datum->syntax #f (append-number (keyword->string (syntax-e x))))] + [else + (datum->syntax #f (append-number 'temp))]))) + l))) + + (#%provide with-syntax with-datum generate-temporaries)) diff --git a/7-4/racket/collects/racket/syntax.rkt b/7-4/racket/collects/racket/syntax.rkt new file mode 100644 index 0000000..85c41d1 --- /dev/null +++ b/7-4/racket/collects/racket/syntax.rkt @@ -0,0 +1,216 @@ +#lang racket/base +(require (only-in "stxloc.rkt" syntax-case) + stxparse-info/current-pvars + (for-syntax racket/base + racket/private/sc + auto-syntax-e/utils)) +(provide define/with-syntax + + current-recorded-disappeared-uses + with-disappeared-uses + syntax-local-value/record + record-disappeared-uses + + format-symbol + format-id + + current-syntax-context + wrong-syntax + + generate-temporary + internal-definition-context-apply + syntax-local-eval + with-syntax*) + +;; == Defining pattern variables == + +(define-syntax (define/with-syntax stx) + (syntax-case stx () + [(define/with-syntax pattern rhs) + (let* ([pvar-env (get-match-vars #'define/with-syntax + stx + #'pattern + '())] + [depthmap (for/list ([x pvar-env]) + (let loop ([x x] [d 0]) + (if (pair? x) + (loop (car x) (add1 d)) + (cons x d))))] + [pvars (map car depthmap)] + [depths (map cdr depthmap)] + [mark (make-syntax-introducer)]) + (with-syntax ([(pvar ...) pvars] + [(depth ...) depths] + [(valvar ...) (generate-temporaries pvars)]) + #'(begin (define-values (valvar ...) + (with-syntax ([pattern rhs]) + (values (pvar-value pvar) ...))) + (define-syntax pvar + (make-auto-pvar 'depth (quote-syntax valvar))) + ... + (define-pvars pvar ...))))])) +;; Ryan: alternative name: define/syntax-pattern ?? + +;; auxiliary macro +(define-syntax (pvar-value stx) + (syntax-case stx () + [(_ pvar) + (identifier? #'pvar) + (let ([mapping (syntax-local-value #'pvar)]) + (unless (syntax-pattern-variable? mapping) + (raise-syntax-error #f "not a pattern variable" #'pvar)) + (syntax-mapping-valvar mapping))])) + + +;; == Disappeared uses == + +(define current-recorded-disappeared-uses (make-parameter #f)) + +(define-syntax-rule (with-disappeared-uses body-expr ... stx-expr) + (let-values ([(stx disappeared-uses) + (parameterize ((current-recorded-disappeared-uses null)) + (let ([result (let () body-expr ... stx-expr)]) + (values result (current-recorded-disappeared-uses))))]) + (syntax-property stx + 'disappeared-use + (append (or (syntax-property stx 'disappeared-use) null) + disappeared-uses)))) + +(define (syntax-local-value/record id pred) + (unless (identifier? id) + (raise-argument-error 'syntax-local-value/record + "identifier?" + 0 id pred)) + (unless (and (procedure? pred) + (procedure-arity-includes? pred 1)) + (raise-argument-error 'syntax-local-value/record + "(-> any/c boolean?)" + 1 id pred)) + (let ([value (syntax-local-value id (lambda () #f))]) + (and (pred value) + (begin (record-disappeared-uses (list id)) + value)))) + +(define (record-disappeared-uses ids [intro? (syntax-transforming?)]) + (cond + [(identifier? ids) (record-disappeared-uses (list ids) intro?)] + [(and (list? ids) (andmap identifier? ids)) + (let ([uses (current-recorded-disappeared-uses)]) + (when uses + (current-recorded-disappeared-uses + (append + (if intro? + (map syntax-local-introduce ids) + ids) + uses))))] + [else (raise-argument-error 'record-disappeared-uses + "(or/c identifier? (listof identifier?))" + ids)])) + + +;; == Identifier formatting == + +(define (format-id lctx + #:source [src #f] + #:props [props #f] + #:cert [cert #f] + fmt . args) + (define (convert x) (->atom x 'format-id)) + (check-restricted-format-string 'format-id fmt) + (let* ([args (map convert args)] + [str (apply format fmt args)] + [sym (string->symbol str)]) + (datum->syntax lctx sym src props cert))) +;; Eli: This looks very *useful*, but I'd like to see it more convenient to +;; "preserve everything". Maybe add a keyword argument that when #t makes +;; all the others use values lctx, and when syntax makes the others use that +;; syntax? +;; Finally, if you get to add this, then another useful utility in the same +;; spirit is one that concatenates symbols and/or strings and/or identifiers +;; into a new identifier. I considered something like that, which expects a +;; single syntax among its inputs, and will use it for the context etc, or +;; throw an error if there's more or less than 1. + +(define (format-symbol fmt . args) + (define (convert x) (->atom x 'format-symbol)) + (check-restricted-format-string 'format-symbol fmt) + (let ([args (map convert args)]) + (string->symbol (apply format fmt args)))) + +(define (restricted-format-string? fmt) + (regexp-match? #rx"^(?:[^~]|~[aAn~%])*$" fmt)) + +(define (check-restricted-format-string who fmt) + (unless (restricted-format-string? fmt) + (raise-arguments-error who + (format "format string should have ~a placeholders" + fmt) + "format string" fmt))) + +(define (->atom x err) + (cond [(string? x) x] + [(symbol? x) x] + [(identifier? x) (syntax-e x)] + [(keyword? x) (keyword->string x)] + [(number? x) x] + [(char? x) x] + [else (raise-argument-error err + "(or/c string? symbol? identifier? keyword? char? number?)" + x)])) + + +;; == Error reporting == + +(define current-syntax-context + (make-parameter #f + (lambda (new-value) + (unless (or (syntax? new-value) (eq? new-value #f)) + (raise-argument-error 'current-syntax-context + "(or/c syntax? #f)" + new-value)) + new-value))) + +(define (wrong-syntax stx #:extra [extras null] format-string . args) + (unless (or (eq? stx #f) (syntax? stx)) + (raise-argument-error 'wrong-syntax "(or/c syntax? #f)" 0 (list* stx format-string args))) + (let* ([ctx (current-syntax-context)] + [blame (and (syntax? ctx) (syntax-property ctx 'report-error-as))]) + (raise-syntax-error (if (symbol? blame) blame #f) + (apply format format-string args) + ctx + stx + extras))) +;; Eli: The `report-error-as' thing seems arbitrary to me. + + +;; == Other utilities == + +;; generate-temporary : any -> identifier +(define (generate-temporary [stx 'g]) + (car (generate-temporaries (list stx)))) + +;; Included for backwards compatibility. +(define (internal-definition-context-apply intdefs stx) + ; The old implementation of internal-definition-context-apply implicitly converted its stx argument + ; to syntax, which some things seem to (possibly unintentionally) rely on, so replicate that + ; behavior here: + (internal-definition-context-introduce intdefs (datum->syntax #f stx) 'add)) + +(define (syntax-local-eval stx [intdefs '()]) + (let* ([name (generate-temporary)] + [intdef (syntax-local-make-definition-context)]) + (syntax-local-bind-syntaxes (list name) + #`(call-with-values (lambda () #,stx) list) + intdef + intdefs) + (apply values + (syntax-local-value (internal-definition-context-introduce intdef name) + #f intdef)))) + +(define-syntax (with-syntax* stx) + (syntax-case stx () + [(_ () body ...) (syntax/loc stx (let () body ...))] + [(_ (cl) body ...) (syntax/loc stx (with-syntax (cl) body ...))] + [(_ (cl cls ...) body ...) + (with-syntax ([with-syntax/rest (syntax/loc stx (with-syntax* (cls ...) body ...))]) + (syntax/loc stx (with-syntax (cl) with-syntax/rest)))])) diff --git a/7-4/racket/collects/syntax/parse.rkt b/7-4/racket/collects/syntax/parse.rkt new file mode 100644 index 0000000..643de4d --- /dev/null +++ b/7-4/racket/collects/syntax/parse.rkt @@ -0,0 +1,33 @@ +#lang racket/base +(require (for-syntax racket/base) + racket/contract/base + "parse/pre.rkt" + "parse/experimental/provide.rkt" + "parse/experimental/contract.rkt") +(provide (except-out (all-from-out "parse/pre.rkt") + static) + expr/c) +(provide-syntax-class/contract + [static (syntax-class/c [(-> any/c any/c) (or/c string? symbol? #f)])]) + +(begin-for-syntax + (require racket/contract/base + syntax/parse/private/residual-ct) + (provide pattern-expander? + (contract-out + [prop:syntax-class + (struct-type-property/c (or/c identifier? (-> any/c identifier?)))] + [pattern-expander + (-> (-> syntax? syntax?) pattern-expander?)] + [prop:pattern-expander + (struct-type-property/c (-> pattern-expander? (-> syntax? syntax?)))] + [syntax-local-syntax-parse-pattern-introduce + (-> syntax? syntax?)])) + + (require (only-in (for-template syntax/parse) pattern-expander)) + #;(define pattern-expander + (let () + (struct pattern-expander (proc) #:transparent + #:omit-define-syntaxes + #:property prop:pattern-expander (λ (this) (pattern-expander-proc this))) + pattern-expander))) diff --git a/7-4/racket/collects/syntax/parse/debug.rkt b/7-4/racket/collects/syntax/parse/debug.rkt new file mode 100644 index 0000000..c62849b --- /dev/null +++ b/7-4/racket/collects/syntax/parse/debug.rkt @@ -0,0 +1,129 @@ +#lang racket/base +(require (for-syntax racket/base + syntax/stx + racket/syntax + syntax/parse/private/rep-data + "private/rep.rkt" + syntax/parse/private/kws) + racket/list + racket/pretty + "../parse.rkt" + (except-in stxparse-info/parse/private/residual + prop:syntax-class + prop:pattern-expander + syntax-local-syntax-parse-pattern-introduce) + "private/runtime.rkt" + "private/runtime-progress.rkt" + "private/runtime-report.rkt" + syntax/parse/private/kws) + +;; No lazy loading for this module's dependencies. + +(provide syntax-class-parse + syntax-class-attributes + syntax-class-arity + syntax-class-keywords + + debug-rhs + debug-pattern + debug-parse + debug-syntax-parse!) + +(define-syntax (syntax-class-parse stx) + (syntax-case stx () + [(_ s x arg ...) + (parameterize ((current-syntax-context stx)) + (with-disappeared-uses + (let* ([argu (parse-argu (syntax->list #'(arg ...)) #:context stx)] + [stxclass + (get-stxclass/check-arity #'s stx + (length (arguments-pargs argu)) + (arguments-kws argu))] + [attrs (stxclass-attrs stxclass)]) + (with-syntax ([parser (stxclass-parser stxclass)] + [argu argu] + [(name ...) (map attr-name attrs)] + [(depth ...) (map attr-depth attrs)]) + #'(let ([fh (lambda (undos fs) fs)]) + (app-argu parser x x (ps-empty x x) #f null fh fh #f + (lambda (fh undos . attr-values) + (map vector '(name ...) '(depth ...) attr-values)) + argu))))))])) + +(define-syntaxes (syntax-class-attributes + syntax-class-arity + syntax-class-keywords) + (let () + (define ((mk handler) stx) + (syntax-case stx () + [(_ s) + (parameterize ((current-syntax-context stx)) + (with-disappeared-uses + (handler (get-stxclass #'s))))])) + (values (mk (lambda (s) + (let ([attrs (stxclass-attrs s)]) + (with-syntax ([(a ...) (map attr-name attrs)] + [(d ...) (map attr-depth attrs)]) + #'(quote ((a d) ...)))))) + (mk (lambda (s) + (let ([a (stxclass-arity s)]) + #`(to-procedure-arity '#,(arity-minpos a) '#,(arity-maxpos a))))) + (mk (lambda (s) + (let ([a (stxclass-arity s)]) + #`(values '#,(arity-minkws a) '#,(arity-maxkws a)))))))) + +(define-syntax (debug-rhs stx) + (syntax-case stx () + [(debug-rhs rhs) + (let ([rhs (parse-rhs #'rhs #f #:context stx)]) + #`(quote #,rhs))])) + +(define-syntax (debug-pattern stx) + (syntax-case stx () + [(debug-pattern p . rest) + (let-values ([(rest pattern defs) + (parse-pattern+sides #'p #'rest + #:splicing? #f + #:decls (new-declenv null) + #:context stx)]) + (unless (stx-null? rest) + (raise-syntax-error #f "unexpected terms" stx rest)) + #`(quote ((definitions . #,defs) + (pattern #,pattern))))])) + +(define-syntax-rule (debug-parse x p ...) + (let/ec escape + (parameterize ((current-failure-handler + (lambda (_ fs) + (define-values (raw-fs-sexpr maximal-fs-sexpr) (fs->sexprs fs)) + (escape + `(parse-failure + #:raw-failures + ,raw-fs-sexpr + #:maximal-failures + ,maximal-fs-sexpr))))) + (syntax-parse x [p 'success] ...)))) + +(define (fs->sexprs fs) + (let* ([raw-fs (map invert-failure (reverse (flatten fs)))] + [selected-groups (maximal-failures raw-fs)]) + (values (failureset->sexpr raw-fs) + (let ([selected (map (lambda (fs) + (cons 'progress-class + (map failure->sexpr fs))) + selected-groups)]) + (if (= (length selected) 1) + (car selected) + (cons 'union selected)))))) + +(define (debug-syntax-parse!) + (define old-failure-handler (current-failure-handler)) + (current-failure-handler + (lambda (ctx fs) + (define-values (raw-fs-sexpr maximal-fs-sexpr) (fs->sexprs fs)) + (eprintf "*** syntax-parse debug info ***\n") + (eprintf "Raw failures:\n") + (pretty-write raw-fs-sexpr (current-error-port)) + (eprintf "Maximal failures:\n") + (pretty-write maximal-fs-sexpr (current-error-port)) + (old-failure-handler ctx fs)))) diff --git a/7-4/racket/collects/syntax/parse/define.rkt b/7-4/racket/collects/syntax/parse/define.rkt new file mode 100644 index 0000000..6392c75 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/define.rkt @@ -0,0 +1,20 @@ +#lang racket/base +(require (for-syntax racket/base + stxparse-info/parse + "private/sc.rkt")) +(provide define-simple-macro + define-syntax-parser + (for-syntax (all-from-out stxparse-info/parse))) + +(define-syntax (define-simple-macro stx) + (syntax-parse stx + [(define-simple-macro (macro:id . pattern) . body) + #`(define-syntax macro + (syntax-parser/template + #,((make-syntax-introducer) stx) + [((~var macro id) . pattern) . body]))])) + +(define-simple-macro (define-syntax-parser macro:id option-or-clause ...) + (define-syntax macro + (syntax-parser option-or-clause ...))) + diff --git a/7-4/racket/collects/syntax/parse/experimental/contract.rkt b/7-4/racket/collects/syntax/parse/experimental/contract.rkt new file mode 100644 index 0000000..b0f932b --- /dev/null +++ b/7-4/racket/collects/syntax/parse/experimental/contract.rkt @@ -0,0 +1,46 @@ +#lang racket/base +(require stxparse-info/parse/pre + "provide.rkt" + syntax/contract + (only-in stxparse-info/parse/private/residual ;; keep abs. path + this-context-syntax + this-role) + racket/contract/base) + +(define not-given (gensym)) + +(define-syntax-class (expr/c ctc-stx + #:arg? [arg? #t] + #:positive [pos-blame 'from-macro] + #:negative [neg-blame 'use-site] + #:macro [macro-name #f] + #:name [expr-name not-given] + #:context [ctx #f] + #:phase [phase (syntax-local-phase-level)]) + #:attributes (c) + #:commit + (pattern y:expr + #:with + c (wrap-expr/c ctc-stx + #'y + #:arg? arg? + #:positive pos-blame + #:negative neg-blame + #:name (if (eq? expr-name not-given) + this-role + expr-name) + #:macro macro-name + #:context (or ctx (this-context-syntax)) + #:phase phase))) + +(provide-syntax-class/contract + [expr/c (syntax-class/c (syntax?) + (#:arg? any/c + #:positive (or/c syntax? string? module-path-index? + 'from-macro 'use-site 'unknown) + #:negative (or/c syntax? string? module-path-index? + 'from-macro 'use-site 'unknown) + #:name (or/c identifier? string? symbol? #f) + #:macro (or/c identifier? string? symbol? #f) + #:context (or/c syntax? #f) + #:phase exact-integer?))]) diff --git a/7-4/racket/collects/syntax/parse/experimental/eh.rkt b/7-4/racket/collects/syntax/parse/experimental/eh.rkt new file mode 100644 index 0000000..f8e1b09 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/experimental/eh.rkt @@ -0,0 +1,5 @@ +#lang racket/base +(require "../private/sc.rkt" + syntax/parse/private/keywords) +(provide ~eh-var + define-eh-alternative-set) diff --git a/7-4/racket/collects/syntax/parse/experimental/private/substitute.rkt b/7-4/racket/collects/syntax/parse/experimental/private/substitute.rkt new file mode 100644 index 0000000..7bc35af --- /dev/null +++ b/7-4/racket/collects/syntax/parse/experimental/private/substitute.rkt @@ -0,0 +1 @@ +#lang racket/base diff --git a/7-4/racket/collects/syntax/parse/experimental/provide.rkt b/7-4/racket/collects/syntax/parse/experimental/provide.rkt new file mode 100644 index 0000000..173d81e --- /dev/null +++ b/7-4/racket/collects/syntax/parse/experimental/provide.rkt @@ -0,0 +1,156 @@ +#lang racket/base +(require racket/contract/base + racket/contract/combinator + syntax/location + (for-syntax racket/base + racket/syntax + syntax/parse/private/minimatch + stxparse-info/parse/pre + syntax/parse/private/residual-ct ;; keep abs. path + syntax/parse/private/kws + syntax/contract)) +(provide provide-syntax-class/contract + syntax-class/c + splicing-syntax-class/c) + +;; FIXME: +;; - seems to get first-requiring-module wrong, not surprising +;; - extend to contracts on attributes? +;; - syntax-class/c etc just a made-up name, for now +;; (connect to dynamic syntax-classes, eventually) + +(define-syntaxes (syntax-class/c splicing-syntax-class/c) + (let ([nope + (lambda (stx) + (raise-syntax-error #f "not within `provide-syntax-class/contract form" stx))]) + (values nope nope))) + +(begin-for-syntax + (define-struct ctcrec (mpcs mkws mkwcs opcs okws okwcs) #:prefab + #:omit-define-syntaxes)) + +(begin-for-syntax + ;; do-one-contract : stx id stxclass ctcrec id -> stx + (define (do-one-contract stx scname stxclass rec pos-module-source) + ;; First, is the contract feasible? + (match (stxclass-arity stxclass) + [(arity minpos maxpos minkws maxkws) + (let* ([minpos* (length (ctcrec-mpcs rec))] + [maxpos* (+ minpos* (length (ctcrec-opcs rec)))] + [minkws* (sort (map syntax-e (ctcrec-mkws rec)) keyword<?)] + [maxkws* (sort (append minkws* (map syntax-e (ctcrec-okws rec))) keyword<?)]) + (define (err msg . args) + (apply wrong-syntax scname msg args)) + (unless (<= minpos minpos*) + (err (string-append "expected a syntax class with at most ~a " + "required positional arguments, got one with ~a") + minpos* minpos)) + (unless (<= maxpos* maxpos) + (err (string-append "expected a syntax class with at least ~a " + "total positional arguments (required and optional), " + "got one with ~a") + maxpos* maxpos)) + (unless (null? (diff/sorted/eq minkws minkws*)) + (err (string-append "expected a syntax class with at most the " + "required keyword arguments ~a, got one with ~a") + (join-sep (map kw->string minkws*) "," "and") + (join-sep (map kw->string minkws) "," "and"))) + (unless (null? (diff/sorted/eq maxkws* maxkws)) + (err (string-append "expected a syntax class with at least the optional " + "keyword arguments ~a, got one with ~a") + (join-sep (map kw->string maxkws*) "," "and") + (join-sep (map kw->string maxkws) "," "and"))) + (with-syntax ([scname scname] + [#s(stxclass name arity attrs parser splicing? opts inline) + stxclass] + [#s(ctcrec (mpc ...) (mkw ...) (mkwc ...) + (opc ...) (okw ...) (okwc ...)) + rec] + [arity* (arity minpos* maxpos* minkws* maxkws*)] + [(parser-contract contracted-parser contracted-scname) + (generate-temporaries #`(contract parser #,scname))]) + (with-syntax ([(mpc-id ...) (generate-temporaries #'(mpc ...))] + [(mkwc-id ...) (generate-temporaries #'(mkwc ...))] + [(opc-id ...) (generate-temporaries #'(opc ...))] + [(okwc-id ...) (generate-temporaries #'(okwc ...))]) + (with-syntax ([((mkw-c-part ...) ...) #'((mkw mkwc-id) ...)] + [((okw-c-part ...) ...) #'((okw okwc-id) ...)] + [((mkw-name-part ...) ...) #'((mkw ,(contract-name mkwc-id)) ...)] + [((okw-name-part ...) ...) #'((okw ,(contract-name okwc-id)) ...)]) + #`(begin + (define parser-contract + (let ([mpc-id mpc] ... + [mkwc-id mkwc] ... + [opc-id opc] ... + [okwc-id okwc] ...) + (rename-contract + (->* (any/c any/c any/c any/c any/c any/c any/c any/c any/c + mpc-id ... mkw-c-part ... ...) + (okw-c-part ... ...) + any) + `(,(if 'splicing? 'splicing-syntax-class/c 'syntax-class/c) + [,(contract-name mpc-id) ... mkw-name-part ... ...] + [okw-name-part ... ...])))) + (define-module-boundary-contract contracted-parser + parser parser-contract #:pos-source #,pos-module-source) + (define-syntax contracted-scname + (make-stxclass + (quote-syntax name) + 'arity* + 'attrs + (quote-syntax contracted-parser) + 'splicing? + 'opts #f)) ;; must disable inlining + (provide (rename-out [contracted-scname scname])))))))]))) + +(define-syntax (provide-syntax-class/contract stx) + + (define-syntax-class stxclass-ctc + #:description "syntax-class/c or splicing-syntax-class/c form" + #:literals (syntax-class/c splicing-syntax-class/c) + #:attributes (rec) + #:commit + (pattern ((~or syntax-class/c splicing-syntax-class/c) + mand:ctclist + (~optional opt:ctclist)) + #:attr rec (make-ctcrec (attribute mand.pc.c) + (attribute mand.kw) + (attribute mand.kwc.c) + (or (attribute opt.pc.c) '()) + (or (attribute opt.kw) '()) + (or (attribute opt.kwc.c) '())))) + + (define-syntax-class ctclist + #:attributes ([pc.c 1] [kw 1] [kwc.c 1]) + #:commit + (pattern ((~or pc:expr (~seq kw:keyword kwc:expr)) ...) + #:with (pc.c ...) (for/list ([pc-expr (in-list (syntax->list #'(pc ...)))]) + (wrap-expr/c #'contract? pc-expr)) + #:with (kwc.c ...) (for/list ([kwc-expr (in-list (syntax->list #'(kwc ...)))]) + (wrap-expr/c #'contract? kwc-expr)))) + + (syntax-parse stx + [(_ [scname c:stxclass-ctc] ...) + #:declare scname (static stxclass? "syntax class") + (parameterize ((current-syntax-context stx)) + (with-disappeared-uses + #`(begin (define pos-module-source (quote-module-name)) + #,@(for/list ([scname (in-list (syntax->list #'(scname ...)))] + [stxclass (in-list (attribute scname.value))] + [rec (in-list (attribute c.rec))]) + (do-one-contract stx scname stxclass rec #'pos-module-source)))))])) + +;; Copied from unstable/contract, +;; which requires racket/contract, not racket/contract/base + +;; rename-contract : contract any/c -> contract +;; If the argument is a flat contract, so is the result. +(define (rename-contract ctc name) + (let ([ctc (coerce-contract 'rename-contract ctc)]) + (if (flat-contract? ctc) + (flat-named-contract name (flat-contract-predicate ctc)) + (let* ([ctc-fo (contract-first-order ctc)] + [late-neg-proj (contract-late-neg-projection ctc)]) + (make-contract #:name name + #:late-neg-projection late-neg-proj + #:first-order ctc-fo))))) diff --git a/7-4/racket/collects/syntax/parse/experimental/reflect.rkt b/7-4/racket/collects/syntax/parse/experimental/reflect.rkt new file mode 100644 index 0000000..8f18781 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/experimental/reflect.rkt @@ -0,0 +1,147 @@ +#lang racket/base +(require (for-syntax racket/base + racket/lazy-require + racket/syntax + syntax/parse/private/residual-ct) ;; keep abs.path + racket/contract/base + racket/contract/combinator + syntax/parse/private/minimatch + syntax/parse/private/keywords + "../private/runtime-reflect.rkt" + syntax/parse/private/kws) +(begin-for-syntax + (lazy-require + [syntax/parse/private/rep-data ;; keep abs. path + (get-stxclass)])) +;; FIXME: workaround for phase>0 bug in racket/runtime-path (and thus lazy-require) +;; Without this, dependencies don't get collected. +(require racket/runtime-path (for-meta 2 '#%kernel)) +(define-runtime-module-path-index _unused_ 'syntax/parse/private/rep-data) + +(define-syntax (reify-syntax-class stx) + (if (eq? (syntax-local-context) 'expression) + (syntax-case stx () + [(rsc sc) + (with-disappeared-uses + (let* ([stxclass (get-stxclass #'sc)] + [splicing? (stxclass-splicing? stxclass)]) + (unless (scopts-delimit-cut? (stxclass-opts stxclass)) + (raise-syntax-error #f "cannot reify syntax class with #:no-delimit-cut option" + stx #'sc)) + (with-syntax ([name (stxclass-name stxclass)] + [parser (stxclass-parser stxclass)] + [arity (stxclass-arity stxclass)] + [(#s(attr aname adepth _) ...) (stxclass-attrs stxclass)] + [ctor + (if splicing? + #'reified-splicing-syntax-class + #'reified-syntax-class)]) + #'(ctor 'name parser 'arity '((aname adepth) ...)))))]) + #`(#%expression #,stx))) + +(define (reified-syntax-class-arity r) + (match (reified-arity r) + [(arity minpos maxpos _ _) + (to-procedure-arity minpos maxpos)])) + +(define (reified-syntax-class-keywords r) + (match (reified-arity r) + [(arity _ _ minkws maxkws) + (values minkws maxkws)])) + +(define (reified-syntax-class-attributes r) + (reified-signature r)) + +(define reified-syntax-class-curry + (make-keyword-procedure + (lambda (kws1 kwargs1 r . rest1) + (match r + [(reified name parser arity1 sig) + (let () + (check-curry arity1 (length rest1) kws1 + (lambda (msg) + (raise-mismatch-error 'reified-syntax-class-curry + (string-append msg ": ") r))) + (let* ([curried-arity + (match arity1 + [(arity minpos maxpos minkws maxkws) + (let* ([rest1-length (length rest1)] + [minpos* (- minpos rest1-length)] + [maxpos* (- maxpos rest1-length)] + [minkws* (sort (remq* kws1 minkws) keyword<?)] + [maxkws* (sort (remq* kws1 maxkws) keyword<?)]) + (arity minpos* maxpos* minkws* maxkws*))])] + [curried-parser + (make-keyword-procedure + (lambda (kws2 kwargs2 x cx pr es undos fh cp rl success . rest2) + (let-values ([(kws kwargs) (merge2 kws1 kws2 kwargs1 kwargs2)]) + (keyword-apply parser kws kwargs x cx pr es undos fh cp rl success + (append rest1 rest2)))))] + [ctor + (cond [(reified-syntax-class? r) + reified-syntax-class] + [(reified-splicing-syntax-class? r) + reified-splicing-syntax-class] + [else + (error 'curry-reified-syntax-class "INTERNAL ERROR: ~e" r)])]) + (ctor name curried-parser curried-arity sig)))])))) + +(define (merge2 kws1 kws2 kwargs1 kwargs2) + (cond [(null? kws1) + (values kws2 kwargs2)] + [(null? kws2) + (values kws1 kwargs1)] + [(keyword<? (car kws1) (car kws2)) + (let-values ([(m-kws m-kwargs) + (merge2 (cdr kws1) kws2 (cdr kwargs1) kwargs2)]) + (values (cons (car kws1) m-kws) (cons (car kwargs1) m-kwargs)))] + [else + (let-values ([(m-kws m-kwargs) + (merge2 kws1 (cdr kws2) kwargs1 (cdr kwargs2))]) + (values (cons (car kws2) m-kws) (cons (car kwargs2) m-kwargs)))])) + +;; ---- + +(provide reify-syntax-class + ~reflect + ~splicing-reflect) + +(provide/contract + [reified-syntax-class? + (-> any/c boolean?)] + [reified-splicing-syntax-class? + (-> any/c boolean?)] + [reified-syntax-class-attributes + (-> (or/c reified-syntax-class? reified-splicing-syntax-class?) + (listof (list/c symbol? exact-nonnegative-integer?)))] + [reified-syntax-class-arity + (-> (or/c reified-syntax-class? reified-splicing-syntax-class?) + procedure-arity?)] + [reified-syntax-class-keywords + (-> (or/c reified-syntax-class? reified-splicing-syntax-class?) + (values (listof keyword?) + (listof keyword?)))] + [reified-syntax-class-curry + (make-contract #:name '(->* ((or/c reified-syntax-class? reified-splicing-syntax-class/c)) + (#:<kw> any/c ...) + #:rest list? + (or/c reified-syntax-class? reified-splicing-syntax-class/c)) + #:late-neg-projection + (lambda (blame) + (let ([check-reified + ((contract-late-neg-projection + (or/c reified-syntax-class? reified-splicing-syntax-class?)) + (blame-swap blame))]) + (lambda (f neg-party) + (if (and (procedure? f) + (procedure-arity-includes? f 1)) + (make-keyword-procedure + (lambda (kws kwargs r . args) + (keyword-apply f kws kwargs (check-reified r neg-party) args))) + (raise-blame-error + blame #:missing-party neg-party + f + "expected a procedure of at least one argument, given ~e" + f))))) + #:first-order + (lambda (f) (procedure? f)))]) diff --git a/7-4/racket/collects/syntax/parse/experimental/specialize.rkt b/7-4/racket/collects/syntax/parse/experimental/specialize.rkt new file mode 100644 index 0000000..ad569c1 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/experimental/specialize.rkt @@ -0,0 +1,40 @@ +#lang racket/base +(require (for-syntax racket/base + racket/syntax + syntax/parse/private/kws + syntax/parse/private/rep-data + "../private/rep.rkt") + "../private/runtime.rkt") +(provide define-syntax-class/specialize) + +(define-syntax (define-syntax-class/specialize stx) + (parameterize ((current-syntax-context stx)) + (syntax-case stx () + [(dscs header sc-expr) + (with-disappeared-uses + (let-values ([(name formals arity) + (let ([p (check-stxclass-header #'header stx)]) + (values (car p) (cadr p) (caddr p)))] + [(target-scname argu) + (let ([p (check-stxclass-application #'sc-expr stx)]) + (values (car p) (cdr p)))]) + (let* ([pos-count (length (arguments-pargs argu))] + [kws (arguments-kws argu)] + [target (get-stxclass/check-arity target-scname target-scname pos-count kws)]) + (with-syntax ([name name] + [formals formals] + [parser (generate-temporary (format-symbol "parser-~a" #'name))] + [splicing? (stxclass-splicing? target)] + [arity arity] + [attrs (stxclass-attrs target)] + [opts (stxclass-opts target)] + [target-parser (stxclass-parser target)] + [argu argu]) + #`(begin (define-syntax name + (stxclass 'name 'arity 'attrs + (quote-syntax parser) + 'splicing? + 'opts #f)) + (define-values (parser) + (lambda (x cx pr es undos fh0 cp0 rl success . formals) + (app-argu target-parser x cx pr es undos fh0 cp0 rl success argu))))))))]))) diff --git a/7-4/racket/collects/syntax/parse/experimental/splicing.rkt b/7-4/racket/collects/syntax/parse/experimental/splicing.rkt new file mode 100644 index 0000000..56abbd5 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/experimental/splicing.rkt @@ -0,0 +1,95 @@ +#lang racket/base +(require (for-syntax racket/base + stxparse-info/parse + racket/lazy-require + syntax/parse/private/kws) + stxparse-info/parse/private/residual) ;; keep abs. path +(provide define-primitive-splicing-syntax-class) + +(begin-for-syntax + (lazy-require + [syntax/parse/private/rep-attrs + (sort-sattrs)])) +;; FIXME: workaround for phase>0 bug in racket/runtime-path (and thus lazy-require) +;; Without this, dependencies don't get collected. +(require racket/runtime-path (for-meta 2 '#%kernel)) +(define-runtime-module-path-index _unused_ 'syntax/parse/private/rep-attrs) + +(define-syntax (define-primitive-splicing-syntax-class stx) + + (define-syntax-class attr + #:commit + (pattern name:id + #:with depth #'0) + (pattern [name:id depth:nat])) + + (syntax-parse stx + [(dssp (name:id param:id ...) + (~or (~once (~seq #:attributes (a:attr ...)) + #:name "attributes declaration") + (~once (~seq #:description description) + #:name "description declaration")) ... + proc:expr) + #'(begin + (define (get-description param ...) + description) + (define parser + (let ([permute (mk-permute '(a.name ...))]) + (lambda (x cx pr es undos fh _cp rl success param ...) + (let ([stx (datum->syntax cx x cx)]) + (let ([result + (let/ec escape + (cons 'ok + (proc stx + (lambda ([msg #f] [stx #f]) + (escape (list 'error msg stx))))))]) + (case (car result) + ((ok) + (apply success + ((mk-check-result pr 'name (length '(a.name ...)) permute x cx undos fh) + (cdr result)))) + ((error) + (let ([es + (es-add-message (cadr result) + (es-add-thing pr (get-description param ...) #f rl es))]) + (fh undos (failure pr es)))))))))) + (define-syntax name + (stxclass 'name (arity (length '(param ...)) (length '(param ...)) '() '()) + (sort-sattrs '(#s(attr a.name a.depth #f) ...)) + (quote-syntax parser) + #t + (scopts (length '(a.name ...)) #t #t #f) + #f)))])) + +(define (mk-permute unsorted-attrs) + (let ([sorted-attrs + (sort unsorted-attrs string<? #:key symbol->string #:cache-keys? #t)]) + (if (equal? unsorted-attrs sorted-attrs) + values + (let* ([pos-table + (for/hasheq ([a (in-list unsorted-attrs)] [i (in-naturals)]) + (values a i))] + [indexes + (for/vector ([a (in-list sorted-attrs)]) + (hash-ref pos-table a))]) + (lambda (result) + (for/list ([index (in-vector indexes)]) + (list-ref result index))))))) + +(define (mk-check-result pr name attr-count permute x cx undos fh) + (lambda (result) + (unless (list? result) + (error name "parser returned non-list")) + (let ([rlength (length result)]) + (unless (= rlength (+ 1 attr-count)) + (error name "parser returned list of wrong length; expected length ~s, got ~e" + (+ 1 attr-count) + result)) + (let ([skip (car result)]) + ;; Compute rest-x & rest-cx from skip + (unless (exact-nonnegative-integer? skip) + (error name "expected exact nonnegative integer for first element of result list, got ~e" + skip)) + (let-values ([(rest-x rest-cx) (stx-list-drop/cx x cx skip)]) + (list* fh undos rest-x rest-cx (ps-add-cdr pr skip) + (permute (cdr result)))))))) diff --git a/7-4/racket/collects/syntax/parse/experimental/template.rkt b/7-4/racket/collects/syntax/parse/experimental/template.rkt new file mode 100644 index 0000000..160eccc --- /dev/null +++ b/7-4/racket/collects/syntax/parse/experimental/template.rkt @@ -0,0 +1,49 @@ +#lang racket/base +(require (for-syntax racket/base + auto-syntax-e/utils) + (only-in racket/private/template + metafunction)) +(provide (rename-out [syntax template] + [syntax/loc template/loc] + [quasisyntax quasitemplate] + [quasisyntax/loc quasitemplate/loc] + [~? ??] + [~@ ?@]) + define-template-metafunction) + +;; ============================================================ +;; Metafunctions + +(define-syntax (define-template-metafunction stx) + (syntax-case stx () + [(dsm (id arg ...) . body) + #'(dsm id (lambda (arg ...) . body))] + [(dsm id expr) + (identifier? #'id) + (with-syntax ([(internal-id) (generate-temporaries #'(id))]) + #'(begin (define internal-id (make-hygienic-metafunction expr)) + (define-syntax id (metafunction (quote-syntax internal-id)))))])) + +(define current-template-metafunction-introducer + (make-parameter (lambda (stx) (if (syntax-transforming?) (syntax-local-introduce stx) stx)))) + +(define old-template-metafunction-introducer + (make-parameter #f)) + +(define (syntax-local-template-metafunction-introduce stx) + (let ([mark (current-template-metafunction-introducer)] + [old-mark (old-template-metafunction-introducer)]) + (unless old-mark + (error 'syntax-local-template-metafunction-introduce + "must be called within the dynamic extent of a template metafunction")) + (mark (old-mark stx)))) + +(define ((make-hygienic-metafunction transformer) stx) + (define mark (make-syntax-introducer)) + (define old-mark (current-template-metafunction-introducer)) + (parameterize ((current-template-metafunction-introducer mark) + (old-template-metafunction-introducer old-mark)) + (define r (call-with-continuation-barrier (lambda () (transformer (mark (old-mark stx)))))) + (unless (syntax? r) + (raise-syntax-error #f "result of template metafunction was not syntax" stx)) + (old-mark (mark r)))) diff --git a/7-4/racket/collects/syntax/parse/lib/function-header.rkt b/7-4/racket/collects/syntax/parse/lib/function-header.rkt new file mode 100644 index 0000000..79e80f3 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/lib/function-header.rkt @@ -0,0 +1,112 @@ +#lang racket/base + +(require "../../parse.rkt" + "../experimental/template.rkt" + racket/dict) + +(provide function-header formal formals) + +(define-syntax-class function-header + (pattern ((~or header:function-header name:id) . args:formals) + #:attr params + (template ((?@ . (?? header.params ())) + . args.params)))) + +(define-syntax-class formals + #:attributes (params) + (pattern (arg:formal ...) + #:attr params #'(arg.name ...) + #:fail-when (check-duplicate-identifier (syntax->list #'params)) + "duplicate argument name" + #:fail-when (check-duplicate (attribute arg.kw) + #:same? (λ (x y) + (and x y (equal? (syntax-e x) + (syntax-e y))))) + "duplicate keyword for argument" + #:fail-when (invalid-option-placement + (attribute arg.name) (attribute arg.default)) + "default-value expression missing") + (pattern (arg:formal ... . rest:id) + #:attr params #'(arg.name ... rest) + #:fail-when (check-duplicate-identifier (syntax->list #'params)) + "duplicate argument name" + #:fail-when (check-duplicate (attribute arg.kw) + #:same? (λ (x y) + (and x y (equal? (syntax-e x) + (syntax-e y))))) + "duplicate keyword for argument" + #:fail-when (invalid-option-placement + (attribute arg.name) (attribute arg.default)) + "default-value expression missing")) + +(define-splicing-syntax-class formal + #:attributes (name kw default) + (pattern name:id + #:attr kw #f + #:attr default #f) + (pattern [name:id default] + #:attr kw #f) + (pattern (~seq kw:keyword name:id) + #:attr default #f) + (pattern (~seq kw:keyword [name:id default]))) + +;; invalid-option-placement : (Listof Id) (Listof Syntax/#f) -> Id/#f +;; Checks for mandatory argument after optional argument; if found, returns +;; identifier of mandatory argument. +(define (invalid-option-placement names defaults) + ;; find-mandatory : (Listof Id) (Listof Syntax/#f) -> Id/#f + ;; Finds first name w/o corresponding default. + (define (find-mandatory names defaults) + (for/first ([name (in-list names)] + [default (in-list defaults)] + #:when (not default)) + name)) + ;; Skip through mandatory args until first optional found, then search + ;; for another mandatory. + (let loop ([names names] [defaults defaults]) + (cond [(or (null? names) (null? defaults)) + #f] + [(eq? (car defaults) #f) ;; mandatory + (loop (cdr names) (cdr defaults))] + [else ;; found optional + (find-mandatory (cdr names) (cdr defaults))]))) + +;; Copied from unstable/list +;; check-duplicate : (listof X) +;; #:key (X -> K) +;; #:same? (or/c (K K -> bool) dict?) +;; -> X or #f +(define (check-duplicate items + #:key [key values] + #:same? [same? equal?]) + (cond [(procedure? same?) + (cond [(eq? same? equal?) + (check-duplicate/t items key (make-hash) #t)] + [(eq? same? eq?) + (check-duplicate/t items key (make-hasheq) #t)] + [(eq? same? eqv?) + (check-duplicate/t items key (make-hasheqv) #t)] + [else + (check-duplicate/list items key same?)])] + [(dict? same?) + (let ([dict same?]) + (if (dict-mutable? dict) + (check-duplicate/t items key dict #t) + (check-duplicate/t items key dict #f)))])) +(define (check-duplicate/t items key table mutating?) + (let loop ([items items] [table table]) + (and (pair? items) + (let ([key-item (key (car items))]) + (if (dict-ref table key-item #f) + (car items) + (loop (cdr items) (if mutating? + (begin (dict-set! table key-item #t) table) + (dict-set table key-item #t)))))))) +(define (check-duplicate/list items key same?) + (let loop ([items items] [sofar null]) + (and (pair? items) + (let ([key-item (key (car items))]) + (if (for/or ([prev (in-list sofar)]) + (same? key-item prev)) + (car items) + (loop (cdr items) (cons key-item sofar))))))) diff --git a/7-4/racket/collects/syntax/parse/pre.rkt b/7-4/racket/collects/syntax/parse/pre.rkt new file mode 100644 index 0000000..00bdaca --- /dev/null +++ b/7-4/racket/collects/syntax/parse/pre.rkt @@ -0,0 +1,49 @@ +#lang racket/base +(require "private/sc.rkt" + "private/litconv.rkt" + "private/lib.rkt" + "private/residual.rkt") +(provide (except-out (all-from-out "private/sc.rkt") + define-integrable-syntax-class + syntax-parser/template) + (all-from-out "private/litconv.rkt") + (all-from-out "private/lib.rkt") + syntax-parse-state-ref + syntax-parse-state-set! + syntax-parse-state-update! + syntax-parse-state-cons! + syntax-parse-track-literals) + +(define not-given (gensym)) + +(define (state-ref who key default) + (define state (current-state)) + (if (eq? default not-given) + (if (hash-has-key? state key) + (hash-ref state key) + (error who "no value found for key\n key: ~e" key)) + (hash-ref state key default))) + +(define (syntax-parse-state-ref key [default not-given]) + (state-ref 'syntax-parse-state-ref key default)) + +(define (check-update who) + (unless (current-state-writable?) + (error who "cannot update syntax-parse state outside of ~~do/#:do block"))) + +(define (syntax-parse-state-set! key value) + (check-update 'syntax-parse-state-set!) + (current-state (hash-set (current-state) key value))) + +(define (syntax-parse-state-update! key update [default not-given]) + (check-update 'syntax-parse-state-update!) + (define old (state-ref 'syntax-parse-state-update! key default)) + (current-state (hash-set (current-state) key (update old)))) + +(define (syntax-parse-state-cons! key value [default null]) + (check-update 'syntax-parse-state-cons!) + (define old (hash-ref (current-state) key default)) + (current-state (hash-set (current-state) key (cons value old)))) + +(define (syntax-parse-track-literals stx #:introduce? [introduce? #t]) + (track-literals 'syntax-parse-track-literals stx #:introduce? introduce?)) diff --git a/7-4/racket/collects/syntax/parse/private/3d-stx.rkt b/7-4/racket/collects/syntax/parse/private/3d-stx.rkt new file mode 100644 index 0000000..b5083d5 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/3d-stx.rkt @@ -0,0 +1,250 @@ +#lang racket/base +(require (only-in '#%flfxnum flvector? fxvector?) + (only-in '#%extfl extflonum? extflvector?)) +(provide 2d-stx? + check-datum) + +;; Checks for 3D syntax (syntax that contains unwritable values, etc) + +(define INIT-FUEL #e1e6) + +;; TO DO: +;; - extension via proc (any -> list/#f), +;; value considered good if result is list, all values in list are good + +;; -- + +#| +Some other predicates one might like to have: + - would (read (write x)) succeed and be equal/similar to x? + - would (datum->syntax #f x) succeed? + - would (syntax->datum (datum->syntax #f x)) succeed and be equal/similar to x? + - would (eval (read (write (compile `(quote ,x))))) succeed and be equal/similar to x? + +where equal/similar could mean one of the following: + - equal?, which equates eg (vector 1 2 3) and (vector-immutable 1 2 3) + - equal? relaxed to equate eg mutable and immutable hashes (but not prefabs) + - equal? but also requiring same mutability at every point + +Some aux definitions: + +(define (rt x) + (define-values (in out) (make-pipe)) + (write x out) + (close-output-port out) + (read in)) + +(define (wrsd x) + (define-values (in out) (make-pipe)) + (write x out) + (close-output-port out) + (syntax->datum (read-syntax #f in))) + +(define (dsd x) + (syntax->datum (datum->syntax #f x))) + +(define (evalc x) ;; mimics compiled zo-file constraints + (eval (rt (compile `(quote ,x))))) + +How mutability behaves: + - for vectors, boxes: + - read always mutable + - read-syntax always immutable + - (dsd x) always immutable + - (evalc x) always immutable + - for hashes: + - read always immutable + - (dsd x) same as x + - (evalc x) always immutable (!!!) + - for prefab structs: + - read same as x + - read-syntax same as x + - (dsd x) same as x + - (evalc x) same as x + +Symbols + - (dsd x) same as x + - (evalc x) preserves interned, unreadable, makes new uninterned (loses eq-ness) + +Chaperones allow the lazy generation of infinite trees of data +undetectable by eq?-based cycle detection. Might be helpful to have +chaperone-eq? (not recursive, just chaperones of same object) and +chaperone-eq?-hash-code, to use with make-custom-hash.) + +Impersonators allow the lazy generation of infinite trees of data, +period. + +|# + +;; ---- + +;; 2d-stx? : any ... -> boolean +;; Would (write (compile `(quote-syntax ,x))) succeed? +;; If traverse-syntax? is #t, recurs into existing syntax +;; If traverse-syntax? is #f, assumes existing stxobjs are 2d, and only +;; checks if *new* 3d syntax would be created. +(define (2d-stx? x + #:traverse-syntax? [traverse-syntax? #t] + #:irritant [irritant-box #f]) + (check-datum x + #:syntax-mode (if traverse-syntax? 'compound 'atomic) + #:allow-impersonators? #f + #:allow-mutable? 'no-hash/prefab + #:allow-unreadable-symbols? #t + #:allow-cycles? #t + #:irritant irritant-box)) + +;; ---- + +;; check-datum : any ... -> boolean +;; where StxMode = (U 'atomic 'compound #f) +;; Returns nat if x is "good", #f if "bad" +;; If irritant-b is a box, the first bad subvalue found is put in the box. +;; If visited-t is a hash, it is used to detect cycles. +(define (check-datum x + #:syntax-mode [stx-mode #f] + #:allow-impersonators? [allow-impersonators? #f] + #:allow-mutable? [allow-mutable? #f] + #:allow-unreadable-symbols? [allow-unreadable? #f] + #:allow-cycles? [allow-cycles? #f] + #:irritant [irritant-b #f]) + ;; Try once with some fuel. If runs out of fuel, try again with cycle checking. + (define (run fuel visited-t) + (check* x fuel visited-t + stx-mode allow-impersonators? allow-mutable? allow-unreadable? allow-cycles? + irritant-b)) + (let ([result (run INIT-FUEL #f)]) + (cond [(not (equal? result 0)) ;; nat>0 or #f + (and result #t)] + [else + ;; (eprintf "out of fuel, restarting\n") + (and (run +inf.0 (make-hasheq)) #t)]))) + +;; check* : any nat/+inf.0 StxMode boolean boolean boolean box -> nat/#f +;; Returns #f if bad, positive nat if good, 0 if ran out of fuel +;; If bad, places bad subvalue in irritant-b, if box +(define (check* x0 fuel0 visited-t + stx-mode allow-impersonators? allow-mutable? allow-unreadable? allow-cycles? + irritant-b) + (define no-mutable? (not allow-mutable?)) + (define no-mutable-hash/prefab? (or no-mutable? (eq? allow-mutable? 'no-hash/prefab))) + (define no-cycle? (not allow-cycles?)) + (define no-impersonator? (not allow-impersonators?)) + (define (loop x fuel) + (if (and fuel (not (zero? fuel))) + (loop* x fuel) + fuel)) + (define (loop* x fuel) + (define (bad) (when irritant-b (set-box! irritant-b x)) #f) + (define-syntax-rule (with-mutable-check mutable? body ...) ;; don't use for hash or prefab + (cond [(and no-mutable? mutable?) + (bad)] + [else + body ...])) + (define-syntax-rule (with-cycle-check body ...) + (cond [(and visited-t (hash-ref visited-t x #f)) + => (lambda (status) + (cond [(and no-cycle? (eq? status 'traversing)) + (bad)] + [else + fuel]))] + [else + (when visited-t + (hash-set! visited-t x 'traversing)) + (begin0 (begin body ...) + (when visited-t + (hash-remove! visited-t x)))])) + ;; (eprintf "-- checking ~s, fuel ~s\n" x fuel) + (cond + ;; Immutable compound + [(and visited-t (list? x)) + ;; space optimization: if list (finite), no need to store all cdr pairs in cycle table + ;; don't do unless visited-t present, else expands fuel by arbitrary factors + (with-cycle-check + (for/fold ([fuel (sub1 fuel)]) ([e (in-list x)] #:break (not fuel)) + (loop e fuel)))] + [(pair? x) + (with-cycle-check + (let ([fuel (loop (car x) (sub1 fuel))]) + (loop (cdr x) fuel)))] + ;; Atomic + [(or (null? x) + (boolean? x) + (number? x) + (char? x) + (keyword? x) + (regexp? x) + (byte-regexp? x) + (extflonum? x)) + fuel] + [(symbol? x) + (cond [(symbol-interned? x) + fuel] + [(symbol-unreadable? x) + (if allow-unreadable? fuel (bad))] + [else ;; uninterned + (if (eq? allow-unreadable? #t) fuel (bad))])] + ;; Mutable flat + [(or (string? x) + (bytes? x)) + (with-mutable-check (not (immutable? x)) + fuel)] + [(or (fxvector? x) + (flvector? x) + (extflvector? x)) + (with-mutable-check (not (immutable? x)) + fuel)] + ;; Syntax + [(syntax? x) + (case stx-mode + ((atomic) fuel) + ((compound) (loop (syntax-e x) fuel)) + (else (bad)))] + ;; Impersonators and chaperones + [(and no-impersonator? (impersonator? x)) ;; else continue to chaperoned type + (bad)] + [(and no-impersonator? (chaperone? x)) ;; else continue to impersonated type + (bad)] + [else + (with-cycle-check + (cond + ;; Mutable (maybe) compound + [(vector? x) + (with-mutable-check (not (immutable? x)) + (for/fold ([fuel fuel]) ([e (in-vector x)] #:break (not fuel)) + (loop e fuel)))] + [(box? x) + (with-mutable-check (not (immutable? x)) + (loop (unbox x) (sub1 fuel)))] + [(prefab-struct-key x) + => (lambda (key) + (cond [(and no-mutable-hash/prefab? (mutable-prefab-key? key)) + (bad)] + [else + ;; traverse key, since contains arbitrary auto-value + (let ([fuel (loop key fuel)]) + (loop (struct->vector x) fuel))]))] + [(hash? x) + (cond [(and no-mutable-hash/prefab? (not (immutable? x))) + (bad)] + [else + (for/fold ([fuel fuel]) ([(k v) (in-hash x)] #:break (not fuel)) + (let ([fuel (loop k fuel)]) + (loop v fuel)))])] + ;; Bad + [else + (bad)]))])) + (loop x0 fuel0)) + +;; mutable-prefab-key? : prefab-key -> boolean +(define (mutable-prefab-key? key) + ;; A prefab-key is either + ;; - symbol + ;; - (list* symbol maybe-nat maybe-list maybe-vector prefab-key) + ;; where mutable fields indicated by vector + ;; This code is probably overly general; racket seems to normalize keys. + (let loop ([k key]) + (and (pair? k) + (or (and (vector? (car k)) + (positive? (vector-length (car k)))) + (loop (cdr k)))))) diff --git a/7-4/racket/collects/syntax/parse/private/lib.rkt b/7-4/racket/collects/syntax/parse/private/lib.rkt new file mode 100644 index 0000000..01e110c --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/lib.rkt @@ -0,0 +1,96 @@ +#lang racket/base +(require "sc.rkt" + syntax/parse/private/keywords + (only-in "residual.rkt" state-cons!) + (for-syntax syntax/parse/private/residual-ct) + (for-syntax racket/base)) + +(provide identifier + boolean + str + character + keyword + number + integer + exact-integer + exact-nonnegative-integer + exact-positive-integer + + id + nat + char + + expr + static) + + +(define (expr-stx? x) + (not (keyword-stx? x))) + +(define ((stxof pred?) x) (and (syntax? x) (pred? (syntax-e x)))) +(define keyword-stx? (stxof keyword?)) +(define boolean-stx? (stxof boolean?)) +(define string-stx? (stxof string?)) +(define bytes-stx? (stxof bytes?)) +(define char-stx? (stxof char?)) +(define number-stx? (stxof number?)) +(define integer-stx? (stxof integer?)) +(define exact-integer-stx? (stxof exact-integer?)) +(define exact-nonnegative-integer-stx? (stxof exact-nonnegative-integer?)) +(define exact-positive-integer-stx? (stxof exact-positive-integer?)) +(define regexp-stx? (stxof regexp?)) +(define byte-regexp-stx? (stxof byte-regexp?)) + + +;; == Integrable syntax classes == + +(define-integrable-syntax-class identifier (quote "identifier") identifier?) +(define-integrable-syntax-class expr (quote "expression") expr-stx?) +(define-integrable-syntax-class keyword (quote "keyword") keyword-stx?) +(define-integrable-syntax-class boolean (quote "boolean") boolean-stx?) +(define-integrable-syntax-class character (quote "character") char-stx?) +(define-integrable-syntax-class number (quote "number") number-stx?) +(define-integrable-syntax-class integer (quote "integer") integer-stx?) +(define-integrable-syntax-class exact-integer (quote "exact-integer") exact-integer-stx?) +(define-integrable-syntax-class exact-nonnegative-integer + (quote "exact-nonnegative-integer") + exact-nonnegative-integer-stx?) +(define-integrable-syntax-class exact-positive-integer + (quote "exact-positive-integer") + exact-positive-integer-stx?) + +(define-integrable-syntax-class -string (quote "string") string-stx?) +(define-integrable-syntax-class -bytes (quote "bytes") bytes-stx?) +(define-integrable-syntax-class -regexp (quote "regexp") regexp-stx?) +(define-integrable-syntax-class -byte-regexp (quote "byte-regexp") byte-regexp-stx?) + +;; Overloading the meaning of existing identifiers +(begin-for-syntax + (set-box! alt-stxclass-mapping + (list (cons #'string (syntax-local-value #'-string)) + (cons #'bytes (syntax-local-value #'-bytes)) + (cons #'regexp (syntax-local-value #'-regexp)) + (cons #'byte-regexp (syntax-local-value #'-byte-regexp))))) + +;; Aliases +(define-syntax id (make-rename-transformer #'identifier)) +(define-syntax nat (make-rename-transformer #'exact-nonnegative-integer)) +(define-syntax char (make-rename-transformer #'character)) +(define-syntax str (make-rename-transformer #'-string)) + + +;; == Normal syntax classes == + +(define notfound (box 'notfound)) + +(define-syntax-class (static pred [name #f]) + #:attributes (value) + #:description name + #:commit + (pattern x:id + #:fail-unless (syntax-transforming?) + "not within the dynamic extent of a macro transformation" + #:attr value (syntax-local-value #'x (lambda () notfound)) + #:fail-when (eq? (attribute value) notfound) #f + #:fail-unless (pred (attribute value)) #f + #:do [(state-cons! 'literals #'x)])) diff --git a/7-4/racket/collects/syntax/parse/private/litconv.rkt b/7-4/racket/collects/syntax/parse/private/litconv.rkt new file mode 100644 index 0000000..9e25611 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/litconv.rkt @@ -0,0 +1,284 @@ +#lang racket/base +(require (for-syntax racket/base + racket/lazy-require + "sc.rkt" + "lib.rkt" + syntax/parse/private/kws + racket/syntax) + syntax/parse/private/residual-ct ;; keep abs. path + stxparse-info/parse/private/residual) ;; keep abs. path +(begin-for-syntax + (lazy-require + [syntax/private/keyword (options-select-value parse-keyword-options)] + [stxparse-info/parse/private/rep ;; keep abs. path + (parse-kw-formals + check-conventions-rules + check-datum-literals-list + create-aux-def)])) +;; FIXME: workaround for phase>0 bug in racket/runtime-path (and thus lazy-require) +;; Without this, dependencies don't get collected. +(require racket/runtime-path racket/syntax (for-meta 2 '#%kernel)) +(define-runtime-module-path-index _unused_ 'stxparse-info/parse/private/rep) + +(provide define-conventions + define-literal-set + literal-set->predicate + kernel-literals) + +(define-syntax (define-conventions stx) + + (define-syntax-class header + #:description "name or name with formal parameters" + #:commit + (pattern name:id + #:with formals #'() + #:attr arity (arity 0 0 null null)) + (pattern (name:id . formals) + #:attr arity (parse-kw-formals #'formals #:context stx))) + + (syntax-parse stx + [(define-conventions h:header rule ...) + (let () + (define rules (check-conventions-rules #'(rule ...) stx)) + (define rxs (map car rules)) + (define dens0 (map cadr rules)) + (define den+defs-list + (for/list ([den0 (in-list dens0)]) + (let-values ([(den defs) (create-aux-def den0)]) + (cons den defs)))) + (define dens (map car den+defs-list)) + (define defs (apply append (map cdr den+defs-list))) + + (define/with-syntax (rx ...) rxs) + (define/with-syntax (def ...) defs) + (define/with-syntax (parser ...) + (map den:delayed-parser dens)) + (define/with-syntax (class-name ...) + (map den:delayed-class dens)) + + ;; FIXME: could move make-den:delayed to user of conventions + ;; and eliminate from residual.rkt + #'(begin + (define-syntax h.name + (make-conventions + (quote-syntax get-parsers) + (lambda () + (let ([class-names (list (quote-syntax class-name) ...)]) + (map list + (list 'rx ...) + (map make-den:delayed + (generate-temporaries class-names) + class-names)))))) + (define get-parsers + (lambda formals + def ... + (list parser ...)))))])) + +(define-for-syntax (check-phase-level stx ctx) + (unless (or (exact-integer? (syntax-e stx)) + (eq? #f (syntax-e stx))) + (raise-syntax-error #f "expected phase-level (exact integer or #f)" ctx stx)) + stx) + +;; check-litset-list : stx stx -> (listof (cons id literalset)) +(define-for-syntax (check-litset-list stx ctx) + (syntax-case stx () + [(litset-id ...) + (for/list ([litset-id (syntax->list #'(litset-id ...))]) + (let* ([val (and (identifier? litset-id) + (syntax-local-value/record litset-id literalset?))]) + (if val + (cons litset-id val) + (raise-syntax-error #f "expected literal set name" ctx litset-id))))] + [_ (raise-syntax-error #f "expected list of literal set names" ctx stx)])) + +;; check-literal-entry/litset : stx stx -> (list id id) +(define-for-syntax (check-literal-entry/litset stx ctx) + (syntax-case stx () + [(internal external) + (and (identifier? #'internal) (identifier? #'external)) + (list #'internal #'external)] + [id + (identifier? #'id) + (list #'id #'id)] + [_ (raise-syntax-error #f "expected literal entry" ctx stx)])) + +(define-for-syntax (check-duplicate-literals ctx imports lits datum-lits) + (let ([lit-t (make-hasheq)]) ;; sym => #t + (define (check+enter! key blame-stx) + (when (hash-ref lit-t key #f) + (raise-syntax-error #f (format "duplicate literal: ~a" key) ctx blame-stx)) + (hash-set! lit-t key #t)) + (for ([id+litset (in-list imports)]) + (let ([litset-id (car id+litset)] + [litset (cdr id+litset)]) + (for ([entry (in-list (literalset-literals litset))]) + (cond [(lse:lit? entry) + (check+enter! (lse:lit-internal entry) litset-id)] + [(lse:datum-lit? entry) + (check+enter! (lse:datum-lit-internal entry) litset-id)])))) + (for ([datum-lit (in-list datum-lits)]) + (let ([internal (den:datum-lit-internal datum-lit)]) + (check+enter! (syntax-e internal) internal))) + (for ([lit (in-list lits)]) + (check+enter! (syntax-e (car lit)) (car lit))))) + +(define-syntax (define-literal-set stx) + (syntax-case stx () + [(define-literal-set name . rest) + (let-values ([(chunks rest) + (parse-keyword-options + #'rest + `((#:literal-sets ,check-litset-list) + (#:datum-literals ,check-datum-literals-list) + (#:phase ,check-phase-level) + (#:for-template) + (#:for-syntax) + (#:for-label)) + #:incompatible '((#:phase #:for-template #:for-syntax #:for-label)) + #:context stx + #:no-duplicates? #t)]) + (unless (identifier? #'name) + (raise-syntax-error #f "expected identifier" stx #'name)) + (let ([relphase + (cond [(assq '#:for-template chunks) -1] + [(assq '#:for-syntax chunks) 1] + [(assq '#:for-label chunks) #f] + [else (options-select-value chunks '#:phase #:default 0)])] + [datum-lits + (options-select-value chunks '#:datum-literals #:default null)] + [lits (syntax-case rest () + [( (lit ...) ) + (for/list ([lit (in-list (syntax->list #'(lit ...)))]) + (check-literal-entry/litset lit stx))] + [_ (raise-syntax-error #f "bad syntax" stx)])] + [imports (options-select-value chunks '#:literal-sets #:default null)]) + (check-duplicate-literals stx imports lits datum-lits) + (with-syntax ([((internal external) ...) lits] + [(datum-internal ...) (map den:datum-lit-internal datum-lits)] + [(datum-external ...) (map den:datum-lit-external datum-lits)] + [(litset-id ...) (map car imports)] + [relphase relphase]) + #`(begin + (define phase-of-literals + (and 'relphase + (+ (variable-reference->module-base-phase (#%variable-reference)) + 'relphase))) + (define-syntax name + (make-literalset + (append (literalset-literals (syntax-local-value (quote-syntax litset-id))) + ... + (list (make-lse:lit 'internal + (quote-syntax external) + (quote-syntax phase-of-literals)) + ... + (make-lse:datum-lit 'datum-internal + 'datum-external) + ...)))) + (begin-for-syntax/once + (for ([x (in-list (list (quote-syntax external) ...))]) + (unless (identifier-binding x 'relphase) + (raise-syntax-error #f + (format "literal is unbound in phase ~a~a~a" + 'relphase + (case 'relphase + ((1) " (for-syntax)") + ((-1) " (for-template)") + ((#f) " (for-label)") + (else "")) + " relative to the enclosing module") + (quote-syntax #,stx) x))))))))])) + +#| +NOTES ON PHASES AND BINDINGS + +(module M .... + .... (define-literal-set LS #:phase PL ....) + ....) + +For the expansion of the define-literal-set form, the bindings of the literals +can be accessed by (identifier-binding lit PL), because the phase of the enclosing +module (M) is 0. + +LS may be used, however, in a context where the phase of the enclosing +module is not 0, so each instantiation of LS needs to calculate the +phase of M and add that to PL. + +-- + +Normally, literal sets that define the same name conflict. But it +would be nice to allow them to both be imported in the case where they +refer to the same binding. + +Problem: Can't do the check eagerly, because the binding of L may +change between when define-literal-set is compiled and the comparison +involving L. For example: + + (module M racket + (require stxparse-info/parse) + (define-literal-set LS (lambda)) + (require (only-in some-other-lang lambda)) + .... LS ....) + +The expansion of the LS definition sees a different lambda than the +one that the literal in LS actually refers to. + +Similarly, a literal in LS might not be defined when the expander +runs, but might get defined later. (Although I think that will already +cause an error, so don't worry about that case.) +|# + +;; FIXME: keep one copy of each identifier (?) + +(define-syntax (literal-set->predicate stx) + (syntax-case stx () + [(literal-set->predicate litset-id) + (let ([val (and (identifier? #'litset-id) + (syntax-local-value/record #'litset-id literalset?))]) + (unless val (raise-syntax-error #f "expected literal set name" stx #'litset-id)) + (let ([lits (literalset-literals val)]) + (with-syntax ([((lit phase-var) ...) + (for/list ([lit (in-list lits)] + #:when (lse:lit? lit)) + (list (lse:lit-external lit) (lse:lit-phase lit)))] + [(datum-lit ...) + (for/list ([lit (in-list lits)] + #:when (lse:datum-lit? lit)) + (lse:datum-lit-external lit))]) + #'(make-literal-set-predicate (list (list (quote-syntax lit) phase-var) ...) + '(datum-lit ...)))))])) + +(define (make-literal-set-predicate lits datum-lits) + (lambda (x [phase (syntax-local-phase-level)]) + (or (for/or ([lit (in-list lits)]) + (let ([lit-id (car lit)] + [lit-phase (cadr lit)]) + (free-identifier=? x lit-id phase lit-phase))) + (and (memq (syntax-e x) datum-lits) #t)))) + +;; Literal sets + +(define-literal-set kernel-literals + (begin + begin0 + define-values + define-syntaxes + define-values-for-syntax ;; kept for compat. + begin-for-syntax + set! + let-values + letrec-values + #%plain-lambda + case-lambda + if + quote + quote-syntax + letrec-syntaxes+values + with-continuation-mark + #%expression + #%plain-app + #%top + #%datum + #%variable-reference + module module* #%provide #%require #%declare + #%plain-module-begin)) diff --git a/7-4/racket/collects/syntax/parse/private/make.rkt b/7-4/racket/collects/syntax/parse/private/make.rkt new file mode 100644 index 0000000..8a4f744 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/make.rkt @@ -0,0 +1,43 @@ +#lang racket/base +(require (for-syntax racket/base + racket/struct-info)) +(provide make) + +;; get-struct-info : identifier stx -> struct-info-list +(define-for-syntax (get-struct-info id ctx) + (define (bad-struct-name x) + (raise-syntax-error #f "expected struct name" ctx x)) + (unless (identifier? id) + (bad-struct-name id)) + (let ([value (syntax-local-value id (lambda () #f))]) + (unless (struct-info? value) + (bad-struct-name id)) + (extract-struct-info value))) + +;; (make struct-name field-expr ...) +;; Checks that correct number of fields given. +(define-syntax (make stx) + (syntax-case stx () + [(make S expr ...) + (let () + (define info (get-struct-info #'S stx)) + (define constructor (list-ref info 1)) + (define accessors (list-ref info 3)) + (unless (identifier? #'constructor) + (raise-syntax-error #f "constructor not available for struct" stx #'S)) + (unless (andmap identifier? accessors) + (raise-syntax-error #f "incomplete info for struct type" stx #'S)) + (let ([num-slots (length accessors)] + [num-provided (length (syntax->list #'(expr ...)))]) + (unless (= num-provided num-slots) + (raise-syntax-error + #f + (format "wrong number of arguments for struct ~s (expected ~s, got ~s)" + (syntax-e #'S) + num-slots + num-provided) + stx))) + (with-syntax ([constructor constructor]) + (syntax-property #'(constructor expr ...) + 'disappeared-use + #'S)))])) diff --git a/7-4/racket/collects/syntax/parse/private/opt.rkt b/7-4/racket/collects/syntax/parse/private/opt.rkt new file mode 100644 index 0000000..12714d6 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/opt.rkt @@ -0,0 +1,456 @@ +#lang racket/base +(require racket/syntax + racket/pretty + syntax/parse/private/residual-ct ;; keep abs. path + syntax/parse/private/minimatch + syntax/parse/private/rep-patterns + syntax/parse/private/kws) +(provide (struct-out pk1) + (rename-out [optimize-matrix0 optimize-matrix])) + +;; ---- + +;; A Matrix is a (listof PK) where each PK has same number of columns +;; A PK is one of +;; - (pk1 (listof pattern) expr) -- a simple row in a parsing matrix +;; - (pk/same pattern Matrix) -- a submatrix with a common first column factored out +;; - (pk/pair Matrix) -- a submatrix with pair patterns in the first column unfolded +;; - (pk/and Matrix) -- a submatrix with and patterns in the first column unfolded +(struct pk1 (patterns k) #:prefab) +(struct pk/same (pattern inner) #:prefab) +(struct pk/pair (inner) #:prefab) +(struct pk/and (inner) #:prefab) + +(define (pk-columns pk) + (match pk + [(pk1 patterns k) (length patterns)] + [(pk/same p inner) (add1 (pk-columns inner))] + [(pk/pair inner) (sub1 (pk-columns inner))] + [(pk/and inner) (sub1 (pk-columns inner))])) + +;; Can factor pattern P given clauses like +;; [ P P1 ... | e1] [ | [P1 ... | e1] ] +;; [ P : | :] => [P | [ : | :] ] +;; [ P PN ... | eN] [ | [PN ... | eN] ] +;; if P cannot cut and P succeeds at most once (otherwise may reorder backtracking) + +;; Can unfold pair patterns as follows: +;; [ (P11 . P12) P1 ... | e1 ] [ P11 P12 P1 ... | e1 ] +;; [ : : | : ] => check pair, [ : | : ] +;; [ (PN1 . PN2) PN ... | eN ] [ PN1 PN2 PN ... | eN ] + +;; Can unfold ~and patterns similarly; ~and patterns can hide +;; factoring opportunities. + +;; ---- + +;; FIXME: New (unimplemented) optimization ideas + +;; (1) When collecting pair patterns, can reorder rows with pair vs never-pair +;; first columns: +;; [ (P11 . P12) P1 ... | e1 ] [ (P11 . P12) P1 ... | e1 ] +;; [ P21 P2 ... | e2 ] => [ (P31 . P32) P3 ... | e3 ] +;; [ (P31 . P32) P3 ... | e3 ] [ P21 P2 ... | e2 ] +;; provided P21 does not cut and cannot match a pair term. +;; Likewise for literals and never-symbol patterns. + +;; (2) If a row has a non-rejecting pattern (ie, always matches) in its first +;; column, then the rows above it do not need to produce failure information +;; *for their first columns*. For example, in the following matrix +;; [ P11 P1 ... | e1 ] +;; [ P21 P2 ... | e2 ] +;; [ P31 P3 ... | e3 ] +;; Suppose that P21 always matches (eg _) and assume P{1,3}1 are cut-free. Then +;; P{1,3}1 do not need to produce failure info (set es = #f, etc). Here's why. +;; If control reaches row 2, then since P21 cannot fail, if it fails the +;; progress must be greater than P11 or P31. FIXME: Must also check neither P11 +;; nor P31 use ~post (or call stxclass that uses ~post, etc)! + + +;; ---- + +(define (optimize-matrix0 rows) + (define now (current-inexact-milliseconds)) + (when (and (> (length rows) 1)) + (log-syntax-parse-debug "OPT matrix (~s rows)\n~a" (length rows) + (pretty-format (matrix->sexpr rows) #:mode 'print))) + (define result (optimize-matrix rows)) + (define then (current-inexact-milliseconds)) + (when (and (> (length rows) 1)) + (cond [(= (length result) (length rows)) + (log-syntax-parse-debug "OPT FAILED (~s ms)" (floor (- then now)))] + [else + (log-syntax-parse-debug "OPT ==> (~s ms)\n~a" (floor (- then now)) + (pretty-format (matrix->sexpr result) #:mode 'print))])) + result) + +;; optimize-matrix : (listof pk1) -> Matrix +(define (optimize-matrix rows) + (cond [(null? rows) null] + [(null? (cdr rows)) rows] ;; no opportunities for 1 row + [(null? (pk1-patterns (car rows))) rows] + [else + ;; first unfold and-patterns + (let-values ([(col1 col2) + (for/lists (col1 col2) ([row (in-list rows)]) + (unfold-and (car (pk1-patterns row)) null))]) + (cond [(ormap pair? col2) + (list + (pk/and + (optimize-matrix* + (for/list ([row (in-list rows)] + [col1 (in-list col1)] + [col2 (in-list col2)]) + (pk1 (list* col1 + (make-and-pattern col2) + (cdr (pk1-patterns row))) + (pk1-k row))))))] + [else (optimize-matrix* rows)]))])) + +;; optimize-matrix* : (listof pk1) -> Matrix +;; The matrix is nonempty, and first column has no unfoldable pat:and. +;; Split into submatrixes (sequences of rows) starting with similar patterns, +;; handle according to similarity, then recursively optimize submatrixes. +(define (optimize-matrix* rows) + (define row1 (car rows)) + (define pat1 (car (pk1-patterns row1))) + (define k1 (pk1-k row1)) + ;; Now accumulate rows starting with patterns like pat1 + (define-values (like? combine) (pattern->partitioner pat1)) + (let loop ([rows (cdr rows)] [rrows (list row1)]) + (cond [(null? rows) + (cons (combine (reverse rrows)) null)] + [else + (define row1 (car rows)) + (define pat1 (car (pk1-patterns row1))) + (cond [(like? pat1) + (loop (cdr rows) (cons row1 rrows))] + [else + (cons (combine (reverse rrows)) + (optimize-matrix* rows))])]))) + +;; pattern->partitioner : pattern -> (values (pattern -> boolean) ((listof pk1) -> PK)) +(define (pattern->partitioner pat1) + (match pat1 + [(pat:pair head tail) + (values (lambda (p) (pat:pair? p)) + (lambda (rows) + (log-syntax-parse-debug "-- got ~s pair rows like ~e" (length rows) (pattern->sexpr pat1)) + (cond [(> (length rows) 1) + (pk/pair (optimize-matrix + (for/list ([row (in-list rows)]) + (let* ([patterns (pk1-patterns row)] + [pat1 (car patterns)]) + (pk1 (list* (pat:pair-head pat1) + (pat:pair-tail pat1) + (cdr patterns)) + (pk1-k row))))))] + [else (car rows)])))] + [(? pattern-factorable?) + (values (lambda (pat2) (pattern-equal? pat1 pat2)) + (lambda (rows) + (log-syntax-parse-debug "-- got ~s factorable like ~e" (length rows) (pattern->sexpr pat1)) + (cond [(> (length rows) 1) + (pk/same pat1 + (optimize-matrix + (for/list ([row (in-list rows)]) + (pk1 (cdr (pk1-patterns row)) (pk1-k row)))))] + [else (car rows)])))] + [_ + (values (lambda (pat2) #f) + (lambda (rows) + ;; (length rows) = 1 + (car rows)))])) + +;; unfold-and : pattern (listof pattern) -> (values pattern (listof pattern)) +(define (unfold-and p onto) + (match p + [(pat:and subpatterns) + ;; pat:and is worth unfolding if first subpattern is not pat:action + ;; if first subpattern is also pat:and, keep unfolding + (let* ([first-sub (car subpatterns)] + [rest-subs (cdr subpatterns)]) + (cond [(not (pat:action? first-sub)) + (unfold-and first-sub (*append rest-subs onto))] + [else (values p onto)]))] + [_ (values p onto)])) + +;; pattern-factorable? : *Pattern -> Boolean +(define (pattern-factorable? p) (not (pattern-unfactorable? p))) + +;; pattern-unfactorable? : *Pattern -> Boolean +(define (pattern-unfactorable? p) + ;; Cannot factor out p if + ;; - if p can succeed multiple times (factoring changes success order) + ;; - if p can cut (factoring changes which choice points are discarded (too few)) + ;; Note: presence of sub-expressions handled by pattern-equal?. + (define (for-pattern p recur) + (match p + [(pat:var/p _ _ _ _ _ (scopts _ commit? _ _)) (not commit?)] + [(pat:action _act _pat) #t] + [(pat:dots heads tail) + ;; Conservative approximation for common case: one head pattern + ;; In general, check if heads don't overlap, don't overlap with tail. + (or (> (length heads) 1) + (not (equal? tail (pat:datum '()))) + (recur))] + [(pat:or _ patterns _) #t] + [(pat:not pattern) #t] + [(pat:commit pattern) #f] + [(? pat:reflect?) #t] + [(hpat:var/p _ _ _ _ _ (scopts _ commit? _ _)) (not commit?)] + [(hpat:commit inner) #f] + [(ehpat _ head repc _) + (or (not (equal? repc #f)) + (recur))] + [_ (recur)])) + (pattern-ormap p for-pattern)) + +(define (subpatterns-equal? as bs) + (and (= (length as) (length bs)) + (for/and ([a (in-list as)] + [b (in-list bs)]) + (pattern-equal? a b)))) + +(define (pattern-equal? a b) + (define result + (cond [(and (pat:any? a) (pat:any? b)) #t] + [(and (pat:svar? a) (pat:svar? b)) + (bound-identifier=? (pat:svar-name a) (pat:svar-name b))] + [(and (pat:var/p? a) (pat:var/p? b)) + (and (free-id/f-equal? (pat:var/p-parser a) (pat:var/p-parser b)) + (bound-id/f-equal? (pat:var/p-name a) (pat:var/p-name b)) + (equal-iattrs? (pat:var/p-nested-attrs a) (pat:var/p-nested-attrs b)) + (equal-argu? (pat:var/p-argu a) (pat:var/p-argu b)) + (expr-equal? (pat:var/p-role a) (pat:var/p-role b)))] + [(and (pat:integrated? a) (pat:integrated? b)) + (and (bound-id/f-equal? (pat:integrated-name a) (pat:integrated-name b)) + (free-identifier=? (pat:integrated-predicate a) + (pat:integrated-predicate b)) + (expr-equal? (pat:integrated-role a) (pat:integrated-role b)))] + [(and (pat:literal? a) (pat:literal? b)) + ;; literals are hard to compare, so compare gensyms attached to + ;; literal ids (see rep.rkt) instead + (let ([ka (syntax-property (pat:literal-id a) 'literal)] + [kb (syntax-property (pat:literal-id b) 'literal)]) + (and ka kb (eq? ka kb)))] + [(and (pat:datum? a) (pat:datum? b)) + (equal? (pat:datum-datum a) + (pat:datum-datum b))] + [(and (pat:head? a) (pat:head? b)) + (and (pattern-equal? (pat:head-head a) (pat:head-head b)) + (pattern-equal? (pat:head-tail a) (pat:head-tail b)))] + [(and (pat:dots? a) (pat:dots? b)) + (and (subpatterns-equal? (pat:dots-heads a) (pat:dots-heads b)) + (pattern-equal? (pat:dots-tail a) (pat:dots-tail b)))] + [(and (pat:and? a) (pat:and? b)) + (subpatterns-equal? (pat:and-patterns a) (pat:and-patterns b))] + [(and (pat:or? a) (pat:or? b)) + (subpatterns-equal? (pat:or-patterns a) (pat:or-patterns b))] + [(and (pat:not? a) (pat:not? b)) + (pattern-equal? (pat:not-pattern a) (pat:not-pattern b))] + [(and (pat:pair? a) (pat:pair? b)) + (and (pattern-equal? (pat:pair-head a) (pat:pair-head b)) + (pattern-equal? (pat:pair-tail a) (pat:pair-tail b)))] + [(and (pat:vector? a) (pat:vector? b)) + (pattern-equal? (pat:vector-pattern a) (pat:vector-pattern b))] + [(and (pat:box? a) (pat:box? b)) + (pattern-equal? (pat:box-pattern a) (pat:box-pattern b))] + [(and (pat:pstruct? a) (pat:pstruct? b)) + (and (equal? (pat:pstruct-key a) + (pat:pstruct-key b)) + (pattern-equal? (pat:pstruct-pattern a) + (pat:pstruct-pattern b)))] + [(and (pat:describe? a) (pat:describe? b)) #f] ;; can't compare desc exprs + [(and (pat:delimit? a) (pat:delimit? b)) + (pattern-equal? (pat:delimit-pattern a) (pat:delimit-pattern b))] + [(and (pat:commit? a) (pat:commit? b)) + (pattern-equal? (pat:commit-pattern a) (pat:commit-pattern b))] + [(and (pat:reflect? a) (pat:reflect? b)) #f] ;; FIXME: ? + [(and (pat:ord? a) (pat:ord? b)) + (and (pattern-equal? (pat:ord-pattern a) (pat:ord-pattern b)) + (equal? (pat:ord-group a) (pat:ord-group b)) + (equal? (pat:ord-index a) (pat:ord-index b)))] + [(and (pat:post? a) (pat:post? b)) + (pattern-equal? (pat:post-pattern a) (pat:post-pattern b))] + [(and (pat:seq-end? a) (pat:seq-end? b)) #t] + ;; --- + [(and (hpat:single? a) (hpat:single? b)) + (pattern-equal? (hpat:single-pattern a) (hpat:single-pattern b))] + [(and (hpat:var/p? a) (hpat:var/p? b)) + (and (free-id/f-equal? (hpat:var/p-parser a) (hpat:var/p-parser b)) + (bound-id/f-equal? (hpat:var/p-name a) (hpat:var/p-name b)) + (equal-iattrs? (hpat:var/p-nested-attrs a) (hpat:var/p-nested-attrs b)) + (equal-argu? (hpat:var/p-argu a) (hpat:var/p-argu b)) + (expr-equal? (hpat:var/p-role a) (hpat:var/p-role b)))] + [(and (hpat:seq? a) (hpat:seq? b)) + (pattern-equal? (hpat:seq-inner a) (hpat:seq-inner b))] + ;; --- + [(and (ehpat? a) (ehpat? b)) + (and (equal? (ehpat-repc a) #f) + (equal? (ehpat-repc b) #f) + (pattern-equal? (ehpat-head a) (ehpat-head b)))] + ;; FIXME: more? + [else #f])) + (when (and (log-level? syntax-parse-logger 'debug) + (eq? result #f) + (equal? (syntax->datum #`#,a) (syntax->datum #`#,b))) + (log-syntax-parse-debug "** pattern-equal? failed on ~e" a)) + result) + +(define (equal-iattrs? as bs) + (and (= (length as) (length bs)) + ;; assumes attrs in same order + (for/and ([aa (in-list as)] + [ba (in-list bs)]) + (and (bound-identifier=? (attr-name aa) (attr-name ba)) + (equal? (attr-depth aa) (attr-depth ba)) + (equal? (attr-syntax? aa) (attr-syntax? ba)))))) + +(define (expr-equal? a b) + ;; Expression equality is undecidable in general. Especially difficult for unexpanded + ;; code, but it would be very difficult to set up correct env for local-expand because of + ;; attr binding rules. So, do *very* conservative approx: simple variables and literals. + ;; FIXME: any other common cases? + (cond [(not (and (syntax? a) (syntax? b))) + (equal? a b)] + [(and (identifier? a) (identifier? b)) + ;; note: "vars" might be identifier macros (unsafe to consider equal), + ;; so check var has no compile-time binding + (and (free-identifier=? a b) + (let/ec k (syntax-local-value a (lambda () (k #t))) #f))] + [(syntax-case (list a b) (quote) + [((quote ad) (quote bd)) + (cons (syntax->datum #'ad) (syntax->datum #'bd))] + [_ #f]) + => (lambda (ad+bd) + (equal? (car ad+bd) (cdr ad+bd)))] + [else + ;; approx: equal? only if both simple data (bool, string, etc), no inner stx + (let ([ad (syntax-e a)] + [bd (syntax-e b)]) + (and (equal? ad bd) + (free-identifier=? (datum->syntax a '#%datum) #'#%datum) + (free-identifier=? (datum->syntax b '#%datum) #'#%datum)))])) + +(define (equal-argu? a b) + (define (unwrap-arguments x) + (match x + [(arguments pargs kws kwargs) + (values pargs kws kwargs)])) + (define (list-equal? as bs inner-equal?) + (and (= (length as) (length bs)) + (andmap inner-equal? as bs))) + (let-values ([(apargs akws akwargs) (unwrap-arguments a)] + [(bpargs bkws bkwargs) (unwrap-arguments b)]) + (and (list-equal? apargs bpargs expr-equal?) + (equal? akws bkws) + (list-equal? akwargs bkwargs expr-equal?)))) + +(define (free-id/f-equal? a b) + (or (and (eq? a #f) + (eq? b #f)) + (and (identifier? a) + (identifier? b) + (free-identifier=? a b)))) + +(define (bound-id/f-equal? a b) + (or (and (eq? a #f) + (eq? b #f)) + (and (identifier? a) + (identifier? b) + (bound-identifier=? a b)))) + +(define (make-and-pattern subs) + (cond [(null? subs) (pat:any)] ;; shouldn't happen + [(null? (cdr subs)) (car subs)] + [else (pat:and subs)])) + +(define (*append a b) (if (null? b) a (append a b))) + +(define (stx-e x) (if (syntax? x) (syntax-e x) x)) + +;; ---- + +(define (matrix->sexpr rows) + (cond [(null? rows) ;; shouldn't happen + '(FAIL)] + [(null? (cdr rows)) + (pk->sexpr (car rows))] + [else + (cons 'TRY (map pk->sexpr rows))])) +(define (pk->sexpr pk) + (match pk + [(pk1 pats k) + (cons 'MATCH (map pattern->sexpr pats))] + [(pk/same pat inner) + (list 'SAME (pattern->sexpr pat) (matrix->sexpr inner))] + [(pk/pair inner) + (list 'PAIR (matrix->sexpr inner))] + [(pk/and inner) + (list 'AND (matrix->sexpr inner))])) +(define (pattern->sexpr p) + (match p + [(pat:any) '_] + [(pat:integrated name pred desc _) + (format-symbol "~a:~a" (or name '_) desc)] + [(pat:svar name) + (syntax-e name)] + [(pat:var/p name parser _ _ _ _) + (cond [(and parser (regexp-match #rx"^parse-(.*)$" (symbol->string (syntax-e parser)))) + => (lambda (m) + (format-symbol "~a:~a" (or name '_) (cadr m)))] + [else + (if name (syntax-e name) '_)])] + [(? pat:literal?) `(syntax ,(syntax->datum (pat:literal-id p)))] + [(pat:datum datum) + (cond [(or (symbol? datum) (pair? datum)) + `(quote ,datum)] + [else datum])] + [(pat:action action (pat:any)) (pattern->sexpr action)] + [(pat:action action inner) (list '~AAND (pattern->sexpr action) (pattern->sexpr inner))] + [(pat:and patterns) (cons '~and (map pattern->sexpr patterns))] + [(pat:or _ patterns _) (cons '~or (map pattern->sexpr patterns))] + [(pat:not pattern) (list '~not (pattern->sexpr pattern))] + [(pat:pair head tail) + (cons (pattern->sexpr head) (pattern->sexpr tail))] + [(pat:head head tail) + (cons (pattern->sexpr head) (pattern->sexpr tail))] + [(pat:dots (list eh) tail) + (list* (pattern->sexpr eh) '... (pattern->sexpr tail))] + [(pat:dots ehs tail) + (list* (cons '~alt (map pattern->sexpr ehs)) '... (pattern->sexpr tail))] + [(pat:describe sp _ _ _) (list '~describe (pattern->sexpr sp))] + [(pat:delimit sp) (list '~delimit-cut (pattern->sexpr sp))] + [(pat:commit sp) (list '~commit (pattern->sexpr sp))] + [(pat:ord pattern _ _) (list '~ord (pattern->sexpr pattern))] + [(pat:post sp) (list '~post (pattern->sexpr sp))] + [(pat:seq-end) '()] + [(action:cut) '~!] + [(action:fail cnd msg) (list '~fail)] + [(action:bind attr expr) (list '~bind)] + [(action:and as) (cons '~and (map pattern->sexpr as))] + [(action:parse sp expr) (list '~parse (pattern->sexpr sp))] + [(action:do stmts) (list '~do)] + [(action:undo stmts) (list '~undo)] + [(action:ord ap _ _) (list '~ord (pattern->sexpr ap))] + [(action:post ap) (list '~post (pattern->sexpr ap))] + [(hpat:single sp) (pattern->sexpr sp)] + [(hpat:var/p name parser _ _ _ _) + (cond [(and parser (regexp-match #rx"^parser-(.*)$" (symbol->string (syntax-e parser)))) + => (lambda (m) (format-symbol "~a:~a" (or name '_) (cadr m)))] + [else (if name (syntax-e name) '_)])] + [(hpat:seq lp) (cons '~seq (pattern->sexpr lp))] + [(hpat:action ap hp) (list '~AAND (pattern->sexpr ap) (pattern->sexpr hp))] + [(hpat:and hp sp) (list '~and (pattern->sexpr hp) (pattern->sexpr sp))] + [(hpat:or _ hps _) (cons '~or (map pattern->sexpr hps))] + [(hpat:describe hp _ _ _) (list '~describe (pattern->sexpr hp))] + [(hpat:delimit hp) (list '~delimit-cut (pattern->sexpr hp))] + [(hpat:commit hp) (list '~commit (pattern->sexpr hp))] + [(hpat:ord hp _ _) (list '~ord (pattern->sexpr hp))] + [(hpat:post hp) (list '~post (pattern->sexpr hp))] + [(hpat:peek hp) (list '~peek (pattern->sexpr hp))] + [(hpat:peek-not hp) (list '~peek-not (pattern->sexpr hp))] + [(ehpat _as hpat repc _cn) + (if (eq? repc #f) (pattern->sexpr hpat) (list '~REPC (pattern->sexpr hpat)))] + [_ '<Pattern>])) diff --git a/7-4/racket/collects/syntax/parse/private/parse.rkt b/7-4/racket/collects/syntax/parse/private/parse.rkt new file mode 100644 index 0000000..7e5c61d --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/parse.rkt @@ -0,0 +1,1206 @@ +#lang racket/base +(require (for-syntax racket/base + syntax/stx + syntax/private/id-table + syntax/keyword + racket/syntax + syntax/parse/private/minimatch + syntax/parse/private/datum-to-expr + syntax/parse/private/rep-attrs + syntax/parse/private/rep-data + syntax/parse/private/rep-patterns + "rep.rkt" + syntax/parse/private/kws + "opt.rkt" + "txlift.rkt") + syntax/parse/private/keywords + racket/syntax + racket/stxparam + syntax/stx + stxparse-info/parse/private/residual ;; keep abs. path + "runtime.rkt" + stxparse-info/parse/private/runtime-reflect) ;; keep abs. path + +;; ============================================================ + +(provide define-syntax-class + define-splicing-syntax-class + define-integrable-syntax-class + syntax-parse + syntax-parser + define/syntax-parse + syntax-parser/template + parser/rhs + define-eh-alternative-set + (for-syntax rhs->parser)) + +(begin-for-syntax + ;; constant-desc : Syntax -> String/#f + (define (constant-desc stx) + (syntax-case stx (quote) + [(quote datum) + (let ([d (syntax-e #'datum)]) + (and (string? d) d))] + [expr + (let ([d (syntax-e #'expr)]) + (and (string? d) + (free-identifier=? #'#%datum (datum->syntax #'expr '#%datum)) + d))])) + + (define (tx:define-*-syntax-class stx splicing?) + (syntax-case stx () + [(_ header . rhss) + (parameterize ((current-syntax-context stx)) + (let-values ([(name formals arity) + (let ([p (check-stxclass-header #'header stx)]) + (values (car p) (cadr p) (caddr p)))]) + (let ([the-rhs (parse-rhs #'rhss splicing? #:context stx + #:default-description (symbol->string (syntax-e name)))]) + (with-syntax ([name name] + [formals formals] + [desc (cond [(rhs-description the-rhs) => constant-desc] [else #f])] + [parser (generate-temporary (format-symbol "parse-~a" name))] + [arity arity] + [attrs (rhs-attrs the-rhs)] + [commit? (rhs-commit? the-rhs)] + [delimit-cut? (rhs-delimit-cut? the-rhs)] + [the-rhs-expr (datum->expression the-rhs)]) + #`(begin (define-syntax name + (stxclass 'name 'arity + 'attrs + (quote-syntax parser) + '#,splicing? + (scopts (length 'attrs) 'commit? 'delimit-cut? desc) + #f)) + (define-values (parser) + (parser/rhs name formals attrs the-rhs-expr #,splicing? #,stx)))))))]))) + +(define-syntax define-syntax-class + (lambda (stx) (tx:define-*-syntax-class stx #f))) +(define-syntax define-splicing-syntax-class + (lambda (stx) (tx:define-*-syntax-class stx #t))) + +(define-syntax (define-integrable-syntax-class stx) + (syntax-case stx (quote) + [(_ name (quote description) predicate) + (with-syntax ([parser (generate-temporary (format-symbol "parse-~a" (syntax-e #'name)))] + [no-arity no-arity]) + #'(begin (define-syntax name + (stxclass 'name no-arity '() + (quote-syntax parser) + #f + (scopts 0 #t #t 'description) + (quote-syntax predicate))) + (define (parser x cx pr es undos fh0 cp0 rl success) + (if (predicate x) + (success fh0 undos) + (let ([es (es-add-thing pr 'description #t rl es)]) + (fh0 undos (failure* pr es)))))))])) + +(define-syntax (parser/rhs stx) + (syntax-case stx () + [(parser/rhs name formals relsattrs the-rhs-expr splicing? ctx) + (with-disappeared-uses + (let () + (define the-rhs + (parameterize ((current-syntax-context #'ctx)) + (fixup-rhs (syntax-local-eval + (syntax-local-introduce #'the-rhs-expr)) + (syntax-e #'splicing?) + (syntax->datum #'relsattrs)))) + (rhs->parser #'name #'formals #'relsattrs the-rhs (syntax-e #'splicing?) #'ctx)))])) + +(begin-for-syntax + (define (rhs->parser name formals relsattrs the-rhs splicing? [ctx #f]) + (define-values (transparent? description variants defs commit? delimit-cut?) + (match the-rhs + [(rhs _ transparent? description variants defs commit? delimit-cut?) + (values transparent? description variants defs commit? delimit-cut?)])) + (define vdefss (map variant-definitions variants)) + (define formals* (rewrite-formals formals #'x #'rl)) + (define patterns (map variant-pattern variants)) + (define no-fail? + (and (not splicing?) ;; FIXME: commit? needed? + (patterns-cannot-fail? patterns))) + (when no-fail? (log-syntax-parse-debug "(stxclass) cannot fail: ~e" ctx)) + (define body + (cond [(null? patterns) + #'(fail (failure* pr es))] + [splicing? + (with-syntax ([(alternative ...) + (for/list ([pattern (in-list patterns)]) + (with-syntax ([pattern pattern] + [relsattrs relsattrs] + [iattrs (pattern-attrs pattern)] + [commit? commit?] + [result-pr + (if transparent? + #'rest-pr + #'(ps-pop-opaque rest-pr))]) + #'(parse:H x cx rest-x rest-cx rest-pr pattern pr es + (variant-success relsattrs iattrs (rest-x rest-cx result-pr) + success cp0 commit?))))]) + #'(try alternative ...))] + [else + (with-syntax ([matrix + (optimize-matrix + (for/list ([pattern (in-list patterns)]) + (with-syntax ([iattrs (pattern-attrs pattern)] + [relsattrs relsattrs] + [commit? commit?]) + (pk1 (list pattern) + #'(variant-success relsattrs iattrs () + success cp0 commit?)))))]) + #'(parse:matrix ((x cx pr es)) matrix))])) + (with-syntax ([formals* formals*] + [(def ...) defs] + [((vdef ...) ...) vdefss] + [description description] + [transparent? transparent?] + [delimit-cut? delimit-cut?] + [body body]) + #`(lambda (x cx pr es undos fh0 cp0 rl success . formals*) + (with ([this-syntax x] + [this-role rl]) + def ... + vdef ... ... + (#%expression + (syntax-parameterize ((this-context-syntax + (syntax-rules () + [(tbs) (ps-context-syntax pr)]))) + (let ([es (es-add-thing pr description 'transparent? rl + #,(if no-fail? #'#f #'es))] + [pr (if 'transparent? pr (ps-add-opaque pr))]) + (with ([fail-handler fh0] + [cut-prompt cp0] + [undo-stack undos]) + ;; Update the prompt, if required + ;; FIXME: can be optimized away if no cut exposed within variants + (with-maybe-delimit-cut delimit-cut? + body)))))))))) + +(define-syntax (syntax-parse stx) + (syntax-case stx () + [(syntax-parse stx-expr . clauses) + (quasisyntax/loc stx + (let ([x (datum->syntax #f stx-expr)]) + (with ([this-syntax x]) + (parse:clauses x clauses body-sequence #,((make-syntax-introducer) stx)))))])) + +(define-syntax (syntax-parser stx) + (syntax-case stx () + [(syntax-parser . clauses) + (quasisyntax/loc stx + (lambda (x) + (let ([x (datum->syntax #f x)]) + (with ([this-syntax x]) + (parse:clauses x clauses body-sequence #,((make-syntax-introducer) stx))))))])) + +(define-syntax (syntax-parser/template stx) + (syntax-case stx () + [(syntax-parser/template ctx . clauses) + (quasisyntax/loc stx + (lambda (x) + (let ([x (datum->syntax #f x)]) + (with ([this-syntax x]) + (parse:clauses x clauses one-template ctx)))))])) + +(define-syntax (define/syntax-parse stx) + (syntax-case stx () + [(define/syntax-parse pattern . rest) + (with-disappeared-uses + (let-values ([(rest pattern defs) + (parse-pattern+sides #'pattern + #'rest + #:splicing? #f + #:decls (new-declenv null) + #:context stx)]) + (define no-fail? (patterns-cannot-fail? (list pattern))) + (let ([expr + (syntax-case rest () + [( expr ) #'expr] + [_ (raise-syntax-error #f "bad syntax" stx)])] + [attrs (pattern-attrs pattern)]) + (with-syntax ([(a ...) attrs] + [(#s(attr name _ _) ...) attrs] + [pattern pattern] + [es0 (if no-fail? #'#f #'#t)] + [(def ...) defs] + [expr expr]) + #'(defattrs/unpack (a ...) + (let* ([x (datum->syntax #f expr)] + [cx x] + [pr (ps-empty x x)] + [es es0] + [fh0 (syntax-patterns-fail + (normalize-context 'define/syntax-parse + '|define/syntax-parse pattern| + x))]) + (parameterize ((current-syntax-context x)) + def ... + (#%expression + (with ([fail-handler fh0] + [cut-prompt fh0] + [undo-stack null]) + (parse:S x cx pattern pr es + (list (attribute name) ...)))))))))))])) + +;; ============================================================ + +#| +Parsing protocols: + +(parse:<X> <X-args> pr es success-expr) : Ans + + <S-args> : x cx + <H-args> : x cx rest-x rest-cx rest-pr + <EH-args> : x cx ??? + <A-args> : x cx + + x is term to parse, usually syntax but can be pair/null (stx-list?) in cdr patterns + cx is most recent syntax object: if x must be coerced to syntax, use cx as lexctx and src + pr, es are progress and expectstack, respectively + rest-x, rest-cx, rest-pr are variable names to bind in context of success-expr + +(stxclass-parser x cx pr es undos fail-handler cut-prompt role success-proc arg ...) : Ans + + success-proc: + for stxclass, is (fail-handler undos attr-value ... -> Ans) + for splicing-stxclass, is (undos fail-handler rest-x rest-cx rest-pr attr-value -> Ans) + fail-handler, cut-prompt : undos failure -> Ans + +Fail-handler is normally represented with stxparam 'fail-handler', but must be +threaded through stxclass calls (in through stxclass-parser, out through +success-proc) to support backtracking. Cut-prompt is never changed within +stxclass or within alternative, so no threading needed. + +The undo stack is normally represented with stxparam 'undo-stack', but must be +threaded through stxclass calls (like fail-handler). A failure handler closes +over a base undo stack and receives an extended current undo stack; the failure +handler unwinds effects by performing every action in the difference between +them and then restores the saved undo stack. + +Usually sub-patterns processed in tail position, but *can* do non-tail calls for: + - ~commit + - var of stxclass with ~commit +It is also safe to keep normal tail-call protocol and just adjust fail-handler. +There is no real benefit to specializing ~commit, since it does not involve +creating a success closure. + +Some optimizations: + - commit protocol for stxclasses (but not ~commit, no point) + - avoid continue-vs-end choice point in (EH ... . ()) by eager pair check + - integrable stxclasses, specialize ellipses of integrable stxclasses + - pattern lists that cannot fail set es=#f to disable ExpectStack allocation +|# + +;; ---- + +(begin-for-syntax + (define (wash stx) + (syntax-e stx)) + (define (wash-list washer stx) + (let ([l (stx->list stx)]) + (unless l (raise-type-error 'wash-list "stx-list" stx)) + (map washer l))) + (define (wash-iattr stx) + (with-syntax ([#s(attr name depth syntax?) stx]) + (attr #'name (wash #'depth) (wash #'syntax?)))) + (define (wash-sattr stx) + (with-syntax ([#s(attr name depth syntax?) stx]) + (attr (wash #'name) (wash #'depth) (wash #'syntax?)))) + (define (wash-iattrs stx) + (wash-list wash-iattr stx)) + (define (wash-sattrs stx) + (wash-list wash-sattr stx)) + (define (generate-n-temporaries n) + (generate-temporaries + (for/list ([i (in-range n)]) + (string->symbol (format "g~sx" i)))))) + +;; ---- + +#| +Conventions: + - rhs : RHS + - iattr : IAttr + - relsattr : SAttr + - splicing? : bool + - x : id (var) + - cx : id (var, may be shadowed) + - pr : id (var, may be shadowed) + - es : id (var, may be shadowed) + - success : var (bound to success procedure) + - k : expr + - rest-x, rest-cx, rest-pr : id (to be bound) + - fh, cp, rl : id (var) +|# + +(begin-for-syntax + (define (rewrite-formals fstx x-id rl-id) + (with-syntax ([x x-id] + [rl rl-id]) + (let loop ([fstx fstx]) + (syntax-case fstx () + [([kw arg default] . more) + (keyword? (syntax-e #'kw)) + (cons #'(kw arg (with ([this-syntax x] [this-role rl]) default)) + (loop #'more))] + [([arg default] . more) + (not (keyword? (syntax-e #'kw))) + (cons #'(arg (with ([this-syntax x] [this-role rl]) default)) + (loop #'more))] + [(formal . more) + (cons #'formal (loop #'more))] + [_ fstx]))))) + +;; (with-maybe-delimit-cut bool expr) +(define-syntax with-maybe-delimit-cut + (syntax-rules () + [(wmdc #t k) + (with ([cut-prompt fail-handler]) k)] + [(wmdc #f k) + k])) + +;; (variant-success relsattrs variant (also:id ...) success bool) : expr[Ans] +(define-syntax (variant-success stx) + (syntax-case stx () + [(variant-success relsattrs iattrs (also ...) success cp0 commit?) + #`(with-maybe-reset-fail commit? cp0 + (base-success-expr iattrs relsattrs (also ...) success))])) + +;; (with-maybe-reset-fail bool id expr) +(define-syntax with-maybe-reset-fail + (syntax-rules () + [(wmrs #t cp0 k) + (with ([fail-handler cp0]) k)] + [(wmrs #f cp0 k) + k])) + +;; (base-success-expr iattrs relsattrs (also:id ...) success) : expr[Ans] +(define-syntax (base-success-expr stx) + (syntax-case stx () + [(base-success-expr iattrs relsattrs (also ...) success) + (let ([reliattrs + (reorder-iattrs (wash-sattrs #'relsattrs) + (wash-iattrs #'iattrs))]) + (with-syntax ([(#s(attr name _ _) ...) reliattrs]) + #'(success fail-handler undo-stack also ... (attribute name) ...)))])) + +;; ---- + +;; (parse:clauses x clauses ctx) +(define-syntax (parse:clauses stx) + (syntax-case stx () + [(parse:clauses x clauses body-mode ctx) + ;; if templates? is true, expect one form after kwargs in clause, wrap it with syntax + ;; otherwise, expect non-empty body sequence (defs and exprs) + (with-disappeared-uses + (with-txlifts + (lambda () + (define who + (syntax-case #'ctx () + [(m . _) (identifier? #'m) #'m] + [_ 'syntax-parse])) + (define-values (chunks clauses-stx) + (parse-keyword-options #'clauses parse-directive-table + #:context #'ctx + #:no-duplicates? #t)) + (define context + (options-select-value chunks '#:context #:default #'x)) + (define colon-notation? + (not (assq '#:disable-colon-notation chunks))) + (define track-literals? + (or (assq '#:track-literals chunks) + (eq? (syntax-e #'body-mode) 'one-template))) + (define-values (decls0 defs) + (get-decls+defs chunks #:context #'ctx)) + ;; for-clause : stx -> (values pattern stx (listof stx)) + (define (for-clause clause) + (syntax-case clause () + [[p . rest] + (let-values ([(rest pattern defs2) + (parameterize ((stxclass-colon-notation? colon-notation?)) + (parse-pattern+sides #'p #'rest + #:splicing? #f + #:decls decls0 + #:context #'ctx))]) + (let ([body-expr + (case (syntax-e #'body-mode) + ((one-template) + (syntax-case rest () + [(template) + #'(syntax template)] + [_ (raise-syntax-error #f "expected exactly one template" #'ctx)])) + ((body-sequence) + (syntax-case rest () + [(e0 e ...) + ;; Should we use a shadower (works on the whole file, unhygienically), + ;; or use the context of the syntax-parse identifier? + (let ([the-#%intdef-begin (datum->syntax #'ctx '#%intdef-begin)]) + (if (syntax-local-value the-#%intdef-begin (λ () #f)) ;; Defined as a macro + #`(let () (#,the-#%intdef-begin e0 e ...)) + #'(let () e0 e ...)))] + [_ (raise-syntax-error #f "expected non-empty clause body" + #'ctx clause)])) + (else + (raise-syntax-error #f "internal error: unknown body mode" #'ctx #'body-mode)))]) + (values pattern body-expr defs2)))] + [_ (raise-syntax-error #f "expected clause" #'ctx clause)])) + (define (wrap-track-literals stx) + (if track-literals? (quasisyntax/loc stx (track-literals '#,who #,stx)) stx)) + (unless (stx-list? clauses-stx) + (raise-syntax-error #f "expected sequence of clauses" #'ctx)) + (define-values (patterns body-exprs defs2s) + (for/lists (patterns body-exprs defs2s) ([clause (in-list (stx->list clauses-stx))]) + (for-clause clause))) + (define no-fail? (patterns-cannot-fail? patterns)) + (when no-fail? (log-syntax-parse-debug "cannot fail: ~e" #'ctx)) + (with-syntax ([(def ...) (apply append (get-txlifts-as-definitions) defs defs2s)]) + #`(let* ([ctx0 (normalize-context '#,who #,context x)] + [pr (ps-empty x (cadr ctx0))] + [es #,(if no-fail? #'#f #'#t)] + [cx x] + [fh0 (syntax-patterns-fail ctx0)]) + def ... + (parameterize ((current-syntax-context (cadr ctx0)) + (current-state '#hasheq()) + (current-state-writable? #f)) + #,(wrap-track-literals + #`(with ([fail-handler fh0] + [cut-prompt fh0] + [undo-stack null]) + #,(cond [(pair? patterns) + (with-syntax ([matrix + (optimize-matrix + (for/list ([pattern (in-list patterns)] + [body-expr (in-list body-exprs)]) + (pk1 (list pattern) body-expr)))]) + #'(parse:matrix ((x cx pr es)) matrix)) + #| + (with-syntax ([(alternative ...) + (for/list ([pattern (in-list patterns)] + [body-expr (in-list body-exprs)]) + #`(parse:S x cx #,pattern pr es #,body-expr))]) + #`(try alternative ...)) + |#] + [else + #`(fail (failure* pr es))])))))))))])) + +;; ---- + +;; (parse:matrix ((x cx pr es) ...) (PK ...)) : expr[Ans] +;; (parse:matrix (in1 ... inN) (#s(pk1 (P11 ... P1N) e1) ... #s(pk1 (PM1 ... PMN) eM))) +;; represents the matching matrix +;; [_in1_..._inN_|____] +;; [ P11 ... P1N | e1 ] +;; [ : : | : ] +;; [ PM1 ... PMN | eM ] + +(define-syntax (parse:matrix stx) + (syntax-case stx () + [(parse:matrix ins (pk ...)) + #'(try (parse:pk ins pk) ...)])) + +(define-syntax (parse:pk stx) + (syntax-case stx () + [(parse:pk () #s(pk1 () k)) + #'k] + [(parse:pk ((x cx pr es) . ins) #s(pk1 (pat1 . pats) k)) + #'(parse:S x cx pat1 pr es (parse:pk ins #s(pk1 pats k)))] + [(parse:pk ((x cx pr es) . ins) #s(pk/same pat1 inner)) + #'(parse:S x cx pat1 pr es (parse:matrix ins inner))] + [(parse:pk ((x cx pr es) . ins) #s(pk/pair inner)) + #'(let-values ([(datum tcx) + (if (syntax? x) + (values (syntax-e x) x) + (values x cx))]) + (if (pair? datum) + (let ([hx (car datum)] + [hcx (car datum)] + [hpr (ps-add-car pr)] + [tx (cdr datum)] + [tpr (ps-add-cdr pr)]) + (parse:matrix ((hx hcx hpr es) (tx tcx tpr es) . ins) inner)) + (let ([es* (if (null? datum) (es-add-proper-pair (first-desc:matrix inner) es) es)]) + (fail (failure* pr es*)))))] + [(parse:pk (in1 . ins) #s(pk/and inner)) + #'(parse:matrix (in1 in1 . ins) inner)])) + +(define-syntax (first-desc:matrix stx) + (syntax-case stx () + [(fdm (#s(pk1 (pat1 . pats) k))) + #'(first-desc:S pat1)] + [(fdm (#s(pk/same pat1 pks))) + #'(first-desc:S pat1)] + [(fdm (pk ...)) ;; FIXME + #'#f])) + +;; ---- + +;; (parse:S x cx S-pattern pr es k) : expr[Ans] +;; In k: attrs(S-pattern) are bound. +(define-syntax (parse:S stx) + (syntax-case stx () + [(parse:S x cx pattern0 pr es k) + (syntax-case #'pattern0 () + [#s(pat:seq-end) + #`(k x cx pr)] + [#s(pat:any) + #'k] + [#s(pat:svar name) + #'(let-attributes ([#s(attr name 0 #t) (datum->syntax cx x cx)]) + k)] + [#s(pat:var/p name parser argu (nested-a ...) role + #s(scopts attr-count commit? _delimit? _desc)) + (with-syntax ([(av ...) (generate-n-temporaries (syntax-e #'attr-count))] + [(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) (datum->syntax cx x cx)]) + #'())]) + (if (not (syntax-e #'commit?)) + ;; The normal protocol + #'(app-argu parser x cx pr es undo-stack fail-handler cut-prompt role + (lambda (fh undos av ...) + (let-attributes (name-attr ...) + (let-attributes* ((nested-a ...) (av ...)) + (with ([fail-handler fh] [undo-stack undos]) + k)))) + argu) + ;; The commit protocol + ;; (Avoids putting k in procedure) + #'(let-values ([(fs undos av ...) + (with ([fail-handler + (lambda (undos fs) + (unwind-to undos undo-stack) + (values fs undo-stack (let ([av #f]) av) ...))]) + (with ([cut-prompt fail-handler]) + (app-argu parser x cx pr es undo-stack + fail-handler cut-prompt role + (lambda (fh undos av ...) (values #f undos av ...)) + argu)))]) + (if fs + (fail fs) + (let-attributes (name-attr ...) + (let-attributes* ((nested-a ...) (av ...)) + (with ([undo-stack undos]) + k)))))))] + [#s(pat:reflect obj argu attr-decls name (nested-a ...)) + (with-syntax ([(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) (datum->syntax cx x cx)]) + #'())]) + (with-syntax ([arity (arguments->arity (syntax->datum #'argu))]) + #'(let ([parser (reflect-parser obj 'arity 'attr-decls #f)]) + (app-argu parser x cx pr es undo-stack fail-handler cut-prompt #f + (lambda (fh undos . result) + (let-attributes (name-attr ...) + (let/unpack ((nested-a ...) result) + (with ([fail-handler fh] [undo-stack undos]) + k)))) + argu))))] + [#s(pat:datum datum) + (with-syntax ([unwrap-x + (if (atomic-datum-stx? #'datum) + #'(if (syntax? x) (syntax-e x) x) + #'(syntax->datum (datum->syntax #f x)))]) + #`(let ([d unwrap-x]) + (if (equal? d (quote datum)) + k + (fail (failure* pr (es-add-atom 'datum es))))))] + [#s(pat:literal literal input-phase lit-phase) + #`(if (and (identifier? x) + (free-identifier=? x (quote-syntax literal) input-phase lit-phase)) + (with ([undo-stack (cons (current-state) undo-stack)]) + (state-cons! 'literals x) + k) + (fail (failure* pr (es-add-literal (quote-syntax literal) es))))] + [#s(pat:action action subpattern) + #'(parse:A x cx action pr es (parse:S x cx subpattern pr es k))] + [#s(pat:head head tail) + #`(parse:H x cx rest-x rest-cx rest-pr head pr es + (parse:S rest-x rest-cx tail rest-pr es k))] + [#s(pat:dots head tail) + #`(parse:dots x cx head tail pr es k)] + [#s(pat:and subpatterns) + (for/fold ([k #'k]) ([subpattern (in-list (reverse (syntax->list #'subpatterns)))]) + #`(parse:S x cx #,subpattern pr es #,k))] + [#s(pat:or (a ...) (subpattern ...) (subattrs ...)) + (with-syntax ([(#s(attr id _ _) ...) #'(a ...)]) + #`(let ([success + (lambda (fh undos id ...) + (let-attributes ([a id] ...) + (with ([fail-handler fh] [undo-stack undos]) + k)))]) + (try (parse:S x cx subpattern pr es + (disjunct subattrs success () (id ...))) + ...)))] + [#s(pat:not subpattern) + #`(let* ([fh0 fail-handler] + [pr0 pr] + [es0 es] + [fail-to-succeed + (lambda (undos fs) (unwind-to undos undo-stack) k)]) + ;; ~not implicitly prompts to be safe, + ;; but ~! not allowed within ~not (unless within ~delimit-cut, etc) + ;; (statically checked!) + (with ([fail-handler fail-to-succeed] + [cut-prompt fail-to-succeed]) ;; to be safe + (parse:S x cx subpattern pr es + (fh0 undo-stack (failure* pr0 es0)))))] + [#s(pat:pair head tail) + #`(let ([datum (if (syntax? x) (syntax-e x) x)] + [cx (if (syntax? x) x cx)]) ;; FIXME: shadowing cx?! + (if (pair? datum) + (let ([hx (car datum)] + [hcx (car datum)] + [hpr (ps-add-car pr)] + [tx (cdr datum)] + [tpr (ps-add-cdr pr)]) + (parse:S hx hcx head hpr es + (parse:S tx cx tail tpr es k))) + (let ([es* (if (null? datum) (es-add-proper-pair (first-desc:S head) es) es)]) + (fail (failure* pr es*)))))] + [#s(pat:vector subpattern) + #`(let ([datum (if (syntax? x) (syntax-e x) x)]) + (if (vector? datum) + (let ([datum (vector->list datum)] + [vcx (if (syntax? x) x cx)] ;; FIXME: (vector? datum) => (syntax? x) ??? + [pr* (ps-add-unvector pr)]) + (parse:S datum vcx subpattern pr* es k)) + (fail (failure* pr es))))] + [#s(pat:box subpattern) + #`(let ([datum (if (syntax? x) (syntax-e x) x)]) + (if (box? datum) + (let ([datum (unbox datum)] + [bcx (if (syntax? x) x cx)] ;; FIXME: (box? datum) => (syntax? x) ??? + [pr* (ps-add-unbox pr)]) + (parse:S datum bcx subpattern pr* es k)) + (fail (failure* pr es))))] + [#s(pat:pstruct key subpattern) + #`(let ([datum (if (syntax? x) (syntax-e x) x)]) + (if (let ([xkey (prefab-struct-key datum)]) + (and xkey (equal? xkey 'key))) + (let ([datum (cdr (vector->list (struct->vector datum)))] + [scx (if (syntax? x) x cx)] ;; FIXME: (struct? datum) => (syntax? x) ??? + [pr* (ps-add-unpstruct pr)]) + (parse:S datum scx subpattern pr* es k)) + (fail (failure* pr es))))] + [#s(pat:describe pattern description transparent? role) + #`(let ([es* (es-add-thing pr description transparent? role es)] + [pr* (if 'transparent? pr (ps-add-opaque pr))]) + (parse:S x cx pattern pr* es* k))] + [#s(pat:delimit pattern) + #`(let ([cp0 cut-prompt]) + (with ([cut-prompt fail-handler]) + (parse:S x cx pattern pr es (with ([cut-prompt cp0]) k))))] + [#s(pat:commit pattern) + #`(let ([fh0 fail-handler] + [cp0 cut-prompt]) + (with ([cut-prompt fh0]) + (parse:S x cx pattern pr es + (with ([cut-prompt cp0] + [fail-handler fh0]) + k))))] + [#s(pat:ord pattern group index) + #`(let ([pr* (ps-add pr '#s(ord group index))]) + (parse:S x cx pattern pr* es k))] + [#s(pat:post pattern) + #`(let ([pr* (ps-add-post pr)]) + (parse:S x cx pattern pr* es k))] + [#s(pat:integrated name predicate description role) + (with-syntax ([(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) x*]) + #'())]) + #'(let ([x* (datum->syntax cx x cx)]) + (if (predicate x*) + (let-attributes (name-attr ...) k) + (let ([es* (es-add-thing pr 'description #t role es)]) + (fail (failure* pr es*))))))] + [_ (wrong-syntax stx "internal error: bad S pattern: ~e" #'pattern0)])])) + +;; (first-desc:S S-pattern) : expr[FirstDesc] +(define-syntax (first-desc:S stx) + (syntax-case stx () + [(fds p) + (syntax-case #'p () + [#s(pat:any) + #''(any)] + [#s(pat:svar name) + #''(any)] + [#s(pat:var/p _ _ _ _ _ #s(scopts _ _ _ desc)) + #'(quote desc)] + [#s(pat:datum d) + #''(datum d)] + [#s(pat:literal id _ip _lp) + #''(literal id)] + [#s(pat:describe _p desc _t? _role) + #`(quote #,(or (constant-desc #'desc) #'#f))] + [#s(pat:delimit pattern) + #'(first-desc:S pattern)] + [#s(pat:commit pattern) + #'(first-desc:S pattern)] + [#s(pat:ord pattern _ _) + #'(first-desc:S pattern)] + [#s(pat:post pattern) + #'(first-desc:S pattern)] + [#s(pat:integrated _name _pred description _role) + #''description] + [_ #'#f])])) + +;; (first-desc:H HeadPattern) : Expr +(define-syntax (first-desc:H stx) + (syntax-case stx () + [(fdh hpat) + (syntax-case #'hpat () + [#s(hpat:single sp) #'(first-desc:S sp)] + [#s(hpat:var/p _ _ _ _ _ #s(scopts _ _ _ desc)) #'(quote desc)] + [#s(hpat:seq lp) #'(first-desc:L lp)] + [#s(hpat:describe _hp desc _t? _r) + #`(quote #,(or (constant-desc #'desc) #'#f))] + [#s(hpat:delimit hp) #'(first-desc:H hp)] + [#s(hpat:commit hp) #'(first-desc:H hp)] + [#s(hpat:ord hp _ _) #'(first-desc:H hp)] + [#s(hpat:post hp) #'(first-desc:H hp)] + [_ #'#f])])) + +(define-syntax (first-desc:L stx) + (syntax-case stx () + [(fdl lpat) + (syntax-case #'lpat () + [#s(pat:pair sp lp) #'(first-desc:S sp)] + [_ #'#f])])) + +;; (disjunct (iattr ...) success (pre:expr ...) (id:id ...)) : expr[Ans] +(define-syntax (disjunct stx) + (syntax-case stx () + [(disjunct (#s(attr sub-id _ _) ...) success (pre ...) (id ...)) + (with-syntax ([(alt-sub-id ...) (generate-temporaries #'(sub-id ...))]) + #`(let ([alt-sub-id (attribute sub-id)] ...) + (let ([id #f] ...) + (let ([sub-id alt-sub-id] ...) + (success fail-handler undo-stack pre ... id ...)))))])) + +;; (parse:A x cx A-pattern pr es k) : expr[Ans] +;; In k: attrs(A-pattern) are bound. +(define-syntax (parse:A stx) + (syntax-case stx () + [(parse:A x cx pattern0 pr es k) + (syntax-case #'pattern0 () + [#s(action:and (action ...)) + (for/fold ([k #'k]) ([action (in-list (reverse (syntax->list #'(action ...))))]) + #`(parse:A x cx #,action pr es #,k))] + [#s(action:cut) + #'(with ([fail-handler cut-prompt]) k)] + [#s(action:bind a expr) + #'(let-attributes ([a (wrap-user-code expr)]) k)] + [#s(action:fail condition message) + #`(let ([c (wrap-user-code condition)]) + (if c + (let ([pr* (if (syntax? c) (ps-add-stx pr c) pr)] + [es* (es-add-message message es)]) + (fail (failure* pr* es*))) + k))] + [#s(action:parse pattern expr) + #`(let* ([y (datum->syntax/with-clause (wrap-user-code expr))] + [cy y] + [pr* (ps-add-stx pr y)]) + (parse:S y cy pattern pr* es k))] + [#s(action:do (stmt ...)) + #'(parameterize ((current-state-writable? #t)) + (let ([init-state (current-state)]) + (no-shadow stmt) ... + (parameterize ((current-state-writable? #f)) + (with ([undo-stack (maybe-add-state-undo init-state (current-state) undo-stack)]) + (#%expression k)))))] + [#s(action:undo (stmt ...)) + #'(with ([undo-stack (cons (lambda () stmt ... (void)) undo-stack)] + [cut-prompt illegal-cut-error]) + k)] + [#s(action:ord pattern group index) + #'(let ([pr* (ps-add pr '#s(ord group index))]) + (parse:A x cx pattern pr* es k))] + [#s(action:post pattern) + #'(let ([pr* (ps-add-post pr)]) + (parse:A x cx pattern pr* es k))] + [_ (wrong-syntax stx "internal error: bad A pattern: ~e" #'pattern0)])])) + +;; (parse:H x cx rest-x rest-cx rest-pr H-pattern pr es k) +;; In k: rest, rest-pr, attrs(H-pattern) are bound. +(define-syntax (parse:H stx) + (syntax-case stx () + [(parse:H x cx rest-x rest-cx rest-pr head pr es k) + (syntax-case #'head () + [#s(hpat:single pattern) + #'(parse:S x cx + ;; FIXME: consider proper-list-pattern? (yes is consistent with ~seq) + #s(pat:pair pattern #s(pat:seq-end)) + pr es (lambda (rest-x rest-cx rest-pr) k))] + [#s(hpat:describe pattern description transparent? role) + #`(let ([es* (es-add-thing pr description transparent? role es)] + [pr* (if 'transparent? pr (ps-add-opaque pr))]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr* es* + (let ([rest-pr (if 'transparent? rest-pr (ps-pop-opaque rest-pr))]) + k)))] + [#s(hpat:var/p name parser argu (nested-a ...) role + #s(scopts attr-count commit? _delimit? _desc)) + (with-syntax ([(av ...) (generate-n-temporaries (syntax-e #'attr-count))] + [(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) + (stx-list-take x (ps-difference pr rest-pr))]) + #'())]) + (if (not (syntax-e #'commit?)) + ;; The normal protocol + #`(app-argu parser x cx pr es undo-stack fail-handler cut-prompt role + (lambda (fh undos rest-x rest-cx rest-pr av ...) + (let-attributes (name-attr ...) + (let-attributes* ((nested-a ...) (av ...)) + (with ([fail-handler fh] [undo-stack undos]) + k)))) + argu) + ;; The commit protocol + ;; (Avoids putting k in procedure) + #'(let-values ([(fs undos rest-x rest-cx rest-pr av ...) + (with ([fail-handler + (lambda (undos fs) + (unwind-to undos undo-stack) + (values fs undo-stack #f #f #f (let ([av #f]) av) ...))]) + (with ([cut-prompt fail-handler]) + (app-argu parser x cx pr es undo-stack + fail-handler cut-prompt role + (lambda (fh undos rest-x rest-cx rest-pr av ...) + (values #f undos rest-x rest-cx rest-pr av ...)) + argu)))]) + (if fs + (fail fs) + (let-attributes (name-attr ...) + (let-attributes* ((nested-a ...) (av ...)) + (with ([undo-stack undos]) + k)))))))] + [#s(hpat:reflect obj argu attr-decls name (nested-a ...)) + (with-syntax ([(name-attr ...) + (if (identifier? #'name) + #'([#s(attr name 0 #t) + (stx-list-take x (ps-difference pr rest-pr))]) + #'())]) + (with-syntax ([arity (arguments->arity (syntax->datum #'argu))]) + #'(let ([parser (reflect-parser obj 'arity 'attr-decls #t)]) + (app-argu parser x cx pr es undo-stack fail-handler cut-prompt #f + (lambda (fh undos rest-x rest-cx rest-pr . result) + (let-attributes (name-attr ...) + (let/unpack ((nested-a ...) result) + (with ([fail-handler fh] [undo-stack undos]) + k)))) + argu))))] + [#s(hpat:and head single) + #`(let ([cx0 cx]) + (parse:H x cx rest-x rest-cx rest-pr head pr es + (let ([lst (stx-list-take x (ps-difference pr rest-pr))]) + (parse:S lst cx0 single pr es k))))] + [#s(hpat:or (a ...) (subpattern ...) (subattrs ...)) + (with-syntax ([(#s(attr id _ _) ...) #'(a ...)]) + #`(let ([success + (lambda (fh undos rest-x rest-cx rest-pr id ...) + (let-attributes ([a id] ...) + (with ([fail-handler fh] [undo-stack undos]) + k)))]) + (try (parse:H x cx rest-x rest-cx rest-pr subpattern pr es + (disjunct subattrs success (rest-x rest-cx rest-pr) (id ...))) + ...)))] + [#s(hpat:seq pattern) + #'(parse:S x cx pattern pr es (lambda (rest-x rest-cx rest-pr) k))] + [#s(hpat:action action subpattern) + #'(parse:A x cx action pr es (parse:H x cx rest-x rest-cx rest-pr subpattern pr es k))] + [#s(hpat:delimit pattern) + #'(let ([cp0 cut-prompt]) + (with ([cut-prompt fail-handler]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr es + (with ([cut-prompt cp0]) k))))] + [#s(hpat:commit pattern) + #`(let ([fh0 fail-handler] + [cp0 cut-prompt]) + (with ([cut-prompt fh0]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr es + (with ([cut-prompt cp0] + [fail-handler fh0]) + k))))] + [#s(hpat:ord pattern group index) + #`(let ([pr* (ps-add pr '#s(ord group index))]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr* es + (let ([rest-pr (ps-pop-ord rest-pr)]) k)))] + [#s(hpat:post pattern) + #'(let ([pr* (ps-add-post pr)]) + (parse:H x cx rest-x rest-cx rest-pr pattern pr* es + (let ([rest-pr (ps-pop-post rest-pr)]) k)))] + [#s(hpat:peek pattern) + #`(let ([saved-x x] [saved-cx cx] [saved-pr pr]) + (parse:H x cx dummy-x dummy-cx dummy-pr pattern pr es + (let ([rest-x saved-x] [rest-cx saved-cx] [rest-pr saved-pr]) + k)))] + [#s(hpat:peek-not subpattern) + #`(let* ([fh0 fail-handler] + [pr0 pr] + [es0 es] + [fail-to-succeed + (lambda (undos fs) + (unwind-to undos undo-stack) + (let ([rest-x x] + [rest-cx cx] + [rest-pr pr]) + k))]) + ;; ~not implicitly prompts to be safe, + ;; but ~! not allowed within ~not (unless within ~delimit-cut, etc) + ;; (statically checked!) + (with ([fail-handler fail-to-succeed] + [cut-prompt fail-to-succeed]) ;; to be safe + (parse:H x cx rest-x rest-cx rest-pr subpattern pr es + (fh0 undo-stack (failure* pr0 es0)))))] + [_ (wrong-syntax stx "internal error: bad H pattern: ~e" #'head)])])) + +;; (parse:dots x cx EH-pattern S-pattern pr es k) : expr[Ans] +;; In k: attrs(EH-pattern, S-pattern) are bound. +(define-syntax (parse:dots stx) + (syntax-case stx () + ;; == Specialized cases + ;; -- (x ... . ()) + [(parse:dots x cx (#s(ehpat (attr0) #s(hpat:single #s(pat:svar name)) #f #f)) + #s(pat:datum ()) pr es k) + #'(let-values ([(status result) (predicate-ellipsis-parser x cx pr es void #f #f)]) + (case status + ((ok) (let-attributes ([attr0 result]) k)) + (else (fail result))))] + ;; -- (x:sc ... . ()) where sc is an integrable stxclass like id or expr + [(parse:dots x cx (#s(ehpat (attr0) #s(hpat:single #s(pat:integrated _name pred? desc role)) #f #f)) + #s(pat:datum ()) pr es k) + #'(let-values ([(status result) (predicate-ellipsis-parser x cx pr es pred? desc role)]) + (case status + ((ok) (let-attributes ([attr0 result]) k)) + (else (fail result))))] + ;; -- (x:sc ... . ()) where sc is a stxclass with commit + ;; Since head pattern does commit, no need to thread fail-handler, cut-prompt through. + ;; Microbenchmark suggests this isn't a useful specialization + ;; (probably try-or-pair/null-check already does the useful part) + ;; == General case + [(parse:dots x cx (#s(ehpat head-attrs head head-repc check-null?) ...) tail pr es k) + (let () + (define repcs (wash-list wash #'(head-repc ...))) + (define rep-ids (for/list ([repc (in-list repcs)]) + (and repc (generate-temporary 'rep)))) + (define rel-repcs (filter values repcs)) + (define rel-rep-ids (filter values rep-ids)) + (define rel-heads (for/list ([head (in-list (syntax->list #'(head ...)))] + [repc (in-list repcs)] + #:when repc) + head)) + (define aattrs + (for/list ([head-attrs (in-list (syntax->list #'(head-attrs ...)))] + [repc (in-list repcs)] + #:when #t + [a (in-list (wash-iattrs head-attrs))]) + (cons a repc))) + (define attrs (map car aattrs)) + (define attr-repcs (map cdr aattrs)) + (define ids (map attr-name attrs)) + (define tail-pattern-is-null? (equal? (syntax->datum #'tail) '#s(pat:datum ()))) + (with-syntax ([(id ...) ids] + [(alt-id ...) (generate-temporaries ids)] + [reps rel-rep-ids] + [(head-rep ...) rep-ids] + [(rel-rep ...) rel-rep-ids] + [(rel-repc ...) rel-repcs] + [(rel-head ...) rel-heads] + [(a ...) attrs] + [(attr-repc ...) attr-repcs] + [do-pair/null? + ;; FIXME: do pair/null check only if no nullable head patterns + ;; (and tail-pattern-is-null? (andmap not (syntax->datum #'(nullable? ...)))) + tail-pattern-is-null?]) + (define/with-syntax alt-map #'((id . alt-id) ...)) + (define/with-syntax loop-k + #'(dots-loop dx* dcx* loop-pr* undo-stack fail-handler rel-rep ... alt-id ...)) + #`(let () + ;; dots-loop : stx progress rel-rep ... alt-id ... -> Ans + (define (dots-loop dx dcx loop-pr undos fh rel-rep ... alt-id ...) + (with ([fail-handler fh] [undo-stack undos]) + (try-or-pair/null-check do-pair/null? dx dcx loop-pr es + (try (parse:EH dx dcx loop-pr head-attrs check-null? head-repc dx* dcx* loop-pr* + alt-map head-rep head es loop-k) + ...) + (cond [(< rel-rep (rep:min-number rel-repc)) + (let ([es (expectation-of-reps/too-few es rel-rep rel-repc rel-head)]) + (fail (failure* loop-pr es)))] + ... + [else + (let-attributes ([a (rep:finalize a attr-repc alt-id)] ...) + (parse:S dx dcx tail loop-pr es k))])))) + (let ([rel-rep 0] ... + [alt-id (rep:initial-value attr-repc)] ...) + (dots-loop x cx pr undo-stack fail-handler rel-rep ... alt-id ...)))))])) + +;; (try-or-pair/null-check bool x cx es pr pair-alt maybe-null-alt) +(define-syntax try-or-pair/null-check + (syntax-rules () + [(topc #t x cx pr es pair-alt null-alt) + (cond [(stx-pair? x) pair-alt] + [(stx-null? x) null-alt] + [else (fail (failure* pr es))])] + [(topc _ x cx pr es alt1 alt2) + (try alt1 alt2)])) + +;; (parse:EH x cx pr repc x* cx* pr* alts rep H-pattern es k) : expr[Ans] +;; In k: x*, cx*, pr*, alts`attrs(H-pattern) are bound and rep is shadowed. +(define-syntax (parse:EH stx) + (syntax-case stx () + [(parse:EH x cx pr attrs check-null? repc x* cx* pr* alts rep head es k) + (let () + (define/with-syntax k* + (let* ([main-attrs (wash-iattrs #'attrs)] + [ids (map attr-name main-attrs)] + [alt-ids + (let ([table (make-bound-id-table)]) + (for ([entry (in-list (syntax->list #'alts))]) + (let ([entry (syntax-e entry)]) + (bound-id-table-set! table (car entry) (cdr entry)))) + (for/list ([id (in-list ids)]) (bound-id-table-ref table id)))]) + (with-syntax ([(id ...) ids] + [(alt-id ...) alt-ids]) + #`(let ([alt-id (rep:combine repc (attribute id) alt-id)] ...) + #,(if (syntax->datum #'check-null?) + #'(if (zero? (ps-difference pr pr*)) (error/null-eh-match) k) + #'k))))) + (syntax-case #'repc () + [#f #`(parse:H x cx x* cx* pr* head pr es k*)] + [_ #`(parse:H x cx x* cx* pr* head pr es + (if (< rep (rep:max-number repc)) + (let ([rep (add1 rep)]) k*) + (let ([es* (expectation-of-reps/too-many es rep repc)]) + (fail (failure* pr* es*)))))]))])) + +;; (rep:initial-value RepConstraint) : expr +(define-syntax (rep:initial-value stx) + (syntax-case stx () + [(_ #s(rep:once _ _ _)) #'#f] + [(_ #s(rep:optional _ _ _)) #'#f] + [(_ _) #'null])) + +;; (rep:finalize RepConstraint expr) : expr +(define-syntax (rep:finalize stx) + (syntax-case stx () + [(_ a #s(rep:optional _ _ defaults) v) + (with-syntax ([#s(attr name _ _) #'a] + [(#s(action:bind da de) ...) #'defaults]) + (let ([default + (for/or ([da (in-list (syntax->list #'(da ...)))] + [de (in-list (syntax->list #'(de ...)))]) + (with-syntax ([#s(attr dname _ _) da]) + (and (bound-identifier=? #'name #'dname) de)))]) + (if default + #`(or v #,default) + #'v)))] + [(_ a #s(rep:once _ _ _) v) #'v] + [(_ a _ v) #'(reverse v)])) + +;; (rep:min-number RepConstraint) : expr +(define-syntax (rep:min-number stx) + (syntax-case stx () + [(_ #s(rep:once _ _ _)) #'1] + [(_ #s(rep:optional _ _ _)) #'0] + [(_ #s(rep:bounds min max _ _ _)) #'min])) + +;; (rep:max-number RepConstraint) : expr +(define-syntax (rep:max-number stx) + (syntax-case stx () + [(_ #s(rep:once _ _ _)) #'1] + [(_ #s(rep:optional _ _ _)) #'1] + [(_ #s(rep:bounds min max _ _ _)) #'max])) + +;; (rep:combine RepConstraint expr expr) : expr +(define-syntax (rep:combine stx) + (syntax-case stx () + [(_ #s(rep:once _ _ _) a b) #'a] + [(_ #s(rep:optional _ _ _) a b) #'a] + [(_ _ a b) #'(cons a b)])) + +;; ---- + +(define-syntax expectation-of-reps/too-few + (syntax-rules () + [(_ es rep #s(rep:once name too-few-msg too-many-msg) hpat) + (cond [(or too-few-msg (name->too-few/once name)) + => (lambda (msg) (es-add-message msg es))] + [(first-desc:H hpat) => (lambda (fd) (es-add-proper-pair fd es))] + [else es])] + [(_ es rep #s(rep:optional name too-many-msg _) hpat) + (error 'syntax-parse "INTERNAL ERROR: impossible (too-few)")] + [(_ es rep #s(rep:bounds min max name too-few-msg too-many-msg) hpat) + (cond [(or too-few-msg (name->too-few name)) + => (lambda (msg) (es-add-message msg es))] + [(first-desc:H hpat) => (lambda (fd) (es-add-proper-pair fd es))] + [else es])])) + +(define-syntax expectation-of-reps/too-many + (syntax-rules () + [(_ es rep #s(rep:once name too-few-msg too-many-msg)) + (es-add-message (or too-many-msg (name->too-many name)) es)] + [(_ es rep #s(rep:optional name too-many-msg _)) + (es-add-message (or too-many-msg (name->too-many name)) es)] + [(_ es rep #s(rep:bounds min max name too-few-msg too-many-msg)) + (es-add-message (or too-many-msg (name->too-many name)) es)])) + +;; ==== + +(define-syntax (define-eh-alternative-set stx) + (define (parse-alt x) + (syntax-case x (pattern) + [(pattern alt) + #'alt] + [else + (wrong-syntax x "expected eh-alternative-set alternative")])) + (parameterize ((current-syntax-context stx)) + (syntax-case stx () + [(_ name a ...) + (unless (identifier? #'name) + (wrong-syntax #'name "expected identifier")) + (let* ([alts (map parse-alt (syntax->list #'(a ...)))] + [decls (new-declenv null #:conventions null)] + [ehpat+hstx-list + (apply append + (for/list ([alt (in-list alts)]) + (parse-EH-variant alt decls #t #:context stx)))] + [eh-alt+defs-list + (for/list ([ehpat+hstx (in-list ehpat+hstx-list)]) + (let ([ehpat (car ehpat+hstx)] + [hstx (cadr ehpat+hstx)]) + (cond [(syntax? hstx) + (define the-pattern (ehpat-head ehpat)) + (define attrs (iattrs->sattrs (pattern-attrs the-pattern))) + (define the-variant (variant hstx attrs the-pattern null)) + (define the-rhs (rhs attrs #t #f (list the-variant) null #f #f)) + (with-syntax ([(parser) (generate-temporaries '(eh-alt-parser))] + [the-rhs-expr (datum->expression the-rhs)]) + (list (eh-alternative (ehpat-repc ehpat) attrs #'parser) + (list #`(define parser + (parser/rhs parser () #,attrs + the-rhs-expr #t #,stx)))))] + [(eh-alternative? hstx) + (list hstx null)] + [else + (error 'define-eh-alternative-set "internal error: unexpected ~e" + hstx)])))] + [eh-alts (map car eh-alt+defs-list)] + [defs (apply append (map cadr eh-alt+defs-list))]) + (with-syntax ([(def ...) defs] + [(alt-expr ...) + (for/list ([alt (in-list eh-alts)]) + (with-syntax ([repc-expr + (datum->expression (eh-alternative-repc alt))] + [attrs-expr + #`(quote #,(eh-alternative-attrs alt))] + [parser-expr + #`(quote-syntax #,(eh-alternative-parser alt))]) + #'(eh-alternative repc-expr attrs-expr parser-expr)))]) + #'(begin def ... + (define-syntax name + (eh-alternative-set (list alt-expr ...))))))]))) diff --git a/7-4/racket/collects/syntax/parse/private/rep.rkt b/7-4/racket/collects/syntax/parse/private/rep.rkt new file mode 100644 index 0000000..be08bc3 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/rep.rkt @@ -0,0 +1,1965 @@ +#lang racket/base +(require (for-template racket/base + syntax/parse/private/keywords + stxparse-info/parse/private/residual ;; keep abs. path + stxparse-info/parse/private/runtime) + racket/list + racket/contract/base + "make.rkt" + syntax/parse/private/minimatch + syntax/apply-transformer + syntax/private/id-table + syntax/stx + syntax/keyword + racket/syntax + racket/struct + "txlift.rkt" + syntax/parse/private/rep-attrs + syntax/parse/private/rep-data + syntax/parse/private/rep-patterns + syntax/parse/private/residual-ct ;; keep abs. path + syntax/parse/private/kws) + +;; Error reporting +;; All entry points should have explicit, mandatory #:context arg +;; (mandatory from outside, at least) + +(provide/contract + [atomic-datum-stx? + (-> syntax? + boolean?)] + [parse-rhs + (->* [syntax? boolean? #:context (or/c false/c syntax?)] + [#:default-description (or/c #f string?)] + rhs?)] + [parse-pattern+sides + (-> syntax? syntax? + #:splicing? boolean? + #:decls DeclEnv/c + #:context syntax? + any)] + [parse-EH-variant + (-> syntax? DeclEnv/c boolean? + #:context syntax? + any)] + [parse-directive-table any/c] + [get-decls+defs + (-> list? #:context (or/c false/c syntax?) + (values DeclEnv/c (listof syntax?)))] + [create-aux-def + (-> DeclEntry/c + (values DeclEntry/c (listof syntax?)))] + [parse-argu + (-> (listof syntax?) + #:context syntax? + arguments?)] + [parse-kw-formals + (-> syntax? + #:context syntax? + arity?)] + [check-stxclass-header + (-> syntax? syntax? + (list/c identifier? syntax? arity?))] + [check-stxclass-application + (-> syntax? syntax? + (cons/c identifier? arguments?))] + [check-conventions-rules + (-> syntax? syntax? + (listof (list/c regexp? any/c)))] + [check-datum-literals-list + (-> syntax? syntax? + (listof den:datum-lit?))] + [check-attr-arity-list + (-> syntax? syntax? + (listof sattr?))] + [stxclass-colon-notation? + (parameter/c boolean?)] + [fixup-rhs + (-> rhs? boolean? (listof sattr?) rhs?)]) + +;; ---- + +(define (atomic-datum-stx? stx) + (let ([datum (syntax-e stx)]) + (or (null? datum) + (boolean? datum) + (string? datum) + (number? datum) + (keyword? datum) + (bytes? datum) + (char? datum) + (regexp? datum) + (byte-regexp? datum)))) + +(define (id-predicate kw) + (lambda (stx) + (and (identifier? stx) + (free-identifier=? stx kw) + (begin (disappeared! stx) #t)))) + +(define wildcard? (id-predicate (quote-syntax _))) +(define epsilon? (id-predicate (quote-syntax ||))) +(define dots? (id-predicate (quote-syntax ...))) +(define plus-dots? (id-predicate (quote-syntax ...+))) + +(define keywords + (list (quote-syntax _) + (quote-syntax ||) + (quote-syntax ...) + (quote-syntax ~var) + (quote-syntax ~datum) + (quote-syntax ~literal) + (quote-syntax ~and) + (quote-syntax ~or) + (quote-syntax ~or*) + (quote-syntax ~alt) + (quote-syntax ~not) + (quote-syntax ~seq) + (quote-syntax ~rep) + (quote-syntax ~once) + (quote-syntax ~optional) + (quote-syntax ~between) + (quote-syntax ~rest) + (quote-syntax ~describe) + (quote-syntax ~!) + (quote-syntax ~bind) + (quote-syntax ~fail) + (quote-syntax ~parse) + (quote-syntax ~do) + (quote-syntax ~undo) + (quote-syntax ...+) + (quote-syntax ~delimit-cut) + (quote-syntax ~commit) + (quote-syntax ~reflect) + (quote-syntax ~splicing-reflect) + (quote-syntax ~eh-var) + (quote-syntax ~peek) + (quote-syntax ~peek-not))) + +(define (reserved? stx) + (and (identifier? stx) + (for/or ([kw (in-list keywords)]) + (free-identifier=? stx kw)))) + +(define (safe-name? stx) + (and (identifier? stx) + (not (regexp-match? #rx"^~" (symbol->string (syntax-e stx)))))) + +;; cut-allowed? : (paramter/c boolean?) +;; Used to detect ~cut within ~not pattern. +;; (Also #:no-delimit-cut stxclass within ~not) +(define cut-allowed? (make-parameter #t)) + +;; A LookupConfig is one of 'no, 'try, 'yes +;; 'no means don't lookup, always use dummy (no nested attrs) +;; 'try means lookup, but on failure use dummy (-> nested attrs only from prev.) +;; 'yes means lookup, raise error on failure + +;; stxclass-lookup-config : parameterof LookupConfig +(define stxclass-lookup-config (make-parameter 'yes)) + +;; stxclass-colon-notation? : (parameterof boolean) +;; if #t, then x:sc notation means (~var x sc) +;; otherwise, just a var +(define stxclass-colon-notation? (make-parameter #t)) + +;; disappeared! : (U Identifier (Stxpair Identifier Any)) -> Void +(define (disappeared! x) + (cond [(identifier? x) + (record-disappeared-uses (list x))] + [(and (stx-pair? x) (identifier? (stx-car x))) + (record-disappeared-uses (list (stx-car x)))] + [else + (raise-type-error 'disappeared! + "identifier or syntax with leading identifier" + x)])) + +(define (propagate-disappeared! stx) + (cond [(and (syntax? stx) (syntax-property stx 'disappeared-use)) + => (lambda (xs) (record-disappeared-uses (filter identifier? (flatten xs)) #f))])) + + +;; ============================================================ +;; Entry points to pattern/rhs parsing + +;; parse-rhs : Syntax Boolean #:context Syntax #:default-description (U String #f) -> RHS +(define (parse-rhs stx splicing? #:context ctx #:default-description [default-description #f]) + (call/txlifts + (lambda () + (parameterize ((current-syntax-context ctx)) + (define-values (rest description transp? attributes auto-nested? colon-notation? + decls defs commit? delimit-cut?) + (parse-rhs/part1 stx splicing?)) + (define variants + (parameterize ((stxclass-lookup-config (if auto-nested? 'try 'no)) + (stxclass-colon-notation? colon-notation?)) + (parse-variants rest decls splicing?))) + (define sattrs + (or attributes + (filter (lambda (a) (symbol-interned? (attr-name a))) + (intersect-sattrss (map variant-attrs variants))))) + (make rhs sattrs transp? (or description #`(quote #,default-description)) variants + (append (get-txlifts-as-definitions) defs) + commit? delimit-cut?))))) + +(define (parse-rhs/part1 stx splicing?) + (define-values (chunks rest) + (parse-keyword-options stx rhs-directive-table + #:context (current-syntax-context) + #:incompatible '((#:attributes #:auto-nested-attributes) + (#:commit #:no-delimit-cut)) + #:no-duplicates? #t)) + (define description (options-select-value chunks '#:description #:default #f)) + (define opaque? (and (assq '#:opaque chunks) #t)) + (define transparent? (not opaque?)) + (define auto-nested? (and (assq '#:auto-nested-attributes chunks) #t)) + (define colon-notation? (not (assq '#:disable-colon-notation chunks))) + (define commit? + (and (assq '#:commit chunks) #t)) + (define delimit-cut? + (not (assq '#:no-delimit-cut chunks))) + (define attributes (options-select-value chunks '#:attributes #:default #f)) + (define-values (decls defs) (get-decls+defs chunks)) + (values rest description transparent? attributes auto-nested? colon-notation? + decls defs commit? delimit-cut?)) + +(define (parse-variants rest decls splicing?) + (define (gather-variants stx) + (syntax-case stx (pattern) + [((pattern . _) . rest) + (begin (disappeared! (stx-car stx)) + (cons (parse-variant (stx-car stx) splicing? decls) + (gather-variants #'rest)))] + [(bad-variant . rest) + (wrong-syntax #'bad-variant "expected syntax-class variant")] + [() + null])) + (gather-variants rest)) + +;; get-decls+defs : chunks boolean -> (values DeclEnv (listof syntax)) +(define (get-decls+defs chunks #:context [ctx (current-syntax-context)]) + (parameterize ((current-syntax-context ctx)) + (let*-values ([(decls defs1) (get-decls chunks)] + [(decls defs2) (decls-create-defs decls)]) + (values decls (append defs1 defs2))))) + +;; get-decls : chunks -> (values DeclEnv (listof syntax)) +(define (get-decls chunks) + (define lits (options-select-value chunks '#:literals #:default null)) + (define datum-lits (options-select-value chunks '#:datum-literals #:default null)) + (define litsets (options-select-value chunks '#:literal-sets #:default null)) + (define convs (options-select-value chunks '#:conventions #:default null)) + (define localconvs (options-select-value chunks '#:local-conventions #:default null)) + (define literals + (append/check-lits+litsets lits datum-lits litsets)) + (define-values (convs-rules convs-defs) + (for/fold ([convs-rules null] [convs-defs null]) + ([conv-entry (in-list convs)]) + (let* ([c (car conv-entry)] + [argu (cdr conv-entry)] + [get-parser-id (conventions-get-procedures c)] + [rules ((conventions-get-rules c))]) + (values (append rules convs-rules) + (cons (make-conventions-def (map cadr rules) get-parser-id argu) + convs-defs))))) + (define convention-rules (append localconvs convs-rules)) + (values (new-declenv literals #:conventions convention-rules) + (reverse convs-defs))) + +;; make-conventions-def : (listof den:delay) id Argument -> syntax +(define (make-conventions-def dens get-parsers-id argu) + (with-syntax ([(parser ...) (map den:delayed-parser dens)] + [get-parsers get-parsers-id] + [argu argu]) + #'(define-values (parser ...) + (apply values (app-argu get-parsers argu))))) + +;; decls-create-defs : DeclEnv -> (values DeclEnv (listof stx)) +(define (decls-create-defs decls0) + (define (updater key value defs) + (let-values ([(value newdefs) (create-aux-def value)]) + (values value (append newdefs defs)))) + (declenv-update/fold decls0 updater null)) + +;; create-aux-def : DeclEntry -> (values DeclEntry (listof stx)) +;; FIXME: replace with txlift mechanism +(define (create-aux-def entry) + (match entry + [(? den:lit?) + (values entry null)] + [(? den:datum-lit?) + (values entry null)] + [(? den:magic-class?) + (values entry null)] + [(den:class name scname argu) + (with-syntax ([parser (generate-temporary scname)]) + (values (make den:delayed #'parser scname) + (list #`(define-values (parser) (curried-stxclass-parser #,scname #,argu)))))] + [(? den:delayed?) + (values entry null)])) + +;; append/check-lits+litsets : .... -> (listof (U den:lit den:datum-lit)) +(define (append/check-lits+litsets lits datum-lits litsets) + (define seen (make-bound-id-table)) + (define (check-id id [blame-ctx id]) + (if (bound-id-table-ref seen id #f) + (wrong-syntax blame-ctx "duplicate literal declaration: ~s" (syntax-e id)) + (bound-id-table-set! seen id #t)) + id) + (let* ([litsets* + (for/list ([entry (in-list litsets)]) + (let ([litset-id (first entry)] + [litset (second entry)] + [lctx (third entry)] + [input-phase (fourth entry)]) + (define (get/check-id sym) + (check-id (datum->syntax lctx sym) litset-id)) + (for/list ([lse (in-list (literalset-literals litset))]) + (match lse + [(lse:lit internal external lit-phase) + (let ([internal (get/check-id internal)] + [external (syntax-property external 'literal (gensym))]) + (make den:lit internal external input-phase lit-phase))] + [(lse:datum-lit internal external) + (let ([internal (get/check-id internal)]) + (make den:datum-lit internal external))]))))] + [lits* + (for/list ([lit (in-list lits)]) + (check-id (den:lit-internal lit)) + lit)] + [datum-lits* + (for/list ([datum-lit (in-list datum-lits)]) + (check-id (den:datum-lit-internal datum-lit)) + datum-lit)]) + (apply append lits* datum-lits* litsets*))) + +;; parse-variant : stx boolean DeclEnv -> RHS +(define (parse-variant stx splicing? decls0) + (syntax-case stx (pattern) + [(pattern p . rest) + (let-values ([(rest pattern defs) + (parse-pattern+sides #'p #'rest + #:simplify? #f + #:splicing? splicing? + #:decls decls0 + #:context stx)]) + (disappeared! stx) + (unless (stx-null? rest) + (wrong-syntax (if (pair? rest) (car rest) rest) + "unexpected terms after pattern directives")) + (let* ([attrs (pattern-attrs pattern)] + [sattrs (iattrs->sattrs attrs)]) + (make variant stx sattrs pattern defs)))])) + +;; parse-EH-variant : Syntax DeclEnv Boolean +;; -> (Listof (list EllipsisHeadPattern Syntax/EH-Alternative)) +(define (parse-EH-variant stx decls allow-or? #:context [ctx (current-syntax-context)]) + (parse*-ellipsis-head-pattern stx decls allow-or? #:context ctx)) + +;; parse-pattern+sides : stx stx <options> -> (values stx Pattern (listof stx)) +;; Parses pattern, side clauses; desugars side clauses & merges with pattern +(define (parse-pattern+sides p-stx s-stx + #:splicing? splicing? + #:decls decls0 + #:context ctx + #:simplify? [simplify? #t]) + (let-values ([(rest decls defs sides) + (parse-pattern-directives s-stx + #:allow-declare? #t + #:decls decls0 + #:context ctx)]) + (let* ([pattern0 (parse-whole-pattern p-stx decls splicing? #:context ctx #:kind 'main)] + [pattern (combine-pattern+sides pattern0 sides splicing?)] + [pattern (if simplify? (simplify-pattern pattern) pattern)]) + (values rest pattern defs)))) + +;; parse-whole-pattern : stx DeclEnv boolean -> Pattern +;; kind is either 'main or 'with, indicates what kind of pattern declare affects +(define (parse-whole-pattern stx decls [splicing? #f] + #:kind kind + #:context [ctx (current-syntax-context)]) + (parameterize ((current-syntax-context ctx)) + (define pattern + (if splicing? + (parse-head-pattern stx decls) + (parse-single-pattern stx decls))) + (define pvars (map attr-name (pattern-attrs pattern))) + (define excess-domain (declenv-domain-difference decls pvars)) + (when (pair? excess-domain) + (wrong-syntax (car excess-domain) + (string-append + "identifier in #:declare clause does not appear in pattern" + (case kind + [(main) ""] ;; ";\n this #:declare clause affects only the main pattern"] + [(with) ";\n this #:declare clause affects only the preceding #:with pattern"])))) + pattern)) + +;; combine-pattern+sides : Pattern (listof SideClause) -> Pattern +(define (combine-pattern+sides pattern sides splicing?) + (check-pattern + (cond [splicing? (hpat:andu (cons pattern sides))] + [else (pat:andu (cons pattern sides))]))) + +;; gensym* : -> UninternedSymbol +;; Like gensym, but with deterministic name from compilation-local counter. +(define gensym*-counter 0) +(define (gensym*) + (set! gensym*-counter (add1 gensym*-counter)) + (string->uninterned-symbol (format "group~a" gensym*-counter))) + + +;; ============================================================ +;; Parsing patterns + +;; parse-single-pattern : stx DeclEnv -> SinglePattern +(define (parse-single-pattern stx decls) + (parse-*-pattern stx decls #f #f)) + +;; parse-head-pattern : stx DeclEnv -> HeadPattern +(define (parse-head-pattern stx decls) + (coerce-head-pattern (parse-*-pattern stx decls #t #f))) + +;; parse-action-pattern : Stx DeclEnv -> ActionPattern +(define (parse-action-pattern stx decls) + (define p (parse-*-pattern stx decls #f #t)) + (unless (action-pattern? p) + (wrong-syntax stx "expected action pattern")) + p) + +(define ((make-not-shadowed? decls) id) + ;; Returns #f if id is in literals/datum-literals list. + ;; Conventions to not shadow pattern-form bindings, under the + ;; theory that conventions only apply to things already determined + ;; to be pattern variables. + (not (declenv-lookup decls id))) +;; suitable as id=? argument to syntax-case* +(define ((make-not-shadowed-id=? decls) lit-id pat-id) + (and (free-identifier=? lit-id pat-id) + (not (declenv-lookup decls pat-id)))) + +;; parse-*-pattern : stx DeclEnv boolean boolean -> Pattern +(define (parse-*-pattern stx decls allow-head? allow-action?) + (define (recur stx) + (parse-*-pattern stx decls allow-head? allow-action?)) + (define (check-head! x) + (unless allow-head? + (wrong-syntax stx "head pattern not allowed here")) + x) + (define (check-action! x) + ;; Coerce to S-pattern IF only S-patterns allowed + (cond [allow-action? x] + [(not allow-head?) (action-pattern->single-pattern x)] + [else + (wrong-syntax stx "action pattern not allowed here")])) + (define not-shadowed? (make-not-shadowed? decls)) + (propagate-disappeared! stx) + (check-pattern + (syntax-case* stx (~var ~literal ~datum ~and ~or ~or* ~alt ~not ~rest ~describe + ~seq ~optional ~! ~bind ~fail ~parse ~do ~undo + ~post ~peek ~peek-not ~delimit-cut ~commit ~reflect + ~splicing-reflect) + (make-not-shadowed-id=? decls) + [id + (and (identifier? #'id) + (not-shadowed? #'id) + (pattern-expander? (syntax-local-value #'id (λ () #f)))) + (begin (disappeared! #'id) + (recur (expand-pattern (syntax-local-value #'id) stx)))] + [(id . rst) + (and (identifier? #'id) + (not-shadowed? #'id) + (pattern-expander? (syntax-local-value #'id (λ () #f)))) + (begin (disappeared! #'id) + (recur (expand-pattern (syntax-local-value #'id) stx)))] + [wildcard + (and (wildcard? #'wildcard) + (not-shadowed? #'wildcard)) + (begin (disappeared! stx) + (pat:any))] + [~! + (disappeared! stx) + (begin + (unless (cut-allowed?) + (wrong-syntax stx + "cut (~~!) not allowed within ~~not pattern")) + (check-action! + (action:cut)))] + [reserved + (and (reserved? #'reserved) + (not-shadowed? #'reserved)) + (wrong-syntax stx "pattern keyword not allowed here")] + [id + (identifier? #'id) + (parse-pat:id stx decls allow-head?)] + [datum + (atomic-datum-stx? #'datum) + (pat:datum (syntax->datum #'datum))] + [(~var . rest) + (disappeared! stx) + (parse-pat:var stx decls allow-head?)] + [(~datum . rest) + (disappeared! stx) + (syntax-case stx (~datum) + [(~datum d) + (pat:datum (syntax->datum #'d))] + [_ (wrong-syntax stx "bad ~~datum form")])] + [(~literal . rest) + (disappeared! stx) + (parse-pat:literal stx decls)] + [(~and . rest) + (disappeared! stx) + (parse-pat:and stx decls allow-head? allow-action?)] + [(~or . rest) + (disappeared! stx) + (parse-pat:or stx decls allow-head?)] + [(~or* . rest) + (disappeared! stx) + (parse-pat:or stx decls allow-head?)] + [(~alt . rest) + (wrong-syntax stx "ellipsis-head pattern allowed only before ellipsis")] + [(~not . rest) + (disappeared! stx) + (parse-pat:not stx decls)] + [(~rest . rest) + (disappeared! stx) + (parse-pat:rest stx decls)] + [(~describe . rest) + (disappeared! stx) + (parse-pat:describe stx decls allow-head?)] + [(~delimit-cut . rest) + (disappeared! stx) + (parse-pat:delimit stx decls allow-head?)] + [(~commit . rest) + (disappeared! stx) + (parse-pat:commit stx decls allow-head?)] + [(~reflect . rest) + (disappeared! stx) + (parse-pat:reflect stx decls #f)] + [(~seq . rest) + (disappeared! stx) + (check-head! + (parse-hpat:seq stx #'rest decls))] + [(~optional . rest) + (disappeared! stx) + (check-head! + (parse-hpat:optional stx decls))] + [(~splicing-reflect . rest) + (disappeared! stx) + (check-head! + (parse-pat:reflect stx decls #t))] + [(~bind . rest) + (disappeared! stx) + (check-action! + (parse-pat:bind stx decls))] + [(~fail . rest) + (disappeared! stx) + (check-action! + (parse-pat:fail stx decls))] + [(~post . rest) + (disappeared! stx) + (parse-pat:post stx decls allow-head? allow-action?)] + [(~peek . rest) + (disappeared! stx) + (check-head! + (parse-pat:peek stx decls))] + [(~peek-not . rest) + (disappeared! stx) + (check-head! + (parse-pat:peek-not stx decls))] + [(~parse . rest) + (disappeared! stx) + (check-action! + (parse-pat:parse stx decls))] + [(~do . rest) + (disappeared! stx) + (check-action! + (parse-pat:do stx decls))] + [(~undo . rest) + (disappeared! stx) + (check-action! + (parse-pat:undo stx decls))] + [(head dots . tail) + (and (dots? #'dots) (not-shadowed? #'dots)) + (begin (disappeared! #'dots) + (parse-pat:dots stx #'head #'tail decls))] + [(head plus-dots . tail) + (and (plus-dots? #'plus-dots) (not-shadowed? #'plus-dots)) + (begin (disappeared! #'plus-dots) + (parse-pat:plus-dots stx #'head #'tail decls))] + [(head . tail) + (let ([headp (parse-*-pattern #'head decls #t #t)] + [tailp (parse-single-pattern #'tail decls)]) + (cond [(action-pattern? headp) + (pat:action headp tailp)] + [else (pat:head (coerce-head-pattern headp) tailp)]))] + [#(a ...) + (let ([lp (parse-single-pattern (syntax/loc stx (a ...)) decls)]) + (pat:vector lp))] + [b + (box? (syntax-e #'b)) + (let ([bp (parse-single-pattern (unbox (syntax-e #'b)) decls)]) + (pat:box bp))] + [s + (and (struct? (syntax-e #'s)) (prefab-struct-key (syntax-e #'s))) + (let* ([s (syntax-e #'s)] + [key (prefab-struct-key s)] + [contents (struct->list s)]) + (let ([lp (parse-single-pattern (datum->syntax #f contents #'s) decls)]) + (pat:pstruct key lp)))]))) + +;; expand-pattern : pattern-expander Syntax -> Syntax +(define (expand-pattern pe stx) + (let ([proc (pattern-expander-proc pe)]) + (local-apply-transformer proc stx 'expression))) + +;; parse-ellipsis-head-pattern : stx DeclEnv -> (listof EllipsisHeadPattern) +(define (parse-ellipsis-head-pattern stx decls) + (for/list ([ehpat+hstx (in-list (parse*-ellipsis-head-pattern stx decls #t))]) + (car ehpat+hstx))) + +;; parse*-ellipsis-head-pattern : stx DeclEnv bool +;; -> (listof (list EllipsisHeadPattern stx/eh-alternative)) +(define (parse*-ellipsis-head-pattern stx decls allow-or? + #:context [ctx (current-syntax-context)]) + (define (recur stx) (parse*-ellipsis-head-pattern stx decls allow-or? #:context ctx)) + (define (recur-cdr-list stx) + (unless (stx-list? stx) (wrong-syntax stx "expected sequence of patterns")) + (apply append (map recur (cdr (stx->list stx))))) + (define not-shadowed? (make-not-shadowed? decls)) + (propagate-disappeared! stx) + (syntax-case* stx (~eh-var ~or ~alt ~between ~optional ~once) + (make-not-shadowed-id=? decls) + [id + (and (identifier? #'id) + (not-shadowed? #'id) + (pattern-expander? (syntax-local-value #'id (lambda () #f)))) + (begin (disappeared! #'id) + (recur (expand-pattern (syntax-local-value #'id) stx)))] + [(id . rst) + (and (identifier? #'id) + (not-shadowed? #'id) + (pattern-expander? (syntax-local-value #'id (lambda () #f)))) + (begin (disappeared! #'id) + (recur (expand-pattern (syntax-local-value #'id) stx)))] + [(~eh-var name eh-alt-set-id) + (disappeared! stx) + (let () + (define prefix (name->prefix #'name ".")) + (define eh-alt-set (get-eh-alternative-set #'eh-alt-set-id)) + (for/list ([alt (in-list (eh-alternative-set-alts eh-alt-set))]) + (let* ([iattrs (id-pattern-attrs (eh-alternative-attrs alt) prefix)] + [attr-count (length iattrs)]) + (list (create-ehpat + (hpat:var/p #f (eh-alternative-parser alt) no-arguments iattrs #f + (scopts attr-count #f #t #f)) + (eh-alternative-repc alt) + #f) + (replace-eh-alternative-attrs + alt (iattrs->sattrs iattrs))))))] + [(~or . _) + (disappeared! stx) + (recur-cdr-list stx)] + [(~alt . _) + (disappeared! stx) + (recur-cdr-list stx)] + [(~optional . _) + (disappeared! stx) + (list (parse*-ehpat/optional stx decls))] + [(~once . _) + (disappeared! stx) + (list (parse*-ehpat/once stx decls))] + [(~between . _) + (disappeared! stx) + (list (parse*-ehpat/bounds stx decls))] + [_ + (let ([head (parse-head-pattern stx decls)]) + (list (list (create-ehpat head #f stx) stx)))])) + +(define (replace-eh-alternative-attrs alt sattrs) + (match alt + [(eh-alternative repc _attrs parser) + (eh-alternative repc sattrs parser)])) + +;; ---------------------------------------- +;; Identifiers, ~var, and stxclasses + +(define (check-no-delimit-cut-in-not id delimit-cut?) + (unless (or delimit-cut? (cut-allowed?)) + (wrong-syntax id + (string-append "syntax class with #:no-delimit-cut option " + "not allowed within ~~not pattern")))) + +(define (parse-pat:id id decls allow-head?) + (cond [(declenv-lookup decls id) + => (lambda (entry) (parse-pat:id/entry id allow-head? entry))] + [(not (safe-name? id)) + (wrong-syntax id "expected identifier not starting with ~~ character")] + [(and (stxclass-colon-notation?) (split-id id)) + => (match-lambda + [(cons name suffix) + (declenv-check-unbound decls name (syntax-e suffix) #:blame-declare? #t) + (define entry (declenv-lookup decls suffix)) + (cond [(or (den:lit? entry) (den:datum-lit? entry)) + (pat:andu (list (pat:svar name) (parse-pat:id/entry id allow-head? entry)))] + [else (parse-stxclass-use id allow-head? name suffix no-arguments "." #f)])])] + [(declenv-apply-conventions decls id) + => (lambda (entry) (parse-pat:id/entry id allow-head? entry))] + [else (pat:svar id)])) + +(define (split-id id0) + (cond [(regexp-match #rx"^([^:]*):(.+)$" (symbol->string (syntax-e id0))) + => (lambda (m) + (define src (syntax-source id0)) + (define ln (syntax-line id0)) + (define col (syntax-column id0)) + (define pos (syntax-position id0)) + (define span (syntax-span id0)) + (define id-str (cadr m)) + (define id-len (string-length id-str)) + (define suffix-str (caddr m)) + (define suffix-len (string-length suffix-str)) + (define id + (datum->syntax id0 (string->symbol id-str) + (list src ln col pos id-len) + id0)) + (define suffix + (datum->syntax id0 (string->symbol suffix-str) + (list src ln (and col (+ col id-len 1)) (and pos (+ pos id-len 1)) suffix-len) + id0)) + (cons id suffix))] + [else #f])) + +;; parse-pat:id/entry : Identifier .... DeclEntry -> SinglePattern +;; Handle when meaning of identifier pattern is given by declenv entry. +(define (parse-pat:id/entry id allow-head? entry) + (match entry + [(den:lit internal literal input-phase lit-phase) + (pat:literal literal input-phase lit-phase)] + [(den:datum-lit internal sym) + (pat:datum sym)] + [(den:magic-class name scname argu role) + (parse-stxclass-use scname allow-head? id scname argu "." role)] + [(den:class _n _c _a) + (error 'parse-pat:id + "(internal error) decls had leftover stxclass entry: ~s" + entry)] + [(den:delayed parser scname) + (parse-stxclass-use id allow-head? id scname no-arguments "." #f parser)])) + +(define (parse-pat:var stx decls allow-head?) + (define name0 + (syntax-case stx () + [(_ name . _) + (unless (identifier? #'name) + (wrong-syntax #'name "expected identifier")) + #'name] + [_ + (wrong-syntax stx "bad ~~var form")])) + (define-values (scname sc+args-stx argu pfx role) + (syntax-case stx () + [(_ _name) + (values #f #f null #f #f)] + [(_ _name sc/sc+args . rest) + (let-values ([(sc argu) + (let ([p (check-stxclass-application #'sc/sc+args stx)]) + (values (car p) (cdr p)))]) + (define chunks + (parse-keyword-options/eol #'rest var-pattern-directive-table + #:no-duplicates? #t + #:context stx)) + (define sep + (options-select-value chunks '#:attr-name-separator #:default #f)) + (define role (options-select-value chunks '#:role #:default #'#f)) + (values sc #'sc/sc+args argu (if sep (syntax-e sep) ".") role))] + [_ + (wrong-syntax stx "bad ~~var form")])) + (cond [(and (epsilon? name0) (not scname)) + (wrong-syntax name0 "illegal pattern variable name")] + [(and (wildcard? name0) (not scname)) + (pat:any)] + [scname + (parse-stxclass-use stx allow-head? name0 scname argu pfx role)] + [else ;; Just proper name + (pat:svar name0)])) + +;; ---- + +(define (parse-stxclass-use stx allow-head? varname scname argu pfx role [parser* #f]) + (define config (stxclass-lookup-config)) + (cond [(and (memq config '(yes try)) (get-stxclass scname (eq? config 'try))) + => (lambda (sc) + (unless parser* + (check-stxclass-arity sc stx (length (arguments-pargs argu)) (arguments-kws argu))) + (parse-stxclass-use* stx allow-head? varname sc argu pfx role parser*))] + [else + (define bind (name->bind varname)) + (pat:fixup stx bind varname scname argu pfx role parser*)])) + +;; ---- + +(define (parse-stxclass-use* stx allow-head? name sc argu pfx role parser*) + ;; if parser* not #f, overrides sc parser + (check-no-delimit-cut-in-not stx (scopts-delimit-cut? (stxclass-opts sc))) + (define bind (name->bind name)) + (define prefix (name->prefix name pfx)) + (define parser (or parser* (stxclass-parser sc))) + (define nested-attrs (id-pattern-attrs (stxclass-attrs sc) prefix)) + (define opts (stxclass-opts sc)) + (cond [(and (stxclass/s? sc) (stxclass-inline sc) (equal? argu no-arguments)) + (pat:integrated bind (stxclass-inline sc) (scopts-desc opts) role)] + [(stxclass/s? sc) + (pat:var/p bind parser argu nested-attrs role opts)] + [(stxclass/h? sc) + (unless allow-head? + (wrong-syntax stx "splicing syntax class not allowed here")) + (hpat:var/p bind parser argu nested-attrs role opts)])) + +(define (name->prefix id pfx) + (cond [(wildcard? id) #f] + [(epsilon? id) id] + [else (format-id id "~a~a" (syntax-e id) pfx #:source id)])) + +(define (name->bind id) + (cond [(wildcard? id) #f] + [(epsilon? id) #f] + [else id])) + +;; id-pattern-attrs : (listof SAttr)IdPrefix -> (listof IAttr) +(define (id-pattern-attrs sattrs prefix) + (if prefix + (for/list ([a (in-list sattrs)]) + (prefix-attr a prefix)) + null)) + +;; prefix-attr : SAttr identifier -> IAttr +(define (prefix-attr a prefix) + (make attr (prefix-attr-name prefix (attr-name a)) + (attr-depth a) + (attr-syntax? a))) + +;; prefix-attr-name : id symbol -> id +(define (prefix-attr-name prefix name) + (orig (format-id prefix "~a~a" (syntax-e prefix) name #:source prefix))) + +(define (orig stx) + (syntax-property stx 'original-for-check-syntax #t)) + +;; ---------------------------------------- +;; Other pattern forms + +(define (parse-pat:reflect stx decls splicing?) + (syntax-case stx () + [(_ name (obj arg ...) . maybe-signature) + (let () + (unless (identifier? #'var) + (raise-syntax-error #f "expected identifier" stx #'name)) + (define attr-decls + (syntax-case #'maybe-signature () + [(#:attributes attr-decls) + (check-attr-arity-list #'attr-decls stx)] + [() null] + [_ (raise-syntax-error #f "bad syntax" stx)])) + (define prefix (name->prefix #'name ".")) + (define bind (name->bind #'name)) + (define ctor (if splicing? hpat:reflect pat:reflect)) + (ctor #'obj (parse-argu (syntax->list #'(arg ...))) attr-decls bind + (id-pattern-attrs attr-decls prefix)))])) + +(define (parse-pat:literal stx decls) + (syntax-case stx () + [(_ lit . more) + (unless (identifier? #'lit) + (wrong-syntax #'lit "expected identifier")) + (let* ([chunks (parse-keyword-options/eol #'more phase-directive-table + #:no-duplicates? #t + #:context stx)] + [phase (options-select-value chunks '#:phase #:default #f)] + [phase (if phase (txlift phase) #'(syntax-local-phase-level))]) + (pat:literal #'lit phase phase))] + [_ + (wrong-syntax stx "bad ~~literal pattern")])) + +(define (parse-pat:describe stx decls allow-head?) + (syntax-case stx () + [(_ . rest) + (let-values ([(chunks rest) + (parse-keyword-options #'rest describe-option-table + #:no-duplicates? #t + #:context stx)]) + (define transparent? (not (assq '#:opaque chunks))) + (define role (options-select-value chunks '#:role #:default #'#f)) + (syntax-case rest () + [(description pattern) + (let ([p (parse-*-pattern #'pattern decls allow-head? #f)]) + (if (head-pattern? p) + (hpat:describe p #'description transparent? role) + (pat:describe p #'description transparent? role)))]))])) + +(define (parse-pat:delimit stx decls allow-head?) + (syntax-case stx () + [(_ pattern) + (let ([p (parameterize ((cut-allowed? #t)) + (parse-*-pattern #'pattern decls allow-head? #f))]) + (if (head-pattern? p) + (hpat:delimit p) + (pat:delimit p)))])) + +(define (parse-pat:commit stx decls allow-head?) + (syntax-case stx () + [(_ pattern) + (let ([p (parameterize ((cut-allowed? #t)) + (parse-*-pattern #'pattern decls allow-head? #f))]) + (if (head-pattern? p) + (hpat:commit p) + (pat:commit p)))])) + +(define (parse-pat:and stx decls allow-head? allow-action?) + ;; allow-action? = allowed to *return* pure action pattern; + ;; all ~and patterns are allowed to *contain* action patterns + (define patterns (parse-cdr-patterns stx decls allow-head? #t)) + (cond [(andmap action-pattern? patterns) + (cond [allow-action? + (action:and patterns)] + [allow-head? + (wrong-syntax stx "expected at least one head or single-term pattern")] + [else + (wrong-syntax stx "expected at least one single-term pattern")])] + [(memq (stxclass-lookup-config) '(no try)) + (pat:and/fixup stx patterns)] + [else (parse-pat:and/k stx patterns)])) + +(define (parse-pat:and/k stx patterns) + ;; PRE: patterns not all action patterns + (cond [(ormap head-pattern? patterns) + ;; Check to make sure *all* are head patterns (and action patterns) + (for ([pattern (in-list patterns)] + [pattern-stx (in-list (stx->list (stx-cdr stx)))]) + (unless (or (action-pattern? pattern) (head-pattern? pattern)) + (wrong-syntax pattern-stx "single-term pattern not allowed after head pattern"))) + (hpat:andu patterns)] + [else (pat:andu patterns)])) + +(define (split-prefix xs pred) + (let loop ([xs xs] [rprefix null]) + (cond [(and (pair? xs) (pred (car xs))) + (loop (cdr xs) (cons (car xs) rprefix))] + [else + (values (reverse rprefix) xs)]))) + +(define (add-actions actions p) + (if (head-pattern? p) + (for/fold ([p p]) ([action (in-list (reverse actions))]) + (hpat:action action p)) + (for/fold ([p p]) ([action (in-list (reverse actions))]) + (pat:action action p)))) + +(define (parse-pat:or stx decls allow-head?) + (define patterns (parse-cdr-patterns stx decls allow-head? #f)) + (cond [(null? (cdr patterns)) + (car patterns)] + [else + (cond [(ormap head-pattern? patterns) + (create-hpat:or (map coerce-head-pattern patterns))] + [else + (create-pat:or patterns)])])) + +(define (parse-pat:not stx decls) + (syntax-case stx () + [(_ pattern) + (let ([p (parameterize ((cut-allowed? #f)) + (parse-single-pattern #'pattern decls))]) + (pat:not p))] + [_ + (wrong-syntax stx "expected a single subpattern")])) + +(define (parse-hpat:seq stx list-stx decls) + (define pattern (parse-single-pattern list-stx decls)) + (unless (proper-list-pattern? pattern) + (wrong-syntax stx "expected proper list pattern")) + (hpat:seq pattern)) + +(define (parse-cdr-patterns stx decls allow-head? allow-action?) + (unless (stx-list? stx) + (wrong-syntax stx "expected sequence of patterns")) + (let ([result + (for/list ([sub (in-list (cdr (stx->list stx)))]) + (parse-*-pattern sub decls allow-head? allow-action?))]) + (when (null? result) + (wrong-syntax stx "expected at least one pattern")) + result)) + +(define (parse-pat:dots stx head tail decls) + (define headps (parse-ellipsis-head-pattern head decls)) + (define tailp (parse-single-pattern tail decls)) + (unless (pair? headps) + (wrong-syntax head "expected at least one pattern")) + (pat:dots headps tailp)) + +(define (parse-pat:plus-dots stx head tail decls) + (define headp (parse-head-pattern head decls)) + (define tailp (parse-single-pattern tail decls)) + (define head/rep (create-ehpat headp (make-rep:bounds 1 +inf.0 #f #f #f) head)) + (pat:dots (list head/rep) tailp)) + +(define (parse-pat:bind stx decls) + (syntax-case stx () + [(_ clause ...) + (let ([clauses (check-bind-clause-list #'(clause ...) stx)]) + (action:and clauses))])) + +(define (parse-pat:fail stx decls) + (syntax-case stx () + [(_ . rest) + (let-values ([(chunks rest) + (parse-keyword-options #'rest fail-directive-table + #:context stx + #:incompatible '((#:when #:unless)) + #:no-duplicates? #t)]) + (let ([condition + (cond [(options-select-value chunks '#:when #:default #f) + => values] + [(options-select-value chunks '#:unless #:default #f) + => (lambda (expr) #`(not #,expr))] + [else #'#t])]) + (syntax-case rest () + [(message) + (action:fail condition #'message)] + [() + (action:fail condition #''#f)] + [_ + (wrong-syntax stx "bad ~~fail pattern")])))])) + +(define (parse-pat:post stx decls allow-head? allow-action?) + (syntax-case stx () + [(_ pattern) + (let ([p (parse-*-pattern #'pattern decls allow-head? allow-action?)]) + (cond [(action-pattern? p) + (cond [allow-action? (action:post p)] + [(not allow-head?) (pat:post (action-pattern->single-pattern p))] + [else (wrong-syntax stx "action pattern not allowed here")])] + [(head-pattern? p) + (cond [allow-head? (hpat:post p)] + [else (wrong-syntax stx "head pattern not allowed here")])] + [else (pat:post p)]))])) + +(define (parse-pat:peek stx decls) + (syntax-case stx () + [(_ pattern) + (let ([p (parse-head-pattern #'pattern decls)]) + (hpat:peek p))])) + +(define (parse-pat:peek-not stx decls) + (syntax-case stx () + [(_ pattern) + (let ([p (parse-head-pattern #'pattern decls)]) + (hpat:peek-not p))])) + +(define (parse-pat:parse stx decls) + (syntax-case stx () + [(_ pattern expr) + (let ([p (parse-single-pattern #'pattern decls)]) + (action:parse p #'expr))] + [_ + (wrong-syntax stx "bad ~~parse pattern")])) + +(define (parse-pat:do stx decls) + (syntax-case stx () + [(_ stmt ...) + (action:do (syntax->list #'(stmt ...)))] + [_ + (wrong-syntax stx "bad ~~do pattern")])) + +(define (parse-pat:undo stx decls) + (syntax-case stx () + [(_ stmt ...) + (action:undo (syntax->list #'(stmt ...)))] + [_ + (wrong-syntax stx "bad ~~undo pattern")])) + +(define (parse-pat:rest stx decls) + (syntax-case stx () + [(_ pattern) + (parse-single-pattern #'pattern decls)])) + +(define (parse-hpat:optional stx decls) + (define-values (head-stx head iattrs _name _tmm defaults) + (parse*-optional-pattern stx decls h-optional-directive-table)) + (create-hpat:or + (list head + (hpat:action (action:and defaults) + (hpat:seq (pat:datum '())))))) + +;; parse*-optional-pattern : stx DeclEnv table +;; -> (values Syntax HeadPattern IAttrs Stx Stx (Listof BindClause)) +(define (parse*-optional-pattern stx decls optional-directive-table) + (syntax-case stx () + [(_ p . options) + (let* ([head (parse-head-pattern #'p decls)] + [chunks + (parse-keyword-options/eol #'options optional-directive-table + #:no-duplicates? #t + #:context stx)] + [too-many-msg + (options-select-value chunks '#:too-many #:default #'#f)] + [name + (options-select-value chunks '#:name #:default #'#f)] + [defaults + (options-select-value chunks '#:defaults #:default '())] + [pattern-iattrs (pattern-attrs head)] + [defaults-iattrs + (append-iattrs (map pattern-attrs defaults))] + [all-iattrs + (union-iattrs (list pattern-iattrs defaults-iattrs))]) + (when (eq? (stxclass-lookup-config) 'yes) + ;; Only check that attrs in defaults clause agree with attrs + ;; in pattern when attrs in pattern are known to be complete. + (check-iattrs-subset defaults-iattrs pattern-iattrs stx)) + (values #'p head all-iattrs name too-many-msg defaults))])) + +;; -- EH patterns +;; Only parse the rep-constraint part; don't parse the head pattern within. +;; (To support eh-alternative-sets.) + +;; parse*-ehpat/optional : stx DeclEnv -> (list EllipsisHeadPattern stx) +(define (parse*-ehpat/optional stx decls) + (define-values (head-stx head iattrs name too-many-msg defaults) + (parse*-optional-pattern stx decls eh-optional-directive-table)) + (list (create-ehpat head (make rep:optional name too-many-msg defaults) head-stx) + head-stx)) + +;; parse*-ehpat/once : stx DeclEnv -> (list EllipsisHeadPattern stx) +(define (parse*-ehpat/once stx decls) + (syntax-case stx () + [(_ p . options) + (let* ([head (parse-head-pattern #'p decls)] + [chunks + (parse-keyword-options/eol #'options + (list (list '#:too-few check-expression) + (list '#:too-many check-expression) + (list '#:name check-expression)) + #:context stx)] + [too-few-msg + (options-select-value chunks '#:too-few #:default #'#f)] + [too-many-msg + (options-select-value chunks '#:too-many #:default #'#f)] + [name + (options-select-value chunks '#:name #:default #'#f)]) + (list (create-ehpat head (make rep:once name too-few-msg too-many-msg) #'p) + #'p))])) + +;; parse*-ehpat/bounds : stx DeclEnv -> (list EllipsisHeadPattern stx) +(define (parse*-ehpat/bounds stx decls) + (syntax-case stx () + [(_ p min max . options) + (let () + (define head (parse-head-pattern #'p decls)) + (define minN (syntax-e #'min)) + (define maxN (syntax-e #'max)) + (unless (exact-nonnegative-integer? minN) + (wrong-syntax #'min + "expected exact nonnegative integer")) + (unless (or (exact-nonnegative-integer? maxN) (equal? maxN +inf.0)) + (wrong-syntax #'max + "expected exact nonnegative integer or +inf.0")) + (when (> minN maxN) + (wrong-syntax stx "minimum larger than maximum repetition constraint")) + (let* ([chunks (parse-keyword-options/eol + #'options + (list (list '#:too-few check-expression) + (list '#:too-many check-expression) + (list '#:name check-expression)) + #:context stx)] + [too-few-msg + (options-select-value chunks '#:too-few #:default #'#f)] + [too-many-msg + (options-select-value chunks '#:too-many #:default #'#f)] + [name + (options-select-value chunks '#:name #:default #'#f)]) + (list (create-ehpat head + (make rep:bounds #'min #'max + name too-few-msg too-many-msg) + #'p) + #'p)))])) + + +;; ============================================================ +;; Fixup pass (also does simplify-pattern) + +(define (fixup-rhs the-rhs head? expected-attrs) + (match the-rhs + [(rhs attrs tr? desc vs defs commit? delimit-cut?) + (define vs* (for/list ([v (in-list vs)]) (fixup-variant v head? expected-attrs))) + (rhs attrs tr? desc vs* defs commit? delimit-cut?)])) + +(define (fixup-variant v head? expected-attrs) + (match v + [(variant stx sattrs p defs) + (parameterize ((current-syntax-context stx)) + (define p1 + (parameterize ((stxclass-lookup-config 'yes)) + (fixup-pattern p head?))) + ;; (eprintf "~v\n===>\n~v\n\n" p p1) + (unless (if head? (wf-H? p1) (wf-S? p1)) + (error 'fixup-variant "result is not well-formed")) + (define p* (simplify-pattern p1)) + ;; (eprintf "=2=>\n~v\n\n" p*) + ;; Called just for error-reporting + (reorder-iattrs expected-attrs (pattern-attrs p*)) + (variant stx sattrs p* defs))])) + +(define (fixup-pattern p0 head?) + (define (S p) (fixup p #f)) + (define (S* p) (fixup p #t)) + (define (A/S p) (if (action-pattern? p) (A p) (S p))) + (define (A/H p) (if (action-pattern? p) (A p) (H p))) + + (define (A p) + (match p + ;; [(action:cut) + ;; (action:cut)] + ;; [(action:fail when msg) + ;; (action:fail when msg)] + ;; [(action:bind attr expr) + ;; (action:bind attr expr)] + [(action:and ps) + (action:and (map A ps))] + [(action:parse sp expr) + (action:parse (S sp) expr)] + ;; [(action:do stmts) + ;; (action:do stmts)] + ;; [(action:undo stmts) + ;; (action:undo stmts)] + [(action:ord sp group index) + (create-ord-pattern (A sp) group index)] + [(action:post sp) + (create-post-pattern (A sp))] + ;; ---- + ;; Default: no sub-patterns, just return + [p p])) + (define (EH p) + (match p + [(ehpat iattrs hp repc check-null?) + (create-ehpat (H hp) repc #f)])) + + (define (fixup p allow-head?) + (define (I p) (fixup p allow-head?)) + (match p + [(pat:fixup stx bind varname scname argu pfx role parser*) + (parse-stxclass-use stx allow-head? varname scname argu pfx role parser*)] + ;; ---- + ;; [(pat:any) + ;; (pat:any)] + ;; [(pat:svar name) + ;; (pat:svar name)] + ;; [(pat:var/p name parser argu nested-attrs role opts) + ;; (pat:var/p name parser argu nested-attrs role opts)] + ;; [(pat:integrated name predicate desc role) + ;; (pat:integrated name predicate desc role)] + ;; [(pat:reflect obj argu attr-decls name nested-attrs) + ;; (pat:reflect obj argu attr-decls name nested-attrs)] + ;; [(pat:datum d) + ;; (pat:datum d)] + ;; [(pat:literal id input-phase lit-phase) + ;; (pat:literal id input-phase lit-phase)] + [(pat:vector sp) + (pat:vector (S sp))] + [(pat:box sp) + (pat:box (S sp))] + [(pat:pstruct key sp) + (pat:pstruct key (S sp))] + [(pat:not sp) + (parameterize ((cut-allowed? #f)) + (pat:not (S sp)))] + [(pat:dots headps tailp) + (pat:dots (map EH headps) (S tailp))] + [(pat:head headp tailp) + (pat:head (H headp) (S tailp))] + ;; --- The following patterns may change if a subpattern switches to head pattern ---- + [(pat:pair headp tailp) (error 'fixup-pattern "internal error: pat:pair in stage 0")] + [(pat:action a sp) + (let ([a (A a)] [sp (I sp)]) + (if (head-pattern? sp) (hpat:action a sp) (pat:action a sp)))] + [(pat:describe sp desc tr? role) + (let ([sp (I sp)]) + (if (head-pattern? sp) (hpat:describe sp desc tr? role) (pat:describe sp desc tr? role)))] + [(pat:andu ps) + (let ([ps (map A/S ps)]) + (pat:andu ps))] + [(pat:and/fixup stx ps) + (let ([ps (for/list ([p (in-list ps)]) + (cond [(action-pattern? p) (A p)] + [(head-pattern? p) (H p)] + [else (I p)]))]) + (parse-pat:and/k stx ps))] + [(pat:or _ ps _) + (let ([ps (map I ps)]) + (if (ormap head-pattern? ps) (create-hpat:or ps) (create-pat:or ps)))] + [(pat:delimit sp) + (let ([sp (parameterize ((cut-allowed? #t)) (I sp))]) + (if (head-pattern? sp) (hpat:delimit sp) (pat:delimit sp)))] + [(pat:commit sp) + (let ([sp (parameterize ((cut-allowed? #t)) (I sp))]) + (if (head-pattern? sp) (hpat:commit sp) (pat:commit sp)))] + [(pat:ord sp group index) + (create-ord-pattern (I sp) group index)] + [(pat:post sp) + (create-post-pattern (I sp))] + ;; ---- + ;; Default: no sub-patterns, just return + [p p])) + + (define (H p) + (match p + [(hpat:single sp) + (let ([sp (fixup sp #t)]) + (if (head-pattern? sp) sp (hpat:single sp)))] + ;; [(hpat:var/p name parser argu nested-attrs role scopts) + ;; (hpat:var/p name parser argu nested-attrs role scopts)] + ;; [(hpat:reflect obj argu attr-decls name nested-attrs) + ;; (hpat:reflect obj argu attr-decls name nested-attrs)] + [(hpat:seq lp) + (hpat:seq (S lp))] + [(hpat:action a hp) + (hpat:action (A a) (H hp))] + [(hpat:describe hp desc tr? role) + (hpat:describe (H hp) desc tr? role)] + [(hpat:andu ps) + (let ([ps (map A/H ps)]) + (hpat:andu ps))] + [(hpat:or _ ps _) + (create-hpat:or (map H ps))] + [(hpat:delimit hp) + (parameterize ((cut-allowed? #t)) + (hpat:delimit (H hp)))] + [(hpat:commit hp) + (parameterize ((cut-allowed? #t)) + (hpat:commit (H hp)))] + [(hpat:ord hp group index) + (create-ord-pattern (H hp) group index)] + [(hpat:post hp) + (create-post-pattern (H hp))] + [(hpat:peek hp) + (hpat:peek (H hp))] + [(hpat:peek-not hp) + (hpat:peek-not (H hp))] + [(? pattern? sp) + (S* sp)] + ;; ---- + ;; Default: no sub-patterns, just return + [p p])) + + (if head? (H p0) (S p0))) + + +;; ============================================================ +;; Simplify pattern + +;;(begin (require racket/pretty) (pretty-print-columns 160)) + +;; simplify-pattern : *Pattern -> *Pattern +(define (simplify-pattern p0) + ;;(eprintf "-- simplify --\n") + ;;(eprintf "~a\n" (pretty-format p0)) + (define p1 (simplify:specialize-pairs p0)) + ;; (eprintf "=1=>\n~a\n" (pretty-format p1)) + (define p2 (simplify:normalize-and p1)) + ;;(eprintf "=2=>\n~a\n" (pretty-format p2)) + (define p3 (simplify:order-and p2)) + ;;(eprintf "=3=>\n~a\n" (pretty-format p3)) + (define p4 (simplify:add-seq-end p3)) + ;;(eprintf "=4=>\n~a\n" (pretty-format p4)) + p4) + +;; ---------------------------------------- +;; Add pair patterns + +(define (simplify:specialize-pairs p) + (define (for-pattern p) + (match p + [(pat:head (hpat:single headp) tailp) + (pat:pair headp tailp)] + [(pat:head (hpat:seq lp) tailp) + (list-pattern-replace-end lp tailp)] + [_ p])) + (pattern-transform p for-pattern)) + +;; list-pattern-replace-end : ListPattern {L,S}Pattern -> {L,S}Pattern +(define (list-pattern-replace-end lp endp) + (let loop ([lp lp]) + (match lp + [(pat:datum '()) endp] + [(pat:seq-end) endp] + [(pat:action ap sp) (pat:action ap (loop sp))] + [(pat:head hp tp) (pat:head hp (loop tp))] + [(pat:dots hs tp) (pat:dots hs (loop tp))] + [(pat:ord sp group index) + ;; This is awkward, but it is needed to pop the ORD progress frame on success. + (define sp* (list-pattern-replace-end sp (pat:seq-end))) + (pat:head (hpat:ord (hpat:seq sp*) group index) endp)] + [(pat:pair hp tp) (pat:pair hp (loop tp))]))) + +;; ---------------------------------------- +;; Normalize *:andu patterns, drop useless actions + +(define (simplify:normalize-and p) + (define (pattern->list p) + (match p + [(pat:any) null] + [(pat:action ap sp) (append (pattern->list ap) (pattern->list sp))] + [(pat:andu ps) (apply append (map pattern->list ps))] + [(hpat:action ap hp) (append (pattern->list ap) (pattern->list hp))] + [(hpat:andu ps) (apply append (map pattern->list ps))] + [(action:and as) (apply append (map pattern->list as))] + [(action:do '()) null] + [(action:undo '()) null] + [_ (list p)])) + (define (for-pattern p) + (match p + [(pat:action ap sp) + (pat:andu (append (pattern->list ap) (pattern->list sp)))] + [(pat:andu ps) + (pat:andu (apply append (map pattern->list ps)))] + [(hpat:action ap hp) + (hpat:andu (append (pattern->list ap) (pattern->list hp)))] + [(hpat:andu ps) + (hpat:andu (apply append (map pattern->list ps)))] + [(action:post ap) + (match (pattern->list ap) + ['() (action:and '())] + [(list ap*) (action:post ap*)] + [as* (action:post (action:and as*))])] + [_ p])) + (pattern-transform p for-pattern)) + +;; ---------------------------------------- +;; Add *:ord and translate back to *:and, *:action + +(define (simplify:order-and p) + (define (A->S p) (if (action-pattern? p) (pat:action p (pat:any)) p)) + (define (for-pattern p) + (match p + [(pat:andu ps0) + (define ord-ps (ord-and-patterns ps0 (gensym*))) + (define-values (as ps) (split-pred action-pattern? ord-ps)) + (define sp* (list->single-pattern (map A->S ps))) + (add-action-patterns as sp*)] + [(hpat:andu ps0) + (define ord-ps (ord-and-patterns ps0 (gensym*))) + (define-values (as ps) (split-pred action-pattern? ord-ps)) + (match ps + ['() (error 'simplify:order-ands "internal error: no head pattern")] + [(list hp) (add-action-patterns as hp)] + [(cons hp1 hps) + (define sp* (list->single-pattern (map action/head-pattern->list-pattern hps))) + (define hp* (hpat:and hp1 sp*)) + (add-action-patterns as hp*)])] + [_ p])) + (pattern-transform p for-pattern)) + +;; add-action-patterns : (Listof ActionPattern) *Pattern -> *Pattern +(define (add-action-patterns as p) + (if (pair? as) + (let ([ap (list->action-pattern as)]) + (cond [(single-pattern? p) (pat:action ap p)] + [(head-pattern? p) (hpat:action ap p)])) + p)) + +;; list->action-pattern : (Listof ActionPattern) -> ActionPattern +(define (list->action-pattern as) + (match as + [(list ap) ap] + [_ (action:and as)])) + +;; list->single-pattern : (Listof SinglePattern) -> SinglePattern +(define (list->single-pattern ps) + (match ps + ['() (pat:any)] + [(list p) p] + [_ (pat:and ps)])) + +(define (split-pred pred? xs) + (let loop ([xs xs] [acc null]) + (if (and (pair? xs) (pred? (car xs))) + (loop (cdr xs) (cons (car xs) acc)) + (values (reverse acc) xs)))) + +;; ---------------------------------------- +;; Add pat:seq-end to end of list-patterns in seq + +(define (simplify:add-seq-end p) + (define (for-pattern p) + (match p + [(hpat:seq lp) + (hpat:seq (list-pattern-replace-end lp (pat:seq-end)))] + [_ p])) + (pattern-transform p for-pattern)) + +;; ============================================================ +;; Parsing pattern directives + +;; parse-pattern-directives : stxs(PatternDirective) <kw-args> +;; -> stx DeclEnv (listof stx) (listof SideClause) +(define (parse-pattern-directives stx + #:allow-declare? allow-declare? + #:decls decls + #:context ctx) + (parameterize ((current-syntax-context ctx)) + (define-values (chunks rest) + (parse-keyword-options stx pattern-directive-table #:context ctx)) + (define-values (decls2 chunks2) + (if allow-declare? + (grab-decls chunks decls) + (values decls chunks))) + (define sides + ;; NOTE: use *original* decls + ;; because decls2 has #:declares for *above* pattern + (parse-pattern-sides chunks2 decls)) + (define-values (decls3 defs) + (decls-create-defs decls2)) + (values rest decls3 defs sides))) + +;; parse-pattern-sides : (listof chunk) DeclEnv -> (listof SideClause) +;; Invariant: decls contains only literals bindings +(define (parse-pattern-sides chunks decls) + (match chunks + [(cons (list '#:declare declare-stx _ _) rest) + (wrong-syntax declare-stx + "#:declare can only appear immediately after pattern or #:with clause")] + [(cons (list '#:role role-stx _) rest) + (wrong-syntax role-stx "#:role can only appear immediately after #:declare clause")] + [(cons (list '#:fail-when fw-stx when-expr msg-expr) rest) + (cons (create-post-pattern (action:fail when-expr msg-expr)) + (parse-pattern-sides rest decls))] + [(cons (list '#:fail-unless fu-stx unless-expr msg-expr) rest) + (cons (create-post-pattern (action:fail #`(not #,unless-expr) msg-expr)) + (parse-pattern-sides rest decls))] + [(cons (list '#:when w-stx unless-expr) rest) + (cons (create-post-pattern (action:fail #`(not #,unless-expr) #'#f)) + (parse-pattern-sides rest decls))] + [(cons (list '#:with with-stx pattern expr) rest) + (let-values ([(decls2 rest) (grab-decls rest decls)]) + (let-values ([(decls2a defs) (decls-create-defs decls2)]) + (list* (action:do defs) + (create-post-pattern + (action:parse (parse-whole-pattern pattern decls2a #:kind 'with) expr)) + (parse-pattern-sides rest decls))))] + [(cons (list '#:attr attr-stx a expr) rest) + (cons (action:bind a expr) ;; no POST wrapper, cannot fail + (parse-pattern-sides rest decls))] + [(cons (list '#:post post-stx pattern) rest) + (cons (create-post-pattern (parse-action-pattern pattern decls)) + (parse-pattern-sides rest decls))] + [(cons (list '#:and and-stx pattern) rest) + (cons (parse-action-pattern pattern decls) ;; no POST wrapper + (parse-pattern-sides rest decls))] + [(cons (list '#:do do-stx stmts) rest) + (cons (action:do stmts) + (parse-pattern-sides rest decls))] + [(cons (list '#:undo undo-stx stmts) rest) + (cons (action:undo stmts) + (parse-pattern-sides rest decls))] + [(cons (list '#:cut cut-stx) rest) + (cons (action:cut) + (parse-pattern-sides rest decls))] + ['() + '()])) + +;; grab-decls : (listof chunk) DeclEnv +;; -> (values DeclEnv (listof chunk)) +(define (grab-decls chunks decls0) + (define (add-decl stx role-stx decls) + (let ([role + (and role-stx + (syntax-case role-stx () + [(#:role role) #'role]))]) + (syntax-case stx () + [(#:declare name sc) + (identifier? #'sc) + (add-decl* decls #'name #'sc (parse-argu null) role)] + [(#:declare name (sc expr ...)) + (identifier? #'sc) + (add-decl* decls #'name #'sc (parse-argu (syntax->list #'(expr ...))) role)] + [(#:declare name bad-sc) + (wrong-syntax #'bad-sc + "expected syntax class name (possibly with parameters)")]))) + (define (add-decl* decls id sc-name argu role) + (declenv-put-stxclass decls id sc-name argu role)) + (define (loop chunks decls) + (match chunks + [(cons (cons '#:declare decl-stx) + (cons (cons '#:role role-stx) rest)) + (loop rest (add-decl decl-stx role-stx decls))] + [(cons (cons '#:declare decl-stx) rest) + (loop rest (add-decl decl-stx #f decls))] + [_ (values decls chunks)])) + (loop chunks decls0)) + + +;; ============================================================ +;; Arguments and Arities + +;; parse-argu : (listof stx) -> Arguments +(define (parse-argu args #:context [ctx (current-syntax-context)]) + (parameterize ((current-syntax-context ctx)) + (define (loop args rpargs rkws rkwargs) + (cond [(null? args) + (arguments (reverse rpargs) (reverse rkws) (reverse rkwargs))] + [(keyword? (syntax-e (car args))) + (let ([kw (syntax-e (car args))] + [rest (cdr args)]) + (cond [(memq kw rkws) + (wrong-syntax (car args) "duplicate keyword")] + [(null? rest) + (wrong-syntax (car args) + "missing argument expression after keyword")] + #| Overzealous, perhaps? + [(keyword? (syntax-e (car rest))) + (wrong-syntax (car rest) "expected expression following keyword")] + |# + [else + (loop (cdr rest) rpargs (cons kw rkws) (cons (car rest) rkwargs))]))] + [else + (loop (cdr args) (cons (car args) rpargs) rkws rkwargs)])) + (loop args null null null))) + +;; parse-kw-formals : stx -> Arity +(define (parse-kw-formals formals #:context [ctx (current-syntax-context)]) + (parameterize ((current-syntax-context ctx)) + (define id-h (make-bound-id-table)) + (define kw-h (make-hasheq)) ;; keyword => 'mandatory or 'optional + (define pos 0) + (define opts 0) + (define (add-id! id) + (when (bound-id-table-ref id-h id #f) + (wrong-syntax id "duplicate formal parameter" )) + (bound-id-table-set! id-h id #t)) + (define (loop formals) + (cond [(and (stx-pair? formals) (keyword? (syntax-e (stx-car formals)))) + (let* ([kw-stx (stx-car formals)] + [kw (syntax-e kw-stx)] + [rest (stx-cdr formals)]) + (cond [(hash-ref kw-h kw #f) + (wrong-syntax kw-stx "duplicate keyword")] + [(stx-null? rest) + (wrong-syntax kw-stx "missing formal parameter after keyword")] + [else + (let-values ([(formal opt?) (parse-formal (stx-car rest))]) + (add-id! formal) + (hash-set! kw-h kw (if opt? 'optional 'mandatory))) + (loop (stx-cdr rest))]))] + [(stx-pair? formals) + (let-values ([(formal opt?) (parse-formal (stx-car formals))]) + (when (and (positive? opts) (not opt?)) + (wrong-syntax (stx-car formals) + "mandatory argument may not follow optional argument")) + (add-id! formal) + (set! pos (add1 pos)) + (when opt? (set! opts (add1 opts))) + (loop (stx-cdr formals)))] + [(identifier? formals) + (add-id! formals) + (finish #t)] + [(stx-null? formals) + (finish #f)] + [else + (wrong-syntax formals "bad argument sequence")])) + (define (finish has-rest?) + (arity (- pos opts) + (if has-rest? +inf.0 pos) + (sort (for/list ([(k v) (in-hash kw-h)] + #:when (eq? v 'mandatory)) + k) + keyword<?) + (sort (hash-map kw-h (lambda (k v) k)) + keyword<?))) + (loop formals))) + +;; parse-formal : stx -> (values id bool) +(define (parse-formal formal) + (syntax-case formal () + [param + (identifier? #'param) + (values #'param #f)] + [(param default) + (identifier? #'param) + (values #'param #t)] + [_ + (wrong-syntax formal + "expected formal parameter with optional default")])) + +;; ============================================================ +;; Keyword Options & Checkers + +;; check-attr-arity-list : stx stx -> (listof SAttr) +(define (check-attr-arity-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected list of attribute declarations" ctx stx)) + (let ([iattrs + (for/list ([x (in-list (stx->list stx))]) + (check-attr-arity x ctx))]) + (iattrs->sattrs (append-iattrs (map list iattrs))))) + +;; check-attr-arity : stx stx -> IAttr +(define (check-attr-arity stx ctx) + (syntax-case stx () + [attr + (identifier? #'attr) + (make-attr #'attr 0 #f)] + [(attr depth) + (begin (unless (identifier? #'attr) + (raise-syntax-error #f "expected attribute name" ctx #'attr)) + (unless (exact-nonnegative-integer? (syntax-e #'depth)) + (raise-syntax-error #f "expected depth (nonnegative integer)" ctx #'depth)) + (make-attr #'attr (syntax-e #'depth) #f))] + [_ + (raise-syntax-error #f "expected attribute name with optional depth declaration" ctx stx)])) + +;; check-literals-list : stx stx -> (listof den:lit) +;; - txlifts defs of phase expressions +;; - txlifts checks that literals are bound +(define (check-literals-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected literals list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-literal-entry x ctx))) + +;; check-literal-entry : stx stx -> den:lit +(define (check-literal-entry stx ctx) + (define (go internal external phase) + (txlift #`(check-literal #,external #,phase #,ctx)) + (let ([external (syntax-property external 'literal (gensym))]) + (make den:lit internal external phase phase))) + (syntax-case stx () + [(internal external #:phase phase) + (and (identifier? #'internal) (identifier? #'external)) + (go #'internal #'external (txlift #'phase))] + [(internal external) + (and (identifier? #'internal) (identifier? #'external)) + (go #'internal #'external #'(syntax-local-phase-level))] + [id + (identifier? #'id) + (go #'id #'id #'(syntax-local-phase-level))] + [_ + (raise-syntax-error #f "expected literal entry" ctx stx)])) + +;; check-datum-literals-list : stx stx -> (listof den:datum-lit) +(define (check-datum-literals-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected datum-literals list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-datum-literal-entry x ctx))) + +;; check-datum-literal-entry : stx stx -> den:datum-lit +(define (check-datum-literal-entry stx ctx) + (syntax-case stx () + [(internal external) + (and (identifier? #'internal) (identifier? #'external)) + (make den:datum-lit #'internal (syntax-e #'external))] + [id + (identifier? #'id) + (make den:datum-lit #'id (syntax-e #'id))] + [_ + (raise-syntax-error #f "expected datum-literal entry" ctx stx)])) + +;; Literal sets - Import + +;; check-literal-sets-list : stx stx -> (listof (list id literalset stx stx)) +(define (check-literal-sets-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected literal-set list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-literal-set-entry x ctx))) + +;; check-literal-set-entry : stx stx -> (list id literalset stx stx) +(define (check-literal-set-entry stx ctx) + (define (elaborate litset-id lctx phase) + (let ([litset (syntax-local-value/record litset-id literalset?)]) + (unless litset + (raise-syntax-error #f "expected identifier defined as a literal-set" + ctx litset-id)) + (list litset-id litset lctx phase))) + (syntax-case stx () + [(litset . more) + (and (identifier? #'litset)) + (let* ([chunks (parse-keyword-options/eol #'more litset-directive-table + #:no-duplicates? #t + #:context ctx)] + [lctx (options-select-value chunks '#:at #:default #'litset)] + [phase (options-select-value chunks '#:phase #:default #f)]) + (elaborate #'litset lctx (if phase (txlift phase) #'(syntax-local-phase-level))))] + [litset + (identifier? #'litset) + (elaborate #'litset #'litset #'(syntax-local-phase-level))] + [_ + (raise-syntax-error #f "expected literal-set entry" ctx stx)])) + +;; Conventions + +;; returns (listof (cons Conventions (listof syntax))) +(define (check-conventions-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected conventions list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-conventions x ctx))) + +;; returns (cons Conventions (listof syntax)) +(define (check-conventions stx ctx) + (define (elaborate conventions-id argu) + (let ([cs (syntax-local-value/record conventions-id conventions?)]) + (unless cs + (raise-syntax-error #f "expected identifier defined as a conventions" + ctx conventions-id)) + (cons cs argu))) + (syntax-case stx () + [(conventions arg ...) + (identifier? #'conventions) + (elaborate #'conventions (parse-argu (syntax->list #'(arg ...))))] + [conventions + (identifier? #'conventions) + (elaborate #'conventions no-arguments)] + [_ + (raise-syntax-error "expected conventions entry" ctx stx)])) + +;; returns (listof (list regexp DeclEntry)) +(define (check-conventions-rules stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected convention rule list" ctx stx)) + (for/list ([x (in-list (stx->list stx))]) + (check-conventions-rule x ctx))) + +;; returns (list regexp DeclEntry) +(define (check-conventions-rule stx ctx) + (define (check-conventions-pattern x blame) + (cond [(symbol? x) + (regexp (string-append "^" (regexp-quote (symbol->string x)) "$"))] + [(regexp? x) x] + [else + (raise-syntax-error #f "expected identifier convention pattern" + ctx blame)])) + (define (check-sc-expr x rx) + (let ([x (check-stxclass-application x ctx)]) + (make den:class rx (car x) (cdr x)))) + (syntax-case stx () + [(rx sc) + (let ([name-pattern (check-conventions-pattern (syntax-e #'rx) #'rx)]) + (list name-pattern (check-sc-expr #'sc name-pattern)))])) + +(define (check-stxclass-header stx ctx) + (syntax-case stx () + [name + (identifier? #'name) + (list #'name #'() no-arity)] + [(name . formals) + (identifier? #'name) + (list #'name #'formals (parse-kw-formals #'formals #:context ctx))] + [_ (raise-syntax-error #f "expected syntax class header" stx ctx)])) + +(define (check-stxclass-application stx ctx) + ;; Doesn't check "operator" is actually a stxclass + (syntax-case stx () + [op + (identifier? #'op) + (cons #'op no-arguments)] + [(op arg ...) + (identifier? #'op) + (cons #'op (parse-argu (syntax->list #'(arg ...))))] + [_ (raise-syntax-error #f "expected syntax class use" ctx stx)])) + +;; bind clauses +(define (check-bind-clause-list stx ctx) + (unless (stx-list? stx) + (raise-syntax-error #f "expected sequence of bind clauses" ctx stx)) + (for/list ([clause (in-list (stx->list stx))]) + (check-bind-clause clause ctx))) + +(define (check-bind-clause clause ctx) + (syntax-case clause () + [(attr-decl expr) + (action:bind (check-attr-arity #'attr-decl ctx) #'expr)] + [_ (raise-syntax-error #f "expected bind clause" ctx clause)])) + +(define (check-stmt-list stx ctx) + (syntax-case stx () + [(e ...) + (syntax->list #'(e ...))] + [_ + (raise-syntax-error #f "expected list of expressions and definitions" ctx stx)])) + + +;; ============================================================ +;; Directive tables + +;; common-parse-directive-table +(define common-parse-directive-table + (list (list '#:disable-colon-notation) + (list '#:literals check-literals-list) + (list '#:datum-literals check-datum-literals-list) + (list '#:literal-sets check-literal-sets-list) + (list '#:conventions check-conventions-list) + (list '#:local-conventions check-conventions-rules))) + +;; parse-directive-table +(define parse-directive-table + (list* (list '#:context check-expression) + (list '#:track-literals) + common-parse-directive-table)) + +;; rhs-directive-table +(define rhs-directive-table + (list* (list '#:description check-expression) + (list '#:transparent) + (list '#:opaque) + (list '#:attributes check-attr-arity-list) + (list '#:auto-nested-attributes) + (list '#:commit) + (list '#:no-delimit-cut) + common-parse-directive-table)) + +;; pattern-directive-table +(define pattern-directive-table + (list (list '#:declare check-identifier check-expression) + (list '#:role check-expression) ;; attached to preceding #:declare + (list '#:fail-when check-expression check-expression) + (list '#:fail-unless check-expression check-expression) + (list '#:when check-expression) + (list '#:with check-expression check-expression) + (list '#:attr check-attr-arity check-expression) + (list '#:and check-expression) + (list '#:post check-expression) + (list '#:do check-stmt-list) + (list '#:undo check-stmt-list) + (list '#:cut))) + +;; fail-directive-table +(define fail-directive-table + (list (list '#:when check-expression) + (list '#:unless check-expression))) + +;; describe-option-table +(define describe-option-table + (list (list '#:opaque) + (list '#:role check-expression))) + +;; eh-optional-directive-table +(define eh-optional-directive-table + (list (list '#:too-many check-expression) + (list '#:name check-expression) + (list '#:defaults check-bind-clause-list))) + +;; h-optional-directive-table +(define h-optional-directive-table + (list (list '#:defaults check-bind-clause-list))) + +;; phase-directive-table +(define phase-directive-table + (list (list '#:phase check-expression))) + +;; litset-directive-table +(define litset-directive-table + (cons (list '#:at (lambda (stx ctx) stx)) + phase-directive-table)) + +;; var-pattern-directive-table +(define var-pattern-directive-table + (list (list '#:attr-name-separator check-stx-string) + (list '#:role check-expression))) diff --git a/7-4/racket/collects/syntax/parse/private/residual.rkt b/7-4/racket/collects/syntax/parse/private/residual.rkt new file mode 100644 index 0000000..1624b63 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/residual.rkt @@ -0,0 +1,302 @@ +#lang racket/base +(require (for-syntax racket/base) + racket/stxparam + racket/lazy-require + racket/private/promise) + +;; ============================================================ +;; Compile-time + +(require (for-syntax racket/private/sc syntax/parse/private/residual-ct)) +(provide (for-syntax (all-from-out syntax/parse/private/residual-ct))) + +(require "../../case/template.rkt") +(provide (for-syntax attribute-mapping attribute-mapping?)) + +;; ============================================================ +;; Run-time + +(require "runtime-progress.rkt" + "3d-stx.rkt" + auto-syntax-e + syntax/stx + stxparse-info/current-pvars) + +(provide (all-from-out "runtime-progress.rkt") + + this-syntax + this-role + this-context-syntax + attribute + attribute-binding + check-attr-value + stx-list-take + stx-list-drop/cx + datum->syntax/with-clause + check-literal* + error/null-eh-match + begin-for-syntax/once + + name->too-few/once + name->too-few + name->too-many + normalize-context + syntax-patterns-fail) + +;; == from runtime.rkt + +;; this-syntax +;; Bound to syntax being matched inside of syntax class +(define-syntax-parameter this-syntax + (lambda (stx) + (raise-syntax-error #f "used out of context: not within a syntax class" stx))) + +(define-syntax-parameter this-role + (lambda (stx) + (raise-syntax-error #f "used out of context: not within a syntax class" stx))) + +;; this-context-syntax +;; Bound to (expression that extracts) context syntax (bottom frame in progress) +(define-syntax-parameter this-context-syntax + (lambda (stx) + (raise-syntax-error #f "used out of context: not within a syntax class" stx))) + +(define-syntax (attribute stx) + (syntax-case stx () + [(attribute name) + (identifier? #'name) + (let ([mapping (syntax-local-value #'name (lambda () #f))]) + (unless (syntax-pattern-variable? mapping) + (raise-syntax-error #f "not bound as a pattern variable" stx #'name)) + (let ([var (syntax-mapping-valvar mapping)]) + (let ([attr (syntax-local-value var (lambda () #f))]) + (unless (attribute-mapping? attr) + (raise-syntax-error #f "not bound as an attribute" stx #'name)) + (syntax-property (attribute-mapping-var attr) + 'disappeared-use + (list (syntax-local-introduce #'name))))))])) + +;; (attribute-binding id) +;; mostly for debugging/testing +(define-syntax (attribute-binding stx) + (syntax-case stx () + [(attribute-bound? name) + (identifier? #'name) + (let ([value (syntax-local-value #'name (lambda () #f))]) + (if (syntax-pattern-variable? value) + (let ([value (syntax-local-value (syntax-mapping-valvar value) (lambda () #f))]) + (if (attribute-mapping? value) + #`(quote #,(make-attr (attribute-mapping-name value) + (attribute-mapping-depth value) + (if (attribute-mapping-check value) #f #t))) + #'(quote #f))) + #'(quote #f)))])) + +;; stx-list-take : stxish nat -> syntax +(define (stx-list-take stx n) + (datum->syntax #f + (let loop ([stx stx] [n n]) + (if (zero? n) + null + (cons (stx-car stx) + (loop (stx-cdr stx) (sub1 n))))))) + +;; stx-list-drop/cx : stxish stx nat -> (values stxish stx) +(define (stx-list-drop/cx x cx n) + (let loop ([x x] [cx cx] [n n]) + (if (zero? n) + (values x + (if (syntax? x) x cx)) + (loop (stx-cdr x) + (if (syntax? x) x cx) + (sub1 n))))) + +;; check-attr-value : Any d:Nat b:Boolean Syntax/#f -> (Listof^d (if b Syntax Any)) +(define (check-attr-value v0 depth0 base? ctx) + (define (bad kind v) + (raise-syntax-error #f (format "attribute contains non-~s value\n value: ~e" kind v) ctx)) + (define (depthloop depth v) + (if (zero? depth) + (if base? (baseloop v) v) + (let listloop ([v v] [root? #t]) + (cond [(null? v) null] + [(pair? v) (let ([new-car (depthloop (sub1 depth) (car v))] + [new-cdr (listloop (cdr v) #f)]) + (cond [(and (eq? (car v) new-car) (eq? (cdr v) new-cdr)) v] + [else (cons new-car new-cdr)]))] + [(promise? v) (listloop (force v) root?)] + [(and root? (eq? v #f)) (begin (signal-absent-pvar) (bad 'list v))] + [else (bad 'list v)])))) + (define (baseloop v) + (cond [(syntax? v) v] + [(promise? v) (baseloop (force v))] + [(eq? v #f) (begin (signal-absent-pvar) (bad 'syntax v))] + [else (bad 'syntax v)])) + (depthloop depth0 v0)) + +;; datum->syntax/with-clause : any -> syntax +(define (datum->syntax/with-clause x) + (cond [(syntax? x) x] + [(2d-stx? x #:traverse-syntax? #f) + (datum->syntax #f x #f)] + [else + (error 'datum->syntax/with-clause + (string-append + "implicit conversion to 3D syntax\n" + " right-hand side of #:with clause or ~~parse pattern would be 3D syntax\n" + " value: ~e") + x)])) + +;; check-literal* : id phase phase (listof phase) stx -> void +(define (check-literal* id used-phase mod-phase ok-phases/ct-rel ctx) + (unless (or (memv (and used-phase (- used-phase mod-phase)) + ok-phases/ct-rel) + (identifier-binding id used-phase)) + (raise-syntax-error + #f + (format "literal is unbound in phase ~a (phase ~a relative to the enclosing module)" + used-phase + (and used-phase (- used-phase mod-phase))) + ctx id))) + +;; error/null-eh-match : -> (escapes) +(define (error/null-eh-match) + (error 'syntax-parse "an ellipsis-head pattern matched an empty sequence")) + +;; (begin-for-syntax/once expr/phase1 ...) +;; evaluates in pass 2 of module/intdefs expansion +(define-syntax (begin-for-syntax/once stx) + (syntax-case stx () + [(bfs/o e ...) + (cond [(list? (syntax-local-context)) + #`(define-values () + (begin (begin-for-syntax/once e ...) + (values)))] + [else + #'(let-syntax ([m (lambda _ (begin e ...) #'(void))]) + (m))])])) + +;; == parse.rkt + +(define (name->too-few/once name) + (and name (format "missing required occurrence of ~a" name))) + +(define (name->too-few name) + (and name (format "too few occurrences of ~a" name))) + +(define (name->too-many name) + (and name (format "too many occurrences of ~a" name))) + +;; == parse.rkt + +;; normalize-context : Symbol Any Syntax -> (list Symbol/#f Syntax) +(define (normalize-context who ctx stx) + (cond [(syntax? ctx) + (list #f ctx)] + [(symbol? ctx) + (list ctx stx)] + [(eq? ctx #f) + (list #f stx)] + [(and (list? ctx) + (= (length ctx) 2) + (or (symbol? (car ctx)) (eq? #f (car ctx))) + (syntax? (cadr ctx))) + ctx] + [else (error who "bad #:context argument\n expected: ~s\n given: ~e" + '(or/c syntax? symbol? #f (list/c (or/c symbol? #f) syntax?)) + ctx)])) + +;; == parse.rkt + +(lazy-require + ["runtime-report.rkt" + (call-current-failure-handler)]) + +;; syntax-patterns-fail : (list Symbol/#f Syntax) -> (Listof (-> Any)) FailureSet -> escapes +(define ((syntax-patterns-fail ctx) undos fs) + (unwind-to undos null) + (call-current-failure-handler ctx fs)) + +;; == specialized ellipsis parser +;; returns (values 'ok attr-values) or (values 'fail failure) + +(provide predicate-ellipsis-parser) + +(define (predicate-ellipsis-parser x cx pr es pred? desc rl) + (let ([elems (stx->list x)]) + (if (and elems (list? elems) (andmap pred? elems)) + (values 'ok elems) + (let loop ([x x] [cx cx] [i 0]) + (cond [(syntax? x) + (loop (syntax-e x) x i)] + [(pair? x) + (if (pred? (car x)) + (loop (cdr x) cx (add1 i)) + (let* ([pr (ps-add-cdr pr i)] + [pr (ps-add-car pr)] + [es (es-add-thing pr desc #t rl es)]) + (values 'fail (failure pr es))))] + [else ;; not null, because stx->list failed + (let ([pr (ps-add-cdr pr i)] + #| + ;; Don't extend es! That way we don't get spurious "expected ()" + ;; that *should* have been cancelled out by ineffable pair failures. + |#) + (values 'fail (failure* pr es)))]))))) + +(provide illegal-cut-error) + +(define (illegal-cut-error . _) + (error 'syntax-parse "illegal use of cut")) + +;; ---- + +(provide unwind-to + maybe-add-state-undo + current-state + current-state-writable? + state-cons! + track-literals) + +(define (unwind-to undos base) + ;; PRE: undos = (list* proc/hash ... base) + (unless (eq? undos base) + (let ([top-undo (car undos)]) + (cond [(procedure? top-undo) (top-undo)] + [(hash? top-undo) (current-state top-undo)])) + (unwind-to (cdr undos) base))) + +(define (maybe-add-state-undo init-state new-state undos) + (if (eq? init-state new-state) + undos + (cons init-state undos))) + +;; To make adding undos to rewind current-state simpler, only allow updates +;; in a few contexts: +;; - literals (handled automatically) +;; - in ~do/#:do blocks (sets current-state-writable? = #t) + +(define current-state (make-parameter (hasheq))) +(define current-state-writable? (make-parameter #f)) + +(define (state-cons! key value) + (define state (current-state)) + (current-state (hash-set state key (cons value (hash-ref state key null))))) + +(define (track-literals who v #:introduce? [introduce? #t]) + (unless (syntax? v) + (raise-argument-error who "syntax?" v)) + (let* ([literals (hash-ref (current-state) 'literals '())]) + (if (null? literals) + v + (let ([literals* (if (and introduce? (syntax-transforming?) (list? literals)) + (for/list ([literal (in-list literals)]) + (if (identifier? literal) + (syntax-local-introduce literal) + literal)) + literals)] + [old-val (syntax-property v 'disappeared-use)]) + (syntax-property v 'disappeared-use + (if old-val + (cons literals* old-val) + literals*)))))) diff --git a/7-4/racket/collects/syntax/parse/private/runtime-progress.rkt b/7-4/racket/collects/syntax/parse/private/runtime-progress.rkt new file mode 100644 index 0000000..ad894d2 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/runtime-progress.rkt @@ -0,0 +1,258 @@ +#lang racket/base +(require racket/list + syntax/parse/private/minimatch) +(provide ps-empty + ps-add-car + ps-add-cdr + ps-add-stx + ps-add-unbox + ps-add-unvector + ps-add-unpstruct + ps-add-opaque + ps-add-post + ps-add + (struct-out ord) + + ps-pop-opaque + ps-pop-ord + ps-pop-post + ps-context-syntax + ps-difference + + (struct-out failure) + failure* + + expect? + (struct-out expect:thing) + (struct-out expect:atom) + (struct-out expect:literal) + (struct-out expect:message) + (struct-out expect:disj) + (struct-out expect:proper-pair) + + es-add-thing + es-add-message + es-add-atom + es-add-literal + es-add-proper-pair) + +;; FIXME: add phase to expect:literal + +;; == Failure == + +#| +A Failure is (failure PS ExpectStack) + +A FailureSet is one of + - Failure + - (cons FailureSet FailureSet) + +A FailFunction = (FailureSet -> Answer) +|# +(define-struct failure (progress expectstack) #:prefab) + +;; failure* : PS ExpectStack/#f -> Failure/#t +(define (failure* ps es) (if es (failure ps es) #t)) + +;; == Progress == + +#| +Progress (PS) is a non-empty list of Progress Frames (PF). + +A Progress Frame (PF) is one of + - stx ;; "Base" frame, or ~parse/#:with term + - 'car ;; car of pair; also vector->list, unbox, struct->list, etc + - nat ;; Represents that many repeated cdrs + - 'post ;; late/post-traversal check + - #s(ord group index) ;; ~and subpattern, only comparable w/in group + - 'opaque + +The error-reporting context (ie, syntax-parse #:context arg) is always +the final frame. + +All non-stx frames (eg car, cdr) interpreted as applying to nearest following +stx frame. + +A stx frame is introduced + - always at base (that is, by syntax-parse) + - if syntax-parse has #:context arg, then two stx frames at bottom: + (list to-match-stx context-stx) + - by #:with/~parse + - by #:fail-*/#:when/~fail & stx + +Interpretation: later frames are applied first. + eg, (list 'car 1 stx) + means ( car of ( cdr once of stx ) ) + NOT apply car, then apply cdr once, then stop +|# +(define-struct ord (group index) #:prefab) + +(define (ps-empty stx ctx) + (if (eq? stx ctx) + (list stx) + (list stx ctx))) +(define (ps-add-car parent) + (cons 'car parent)) +(define (ps-add-cdr parent [times 1]) + (if (zero? times) + parent + (match (car parent) + [(? exact-positive-integer? n) + (cons (+ times n) (cdr parent))] + [_ + (cons times parent)]))) +(define (ps-add-stx parent stx) + (cons stx parent)) +(define (ps-add-unbox parent) + (ps-add-car parent)) +(define (ps-add-unvector parent) + (ps-add-car parent)) +(define (ps-add-unpstruct parent) + (ps-add-car parent)) +(define (ps-add-opaque parent) + (cons 'opaque parent)) +(define (ps-add parent frame) + (cons frame parent)) +(define (ps-add-post parent) + (cons 'post parent)) + +;; ps-context-syntax : Progress -> syntax +(define (ps-context-syntax ps) + ;; Bottom frame is always syntax + (last ps)) + +;; ps-difference : PS PS -> nat +;; Returns N s.t. B = (ps-add-cdr^N A) +(define (ps-difference a b) + (define-values (a-cdrs a-base) + (match a + [(cons (? exact-positive-integer? a-cdrs) a-base) + (values a-cdrs a-base)] + [_ (values 0 a)])) + (define-values (b-cdrs b-base) + (match b + [(cons (? exact-positive-integer? b-cdrs) b-base) + (values b-cdrs b-base)] + [_ (values 0 b)])) + (unless (eq? a-base b-base) + (error 'ps-difference "INTERNAL ERROR: ~e does not extend ~e" b a)) + (- b-cdrs a-cdrs)) + +;; ps-pop-opaque : PS -> PS +;; Used to continue with progress from opaque head pattern. +(define (ps-pop-opaque ps) + (match ps + [(cons (? exact-positive-integer? n) (cons 'opaque ps*)) + (ps-add-cdr ps* n)] + [(cons 'opaque ps*) + ps*] + [_ (error 'ps-pop-opaque "INTERNAL ERROR: opaque frame not found: ~e" ps)])) + +;; ps-pop-ord : PS -> PS +(define (ps-pop-ord ps) + (match ps + [(cons (? exact-positive-integer? n) (cons (? ord?) ps*)) + (ps-add-cdr ps* n)] + [(cons (? ord?) ps*) + ps*] + [_ (error 'ps-pop-ord "INTERNAL ERROR: ord frame not found: ~e" ps)])) + +;; ps-pop-post : PS -> PS +(define (ps-pop-post ps) + (match ps + [(cons (? exact-positive-integer? n) (cons 'post ps*)) + (ps-add-cdr ps* n)] + [(cons 'post ps*) + ps*] + [_ (error 'ps-pop-post "INTERNAL ERROR: post frame not found: ~e" ps)])) + + +;; == Expectations == + +#| +There are multiple types that use the same structures, optimized for +different purposes. + +-- During parsing, the goal is to minimize/consolidate allocations. + +An ExpectStack (during parsing) is one of + - (expect:thing Progress String Boolean String/#f ExpectStack) + - (expect:thing Progress #f #f String/#f ExpectStack) + * (expect:message String ExpectStack) + * (expect:atom Datum ExpectStack) + * (expect:literal Identifier ExpectStack) + * (expect:proper-pair FirstDesc ExpectStack) + * #t + +The *-marked variants can only occur at the top of the stack (ie, not +in the next field of another Expect). The top of the stack contains +the most specific information. + +An ExpectStack can also be #f, which means no failure tracking is +requested (and thus no more ExpectStacks should be allocated). + +-- During reporting, the goal is ease of manipulation. + +An ExpectList (during reporting) is (listof Expect). + +An Expect is one of + - (expect:thing #f String #t String/#f StxIdx) + * (expect:message String StxIdx) + * (expect:atom Datum StxIdx) + * (expect:literal Identifier StxIdx) + * (expect:proper-pair FirstDesc StxIdx) + * (expect:disj (NEListof Expect) StxIdx) + - '... + +A StxIdx is (cons Syntax Nat) + +That is, the next link is replaced with the syntax+index of the term +being complained about. An expect:thing's progress is replaced with #f. + +An expect:disj never contains a '... or another expect:disj. + +We write ExpectList when the most specific information comes first and +RExpectList when the most specific information comes last. +|# +(struct expect:thing (term description transparent? role next) #:prefab) +(struct expect:message (message next) #:prefab) +(struct expect:atom (atom next) #:prefab) +(struct expect:literal (literal next) #:prefab) +(struct expect:disj (expects next) #:prefab) +(struct expect:proper-pair (first-desc next) #:prefab) + +(define (expect? x) + (or (expect:thing? x) + (expect:message? x) + (expect:atom? x) + (expect:literal? x) + (expect:disj? x) + (expect:proper-pair? x))) + +(define (es-add-thing ps description transparent? role next) + (if (and next (or description (not transparent?))) + (expect:thing ps description transparent? role next) + next)) + +(define (es-add-message message next) + (if (and next message) + (expect:message message next) + next)) + +(define (es-add-atom atom next) + (and next (expect:atom atom next))) + +(define (es-add-literal literal next) + (and next (expect:literal literal next))) + +(define (es-add-proper-pair first-desc next) + (and next (expect:proper-pair first-desc next))) + +#| +A FirstDesc is one of + - #f -- unknown, multiple possible, etc + - string -- description + - (list 'any) + - (list 'literal symbol) + - (list 'datum datum) +|# diff --git a/7-4/racket/collects/syntax/parse/private/runtime-reflect.rkt b/7-4/racket/collects/syntax/parse/private/runtime-reflect.rkt new file mode 100644 index 0000000..59125cf --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/runtime-reflect.rkt @@ -0,0 +1,96 @@ +#lang racket/base +(require "residual.rkt" + (only-in syntax/parse/private/residual-ct attr-name attr-depth) + syntax/parse/private/kws) +(provide reflect-parser + (struct-out reified) + (struct-out reified-syntax-class) + (struct-out reified-splicing-syntax-class)) + +#| +A Reified is + (reified symbol ParserFunction nat (listof (list symbol nat))) +|# +(require (only-in syntax/parse/private/runtime-reflect + reified + reified? + reified-parser + reified-arity + reified-signature + make-reified + struct:reified + + reified-syntax-class + reified-syntax-class? + make-reified-syntax-class + struct:reified-syntax-class + + reified-splicing-syntax-class + reified-splicing-syntax-class? + make-reified-splicing-syntax-class + struct:reified-splicing-syntax-class)) +#;(define-struct reified-base (name) #:transparent) +#;(define-struct (reified reified-base) (parser arity signature)) +#;(define-struct (reified-syntax-class reified) ()) +#;(define-struct (reified-splicing-syntax-class reified) ()) + +(define (reflect-parser obj e-arity e-attrs splicing?) + ;; e-arity represents single call; min and max are same + (define who (if splicing? 'reflect-splicing-syntax-class 'reflect-syntax-class)) + (if splicing? + (unless (reified-splicing-syntax-class? obj) + (raise-type-error who "reified splicing-syntax-class" obj)) + (unless (reified-syntax-class? obj) + (raise-type-error who "reified syntax-class" obj))) + (check-params who e-arity (reified-arity obj) obj) + (adapt-parser who + (for/list ([a (in-list e-attrs)]) + (list (attr-name a) (attr-depth a))) + (reified-signature obj) + (reified-parser obj) + splicing?)) + +(define (check-params who e-arity r-arity obj) + (let ([e-pos (arity-minpos e-arity)] + [e-kws (arity-minkws e-arity)]) + (check-arity r-arity e-pos e-kws (lambda (msg) (error who "~a" msg))))) + +(define (adapt-parser who esig0 rsig0 parser splicing?) + (if (equal? esig0 rsig0) + parser + (let ([indexes + (let loop ([esig esig0] [rsig rsig0] [index 0]) + (cond [(null? esig) + null] + [(and (pair? rsig) (eq? (caar esig) (caar rsig))) + (unless (= (cadar esig) (cadar rsig)) + (wrong-depth who (car esig) (car rsig))) + (cons index (loop (cdr esig) (cdr rsig) (add1 index)))] + [(and (pair? rsig) + (string>? (symbol->string (caar esig)) + (symbol->string (caar rsig)))) + (loop esig (cdr rsig) (add1 index))] + [else + (error who "reified syntax-class is missing declared attribute `~s'" + (caar esig))]))]) + (define (take-indexes result indexes) + (let loop ([result result] [indexes indexes] [i 0]) + (cond [(null? indexes) null] + [(= (car indexes) i) + (cons (car result) (loop (cdr result) (cdr indexes) (add1 i)))] + [else + (loop (cdr result) indexes (add1 i))]))) + (make-keyword-procedure + (lambda (kws kwargs x cx pr es undos fh cp rl success . rest) + (keyword-apply parser kws kwargs x cx pr es undos fh cp rl + (if splicing? + (lambda (fh undos x cx pr . result) + (apply success fh undos x cx pr (take-indexes result indexes))) + (lambda (fh undos . result) + (apply success fh undos (take-indexes result indexes)))) + rest)))))) + +(define (wrong-depth who a b) + (error who + "reified syntax-class has wrong depth for attribute `~s'; expected ~s, got ~s instead" + (car a) (cadr a) (cadr b))) diff --git a/7-4/racket/collects/syntax/parse/private/runtime-report.rkt b/7-4/racket/collects/syntax/parse/private/runtime-report.rkt new file mode 100644 index 0000000..a1d09e1 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/runtime-report.rkt @@ -0,0 +1,816 @@ +#lang racket/base +(require racket/list + racket/format + syntax/stx + racket/struct + syntax/srcloc + syntax/parse/private/minimatch + stxparse-info/parse/private/residual + syntax/parse/private/kws) +(provide call-current-failure-handler + current-failure-handler + invert-failure + maximal-failures + invert-ps + ps->stx+index) + +#| +TODO: given (expect:thing _ D _ R) and (expect:thing _ D _ #f), + simplify to (expect:thing _ D _ #f) + thus, "expected D" rather than "expected D or D for R" (?) +|# + +#| +Note: there is a cyclic dependence between residual.rkt and this module, +broken by a lazy-require of this module into residual.rkt +|# + +(define (call-current-failure-handler ctx fs) + (call-with-values (lambda () ((current-failure-handler) ctx fs)) + (lambda vals + (error 'current-failure-handler + "current-failure-handler: did not escape, produced ~e" + (case (length vals) + ((1) (car vals)) + (else (cons 'values vals))))))) + +(define (default-failure-handler ctx fs) + (handle-failureset ctx fs)) + +(define current-failure-handler + (make-parameter default-failure-handler)) + + +;; ============================================================ +;; Processing failure sets + +#| +We use progress to select the maximal failures and determine the syntax +they're complaining about. After that, we no longer care about progress. + +Old versions of syntax-parse (through 6.4) grouped failures into +progress-equivalence-classes and generated reports by class, but only showed +one report. New syntax-parse just mixes all maximal failures together and +deals with the fact that they might not be talking about the same terms. +|# + +;; handle-failureset : (list Symbol/#f Syntax) FailureSet -> escapes +(define (handle-failureset ctx fs) + (define inverted-fs (map invert-failure (reverse (flatten fs)))) + (define maximal-classes (maximal-failures inverted-fs)) + (define ess (map failure-expectstack (append* maximal-classes))) + (define report (report/sync-shared ess)) + ;; Hack: alternative to new (primitive) phase-crossing exn type is to store + ;; extra information in exn continuation marks. Currently for debugging only. + (with-continuation-mark 'syntax-parse-error + (hasheq 'raw-failures fs + 'maximal maximal-classes) + (error/report ctx report))) + +;; An RFailure is (failure IPS RExpectList) + +;; invert-failure : Failure -> RFailure +(define (invert-failure f) + (match f + [(failure ps es) + (failure (invert-ps ps) (invert-expectstack es (ps->stx+index ps)))])) + +;; A Report is (report String (Listof String) Syntax/#f Syntax/#f) +(define-struct report (message context stx within-stx) #:prefab) + +;; Sometimes the point where an error occurred does not correspond to +;; a syntax object within the original term being matched. We use one +;; or two syntax objects to identify where an error occurred: +;; - the "at" term is the specific point of error, coerced to a syntax +;; object if it isn't already +;; - the "within" term is the closest enclosing original syntax object, +;; dropped (#f) if same as "at" term + +;; Examples (AT is pre-coercion): +;; TERM PATTERN => AT WITHIN +;; #'(1) (a:id) #'1 -- ;; the happy case +;; #'(1) (a b) () #'(1) ;; tail of syntax list, too short +;; #'(1 . ()) (a b) #'() -- ;; tail is already syntax +;; #'#(1) #(a b) () #'#(1) ;; "tail" of syntax vector +;; #'#s(X 1) #s(X a b) () #'#s(X 1) ;; "tail" of syntax prefab +;; #'(1 2) (a) (#'2) #'(1 2) ;; tail of syntax list, too long + + +;; ============================================================ +;; Progress + +;; maximal-failures : (listof InvFailure) -> (listof (listof InvFailure)) +(define (maximal-failures fs) + (maximal/progress + (for/list ([f (in-list fs)]) + (cons (failure-progress f) f)))) + +#| +Progress ordering +----------------- + +Nearly a lexicographic generalization of partial order on frames. + (( CAR < CDR ) || stx ) < POST ) + - stx incomparable except with self + +But ORD prefixes are sorted out (and discarded) before comparison with +rest of progress. Like post, ord comparable only w/in same group: + - (ord g n1) < (ord g n2) if n1 < n2 + - (ord g1 n1) || (ord g2 n2) when g1 != g2 + + +Progress equality +----------------- + +If ps1 = ps2 then both must "blame" the same term, +ie (ps->stx+index ps1) = (ps->stx+index ps2). +|# + +;; An Inverted PS (IPS) is a PS inverted for easy comparison. +;; An IPS may not contain any 'opaque frames. + +;; invert-ps : PS -> IPS +;; Reverse and truncate at earliest 'opaque frame. +(define (invert-ps ps) + (reverse (ps-truncate-opaque ps))) + +;; ps-truncate-opaque : PS -> PS +;; Returns maximal tail with no 'opaque frame. +(define (ps-truncate-opaque ps) + (let loop ([ps ps] [acc ps]) + ;; acc is the biggest tail that has not been seen to contain 'opaque + (cond [(null? ps) acc] + [(eq? (car ps) 'opaque) + (loop (cdr ps) (cdr ps))] + [else (loop (cdr ps) acc)]))) + +;; maximal/progress : (listof (cons IPS A)) -> (listof (listof A)) +;; Eliminates As with non-maximal progress, then groups As into +;; equivalence classes according to progress. +(define (maximal/progress items) + (cond [(null? items) + null] + [(null? (cdr items)) + (list (list (cdr (car items))))] + [else + (let loop ([items items] [non-ORD-items null]) + (define-values (ORD non-ORD) + (partition (lambda (item) (ord? (item-first-prf item))) items)) + (cond [(pair? ORD) + (loop (maximal-prf1/ord ORD) (append non-ORD non-ORD-items))] + [else + (maximal/prf1 (append non-ORD non-ORD-items))]))])) + +;; maximal/prf1 : (Listof (Cons IPS A) -> (Listof (Listof A)) +(define (maximal/prf1 items) + (define-values (POST rest1) + (partition (lambda (item) (eq? 'post (item-first-prf item))) items)) + (cond [(pair? POST) + (maximal/progress (map item-pop-prf POST))] + [else + (define-values (STX rest2) + (partition (lambda (item) (syntax? (item-first-prf item))) rest1)) + (define-values (CDR rest3) + (partition (lambda (item) (exact-integer? (item-first-prf item))) rest2)) + (define-values (CAR rest4) + (partition (lambda (item) (eq? 'car (item-first-prf item))) rest3)) + (define-values (NULL rest5) + (partition (lambda (item) (eq? '#f (item-first-prf item))) rest4)) + (unless (null? rest5) + (error 'syntax-parse "INTERNAL ERROR: bad progress: ~e\n" rest5)) + (cond [(pair? CDR) + (define leastCDR (apply min (map item-first-prf CDR))) + (append + (maximal/stx STX) + (maximal/progress (map (lambda (item) (item-pop-prf-ncdrs item leastCDR)) CDR)))] + [(pair? CAR) + (append + (maximal/stx STX) + (maximal/progress (map item-pop-prf CAR)))] + [(pair? STX) + (maximal/stx STX)] + [(pair? NULL) + (list (map cdr NULL))] + [else null])])) + +;; maximal-prf1/ord : (NEListof (Cons IPS A)) -> (NEListof (Cons IPS A)) +;; PRE: each item has ORD first frame +;; Keep only maximal by first frame and pop first frame from each item. +(define (maximal-prf1/ord items) + ;; groups : (NEListof (NEListof (cons A IPS))) + (define groups (group-by (lambda (item) (ord-group (item-first-prf item))) items)) + (append* + (for/list ([group (in-list groups)]) + (define group* (filter-max group (lambda (item) (ord-index (item-first-prf item))))) + (map item-pop-prf group*)))) + +;; maximal/stx : (NEListof (cons IPS A)) -> (NEListof (NEListof A)) +;; PRE: Each IPS starts with a stx frame. +(define (maximal/stx items) + ;; groups : (Listof (Listof (cons IPS A))) + (define groups (group-by item-first-prf items)) + (append* + (for/list ([group (in-list groups)]) + (maximal/progress (map item-pop-prf group))))) + +;; filter-max : (Listof X) (X -> Nat) -> (Listof X) +(define (filter-max xs x->nat) + (let loop ([xs xs] [nmax -inf.0] [r-keep null]) + (cond [(null? xs) + (reverse r-keep)] + [else + (define n0 (x->nat (car xs))) + (cond [(> n0 nmax) + (loop (cdr xs) n0 (list (car xs)))] + [(= n0 nmax) + (loop (cdr xs) nmax (cons (car xs) r-keep))] + [else + (loop (cdr xs) nmax r-keep)])]))) + +;; item-first-prf : (cons IPS A) -> prframe/#f +(define (item-first-prf item) + (define ips (car item)) + (and (pair? ips) (car ips))) + +;; item-split-ord : (cons IPS A) -> (cons IPS (cons IPS A)) +(define (item-split-ord item) + (define ips (car item)) + (define a (cdr item)) + (define-values (rest-ips r-ord) + (let loop ([ips ips] [r-ord null]) + (cond [(and (pair? ips) (ord? (car ips))) + (loop (cdr ips) (cons (car ips) r-ord))] + [else (values ips r-ord)]))) + (list* (reverse r-ord) rest-ips a)) + +;; item-pop-prf : (cons IPS A) -> (cons IPS A) +(define (item-pop-prf item) + (let ([ips (car item)] + [a (cdr item)]) + (cons (cdr ips) a))) + +;; item-pop-prf-ncdrs : (cons IPS A) -> (cons IPS A) +;; Assumes first frame is nat > ncdrs. +(define (item-pop-prf-ncdrs item ncdrs) + (let ([ips (car item)] + [a (cdr item)]) + (cond [(= (car ips) ncdrs) (cons (cdr ips) a)] + [else (cons (cons (- (car ips) ncdrs) (cdr ips)) a)]))) + +;; StxIdx = (cons Syntax Nat), the "within" term and offset (#cdrs) of "at" subterm + +;; ps->stx+index : Progress -> StxIdx +;; Gets the innermost stx that should have a real srcloc, and the offset +;; (number of cdrs) within that where the progress ends. +(define (ps->stx+index ps) + (define (interp ps top?) + ;; if top?: first frame is 'car, must return Syntax, don't unwrap vector/struct + (match ps + [(cons (? syntax? stx) _) stx] + [(cons 'car parent) + (let* ([x (interp parent #f)] + [d (if (syntax? x) (syntax-e x) x)]) + (cond [(pair? d) (car d)] + [(vector? d) + (if top? x (vector->list d))] + [(box? d) (unbox d)] + [(prefab-struct-key d) + (if top? x (struct->list d))] + [else (error 'ps->stx+index "INTERNAL ERROR: unexpected: ~e" d)]))] + [(cons (? exact-positive-integer? n) parent) + (for/fold ([stx (interp parent #f)]) ([i (in-range n)]) + (stx-cdr stx))] + [(cons (? ord?) parent) + (interp parent top?)] + [(cons 'post parent) + (interp parent top?)])) + (let loop ([ps (ps-truncate-opaque ps)]) + (match ps + [(cons (? syntax? stx) _) + (cons stx 0)] + [(cons 'car _) + (cons (interp ps #t) 0)] + [(cons (? exact-positive-integer? n) parent) + (match (loop parent) + [(cons stx m) (cons stx (+ m n))])] + [(cons (? ord?) parent) + (loop parent)] + [(cons 'post parent) + (loop parent)]))) + +;; stx+index->at+within : StxIdx -> (values Syntax Syntax/#f) +(define (stx+index->at+within stx+index) + (define within-stx (car stx+index)) + (define index (cdr stx+index)) + (cond [(zero? index) + (values within-stx #f)] + [else + (define d (syntax-e within-stx)) + (define stx* + (cond [(vector? d) (vector->list d)] + [(prefab-struct-key d) (struct->list d)] + [else within-stx])) + (define at-stx* + (for/fold ([x stx*]) ([_i (in-range index)]) (stx-cdr x))) + (values (datum->syntax within-stx at-stx* within-stx) + within-stx)])) + +;; ============================================================ +;; Expectation simplification + +;; normalize-expectstack : ExpectStack StxIdx -> ExpectList +;; Converts to list, converts expect:thing term rep, and truncates +;; expectstack after opaque (ie, transparent=#f) frames. +(define (normalize-expectstack es stx+index [truncate-opaque? #t]) + (reverse (invert-expectstack es stx+index truncate-opaque?))) + +;; invert-expectstack : ExpectStack StxIdx -> RExpectList +;; Converts to reversed list, converts expect:thing term rep, +;; and truncates expectstack after opaque (ie, transparent=#f) frames. +(define (invert-expectstack es stx+index [truncate-opaque? #t]) + (let loop ([es es] [acc null]) + (match es + ['#f acc] + ['#t acc] + [(expect:thing ps desc tr? role rest-es) + (let* (;; discard frames so far if opaque + [acc (if (and truncate-opaque? (not tr?)) null acc)] + ;; discard this frame if desc is #f + [acc (if desc (cons (expect:thing #f desc tr? role (ps->stx+index ps)) acc) acc)]) + (loop rest-es acc))] + [(expect:message message rest-es) + (loop rest-es (cons (expect:message message stx+index) acc))] + [(expect:atom atom rest-es) + (loop rest-es (cons (expect:atom atom stx+index) acc))] + [(expect:literal literal rest-es) + (loop rest-es (cons (expect:literal literal stx+index) acc))] + [(expect:proper-pair first-desc rest-es) + (loop rest-es (cons (expect:proper-pair first-desc stx+index) acc))]))) + +;; expect->stxidx : Expect -> StxIdx +(define (expect->stxidx e) + (cond [(expect:thing? e) (expect:thing-next e)] + [(expect:message? e) (expect:message-next e)] + [(expect:atom? e) (expect:atom-next e)] + [(expect:literal? e) (expect:literal-next e)] + [(expect:proper-pair? e) (expect:proper-pair-next e)] + [(expect:disj? e) (expect:disj-next e)])) + +#| Simplification + +A list of ExpectLists represents a tree, with shared tails meaning shared +branches of the tree. We need a "reasonable" way to simplify it to a list to +show to the user. Here we develop "reasonable" by example. (It would be nice, +of course, to also have some way of exploring the full failure trees.) + +Notation: [A B X] means an ExpectList with class/description A at root and X +at leaf. If the term sequences differ, write [t1:A ...] etc. + +Options: + (o) = "old behavior (through 6.4)" + (f) = "first divergence" + (s) = "sync on shared" + +Case 1: [A B X], [A B Y] + + This is nearly the ideal situation: report as + + expected X or Y, while parsing B, while parsing A + +Case 2: [A X], [A] + + For example, matching #'1 as (~describe A (x:id ...)) yields [A], [A '()], + but we don't want to see "expected ()". + + So simplify to [A]---that is, drop X. + +But there are other cases that are more problematic. + +Case 3: [t1:A t2:B t3:X], [t1:A t2:C t3:Y] + + Could report as: + (o) expected X for t3, while parsing t2 as B, while parsing t1 as A (also other errors) + (f) expected B or C for t2, while parsing t1 as A + (x) expected X or Y for t3, while parsing t2 as B or C, while parsing t1 as A + + (o) is not good + (b) loses the most specific error information + (x) implies spurious contexts (eg, X while parsing C) + + I like (b) best for this situation, but ... + +Case 4: [t1:A t2:B t4:X], [t1:A t3:C t4:Y] + + Could report as: + (f') expected B or C, while parsing t1 as A + (s) expected X or Y for t4, while ..., while parsing t1 as A + (f) expected A for t1 + + (f') is problematic, since terms are different! + (s) okay, but nothing good to put in that ... space + (f) loses a lot of information + +Case 5: [t1:A t2:B t3:X], [t1:A t4:C t5:Y] + + Only feasible choice (no other sync points): + (f,s) expected A for t1 + +Case 6: [t1:A _ t2:B t3:X], [t1:A _ t2:C t3:Y] + + Could report as: + (s') expected X or Y for t3, while parsing t2 as B or C, while ..., while parsing t1 as A + (s) expected X or Y for t3, while ..., while parsing t1 as A + + (s') again implies spurious contexts, bad + (s) okay + +Case 7: [_ t2:B t3:C _], [_ t3:C t2:B _] + + Same frames show up in different orders. (Can this really happen? Probably, + with very weird uses of ~parse.) + +-- + +This suggests the following new algorithm based on (s): +- Step 1: emit an intermediate "unified" expectstack (extended with "..." markers) + - make a list (in order) of frames shared by all expectstacks + - emit those frames with "..." markers if (sometimes) unshared stuff between + - continue processing with the tails after the last shared frame: + - find the last term shared by all expectstacks (if any) + - find the last frame for that term for each expectstack + - combine in expect:disj and emit +- Step 2: + - remove trailing and collapse adjacent "..." markers + +|# + +;; report* : (NEListof RExpectList) ((NEListof (NEListof RExpectList)) -> ExpectList) +;; -> Report +(define (report* ess handle-divergence) + (define es ;; ExpectList + (let loop ([ess ess] [acc null]) + (cond [(ormap null? ess) acc] + [else + (define groups (group-by car ess)) + (cond [(singleton? groups) + (define group (car groups)) + (define frame (car (car group))) + (loop (map cdr group) (cons frame acc))] + [else ;; found point of divergence + (append (handle-divergence groups) acc)])]))) + (define stx+index (if (pair? es) (expect->stxidx (car es)) (cons #f 0))) + (report/expectstack (clean-up es) stx+index)) + +;; clean-up : ExpectList -> ExpectList +;; Remove leading and collapse adjacent '... markers +(define (clean-up es) + (if (and (pair? es) (eq? (car es) '...)) + (clean-up (cdr es)) + (let loop ([es es]) + (cond [(null? es) null] + [(eq? (car es) '...) + (cons '... (clean-up es))] + [else (cons (car es) (loop (cdr es)))])))) + +;; -- + +;; report/first-divergence : (NEListof RExpectList) -> Report +;; Generate a single report, using frames from root to first divergence. +(define (report/first-divergence ess) + (report* ess handle-divergence/first)) + +;; handle-divergence/first : (NEListof (NEListof RExpectList)) -> ExpectList +(define (handle-divergence/first ess-groups) + (define representative-ess (map car ess-groups)) + (define first-frames (map car representative-ess)) + ;; Do all of the first frames talk about the same term? + (cond [(all-equal? (map expect->stxidx first-frames)) + (list (expect:disj first-frames #f))] + [else null])) + +;; -- + +;; report/sync-shared : (NEListof RExpectList) -> Report +;; Generate a single report, syncing on shared frames (and later, terms). +(define (report/sync-shared ess) + (report* ess handle-divergence/sync-shared)) + +;; handle-divergence/sync-shared : (NEListof (NEListof RExpectList)) -> ExpectList +(define (handle-divergence/sync-shared ess-groups) + (define ess (append* ess-groups)) ;; (NEListof RExpectList) + (define shared-frames (get-shared ess values)) + ;; rsegs : (NEListof (Rev2n+1-Listof RExpectList)) + (define rsegs (for/list ([es (in-list ess)]) (rsplit es values shared-frames))) + (define final-seg (map car rsegs)) ;; (NEListof RExpectList), no common frames + (define ctx-rsegs (transpose (map cdr rsegs))) ;; (Rev2n-Listof (NEListof RExpectList)) + (append (hd/sync-shared/final final-seg) + (hd/sync-shared/ctx ctx-rsegs))) + +;; hd/sync-shared/final : (NEListof RExpectList) -> ExpectList +;; PRE: ess has no shared frames, but may have shared terms. +(define (hd/sync-shared/final ess0) + (define ess (remove-extensions ess0)) + (define shared-terms (get-shared ess expect->stxidx)) + (cond [(null? shared-terms) null] + [else + ;; split at the last shared term + (define rsegs ;; (NEListof (3-Listof RExpectList)) + (for/list ([es (in-list ess)]) + (rsplit es expect->stxidx (list (last shared-terms))))) + ;; only care about the got segment and pre, not post + (define last-term-ess ;; (NEListof RExpectList) + (map cadr rsegs)) + (define pre-term-ess ;; (NEListof RExpectList) + (map caddr rsegs)) + ;; last is most specific + (append + (list (expect:disj (remove-duplicates (reverse (map last last-term-ess))) + (last shared-terms))) + (if (ormap pair? pre-term-ess) '(...) '()))])) + +;; hd/sync-shared/ctx : (Rev2n-Listof (NEListof RExpectList)) -> ExpectList +;; In [gotN preN ... got1 pre1] order, where 1 is root-most, N is leaf-most. +;; We want leaf-most-first, so just process naturally. +(define (hd/sync-shared/ctx rsegs) + (let loop ([rsegs rsegs]) + (cond [(null? rsegs) null] + [(null? (cdr rsegs)) (error 'syntax-parse "INTERNAL ERROR: bad segments")] + [else (append + ;; shared frame: possible for duplicate ctx frames, but unlikely + (let ([ess (car rsegs)]) (list (car (car ess)))) + ;; inter frames: + (let ([ess (cadr rsegs)]) (if (ormap pair? ess) '(...) '())) + ;; recur + (loop (cddr rsegs)))]))) + +;; transpose : (Listof (Listof X)) -> (Listof (Listof X)) +(define (transpose xss) + (cond [(ormap null? xss) null] + [else (cons (map car xss) (transpose (map cdr xss)))])) + +;; get-shared : (Listof (Listof X)) (X -> Y) -> (Listof Y) +;; Return a list of Ys s.t. occur in order in (map of) each xs in xss. +(define (get-shared xss get-y) + (cond [(null? xss) null] + [else + (define yhs ;; (Listof (Hash Y => Nat)) + (for/list ([xs (in-list xss)]) + (for/hash ([x (in-list xs)] [i (in-naturals 1)]) + (values (get-y x) i)))) + (remove-duplicates + (let loop ([xs (car xss)] [last (for/list ([xs (in-list xss)]) 0)]) + ;; last is list of indexes of last accepted y; only accept next if occurs + ;; after last in every sequence (see Case 7 above) + (cond [(null? xs) null] + [else + (define y (get-y (car xs))) + (define curr (for/list ([yh (in-list yhs)]) (hash-ref yh y -1))) + (cond [(andmap > curr last) + (cons y (loop (cdr xs) curr))] + [else (loop (cdr xs) last)])])))])) + +;; rsplit : (Listof X) (X -> Y) (Listof Y) -> (Listof (Listof X)) +;; Given [y1 ... yN], splits xs into [rest gotN preN ... got1 pre1]. +;; Thus the result has 2N+1 elements. The sublists are in original order. +(define (rsplit xs get-y ys) + (define (loop xs ys segsacc) + (cond [(null? ys) (cons xs segsacc)] + [else (pre-loop xs ys segsacc null)])) + (define (pre-loop xs ys segsacc preacc) + (cond [(and (pair? xs) (equal? (get-y (car xs)) (car ys))) + (got-loop (cdr xs) ys segsacc preacc (list (car xs)))] + [else + (pre-loop (cdr xs) ys segsacc (cons (car xs) preacc))])) + (define (got-loop xs ys segsacc preacc gotacc) + (cond [(and (pair? xs) (equal? (get-y (car xs)) (car ys))) + (got-loop (cdr xs) ys segsacc preacc (cons (car xs) gotacc))] + [else + (loop xs (cdr ys) (list* (reverse gotacc) (reverse preacc) segsacc))])) + (loop xs ys null)) + +;; singleton? : list -> boolean +(define (singleton? x) (and (pair? x) (null? (cdr x)))) + +;; remove-extensions : (Listof (Listof X)) -> (Listof (Listof X)) +;; Remove any element that is an extension of another. +(define (remove-extensions xss) + (cond [(null? xss) null] + [else + (let loop ([xss xss]) + (cond [(singleton? xss) xss] + [(ormap null? xss) (list null)] + [else + (define groups (group-by car xss)) + (append* + (for/list ([group (in-list groups)]) + (define group* (loop (map cdr group))) + (map (lambda (x) (cons (caar group) x)) group*)))]))])) + +;; all-equal? : (Listof Any) -> Boolean +(define (all-equal? xs) (for/and ([x (in-list xs)]) (equal? x (car xs)))) + + +;; ============================================================ +;; Reporting + +;; report/expectstack : ExpectList StxIdx -> Report +(define (report/expectstack es stx+index) + (define frame-expect (and (pair? es) (car es))) + (define context-frames (if (pair? es) (cdr es) null)) + (define context (append* (map context-prose-for-expect context-frames))) + (cond [(not frame-expect) + (report "bad syntax" context #f #f)] + [else + (define-values (frame-stx within-stx) (stx+index->at+within stx+index)) + (cond [(and (match frame-expect [(expect:atom '() _) #t] [_ #f]) + (stx-pair? frame-stx)) + (report "unexpected term" context (stx-car frame-stx) #f)] + [(expect:disj? frame-expect) + (report (prose-for-expects (expect:disj-expects frame-expect)) + context frame-stx within-stx)] + [else + (report (prose-for-expects (list frame-expect)) + context frame-stx within-stx)])])) + +;; prose-for-expects : (listof Expect) -> string +(define (prose-for-expects expects) + (define msgs (filter expect:message? expects)) + (define things (filter expect:thing? expects)) + (define literal (filter expect:literal? expects)) + (define atom/symbol + (filter (lambda (e) (and (expect:atom? e) (symbol? (expect:atom-atom e)))) expects)) + (define atom/nonsym + (filter (lambda (e) (and (expect:atom? e) (not (symbol? (expect:atom-atom e))))) expects)) + (define proper-pairs (filter expect:proper-pair? expects)) + (join-sep + (append (map prose-for-expect (append msgs things)) + (prose-for-expects/literals literal "identifiers") + (prose-for-expects/literals atom/symbol "literal symbols") + (prose-for-expects/literals atom/nonsym "literals") + (prose-for-expects/pairs proper-pairs)) + ";" "or")) + +(define (prose-for-expects/literals expects whats) + (cond [(null? expects) null] + [(singleton? expects) (map prose-for-expect expects)] + [else + (define (prose e) + (match e + [(expect:atom (? symbol? atom) _) + (format "`~s'" atom)] + [(expect:atom atom _) + (format "~s" atom)] + [(expect:literal literal _) + (format "`~s'" (syntax-e literal))])) + (list (string-append "expected one of these " whats ": " + (join-sep (map prose expects) "," "or")))])) + +(define (prose-for-expects/pairs expects) + (if (pair? expects) (list (prose-for-proper-pair-expects expects)) null)) + +;; prose-for-expect : Expect -> string +(define (prose-for-expect e) + (match e + [(expect:thing _ description transparent? role _) + (if role + (format "expected ~a for ~a" description role) + (format "expected ~a" description))] + [(expect:atom (? symbol? atom) _) + (format "expected the literal symbol `~s'" atom)] + [(expect:atom atom _) + (format "expected the literal ~s" atom)] + [(expect:literal literal _) + (format "expected the identifier `~s'" (syntax-e literal))] + [(expect:message message _) + message] + [(expect:proper-pair '#f _) + "expected more terms"])) + +;; prose-for-proper-pair-expects : (listof expect:proper-pair) -> string +(define (prose-for-proper-pair-expects es) + (define descs (remove-duplicates (map expect:proper-pair-first-desc es))) + (cond [(for/or ([desc descs]) (equal? desc #f)) + ;; FIXME: better way to indicate unknown ??? + "expected more terms"] + [else + (format "expected more terms starting with ~a" + (join-sep (map prose-for-first-desc descs) + "," "or"))])) + +;; prose-for-first-desc : FirstDesc -> string +(define (prose-for-first-desc desc) + (match desc + [(? string?) desc] + [(list 'any) "any term"] ;; FIXME: maybe should cancel out other descs ??? + [(list 'literal id) (format "the identifier `~s'" id)] + [(list 'datum (? symbol? s)) (format "the literal symbol `~s'" s)] + [(list 'datum d) (format "the literal ~s" d)])) + +;; context-prose-for-expect : (U '... expect:thing) -> (listof string) +(define (context-prose-for-expect e) + (match e + ['... + (list "while parsing different things...")] + [(expect:thing '#f description transparent? role stx+index) + (let-values ([(stx _within-stx) (stx+index->at+within stx+index)]) + (cons (~a "while parsing " description + (if role (~a " for " role) "")) + (if (error-print-source-location) + (list (~a " term: " + (~s (syntax->datum stx) + #:limit-marker "..." + #:max-width 50)) + (~a " location: " + (or (source-location->string stx) "not available"))) + null)))])) + + +;; ============================================================ +;; Raise exception + +(define (error/report ctx report) + (let* ([message (report-message report)] + [context (report-context report)] + [stx (cadr ctx)] + [who (or (car ctx) (infer-who stx))] + [sub-stx (report-stx report)] + [within-stx (report-within-stx report)] + [message + (format "~a: ~a~a~a~a~a" + who message + (format-if "at" (stx-if-loc sub-stx)) + (format-if "within" (stx-if-loc within-stx)) + (format-if "in" (stx-if-loc stx)) + (if (null? context) + "" + (apply string-append + "\n parsing context: " + (for/list ([c (in-list context)]) + (format "\n ~a" c)))))] + [message + (if (error-print-source-location) + (let ([source-stx (or stx sub-stx within-stx)]) + (string-append (source-location->prefix source-stx) message)) + message)]) + (raise + (exn:fail:syntax message (current-continuation-marks) + (map syntax-taint + (cond [within-stx (list within-stx)] + [sub-stx (list sub-stx)] + [stx (list stx)] + [else null])))))) + +(define (format-if prefix val) + (if val + (format "\n ~a: ~a" prefix val) + "")) + +(define (stx-if-loc stx) + (and (syntax? stx) + (error-print-source-location) + (format "~.s" (syntax->datum stx)))) + +(define (infer-who stx) + (let* ([maybe-id (if (stx-pair? stx) (stx-car stx) stx)]) + (if (identifier? maybe-id) (syntax-e maybe-id) '?))) + +(define (comma-list items) + (join-sep items "," "or")) + +(define (improper-stx->list stx) + (syntax-case stx () + [(a . b) (cons #'a (improper-stx->list #'b))] + [() null] + [rest (list #'rest)])) + + +;; ============================================================ +;; Debugging + +(provide failureset->sexpr + failure->sexpr + expectstack->sexpr + expect->sexpr) + +(define (failureset->sexpr fs) + (let ([fs (flatten fs)]) + (case (length fs) + ((1) (failure->sexpr (car fs))) + (else `(union ,@(map failure->sexpr fs)))))) + +(define (failure->sexpr f) + (match f + [(failure progress expectstack) + `(failure ,(progress->sexpr progress) + #:expected ,(expectstack->sexpr expectstack))])) + +(define (expectstack->sexpr es) + (map expect->sexpr es)) + +(define (expect->sexpr e) e) + +(define (progress->sexpr ps) + (for/list ([pf (in-list ps)]) + (match pf + [(? syntax? stx) 'stx] + [_ pf]))) diff --git a/7-4/racket/collects/syntax/parse/private/runtime.rkt b/7-4/racket/collects/syntax/parse/private/runtime.rkt new file mode 100644 index 0000000..90d7ea8 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/runtime.rkt @@ -0,0 +1,235 @@ +#lang racket/base +(require racket/stxparam + stxparse-info/parse/private/residual ;; keep abs. path + stxparse-info/current-pvars + (for-syntax racket/base + racket/list + syntax/kerncase + syntax/strip-context + racket/private/sc + auto-syntax-e/utils + racket/syntax + syntax/parse/private/rep-data)) + +(provide with + fail-handler + cut-prompt + undo-stack + wrap-user-code + + fail + try + + let-attributes + let-attributes* + let/unpack + + defattrs/unpack + + check-literal + no-shadow + curried-stxclass-parser + app-argu) + +#| +TODO: rename file + +This file contains "runtime" (ie, phase 0) auxiliary *macros* used in +expansion of syntax-parse etc. This file must not contain any +reference that persists in a compiled program; those must go in +residual.rkt. +|# + +;; == with == + +(define-syntax (with stx) + (syntax-case stx () + [(with ([stxparam expr] ...) . body) + (with-syntax ([(var ...) (generate-temporaries #'(stxparam ...))]) + (syntax/loc stx + (let ([var expr] ...) + (syntax-parameterize ((stxparam (make-rename-transformer (quote-syntax var))) + ...) + . body))))])) + +;; == Control information == + +(define-syntax-parameter fail-handler + (lambda (stx) + (wrong-syntax stx "internal error: fail-handler used out of context"))) +(define-syntax-parameter cut-prompt + (lambda (stx) + (wrong-syntax stx "internal error: cut-prompt used out of context"))) +(define-syntax-parameter undo-stack + (lambda (stx) + (wrong-syntax stx "internal error: undo-stack used out of context"))) + +(define-syntax-rule (wrap-user-code e) + (with ([fail-handler #f] + [cut-prompt #t] + [undo-stack null]) + e)) + +(define-syntax-rule (fail fs) + (fail-handler undo-stack fs)) + +(define-syntax (try stx) + (syntax-case stx () + [(try e0 e ...) + (with-syntax ([(re ...) (reverse (syntax->list #'(e ...)))]) + (with-syntax ([(fh ...) (generate-temporaries #'(re ...))]) + (with-syntax ([(next-fh ... last-fh) #'(fail-handler fh ...)]) + #'(let* ([fh (lambda (undos1 fs1) + (with ([fail-handler + (lambda (undos2 fs2) + (unwind-to undos2 undos1) + (next-fh undos1 (cons fs1 fs2)))] + [undo-stack undos1]) + re))] + ...) + (with ([fail-handler + (lambda (undos2 fs2) + (unwind-to undos2 undo-stack) + (last-fh undo-stack fs2))] + [undo-stack undo-stack]) + e0)))))])) + +;; == Attributes + +(define-for-syntax (parse-attr x) + (syntax-case x () + [#s(attr name depth syntax?) #'(name depth syntax?)])) + +(define-syntax (let-attributes stx) + (syntax-case stx () + [(let-attributes ([a value] ...) . body) + (with-syntax ([((name depth syntax?) ...) + (map parse-attr (syntax->list #'(a ...)))]) + (with-syntax ([(vtmp ...) (generate-temporaries #'(name ...))] + [(stmp ...) (generate-temporaries #'(name ...))]) + #'(letrec-syntaxes+values + ([(stmp) (attribute-mapping (quote-syntax vtmp) 'name 'depth + (if 'syntax? #f (quote-syntax check-attr-value)))] + ...) + ([(vtmp) value] ...) + (letrec-syntaxes+values + ([(name) (make-auto-pvar 'depth (quote-syntax stmp))] ...) + () + (with-pvars (name ...) + . body)))))])) + +;; (let-attributes* (([id num] ...) (expr ...)) expr) : expr +;; Special case: empty attrs need not match number of value exprs. +(define-syntax let-attributes* + (syntax-rules () + [(la* (() _) . body) + (let () . body)] + [(la* ((a ...) (val ...)) . body) + (let-attributes ([a val] ...) . body)])) + +;; (let/unpack (([id num] ...) expr) expr) : expr +;; Special case: empty attrs need not match packed length +(define-syntax (let/unpack stx) + (syntax-case stx () + [(let/unpack (() packed) body) + #'body] + [(let/unpack ((a ...) packed) body) + (with-syntax ([(tmp ...) (generate-temporaries #'(a ...))]) + #'(let-values ([(tmp ...) (apply values packed)]) + (let-attributes ([a tmp] ...) body)))])) + +(define-syntax (defattrs/unpack stx) + (syntax-case stx () + [(defattrs (a ...) packed) + (with-syntax ([((name depth syntax?) ...) + (map parse-attr (syntax->list #'(a ...)))]) + (with-syntax ([(vtmp ...) (generate-temporaries #'(name ...))] + [(stmp ...) (generate-temporaries #'(name ...))]) + #'(begin (define-values (vtmp ...) (apply values packed)) + (define-syntax stmp + (attribute-mapping (quote-syntax vtmp) 'name 'depth + (if 'syntax? #f (quote-syntax check-attr-value)))) + ... + (define-syntax name (make-auto-pvar 'depth (quote-syntax stmp))) + ... + (define-pvars name ...))))])) + +(define-syntax-rule (phase-of-enclosing-module) + (variable-reference->module-base-phase + (#%variable-reference))) + +;; (check-literal id phase-level-expr ctx) -> void +(define-syntax (check-literal stx) + (syntax-case stx () + [(check-literal id used-phase-expr ctx) + (let* ([ok-phases/ct-rel + ;; id is bound at each of ok-phases/ct-rel + ;; (phase relative to the compilation of the module in which the + ;; 'syntax-parse' (or related) form occurs) + (filter (lambda (p) (identifier-binding #'id p)) '(0 1 -1 #f))]) + ;; so we can avoid run-time call to identifier-binding if + ;; (+ (phase-of-enclosing-module) ok-phase/ct-rel) = used-phase + (with-syntax ([ok-phases/ct-rel ok-phases/ct-rel]) + #`(check-literal* (quote-syntax id) + used-phase-expr + (phase-of-enclosing-module) + 'ok-phases/ct-rel + ;; If context is not stripped, racket complains about + ;; being unable to restore bindings for compiled code; + ;; and all we want is the srcloc, etc. + (quote-syntax #,(strip-context #'ctx)))))])) + +;; ==== + +(begin-for-syntax + (define (check-shadow def) + (syntax-case def () + [(_def (x ...) . _) + (parameterize ((current-syntax-context def)) + (for ([x (in-list (syntax->list #'(x ...)))]) + (let ([v (syntax-local-value x (lambda _ #f))]) + (when (syntax-pattern-variable? v) + (wrong-syntax + x + ;; FIXME: customize "~do pattern" vs "#:do block" as appropriate + "definition in ~~do pattern must not shadow attribute binding")))))]))) + +(define-syntax (no-shadow stx) + (syntax-case stx () + [(no-shadow e) + (let ([ee (local-expand #'e (syntax-local-context) + (kernel-form-identifier-list))]) + (syntax-case ee (begin define-values define-syntaxes) + [(begin d ...) + #'(begin (no-shadow d) ...)] + [(define-values . _) + (begin (check-shadow ee) + ee)] + [(define-syntaxes . _) + (begin (check-shadow ee) + ee)] + [_ + ee]))])) + +(define-syntax (curried-stxclass-parser stx) + (syntax-case stx () + [(_ class argu) + (with-syntax ([#s(arguments (parg ...) (kw ...) _) #'argu]) + (let ([sc (get-stxclass/check-arity #'class #'class + (length (syntax->list #'(parg ...))) + (syntax->datum #'(kw ...)))]) + (with-syntax ([parser (stxclass-parser sc)]) + #'(lambda (x cx pr es undos fh cp rl success) + (app-argu parser x cx pr es undos fh cp rl success argu)))))])) + +(define-syntax (app-argu stx) + (syntax-case stx () + [(aa proc extra-parg ... #s(arguments (parg ...) (kw ...) (kwarg ...))) + #| + Use keyword-apply directly? + #'(keyword-apply proc '(kw ...) (list kwarg ...) parg ... null) + If so, create separate no-keyword clause. + |# + ;; For now, let #%app handle it. + (with-syntax ([((kw-part ...) ...) #'((kw kwarg) ...)]) + #'(proc kw-part ... ... extra-parg ... parg ...))])) diff --git a/7-4/racket/collects/syntax/parse/private/sc.rkt b/7-4/racket/collects/syntax/parse/private/sc.rkt new file mode 100644 index 0000000..d4e5ff6 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/sc.rkt @@ -0,0 +1,32 @@ +#lang racket/base +(require racket/lazy-require + syntax/parse/private/keywords + "residual.rkt") + +(lazy-require-syntax + ["parse.rkt" + (define-syntax-class + define-splicing-syntax-class + define-integrable-syntax-class + syntax-parse + syntax-parser + define/syntax-parse + syntax-parser/template + define-eh-alternative-set)]) + +(provide define-syntax-class + define-splicing-syntax-class + define-integrable-syntax-class + syntax-parse + syntax-parser + define/syntax-parse + + (except-out (all-from-out syntax/parse/private/keywords) + ~reflect + ~splicing-reflect + ~eh-var) + attribute + this-syntax + + syntax-parser/template + define-eh-alternative-set) diff --git a/7-4/racket/collects/syntax/parse/private/txlift.rkt b/7-4/racket/collects/syntax/parse/private/txlift.rkt new file mode 100644 index 0000000..57c5497 --- /dev/null +++ b/7-4/racket/collects/syntax/parse/private/txlift.rkt @@ -0,0 +1,45 @@ +#lang racket/base +(require (for-template racket/base)) +(provide txlift + get-txlifts-as-definitions + with-txlifts + call/txlifts) + +;; Like lifting definitions, but within a single transformer. + +;; current-liftbox : Parameter of [#f or (Listof (list Id Stx))] +(define current-liftbox (make-parameter #f)) + +(define (call/txlifts proc) + (parameterize ((current-liftbox (box null))) + (proc))) + +(define (txlift expr) + (let ([liftbox (current-liftbox)]) + (check 'txlift liftbox) + (let ([var (car (generate-temporaries '(txlift)))]) + (set-box! liftbox (cons (list var expr) (unbox liftbox))) + var))) + +(define (get-txlifts) + (let ([liftbox (current-liftbox)]) + (check 'get-txlifts liftbox) + (reverse (unbox liftbox)))) + +(define (get-txlifts-as-definitions) + (let ([liftbox (current-liftbox)]) + (check 'get-txlifts-as-definitions liftbox) + (map (lambda (p) + #`(define #,@p)) + (reverse (unbox liftbox))))) + +(define (check who lb) + (unless (box? lb) + (error who "not in a txlift-catching context"))) + +(define (with-txlifts proc) + (call/txlifts + (lambda () + (let ([v (proc)]) + (with-syntax ([((var rhs) ...) (get-txlifts)]) + #`(let* ([var rhs] ...) #,v)))))) diff --git a/7-4/stxparse-info.scrbl b/7-4/stxparse-info.scrbl new file mode 100644 index 0000000..8cd3733 --- /dev/null +++ b/7-4/stxparse-info.scrbl @@ -0,0 +1,357 @@ +#lang scribble/manual +@require[racket/require + @for-label[stxparse-info/parse + stxparse-info/parse/experimental/template + stxparse-info/case + stxparse-info/current-pvars + (subtract-in racket/syntax stxparse-info/case) + (subtract-in racket/base stxparse-info/case)] + version-case + @for-syntax[racket/base] + "ovl.rkt"] + +@; Circumvent https://github.com/racket/scribble/issues/79 +@(require scribble/struct + scribble/decode) +@(define (nested-inset . vs) + (nested #:style 'inset vs)) + +@(version-case + [(version< (version) "6.4") + ] + [else + (require scribble/example) + (define ev ((make-eval-factory '(racket))))]) + +@title{@racketmodname[stxparse-info]: Track @racket[syntax-parse] and @racket[syntax-case] pattern vars} +@author[@author+email["Suzanne Soy" "racket@suzanne.soy"]] + +Source code: @url{https://github.com/jsmaniac/stxparse-info} + +@defmodule[stxparse-info] + +This library provides some patched versions of @orig:syntax-parse and of the +@orig:syntax-case family. These patched versions track which syntax pattern +variables are bound. This allows some libraries to change the way syntax +pattern variables work. + +For example, @tt{subtemplate} automatically derives temporary +identifiers when a template contains @racket[yᵢ …], and @racket[xᵢ] is a +pattern variable. To know from which @racket[varᵢ] the @racket[yᵢ …] +identifiers must be derived, @tt{subtemplate} needs to know which +syntax pattern variables are within scope. + +@section{Tracking currently-bound pattern variables with @racket[syntax-parse]} + +@defmodule[stxparse-info/parse] + +The module @racketmodname[stxparse-info/parse] provides patched versions of +@orig:syntax-parse, @orig:syntax-parser and @orig:define/syntax-parse which +track which syntax pattern variables are bound. + +@(ovl syntax/parse + syntax-parse + syntax-parser + define/syntax-parse) + +Additionally, the following identifiers are overridden as they are part of the +duplicated implementation of @racketmodname[syntax/parse]. + +@(ovl #:wrapper nested-inset + syntax/parse + ...+ + attribute + boolean + char + character + define-conventions + define-eh-alternative-set + define-literal-set + define-splicing-syntax-class + define-syntax-class + exact-integer + exact-nonnegative-integer + exact-positive-integer + expr + expr/c + id + identifier + integer + kernel-literals + keyword + literal-set->predicate + nat + number + pattern + prop:syntax-class + static + str + syntax-parse-state-cons! + syntax-parse-state-ref + syntax-parse-state-set! + syntax-parse-state-update! + syntax-parse-track-literals + this-syntax + ~! + ~and + ~between + ~bind + ~commit + ~datum + ~delimit-cut + ~describe + ~do + ~fail + ~literal + ~not + ~once + ~optional + ~or + ~parse + ~peek + ~peek-not + ~post + ~rest + ~seq + ~undo + ~var) + +@(version-case + [(version>= (version) "6.9.0.6") + (ovl #:wrapper nested-inset + syntax/parse + ~alt + ~or*)] + [else (begin)]) + +@(ovl #:wrapper nested-inset + #:require (for-template syntax/parse) + syntax/parse + pattern-expander? + pattern-expander + prop:pattern-expander + syntax-local-syntax-parse-pattern-introduce) + +@section{Tracking currently-bound pattern variables with @racket[syntax-case]} + +@defmodule[stxparse-info/case] + +The module @racketmodname[stxparse-info/case] provides patched versions of +@orig:syntax-case, @orig:syntax-case*, @orig:with-syntax, +@orig:define/with-syntax, @orig:datum-case and @orig:with-datum which +track which syntax or datum pattern variables are bound. + +@(ovl racket/base + syntax-case + syntax-case* + with-syntax) + +@(ovl syntax/datum + datum-case + with-datum) + +@(ovl racket/syntax + define/with-syntax) + +@section{Reading and updating the list of currently-bound pattern variables} + +@defmodule[stxparse-info/current-pvars] + +@defproc[#:kind "procedure at phase 1" + (current-pvars) (listof identifier?)]{ + This for-syntax procedure returns the list of syntax pattern variables which + are known to be bound. The most recently bound variables are at the beginning + of the list. + + It is the responsibility of the reader to check that the identifiers are + bound, and that they are bound to syntax pattern variables, for example using + @racket[identifier-binding] and @racket[syntax-pattern-variable?]. This allows + libraries to also track variables bound by match-like forms, for example.} + +@defproc[#:kind "procedure at phase 1" + (current-pvars+unique) (listof (pairof identifier? identifier?))]{ + This for-syntax procedure works like @racket[current-pvars], but associates + each syntax pattern variable with an identifier containing a unique symbol + which is generated at each execution of the code recording the pattern + variable via @racket[with-pvars] or @racket[define-pvars]. + + The @racket[car] of each pair in the returned list is the syntax pattern + variable (as produced by @racket[current-pvars]). It is the responsibility of + the reader to check that the identifiers present in the @racket[car] of each + element of the returned list are bound, and that they are bound to syntax + pattern variables, for example using @racket[identifier-binding] and + @racket[syntax-pattern-variable?]. This allows libraries to also track + variables bound by match-like forms, for example. + + The @racket[cdr] of each pair is the identifier of a temporary variable. + Reading that temporary variable produces a @racket[gensym]-ed symbol, which + was generated at run-time at the point where @racket[with-pvars] or + @racket[define-pvars] was used to record the corresponding pattern variable. + + This can be used to associate run-time data with each syntax pattern + variable, via a weak hash table created with @racket[make-weak-hasheq]. For + example, the @tt{subtemplate} library implicitly derives + identifiers (similarly to @racket[generate-temporaries]) for uses of + @racket[yᵢ ...] from a @racket[xᵢ] pattern variable bearing the same + subscript. The generated identifiers are associated with @racket[xᵢ] via this + weak hash table mechanism, so that two uses of @racket[yᵢ ...] within the + scope of the same @racket[xᵢ] binding derive the same identifiers. + + The code @racket[(with-pvars (v) body)] roughly expands to: + + @racketblock[ + (let-values ([(tmp) (gensym 'v)]) + (letrec-syntaxes+values ([(shadow-current-pvars) + (list* (cons (quote-syntax v) + (quote-syntax tmp)) + old-current-pvars)]) + body))] + + @bold{Caveat:} this entails that the fresh symbol stored in @racket[tmp] is + generated when @racket[with-pvars] or @racket[define-pvars] is called, not + when the syntax pattern variable is actually bound. For example: + + @RACKETBLOCK[ + (define-syntax (get-current-pvars+unique stx) + #`'#,(current-pvars+unique)) + + (require racket/private/sc) + (let ([my-valvar (quote-syntax x)]) + (let-syntax ([my-pvar (make-syntax-mapping 0 (quote-syntax my-valvar))]) + (with-pvars (x) + (get-current-pvars+unique)) (code:comment "'([x . g123])") + (with-pvars (x) + (get-current-pvars+unique)))) (code:comment "'([x . g124])")] + + Under normal circumstances, @racket[with-pvars] @racket[define-pvars] should + be called immediately after binding the syntax pattern variable, but the code + above shows that it is technically possible to do otherwise. + + This caveat is not meant to dissuade the use of + @racket[current-pvars+unique], it rather serves as an explanation of the + behaviour encountered when @racket[with-pvars] or @racket[define-pvars] are + incorrectly used more than once to record the same pattern variable.} + +@defform[(with-pvars (pvar ...) . body) + #:contracts ([pvar identifier?])]{ + Prepends the given @racket[pvar ...] to the list of pattern variables which + are known to be bound. The @racket[pvar ...] are prepended in reverse order, + so within the body of + + @racketblock[(with-pvars (v₁ v₂ v₃) . body)] + + a call to the for-syntax function @racket[(current-pvars)] returns: + + @racketblock[(list* (quote-syntax v₃) (quote-syntax v₂) (quote-syntax v₁) + old-current-pvars)] + + This can be used to implement macros which work similarly to + @racket[syntax-parse] or @racket[syntax-case], and have them record the syntax + pattern variables which they bind. + + Note that the identifiers @racket[pvar ...] must already be bound to syntax + pattern variables when @racket[with-pvars] is used, e.g. + + @racketblock[ + (let-syntax ([v₁ (make-syntax-mapping depth (quote-syntax valvar))] + [v₂ (make-syntax-mapping depth (quote-syntax valvar))]) + (with-pvars (v₁ v₂) + code))] + + instead of: + + @racketblock[ + (with-pvars (v₁ v₂) + (let-syntax ([v₁ (make-syntax-mapping depth (quote-syntax valvar))] + [v₂ (make-syntax-mapping depth (quote-syntax valvar))]) + code))]} + +@defform[(define-pvars pvar ...) + #:contracts ([pvar identifier?])]{ + + Prepends the given @racket[pvar ...] to the list of pattern variables which + are known to be bound, in the same way as @racket[with-pvars]. Whereas + @racket[with-pvars] makes the modified list visible in the @racket[_body], + @racket[define-pvars] makes the modified list visible in the statements + following @racket[define-pvars]. @racket[define-pvars] can be used multiple + times within the same @racket[let] or equivalent. + + This can be used to implement macros which work similarly to + @racket[define/syntax-parse] or @racket[define/with-syntax], and have them + record the syntax pattern variables which they bind. + + @(version-case + [(version< (version) "6.4") + @RACKETBLOCK[ + (let () + (code:comment "Alternate version of define/syntax-parse which") + (code:comment "contains (define-pvars x) in its expanded form.") + (define/syntax-parse x #'1) + (define/syntax-parse y #'2) + (define-syntax (get-pvars stx) + #`'#,(current-pvars)) + (get-pvars)) + (code:comment "=> '(y x)")]] + [else + @examples[ + #:eval ev + #:hidden + (require stxparse-info/parse + stxparse-info/current-pvars + racket/syntax + (for-syntax racket/base))] + + @examples[ + #:eval ev + #:escape UNSYNTAX + (eval:check + (let () + (code:comment "Alternate version of define/syntax-parse which") + (code:comment "contains (define-pvars x) in its expanded form.") + (define/syntax-parse x #'1) + (define/syntax-parse y #'2) + (define-syntax (get-pvars stx) + #`'#,(current-pvars)) + (get-pvars)) + '(y x))]])} + +@section{Extensions to @racketmodname[syntax/parse/experimental/template]} + +@defmodule[stxparse-info/parse/experimental/template] + +@(orig syntax/parse/experimental/template + define-template-metafunction) + +@defidform[define-template-metafunction]{ + Overloaded version of @orig:define-template-metafunction from + @racketmodname[syntax/parse/experimental/template]. + + Note that currently, template metafunctions defined via + @racketmodname[stxparse-info/parse/experimental/template] are not compatible + with the forms from @racketmodname[syntax/parse/experimental/template], and + vice versa. There is a pending Pull Request which would make the necessary + primitives from @racketmodname[syntax/parse/experimental/template] public, so + hopefully this problem will be solved in future versions.} + +@defform[(syntax-local-template-metafunction-introduce stx)]{ + Like @racket[syntax-local-introduce], but for + @tech[#:doc '(lib "syntax/scribblings/syntax.scrbl")]{template metafunctions}. + + This change is also available in the package + @racketmodname{backport-template-pr1514}. It has been submitted as a Pull + Request to Racket, but can already be used in + @racketmodname[stxparse-info/parse/experimental/template] right now.} + +@(ovl syntax/parse/experimental/template + template + quasitemplate + template/loc + quasitemplate/loc) + +Additionally, the following identifiers are overridden as they are part of the +duplicated implementation of @racketmodname[syntax/parse]. + +@(ovl #:wrapper nested-inset + syntax/parse/experimental/template + ?? + ?@) diff --git a/info.rkt b/info.rkt index 2376213..61b929d 100644 --- a/info.rkt +++ b/info.rkt @@ -11,8 +11,8 @@ "racket-doc" "at-exp-lib")) ;; for the documentation only (define scribblings '(("scribblings/stxparse-info.scrbl" () ("Syntax Extensions")))) -(define compile-omit-paths '("6-11" "6-12" "6-90-0-29" "7-0-0-20" "7-3-0-1" "8-0")) -(define test-omit-paths '("6-11" "6-12" "6-90-0-29" "7-0-0-20" "7-3-0-1" "8-0")) +(define compile-omit-paths '("6-11" "6-12" "6-90-0-29" "7-0-0-20" "7-3-0-1" "7-4" "8-0")) +(define test-omit-paths '("6-11" "6-12" "6-90-0-29" "7-0-0-20" "7-3-0-1" "7-4" "8-0")) (define pkg-desc "Description Here") (define version "0.0") (define pkg-authors '(Suzanne Soy)) diff --git a/my-include.rkt b/my-include.rkt index 183487d..459c897 100644 --- a/my-include.rkt +++ b/my-include.rkt @@ -42,7 +42,9 @@ #,(my-include1 (loc (string-append -updir "6-90-0-29" -filename)))] [(version< (version) "7.3.0.1") #,(my-include1 (loc (string-append -updir "7-0-0-20" -filename)))] - [(version< (version) "7.4") - #,(my-include1 (loc (string-append -updir "7-3-0-1" -filename)))] + ;[(version< (version) "7.4") + ; #,(my-include1 (loc (string-append -updir "7-3-0-1" -filename)))] + [(version< (version) "7.5") + #,(my-include1 (loc (string-append -updir "7-4" -filename)))] [else #,(my-include1 (loc (string-append -updir "8-0" -filename)))]))]))