# 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()