Skip to content

Commit 8deb063

Browse files
Copilotjackfirth
andcommitted
Address PR review feedback: fix dotted syntax handling and simplify API
Co-authored-by: jackfirth <[email protected]>
1 parent 89a6de5 commit 8deb063

File tree

2 files changed

+127
-40
lines changed

2 files changed

+127
-40
lines changed

private/syntax-movement.rkt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,6 @@
9191

9292
; void
9393
(syntax-path (list 3 0))
94-
(sorted-set (syntax-path (list 3 2 1 0)) #:comparator syntax-path<=>)))
94+
(sorted-set (syntax-path (list 3 2 1)) #:comparator syntax-path<=>)))
9595

9696
(check-equal? table expected-table)))

private/syntax-path.rkt

Lines changed: 126 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
[nonempty-syntax-path? (-> any/c boolean?)]
1313
[proper-syntax-path? (-> any/c boolean?)]
1414
[empty-syntax-path syntax-path?]
15-
[syntax-path (-> (sequence/c syntax-path-element?) syntax-path?)]
16-
[syntax-path-elements (-> syntax-path? (treelist/c syntax-path-element?))]
15+
[syntax-path (-> (sequence/c exact-nonnegative-integer?) syntax-path?)]
16+
[syntax-path-elements (-> syntax-path? (treelist/c exact-nonnegative-integer?))]
1717
[syntax-path-element? (-> any/c boolean?)]
1818
[syntax-path-parent (-> nonempty-syntax-path? syntax-path?)]
1919
[syntax-path-next-neighbor (-> syntax-path? (or/c syntax-path? #false))]
20-
[syntax-path-last-element (-> nonempty-syntax-path? syntax-path-element?)]
21-
[syntax-path-add (-> syntax-path? syntax-path-element? syntax-path?)]
20+
[syntax-path-last-element (-> nonempty-syntax-path? exact-nonnegative-integer?)]
21+
[syntax-path-add (-> syntax-path? exact-nonnegative-integer? syntax-path?)]
2222
[syntax-path-remove-prefix (-> syntax-path? syntax-path? syntax-path?)]
2323
[syntax-path-neighbors? (-> syntax-path? syntax-path? boolean?)]
2424
[syntax-ref (-> syntax? syntax-path? syntax?)]
@@ -57,9 +57,8 @@
5757

5858
;@----------------------------------------------------------------------------------------------------
5959

60-
61-
(define (syntax-path-element? v)
62-
(exact-nonnegative-integer? v))
60+
; syntax-path-element? is now just exact-nonnegative-integer?
61+
(define syntax-path-element? exact-nonnegative-integer?)
6362

6463

6564
(struct syntax-path (elements)
@@ -302,13 +301,52 @@
302301
result)
303302

304303

305-
; Helper function to flatten improper lists
304+
; Helper function to check if a structure is truly improper (has an atom tail)
305+
; vs. dotted syntax (wraps proper lists with syntax objects)
306+
(define (has-improper-tail? lst)
307+
(cond
308+
[(null? lst) #false]
309+
[(pair? lst)
310+
(define cdr-elem (cdr lst))
311+
(cond
312+
[(syntax? cdr-elem)
313+
(define unwrapped (syntax-e cdr-elem))
314+
(cond
315+
[(list? unwrapped) #false] ; Dotted syntax with proper list
316+
[(pair? unwrapped) (has-improper-tail? unwrapped)] ; Keep checking
317+
[else #true])] ; Atom tail
318+
[(null? cdr-elem) #false]
319+
[(pair? cdr-elem) (has-improper-tail? cdr-elem)]
320+
[else #true])] ; Non-syntax atom tail
321+
[else #false]))
322+
323+
324+
; Helper function to flatten improper lists and dotted syntax
306325
; e.g., '(a b . c) becomes '(a b c)
307326
; e.g., '(a . (b . (c . d))) becomes '(a b c d)
327+
; e.g., pair with syntax object cdr like (a . #'(b c)) becomes (a b c)
308328
(define (flatten-improper-list lst)
309329
(cond
310330
[(null? lst) '()]
311-
[(pair? lst) (cons (car lst) (flatten-improper-list (cdr lst)))]
331+
[(pair? lst)
332+
(define car-elem (car lst))
333+
(define cdr-elem (cdr lst))
334+
; If cdr is a syntax object, unwrap it to handle dotted syntax
335+
(define cdr-unwrapped
336+
(if (syntax? cdr-elem)
337+
(syntax-e cdr-elem)
338+
cdr-elem))
339+
; Check if the unwrapped cdr is a list
340+
(cond
341+
[(list? cdr-unwrapped)
342+
; It's a proper list, so append it (flattens dotted syntax)
343+
(cons car-elem cdr-unwrapped)]
344+
[(pair? cdr-unwrapped)
345+
; It's an improper list, recursively flatten
346+
(cons car-elem (flatten-improper-list cdr-unwrapped))]
347+
[else
348+
; It's an atom (the improper tail)
349+
(list car-elem cdr-elem)])]
312350
[else (list lst)]))
313351

314352

@@ -325,13 +363,12 @@
325363
(define actual (syntax-ref stx (syntax-path (list 1))))
326364
(check-equal? (syntax->datum actual) 'b))
327365

328-
(test-case "improper list - accessing second element (the tail)"
329-
; In #'(a . (b c)), the cdr is a syntax object wrapping (b c)
330-
; After flattening, we have: (a (b c)), so index 1 gives us (b c) as a syntax object
366+
(test-case "improper list - dotted syntax treated as proper list"
367+
; #'(a . (b c)) is dotted syntax equivalent to #'(a b c)
368+
; After flattening, we have: (a b c), so index 1 gives us b
331369
(define stx #'(a . (b c)))
332370
(define actual (syntax-ref stx (syntax-path (list 1))))
333-
; The result is a syntax object wrapping the list (b c), not the atom b
334-
(check-equal? (syntax->datum actual) '(b c)))
371+
(check-equal? (syntax->datum actual) 'b))
335372

336373
(test-case "improper list with atom tail"
337374
(define stx #'(a b . c))
@@ -363,12 +400,11 @@
363400
(define actual (syntax-ref stx (syntax-path (list 3 1 0))))
364401
(check-equal? (syntax->datum actual) 'FOO))
365402

366-
(test-case "improper list with nested access"
367-
; #'(a b . (c FOO e)) flattens to (a b (c FOO e))
368-
; The tail (c FOO e) is a syntax object at index 2
369-
; So at index 2, we get the whole (c FOO e) syntax, then index 1 within that gives FOO
403+
(test-case "improper list with dotted syntax - nested access"
404+
; #'(a b . (c FOO e)) is dotted syntax equivalent to #'(a b c FOO e)
405+
; After flattening: (a b c FOO e), so index 3 gives us FOO
370406
(define stx #'(a b . (c FOO e)))
371-
(define actual (syntax-ref stx (syntax-path (list 2 1))))
407+
(define actual (syntax-ref stx (syntax-path (list 3))))
372408
(check-equal? (syntax->datum actual) 'FOO))
373409

374410
(test-case "prefab struct field path"
@@ -399,14 +435,21 @@
399435
(define remaining-elements (treelist-rest elements))
400436
(define unwrapped (syntax-e stx))
401437
(cond
402-
; Handle improper lists - flatten, update, and reconstruct
403-
; Note: This does flatten/unflatten on each access, which could be optimized if needed
404-
; for deeply nested paths, but improper lists are relatively rare in practice.
438+
; Handle improper lists and dotted syntax - flatten, update, and potentially reconstruct
439+
; Note: Dotted syntax like #'(a . (b c)) flattens to (a b c) and stays flattened.
440+
; Only truly improper lists with atom tails need to be reconstructed.
405441
[(and (pair? unwrapped) (not (list? unwrapped)))
442+
(define is-truly-improper (has-improper-tail? unwrapped))
406443
(define flattened (flatten-improper-list unwrapped))
407444
(define updated-elem (loop (list-ref flattened i) remaining-elements))
408445
(define updated-flattened (list-set flattened i updated-elem))
409-
(define updated-datum (unflatten-improper-list updated-flattened unwrapped))
446+
; Check if the original had a true improper tail (atom) vs dotted syntax (wraps lists)
447+
(define updated-datum
448+
(if is-truly-improper
449+
; Truly improper list, reconstruct the structure
450+
(unflatten-improper-list updated-flattened unwrapped)
451+
; Dotted syntax that flattened to proper list, keep it proper
452+
updated-flattened))
410453
(datum->syntax stx updated-datum stx stx)]
411454
; Handle proper lists
412455
[(list? unwrapped)
@@ -468,6 +511,28 @@
468511
flattened]))
469512

470513

514+
(module+ test
515+
(test-case "unflatten-improper-list"
516+
517+
(test-case "empty list"
518+
(check-equal? (unflatten-improper-list '() '()) '()))
519+
520+
(test-case "proper list stays proper"
521+
(define flat '(a b c))
522+
(define orig '(x y z)) ; proper list
523+
(check-equal? (unflatten-improper-list flat orig) '(a b c)))
524+
525+
(test-case "reconstruct simple improper list"
526+
(define flat '(a b c))
527+
(define orig '(x y . z)) ; improper list structure
528+
(define result (unflatten-improper-list flat orig))
529+
(check-equal? result '(a b . c)))
530+
531+
(test-case "reconstruct nested improper list"
532+
(define flat '(a b c d))
533+
(define orig '(w . (x . (y . z)))) ; nested improper structure
534+
(define result (unflatten-improper-list flat orig))
535+
(check-equal? result '(a . (b . (c . d)))))))
471536

472537

473538
(module+ test
@@ -485,11 +550,11 @@
485550
(define actual (syntax-set stx (syntax-path (list 1)) new-subform))
486551
(check-equal? (syntax->datum actual) '(a FOO c)))
487552

488-
(test-case "improper list - replacing the tail"
489-
; In #'(a . (b c)), index 1 is the tail (b c), replace it with FOO
553+
(test-case "dotted syntax - replacing middle element"
554+
; #'(a . (b c)) is equivalent to #'(a b c), so index 1 is b
490555
(define stx #'(a . (b c)))
491556
(define actual (syntax-set stx (syntax-path (list 1)) new-subform))
492-
(check-equal? (syntax->datum actual) '(a . FOO)))
557+
(check-equal? (syntax->datum actual) '(a FOO c)))
493558

494559
(test-case "improper list with atom tail"
495560
(define stx #'(a b . c))
@@ -521,10 +586,11 @@
521586
(define actual (syntax-set stx (syntax-path (list 3 1 0)) new-subform))
522587
(check-equal? (syntax->datum actual) '(a b c (m (FOO x y z) n))))
523588

524-
(test-case "nested path in improper list tail"
525-
; #'(a b . (c OLD e)) - index 2 gives us the tail (c OLD e), then index 1 in that
589+
(test-case "dotted syntax - replacing in flattened list"
590+
; #'(a b . (c OLD e)) is equivalent to #'(a b c OLD e)
591+
; Index 3 is OLD in the flattened list
526592
(define stx #'(a b . (c OLD e)))
527-
(define actual (syntax-set stx (syntax-path (list 2 1)) new-subform))
593+
(define actual (syntax-set stx (syntax-path (list 3)) new-subform))
528594
(check-equal? (syntax->datum actual) '(a b c FOO e)))))
529595

530596

@@ -775,15 +841,41 @@
775841
(for/list ([child (in-list children)]
776842
[i (in-naturals)])
777843
(loop child (syntax-path-add path i)))]
778-
; Handle improper lists - flatten them for path purposes
844+
; Handle improper lists and dotted syntax
845+
; We flatten for path indexing but preserve structure in output
779846
[(cons _ _) #:when (not (list? unwrapped))
780847
(define flattened-children (flatten-improper-list unwrapped))
781848
(define labeled-flat
782849
(for/list ([child (in-list flattened-children)]
783850
[i (in-naturals)])
784851
(loop child (syntax-path-add path i))))
785-
; Reconstruct the improper structure
786-
(unflatten-improper-list labeled-flat unwrapped)]
852+
; Reconstruct the ORIGINAL structure with labeled children
853+
; This is tricky: we need to preserve dotted syntax
854+
(let rebuild ([orig unwrapped] [flat labeled-flat])
855+
(cond
856+
[(pair? orig)
857+
(define car-labeled (car flat))
858+
(define cdr-elem (cdr orig))
859+
(cond
860+
[(syntax? cdr-elem)
861+
(define cdr-unwrapped (syntax-e cdr-elem))
862+
(cond
863+
[(list? cdr-unwrapped)
864+
; Dotted syntax - cdr wraps a list, so rest of flat goes there
865+
(cons car-labeled (cdr flat))]
866+
[(pair? cdr-unwrapped)
867+
; Nested improper - recurse
868+
(cons car-labeled (rebuild cdr-unwrapped (cdr flat)))]
869+
[else
870+
; Atom tail - single element
871+
(cons car-labeled (car (cdr flat)))])]
872+
[(pair? cdr-elem)
873+
; Direct pair - recurse
874+
(cons car-labeled (rebuild cdr-elem (cdr flat)))]
875+
[else
876+
; Atom tail
877+
(cons car-labeled (car (cdr flat)))])]
878+
[else orig]))]
787879
; Handle vectors - treat like lists
788880
[(vector children ...)
789881
(for/vector ([child (in-list children)]
@@ -858,13 +950,8 @@
858950
['< lesser]))))
859951

860952

861-
(define syntax-path-element<=>
862-
(make-comparator
863-
(λ (left right)
864-
(cond
865-
[(< left right) lesser]
866-
[(> left right) greater]
867-
[else equivalent]))))
953+
; syntax-path-element<=> is now just natural<=> from rebellion
954+
(define syntax-path-element<=> natural<=>)
868955

869956

870957
(module+ test

0 commit comments

Comments
 (0)