Skip to content

Commit 0536694

Browse files
committed
more name changes
1 parent bb1de32 commit 0536694

4 files changed

Lines changed: 87 additions & 83 deletions

File tree

interpreter/byterun/pyvm2.py

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ class Function(object):
3232
'_vm', '_func',
3333
]
3434

35+
3536
def __init__(self, name, code, globs, defaults, closure, vm):
3637
self._vm = vm
3738
self.func_code = code
@@ -171,9 +172,7 @@ def parse_byte_and_args(self):
171172
opoffset = f.last_instruction
172173
byteCode = f.code_obj.co_code[opoffset]
173174
f.last_instruction += 1
174-
byteName = dis.opname[byteCode]
175-
arg = None
176-
arguments = []
175+
byte_name = dis.opname[byteCode]
177176
if byteCode >= dis.HAVE_ARGUMENT:
178177
arg = f.code_obj.co_code[f.last_instruction:f.last_instruction+2] # index into the bytecode
179178
f.last_instruction += 2 # advance the instruction pointer
@@ -188,30 +187,32 @@ def parse_byte_and_args(self):
188187
arg = f.last_instruction + arg_val
189188
else:
190189
arg = arg_val
191-
arguments = [arg]
190+
argument = [arg]
191+
else:
192+
argument = []
192193

193-
return byteName, arguments
194+
return byte_name, argument
194195

195-
def dispatch(self, byteName, arguments):
196+
def dispatch(self, byte_name, argument):
196197
""" Dispatch by bytename to the corresponding methods.
197198
Exceptions are caught and set on the virtual machine."""
198199

