Skip to content

Commit

Permalink
implement maps
Browse files Browse the repository at this point in the history
  • Loading branch information
cfbolz committed Feb 28, 2014
1 parent 7351f6f commit f155127
Show file tree
Hide file tree
Showing 2 changed files with 365 additions and 0 deletions.
123 changes: 123 additions & 0 deletions objmodel/04-maps/objmodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
class LanguageError(Exception):
pass

class Map(object):
def __init__(self, attrs):
self.attrs = attrs
self.next_maps = {}

def get_index(self, fieldname):
return self.attrs.get(fieldname, -1)

def next_map(self, fieldname):
if fieldname in self.next_maps:
return self.next_maps[fieldname]
attrs = self.attrs.copy()
assert fieldname not in attrs
attrs[fieldname] = len(attrs)
result = self.next_maps[fieldname] = Map(attrs)
return result

EMPTY_MAP = Map({})

class Base(object):
def __init__(self, cls):
self.cls = cls

def read_field(self, fieldname):
raise NotImplementedError("abstract base class")

def write_field(self, fieldname, value):
raise NotImplementedError("abstract base class")

def isinstance(self, cls):
return self.cls.issubclass(cls)

def send(self, methname, *args):
meth = self.read_field(methname)
return meth(*args)

class Instance(Base):
def __init__(self, cls):
assert cls is None or isinstance(cls, Class)
self.cls = cls
self.map = EMPTY_MAP
self.storage = []

def read_field(self, fieldname):
index = self.map.get_index(fieldname)
if index != -1:
return self.storage[index]
try:
return self.cls._read_from_class(fieldname, self)
except AttributeError, orig_error:
# not found on class
pass
# try special method __getattr__
try:
meth = self.cls._read_from_class("__getattr__", self)
except AttributeError:
raise orig_error # get the right error message
return meth(fieldname)

def write_field(self, fieldname, value):
meth = self.cls._read_from_class("__setattr__", self)
return meth(fieldname, value)


def _make_boundmethod(meth, cls, self):
return meth.__get__(self, cls)

class Class(Base):
def __init__(self, name, base_class, dct, metaclass):
Base.__init__(self, metaclass)
self.dct = dct
self.name = name
self.base_class = base_class

def read_field(self, fieldname):
if fieldname in self.dct:
return self.dct[fieldname]
raise AttributeError("attribute %s not found" % fieldname)

def write_field(self, fieldname, value):
self.dct[fieldname] = value

def mro(self):
""" compute the mro (method resolution order) of the class """
if self.base_class is None:
return [self]
else:
return [self] + self.base_class.mro()

def issubclass(self, cls):
return cls in self.mro()

def _read_from_class(self, methname, obj):
for cls in self.mro():
if methname in cls.dct:
result = cls.dct[methname]
if hasattr(result, "__get__"):
return _make_boundmethod(result, self, obj)
return result
raise AttributeError("method %s not found" % methname)


def __setattr__OBJECT(self, fieldname, value):
index = self.map.get_index(fieldname)
if index != -1:
self.storage[index] = value
else:
new_map = self.map.next_map(fieldname)
self.storage.append(value)
self.map = new_map

# set up the base hierarchy like in Python (the ObjVLisp model)
# the ultimate base class is OBJECT
OBJECT = Class("object", None, {"__setattr__": __setattr__OBJECT}, None)
# TYPE is a subclass of OBJECT
TYPE = Class("type", OBJECT, {}, None)
# TYPE is an instance of itself
TYPE.cls = TYPE
# OBJECT is an instance of TYPE
OBJECT.cls = TYPE
242 changes: 242 additions & 0 deletions objmodel/04-maps/test_objmodel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
from objmodel import Class, Instance, TYPE, OBJECT

# copied from the previous version
def test_isinstance():
# Python code
class A(object):
pass
class B(A):
pass
b = B()
assert isinstance(b, B)
assert isinstance(b, A)
assert isinstance(b, object)
assert not isinstance(b, type)

# Object model code
A = Class("A", OBJECT, {}, TYPE)
B = Class("B", A, {}, TYPE)
b = Instance(B)
assert b.isinstance(B)
assert b.isinstance(A)
assert b.isinstance(OBJECT)
assert not b.isinstance(TYPE)

# copied from the previous version
def test_read_write_field():
# Python code
class A(object):
pass
obj = A()
obj.a = 1
assert obj.a == 1

obj.b = 5
assert obj.a == 1
assert obj.b == 5

obj.a = 2
assert obj.a == 2
assert obj.b == 5

# Object model code
A = Class("A", OBJECT, {}, TYPE)
obj = Instance(A)
obj.write_field("a", 1)
assert obj.read_field("a") == 1

obj.write_field("b", 5)
assert obj.read_field("a") == 1
assert obj.read_field("b") == 5

obj.write_field("a", 2)
assert obj.read_field("a") == 2
assert obj.read_field("b") == 5

# copied from the previous version
def test_send_simple():
# Python code
class A(object):
def f(self):
return self.x + 1
obj = A()
obj.x = 1
assert obj.f() == 2

class B(A):
pass
obj = B()
obj.x = 1
assert obj.f() == 2 # works on subclass too

# Object model code
def f(self):
return self.read_field("x") + 1
A = Class("A", OBJECT, {"f": f}, TYPE)
obj = Instance(A)
obj.write_field("x", 1)
assert obj.send("f") == 2

B = Class("B", A, {}, TYPE)
obj = Instance(B)
obj.write_field("x", 2)
assert obj.send("f") == 3

# copied from the previous version
def test_send_subclassing_and_arguments():
# Python code
class A(object):
def g(self, arg):
return self.x + arg
obj = A()
obj.x = 1
assert obj.g(4) == 5

class B(A):
def g(self, arg):
return self.x + arg * 2
obj = B()
obj.x = 4
assert obj.g(4) == 12

# Object model code
def g_A(self, arg):
return self.read_field("x") + arg
A = Class("A", OBJECT, {"g": g_A}, TYPE)
obj = Instance(A)
obj.write_field("x", 1)
assert obj.send("g", 4) == 5

def g_B(self, arg):
return self.read_field("x") + arg * 2
B = Class("B", A, {"g": g_B}, TYPE)
obj = Instance(B)
obj.write_field("x", 4)
assert obj.send("g", 4) == 12


# copied from the previous version
def test_bound_method():
# Python code
class A(object):
def f(self, a):
return self.x + a + 1
obj = A()
obj.x = 2
m = obj.f
assert m(4) == 7

class B(A):
pass
obj = B()
obj.x = 1
m = obj.f
assert m(10) == 12 # works on subclass too

# Object model code
def f(self, a):
return self.read_field("x") + a + 1
A = Class("A", OBJECT, {"f": f}, TYPE)
obj = Instance(A)
obj.write_field("x", 2)
m = obj.read_field("f")
assert m(4) == 7

B = Class("B", A, {}, TYPE)
obj = Instance(B)
obj.write_field("x", 1)
m = obj.read_field("f")
assert m(10) == 12

def test_getattr():
# Python code
class A(object):
def __getattr__(self, name):
if name == "fahrenheit":
return self.celsius * 9. / 5. + 32
raise AttributeError(name)

def __setattr__(self, name, value):
if name == "fahrenheit":
self.celsius = (value - 32) * 5. / 9.
else:
# call the base implementation
object.__setattr__(self, name, value)
obj = A()
obj.celsius = 30
assert obj.fahrenheit == 86
obj.celsius = 40
assert obj.fahrenheit == 104

obj.fahrenheit = 86
assert obj.celsius == 30
assert obj.fahrenheit == 86

# Object model code
def __getattr__(self, name):
if name == "fahrenheit":
return self.read_field("celsius") * 9. / 5. + 32
raise AttributeError(name)
def __setattr__(self, name, value):
if name == "fahrenheit":
self.write_field("celsius", (value - 32) * 5. / 9.)
else:
# call the base implementation
OBJECT.read_field("__setattr__")(self, name, value)

A = Class("A", OBJECT, {"__getattr__": __getattr__, "__setattr__": __setattr__}, TYPE)
obj = Instance(A)
obj.write_field("celsius", 30)
assert obj.read_field("fahrenheit") == 86
obj.write_field("celsius", 40)
assert obj.read_field("fahrenheit") == 104
obj.write_field("fahrenheit", 86)
assert obj.read_field("celsius") == 30
assert obj.read_field("fahrenheit") == 86

def test_get():
# Python code
class FahrenheitGetter(object):
def __get__(self, inst, cls):
return inst.celsius * 9. / 5. + 32

class A(object):
fahrenheit = FahrenheitGetter()
obj = A()
obj.celsius = 30
assert obj.fahrenheit == 86

# Object model code
class FahrenheitGetter(object):
def __get__(self, inst, cls):
return inst.read_field("celsius") * 9. / 5. + 32

def __getattr__(self, name):
if name == "fahrenheit":
return self.read_field("celsius") * 9. / 5. + 32
raise AttributeError(name)

A = Class("A", OBJECT, {"fahrenheit": FahrenheitGetter()}, TYPE)
obj = Instance(A)
obj.write_field("celsius", 30)
assert obj.read_field("fahrenheit") == 86


def test_maps():
# white box test inspecting the implementation
Point = Class("Point", OBJECT, {}, TYPE)
p1 = Instance(Point)
p1.write_field("x", 1)
p1.write_field("y", 2)

p2 = Instance(Point)
p2.write_field("x", 5)
p2.write_field("y", 6)
assert p1.map is p2.map
assert p1.storage == [1, 2]
assert p2.storage == [5, 6]

p1.write_field("x", -1)
p1.write_field("y", -2)
assert p1.map is p2.map
assert p1.storage == [-1, -2]

0 comments on commit f155127

Please sign in to comment.