I've been meaning to ask, how are you doing macros in the read step? I take it they're able to parse their bodies as text, or else things like argument-only ssyntax wouldn't even be an option. This would make them similar either to Penknife syntaxes or Scheme/CL reader macros, depending on whether their input is string-like or stream-like. But doesn't this mean they're (at least sometimes) defined in a completely different way from Arc macros?
I tokenize the input stream (which is probably a string) one step at a time, and when I see a "(" I then keep parsing until I find a closing ")" Now, it's available as a list, so I check if the car is a macro, and if so I then call it with the remaining arguments, and return that.
However, I ran into a bit of a problem while parsing arc.arc, so I'll likely be refactoring that system. For instance, I may want it to only expand macros one level, rather than doing them all. The problem is, let's say you have this:
Oops! Note how it expanded the (foo) call, whereas Arc didn't. In other words, PyArc is a little bit too greedy about expanding macros.
In any case, the whole tokenize/parse/transform/macro expand stage is handled at the same time, so once that part's done, it can just use eval on the rest.
---
As for handling argument-only ssyntax, I've been wondering about that myself. But here's my current idea: when eval sees an (fn) form, it then creates and returns a (Python) function, which it can then execute later. Arguments and closures are handled when the function is created, so I should be able to do ssyntax expansion then.
And since macros are just annotated functions, I figure that approach will work with macros too. Thus, global ssyntax expands at read time, but argument ssyntax expands when the function is created, but before it's actually run.
Ah, was afraid of just that gotcha. It means if you say (afn ((= b c)) ...), you'll end up expanding the = macro before even realizing it's an argument list. o.o; (Right?)
There's another approach I thought you might have taken, and it's free of that trouble. You mentioned looking for (fn) forms, and it's probably the same general idea: When you encounter a left paren, read the next expression after that, and use it to determine what happens next (like a reader macro). If it's a symbol, look it up and see if it's bound to a macro, and let the macro (somehow) take care of reading its body and finding the right paren. Otherwise, read elements until you get a right paren, and treat the expreaaions as a function call.
Unfortunately, I don't know how to polish up that idea to make syntax like "a.(b c).d" work out correctly, especially in cases like "catch.(b throw).d" where 'catch determines the meaning of 'throw. Actually, I could figure something out for that too, but it would be a bit arbitrary, and you may already know you're not headed this way. :-p
Yes. I'll need to do some thinking on how to fix macro expansion.
---
Actually, I've been thinking about looking into reader macros, and seeing if I could use something similar to implement customizable ssyntax. That would have the benefit of potentially allowing for more powerful syntax.