www

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

commit 03e284cde1b6a3c10d01404ef626905defd55840
parent 6db01589c69a99f9e1b687484752d8788d0095db
Author: Georges Dupéron <georges.duperon@gmail.com>
Date:   Sat, 24 Sep 2016 21:29:54 +0200

Implemented split-xlist.rkt. Broke the old match expander for xlist by fixing a bug. The old match expander can't be made to work with constant repetitions like ^ 3.

Diffstat:
Mmain.rkt | 198++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mscribblings/xlist.scrbl | 9++++++++-
Asplit-xlist.rkt | 121+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/test-match.rkt | 9+++++++++
4 files changed, 288 insertions(+), 49 deletions(-)

diff --git a/main.rkt b/main.rkt @@ -10,7 +10,7 @@ "between.rkt" match-string racket/match - (only-in phc-toolkit/typed-untyped when-typed) + (only-in phc-toolkit/typed-untyped if-typed when-typed) (only-in syntax/parse ...+) (for-syntax (rename-in racket/base [* mul] @@ -28,11 +28,12 @@ syntax/parse/experimental/template (subtract-in syntax/stx phc-toolkit/untyped) type-expander/expander - phc-toolkit/untyped) + phc-toolkit/untyped + racket/pretty) (for-meta 2 racket/base) (for-meta 2 syntax/parse)) - (provide xlist ^ ∞) + (provide xlist ^ ∞ (for-syntax normalize-xlist-type)) (define-syntax stop (λ (stx) (raise-syntax-error 'stop "This is a private marker" stx))) @@ -43,8 +44,8 @@ (λ (stx) (syntax-case stx () [(_ pat ...) - #`{~or {~seq #,(syntax-local-introduce #'^) pat ...} - {~seq {~optional #,(syntax-local-introduce #'^)} + #`{~or {~seq {~literal #,(syntax-local-introduce #'^)} pat ...} + {~seq {~optional {~literal #,(syntax-local-introduce #'^)}} (pat ...)}}])))) (define number/rx #px"^(.*?)([⁰¹²³⁴⁵⁶⁷⁸⁹]+)$") @@ -179,42 +180,85 @@ (xl #'rest)] [(s:with-superscripts . rest) (xl #'(s.expanded … . rest))] - [(:base {~optional ^} *) + [(:base {~or * {~^ *}}) #'(Listof base)] - [(:base {~optional ^} * . rest) + [(:base {~or * {~^ *}} . rest) #:with R (gensym 'R) #`(Rec R (U (Pairof base R) #,(xl #'rest)))] - [(:base {~optional ^} + . rest) + [(:base {~or + {~^ +}} . rest) (xl #'(base ^ 1 + . rest))] - [(:base ^ power:nat + . rest) + [(:base {~^ power:nat +} . rest) (xl #'(base ^ power stop base * . rest))] - [(:base ^ - . rest) - (xl #'(base ^ 0 - . rest))] - [(:base ^ from:nat - ∞ . rest) + [(:base {~^ -} . rest) ;; TODO: if there was ^ {-}, then it should keep the braces because the next thing may be a number + (xl #'(base {~^ 0 -} . rest))] + [(:base {~^ from:nat - ∞} . rest) (xl #'(base ^ from + . rest))] - [(:base ^ 0 - to:nat . rest) + [(:base {~^ 0 - to:nat} . rest) #`(U . #,(foldl (λ (iteration u*) (syntax-case u* () [[(_ . base…rest) . _] #`[(List* base . base…rest) . #,u*]])) #`[(List* #,(xl #'rest))] (range (syntax-e #'to))))] - [(:base ^ from:nat - to:nat . rest) + [(:base {~^ from:nat - to:nat} . rest) #:with difference (- (syntax-e #'to) (syntax-e #'from)) (when (< (syntax-e #'difference) 0) (raise-syntax-error 'xlist "invalid range: m is larger than n" #'-)) (xl #'(base ^ from stop base ^ 0 - difference . rest))] - [(:base ^ from:nat - . rest) + [(:base {~^ from:nat -} . rest) ;; "-" is not followed by a number, nor by ∞, so default to ∞. (xl #'(base ^ from - ∞ . rest))] [(e:fixed-repeat . rest) #`(List* e.expanded … #,(xl #'rest))])) (xl stx)) - + ;; normalize the xlist type + ;; The normalized form has one type followed by ^ followed by a repeat + ;; within braces (possibly {1}) for each position in the original type. It + ;; always finishes with #:rest rest-type + + (define (normalize-xlist-type stx context) + (define nt + (syntax-parser + #:context context + #:literals (^ * + - ∞) + [() + #'(#:rest Null)] + [rest:not-stx-pair + #'(#:rest rest)] + [(#:rest rest) + #'(#:rest rest)] + [(s:with-superscripts . rest) + (nt #'(s.expanded … . rest))] + [(:base {~or * {~^ *}} . rest) + #`(base ^ {*} . #,(nt #'rest))] + [(:base {~or + {~^ +}} . rest) + #`(base ^ {1 +} . #,(nt #'rest))] + [(:base {~^ 0 +} . rest) + #`(base ^ {*} . #,(nt #'rest))] + [(:base {~^ power:nat +} . rest) + #`(base ^ {power +} . #,(nt #'rest))] + [(:base {~optional ^} {-} . rest) + #`(base ^ {*} . #,(nt #'rest))] + [(:base ^ - . rest) ;; not with {}, check if there's stuff after + (nt #'(base ^ 0 - . rest))] + [(:base {~^ 0 - ∞} . rest) + #`(base ^ {*} . #,(nt #'rest))] + [(:base {~^ from:nat - ∞} . rest) + (nt #'(base ^ from + . rest))] + [(:base {~^ from:nat - to:nat} . rest) + #`(base ^ {from - to} . #,(nt #'rest))] + [(:base {~^ from:nat -} . rest) + ;; "-" is not followed by a number, nor by ∞, so default to ∞. + (nt #'(base ^ from - ∞ . rest))] + [(:base {~^ power:nat}) + #`(base ^ {power} . #,(nt #'rest))] + [(:base . rest) + #`(base ^ {1} . #,(nt #'rest))])) + (nt stx)) @@ -232,27 +276,27 @@ (define xl (syntax-parser #:context context - #:literals (^ * + - ∞ stop) + #:literals (^ * + - ∞) [() - #'(list)] + #'(#:proper-list)] [rest:not-stx-pair - #'rest] + #'(#:rest rest)] + [(#:rest ((~literal ?) (~literal null?))) + #'(#:proper-list)] [(#:rest rest) - #'rest] - [(stop . rest) ;; eliminate the private marker - (xl #'rest)] + #'(#:rest rest)] [(({~literal unquote-splicing} splice) …+ . rest) - #`(append splice … #,(xl #'rest))] + #`(#:rest (append splice … #,(xl #'rest)))] [(s:with-superscripts . rest) (xl #'(s.expanded … . rest))] [(:base {~optional ^} * . rest) #:with R (gensym 'R) - #`(list-rest-ish [] base ooo #,(xl #'rest))] + #`([[] base ooo] . #,(xl #'rest))] [(:base {~optional ^} + . rest) (xl #'(base ^ 1 + . rest))] [(:base ^ power:nat + . rest) #:with ..power (format-id #'power "..~a" (syntax-e #'power)) - #`(list-rest-ish [] base ..power #,(xl #'rest))] + #`([[] base ..power] . #,(xl #'rest))] [(:base ^ - . rest) (xl #'(base ^ 0 - . rest))] [(:base ^ from:nat - ∞ . rest) @@ -262,24 +306,34 @@ (raise-syntax-error 'xlist "invalid range: m is larger than n" #'-)) - #`(list-rest-ish - [(? (λ (_) ((between/c from to) (length occurrences))))] - (and occurrences base) ooo #,(xl #'rest))] + (define/with-syntax occurrences (gensym 'occurrences)) + #`([[((between/c from to) (length occurrences))] + (and occurrences base) + ooo] + . #,(xl #'rest))] [(:base ^ from:nat - . rest) ;; "-" is not followed by a number, nor by ∞, so default to ∞. (xl #'(base ^ from - ∞ . rest))] ;; aliases [(:base {~or {~literal ...} {~literal ___}} . rest) - #`(list-rest-ish [] base ooo #,(xl #'rest))] + #`([[] base ooo] . #,(xl #'rest))] [(:base {~literal ...+} . rest) - #`(list-rest-ish base ..1 #,(xl #'rest))] + #`([[] base ..1] . #,(xl #'rest))] [(:base ellipsis:id . rest) #:when (regexp-match? #px"^\\.\\.[0-9]+$" (symbol->string (syntax-e #'ellipsis))) - #`(list-rest-ish [] base ellipsis #,(xl #'rest))] - [(e:fixed-repeat . rest) - #`(list-rest-ish [] e.expanded … #,(xl #'rest))])) - (xl stx)) + #`([[] base ellipsis] . #,(xl #'rest))] + [(:base {~^ 1}) + #`([[] base #|no ellipsis|#] . #,(xl #'rest))] + [(:base {~^ power:nat}) + (define/with-syntax occurrences (gensym 'occurrences)) + #`([[(= (length occurrences) power)] + (and occurrences base) ooo] + . #,(xl #'rest))] + [(:base . rest) + #`([[] base #|no ellipsis|#] . #,(xl #'rest))])) + ((λ (x) (pretty-write (syntax->datum x)) x) + #`(list-rest-ish . #,(xl stx)))) #;("This is completely wrong" ;; Expands 0 or more mandatory-doms for ->* @@ -392,19 +446,67 @@ #:match-expander (λ (stx) ((xlist-match stx) (stx-cdr stx))) #;{#:call (λ (stx) ((xlist-builder stx) (stx-cdr stx)))}) - (define-match-expander list-rest-ish - (syntax-parser - #:literals (list list-rest-ish) - #:datum-literals (list-rest) - [(_ [c₁ …] e₁ … (list-rest-ish [c₂ …] e₂ … r)) - #'(list-rest-ish [c₁ … c₂ …] e₁ … e₂ … r)] - [(_ [c₁ …] e₁ … (list-rest e₂ … r)) - #'(list-rest-ish [c₁ …] e₁ … e₂ … r)] - [(_ [c₁ …] e₁ … (list e₂ …)) - #'(and (list e₁ … e₂ …) c₁ …)] - [(_ [c₁ …] e₁ … r) - #'(and (list-rest e₁ … r) - c₁ …)])) + (if-typed + (begin + ;(require "split-list.rkt") + (define-match-expander list-rest-ish + (λ (stx) + ((λ (x) (pretty-write (syntax->datum x)) x) + ((syntax-parser + #:literals (list list-rest-ish) + ;#:datum-literals (list-rest) + ;[(_ [c₁ …] e₁ … (list-rest e₂ … r)) + ; #'(list-rest-ish [c₁ …] e₁ … e₂ … r)] + ;[(_ [c₁ …] e₁ … (list e₂ …)) + ; #'(list-rest-ish [c₁ …] e₁ … e₂ …)] + [(_ [[c₁ …] e₁ ooo₁ …] … #:proper-list) + #:with whole (gensym 'whole) + #'TODO + #;#'(? (λ (whole) + (match whole + [(list e₁ … e₂ …) (and c₁ …)]))) + #;#'(and (list e₁ … e₂ …) c₁ …)] + [(_ [[c₁ …] e₁ ooo₁ …] … #:rest r) + #'TODO + #;#'(and (list-rest e₁ … r) + c₁ …)]) + stx))))) + (define-match-expander list-rest-ish + (λ (stx) + ((λ (x) (pretty-write (syntax->datum x)) x) + ((syntax-parser + #:literals (list list-rest-ish) + ;#:datum-literals (list-rest) + ;[(_ [c₁ …] e₁ … (list-rest e₂ … r)) + ; #'(list-rest-ish [c₁ …] e₁ … e₂ … r)] + ;[(_ [c₁ …] e₁ … (list e₂ …)) + ; #'(list-rest-ish [c₁ …] e₁ … e₂ …)] + [(_ [[c₁ …] e₁ {~optional ooo₁}] … #:proper-list) + (define-temp-ids "~a/copy" (e₁ …)) + ;; like below, but without the r pattern + (template + (app (λ (l) + (match l + [(list (?@ (and e₁ e₁/copy) (?? ooo₁)) …) + #:when (and c₁ … …) + (list e₁/copy …)] + [_ #f])) + (list (?? (list e₁ ooo₁) e₁) + …)))] + [(_ [[c₁ …] e₁ {~optional ooo₁}] … #:rest r) + (define-temp-ids "~a/copy" (e₁ …)) + (template + (app (λ (l) + (match l + [(list-rest (?@ (and e₁ e₁/copy) (?? ooo₁)) … (and the-r r)) + #:when (and c₁ … …) + (list e₁/copy … the-r)])) + (list (?? (list e₁ ooo₁) e₁) + … + r))) + #;#'(and (list-rest e₁ … r) + c₁ …)]) + stx))))) (when-typed (provide xList #;xListBuilder) @@ -412,4 +514,4 @@ ((xlist-type stx) (stx-cdr stx))) #;(define-type-expander (xListBuilder stx) - ((xlist-builder-type stx) (stx-cdr stx))))) + ((xlist-builder-type stx) (stx-cdr stx))))) diff --git a/scribblings/xlist.scrbl b/scribblings/xlist.scrbl @@ -132,7 +132,7 @@ To use the type expander, you must first require the #:literals (^ *) [(xlist patᵢ ...) (xlist patᵢ ... . rest) - (xList patᵢ ... #:rest . rest)] + (xlist patᵢ ... #:rest . rest)] #:grammar [(patᵢ pattern-or-spliced repeated-pattern @@ -267,5 +267,12 @@ To use the type expander, you must first require the are some function types that Typed/Racket cannot express (yet).} } +@defproc[(normalize-xlist-type [stx syntax?] [context syntax?]) syntax?]{ + Normalizes the xlist type. The normalized form has one type followed by ^ + followed by a repeat within braces (possibly {1}) for each position in the + original type. It always finishes with #:rest rest-type. This function also + performs a few simplifications on the type, like transforming @racket[^ {3 -}] + into @racket[^ {3 +}], and transforming @racket[^ {0 -}] into @racket[^ {*}].} + @include-section{xlist-untyped.scrbl} @include-section{identifiers.scrbl} \ No newline at end of file diff --git a/split-xlist.rkt b/split-xlist.rkt @@ -0,0 +1,120 @@ +#lang typed/racket + +(require (for-syntax phc-toolkit/untyped + syntax/parse + syntax/parse/experimental/template) + xlist + "caret-identifier.rkt" + type-expander) + +(provide f-split-list) + +(: f-split-list (∀ (A B) (→ (→ Any Boolean : B) + (→ (Rec R (U (Pairof A R) B)) + (List (Listof A) + B))))) +(define (f-split-list pred-b?) + (: recur (→ (Rec R (U (Pairof A R) B)) + (List (Listof A) + B))) + (define (recur l) + (if (null? l) + (list '() (ann l B)) + (if (pred-b? l) + (list '() l) + (let ([split-rest (recur (cdr l))]) + (cons (cons (car l) + (car split-rest)) + (cdr split-rest))) + ))) + recur) + +(define-syntax-rule (m-split-list v (xlist τ₁ ^ *₁ . whole-τ-rest)) + (((inst f-split-list τ₁ (xlist . whole-τ-rest)) + ;; TODO: could drop the tail type after the first mandatory repeat + ;; Not sure if that would make it possible to typecheck more easily + ;; though, as the rest of the type will be used to split the rest + ;; anyway. + (make-predicate (xlist . whole-τ-rest))) + v)) + +(module+ test + (require phc-toolkit) + + (check-equal?: + (((inst f-split-list Number (Listof Symbol)) + (make-predicate (Listof Symbol))) '(1 2 3 a b)) + : (List (Listof Number) + (Listof Symbol)) + '((1 2 3) (a b)))) + +(define-syntax m-split-xlist* + (syntax-parser + #:literals (^) + [(_ v [v₁ vᵢ …] {~seq τ₁ ^ *₁} {~seq τᵢ ^ *ᵢ} … #:rest r) + ((λ (x) #;(displayln x) x) + (template + (begin + (define split (m-split-list v (xlist τ₁ ^ *₁ (?@ τᵢ ^ *ᵢ) … #:rest r))) + (define v₁ (car split)) + (m-split-xlist* (cadr split) [vᵢ …] (?@ τᵢ ^ *ᵢ) … #:rest r))))] + [(_ v [vr] #:rest r) + #'(define vr v)])) + +(module+ test + (require phc-toolkit) + (check-equal?: + (let () + (m-split-xlist* '(1 2 3 d e f 7 8 9 . 42) + [n1 s n2 r] + Number ^ {*} + Symbol ^ {*} + Number ^ {*} + #:rest Number) + (list n1 s n2 r)) + : (List (Listof Number) + (Listof Symbol) + (Listof Number) + Number) + '((1 2 3) (d e f) (7 8 9) 42)) + + (check-equal?: + (let () + (m-split-xlist* '(1 2 3 d e f 7 8 9) + [n1 s n2 nul] + Number ^ {*} + Symbol ^ {*} + Number ^ {*} + #:rest Null) + (list n1 s n2 nul)) + : (List (Listof Number) + (Listof Symbol) + (Listof Number) + Null) + '((1 2 3) (d e f) (7 8 9) ()))) + +(define-match-expander split-xlist + (syntax-parser + #:literals (^) + [(_ pat . whole-τ) + #:with ({~seq normalized-τᵢ ^ normalized-*ᵢ} … #:rest τ-rest) + (normalize-xlist-type #'whole-τ this-syntax) + + (define-temp-ids "~a/v" (normalized-τᵢ …)) + ((λ (x) (displayln x) x) + (template + (app (λ (l) + (m-split-xlist* l + [normalized-τᵢ/v … rest/v] + (?@ normalized-τᵢ ^ normalized-*ᵢ) … + #:rest τ-rest) + (list normalized-τᵢ/v … rest/v)) + pat)))])) + +(module+ test + (check-equal?: + (match '(1 2 3 d e f 7 8 9) + [(split-xlist (list a b c d) Number⃰ Symbol⃰ Number⃰) + (list d c b a)]) + : (List Null (Listof Number) (Listof Symbol) (Listof Number)) + '(() (7 8 9) (d e f) (1 2 3)))) +\ No newline at end of file diff --git a/test/test-match.rkt b/test/test-match.rkt @@ -189,3 +189,12 @@ (check-match '(1 1 1 "b") [(xlist (? number? n) ^ 1 - (? string? s)) (cons n s)] '((1 1 1) . "b")) (check-match '(1 1 1 "b") [(xlist (? number? n) ^ 2 - (? string? s)) (cons n s)] '((1 1 1) . "b")) (void)) + + +(test-begin + "More complex repetitions" + (check-match '(1 2 3 d e f 7 8 9) + [(xlist (? number? n1) * (? symbol? s) * (? number? n2) *) + (list n2 s n1)] + '((7 8 9) (d e f) (1 2 3))) + (void))