[plt-scheme] Re: Emacs tags for MzScheme modules

From: Eli Barzilay (eli at barzilay.org)
Date: Sun Nov 2 19:47:20 EST 2003

On Nov  2, Bill Clementson wrote:
> 
> > What you want is the ability to find functions where you can only
> > see their name, but don't know where they are -- a la M-. of
> > etags.
> 
> Yes, what I'm struggling with is finding a mechanism for locating
> the definitions of individual functions, macros, etc. In C, Java,
> and CL, I have tools for doing this. I'm just trying to find the
> equivalent tools to use when I'm working with MzScheme.

OK, this bothered me since it *should* be possible to do a much better
job than tools like the ones you use for Java/C/CL etc.  So I tried to
figure out the best way to do it, and it looks like it is pretty
simple to simply read a module definition from a file, fully expand
id, and analyze the results.  This made me write the following
semi-simple function that does just that.  After the module expression
is expanded, it just loops over stuff in the module body, collecting
stuff that it provided, required, required-for-syntax, and bindings
that were made.  Each one of these things are being collected as the
original identifier syntax, which means that source infomation for
these things is still available after all this processing.  Then each
of the lists are translated, so instead of an identifier object you
get a list of the symbol, the source file name, the character
position, line and column.  For example, running

  (module-walk "~/plt/collects/mzlib/list.ss")

produces:

  ((list "~/plt/collects/mzlib/list.ss" 10 2 8)
   (mzscheme "~/plt/collects/mzlib/list.ss" 15 2 13)
   (requires ("spidey.ss" "~/plt/collects/mzlib/list.ss" 35 3 11))
   (requires-for-syntax (mzscheme #f #f #f #f))
   (provides (set-first! "~/plt/collects/mzlib/list.ss" 60 5 11)
             (first "~/plt/collects/mzlib/list.ss" 74 6 10)
             (second "~/plt/collects/mzlib/list.ss" 83 7 10)
             ...
             (filter "~/plt/collects/mzlib/list.ss" 309 37 10)
             (quicksort "~/plt/collects/mzlib/list.ss" 323 39 10)
             (mergesort "~/plt/collects/mzlib/list.ss" 336 40 10))
   (bindings (quicksort "~/plt/collects/mzlib/list.ss" 360 42 10)
             (mergesort "~/plt/collects/mzlib/list.ss" 1602 71 10)
             (remove "~/plt/collects/mzlib/list.ss" 2412 100 10)
             ...
             (cons? "~/plt/collects/mzlib/list.ss" 8961 332 10)
             (empty? "~/plt/collects/mzlib/list.ss" 9001 333 10)
             (empty "~/plt/collects/mzlib/list.ss" 9042 334 10))
   (syntaxes (mk-lget "~/plt/collects/mzlib/list.ss" 7715 296 18)))

I think that this should be more than enough for something that you
could later on interface with etags, and the result is much much
better than you could do, since it doesn't rely on regexps (eg, when
you have a macro that expands to a define but it doesn't look like
(def...).

My function follows -- it probably needs a little more work for cases
where there are things that are not single identifiers, for example, a
"provide" form that is not a single identifier but some rename.  But
maybe it will work (and you'll get such an (datum src pos line col)
thing but the datum will no longer be a symbol).  Please tell me if
this works with etags -- I never bothered much to use it (probably for
the same reason I never liked working in a gui too much...).


======================================================================
(define (module-walk file)
  (let ((s (expand (parameterize ((port-count-lines-enabled #t))
                     (with-input-from-file file
                       (lambda () (read-syntax file))))))
        (name     #f)
        (lang     #f)
        (requires  (list 'requires))
        (srequires (list 'requires-for-syntax))
        (provides  (list 'provides))
        (variables (list 'bindings))
        (syntaxes  (list 'syntaxes)))
    (define (add-to l1 l2)
      (set! l1 (append! l1 (map (lambda (x) x) (syntax->list l2)))))
    (define (inspect stxs)
      (unless (null? stxs)
        (syntax-case (car stxs)
            (require require-for-syntax provide define-values define-syntaxes)
          ((require . stuff)            (add-to requires  #'stuff))
          ((require-for-syntax . stuff) (add-to srequires #'stuff))
          ((provide . stuff)            (add-to provides  #'stuff))
          ((define-values   ids _)      (add-to variables #'ids))
          ((define-syntaxes ids _)      (add-to syntaxes  #'ids))
          (_ (error 'module-walk "unknown module expression")))
        (inspect (cdr stxs))))
    (syntax-case s (module #%plain-module-begin)
      ((module mname mlang (#%plain-module-begin . mbody))
       (begin (set! name #'mname) (set! lang #'mlang)
              (inspect (syntax->list #'mbody))))
      (_ (error 'module-walk "weird module body")))
    (let* ((* (lambda (x)
                (list (syntax-object->datum x)
                      (syntax-source x)
                      (syntax-position x)
                      (syntax-line x)
                      (syntax-column x))))
           (** (lambda (l) (cons (car l) (map * (cdr l))))))
      (list (* name) (* lang) (** requires) (** srequires) (** provides)
            (** variables) (** syntaxes)))))
======================================================================

-- 
          ((lambda (x) (x x)) (lambda (x) (x x)))          Eli Barzilay:
                  http://www.barzilay.org/                 Maze is Life!


Posted on the users mailing list.