AppleEvents, Mops
Volume Number: 8
Issue Number: 4
Column Tag: Jörg's Folder
Mops & More Apple Events
Mops is improved, again and more LS Fortran Apple Events
By Jörg Langowski, MacTutor Regular Contributing Author
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
Two themes this month: first, the OOPS Forth lovers among you will appreciate
that Michael Hore has again improved his MOPS system (see also V7#9); we are at
version 2.1 now. Second, we’ll continue a little our experiments with Apple Events;
this time with some more examples in LS Fortran.
MOPS 2.1
Not even half a year after the announcement of Mops 2.0, I received a new
diskette from Australia with Michael Hore’s newest release of MOPS, version 2.1.
Again, he has added a whole stock of new things, and I only hope that the word will
spread that this is probably one of the best high level programming systems on the Mac.
Many systems that you pay a lot of money for get much less support from its developers
than MOPS, which is free.
I wish MOPS many users, and wouldn’t actually be surprised if it went
commercial one day. Up to now you can get it from me at langowski@frembl51.bitnet,
though. I’ll also try again and post it on sumex-aim.stanford.edu, although they don’t
seem to receive my mailings somehow. The compacted MOPS system is 800 K long, so
we can’t put it on the source code disk.
Here’s Michael’s letter and the release notes (edited somewhat):
Dear Jörg,
Thanks for a great writeup of Mops in the Sept. MacTutor! I’ve only got one
complaint-now I’m surely going to get enquiries from non-Neon users and all kinds of
people, so I really ought to write a manual! Horrors!!! (Seriously though, I’ve already
started on it. I think I can reasonably assume some knowledge of Forth, and start from
there. Maybe in another few months it’ll be ready. Maybe.)
Well, time never stands still, so here’s release 2.1. Sorry, but after your
CtlWind example, what have I gone and done but altered the handling of windows with
controls!! Would you believe it was a complete coincidence. I’ve introduced the “view”
concept, similar to MacApp and TCL, so now controls belong to views, and views belong
to windows. The changeover is very straightforward, though, and I think will make it
much easier in the long run to put all kinds of things in windows. We don’t have fully
automatic handling of scrolling yet, but that should come. The other big change is that
classes can now be exported from Modules. All the changes are listed in the “Mops 2.1
release notes” file.
The other big news is that I’m isolated no longer! CompuServe is now in
Australia, so anyone can now send email to me at [100033,3164]. I think I’ll have an
InterNet address in the new year, too.
Michael Hore, PO Box 821, Nhulunbuy, NT 0881, Australia
[Parts of the release notes follow]
Classes can be exported from modules
Yes, it’s true! You can now define a class in a module, include the class name in
the imports list, and instantiate objects of that class anywhere. In fact in the Mops
system itself, we are now handling windows, menus and dialogs this way. All the
methods of the class are in effect exported along with the class name; you don’t have to
take any special action, apart from putting the class name in the imports list, and
taking care with action handlers (see below). Whenever you send a message to an
exported class, the module will be invoked automatically.
Naturally this has some performance implications. There is a fair amount of
overhead involved in invoking a module. You can reduce this to some extent by locking
the module over a number of calls (this is true for ordinary exported words as well).
But if message execution for a particular class is really time-critical, it would really
best to leave the class in the main dictionary. Where exported classes are most useful is
for those classes that depend heavily on Toolbox calls for most of their methods (such
as windows, menus and dialogs). Toolbox calls are generally much slower than Mops
module invocations, so the extra time penalty of putting the class into a module won’t be
significant.
Now for the point about action handlers. If the class is defined in module 1, let’s
say, and if it is instantiated in another module, module 2, and if this object has action
handlers which are also words from the module 2 (rather than in the main dictionary),
you will need to declare these action handlers with a new syntax :a .... ;a (pronounced
“colon A” and “semi A”) instead of : ... ; . This is because these action handlers will
probably be invoked from the class implementation, in module 1, which amounts to a
“back-door” entry to module 2. As I have pointed out in the main documentation file,
this is a big no-no. But any word declared with the :a ... ;a syntax avoids this problem.
This is a rather subtle condition, so it is probably best simply to declare all
action handlers with the :a ... ;a syntax no matter what-it won’t hurt. There is no
performance penalty. In fact, :a definitions in the main dictionary compile identical
code to normal definitions. And if you use :a definitions routinely, then it is always
obvious if a word is an action handler. And this way, whenever you move class
definitions into modules, you won’t run into the above problem.
Here are the technical details for those who are interested. An action handler is
either in the main dictionary or a module. If it’s in a module, module 2 let’s say, the
object using it must also be in module 2 (otherwise you would get a “Can’t store a
module address outside the module” error when you tried to install the action handler in
the object).
The problem comes up if the class implementation is in another module, say
module 1. When a method (in module 1) invokes the action handler, A5 (modBase) will
still be set up for module 1, and so will be wrong for the action handler (in module 2).
So :a definitions, at the start, first push A5 on to the return stack, then set A5 to the
correct value for the current module. This is done by PC-relative addressing, so it
makes no assumptions about the previous value of A5. Then at the end of the definition
(or if EXIT is done), the previous A5 value is popped back from the return stack.
Note that the overhead is only a few machine instructions. But if the :a word is
compiled in the main dictionary, these extra instructions are unnecessary and are
omitted, so there is no penalty at all. This is a blatantly undisguised attempt to
encourage use of the :a syntax for all action handlers no matter where they are, which
will minimize problems with moving class implementations into modules.
CallBefore and CallAfter
This is a new feature which has been in my “coming attractions” list, and has
now arrived. It is a bit complex to explain, but if you don’t need this feature, ignore it.
It is entirely optional, but can be really useful in some situations.
We have wanted to provide a bit more flexibility in the way a subclass overrides
a method. Up to now, it could either override or not. If it did, it could possibly call the
Super version of the method within the new definition, but that was all. A superclass
had no way of limiting what a subclass may do. So now, along with other languages such
as Flavors and Eiffel, we provide a superclass with the power to limit the extent to
which a subclass may override a method, basically by allowing the provision of code
which must be executed BEFORE the subclass’s version of the method executes, and code
which must be executed AFTER the subclass’s method has finished. These pieces of code
can do things like check that certain constraints haven’t been violated, or do some other
obligatory setting up and winding up housekeeping.
The best way do describe this feature is by means of a real-life example-this
code is extracted from class Window (now in the file WindowMod.txt):
/* 1 */
private
:m SETUP_DRAW:
get: fPrect \ Save fPrect as it might get changed
^base call BeginUpdate ;m
:m WINDUP_DRAW:
^base call EndUpdate
put: fPrect ;m \ Restore fPrect
callFirst setup_draw:
callLast windup_draw:
public
:m DRAW: (draw): self ;m
Drawing to a window in response to an update event requires certain
housekeeping chores to be done at the beginning and the end. Rather than require them to
be repeated
in every window subclass that overrides Draw:, we move them into two private
methods, Setup_draw: and Windup_draw:. Then we use the callFirst and callLast syntax
to specify that these methods are to be called (as messages to Self) before and after
Draw: , respectively. Whenever Draw: is redefined in a window subclass, Setup_draw:
and Windup_draw: are executed at the beginning and the end, so that the housekeeping
code doesn’t need to be repeated (and you can even forget what it is, if you want to).
Note that the CallFirst and CallLast declarations always apply to the next method
declared. You can actually use these declarations more than once, as long as all the
CallFirsts come before the CallLasts. A subclass may add methods to the CallFirst and
CallLast list of any method (thus, the subclass doesn’t need to know if there are already
CallFirst or CallLast declarations in existence for a method it is overriding).
There is no need for CallFirst or CallLast messages to be private, although we
have done it in this example. But it wouldn’t really make much sense to call them
directly from the outside world.
The order of execution is significant. All entries on the CallFirst list are called
in order from the beginning to the end. Thus the CallFirst entries specified by the
highest superclass get executed first. This is exactly what you would expect, given the
hierarchical nature of Mops code.
But notice carefully, that the same logic applied to the CallLast list means that
the entries specified by the highest superclass should get executed last. So what we do is
execute the entries in the CallLast list from the end backwards. The entries are put on
the list simply in the order they are declared, but they will be executed in the reverse
order.
A CallFirst or CallLast method is executed as an early-bound message to Self. All
stack parameters and results will be as they are for the method to which the
CallFirst/CallLast is being applied. If this “main” method uses the named parameters
syntax, then at the time any CallFirst method is invoked these parameters will still be
on the stack. Thus the CallFirst method is free to use the named parms syntax itself, as
long as it pushes the parms back on the stack when it finishes-otherwise the main
method would find its parameters had mysteriously disappeared!
I probably shouldn’t say this. But there is a way to override a CallFirst/CallLast.
Forth programmers are notoriously anarchic, and may resent a superclass
autocratically decreeing that certain things must take place at the beginning and end of
the method they are about to write. (Even if they wrote the superclass themselves.
E specially if they wrote it themselves.) Well... the entries on the CallFirst/CallLast
lists are simply the hashed forms of the selector names. The binding isn’t actually done