Hi Colin,
Cursive looks like a really cool project. DrRacket would definitely
benefit from better support for the sort of structural editing that
Cursive enables.
> My understanding is that using macroexpansion in the way that
> DrRacket does requires a fairly deep integration between the
> macroexpander, the macros themselves and the IDE - is that a
> fair statement?
I don't think they are as deeply integrated as they might appear
to be. DrRacket and the macro expander have certainly evolved
together over 25 years of development and the macro expander has
had to support more features to enable some of the tools that we
use everyday in DrRacket, but today the two are developed mostly
independently.
DrRacket supports background expansion of Racket programs and
tools like Check Syntax by expanding programs on a separate
core (using places[1]) and by analyzing the resulting fully
expanded program. Check Syntax in particular looks at the
identifiers that show up in a fully expanded program and builds
data structures that associate the definitions and uses of
variables that will later be used to draw the binding arrows that
you see when you hover over identifiers in DrRacket. As Alex
mentioned, Check Syntax also uses the `mouse-over-tooltips`
syntax property in order to show tool tips in DrRacket. Check
syntax also uses variable binding structure for a a number of
other purposes including showing relevant documentation based on
the binding of an identifier, opening the file where a given
identifier is defined, and supporting simple refactorings such as
variable renaming to name few.
> How well does all this work for macros which aren't written
> with DrRacket in mind?
For the most part, things just work. That is if you implement a
macro that introduces a new identifier then DrRacket, usually, is
able to associate the uses of that identifier with the form that
introduces it. When this doesn't work, DrRacket provides hooks
for macro implementers to cooperate with Check Syntax via syntax
properties like `disappeared-use` and `disappeared-binding` which
Check Syntax uses to draw binding arrows for identifiers that may
not exist in the fully expanded program. As Alex explained, you
can also use the `sub-range-binders` syntax property to show the
connections between identifiers constructed only partially from
those that appear in a macro invocation as the `struct` macro
does.
Here is a small macro that demonstrates that most of the time
things just work in DrRacket:
```
#lang racket
(require (for-syntax syntax/parse syntax/transformer))
(define-syntax (my-letrec stx)
(syntax-parse stx
[(my-letrec ([x expr]) body)
#`(let ([temp (box #f)])
(let-syntax ([x (make-variable-like-transformer
#'(unbox temp)
#'(lambda (v) (set-box! temp v)))])
(set-box! temp expr)
body))]))
(my-letrec ([fact (λ (x) (if (zero? x) 1 (* x (fact (sub1 x)))))])
(fact 5))
```
When this program is fully expanded there are no more references
to the identifier `fact` in the program, but DrRacket is still
able to correctly draw arrows between the binding of fact and its
uses in the body of the `my-letrec` form. What happens in this
program is that the references to `fact` end up stored in the
`origin` syntax property wherever the original program referred
to `fact`. The macro expander stores the original syntax that a
piece of syntax expanded from in this field, and Check Syntax
uses this information to reconstruct the binding structure of the
original program without needing any help from the developed of
the macro. There are cases where this doesn't quite work out for
more complicated macros, and in those cases manually attaching
the `disappeared-use` and `disappeared-binding` properties allows
programmers to specify the correct binding structure in a way
that Check Syntax can use.
> And finally, of course - is there some documentation about all
> this I could look at? Is this all implemented with syntax
> object properties?