#lang scheme/base (require (for-syntax scheme/base) scheme/list) ;; Returns the module (as a syntax string, an absolute path) of the ;; caller's lexical context (define-syntax (current-module-path stx) (let ([src (syntax-source stx)]) (if (path? src) (with-syntax ([path (path->string src)]) (syntax/loc stx path)) (raise-syntax-error 'current-module-path "could not determine the current module path (is the file unsaved?)" stx)))) (define module-path (current-module-path)) ;; Skips whitespace characters using the current readtable's definition ;; of whitespace (define (skip-whitespace port) (let ([ch (peek-char port)]) (unless (eof-object? ch) (let-values ([(like-ch/sym proc dispatch-proc) (readtable-mapping (current-readtable) ch)]) (when (and (char? like-ch/sym) (char-whitespace? like-ch/sym)) (read-char port) (skip-whitespace port)))))) ;; Returns true if reading terms after $ should stop (define (is-end? port) (and (member (peek-char port) (list #\) #\] #\} eof)) #t)) ;; Reads a datum from the input port (define (read-rest-datum port) (skip-whitespace port) (if (is-end? port) empty (let* ([datum (read/recursive port #f (current-readtable))] [rest-datum (read-rest-datum port)]) (if (special-comment? datum) rest-datum (append (list datum) rest-datum))))) ;; Creates a syntax object from datum using the input port's position to ;; compute its span (define (dtm->stx datum port src line col pos) (let-values ([(l c p) (port-next-location port)]) (datum->syntax #f datum (list src line col pos (and pos (- p pos)))))) ;; Reads a syntax object from the input port (define (read-rest-syntax port src) (skip-whitespace port) (let-values ([(line col pos) (port-next-location port)]) (if (is-end? port) empty (let* ([stx (read-syntax/recursive src port #f (current-readtable))] [rest-stx (read-rest-syntax port src)]) (if (special-comment? stx) rest-stx (dtm->stx (cons stx rest-stx) port src line col pos)))))) ;; Returns the current readtable extended with a handler for $ (define (make-rest-readtable) (make-readtable (current-readtable) #\$ 'non-terminating-macro (case-lambda [(ch port) (read-rest-datum port)] [(ch port src line col pos) (read-rest-syntax port src)]))) ;; Reads a datum from the input port, handles $ (define (*read port) (parameterize ([current-readtable (make-rest-readtable)]) (read port))) ;; Reads a syntax object from the input port, handles $ ;; If the result is recognizable as a module form, inserts a require for ;; this module's make-rest-readtable and an expression that sets the ;; current readtable ;; Without this, calls to 'read' at evaluation time (like in the REPL!) ;; wouldn't handle $ properly (define (*read-syntax src port) (parameterize ([current-readtable (make-rest-readtable)]) (define stx (read-syntax src port)) (with-syntax ([module-path module-path]) (syntax-case stx () ; already has #%module-begin [(module name lang (#%module-begin body ...)) (and (eq? (syntax->datum #'module) 'module) (eq? (syntax->datum #'#%module-begin) '#%module-begin)) (syntax/loc stx (module name lang (#%module-begin (require (only-in (file module-path) make-rest-readtable)) (current-readtable (make-rest-readtable)) body ...)))] ; doesn't have #%module-begin yet [(module name lang body ...) (eq? (syntax->datum #'module) 'module) (syntax/loc stx (module name lang (require (only-in (file module-path) make-rest-readtable)) (current-readtable (make-rest-readtable)) body ...))] ; module is probably broken anyway... [_ stx])))) (provide make-rest-readtable (rename-out [*read read] [*read-syntax read-syntax]))