|
23 | 23 | [syntax-path->string (-> syntax-path? immutable-string?)] |
24 | 24 | [string->syntax-path (-> string? syntax-path?)] |
25 | 25 | [syntax-ref (-> syntax? syntax-path? syntax?)] |
| 26 | + [syntax-contains-path? (-> syntax? syntax-path? boolean?)] |
26 | 27 | [syntax-set (-> syntax? syntax-path? syntax? syntax?)] |
27 | 28 | [syntax-remove-splice |
28 | 29 | (-> syntax? (and/c proper-syntax-path? nonempty-syntax-path?) exact-nonnegative-integer? syntax?)] |
|
564 | 565 | (check-regexp-match #rx"path is inconsistent" (exn-message thrown))))) |
565 | 566 |
|
566 | 567 |
|
| 568 | +(define (syntax-contains-path? init-stx path) |
| 569 | + (let/ec return |
| 570 | + (for/fold ([stx init-stx]) |
| 571 | + ([element (in-treelist (syntax-path-elements path))]) |
| 572 | + (define unwrapped (syntax-e stx)) |
| 573 | + (cond |
| 574 | + ; Handle improper lists - flatten them so the tail is treated as the last element |
| 575 | + [(and (pair? unwrapped) (not (list? unwrapped))) |
| 576 | + (define flattened (flatten-improper-list unwrapped)) |
| 577 | + (unless (< element (length flattened)) |
| 578 | + (return #false)) |
| 579 | + (list-ref flattened element)] |
| 580 | + ; Handle proper lists |
| 581 | + [(list? unwrapped) |
| 582 | + (unless (< element (length unwrapped)) |
| 583 | + (return #false)) |
| 584 | + (list-ref unwrapped element)] |
| 585 | + ; Handle vectors |
| 586 | + [(vector? unwrapped) |
| 587 | + (unless (< element (vector-length unwrapped)) |
| 588 | + (return #false)) |
| 589 | + (vector-ref unwrapped element)] |
| 590 | + ; Handle boxes - treat as single-element list |
| 591 | + [(box? unwrapped) |
| 592 | + (unless (zero? element) |
| 593 | + (return #false)) |
| 594 | + (unbox unwrapped)] |
| 595 | + ; Handle prefab structs - treat as list of fields |
| 596 | + [(prefab-struct? unwrapped) |
| 597 | + (define fields (struct->list unwrapped)) |
| 598 | + (unless (< element (length fields)) |
| 599 | + (return #false)) |
| 600 | + (list-ref fields element)] |
| 601 | + ; Hashes are unsupported |
| 602 | + [(hash? unwrapped) |
| 603 | + (return #false)] |
| 604 | + ; Other datums don't have children |
| 605 | + [else |
| 606 | + (return #false)])) |
| 607 | + ; Check if result would be a non-syntax component |
| 608 | + (define result-stx |
| 609 | + (for/fold ([stx init-stx]) |
| 610 | + ([element (in-treelist (syntax-path-elements path))]) |
| 611 | + (define unwrapped (syntax-e stx)) |
| 612 | + (cond |
| 613 | + [(and (pair? unwrapped) (not (list? unwrapped))) |
| 614 | + (list-ref (flatten-improper-list unwrapped) element)] |
| 615 | + [(list? unwrapped) |
| 616 | + (list-ref unwrapped element)] |
| 617 | + [(vector? unwrapped) |
| 618 | + (vector-ref unwrapped element)] |
| 619 | + [(box? unwrapped) |
| 620 | + (unbox unwrapped)] |
| 621 | + [(prefab-struct? unwrapped) |
| 622 | + (list-ref (struct->list unwrapped) element)] |
| 623 | + [else stx]))) |
| 624 | + (not (or (pair? result-stx) (empty? result-stx))))) |
| 625 | + |
| 626 | + |
| 627 | +(module+ test |
| 628 | + (test-case "syntax-contains-path?" |
| 629 | + |
| 630 | + (test-case "empty path on any syntax" |
| 631 | + (define stx #'a) |
| 632 | + (check-true (syntax-contains-path? stx empty-syntax-path))) |
| 633 | + |
| 634 | + (test-case "valid path in list" |
| 635 | + (define stx #'(a b c)) |
| 636 | + (check-true (syntax-contains-path? stx (syntax-path (list 0)))) |
| 637 | + (check-true (syntax-contains-path? stx (syntax-path (list 1)))) |
| 638 | + (check-true (syntax-contains-path? stx (syntax-path (list 2))))) |
| 639 | + |
| 640 | + (test-case "invalid path in list - out of bounds" |
| 641 | + (define stx #'(a b c)) |
| 642 | + (check-false (syntax-contains-path? stx (syntax-path (list 3)))) |
| 643 | + (check-false (syntax-contains-path? stx (syntax-path (list 10))))) |
| 644 | + |
| 645 | + (test-case "valid nested path" |
| 646 | + (define stx #'(a (b c) d)) |
| 647 | + (check-true (syntax-contains-path? stx (syntax-path (list 1 0)))) |
| 648 | + (check-true (syntax-contains-path? stx (syntax-path (list 1 1))))) |
| 649 | + |
| 650 | + (test-case "invalid nested path" |
| 651 | + (define stx #'(a (b c) d)) |
| 652 | + (check-false (syntax-contains-path? stx (syntax-path (list 1 2)))) |
| 653 | + (check-false (syntax-contains-path? stx (syntax-path (list 3 0))))) |
| 654 | + |
| 655 | + (test-case "valid path in vector" |
| 656 | + (define stx #'#[a b c]) |
| 657 | + (check-true (syntax-contains-path? stx (syntax-path (list 0)))) |
| 658 | + (check-true (syntax-contains-path? stx (syntax-path (list 1))))) |
| 659 | + |
| 660 | + (test-case "invalid path in vector" |
| 661 | + (define stx #'#[a b c]) |
| 662 | + (check-false (syntax-contains-path? stx (syntax-path (list 5))))) |
| 663 | + |
| 664 | + (test-case "valid path in box" |
| 665 | + (define stx #'#&a) |
| 666 | + (check-true (syntax-contains-path? stx (syntax-path (list 0))))) |
| 667 | + |
| 668 | + (test-case "invalid path in box" |
| 669 | + (define stx #'#&a) |
| 670 | + (check-false (syntax-contains-path? stx (syntax-path (list 1))))) |
| 671 | + |
| 672 | + (test-case "path on non-compound datum" |
| 673 | + (define stx #'atom) |
| 674 | + (check-false (syntax-contains-path? stx (syntax-path (list 0))))) |
| 675 | + |
| 676 | + (test-case "path referring to non-syntax component" |
| 677 | + ; This test ensures we catch the error when syntax-ref finds a non-syntax component |
| 678 | + (define stx #'(a b c)) |
| 679 | + ; If we try to drill down into 'a', we should fail because atoms don't have children |
| 680 | + (check-false (syntax-contains-path? stx (syntax-path (list 0 0))))))) |
567 | 681 |
|
568 | 682 |
|
569 | 683 |
|
|
0 commit comments