diff --git a/byterun/pyobj.py b/byterun/pyobj.py index cfd536c3..66b31bba 100644 --- a/byterun/pyobj.py +++ b/byterun/pyobj.py @@ -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: @@ -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 '' % (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 @@ -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: @@ -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. diff --git a/byterun/pyvm2.py b/byterun/pyvm2.py index f4f2318e..0a53ee91 100644 --- a/byterun/pyvm2.py +++ b/byterun/pyvm2.py @@ -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__) @@ -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. @@ -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. @@ -70,15 +68,15 @@ 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.""" @@ -86,7 +84,7 @@ def jump(self, 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): @@ -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 @@ -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': @@ -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)) @@ -314,6 +312,8 @@ def run_frame(self, frame): if why: break + # TODO: handle generator exception state + self.pop_frame() if why == 'exception': @@ -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): @@ -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 = {} @@ -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)' % ( @@ -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): @@ -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): diff --git a/tests/test_basic.py b/tests/test_basic.py index 2fd02cd1..bfc3a958 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -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): @@ -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): diff --git a/tests/test_functions.py b/tests/test_functions.py index 19a615c0..4fa5a2d9 100644 --- a/tests/test_functions.py +++ b/tests/test_functions.py @@ -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=[]): @@ -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')