|
12 | 12 | [nonempty-syntax-path? (-> any/c boolean?)] |
13 | 13 | [proper-syntax-path? (-> any/c boolean?)] |
14 | 14 | [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?))] |
17 | 17 | [syntax-path-element? (-> any/c boolean?)] |
18 | 18 | [syntax-path-parent (-> nonempty-syntax-path? syntax-path?)] |
19 | 19 | [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?)] |
22 | 22 | [syntax-path-remove-prefix (-> syntax-path? syntax-path? syntax-path?)] |
23 | 23 | [syntax-path-neighbors? (-> syntax-path? syntax-path? boolean?)] |
24 | 24 | [syntax-ref (-> syntax? syntax-path? syntax?)] |
|
57 | 57 |
|
58 | 58 | ;@---------------------------------------------------------------------------------------------------- |
59 | 59 |
|
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?) |
63 | 62 |
|
64 | 63 |
|
65 | 64 | (struct syntax-path (elements) |
|
302 | 301 | result) |
303 | 302 |
|
304 | 303 |
|
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 |
306 | 325 | ; e.g., '(a b . c) becomes '(a b c) |
307 | 326 | ; 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) |
308 | 328 | (define (flatten-improper-list lst) |
309 | 329 | (cond |
310 | 330 | [(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)])] |
312 | 350 | [else (list lst)])) |
313 | 351 |
|
314 | 352 |
|
|
325 | 363 | (define actual (syntax-ref stx (syntax-path (list 1)))) |
326 | 364 | (check-equal? (syntax->datum actual) 'b)) |
327 | 365 |
|
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 |
331 | 369 | (define stx #'(a . (b c))) |
332 | 370 | (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)) |
335 | 372 |
|
336 | 373 | (test-case "improper list with atom tail" |
337 | 374 | (define stx #'(a b . c)) |
|
363 | 400 | (define actual (syntax-ref stx (syntax-path (list 3 1 0)))) |
364 | 401 | (check-equal? (syntax->datum actual) 'FOO)) |
365 | 402 |
|
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 |
370 | 406 | (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)))) |
372 | 408 | (check-equal? (syntax->datum actual) 'FOO)) |
373 | 409 |
|
374 | 410 | (test-case "prefab struct field path" |
|
399 | 435 | (define remaining-elements (treelist-rest elements)) |
400 | 436 | (define unwrapped (syntax-e stx)) |
401 | 437 | (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. |
405 | 441 | [(and (pair? unwrapped) (not (list? unwrapped))) |
| 442 | + (define is-truly-improper (has-improper-tail? unwrapped)) |
406 | 443 | (define flattened (flatten-improper-list unwrapped)) |
407 | 444 | (define updated-elem (loop (list-ref flattened i) remaining-elements)) |
408 | 445 | (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)) |
410 | 453 | (datum->syntax stx updated-datum stx stx)] |
411 | 454 | ; Handle proper lists |
412 | 455 | [(list? unwrapped) |
|
468 | 511 | flattened])) |
469 | 512 |
|
470 | 513 |
|
| 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))))))) |
471 | 536 |
|
472 | 537 |
|
473 | 538 | (module+ test |
|
485 | 550 | (define actual (syntax-set stx (syntax-path (list 1)) new-subform)) |
486 | 551 | (check-equal? (syntax->datum actual) '(a FOO c))) |
487 | 552 |
|
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 |
490 | 555 | (define stx #'(a . (b c))) |
491 | 556 | (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))) |
493 | 558 |
|
494 | 559 | (test-case "improper list with atom tail" |
495 | 560 | (define stx #'(a b . c)) |
|
521 | 586 | (define actual (syntax-set stx (syntax-path (list 3 1 0)) new-subform)) |
522 | 587 | (check-equal? (syntax->datum actual) '(a b c (m (FOO x y z) n)))) |
523 | 588 |
|
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 |
526 | 592 | (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)) |
528 | 594 | (check-equal? (syntax->datum actual) '(a b c FOO e))))) |
529 | 595 |
|
530 | 596 |
|
|
775 | 841 | (for/list ([child (in-list children)] |
776 | 842 | [i (in-naturals)]) |
777 | 843 | (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 |
779 | 846 | [(cons _ _) #:when (not (list? unwrapped)) |
780 | 847 | (define flattened-children (flatten-improper-list unwrapped)) |
781 | 848 | (define labeled-flat |
782 | 849 | (for/list ([child (in-list flattened-children)] |
783 | 850 | [i (in-naturals)]) |
784 | 851 | (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]))] |
787 | 879 | ; Handle vectors - treat like lists |
788 | 880 | [(vector children ...) |
789 | 881 | (for/vector ([child (in-list children)] |
|
858 | 950 | ['< lesser])))) |
859 | 951 |
|
860 | 952 |
|
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<=>) |
868 | 955 |
|
869 | 956 |
|
870 | 957 | (module+ test |
|
0 commit comments