#lang racket/base ;; from Eli (provide recover-source-syntax) ;; -------------------- utilities (define (syntax-loc stx) (list (syntax-source stx) (syntax-position stx) (syntax-span stx))) ;; -------------------- the real stuff ;; Look for (the outermost) syntax in `orig' that has the same ;; location as `lookfor' which is coming from the expanded `orig', ;; given in `expanded'. (define (recover-source-syntax orig expanded #:traverse-now? [now? #f]) (define src (syntax-source orig)) ;; this maps source locations that are from orig to their syntax (define syntax-locs (make-hash)) ;; build `syntax-locs` (let loop ([stx orig]) (when (and (syntax? stx) ;; avoid spurious hits in the table from syntaxes ;; that have no useful source information (and (syntax-source stx) (syntax-position stx))) (hash-set! syntax-locs (syntax-loc stx) stx)) (let ([stx (if (syntax? stx) (syntax-e stx) stx)]) (when (pair? stx) (loop (car stx)) (loop (cdr stx))))) ;; this maps syntax from expanded to the original (define parent-table (make-hasheq)) ;; if `expanded` is mapped to something, then we'll start with it (define initial-target (hash-ref syntax-locs (syntax-loc expanded) #f)) ;; this searches for lookfor in orig, building up the table as we go ;; add-to-table: stx or #f -> stx or #f ;; #f as `lookfor` indicates "traverse all of `expanded` (define (add-to-table lookfor) ;; stx is expanded syntax, target is source syntax (let loop ([stx expanded] [target initial-target]) (cond [(syntax? stx) (define new-target ;; check if `stx` has the same srcloc as something in orig ;; in which case it's a good target to use ;; otherwise keep using the old target (hash-ref syntax-locs (syntax-loc stx) target)) ;; map `stx` to the best enclosing syntax we have, if it's not already there (hash-ref! parent-table stx new-target) (cond ;; if we got what we came for, stop [(and lookfor (eq? stx lookfor)) new-target] ;; take apart stx and loop on the components [else (let inner ([stxe (syntax-e stx)]) (cond [(list? stxe) (for/or ([x (in-list stxe)]) (loop x new-target))] [(pair? stxe) ; may be an improper syntax list (or (loop (car stxe) new-target) (inner (cdr stxe)))] [(syntax? stxe) ; base case (loop stxe new-target)] [else #f]))])] [else #f]))) ;; if now?, add everything to the table (when now? (add-to-table #f)) (lambda (lookfor) (or ;; we just might get a lookfor that is already in the original (and (eq? src (syntax-source lookfor)) (hash-ref syntax-locs (syntax-loc lookfor) #f)) (hash-ref parent-table lookfor (λ () (cond [now? #f] [else (add-to-table lookfor) (hash-ref parent-table lookfor #f)]))))))