[squeak-dev] [ANN] Tirade
Göran Krampe
goran at krampe.se
Wed Mar 18 14:46:26 UTC 2009
Hi!
(this thread is probably done now, but one last "devil in the
details"-post for the insatiably interested...)
Nicolas Cellier wrote:
> I wrote:
>> It might be worth noting that I want something simple that people can use
>> with very little effort. This means cutting some corners.
>>
>> But I also don't want to do "plain serialization", instead I want to create
>> a *custom* builder (serializers have a generic builder for all kinds of
>> Smalltalk objects) that works in *tandem* with the domain objects it
>> constructs (by utilizing real instance creation methods taking arguments and
>> not just "basicNew" followed by stuffing ivars) and that can be driven by
>> Tirade messages.
>>
>> The above paragraph catches it quite well I think. So construction looks
>> like this perhaps:
>
> Agree.
> My "serialization" was customized to use higher level messages to re-build
> the objects (a public API)...
> ...rather than lower level inst var description.
> When necessary, with help of a builder (stored in a predefined file scope
> variable).
Ok, so in fact a bit similar in philosophy.
>> "tirade message sequence" => TiradeParser (parses) => TiradeStackReader
>> (security checks and maintaining stack of receivers) => DeltaBuilder =>
>> "delta domain objects"
>>
>> Now, a very small example:
>>
>> Tirade input:
>> ---------------
>> createDelta: 'Name of delta'.
>> addRenameClass: #OldClassName.
>> newClassName: #NewClassName.
>> end.
>> addRenameClass: #AnotherOldClassName.
>> newClassName: #AnotherNewClassName.
>> end.
>> end.
>> -----------------------------
>>
>> DeltaBuilder>>createDelta: aName
>> ^DSDelta named: aName
>>
>> DSDelta>>addRenameClass: oldClassName
>> "Here we return the DSClassRenameChange instance.
>> Thus it will be the stacked receiver for Tirade messages."
>> ^self addChange: (DSClassRenameChange from: oldClassName)
>>
>> DSClassRenameChange>>newClassName: newClassName
>> newName := newClassName
>>
>>
>> ...ok, so the DeltaBuilder creates a Delta and returns it as the next
>> receiver on the stack.
>>
>> Then comes another message to add a "rename class" change. (A Delta is a
>> sequence of Changes basically). We do that BUT we also return this new
>> DSClassRenameChange object so that it will be the next receiver.
>>
>> #newClassName: is thus not sent to the DSClassRenameChange object, setting
>> one of its attributes. It returns self so it will still be the receiver for
>> more messages. If it returned nil it would cause the TiradeStackReader to
>> pop it, thus an object can actually "pop itself".
>>
>> More likely the Tirade input knows when we are done setting attributes so
>> it sends #end. This message is a message that TiradeStackReader intercepts
>> and causes it to pop the stack. The final #end pops the Delta too.
>>
>> Note that we are not naming ivars in the Tirade input, we aren't even
>> naming the class DSDelta! Everything is a message with data as arguments.
>>
>> regards, Göran
>
> OK, this is cute, both syntax and implementation are simple, efficient and
> readable.
Thanks! Yeah, I like it so far, although the stack bits aren't set in
stone yet - see below.
> I was fearing this would apply only to a collection of flat objects.
> ... and any deeper level would require another strategy, like using a JSON
> like literal array...
No, definitely not - in that case it would be rather ... dull. :)
> ... unless, each object acts as the builder for its next level in hierarchy.
> Here DSDelta acts as the builder for building a DSClassRenameChange.
> This is certainly a good pattern.
Yes, but I don't want to "hardcode" this pattern into Tirade, which is
why I separate some things from the TiradeParser (language) into
TiradeReader (subclass of parser that actually "does" something) and
also into the builder object(s).
My current view on the responsibilities:
TiradeParser: Parses an input stream of Tirade messages. Completely
defines the syntax of Tirade. If you use TiradeParser on a stream you
will get printouts in Transcript because it doesn't really *do* anything
with all the messages it reads. TiradeParser knows nothing about
receivers, it just parses an endless sequence of messages.
The reader: A reader is a subclass of TiradeParser and implements
#processMessage. It could do whatever it likes, printing them,
constructing an object - beep, whatever. :)
TiradeReader: The only included reader class in the Tirade package. It
deals with three things:
1. Figuring out the receiver for the message.
2. Making security checks on the message to be sent.
3. How to actually send the message.
Figuring out the receiver is done by using a stack of receivers and
following some conventions around that stack.
The security check right now is based on checking the method category
and verifying it beginsWith: 'tirade'. Otherwise there is an error. If
there is no implementation (then the receiver probably has implemented
doesNotUnderstand:) there is no check, we presume it does its own
checking. There is also a "whitelist" of allowed messages that is empty
by default. Using this you can allow receivers like blocks etc (allowing
#value:) or such.
How to send the message. This is interesting, I added a Set of
"controlMessages" in the reader in which the builder can register
messages that *it* wants to receive and not the current receiver on the
stack. This makes it possibly for the builder to pre-register for
example stack manipulation messages and when it receives them it can
manipulate the reader "from the outside" regardless of the receiver stack.
This is very nice because then the builder is "in full control" and can
decide how to deal with Tirade input. Even if the input stream says
"end." the builder can decide to not pop the current receiver.
Anyway, I find Tirade to be quite nice so far, and encourage anyone
interested in "file formats" or similar to give feedback. The SS repo is
open for writes :)
regards, Göran
More information about the Squeak-dev
mailing list
|