#!/bin/sh
#|
exec mzscheme -M errortrace -r "$0" "$@"

Generates an TAGS file suitable for Emacs consumption, from the plt-scheme 
files given on the command line. For example, the following is a useful thing:

   cd plt/collects; find -name \*.ss | xargs etags.ss

This will create a file called 'plt/collects/TAGS'. Boot Emacs and hit 'M-.'
Emacs will ask you for the location of the TAGS file (as above) and a name. 
It will then jump to its to the file and the line where that name is defined.


Sun Apr 17 2005
    Ported to MzScheme 299, based on a patch by Danny Yoo

Sun Nov 2 2003
    Written by Guillaume Marceau (gmarceau@cs.brown.edu)  

|#

(require (lib "errortrace.ss" "errortrace")
         (lib "list.ss")
         (lib "etc.ss")
         (lib "file.ss")
         (lib "string.ss"))


;; By default, use the encoding from the default locale.
(define bytes->string bytes->string/locale)



(define (string-chop str)
  (substring str 0 (- (string-length str) 1)))

(define (hash-table-mem? hash item) 
  (let/ec k (begin (hash-table-get hash item (lambda () (k false))) true)))

(define (hash-table-gad! hash key val-fn)
  (if (not (hash-table-mem? hash key)) 
      (begin (let ((v (val-fn)))
               (hash-table-put! hash key v)
               v))
      (hash-table-get hash key)))

(define (string-prefix str len)
  (substring str 0 (min (string-length str) len)))

(define (id-to-tag-line id port)
  (if (or (not (syntax-source id))
          (not (syntax-position id)))
      ""
      (let* ([name-sym (syntax-e id)]
             [s (syntax-source id)]
             [line (syntax-line id)]
             [p (syntax-position id)]
             [lead-in (open-output-string)]
             [rewound-p
              (let loop ([p p])
                (file-position port p)
                (if (and (> p 0) 
                         (not (eq? (read-char port) #\newline)))
                    (loop (- p 1))
                    (+ p 1)))])
        
        (let* ([matched (regexp-match 
                         (regexp (format "^[^\n]*~a[ \t\n]" 
                                         (regexp-quote
                                          (symbol->string name-sym))))
                         port 0 false lead-in)])
          (format
           "~a~a~a,~a\n" 
           (if (not matched) (string-prefix (get-output-string lead-in) 22)
               (string-append (get-output-string lead-in)
                              (string-chop (bytes->string (first matched)))))
           name-sym line rewound-p)))))

(define (find-defs-in-stx stx)
  (let loop ([substx (expand stx)])
    (syntax-case substx (define-values define-syntaxes)
      [(define-values names-pt body ...) (syntax->list #'names-pt)]
      
      [(define-syntaxes names-pt body ...) (syntax->list #'names-pt)]
      
      [(i ...)  
       (apply append (map loop (syntax->list (syntax (i ...)))))]
      
      [_ empty])))



(define (for-each-tle-in-file fn target-file)
  (with-handlers
      ([exn:fail?
        (lambda (exn) (display (format "Error while processing ~a:\n~a\n\n" 
                                  target-file (exn-message exn)))
           empty)])
    
    (parameterize ([port-count-lines-enabled true]
                   [read-case-sensitive true]
                   [current-load-relative-directory 
                    (path-only (normalize-path target-file))])
      (let ([port (open-input-file target-file)])
	(skip-magic! port)
        (begin0
	      (let loop ([stx (read-syntax target-file port)])
              (unless (eof-object? stx)
                (fn (expand stx))
                (loop (read-syntax target-file port))))
          (close-input-port port))))))

;; skips the magic header off a port, for files that look like scripts.
(define (skip-magic! port)
  (when (equal? #"#!" (peek-bytes 2 0 port))
    (read-line port)))

(define (process-file target-file output-file)           
  (let ([ids empty])
    (for-each-tle-in-file 
     (lambda (stx) (set! ids (append ids (find-defs-in-stx stx))))
     target-file)
    (let* ([target-file-port (open-input-file target-file)]
           [lines (map (lambda (i) (id-to-tag-line i target-file-port)) ids)]
           [section (apply string-append lines)])
      (display (format "\n~a,~a\n~a"
                       target-file (string-length section) section)
               output-file)
      (close-input-port target-file-port))))

(define (process-stdin target-tag-file)
  (let loop ([line (read-line)])
    (unless (eof-object? line)
      (process-file line target-tag-file)
      (loop (read-line)))))

(with-handlers
    ([exn:break? (lambda (exn) (void))])
  (let ([target-tag-file (open-output-file "TAGS" 'replace)]
        [done-stdin false])
    (for-each 
     (lambda (f) 
       (if (and (equal? f "-") (not done-stdin))
           (begin (process-stdin target-tag-file)
                  (set! done-stdin true))
           (process-file f target-tag-file)))
     (vector->list argv))
    (close-output-port target-tag-file)))


