101 lines
3.6 KiB
Racket
101 lines
3.6 KiB
Racket
![]() |
#lang racket/base
|
||
|
|
||
|
(require (only-in compiler/cm [get-file-sha1 file->digest])
|
||
|
racket/contract
|
||
|
racket/match
|
||
|
racket/promise
|
||
|
syntax/modread
|
||
|
"mod.rkt")
|
||
|
|
||
|
(provide file->syntax
|
||
|
file->expanded-syntax
|
||
|
before-run
|
||
|
make-eval-handler
|
||
|
after-run)
|
||
|
|
||
|
;; Return a syntax object or #f for the contents of `file`. The
|
||
|
;; resulting syntax is applied to `k` while the parameters
|
||
|
;; current-load-relative-directory and current-namespace are still set
|
||
|
;; appropriately.
|
||
|
(define/contract (file->syntax file [k values])
|
||
|
(->* (path-string?)
|
||
|
((-> syntax? syntax?))
|
||
|
(or/c #f syntax?))
|
||
|
(define-values (base _ __) (split-path file))
|
||
|
(parameterize ([current-load-relative-directory base]
|
||
|
[current-namespace (make-base-namespace)])
|
||
|
(with-handlers ([exn:fail? (λ _ #f)])
|
||
|
(k
|
||
|
(with-module-reading-parameterization
|
||
|
(λ ()
|
||
|
(with-input-from-file file read-syntax/count-lines)))))))
|
||
|
|
||
|
(define (read-syntax/count-lines)
|
||
|
(port-count-lines! (current-input-port))
|
||
|
(read-syntax))
|
||
|
|
||
|
;;; expanded syntax caching
|
||
|
|
||
|
;; cache : (hash/c file (cons/c digest-string? (or/c promise? syntax?)))
|
||
|
(define cache (make-hash))
|
||
|
(define last-mod #f)
|
||
|
|
||
|
;; Call this early in a file run, _before_ any evaluation. If it's not
|
||
|
;; the same file as before, we empty the cache -- to free up memory.
|
||
|
;; If it's the same file, we keep the cache.
|
||
|
(define (before-run maybe-mod)
|
||
|
(unless (equal? last-mod maybe-mod)
|
||
|
(hash-clear! cache)
|
||
|
(set! last-mod maybe-mod)))
|
||
|
|
||
|
(define ((make-eval-handler maybe-mod [orig-eval (current-eval)]) e)
|
||
|
(cond [(and (syntax? e)
|
||
|
(syntax-source e)
|
||
|
(path-string? (syntax-source e))
|
||
|
(not (compiled-expression? (syntax-e e))))
|
||
|
(define expanded-stx (expand e))
|
||
|
(cache-set! (syntax-source e) (λ () expanded-stx))
|
||
|
(orig-eval expanded-stx)]
|
||
|
[else (orig-eval e)]))
|
||
|
|
||
|
(define (after-run maybe-mod)
|
||
|
;; When the rkt file being run has a compiled zo that was used, then
|
||
|
;; our eval-hander above won't expand and cache any syntax. That
|
||
|
;; means when the user does a command that needs expanded syntax
|
||
|
;; (e.g. find-completion), they will need to wait for expansion. But
|
||
|
;; if you call this _after_ the file was run, it will cache-set! the
|
||
|
;; expansion using `delay/thread` -- i.e. the work will happen "in
|
||
|
;; the background". (Furthermore, when we already have a cache entry
|
||
|
;; for the file and digest, from a previous run, we'll just use
|
||
|
;; that.) As a result, it's likely to be mostly or entirely ready
|
||
|
;; when the user does a command.
|
||
|
(define-values (dir base _) (maybe-mod->dir/file/rmp maybe-mod))
|
||
|
(when (and dir base)
|
||
|
(define path (build-path dir base))
|
||
|
(cache-set! path (λ () (delay/thread (file->syntax path expand))))))
|
||
|
|
||
|
;; cache-set! takes a thunk so that, if the cache already has an entry
|
||
|
;; for the file and digest, it can avoid doing any work. Furthermore,
|
||
|
;; if you already have a digest for file, supply it to avoid redoing
|
||
|
;; that work, too.
|
||
|
(define/contract (cache-set! file thk [digest #f])
|
||
|
(->* (path-string? (-> (or/c promise? syntax?)))
|
||
|
((or/c #f string?))
|
||
|
any)
|
||
|
(let ([digest (or digest (file->digest file))])
|
||
|
(match (hash-ref cache file #f)
|
||
|
[(cons (== digest) _)
|
||
|
(void)]
|
||
|
[_
|
||
|
(hash-set! cache file (cons digest (thk)))])))
|
||
|
|
||
|
(define (file->expanded-syntax file)
|
||
|
(define digest (file->digest file))
|
||
|
(match (hash-ref cache file #f)
|
||
|
[(cons (== digest) promise)
|
||
|
(force promise)]
|
||
|
[_
|
||
|
(define stx (file->syntax file expand))
|
||
|
(cache-set! file (λ () stx) digest)
|
||
|
stx]))
|