199200
# When later unwinding the block stack,
200201
# we need to keep track of why we are doing it.
201202
why = None
202203
try:
203-
bytecode_fn = getattr(self, 'byte_%s' % byteName, None)
204+
bytecode_fn = getattr(self, 'byte_%s' % byte_name, None)
204205
if bytecode_fn is None:
205-
if byteName.startswith('UNARY_'):
206-
self.unaryOperator(byteName[6:])
207-
elif byteName.startswith('BINARY_'):
208-
self.binaryOperator(byteName[7:])
206+
if byte_name.startswith('UNARY_'):
207+
self.unaryOperator(byte_name[6:])
208+
elif byte_name.startswith('BINARY_'):
209+
self.binaryOperator(byte_name[7:])
209210
else:
210211
raise VirtualMachineError(
211-
"unsupported bytecode type: %s" % byteName
212+
"unsupported bytecode type: %s" % byte_name
212213
)
213214
else:
214-
why = bytecode_fn(*arguments)
215+
why = bytecode_fn(*argument)
215216
except:
216217
# deal with exceptions encountered while executing the op.
217218
self.last_exception = sys.exc_info()[:2] + (None,)
@@ -258,9 +259,9 @@ def run_frame(self, frame):
258259
"""
259260
self.push_frame(frame)
260261
while True:
261-
byteName, arguments = self.parse_byte_and_args()
262+
byte_name, argument = self.parse_byte_and_args()
262263

263-
why = self.dispatch(byteName, arguments)
264+
why = self.dispatch(byte_name, argument)
264265

265266
# Deal with any block management we need to do
266267
while why and frame.block_stack:

interpreter/chapter.txt

Lines changed: 59 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -420,35 +420,32 @@ class VirtualMachine(object):
420420
self.return_value = None
421421
self.last_exception = None
422422

423-
def run_code(self, code, f_globals=None, f_locals=None):
423+
def run_code(self, code, global_names=None, local_names=None):
424424
""" An entry point to execute code using the virtual machine."""
425-
frame = self.make_frame(code, f_globals=f_globals, f_locals=f_locals)
425+
frame = self.make_frame(code, global_names=global_names, local_names=local_names)
426426
self.run_frame(frame)
427-
```
428427

428+
```
429429

430-
Next we'll write the Frame object. The frame is a collection of attributes with no methods. As mentioned above, the attributes include `f_code`, the code object (created by the compiler) corresponding to the frame; the local, global, and builtin namespaces (`f_globals`, `f_locals`, and `f_builtins`); a reference to the previous frame, `f_back`; a data stack, a block stack, and the last instruction executed (`f_lasti`). (We have to do a little extra work to get to the builtin namespace because Python treats this namespace differently in different modules; this detail is not important to the virtual machine.)
431-
#TODO: fix the naming here
430+
Next we'll write the Frame object. The frame is a collection of attributes with no methods. As mentioned above, the attributes include the code object created by the compiler; the local, global, and builtin namespaces; a reference to the previous frame; a data stack, a block stack, and the last instruction executed. (We have to do a little extra work to get to the builtin namespace because Python treats this namespace differently in different modules; this detail is not important to the virtual machine.)
432431

433432
``` python
434433
class Frame(object):
435-
def __init__(self, f_code, f_globals, f_locals, f_back):
436-
self.f_code = f_code # The code object
437-
self.f_globals = f_globals # Global namespace
438-
self.f_locals = f_locals # Local namespace
439-
if f_back:
440-
self.f_builtins = f_back.f_builtins # Builtin namespace
434+
def __init__(self, code_obj, global_names, local_names, prev_frame):
435+
self.code_obj = code_obj
436+
self.global_names = global_names
437+
self.local_names = local_names
438+
self.prev_frame = prev_frame
439+
self.stack = []
440+
if prev_frame:
441+
self.builtin_names = prev_frame.builtin_names
441442
else:
442-
self.f_builtins = f_locals['__builtins__']
443-
if hasattr(self.f_builtins, '__dict__'):
444-
self.f_builtins = self.f_builtins.__dict__
443+
self.builtin_names = local_names['__builtins__']
444+
if hasattr(self.builtin_names, '__dict__'):
445+
self.builtin_names = self.builtin_names.__dict__
445446

446-
self.f_back = f_back # Reference to previous frame
447-
self.stack = [] # Data stack
448-
self.block_stack = [] # Block stack
449-
450-
self.f_lineno = f_code.co_firstlineno # First line number in original source code
451-
self.f_lasti = 0 # Last executed bytecode instruction
447+
self.last_instruction = 0
448+
self.block_stack = []
452449
```
453450

454451
Next, we'll add frame manipulation to the virtual machine. There are three helper functions for frames: one to create new frames (which is responsible for sorting out the new frame's namespacing) and one each to push and pop frames from the frame stack. A fourth function, `run_frame`, does the main work of executing a frame. We'll come back to this momentarily.
@@ -458,26 +455,26 @@ class VirtualMachine(object):
458455
[... snip ...]
459456

460457
# Frame manipulation
461-
def make_frame(self, code, callargs={}, f_globals=None, f_locals=None):
462-
if f_globals is not None and f_locals is None::
463-
f_locals = f_globals
458+
def make_frame(self, code, callargs={}, global_names=None, local_names=None):
459+
if global_names is not None and local_names is not None:
460+
local_names = global_names
464461
elif self.frames:
465-
f_globals = self.frame.f_globals
466-
f_locals = {}
462+
global_names = self.frame.global_names
463+
local_names = {}
467464
else:
468-
f_globals = f_locals = {
465+
global_names = local_names = {
469466
'__builtins__': __builtins__,
470467
'__name__': '__main__',
471468
'__doc__': None,
472469
'__package__': None,
473470
}
474-
f_locals.update(callargs)
475-
frame = Frame(code, f_globals, f_locals, self.frame)
471+
local_names.update(callargs)
472+
frame = Frame(code, global_names, local_names, self.frame)
476473
return frame
477474

478475
def push_frame(self, frame):
479476
self.frames.append(frame)
480-
self.frame = frame # Update current frame in VM
477+
self.frame = frame
481478

482479
def pop_frame(self):
483480
self.frames.pop()
@@ -491,8 +488,7 @@ class VirtualMachine(object):
491488
# we'll come back to this shortly
492489
```
493490

494-
The Function object appears below. The implementation is somewhat twisty, and most of the details aren't critical to understanding the interpreter. The important thing to notice is that calling a function - invoking the `__call__` method - creates a new Frame object and starts running it. (The implementation is twisty because this is one place where our Python interpreter interacts with the real Python compiler
495-
TODO: TRUE?.)
491+
The Function object appears below. The implementation is somewhat twisty, and most of the details aren't critical to understanding the interpreter. The important thing to notice is that calling a function - invoking the `__call__` method - creates a new Frame object and starts running it. (The implementation is twisty because TODO: why? Ask Ned.)
496492

497493
```
498494
class Function(object):
@@ -570,7 +566,7 @@ Before we get to running a frame, we need two more methods. The first, `parse_by
570566

571567
A single instruction is one byte long if it doesn't have an argument, or three bytes if it does have an argument, where the last two bytes are the argument. The meaning of the argument to each instruction depends on which instruction it is. For example, as mentioned above, for `POP_JUMP_IF_FALSE`, the argument to the instruction is the jump target. For `BUILD_LIST`, the argument is the number of elements in the list. For `LOAD_CONST`, it's an index into the list of constants.
572568

573-
Some instructions use simple numbers as their arguments. For others, the virtual machine has to do a little work to discover what the arguments mean. The `dis` module in the standard library exposes a cheatsheet to what arguments have what meaning, which makes our code more compact. For example, the list `dis.hasname` tells us that the arguments to `LOAD_NAME`, `IMPORT_NAME`, `LOAD_GLOBAL`, and nine other instructions have the same meaing: in each case, the argument represents an index into the list of names on the code object.
569+
Some instructions use simple numbers as their arguments. For others, the virtual machine has to do a little work to discover what the arguments mean. The `dis` module in the standard library exposes a cheatsheet to what arguments have what meaning, which makes our code more compact. For example, the list `dis.hasname` tells us that the arguments to `LOAD_NAME`, `IMPORT_NAME`, `LOAD_GLOBAL`, and nine other instructions have the same meaing: for these twelve instructions, the argument represents an index into the list of names on the code object.
574570

575571

576572
TODO: fix frame naming
@@ -579,32 +575,30 @@ class VirtualMachine(object):
579575
[... snip ...]
580576

581577
def parse_byte_and_args(self):
582-
""" Parse 1 - 3 bytes of bytecode into
583-
an instruction and maybe arguments."""
584578
f = self.frame
585-
opoffset = f.f_lasti
586-
byteCode = f.f_code.co_code[opoffset]
587-
f.f_lasti += 1
588-
byteName = dis.opname[byteCode]
589-
arg = None
590-
arguments = []
579+
opoffset = f.last_instruction
580+
byteCode = f.code_obj.co_code[opoffset]
581+
f.last_instruction += 1
582+
byte_name = dis.opname[byteCode]
591583
if byteCode >= dis.HAVE_ARGUMENT:
592-
arg = f.f_code.co_code[f.f_lasti:f.f_lasti+2]
593-
f.f_lasti += 2
584+
arg = f.code_obj.co_code[f.last_instruction:f.last_instruction+2] # index into the bytecode
585+
f.last_instruction += 2 # advance the instruction pointer
594586
arg_val = arg[0] + (arg[1] << 8)
595587
if byteCode in dis.hasconst: # Look up a constant
596-
arg = f.f_code.co_consts[arg_val]
588+
arg = f.code_obj.co_consts[arg_val]
597589
elif byteCode in dis.hasname: # Look up a name
598-
arg = f.f_code.co_names[arg_val]
590+
arg = f.code_obj.co_names[arg_val]
599591
elif byteCode in dis.haslocal: # Look up a local name
600-
arg = f.f_code.co_varnames[arg_val]
592+
arg = f.code_obj.co_varnames[arg_val]
601593
elif byteCode in dis.hasjrel: # Calculate a relative jump
602-
arg = f.f_lasti + arg_val
594+
arg = f.last_instruction + arg_val
603595
else:
604596
arg = arg_val
605-
arguments = [arg]
597+
argument = [arg]
598+
else:
599+
argument = []
606600

607-
return byteName, arguments
601+
return byte_name, argument
608602
```
609603

610604
The next method is `dispatch`, which looks up the operations for a given instruction and executes them. In the CPython interpreter, this dispatch is done with a giant switch statement that spans 1,500 lines! Luckily, since we're writing Python, we can be more compact. We'll define a method for each byte name and then use `getattr` to look it up. If our bytecode was named `FOO_BAR`, the corresponding method would be named `byte_FOO_BAR`. For the moment, we'll leave the content of these methods as a black box. Each bytecode method will return either `None` or a `why` string to be passed to block management. The return values of the individual bytecode methods are used only as internal indicators of interpreter state - don't confuse these with return values from executing frames.
@@ -614,27 +608,26 @@ The next method is `dispatch`, which looks up the operations for a given instruc
614608
class VirtualMachine(object):
615609
[... snip ...]
616610

617-
def dispatch(self, byteName, arguments):
618-
""" Dispatch by bytename to the corresponding method and call it.
619-
Exceptions are caught and set on the virtual machine."""
611+
def dispatch(self, byte_name, argument):
612+
""" Dispatch by bytename to the corresponding methods.
613+
Exceptions are caught and set on the virtual machine."""
614+
620615
# When later unwinding the block stack,
621616
# we need to keep track of why we are doing it.
622617
why = None
623-
624618
try:
625-
if byteName.startswith('UNARY_'):
626-
self.unaryOperator(byteName[6:])
627-
elif byteName.startswith('BINARY_'):
628-
self.binaryOperator(byteName[7:])
629-
else:
630-
# primary dispatch
631-
bytecode_fn = getattr(self, 'byte_%s' % byteName, None)
632-
if not bytecode_fn: # Not all bytecodes are supported in 500 lines
619+
bytecode_fn = getattr(self, 'byte_%s' % byte_name, None)
620+
if bytecode_fn is None:
621+
if byte_name.startswith('UNARY_'):
622+
self.unaryOperator(byte_name[6:])
623+
elif byte_name.startswith('BINARY_'):
624+
self.binaryOperator(byte_name[7:])
625+
else:
633626
raise VirtualMachineError(
634-
"unsupported bytecode type: %s" % byteName
627+
"unsupported bytecode type: %s" % byte_name
635628
)
636-
why = bytecode_fn(*arguments)
637-
629+
else:
630+
why = bytecode_fn(*argument)
638631
except:
639632
# deal with exceptions encountered while executing the op.
640633
self.last_exception = sys.exc_info()[:2] + (None,)
@@ -653,9 +646,9 @@ class VirtualMachine(object):
653646
"""
654647
self.push_frame(frame)
655648
while True:
656-
byteName, arguments = self.parse_byte_and_args()
649+
byte_name, arguments = self.parse_byte_and_args()
657650

