hyper-literate/scribble-lib/scribble/search.rkt

188 lines
11 KiB
Racket

(module search racket/base
(require "struct.rkt"
"basic.rkt"
syntax/modcode)
(provide find-racket-tag
(rename-out [find-racket-tag find-scheme-tag]))
(define module-info-cache (make-hasheq))
(define (module-path-index-rejoin mpi rel-to)
(let-values ([(name base) (module-path-index-split mpi)])
(cond
[(not name) rel-to]
[(not base) mpi]
[else
(module-path-index-join name
(module-path-index-rejoin base rel-to))])))
(define (try thunk fail-thunk)
(with-handlers* ([exn:fail? (lambda (exn) (fail-thunk))])
(thunk)))
(define (find-racket-tag part ri stx/binding phase-level)
;; The phase-level argument is used only when `stx/binding'
;; is an identifier.
;;
;; Note: documentation keys currently don't distinguish different
;; phase definitions of an identifier from a source module.
;; That is, there's no way to document (define x ....) differently
;; from (define-for-syntax x ...). This isn't a problem in practice,
;; because no one uses the same name for different-phase exported
;; bindings.
;;
;; Formerly, we assumed that bindings are defined as originating from some
;; module at phase 0. [Maybe it's defined at phase 1 and re-exported
;; later for phase 0 (after a require-for-template), in which case the
;; re-exporting module is the one we find.] That assumption has been
;; lifted, however; search for "GONE" below.
(let ([b (cond
[(identifier? stx/binding)
(identifier-binding stx/binding phase-level)]
[(and (list? stx/binding)
(= 7 (length stx/binding)))
stx/binding]
[else
(and (not (symbol? (car stx/binding)))
(list #f
(cadr stx/binding)
(car stx/binding)
(cadr stx/binding)
(if (= 2 (length stx/binding))
0
(caddr stx/binding))
(if (= 2 (length stx/binding))
0
(cadddr stx/binding))
(if (= 2 (length stx/binding))
0
(cadddr (cdr stx/binding)))))])])
(and
(pair? b)
(let ([seen (make-hash)]
[search-key #f])
(let loop ([queue (list (list (caddr b) (cadddr b) (list-ref b 4) (list-ref b 5) (list-ref b 6)))]
[rqueue null]
[need-result? #t])
(cond
[(null? queue)
(if (null? rqueue)
;; Not documented
#f
(loop (reverse rqueue) null need-result?))]
[else
(let ([mod (list-ref (car queue) 0)]
[id (list-ref (car queue) 1)]
[defn-phase (list-ref (car queue) 2)]
[import-phase (list-ref (car queue) 3)]
[export-phase (list-ref (car queue) 4)]
[queue (cdr queue)])
(let* ([rmp (module-path-index-resolve mod)]
[eb (and ;; GONE: (equal? 0 export-phase) ;; look for the phase-0 export; good idea?
(list (module-path-index->taglet mod)
id))])
(when (and eb
(not search-key))
(set! search-key eb))
(let ([v (and eb (resolve-search search-key part ri `(dep ,eb)))])
(let* ([here-result
(and need-result?
v
(let ([v (resolve-get/tentative part ri `(form ,eb))])
(or (and v `(form ,eb))
`(def ,eb))))]
[need-result? (and need-result? (not here-result))]
[rmp-name (resolved-module-path-name rmp)])
;; Even if we've found `here-result', look deeper so that we have
;; consistent `dep' results.
(let ([nest-result
;; Maybe it's re-exported from this module...
;; Try a shortcut:
(if (eq? rmp (and (car b) (module-path-index-resolve (car b))))
;; Not defined through this path, so keep looking
(loop queue rqueue need-result?)
;; Check parents, if we can get the source:
(if (and (or (path? rmp-name)
(and (list? rmp-name)
(path? (car rmp-name))))
(not (hash-ref seen (cons export-phase rmp) #f)))
(let ([exports
(hash-ref
module-info-cache
rmp
(lambda ()
(let-values ([(valss stxess)
(try
(lambda ()
;; First, try using bytecode:
(module-compiled-exports
(get-module-code (if (list? rmp-name)
(car rmp-name)
rmp-name)
#:submodule-path (if (list? rmp-name)
(cdr rmp-name)
'())
#:choose (lambda (src zo so) 'zo))))
(lambda ()
(try
(lambda ()
;; Bytecode not available. Declaration in the
;; current namespace?
(module->exports rmp))
(lambda ()
(values null null)))))])
(let ([t
;; Merge the two association lists:
(let loop ([base valss]
[stxess stxess])
(cond
[(null? stxess) base]
[(assoc (caar stxess) base)
=> (lambda (l)
(loop (cons (cons (car l)
(append (cdar stxess)
(cdr l)))
(remq l base))
(cdr stxess)))]
[else (loop (cons (car stxess)
base)
(cdr stxess))]))])
(hash-set! module-info-cache rmp t)
t))))])
(hash-set! seen (cons export-phase rmp) #t)
(let ([a (assq id (let ([a (assoc export-phase exports)])
(if a
(cdr a)
null)))])
(if a
(loop queue
(append (map (lambda (m)
(if (pair? m)
(list (module-path-index-rejoin (car m) mod)
(list-ref m 2)
defn-phase
(list-ref m 1)
(list-ref m 3))
(list (module-path-index-rejoin m mod)
id
defn-phase
import-phase
export-phase)))
(reverse (cadr a)))
rqueue)
need-result?)
(begin
;; A dead end may not be our fault: the files could
;; have changed in inconsistent ways. So just say #f
;; for now.
#;
(error 'find-racket-tag
"dead end when looking for binding source: ~e"
id)
(loop queue rqueue need-result?)))))
;; Can't get the module source, so continue with queue:
(loop queue rqueue need-result?)))])
(or here-result
nest-result))))))])))))))