Meta-Object
Protocols
Contents
Overview
XMF is an environment for language design and deployment. Languages control the structure and behaviour of the values that they denote. In this sense, language design is a meta-activity and requires a meta-language that represents the structure and behaviour of the language components. XMF provides a meta-circular object-oriented kernel language called XOCL. The meta-circular property means that XOCL is defined completely in itself. This property validates XOCL as a meta-language. Object-orientation provides a basis for application extension and reuse through inheritance and modularity through encapsulation.XOCL is both meta-circular and object-oriented, it is suitable for language definition where languages can be easily constructed as modular extensions of the basic XOCL language. Instantiations of XOCL are languages and extensions of XOCL are meta-languages.
All languages have key semantic features that can be represented as an interface in the definition of the language. Consider a language with an operational semantics. In this case programs written in the language may be viewed as controlling a machine that contains the state of the execution at any given snapshot in time. The key semantic features of the language form the API of the machine.
Given a language L, we would like to construct a new language that is L-like. If L is defined using object-oriented principles then it is attractive to construct the new language as an extension of {\em L} using inheritance. Syntax structures and values of the new language can be defined by extending the appropriate features of L. We would like to construct the semantics of the new language using the same approach. If we have constructed the semantics of L by encapsulating the key features as an implementation of the API as described above, then the new language semantics can be defined by inheriting and extending these operations as appropriate.
Where the semantics of a language has been constructed using object-oriented principles, the resulting collection of classes and operations is referred to as a meta-object-protocol. This document describes the XOCL MOP.
To top.
Message Passing
XOCL performs computation in terms of messages between elements. A message consists of a name andsome data. A message is sent from a source element to a target element. The target element receives the message, performs appropriate computation and returns a result. Messages between elements are synchronous: the source element halts computation and waits the return value from the target element.Message passing occurs when the source element performs an expression of the form: o.m(x,y,z,...) where o is the target element, m is the message name and x, y, z etc. is the data, or parameters, of the message.
Message passing is defined by the MOP component referred to as the message passing protocol. The protocol is defined by the meta-class of the target element and is called sendInstance:
context Element
@Operation send(message,args):Element
self.of().sendInstance(self,message,args)
end
context Classifier
@Operation sendInstance(element,name,args)
// Get all the operations of element with the
// correct name and arity. Select the most
// specific and invoke it.
let arity = args->size then
operations = element.of().allOperations()
->asSeq
->select(o | o.name = name and o.arity() = arity)
in if operations->isEmpty
then element.error("Cannot handle " + message + "/" + arity)
else operations->head.invoke(element,args)
end
end
end
To top.
Object Creation
Objects are created by sending a new message to a class together with some initialization data. The preferred way of invoking the new operation of a class is to apply the class as an operator to the initialization arguments. This is preferred because it is succinct and because the compiler and XMF VM can handle class instantiation more efficiently in this form:context Classifier
@Operation invoke(target:Element,args:Seq(Element)):Element
self.new(args)
end
context Classifier
@Operation new(args:Seq(Element)):Element
self.new().init(args)
end
Sub-classes of Classifier can define their own instantiation protocol. Typically this will use {\tt super} to create an instance using the default protocol and then perform some extra computation to initialize the new instance; however, in principle the instantiation protocol can by-pass the default protocol altogether. To create a raw instance of a class C and add a single slot named "x" with initial value 10 you can do the following:
let o = Kernel_mkObj()
in Kernel_setOf(o,C);
Kernel_addAtt(o,Symbol("x",100));
o
end
To top.
Slot Access
Objects have internal storage in the form of named slots. Access to a
slot value is via the object and the name of the slot. Slots are named
using symbols. Access is defined by the object's slot access protocol. The slot
access protocol is used when an expression of the form o.a is
performed. Access involves checking that the slot exists and then
accessing the value of the slot.The existence of a slot can be checked using the hasSlot operation defined by Object:
context Object
@Operation hasSlot(name):Boolean
self.of().hasInstanceSlot(self,name)
end
context Class
@Operation hasInstanceSlot(object,name)
Kernel_hasSlot(object,name)
end
context Object
@Operation get(name:String):Element
self.of().getInstanceSlot(self,name)
end
context Class
@Operation getInstanceSlot(object,name)
Kernel_getSlotValue(object,name)
end
To top.
Slot Update
The value of an object's slot can be updated using the object's slot update protocol. A slot is updated when an expression of the form o.a := e is performed. The class Object provides an operation used to set the value of a slot:context Object
@Operation set(name:String,value:Element):Element
self.of().setInstanceSlot(self,name,value);
self
end
context Class
@Operation setInstanceSlot(object,name,value)
Kernel_setSlotValue(object,name,value)
end
To top.
Default Parents
A class is created as an instance of a meta-class. When a class is created it must have some parents. The meta-class defines an operation defaultParents that produces a set of classes that are the default parents for its instances. The basic definition for defaultParents is provided by Classifier:context Classifier
@Operation defaultParents():Set(Classifier)
Set{Element}
end
context Class
@Operation defaultParents():Set(Classifier)
Set{Object}
end
Example
Suppose that we want to define a class of objects that can have standard attribute defined slots in addition to dynamic slots. Attribute defined slots are defined at the class level. Dynamic slots are defined at the object level and can be added and removed dynamically. Both types of slot can be accessed and updated via the standard protocols using o.a and o.a := e expressions.In order to implement these objects we require a new slot access and update protocol. The protocol is defined at the meta-level and is to be called the Elastic protocol. We require two new classes: ElasticObject that is the super-class of all user-defined elastic classes; and, ElasticClass that defines the elastic protocol.
The class ElasticObject uses a table to contain the dynamic slots:
context Root
@Class ElasticObject
@Attribute slots : Table = Table(100) end
end
context ElasticObject
@Operation addSlot(name:String,value)
slots.put(Symbol(name),value)
end
context ElasticObject
@Operation removeSlot(name)
slots.remove(Symbol(name))
end
context ElasticObject
@Operation removeAll()
@For key inTableKeys slots do
self.removeSlot(key.toString())
end
end
context ElasticObject
@Operation incAll()
@For key inTableKeys slots do
self.set(key,self.get(key) + 1)
end
end
context Root
@Class ElasticClass extends Class
end
context ElasticClass
@Operation defaultParents()
Set{ElasticObject}
end
context ElasticClass
@Operation getInstanceSlot(object,name)
if Kernel_getSlotValue(object,Symbol("slots")).hasKey(name)
then Kernel_getSlotValue(object,Symbol("slots")).get(name)
else super(object,name)
end
end
context ElasticClass
@Operation setInstanceSlot(object,name,value)
if Kernel_getSlotValue(object,Symbol("slots")).hasKey(name)
then Kernel_getSlotValue(object,Symbol("slots")).put(name,value)
else super(object,name,value)
end
end
context ElasticClass
@Operation hasInstanceSlot(object,name)
if Kernel_getSlotValue(object,Symbol("slots")).hasKey(name)
then true
else super(object,name)
end
end
context ElasticClass
@Operation sendInstance(element,message,args)
format(stdout,"Sending message ~S(~{,~;~S~})~%",Seq{message,args});
super(element,message,args)
end
context ElasticClass
@Operation new(args)
format(stdout,"Creating a new instance of an elastic class~%");
super(args)
end
context Root
@Class C metaclass ElasticClass
@Attribute s : Element end
@Operation test()
self.addSlot("x",100);
self.addSlot("y",200);
self.addSlot("z",300);
self.x := self.x + 1;
self.y := self.y + 1;
self.z := self.z + 1;
self.incAll();
self.s := self.x + self.y + self.z;
self.removeAll();
s
end
end