658-
why = self.dispatch(byteName, arguments)
651+
why = self.dispatch(byte_name, arguments)
659652

660653
# Deal with any block management we need to do
661654
while why and frame.block_stack:
@@ -907,7 +900,6 @@ class VirtualMachine(object):
907900
the_list = self.frame.stack[-count] # peek
908901
the_list.append(val)
909902

910-
911903
## Jumps
912904

913905
def byte_JUMP_FORWARD(self, jump):
@@ -1055,7 +1047,7 @@ class VirtualMachine(object):
10551047

10561048
### What can we actually do with this? Pick an interesting example and scope the code to just those bytecode instructions, maybe?
10571049

1058-
### Somehow conclude
1050+
### With byterun, you have a compact Python interpreter that's easier to understand than CPython. I encourage you to disassemble your own programs and run them using Byterun. (You'll eventually run into instructions that this shorter version of Byterun doesn't implement. The full implementation can be found at github.com/nedbat/byterun. )
10591051

10601052
# Exceptions ?
10611053

interpreter/integration.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
# A file to test if pyvm works from the command line.
22

3-
print("Success!")
3+
def it_works():
4+
print("Success!")
5+
6+
it_works()

interpreter/tests/test_functions.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,14 @@ def f(a,b):
9494
assert four == 4
9595
""")
9696

97+
# def test_weird_splatting(self):
98+
# self.assert_ok("""\
99+
# def foo(arg):
100+
# pass
101+
# li = [[]]
102+
# foo(*li)
103+
# """)
104+
97105
# def test_partial_with_kwargs(self):
98106
# """ KW args not suppoted"""
99107
# self.assert_ok("""\

0 commit comments

Comments
 (0)