forked from aosabook/500lines
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
365 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |