The auxiliary code is responsible for finding all the variables in the code and then finding all the _\d+s (and __) that occur free in the expression. It's probably bulkier than it needs to be---I wrote it with a decent amount of class experience in writing interpreters, but without any real-world experience. The big thing you seem to be missing is expand, which will macro-expand its argument. That would result in the following change:
I've looked at expand; how does it help there? Is that necessary to expand macros inside a bracket function?
I still have no idea how to check binding at compile time; in theory it could know that it was inside a binding expression (fn) but I think that would take a lot of modification to the language, and probably isn't worth it, unless it allows other nifty features.
Aha, I see. Yes, in this case, expand probably won't help. But it's actually conceptually simple to checking binding (without eval) at compile time; that's what make-br-fns does, after all. What you do is you run expand on the source tree, then just go through and check and see if it's within a fn. This is what all the auxiliary functions for make-br-fns are doing: checking to see what's in an argument list, seeing if variables used are in argument lists, etc. Obviously this breaks if you run, say, (eval '_2), but it works in other situations.
Wait, how does it know what context it's run in? I can see checking if the symbol is bound underneath it, but what about checking to see if it is bound above? (i.e. br-fn inside a withs) How does that work?
...Oh dear :-[ It doesn't, and I hadn't noticed that until right now. Apparently, it's not a problem, presumably because nobody ever declares variables called _1 :P Yeah, I don't see a feasible way to do that, unless make-br-fns produces something like
Even the 'eval thing wouldn't work. 'eval evaluates its argument at top-level, so the function would no longer be lexically scoped within its environment. You wouldn't be able to write anonymous closures anymore, just anonymous toplevel functions. Moreover, 'bound checks whether its argument is bound at global scope.
Unless there was a way for a function to examine its declaration environment. Then we could have a function that crawls up the tree looking for binding statements, and removing those from the list of unbound symbols. I just don't know how hard that would be to add. It could be useful for other things though, like better error messages. If the context were mutable, it could allow macros to do a) really cool things and b) likely very buggy things. But if people knew to expect it, it might be ok.
How would you implement a read-only context, visible during compile time? Is it even possible? In theory, the reader has already read in the other stuff, and parsed it into a list.
Allowing a macro to know what lexical variables are bound in its calling environment is perfectly possible, though it would require some modification to ac.scm. In order to translate arc symbols into mzscheme symbols, the compiler already keeps track of what variables are locally bound. So you'd need to modify the compiler so that this list gets passed in as a "hidden parameter" to macros, and make a special form to access it. However, I'd advise against implementing this, because there's an even more unsolvable problem. Even if you fixed it, the following would still break:
The intended meaning of [square x] here is (fn () (square x)). But because 'square is not bound at the time of [square x]'s macroexpansion, even if you did have the more "intelligent" version of 'make-br-fn, it would end up as (fn (square) (square x)). At present, of course, it ends up as (fn (square x) (square x)).
Hmmm. So how about looking for only unbound single letter symbols? That would cut out all of the predefined functions, and still provide usability (I wasn't going to use it with anything more than x y z and a b c anyway.
If not, I guess there's no point in continuing to pursue this idea, is there ;)
Maybe we could look at only single letter symbols, or symbols that start with _. That would give compatibility with (most) of the current uses; the only times that it wouldn't work would be when it was used in a function that had a single letter parameter. So, maybe I should just give up then ;) Even though I don't really like _1 _2, etc., I guess it's the most viable option.