OILS / opy / byterun / pyobj.py View on Github | oilshell.org

397 lines, 236 significant
1"""Implementations of Python fundamental objects for Byterun."""
2from __future__ import print_function
3
4import collections
5import sys
6import types
7
8from opy.lib import dis
9from opy.lib import inspect
10
11
12def debug1(msg, *args):
13 if args:
14 msg = msg % args
15 print(msg, file=sys.stderr)
16
17
18def make_cell(value):
19 # Thanks to Alex Gaynor for help with this bit of twistiness.
20 # Construct an actual cell object by creating a closure right here,
21 # and grabbing the cell object out of the function we create.
22 fn = (lambda x: lambda: x)(value)
23 return fn.func_closure[0]
24
25
26class Function(object):
27 """
28 CPython equivalent:
29
30 x = PyFunction_New(v, f->f_globals);
31 PyFunction_SetClosure(x, v)
32 """
33
34 __slots__ = [
35 'func_code', 'func_name', 'func_defaults', 'func_globals',
36 'func_dict', 'func_closure',
37 '__name__', '__dict__', '__doc__',
38 '_vm', '_func',
39 ]
40
41 def __init__(self, name, code, globs, defaults, closure, vm):
42 self._vm = vm
43 self.func_code = code
44 self.func_name = self.__name__ = name or code.co_name
45 self.func_defaults = tuple(defaults)
46 self.func_globals = globs
47 self.__dict__ = {}
48 self.func_closure = closure
49 self.__doc__ = code.co_consts[0] if code.co_consts else None
50
51 # Sometimes, we need a real Python function. This is for that.
52 # For some reason types.FunctionType doesn't accept normal keyword
53 # args? Like args=, closure= ?
54 # Is this only used for inspect.getcallargs?
55
56 kw = {'argdefs': self.func_defaults}
57 if closure:
58 kw['closure'] = tuple(make_cell(0) for _ in closure)
59 self._func = types.FunctionType(code, globs, **kw)
60
61 def __repr__(self): # pragma: no cover
62 return '<byterun Function %s at 0x%08x>' % (self.func_name, id(self))
63
64 def __get__(self, instance, owner):
65 # used in unit tests? But I don't see it triggered in real code.
66 #raise AssertionError('Function.__get__')
67 m = Method(instance, owner, self)
68 #debug1('*** METHOD %s', m)
69 return m
70
71 def __call__(self, *args, **kwargs):
72 #if PY2 and self.func_name in ["<setcomp>", "<dictcomp>", "<genexpr>"]:
73 # D'oh! http://bugs.python.org/issue19611 Py2 doesn't know how to
74 # inspect set comprehensions, dict comprehensions, or generator
75 # expressions properly. They are always functions of one argument,
76 # so just do the right thing.
77 #assert len(args) == 1 and not kwargs, "Surprising comprehension!"
78 #callargs = {".0": args[0]}
79
80 # Different workaround for issue 19611 that works with
81 # compiler2-generated code. Note that byterun does not use fastlocals,
82 # so the name matters. With fastlocals, the co_varnames entry is just
83 # a comment; the index is used instead.
84 code = self.func_code
85 if code.co_argcount == 1 and code.co_varnames[0] == '.0':
86 callargs = {".0": args[0]}
87 else:
88 # NOTE: Can get ValueError due to issue 19611
89 callargs = inspect.getcallargs(self._func, *args, **kwargs)
90 #print('-- func_name %s CALLS ARGS %s' % (self.func_name, callargs))
91
92 frame = self._vm.make_frame(self.func_code, callargs,
93 self.func_globals, {})
94
95 CO_GENERATOR = 32 # flag for "this code uses yield"
96 if self.func_code.co_flags & CO_GENERATOR:
97 gen = Generator(frame, self._vm)
98 frame.generator = gen
99 retval = gen
100 else:
101 # NOTE: Can raise exceptions!
102 retval = self._vm.run_frame(frame)
103 return retval
104
105
106class Method(object):
107 def __init__(self, obj, _class, func):
108 self.im_self = obj
109 self.im_class = _class
110 self.im_func = func
111
112 def __repr__(self): # pragma: no cover
113 name = "%s.%s" % (self.im_class.__name__, self.im_func.func_name)
114 if self.im_self is not None:
115 return '<Bound Method %s of %s>' % (name, self.im_self)
116 else:
117 return '<Unbound Method %s>' % (name,)
118
119 def __call__(self, *args, **kwargs):
120 if self.im_self is not None:
121 return self.im_func(self.im_self, *args, **kwargs)
122 else:
123 return self.im_func(*args, **kwargs)
124
125
126class Cell(object):
127 """A fake cell for closures.
128
129 Closures keep names in scope by storing them not in a frame, but in a
130 separate object called a cell. Frames share references to cells, and
131 the LOAD_DEREF and STORE_DEREF opcodes get and set the value from cells.
132
133 This class acts as a cell, though it has to jump through two hoops to make
134 the simulation complete:
135
136 1. In order to create actual FunctionType functions, we have to have
137 actual cell objects, which are difficult to make. See the twisty
138 double-lambda in __init__.
139
140 2. Actual cell objects can't be modified, so to implement STORE_DEREF,
141 we store a one-element list in our cell, and then use [0] as the
142 actual value.
143
144 """
145 def __init__(self, value):
146 self.contents = value
147
148 def get(self):
149 return self.contents
150
151 def set(self, value):
152 self.contents = value
153
154
155# PyTryBlock in CPython, for SETUP_EXCEPT, etc.
156Block = collections.namedtuple("Block", "type, handler, level")
157
158
159class Frame(object):
160 def __init__(self, f_code, f_globals, f_locals, f_back):
161 self.f_code = f_code
162 self.f_globals = f_globals
163 self.f_locals = f_locals
164 self.f_back = f_back
165 self.stack = []
166 if f_back:
167 self.f_builtins = f_back.f_builtins
168 else:
169 self.f_builtins = f_locals['__builtins__']
170 if hasattr(self.f_builtins, '__dict__'):
171 self.f_builtins = self.f_builtins.__dict__
172
173 self.f_lineno = f_code.co_firstlineno
174 self.f_lasti = 0
175
176 if f_code.co_cellvars:
177 self.cells = {}
178 if not f_back.cells:
179 f_back.cells = {}
180 for var in f_code.co_cellvars:
181 # Make a cell for the variable in our locals, or None.
182 cell = Cell(self.f_locals.get(var))
183 f_back.cells[var] = self.cells[var] = cell
184 else:
185 self.cells = None
186
187 if f_code.co_freevars:
188 if not self.cells:
189 self.cells = {}
190 for var in f_code.co_freevars:
191 assert self.cells is not None
192 assert f_back.cells, "f_back.cells: %r" % (f_back.cells,)
193 self.cells[var] = f_back.cells[var]
194
195 self.block_stack = []
196 self.generator = None
197
198 def __repr__(self): # pragma: no cover
199 return '<Frame at 0x%08x: %r @ %d>' % (
200 id(self), self.f_code.co_filename, self.f_lineno
201 )
202
203 def top(self):
204 """Return the value at the top of the stack, with no changes."""
205 return self.stack[-1]
206
207 def pop(self, i=0):
208 """Pop a value from the stack.
209
210 Default to the top of the stack, but `i` can be a count from the top
211 instead.
212
213 """
214 return self.stack.pop(-1-i)
215
216 def push(self, *vals):
217 """Push values onto the value stack."""
218 self.stack.extend(vals)
219
220 def popn(self, n):
221 """Pop a number of values from the value stack.
222
223 A list of `n` values is returned, the deepest value first.
224 """
225 if n:
226 ret = self.stack[-n:]
227 del self.stack[-n:]
228 return ret
229 else:
230 return []
231
232 def peek(self, n):
233 """Get a value `n` entries down in the stack, without changing the stack."""
234 return self.stack[-n]
235
236 def jump(self, offset):
237 """Move the bytecode pointer to `offset`, so it will execute next."""
238 self.f_lasti = offset
239
240 def push_block(self, type, handler=None, level=None):
241 """Used for SETUP_{LOOP,EXCEPT,FINALLY,WITH}."""
242 if level is None:
243 level = len(self.stack)
244 self.block_stack.append(Block(type, handler, level))
245
246 def pop_block(self):
247 return self.block_stack.pop()
248
249 def _unwind_block(self, block, vm):
250 """
251 Args:
252 vm: VirtualMachineError to possibly mutate
253 """
254 if block.type == 'except-handler':
255 offset = 3
256 else:
257 offset = 0
258
259 while len(self.stack) > block.level + offset:
260 self.pop()
261
262 if block.type == 'except-handler':
263 tb, value, exctype = self.popn(3)
264 vm.last_exception = exctype, value, tb
265
266 def handle_block_stack(self, why, vm):
267 """
268 After every bytecode that returns why != None, handle everything on the
269 block stack.
270
271 The block stack and data stack are shuffled for looping, exception
272 handling, or returning.
273 """
274 assert why != 'yield'
275
276 block = self.block_stack[-1]
277 if block.type == 'loop' and why == 'continue':
278 self.jump(vm.return_value)
279 why = None
280 return why
281
282 self.pop_block()
283 self._unwind_block(block, vm)
284
285 if block.type == 'loop' and why == 'break':
286 why = None
287 self.jump(block.handler)
288 return why
289
290 if (block.type in ('finally', 'with') or
291 block.type == 'setup-except' and why == 'exception'):
292 if why == 'exception':
293 exctype, value, tb = vm.last_exception
294 self.push(tb, value, exctype)
295 else:
296 if why in ('return', 'continue'):
297 self.push(vm.return_value)
298 self.push(why)
299
300 why = None
301 self.jump(block.handler)
302 return why
303
304 return why
305
306 def decode_next(self):
307 """
308 Parse 1 - 3 bytes of bytecode into an instruction and maybe arguments.
309 """
310 byteCode = ord(self.f_code.co_code[self.f_lasti])
311 self.f_lasti += 1
312
313 arguments = []
314 if byteCode >= dis.HAVE_ARGUMENT:
315 arg = self.f_code.co_code[self.f_lasti : self.f_lasti+2]
316 self.f_lasti += 2
317 intArg = ord(arg[0]) + (ord(arg[1]) << 8)
318 if byteCode in dis.hasconst:
319 arg = self.f_code.co_consts[intArg]
320 elif byteCode in dis.hasfree:
321 if intArg < len(self.f_code.co_cellvars):
322 arg = self.f_code.co_cellvars[intArg]
323 else:
324 var_idx = intArg - len(self.f_code.co_cellvars)
325 arg = self.f_code.co_freevars[var_idx]
326 elif byteCode in dis.hasname:
327 arg = self.f_code.co_names[intArg]
328 elif byteCode in dis.hasjrel:
329 arg = self.f_lasti + intArg
330 elif byteCode in dis.hasjabs:
331 arg = intArg
332 elif byteCode in dis.haslocal:
333 arg = self.f_code.co_varnames[intArg]
334 else:
335 arg = intArg
336 arguments = [arg]
337
338 byteName = dis.opname[byteCode]
339 return byteName, arguments
340
341 def line_number(self):
342 """Get the current line number the frame is executing."""
343 # We don't keep f_lineno up to date, so calculate it based on the
344 # instruction address and the line number table.
345 lnotab = self.f_code.co_lnotab
346 byte_increments = lnotab[0::2]
347 line_increments = lnotab[1::2]
348
349 byte_num = 0
350 line_num = self.f_code.co_firstlineno
351
352 for byte_incr, line_incr in zip(byte_increments, line_increments):
353 byte_incr = ord(byte_incr)
354 line_incr = ord(line_incr)
355
356 byte_num += byte_incr
357 if byte_num > self.f_lasti:
358 break
359 line_num += line_incr
360
361 return line_num
362
363
364class Generator(object):
365 """A wrapper around a Frame that can run for one generator tick."""
366
367 def __init__(self, g_frame, vm):
368 self.g_frame = g_frame
369 self._vm = vm
370 self.started = False
371 self.finished = False
372
373 # Part of the iterator protocol.
374 def __iter__(self):
375 """DO_NOT_INTERPRET"""
376 return self
377
378 # Part of the iterator protocol.
379 def next(self):
380 """DO_NOT_INTERPRET"""
381 # Docstring is a hack for pyvm2 ! Is there a better way?
382 # This is a THIRD path for a function.
383
384 return self.send(None)
385
386 # Part of the iterator protocol.
387 def send(self, value=None):
388 """DO_NOT_INTERPRET"""
389 if not self.started and value is not None:
390 raise TypeError("Can't send non-None value to a just-started generator")
391 self.g_frame.stack.append(value)
392 self.started = True
393 # NOTE: Can raise exceptions!
394 val = self._vm.resume_frame(self.g_frame)
395 if self.finished:
396 raise StopIteration(val)
397 return val