# Copyright (c) 2005 Laurence Tratt
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
# IN THE SOFTWARE.
#
# This file is a simple example of a self-contained metacircular system.
#
# This class is a simple way of representing objects. At a low level an object
# is basically just a map from slot names to slot values.
#
# The only slight trick is that when one looks up a slot value in an object, if
# the value of that slot is a function, one needs to create a Func_Binding so
# that when the function is executed, it remembers which object the function
# was bound to.
class Raw_Object:
def __init__(self, slot_names):
self.slots = {}
for name in slot_names:
self.slots[name] = None
def get_slot(self, name):
if self.slots.has_key(name):
return self.slots[name]
else:
func = locate_and_return_method(self, name, self.slots["instance_of"])
if func is None:
raise Exception("No such attribute or method '%s'." % name)
else:
return func
def set_slot(self, name, val):
if not self.slots.has_key(name):
raise Exception("No such attribute or method '%s'." % name)
self.slots[name] = val
# A Func_Binding is basically a two-element tuple (self_val, func) i.e. a way
# of remembering which object "func" was extracted from.
class Func_Binding:
def __init__(self, self_val, func):
self.self_val = self_val
self.func = func
def execute(self, *args):
return apply(self.func, (self.self_val, ) + args)
# Given a class "class_", find the method named "self_val" in that class or one
# of its super classes. When found, create a Func_Binding(self_val, ).
#
# If method not found, return None.
def locate_and_return_method(self_val, name, class_):
if class_ is None:
return None
if class_.get_slot("methods").has_key(name):
return Func_Binding(self_val, class_.get_slot("methods")[name])
else:
return locate_and_return_method(self_val, name, class_.get_slot("parent"))
# Return all the attributes of class_ (i.e. flatten a class and its super
# classes attributes).
def all_attributes(class_):
attrs = []
current_class = class_
while 1:
for attr_name in current_class.get_slot("attributes"):
if attr_name not in attrs:
attrs.append(attr_name)
if current_class.get_slot("parent") is None:
break
current_class = current_class.get_slot("parent")
return attrs
#
# Methods for the Object class
#
# init is a no-op
def cobject_init(self_cobject):
pass
# to_str just prints out the object identifier
def cobject_to_str(self_cobject):
return "" % id(self_cobject)
#
# Methods for the Class class
#
# init sets the name, parent, attributes and methods of the class
def cclass_init(self_cobject, name, parent, attributes, methods):
self_cobject.set_slot("name", name)
self_cobject.set_slot("parent", parent)
self_cobject.set_slot("attributes", attributes)
self_cobject.set_slot("methods", methods)
# new actually creates an object, sets its instance_of slot, and then calls
# the object's init function
def cclass_new(self_cobject, *args):
new_obj = Raw_Object(all_attributes(self_cobject))
new_obj.set_slot("instance_of", self_cobject)
apply(new_obj.get_slot("init").execute, args)
return new_obj
#
# Methods for the Singleton meta-class
#
# new
def singleton_new(self_cobject):
if self_cobject.get_slot("singleton_instance") is None:
self_cobject.set_slot("singleton_instance", cclass_new(self_cobject))
return self_cobject.get_slot("singleton_instance")
def bootstrap():
# Bootstrap Object and Class
cobject = Raw_Object(["instance_of", "name", "parent", "attributes", "methods"])
cclass = Raw_Object(["instance_of", "name", "parent", "attributes", "methods"])
cobject.set_slot("instance_of", cclass)
cobject.set_slot("name","CObject")
cobject.set_slot("parent", None)
cobject.set_slot("attributes", ["instance_of"])
cobject.set_slot("methods", {"init" : cobject_init, "to_str" : cobject_to_str})
cclass.set_slot("instance_of", cclass) # THE IMPORTANT LINE
cclass.set_slot("name", "CClass")
cclass.set_slot("parent", cobject)
cclass.set_slot("attributes", ["name", "parent", "attributes", "methods"])
cclass.set_slot("methods", {"init" : cclass_init, "new" : cclass_new})
print "CObject's name:", cobject.get_slot("name")
print "CObject's attributes:", cobject.get_slot("attributes")
print "COBject's instance_of:", cobject.get_slot("instance_of")
# Notice cobject() always returns a new fresh object
o1 = cobject.get_slot("new").execute()
o2 = cobject.get_slot("new").execute()
o3 = cobject.get_slot("new").execute()
print "new object 1:", o1.get_slot("to_str").execute()
print "new object 2:", o2.get_slot("to_str").execute()
print "new object 3:", o3.get_slot("to_str").execute()
new_class = cclass.get_slot("new").execute("Person", cobject, ["name", "age"], {})
person = new_class.get_slot("new").execute()
person.set_slot("name", "Laurie")
person.set_slot("age", 7)
print person.get_slot("to_str").execute(), person.get_slot("name"), person.get_slot("age")
# A meta-class - so here we introduce a new meta-level.
#
# The Singleton meta-class makes sure that a class which is an instance of
# it only has a single instance itself.
singleton = cclass.get_slot("new").execute("Singleton", cclass, ["singleton_instance"], {"new" : singleton_new})
database_connection = singleton.get_slot("new").execute("DBConnect", cobject, [], {})
# Notice that no matter how many times we instantiate the Database_connection class, because
# it is an instance of the Singletone meta-class we always get the same object back. Have
# a look into singleton_new to see what's going on.
d1 = database_connection.get_slot("new").execute()
d2 = database_connection.get_slot("new").execute()
print "database_connection():", d1.get_slot("to_str").execute()
print "database_connection():", d2.get_slot("to_str").execute()
def main():
bootstrap()
if __name__ == "__main__":
main()