(* https://web.archive.org/web/20041226165949/http://www.oberon.ethz.ch:80/ethoberon/defs/Objects.Def.html *) DEFINITION Objects; (* portable *) (* Module Objects forms the basis of the object-oriented part of the Oberon system. It provides the system with the type Object and defines what messages objects understand. Most entities in Oberon are derived from this base type. *) IMPORT Files; CONST enum = 0; get = 1; set = 2; (* AttrMsg and LinkMsg id *) shallow = 0; deep = 1; (* CopyMsg id *) load = 0; store = 1; (* FileMsg id*) (* AttrMsg class *) Inval = 0; String = 2; Int = 3; Real = 4; LongReal = 5; Char = 6; Bool = 7; TYPE Name = ARRAY 32 OF CHAR; Object = POINTER TO ObjDesc; Dummy = POINTER TO DummyDesc; Library = POINTER TO LibDesc; ObjMsg = RECORD (* Base type of all messages sent to objects. *) stamp: LONGINT; (* Message time stamp. *) dlink: Object (* Sender of the message. *) END; Handler = PROCEDURE (obj: Object; VAR M: ObjMsg); ObjDesc = RECORD (* Base type of all objects. *) stamp: LONGINT; (* Time stamp of last message processed by object. *) dlink, (* Next object in the message thread. *) slink: Object; (* Next object in a list of objects. *) lib: Library; ref: INTEGER; (* Library and reference number of object. *) handle: Handler (* Message handler. *) END; (* Set, get and enumerate the attributes of an object. *) AttrMsg = RECORD ( ObjMsg ) id: INTEGER; (* get, set or enum. *) Enum: PROCEDURE (name: ARRAY OF CHAR); (* Called by object to enumerate attribute names. *) name: Name; (* Name of the attribute to be set or retrieved. *) res: INTEGER; (* Return result: < 0 = no response, >= 0 action completed. *) class: INTEGER; (* Attribute class (Inval, String, Int, Real, LongReal, Char or Bool). *) i: LONGINT; x: REAL; y: LONGREAL; c: CHAR; b: BOOLEAN; s: ARRAY 64 OF CHAR END; (* Link objects with each other or retrieve the link structure between objects *) LinkMsg = RECORD ( ObjMsg ) id: INTEGER; (* get, set or enum. *) Enum: PROCEDURE (name: ARRAY OF CHAR); (* Called by object to enumerate link names. *) name: Name; (* Link name. *) res: INTEGER; (* Return result: < 0 = no response, >= 0 action completed. *) obj: Object (* Value of the link to be set, or link result. *) END; (* Request to an object to make a copy of itself *) CopyMsg = RECORD ( ObjMsg ) id: INTEGER; (* Copy style: deep or shallow. *) obj: Object (* Result of the copy operation. *) END; (* Request to an object to bind itself to a library. *) BindMsg = RECORD ( ObjMsg ) lib: Library (* Library where object should be bound. *) END; (* Request to an object to load/store itself. *) FileMsg = RECORD ( ObjMsg ) id: INTEGER; (* load or store *) len: LONGINT; (* Length of the object data on loading. *) R: Files.Rider (* Rider with which to load or store data. *) END; (* Search request for an object with the specified name. *) FindMsg = RECORD ( ObjMsg ) name: Name; obj: Object (* Result object, if found. *) END; (* A placeholder object created for objects that cannot be loaded. *) DummyDesc = RECORD ( ObjDesc ) GName: Name; (* Generator procedure of failed object. *) END; (* (Hidden) Data structure containing the objects of a library. *) Index = POINTER TO IndexDesc; IndexDesc = RECORD END; (* (Hidden) Map of (ref) numbers and corresponding object names. *) Dictionary = POINTER TO DictionaryDesc; DictionaryDesc = RECORD END; LibDesc = RECORD (* Container for persistent objects. *) ind: Index; (* Library contents. *) name: Name; (* name of the library. Private library when "", else public library. *) dict: Dictionary; (* Object names. *) maxref: INTEGER; (* Highest ref number used in library. *) (* Return a free reference number. *) GenRef: PROCEDURE (L: Library; VAR ref: INTEGER); (* Return the object with the indicated reference number. *) GetObj: PROCEDURE (L: Library; ref: INTEGER; VAR obj: Object); (* Insert an object under the indicated reference number. *) PutObj: PROCEDURE (L: Library; ref: INTEGER; obj: Object); (* Free object with indicated reference number. *) FreeObj: PROCEDURE (L: Library; ref: INTEGER); (* Initialize/load library with L.name. *) Load: PROCEDURE (L: Library); (* Store library under L.name. *) Store: PROCEDURE (L: Library) END; NewProc = PROCEDURE (): Library; (* Library generator. *) EnumProc = PROCEDURE (L: Library); (* Enumerator of public libraries *) VAR LibBlockId: CHAR; (* Identification character as first character of a Library file. *) NewObj: Object; (* Newly generated objects are returned here. *) PROCEDURE Stamp (VAR M: ObjMsg); (* Timestamp a message. *) (* Search, load and cache a public library. *) PROCEDURE ThisLibrary (name: ARRAY OF CHAR): Library; (* Free library from public library cache *) PROCEDURE FreeLibrary (name: ARRAY OF CHAR); (* Enumerate public libraries. Don't free libraries during enumeration! *) PROCEDURE Enumerate (P: EnumProc); (* Register a new library file extension and its associated generator procedure. *) PROCEDURE Register (ext: ARRAY OF CHAR; new: NewProc); (* Load a standard object library from position pos in file f. *) PROCEDURE LoadLibrary (L: Library; f: Files.File; pos: LONGINT; VAR len: LONGINT); (* Store a standard object library at position pos in file f. *) PROCEDURE StoreLibrary (L: Library; f: Files.File; pos: LONGINT; VAR len: LONGINT); (* Initialize a standard object library. *) PROCEDURE OpenLibrary (L: Library); (* Given an object name, return the object reference number from the dictionary. *) PROCEDURE GetRef (VAR D: Dictionary; name: ARRAY OF CHAR; VAR ref: INTEGER); (* Allocate a key (any integer < 0) to a name. *) PROCEDURE GetKey (VAR D: Dictionary; name: ARRAY OF CHAR; VAR key: INTEGER); (* Get name associated with a key/reference number. *) PROCEDURE GetName (VAR D: Dictionary; key: INTEGER; VAR name: ARRAY OF CHAR); (* Associate a name with a reference number. *) PROCEDURE PutName (VAR D: Dictionary; key: INTEGER; name: ARRAY OF CHAR); END Objects. (* Remarks: 1. Objects and Messages Objects and the messages sent to them are both types in the Oberon system. Just as we can extend an object by defining an object-subtype, we can extend a message by defining a message sub-type. As root of the object and message type hierarchies we have the types Objects.Object and Object.ObjMsg respectively. We will refering to extensions of these types as Objects and Messages respectively. This way of organizing things allows us to send a message of any type to an object of any type (even when the receiving object might not make sense of the message). As an examples of an object we can mention the Frames of module Display (visual objects). Frames have a set of associated messages called frame messages (i.e. messages sent to frames). A base type called Display.FrameMsg is an extension of Object.ObjMsg and the base of the frame messages. The module Objects define the object messages, i.e. the messages that all objects understand. Objects are allocated on the heap and messages temporarily on the stack. 2. Message Handlers Message handlers process the message sent to an object. A message handler is a procedure with the definition Objects.Handler. A message handler receives as first parameter the object the message is sent to, and as second parameter the message itself. The message handler does message type tests to discrimate between the different message types it receives, and acts accordingly to each message type (most of the actions are prescribed the messages defined in modules like Objects and Display). The message handler of a newly created object is "installed" in an object by assigning it to the field handle of the object. A typical handler might look as follows: PROCEDURE MyHandler(obj: Object; VAR M: ObjMsg); BEGIN IF M IS Objects.AttrMsg THEN WITH M: Objects.AttrMsg DO ... END ELSIF M IS Objects.CopyMsg THEN WITH M: Objects.CopyMsg DO ... END ELSE (* message not understood by handler. *) END END MyHandler; To create a new object, we first have to introduce a new object type, allocate a new instance on the heap and attach the message handler: TYPE MyObj = POINTER TO MyObjDesc; MyObjDesc = RECORD (Objects.ObjDesc) (* Extension of Objects.ObjDesc. *) A, B: LONGINT; (* Object instance variables. *) END; PROCEDURE CreateObj; VAR obj: MyObj; BEGIN NEW(obj); (* allocate a new object on the heap *) obj.handle := MyHandler; (* attach the message handler. *) END CreateObj; Here we created a new object type with two additional instance variables A and B. To open up access to the instance variables in the message handler, we will need to modify the message handler slightly: PROCEDURE MyHandler(obj: Object; VAR M: ObjMsg); BEGIN WITH obj: MyObj DO (* Open up access to the instance variables of MyObj. *) IF M IS Objects.AttrMsg THEN WITH M: Objects.AttrMsg DO ... END ELSIF M IS Objects.CopyMsg THEN WITH M: Objects.CopyMsg DO ... END ELSE (* message not understood by handler. *) END END END MyHandler; This change also means that MyHandler can only be safely attached to objects (or extensions) of type MyObj; attaching the handler to objects of other types will cause a runtime exception (trap) when trying to open access to the fields of MyObj. Sending a message to an object involves allocating it on the stack, filling out the message fields, and calling the object message handler. For example: VAR obj: MyObj; PROCEDURE GetName; VAR M: Objects.AttrMsg; (* Allocate message on the stack. *) BEGIN M.id := Objects.get; M.name := "Name"; M.res := -1; (* Fill out message fields *) obj.handle(obj, M); (* Send message. *) Out.String(M.s); Out.Ln; (* Process result. *) END GetName; You are allowed to define new message types for your own objects, in a similar manner as shown in the message definitions above. Note how many of the messages have id fields; these indicate different sub-operations a message requests. The id values are declared per message as INTEGER constants at the beginning of the module. 3. Forwarding and Broadcasts Objects may forward messages to other objects. This is typically done when an object cannot handle a message itself or does not even know the message. Sometimes messages are sent in such a way that each object does some handle of a message, and then forwards it anyway to all other objects it controls. This we call message broadcasting. Messages thus pass from one object to another in ways only known to the objects themselves. The route a message follows we call the message path. 4. Time stamps During a message broadcast, more than one message path may lead to the same object, resulting in the object receiving a the message many times (i.e. exactly once for each message path). To allow an object to determine if it has already processed a message, each message that is broadcast is given a timestamp. The receiving object remembers the message timestamp in its field stamp, and can compare it against a later message received. Due to message broadcasts occuring during a message broadcast itself (i.e. recursive broadcasts), you should not assume that message arrive in time stamp sequence. The stamp is a LONGINT value incremented on each broadcast by the procedure Stamp. 5. The Message Thread The message thread informs an object of the path a message followed to reach it, and can be used to implement path dependent behaviour. The dlink field in the ObjMsg points to the last forwarder of the message. The dlink field of the latter object contains the previous object in the path, and so onwards until the beginning of the path (the thread points backwards). Due to recursive message broadcasts the dlink field in the message and the objects themselves should be saved on the stack before the values are changed: (* Forward a message from one object to another. *) PROCEDURE SendMsg(from, to: Objects.Object; VAR M: Objects.ObjMsg); VAR p, p0; Objects.Object; BEGIN p := from.dlink; p0 := M.dlink; (* save *) from.dlink := M.dlink; (* hook sender in dlink chain *) M.dlink := from; (* set sender of the message *) to.handle(to, M); from.dlink := p; M.dlink := p0 (* restore *) END SendMsg; A message sender may refuse to add itself to the message thread (for optimization purposes). This has no effect but to make it invisible to further recipients in the message path. The message thread is typically used in the display space (see module Display) to find out how a message travelled from the display root to an object located somewhere in the display space. 6. The slink field The slink field links objects together in a list so that they can be passed around as a group. Never assume that the slink list remains the same before and after a message broadcast. 7. Libraries Libraries are indexed collections of objects. An object belonging to a library is said to be bound to the library (otherwise it is free). When bound, an objects obtains an index or reference number (>= 0) in its library (and its lib and ref fields are set accordingly). The Objects module implements the standard object libraries. These allow you to store the library and its contents in an atomic action to disk. On disk, reference numbers instead of pointers are used to refer to objects. Thus pointers and reference numbers are swizzled (exchanged) when loading or storing libraries. The procedures Gadgets.ReadRef and Gadgets.WriteRef use the library mechanism to transparently read and write object pointers to disk. The library dictionary mechanism allows you to attach names to objects (more concretely to reference numbers). An object belonging to public library L and having the name O in the dictionary, is refered to as "L.O" (note the similarity with "M.P"). Sometimes the dictionary is also used to attach keys (< 0) to strings. Keys are used to save string space when storing libraries. Libraries are divided into public and private libraries. Public libraries are named (i.e. L.name # "") and are cached in memory on loading. The garbage collector will uncache a library automatically if it is not required any more. The Libraries.Panel allow you to manipulate the contents of public libraries. Private libraries are primarily used as a means to make objects persistent in documents and are never cached. The default public library file extension is "Lib". It is possible to add new types of libraries by registering new library extensions and the associated library generator. 8. The Object Messages All objects should implement handlers for the so-called object messages defined in this module. The object messages are the LinkMsg (for structure building and exploration), the CopyMsg (for copying an object), the BindMsg (for binding an object to a library), the AttrMsg (for setting and getting attributes), the FileMsg (for loading and storing), and the FindMsg (for locating named objects). 9. The LinkMsg The LinkMsg is used to link objects between each other i.e. setting a pointer in one object to point to another. The links must be identified by name. Most displayable gadgets have a "Model" link that points to a model gadget. 10. The CopyMsg Shallow copy means copying an object but reusing its descendants, and deep copy means copying all objects reachable from a certain root object. Due to the DAG nature of the display space, the deep copy message arrives once or more times at an object, in which case it only should copy itself once to guarantee structure preserving copies. The following shows that an object should cache the first copy that it makes of itself in the dlink field, which is then returned on receiving the message a second time: VAR F0: Frame; (* the copy goes here *) IF M IS Objects.CopyMsg THEN WITH M: Objects.CopyMsg DO IF M.stamp = F.stamp THEN M.obj := F.dlink (* copy msg arrives again *) ELSE (* first time copy message arrives *) NEW(F0); F.stamp := M.stamp; F.dlink := F0; CopyFrame(M, F, F0); M.obj := F0 END END END 11. The BindMsg The BindMsg is a request to an object to bind itself to a library. By convention, an object can migrate from library to library, except when bound to a public library. Binding allocates a reference number to an object which is conveniently used as a pointer alias between objects stored in a file. PROCEDURE BindObj(obj: Objects.Object; lib: Objects.Library); VAR ref: INTEGER; name: ARRAY 32 OF CHAR; BEGIN IF lib # NIL THEN IF (obj.lib = NIL) OR (obj.lib.name[0] = 0X) & (obj.lib # lib) THEN (* free, or belongs to a private library *) lib.GenRef(lib, ref); (* allocate reference number *) IF ref >= 0 THEN (* successful *) lib.PutObj(lib, ref, obj); END END END END BindObj; 12. The AttrMsg The attribute message is used to enumerate, set or retrieve an object attribute. The class field of the AttrMsg indicate what the type of an attribute is. Each object should have a Name attribute and a Gen attribute (both of type String). The name attribute refers to the intrinsic name of an object (it should not be confused with the name the object might have in a dictionary). Copying an object results in two objects with the same names. The FindMsg locates an object with a certain intrinsic name. The Gen attribute indicates the name of the object generator (in the form "M.P"). Calling the generator of an object results in the freshly created object attached to Objects.NewObj, from where it is picked up by commands like Gadgets.Insert. 13. The FileMsg The FileMsg is a request to an object to write or read its state to or from a Rider. An object should always read and write the same number of bytes, otherwise traps may result. It is recommended to use version numbers to distinguish objects of different generations from each other and so allow for smooth upgrading to new file formats for older objects. The FileMsg is typically used when reading or writing a library from or to disk. 14. The FindMsg The FindMsg is a request to an object to locate the object with the indicated intrinsic name. Should an object not know of an object with such a name, it should forward the message to all objects it controls (children). By convention, searching should be done in a bread-first manner between descendants of a container. 15. Keys Each library has a dictionary of (key, name) pairs. The key is either positive or zero, in which case it is regarded as a reference number in the library (with associated object name), or negative, in which case it is simply a short way of refering to a string (an atom). The latter reduces the space used when the same string appears many times in a library file. 16. Dummies Dummies are objects created in place of objects that cannot be loaded into memory (module missing). Pointers to Dummies are often set to NIL by the application itself. 17. Extended Libraries It is possible to add new library types to the system. New types are distinguished by filename extensions that are registered by Objects.Register. The NewProc is called by Objects.ThisLibrary to create an empty instance of the new library type. The name field is filled in, after which the Load procedure of the library is called to load the library from disk. In accordance, the Store procedure stores the library under its name to disk. The LoadLibrary and StoreLibrary procedures implement the default behaviour for the standard object libraries. *)