Below is the first design for blocks that I am not totally unhappy with. Some of this could be used in Self but parts depend on the Neo Smalltalk syntax rules to keep it visually similar to the current system.
Instead of a special syntax for blocks, context (activation) objects would understand the message [ ... ] with a method object as the argument (the thing between "[" and "]") which would be like a Self message "copyAsBlock: methObj".
While it would be possible to write
a [ b ]
in practice the '.[.]' message would always be sent to the implicit self receiver (the currently executing context). Here is how this method would be implemented (in a mix of Neo Smalltalk, Self and Smalltalk-80 notations for convenience)
.[.] = ( | :m. b | b: block copyArity: m arity. b context: thisContext sender. b method: m. b )
The Smalltalk-80 notation "thisContext sender" could be replaced in Self with just "self", except that might refer to the receiver of the original method instead of the current one. It isn't important.
Supposing "m arity" is 2 (m takes two arguments), then "b" would be:
( | parent* = traits block. context <- prototypes context. method <- prototypes method. value = (error: "needs 2 arguments"). value: arg = (error: "needs 2 arguments"). value: arg1 with: arg2 = (method runFor: context With: arg1 With: arg2). )
Obviously 'runFor:With:With:' should actually be something properly reflective, but this should give the general idea.
The point is that this is all done with regular messages and objects (blocks are not special in any way, though methods still are a little) and actually exposes the way things are done in many Smalltalks (using the "send blockCopy" bytecodes in Smalltalk-80, for example). Yet it looks almost exactly the same and works almost exactly the same (the only difference is that you can now send 'context' or 'method' to a block) as the current solutions. How the implementation actually does things, however, is an entirely different story...