Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 4 additions & 54 deletions byterun/pyobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ def __call__(self, *args, **kwargs):
try:
callargs = inspect.getcallargs(self._func, *args, **kwargs)
except Exception as e:
import pudb;pudb.set_trace() # -={XX}=-={XX}=-={XX}=-
# import pudb;pudb.set_trace() # -={XX}=-={XX}=-={XX}=-
raise
frame = self._vm.make_frame(
self.func_code, callargs, self.func_globals, self.func_locals
self.func_code, callargs, self.func_globals, {}
)
CO_GENERATOR = 32 # flag for "this code uses yield"
if self.func_code.co_flags & CO_GENERATOR:
Expand All @@ -86,57 +86,6 @@ def __call__(self, *args, **kwargs):
retval = self._vm.run_frame(frame)
return retval


class Class(object):
def __init__(self, name, bases, methods):
self.__name__ = name
self.__bases__ = bases
self.locals = dict(methods)

def __call__(self, *args, **kw):
return Object(self, self.locals, args, kw)

def __repr__(self): # pragma: no cover
return '<Class %s at 0x%08x>' % (self.__name__, id(self))

def __getattr__(self, name):
try:
val = self.locals[name]
except KeyError:
raise AttributeError("Fooey: %r" % (name,))
# Check if we have a descriptor
get = getattr(val, '__get__', None)
if get:
return get(None, self)
# Not a descriptor, return the value.
return val


class Object(object):
def __init__(self, _class, methods, args, kw):
self._class = _class
self.locals = methods
if '__init__' in methods:
methods['__init__'](self, *args, **kw)

def __repr__(self): # pragma: no cover
return '<%s Instance at 0x%08x>' % (self._class.__name__, id(self))

def __getattr__(self, name):
try:
val = self.locals[name]
except KeyError:
raise AttributeError(
"%r object has no attribute %r" % (self._class.__name__, name)
)
# Check if we have a descriptor
get = getattr(val, '__get__', None)
if get:
return get(self, self._class)
# Not a descriptor, return the value.
return val


class Method(object):
def __init__(self, obj, _class, func):
self.im_self = obj
Expand Down Expand Up @@ -195,6 +144,7 @@ def __init__(self, f_code, f_globals, f_locals, f_back):
self.f_globals = f_globals
self.f_locals = f_locals
self.f_back = f_back
self.stack = []
if f_back:
self.f_builtins = f_back.f_builtins
else:
Expand Down Expand Up @@ -265,7 +215,7 @@ def __iter__(self):
def next(self):
# Ordinary iteration is like sending None into a generator.
if not self.first:
self.vm.push(None)
self.gi_frame.stack.append(None)
self.first = False
# To get the next value from an iterator, push its frame onto the
# stack, and let it run.
Expand Down
61 changes: 29 additions & 32 deletions byterun/pyvm2.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

PY3, PY2 = six.PY3, not six.PY3

from .pyobj import Frame, Block, Method, Object, Function, Class, Generator
from .pyobj import Frame, Block, Method, Function, Generator

log = logging.getLogger(__name__)

Expand All @@ -41,14 +41,12 @@ def __init__(self):
self.frames = []
# The current frame.
self.frame = None
# The data stack.
self.stack = []
self.return_value = None
self.last_exception = None

def top(self):
"""Return the value at the top of the stack, with no changes."""
return self.stack[-1]
return self.frame.stack[-1]

def pop(self, i=0):
"""Pop a value from the stack.
Expand All @@ -57,11 +55,11 @@ def pop(self, i=0):
instead.

"""
return self.stack.pop(-1-i)
return self.frame.stack.pop(-1-i)

def push(self, *vals):
"""Push values onto the value stack."""
self.stack.extend(vals)
self.frame.stack.extend(vals)

def popn(self, n):
"""Pop a number of values from the value stack.
Expand All @@ -70,23 +68,23 @@ def popn(self, n):

"""
if n:
ret = self.stack[-n:]
self.stack[-n:] = []
ret = self.frame.stack[-n:]
self.frame.stack[-n:] = []
return ret
else:
return []

def peek(self, n):
"""Get a value `n` entries down in the stack, without changing the stack."""
return self.stack[-n]
return self.frame.stack[-n]

def jump(self, jump):
"""Move the bytecode pointer to `jump`, so it will execute next."""
self.frame.f_lasti = jump

def push_block(self, type, handler=None, level=None):
if level is None:
level = len(self.stack)
level = len(self.frame.stack)
self.frame.block_stack.append(Block(type, handler, level))

def pop_block(self):
Expand Down Expand Up @@ -148,8 +146,8 @@ def run_code(self, code, f_globals=None, f_locals=None):
# Check some invariants
if self.frames: # pragma: no cover
raise VirtualMachineError("Frames left over!")
if self.stack: # pragma: no cover
raise VirtualMachineError("Data left on stack! %r" % self.stack)
if self.frame and self.frame.stack: # pragma: no cover
raise VirtualMachineError("Data left on stack! %r" % self.frame.stack)

return val

Expand All @@ -159,7 +157,7 @@ def unwind_block(self, block):
else:
offset = 0

while len(self.stack) > block.level + offset:
while len(self.frame.stack) > block.level + offset:
self.pop()

if block.type == 'except-handler':
Expand Down Expand Up @@ -209,7 +207,7 @@ def run_frame(self, frame):
if arguments:
op += " %r" % (arguments[0],)
indent = " "*(len(self.frames)-1)
stack_rep = repper(self.stack)
stack_rep = repper(self.frame.stack)
block_stack_rep = repper(self.frame.block_stack)

