edu.vub.at.objects
Interface MirrorRoot


 MirrorRoot

This is the interface of the root node of the intercessive mirrors delegation hierarchy.

Intercessive mirrors are always tied to a particular 'base' object. The default intercessive mirror is named 'defaultMirror' and is an object that understands all meta-level operations applicable on objects, implementing them using default semantics. It can be thought of as being defined as follows:

 def mirrorroot := object: {
   def base := object: { nil } mirroredBy: self // base of the mirror root is an empty mirage
   def init(b) {
     base := b
   }
   def invoke(@args) {  }
   def select(@args) {  }
   ...
 } taggedAs: [ Mirror ]
 

This object can then simply be extended / composed by other objects to deviate from the default semantics. Note that the default semantics is applied to 'base' and *not* 'self.base', in other words: although child mirrors can define their own 'base' field, it is not taken into consideration by the mirror root. This also ensures that the mirror root is not abused to enact upon a mirage for which it was not assigned to be the mirror.

Hence, 'mirrors' are simply objects with the same interface as this mirrorroot object: they should be able to respond to all meta-level messages and have a 'base' field.

Author:
tvcutsem, smostinc

Method Summary
 Nil addField(Field field)
          This structural meta-level operation adds a field object to the receiver mirror's base object.
 Nil addMethod(Method method)
          This structural meta-level operation adds a method to the receiver mirror's base object.
 Nil addSlot(Method slot)
          This structural meta-level operation adds a slot object to the receiver mirror's base object.
 at.objects.natives.NATText asCode()
          This behavioural meta-level operation reifies the act of representing the base object as self-containing source code.
 at.objects.mirrors.NATMirage base()
          The read-only field containing the mirror's base-level mirage.
 Object clone()
          This meta-level operation reifies the act of cloning the base-level object.
 Nil defineField(Symbol name, Object value)
          This meta-level operation reifies field definition.
 Closure doesNotUnderstand(Symbol selector)
          This behavioural meta-level operation reifies a failed dynamic method or field lookup.
 Object eval(Context ctx)
          This behavioural meta-level operation reifies the evaluation of abstract grammar objects into values.
 Field grabField(Symbol selector)
          This structural meta-level operation allows the metaprogrammer to reify a field of the receiver mirror's base object.
 Method grabMethod(Symbol selector)
          This structural meta-level operation allows the metaprogrammer to reify a method defined on the receiver mirror's base object.
 Method grabSlot(Symbol selector)
          This structural meta-level operation allows the metaprogrammer to reify a slot of the receiver mirror's base object.
 Object invoke(Object delegate, MethodInvocation invocation)
          This meta-level operation reifies synchronous message sending ("method invocation").
 Object invokeField(Object receiver, Symbol selector)
          This meta-level operation reifies "field selection".
 Boolean isCloneOf(Object other)
          This meta-level operation determines whether this mirror's base object is a clone of the parameter object.
 Boolean isExtensionOfParent()
          This structural meta-level operation returns whether or not the receiver mirror's base object is an extension of its parent object.
 Boolean isRelatedTo(Object object)
          This meta-level operation determines whether this mirror's base object is related to the parameter object by a combination of cloning and extension operators.
 Boolean isTaggedAs(TypeTag type)
          Tests whether the receiver mirror's base object is tagged as a particular type.
 Table listFields()
          This structural meta-level operation allows access to all of the fields defined on the receiver mirror's base object.
 Table listMethods()
          This structural meta-level operation allows access to all of the methods defined on the receiver mirror's base object.
 Table listSlots()
          This structural meta-level operation allows access to all of the slots defined on the receiver mirror's base object.
 Object newInstance(Table initargs)
          This meta-level operation reifies instance creation.
 Object pass()
          This behavioural meta-level operation reifies object serialization.
 at.objects.natives.NATText print()
          This behavioural meta-level operation reifies the act of printing the base object in the read-eval-print loop.
 Object quote(Context ctx)
          This behavioural meta-level operation reifies the quotation of abstract grammar elements.
 Object receive(AsyncMessage message)
          This behavioural meta-level operation reifies the act of receiving an asynchronous message.
 Object removeSlot(Symbol selector)
          This structural meta-level operation removes a slot from the object.
 Object resolve()
          This behavioural meta-level operation reifies object deserialization.
 Boolean respondsTo(Symbol selector)
          This meta-level method is used to determine whether an object has a field or method corresponding to the given selector, without actually invoking or selecting any value associated with that selector.
 Closure select(Object receiver, Symbol selector)
          This meta-level operation reifies first-class field or method selection.
 Object send(Object receiver, AsyncMessage message)
          This behavioural meta-level operation reifies the act of sending an asynchronous message.
 Table typeTags()
          Returns all of the local type tags of this object.
 