log.info(" %sdata: %s" % (indent, stack_rep))
Expand Down Expand Up @@ -314,6 +312,8 @@ def run_frame(self, frame):
if why:
break

# TODO: handle generator exception state

self.pop_frame()

if why == 'exception':
Expand Down Expand Up @@ -891,7 +891,7 @@ def byte_MAKE_CLOSURE(self, argc):
closure, code = self.popn(2)
defaults = self.popn(argc)
globs = self.frame.f_globals
fn = Function(None, code, globs, defaults, closure, self)
fn = Function(name, code, globs, defaults, closure, self)
self.push(fn)

def byte_CALL_FUNCTION(self, arg):
Expand All @@ -909,14 +909,6 @@ def byte_CALL_FUNCTION_VAR_KW(self, arg):
args, kwargs = self.popn(2)
return self.call_function(arg, args, kwargs)

def isinstance(self, obj, cls):
if isinstance(obj, Object):
return issubclass(obj._class, cls)
elif isinstance(cls, Class):
return False
else:
return isinstance(obj, cls)

def call_function(self, arg, args, kwargs):
lenKw, lenPos = divmod(arg, 256)
namedargs = {}
Expand All @@ -934,7 +926,7 @@ def call_function(self, arg, args, kwargs):
if func.im_self:
posargs.insert(0, func.im_self)
# The first parameter must be the correct type.
if not self.isinstance(posargs[0], func.im_class):
if not isinstance(posargs[0], func.im_class):
raise TypeError(
'unbound method %s() must be called with %s instance '
'as first argument (got %s instance instead)' % (
Expand All @@ -957,6 +949,8 @@ def byte_YIELD_VALUE(self):
self.return_value = self.pop()
return "yield"

#TODO: implement byte_YIELD_FROM for 3.3+

## Importing

def byte_IMPORT_NAME(self, name):
Expand All @@ -983,16 +977,19 @@ def byte_EXEC_STMT(self):
stmt, globs, locs = self.popn(3)
six.exec_(stmt, globs, locs)

def byte_BUILD_CLASS(self):
name, bases, methods = self.popn(3)
self.push(Class(name, bases, methods))
if PY2:
def byte_BUILD_CLASS(self):
name, bases, methods = self.popn(3)
self.push(type(name, bases, methods))


def byte_LOAD_BUILD_CLASS(self):
# New in py3
self.push(__build_class__)
elif PY3:
def byte_LOAD_BUILD_CLASS(self):
# New in py3
self.push(__build_class__)

def byte_STORE_LOCALS(self):
self.frame.f_locals = self.pop()
def byte_STORE_LOCALS(self):
self.frame.f_locals = self.pop()

if 0: # Not in py2.7
def byte_SET_LINENO(self, lineno):
Expand Down
80 changes: 80 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,76 @@ class SubThing(Thing):
print(st.foo())
""")

def test_subclass_attribute(self):
self.assert_ok("""\
class Thing(object):
def __init__(self):
self.foo = 17
class SubThing(Thing):
pass
st = SubThing()
print(st.foo)
""")

def test_subclass_attributes_not_shared(self):
self.assert_ok("""\
class Thing(object):
foo = 17
class SubThing(Thing):
foo = 25
st = SubThing()
t = Thing()
assert st.foo == 25
assert t.foo == 17
""")

def test_object_attrs_not_shared_with_class(self):
self.assert_ok("""\
class Thing(object):
pass
t = Thing()
t.foo = 1
Thing.foo""", raises=AttributeError)

def test_data_descriptors_precede_instance_attributes(self):
self.assert_ok("""\
class Foo(object):
pass
f = Foo()
f.des = 3
class Descr(object):
def __get__(self, obj, cls=None):
return 2
def __set__(self, obj, val):
raise NotImplementedError
Foo.des = Descr()
assert f.des == 2
""")

def test_instance_attrs_precede_non_data_descriptors(self):
self.assert_ok("""\
class Foo(object):
pass
f = Foo()
f.des = 3
class Descr(object):
def __get__(self, obj, cls=None):
return 2
Foo.des = Descr()
assert f.des == 3
""")

def test_subclass_attributes_dynamic(self):
self.assert_ok("""\
class Foo(object):
pass
class Bar(Foo):
pass
b = Bar()
Foo.baz = 3
assert b.baz == 3
""")

def test_attribute_access(self):
self.assert_ok("""\
class Thing(object):
Expand Down Expand Up @@ -354,6 +424,16 @@ def meth(self, x):
m(Thing(), 1815)
""")

def test_bound_methods(self):
self.assert_ok("""\
class Thing(object):
def meth(self, x):
print(x)
t = Thing()
m = t.meth
m(1815)
""")

def test_callback(self):
self.assert_ok("""\
def lcase(s):
Expand Down
19 changes: 17 additions & 2 deletions tests/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@ def fact(n):
assert f6 == 720
""")

def test_nested_names(self):
self.assert_ok("""\
def one():
x = 1
def two():
x = 2
print(x)
two()
print(x)
one()
""")

def test_calling_functions_with_args_kwargs(self):
self.assert_ok("""\
def fn(a, b=17, c="Hello", d=[]):
Expand Down Expand Up @@ -228,17 +240,20 @@ def triples():
print(a, b, c)
""")

def test_generator_from_generator2(self):
def test_simple_generator(self):
self.assert_ok("""\
g = (x*x for x in range(3))
print(list(g))
""")

def test_generator_from_generator(self):
self.assert_ok("""\
g = (x*x for x in range(5))
g = (y+1 for y in g)
print(list(g))
""")

def test_generator_from_generator(self):
def test_generator_from_generator2(self):
self.assert_ok("""\
class Thing(object):
RESOURCES = ('abc', 'def')
Expand Down