Methods inherited from interface edu.vub.at.objects.Object
super
 

Method Detail

base

at.objects.mirrors.NATMirage base()
The read-only field containing the mirror's base-level mirage.


send

Object send(Object receiver,
            AsyncMessage message)
This behavioural meta-level operation reifies the act of sending an asynchronous message. When the base-level AmbientTalk code rcv<-m() is evaluated in the context of an object o, an asynchronous message <-m() is first created by the current actor mirror. Subsequently, this message needs to be sent to the receiver. This meta-level operation is reified by this method, as if by invoking:
(reflect: o).send(message)
The default behaviour is to access the current actor's mirror and to ask the actor to send the message in this object's stead by invoking
actor.send(message)

Parameters:
receiver - the object designated to receive the asynchronous message
message - the asynchronous message to be sent by this object
Returns:
the result of message sending, which will be the value of an asynchronous message send expression.

receive

Object receive(AsyncMessage message)
This behavioural meta-level operation reifies the act of receiving an asynchronous message. When an asynchronous message is sent to an AmbientTalk object, its mirror is notified of this event by the invocation of this method. The method is invoked in the same execution turn as the turn in which the message is sent. This allows the receiver (e.g. a custom eventual reference proxy) to intervene in the message sending process and return a value different than the default nil value.

The default behaviour of a mirror on a local reference in response to the reception of an async message is to schedule this message for execution in a later turn in its owner's message queue. The actor will then later process the message by invoking

msg.process(self)
In turn, the default message processing behaviour is to invoke the method corresponding to the message's selector on this object. Hence, usually a receive operation is translated into a invoke operation in a later turn. The reason for having a separate receive operation is that this enables the AmbientTalk meta-level programmer to distinguish between synchronously and asynchronously received messages. Far references react to receive by transmitting their message to their remote target.

Parameters:
message - the message that was asynchronously sent to this object
Returns:
nil, by default

invoke

Object invoke(Object delegate,
              MethodInvocation invocation)
This meta-level operation reifies synchronous message sending ("method invocation"). Hence, the meta-level equivalent of the base-level code o.m() is:
(reflect: o).invoke(o,`m,[])
. Method invocation comprises selector lookup and the application of the value bound to the selector. Selector lookup first queries an object's local fields, then the method dictionary:

Note also that the first argument to invoke denotes the so-called "receiver" of the invocation. It is this object to which the self pseudo-variable should be bound during method execution.

Parameters:
delegate - the object to which self is bound during execution of the method
invocation - an object encapsulating at least the invocation's selector (a ATSymbol) and arguments (a ATTable).
Returns:
by default, the object returned from the invoked method
See Also:
for what happens if the selector is not found.

invokeField

Object invokeField(Object receiver,
                   Symbol selector)
This meta-level operation reifies "field selection". In other words, the base-level code o.m is interpreted at the meta-level as: (reflect: o).invokeField(o, `m) This meta-level operation is nearly identical to base_invoke(ATObject, ATMethodInvocation) with one important difference. When the selector is bound to a field storing a closure, this meta-level operation does not auto-apply the closure, but returns the closure instead. For all other cases, the following equality holds: o.m == o.m() or, at the meta-level: (reflect: o).invokeField(o, `m) == (reflect: o).invoke(o, MethodInvocation.new(`m, [])) This effectively means that for client objects, it should not matter whether a property is implemented as a field or as a pair of accessor/mutator methods.

Parameters:
receiver - the base-level object from which the 'field' should be selected.
selector - a symbol denoting the name of the method, accessor or mutator to be invoked
Returns:
the value of a field, or the return value of a nullary method.

respondsTo

Boolean respondsTo(Symbol selector)
This meta-level method is used to determine whether an object has a field or method corresponding to the given selector, without actually invoking or selecting any value associated with that selector.

The lookup process is the same as that for the invoke operation (i.e. not only the object's own fields and methods are searched, but also those of its dynamic parents).

Parameters:
selector - a symbol denoting the name of a field (accessor or mutator) or method
Returns:
a boolean denoting whether the object responds to o.selector

doesNotUnderstand

Closure doesNotUnderstand(Symbol selector)
This behavioural meta-level operation reifies a failed dynamic method or field lookup. When method invocation or field selection fails to find the selector in the dynamic parent chain of an object, rather than immediately raising an SelectorNotFound exception, the mirror of the original receiver of the method invocation or field selection is asked to handle failed lookup.

The default behaviour of doesNotUnderstand is to raise an SelectorNotFound exception.

This method is very reminiscent of Smalltalk's well-known doesNotUnderstand: and of Ruby's method_missing methods. There are, however, two important differences:

Parameters:
selector - a symbol denoting the name of a method or field that could not be found
Returns:
by default, this operation does not return a value, but raises an exception instead.
Throws:
edu.vub.at.exceptions.XSelectorNotFound - the default reaction to a failed selection

pass

Object pass()
This behavioural meta-level operation reifies object serialization. When an AmbientTalk object crosses actor boundaries, e.g. by means of parameter passing, as a return value or because it was explicitly exported, this meta-level operation is invoked on the object's mirror.

This operation allows objects to specify themselves how they should be parameter-passed during inter-actor communication. The interpreter will never pass an object to another actor directly, but instead always parameter-passes the return value of invoing pass() on the object's mirror.

Mirrors on by-copy objects implement pass as follows:

def pass() { base }
Mirrors on by-reference objects implement pass by returning a far reference to their base-level object.

Returns:
the object to be parameter-passed instead of this object. For objects, the default is a far reference to themselves. For isolates, the default is to return themselves.

resolve

Object resolve()
This behavioural meta-level operation reifies object deserialization. When an AmbientTalk object has just crossed an actor boundary (e.g. because of inter-actor message sending) this meta-level operation is invoked on the object's mirror.

This meta-level operation gives objects a chance to tell the interpreter which object they actually represent, because the object retained after parameter passing is the return value of the resolve operation.

Mirrors on by-copy objects, like isolates, implement resolve as follows:

def resolve() { base }
In other words, by-copy objects represent themselves. By-reference objects are paremeter passed as far references. Mirrors on far references implement resolve by trying to resolve the far reference into a local, regular object reference (which is possible if the object they point to is located in the actor in which they just arrived). If it is not possible to resolve a far reference into a local object, the far reference remains a far reference.

Note that for isolates, this operation also ensures that the isolate's lexical scope is rebound to the lexical root of the recipient actor.

Returns:
the object represented by this object
Throws:
edu.vub.at.exceptions.XObjectOffline - if a far reference to a local object can no longer be resolved because the object has been taken offline

select

Closure select(Object receiver,
               Symbol selector)
This meta-level operation reifies first-class field or method selection. Hence, the base-level evaluation of o.&x is interpreted at the meta-level as:
(reflect: o).select(o, `x)
The selector lookup follows the same search rules as those for invoke. That is: first an object's local fields and method dictionary are searched, and only then the object's dynamic parent.

The select operation can be used to both select fields or methods from an object. When the selector is bound to a method, the return value of select is a closure that wraps the found method in the object in which the method was found. This ensures that the method retains its context information, such as the lexical scope in which it was defined and the value of self, which will be bound to the original receiver, i.e. the first argument of select.

If the selector matches a field, an accessor is returned. If the selector ends with :=, a mutator is returned instead. An accessor is a nullary closure which upon application yields the field's value. A mutator is a unary closure which upon application assigns the field to the specified value. Even for fields already bound to a closure, selecting the field returns an accessor closure, not the bound closure itself.

Parameters:
receiver - the dynamic receiver of the selection. If the result of the selection is a method, the closure wrapping the method will bind self to this object.
selector - a symbol denoting the name of the field or method to select.
Returns:
if selector is bound to a field, an accessor or mutator for the field; otherwise if the selector is bound to a method, a closure wrapping the method.
See Also:
for what happens if the selector is not found.

defineField

Nil defineField(Symbol name,
                Object value)
This meta-level operation reifies field definition. Hence, the base-level code def x := v evaluated in a lexical scope lex is interpreted at the meta-level as:
(reflect: lex).defineField(`x, v)
Invoking this meta-level operation on an object's mirror adds a new field to that object. An object cannot contain two or more fields with the same name.

Parameters:
name - a symbol denoting the name of the new field
value - the value of the new field
Returns:
nil
Throws:
edu.vub.at.exceptions.XDuplicateSlot - if the object already has a local field with the given name

clone

Object clone()
This meta-level operation reifies the act of cloning the base-level object. Hence, the code clone: o is interpreted at the meta-level as
(reflect: o).clone()
AmbientTalk's default cloning semantics are based on shallow copying. A cloned object has copies of the original object's fields, but the values of the fields are shared between the clones. A clone has the same methods as the original object. Methods added at a later stage to the original will not affect the clone's methods and vice versa. This means that each objects has its own independent fields and methods.

If the cloned AmbientTalk object contains programmer-defined field objects, each of these fields is re-instantiated with the clone as a parameter. The clone is intialized with the re-instantiated fields rather than with the fields of the original object. This property helps to ensure that each object has its own independent fields.

If the object has a shares-a relationship with its parent, the object and its clone will share the same parent object. Shares-a relationships are the default in AmbientTalk, and they match with the semantics of shallow copying: the dynamic parent of an object is a regular field, hence its contents is shallow-copied.

If the object has an is-a relationship with its parent object, a clone of the object will receive a clone of the parent object as its parent. Hence, is-a relationships "override" the default shallow copying semantics and recursively clone the parent of an object up to a shares-a relationship.

If a mirage is cloned, its mirror is automatically re-instantiated with the new mirage, to ensure that each mirage has its independent mirror.

Returns:
a clone of the mirror's base object

newInstance

Object newInstance(Table initargs)
This meta-level operation reifies instance creation. The default implementation of an AmbientTalk object's new method is:
def new(@initargs) { (reflect: self).newInstance(initargs) }
Creating a new instance of an object is a combination of: The default implementation is:
def newInstance(initargs) {
  def instance := self.clone();
  instance.init(@initargs);
  instance;
}
 
Instance creation in AmbientTalk is designed to mimick class instantiation in a class-based language. Instantiating a class c requires allocating a new instance i and then invoking the constructor on that new instance. In AmbientTalk, class allocation is replaced by object cloning. The benefit is that an instantiated object its variables are already initialized to useful values, being those of the object from which it is instantiated. The init method plays the role of "constructor" in AmbientTalk.

Parameters:
initargs - a table denoting the actual arguments to be passed to the init method
Returns:
the new instance

addField

Nil addField(Field field)
This structural meta-level operation adds a field object to the receiver mirror's base object. An object cannot contain two or more fields with the same name. Note that the field object passed as an argument serves as a prototype object: the actual field object added is an instance of the passed field object. A field object should always have an init method that takes as an argument the new host object to which it is added. This is often useful, as the behaviour of a field may depend on the object in which it resides. Because addField creates a new instance of the field, this gives the field object a chance to properly refer to its new host.

As an example, here is how to add a read-only field foo initialized to 5 to an object obj:

def makeConstantField(nam, val) {
   object: {
     def new(newHost) { self }; // singleton pattern
     def name := nam;
     def readField() { val };
     def writeField(newVal) { nil };
   }
 };
 (reflect: obj).addField(makeConstantField(`foo, 5));
 

Parameters:
field - the prototype field object whose instance should be added to the receiver's base object
Returns:
nil
Throws:
edu.vub.at.exceptions.XDuplicateSlot - if the base object already has a field with the same name as the new field

addMethod

Nil addMethod(Method method)
This structural meta-level operation adds a method to the receiver mirror's base object. An object cannot contain two or more methods with the same name.

Parameters:
method - a method object to add to the receiver's base object's method dictionary.
Returns:
nil
Throws:
edu.vub.at.exceptions.XDuplicateSlot - if a method with the new method's selector already exists in the base object.

grabField

Field grabField(Symbol selector)
This structural meta-level operation allows the metaprogrammer to reify a field of the receiver mirror's base object. Hence, unlike select and lookup, grabField returns a field object rather than the value bound to the field. For example: one could express obj.super := val at the meta-level as:
 def superField := (reflect: obj).grabField(`super);
 superField.writeField(val);
 
Another important difference between select, lookup and grabField is that grabField only considers the fields local to the receiver's base object. Fields of lexical or dynamic parent objects are not considered.

Parameters:
selector - a symbol representing the name of the field to select.
Returns:
a mirror on this object's field slot.
Throws:
edu.vub.at.exceptions.XUndefinedSlot - if the field cannot be found within the receiver's base object.

grabMethod

Method grabMethod(Symbol selector)
This structural meta-level operation allows the metaprogrammer to reify a method defined on the receiver mirror's base object. Note that, unlike the select or lookup operations, grabMethod returns the bare method object, i.e. not a closure wrapping the method.

Also, unlike select and lookup, grabField only considers the locally defined methods of an object, methods of lexical or dynamic parent objects are not considered.

Parameters:
selector - a symbol representing the name of the method to grab from the receiver's base object.
Returns:
the bare method object bound to the given selector.
Throws:
edu.vub.at.exceptions.XSelectorNotFound - if the method object cannot be found within the receiver's base object.

listFields

Table listFields()
This structural meta-level operation allows access to all of the fields defined on the receiver mirror's base object. Note that this method only returns the base object's locally defined fields. Fields from parent objects are not returned.

Returns:
a table of field objects (of type Field).
See Also:
for details about the returned field objects.

listMethods

Table listMethods()
This structural meta-level operation allows access to all of the methods defined on the receiver mirror's base object. Note that this method only returns the base object's locally defined methods. Methods from parent objects are not returned.

Returns:
a table of method objects (of type Method).
See Also:
for details about the returned method objects.

addSlot

Nil addSlot(Method slot)
This structural meta-level operation adds a slot object to the receiver mirror's base object. An object cannot contain two or more slots with the same name. A slot is either a method or a closure. A closure serves to encapsulate access to or mutation of a field. Care must be taken with closures when the object to which they are added is cloned or instantiated: the closure will be shared between clones!

As an example, here is how to add a read-only field foo initialized to 5 to an object obj:

 def [accessor,mutator] := /.at.lang.values.createFieldSlot(`foo, 5);
 (reflect: obj).addSlot(accessor);
 

Parameters:
slot - the method representing the slot to be added to the receiver's base object
Returns:
nil
Throws:
edu.vub.at.exceptions.XDuplicateSlot - if the base object already has a slot with the same name as the new slot

grabSlot

Method grabSlot(Symbol selector)
This structural meta-level operation allows the metaprogrammer to reify a slot of the receiver mirror's base object. Hence, unlike select and lookup, grabSlot returns a slot object rather than the value bound to the slot. For example: one could express obj.super := val at the meta-level as:
 def superMutator := (reflect: obj).grabSlot(`super:=);
 superMutator(val);
 
Another important difference between select, lookup and grabSlot is that grabSlot only considers the slots local to the receiver's base object. Slots of lexical or dynamic parent objects are not considered.

Parameters:
selector - a symbol representing the name of the slot to select.
Returns:
a method representing the selected slot.
Throws:
edu.vub.at.exceptions.XUndefinedSlot - if the field cannot be found within the receiver's base object.

listSlots

Table listSlots()
This structural meta-level operation allows access to all of the slots defined on the receiver mirror's base object. Note that this method only returns the base object's locally defined slots. Slots from parent objects are not returned.

Returns:
a table of slot objects (of type Method).
See Also:
for details about the returned slot objects.

removeSlot

Object removeSlot(Symbol selector)
This structural meta-level operation removes a slot from the object. Note that this method only removes slots that are locally defined in the object.

Parameters:
selector - the name of the slot to remove
Returns:
the value to which the slot was previously bound (e.g. a value or a method object)
Throws:
edu.vub.at.exceptions.XSelectorNotFound - if no slot with the given name is found in the object

isExtensionOfParent

Boolean isExtensionOfParent()
This structural meta-level operation returns whether or not the receiver mirror's base object is an extension of its parent object.

In AmbientTalk, all objects are part of a dynamic parent delegation chain: each object has a super field that denotes the object to which to delegate messages the object cannot understand itself. There are, however, two kinds of delegation links:

Examples:
(reflect: (extend: parent with: code)).isExtensionOfParent() => true
(reflect: (share: parent with: code)).isExtensionOfParent() => false
 
Note that accessing the dynamic parent itself is not a meta-level operation, the dynamic parent can simply be accessed from the base level by performing obj.super.

Returns:
whether the base object extends its parent object via an IS-A link or not.

eval

Object eval(Context ctx)
This behavioural meta-level operation reifies the evaluation of abstract grammar objects into values. For objects, this operation returns the base object itself, signifying that the evaluation function defined on objects is the identity function. In other words, objects are self-evaluating. Parse tree objects (first-class abstract grammar elements), however, have dedicated evaluation functions. For example, evaluating x is equivalent to evaluating (reflect: `x).eval(ctx) where ctx is a reification of the current evaluation context.

Parameters:
ctx - a context object that stores the current lexical scope and the current value of self
Returns:
the value of the abstract grammar element denoted by this mirror's base object.
Throws:
edu.vub.at.exceptions.XIllegalUnquote - if an unquote abstract grammar element is evaluated. Such abstract grammar elements should only be encountered in a quoted parse tree.

quote

Object quote(Context ctx)
This behavioural meta-level operation reifies the quotation of abstract grammar elements. Regular objects simply return themselves upon quotation. When an abstract grammar element is quoted, rather than tree-recursively invoking eval on the parse trees, quote is tree-recursively invoked. When encountering an unquote, eval is again invoked on the unquoted subtree, with the context passed as an argument to quote.

Parameters:
ctx - a context object passed on to be used in subsequent evaluations.
Throws:
edu.vub.at.exceptions.XIllegalQuote - exception whenever an unquote-splice unquotation is discovered in an Abstract Grammar node where the resulting table cannot be spliced.

print

at.objects.natives.NATText print()
This behavioural meta-level operation reifies the act of printing the base object in the read-eval-print loop. This operation may be overridden by mirrors to customise the printed representation of their base object.

Returns:
a text value denoting a human-readable representation of the object.

asCode

at.objects.natives.NATText asCode()
This behavioural meta-level operation reifies the act of representing the base object as self-containing source code. This operation may be overridden by mirrors to customise the printed representation of their base object.

Returns:
a text value representating the source code of the object.

isRelatedTo

Boolean isRelatedTo(Object object)
This meta-level operation determines whether this mirror's base object is related to the parameter object by a combination of cloning and extension operators. The default implementation is:
def isRelatedTo(object) {
  self.isCloneOf(object).or: { (reflect: base.super).isRelatedTo(object) }
}

Parameters:
object - the object to compare this mirror's base object to
Returns:
true if the given object is a clone of the base object or a clone of the base object's parents.

isCloneOf

Boolean isCloneOf(Object other)
This meta-level operation determines whether this mirror's base object is a clone of the parameter object. The is-clone-of relation is transitive, so if martin is a clone of sally and sally is a clone of dolly, then martin is a clone of dolly as well. The relation is reflexive: dolly is a clone of itself. The relation is symmetric: dolly is also a clone of sally.

Parameters:
other - the object to check the is-clone-of relationship with.
Returns:
true if the base object and the parameter object are clones (i.e. one was created by cloning the other), false otherwise.

isTaggedAs

Boolean isTaggedAs(TypeTag type)
Tests whether the receiver mirror's base object is tagged as a particular type. The default implementation first compares the object's local type tags to the given type by means of the TypeTag.base_isSubtypeOf(ATTypeTag) method. If no local type is found, the test is applied recursively on this object's dynamic parent. In code:
def isTaggedAs(type) {
  (nil != (self.tagsOf: object).find: { |localType|
    localType.isSubtypeOf(type)
  }).or: { (reflect: base.super).isTaggedAs(type) }
 };
 
The primitive method is: obj taggedAs: type is defined in terms of this method:
 def is: obj taggedAs: type {
  (reflect: obj).isTaggedAs(type)
};
 

Parameters:
type - the type tag object to check for
Returns:
true if this mirror's base object or one of its parent objects is tagged with a subtype of the given type, false otherwise.

typeTags

Table typeTags()
Returns all of the local type tags of this object. The primitive method tagsOf: obj is defined in terms of this method:
 def tagsOf: obj {
  (reflect: obj).typeTags
};
 

Returns:
a table of the type tags that were attached directly to this mirror's base object. The type tags of its parent objects are not returned.