[Subversion] / BytecodeAssembler / README.txt  

Diff of /BytecodeAssembler/README.txt

Parent Directory | Revision Log

version 2198, Tue Jun 20 01:34:07 2006 UTC version 2571, Mon Aug 4 21:47:49 2008 UTC
Line 9 
Line 9 
 bytecode instead of on these mechanical issues.  bytecode instead of on these mechanical issues.
   
 In addition to a low-level opcode-oriented API for directly generating specific  In addition to a low-level opcode-oriented API for directly generating specific
 bytecodes, this module also offers an extensible mini-AST framework for  Python bytecodes, this module also offers an extensible mini-AST framework for
 generating code from high-level specifications.  This framework does most of  generating code from high-level specifications.  This framework does most of
 the work needed to transform tree-like structures into linear bytecode  the work needed to transform tree-like structures into linear bytecode
 instructions, and includes the ability to do compile-time constant folding.  instructions, and includes the ability to do compile-time constant folding.
   
   Please see the `BytecodeAssembler reference manual`_ for more details.
   
   .. _BytecodeAssembler reference manual: http://peak.telecommunity.com/DevCenter/BytecodeAssembler#toc
   
   Changes since version 0.3:
   
   * New node types:
   
     * ``For(iterable, assign, body)`` -- define a "for" loop over `iterable`
   
     * ``UnpackSequence(nodes)`` -- unpacks a sequence that's ``len(nodes)`` long,
       and then generates the given nodes.
   
     * ``LocalAssign(name)`` -- issues a ``STORE_FAST``, ``STORE_DEREF`` or
       ``STORE_LOCAL`` as appropriate for the given name.
   
     * ``Function(body, name='<lambda>', args=(), var=None, kw=None, defaults=())``
       -- creates a nested function from `body` and puts it on the stack.
   
     * ``If(cond, then_, else_=Pass)`` -- "if" statement analogue
   
     * ``ListComp(body)`` and ``LCAppend(value)`` -- implement list comprehensions
   
     * ``YieldStmt(value)`` -- generates a ``YIELD_VALUE`` (plus a ``POP_TOP`` in
       Python 2.5+)
   
   * ``Code`` objects are now iterable, yielding ``(offset, op, arg)`` triples,
     where `op` is numeric and `arg` is either numeric or ``None``.
   
   * ``Code`` objects' ``.code()`` method can now take a "parent" ``Code`` object,
     to link the child code's free variables to cell variables in the parent.
   
   * Added ``Code.from_spec()`` classmethod, that initializes a code object from a
     name and argument spec.
   
   * ``Code`` objects now have a ``.nested(name, args, var, kw)`` method, that
     creates a child code object with the same ``co_filename`` and the supplied
     name/arg spec.
   
   * Fixed incorrect stack tracking for the ``FOR_ITER`` and ``YIELD_VALUE``
     opcodes
   
   * Ensure that ``CO_GENERATOR`` flag is set if ``YIELD_VALUE`` opcode is used
   
   * Change tests so that Python 2.3's broken line number handling in ``dis.dis``
     and constant-folding optimizer don't generate spurious failures in this
     package's test suite.
   
   
   Changes since version 0.2:
   
   * Added ``Suite``, ``TryExcept``, and ``TryFinally`` node types
   
   * Added a ``Getattr`` node type that does static or dynamic attribute access
     and constant folding
   
   * Fixed ``code.from_function()`` not copying the ``co_filename`` attribute when
     ``copy_lineno`` was specified.
   
   * The ``repr()`` of AST nodes doesn't include a trailing comma for 1-argument
     node types any more.
   
   * Added a ``Pass`` symbol that generates no code, a ``Compare()`` node type
     that does n-way comparisons, and ``And()`` and ``Or()`` node types for doing
     logical operations.
   
   * The ``COMPARE_OP()`` method now accepts operator strings like ``"<="``,
     ``"not in"``, ``"exception match"``, and so on, as well as numeric opcodes.
     See the standard library's ``opcode`` module for a complete list of the
     strings accepted (in the ``cmp_op`` tuple).  ``"<>"`` is also accepted as an
     alias for ``"!="``.
   
   * Added code to verify that forward jump offsets don't exceed a 64KB span, and
     support absolute backward jumps to locations >64KB.
   
   Changes since version 0.1:
   
   * Constant handling has been fixed so that it doesn't confuse equal values of
     differing types (e.g. ``1.0`` and ``True``), or equal unhashable objects
     (e.g. two empty lists).
   
   * Removed ``nil``, ``ast_curry()`` and ``folding_curry()``, replacing them with
     the ``nodetype()`` decorator and ``fold_args()``; please see the docs for
     more details.
   
   * Added stack tracking across jumps, globally verifying stack level prediction
     consistency and automatically rejecting attempts to generate dead code.  It
     should now be virtually impossible to accidentally generate bytecode that can
     crash the interpreter.  (If you find a way, let me know!)
   
 Changes since version 0.0.1:  Changes since version 0.0.1:
   
 * Added massive quantities of new documentation and examples  * Added massive quantities of new documentation and examples
Line 35 
Line 125 
 * Various bug fixes  * Various bug fixes
   
 There are a few features that aren't tested yet, and not all opcodes may be  There are a few features that aren't tested yet, and not all opcodes may be
 fully supported.  Notably, the following features are still NOT reliably  fully supported.  Also note the following limitations:
 supported yet:  
   
 * Wide jump addressing (for generated bytecode>64K in size)  * Jumps to as-yet-undefined labels cannot span a distance greater than 65,535
     bytes.
   
 * The ``dis()`` module in Python 2.3 has a bug that makes it show incorrect  * The ``dis()`` function in Python 2.3 has a bug that makes it show incorrect
   line numbers when the difference between two adjacent line numbers is    line numbers when the difference between two adjacent line numbers is
   greater than 255.  This causes two shallow failures in the current test    greater than 255.  (To work around this, the test_suite uses a later version
   suite when it's run under Python 2.3.    of ``dis()``, but do note that it may affect your own tests if you use
     ``dis()`` with Python 2.3 and use widely separated line numbers.)
   
 If you find any other issues, please let me know.  If you find any other issues, please let me know.
   
Line 53 
Line 144 
 Questions and discussion regarding this software should be directed to the  Questions and discussion regarding this software should be directed to the
 `PEAK Mailing List <http://www.eby-sarna.com/mailman/listinfo/peak>`_.  `PEAK Mailing List <http://www.eby-sarna.com/mailman/listinfo/peak>`_.
   
   .. _toc:
 .. contents:: **Table of Contents**  .. contents:: **Table of Contents**
   
   
Line 103 
Line 195 
     >>> f()      >>> f()
     42      42
   
   Finally, code objects are also iterable, yielding ``(offset, opcode, arg)``
   tuples, where `arg` is ``None`` for opcodes with no arguments, and an integer
   otherwise::
   
       >>> import peak.util.assembler as op
       >>> list(c) == [
       ...     (0, op.LOAD_CONST, 1),
       ...     (3, op.RETURN_VALUE, None)
       ... ]
       True
   
   This can be useful for testing or otherwise inspecting code you've generated.
   
   
 Opcodes and Arguments  Opcodes and Arguments
 =====================  =====================
Line 190 
Line 295 
 method::  method::
   
     >>> c = Code()      >>> c = Code()
     >>> where = c.here()         # get a location at the start of the code  
   
     >>> c.LOAD_CONST(42)      >>> c.LOAD_CONST(42)
       >>> where = c.here()         # get a location near the start of the code
       >>> c.DUP_TOP()
       >>> c.POP_TOP()
     >>> c.JUMP_ABSOLUTE(where)   # now jump back to it      >>> c.JUMP_ABSOLUTE(where)   # now jump back to it
   
     >>> dis(c.code())      >>> dis(c.code())
       0     >>    0 LOAD_CONST               1 (42)        0           0 LOAD_CONST               1 (42)
                   3 JUMP_ABSOLUTE            0              >>    3 DUP_TOP
                     4 POP_TOP
                     5 JUMP_ABSOLUTE            3
   
 But if you are jumping *forward*, you will need to call the jump or setup  But if you are jumping *forward*, you will need to call the jump or setup
 method without any arguments.  The return value will be a "forward reference"  method without any arguments.  The return value will be a "forward reference"
Line 205 
Line 313 
 been reached::  been reached::
   
     >>> c = Code()      >>> c = Code()
     >>> forward = c.JUMP_ABSOLUTE() # create a jump and a forward reference      >>> c.LOAD_CONST(99)
       >>> forward = c.JUMP_IF_TRUE() # create a jump and a forward reference
   
     >>> c.LOAD_CONST(42)            # this is what we want to skip over      >>> c.LOAD_CONST(42)            # this is what we want to skip over
       >>> c.POP_TOP()
   
     >>> forward()   # calling the reference changes the jump to point here      >>> forward()   # calling the reference changes the jump to point here
     >>> c.LOAD_CONST(23)      >>> c.LOAD_CONST(23)
     >>> c.RETURN_VALUE()      >>> c.RETURN_VALUE()
   
     >>> dis(c.code())      >>> dis(c.code())
       0           0 JUMP_ABSOLUTE            6        0           0 LOAD_CONST               1 (99)
                   3 LOAD_CONST               1 (42)                    3 JUMP_IF_TRUE             4 (to 10)
             >>    6 LOAD_CONST               2 (23)                    6 LOAD_CONST               2 (42)
                   9 RETURN_VALUE                    9 POP_TOP
               >>   10 LOAD_CONST               3 (23)
                    13 RETURN_VALUE
   
     >>> eval(c.code())      >>> eval(c.code())
     23      23
Line 246 
Line 358 
     >>> c.LOAD_CONST(None)  # in real code, this'd be a Python code constant      >>> c.LOAD_CONST(None)  # in real code, this'd be a Python code constant
     >>> c.MAKE_CLOSURE(0,2) # no defaults, 2 free vars in the new function      >>> c.MAKE_CLOSURE(0,2) # no defaults, 2 free vars in the new function
   
   The ``COMPARE_OP`` method takes an argument which can be a valid comparison
   integer constant, or a string containing a Python operator, e.g.::
   
       >>> c = Code()
       >>> c.LOAD_CONST(1)
       >>> c.LOAD_CONST(2)
       >>> c.COMPARE_OP('not in')
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (1)
                     3 LOAD_CONST               2 (2)
                     6 COMPARE_OP               7 (not in)
   
   The full list of valid operator strings can be found in the standard library's
   ``opcode`` module.  ``"<>"`` is also accepted as an alias for ``"!="``::
   
       >>> c.LOAD_CONST(3)
       >>> c.COMPARE_OP('<>')
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (1)
                     3 LOAD_CONST               2 (2)
                     6 COMPARE_OP               7 (not in)
                     9 LOAD_CONST               3 (3)
                    12 COMPARE_OP               3 (!=)
   
   
 High-Level Code Generation  High-Level Code Generation
 ==========================  ==========================
Line 279 
Line 415 
                  21 LOAD_CONST               0 (None)                   21 LOAD_CONST               0 (None)
                  24 LOAD_CONST               8 (<code object <lambda> at ...>)                   24 LOAD_CONST               8 (<code object <lambda> at ...>)
   
   Note that although some values of different types may compare equal to each
   other, ``Code`` objects will not substitute a value of a different type than
   the one you requested::
   
       >>> c = Code()
       >>> c(1, True, 1.0, 1L)     # equal, but different types
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (1)
                     3 LOAD_CONST               2 (True)
                     6 LOAD_CONST               3 (1.0)
                     9 LOAD_CONST               4 (1L)
   
 Simple Containers  Simple Containers
 -----------------  -----------------
Line 323 
Line 470 
 a constant, rather than generating code to recreate the tuple using a series of  a constant, rather than generating code to recreate the tuple using a series of
 ``LOAD_CONST`` operations followed by a ``BUILD_TUPLE``.  ``LOAD_CONST`` operations followed by a ``BUILD_TUPLE``.
   
   If the value wrapped in a ``Const`` is not hashable, it is compared by identity
   rather than value.  This prevents equal mutable values from being reused by
   accident, e.g. if you plan to mutate the "constant" values later::
   
       >>> c = Code()
       >>> c(Const([]), Const([]))     # equal, but not the same object!
       >>> dis(c.code())
         0           0 LOAD_CONST               1 ([])
                     3 LOAD_CONST               2 ([])
   
   Thus, although ``Const`` objects hash and compare based on equality for
   hashable types::
   
       >>> hash(Const(3)) == hash(3)
       True
       >>> Const(3)==Const(3)
       True
   
   They hash and compare based on object identity for non-hashable types::
   
       >>> c = Const([])
       >>> hash(c) == hash(id(c.value))
       True
       >>> c == Const(c.value)     # compares equal if same object
       True
       >>> c == Const([])          # but is not equal to a merely equal object
       False
   
   
   ``Suite`` and ``Pass``
   ----------------------
   
   On occasion, it's helpful to be able to group a sequence of opcodes,
   expressions, or statements together, to be passed as an argument to other node
   types.  The ``Suite`` node type accomplishes this::
   
       >>> from peak.util.assembler import Suite, Pass
   
       >>> c = Code()
       >>> c.return_(Suite([Const(42), Code.DUP_TOP, Code.POP_TOP]))
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 DUP_TOP
                     4 POP_TOP
                     5 RETURN_VALUE
   
   And ``Pass`` is a shortcut for an empty ``Suite``, that generates nothing::
   
       >>> Suite([])
       Pass
   
       >>> c = Code()
       >>> c(Pass)
       >>> c.return_(None)
       >>> dis(c.code())
         0           0 LOAD_CONST               0 (None)
                     3 RETURN_VALUE
   
   
 Local and Global Names  Local and Global Names
 ----------------------  ----------------------
Line 338 
Line 543 
       0           0 LOAD_FAST                0 (x)        0           0 LOAD_FAST                0 (x)
                   3 LOAD_GLOBAL              0 (y)                    3 LOAD_GLOBAL              0 (y)
   
   
 As with simple constants and ``Const`` wrappers, these objects can be used to  As with simple constants and ``Const`` wrappers, these objects can be used to
 construct more complex expressions, like ``{a:(b,c)}``::  construct more complex expressions, like ``{a:(b,c)}``::
   
Line 354 
Line 558 
                  16 ROT_THREE                   16 ROT_THREE
                  17 STORE_SUBSCR                   17 STORE_SUBSCR
   
   The ``LocalAssign`` node type takes a name, and stores a value in a local
   variable::
   
       >>> from peak.util.assembler import LocalAssign
       >>> c = Code()
       >>> c(42, LocalAssign('x'))
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_FAST               0 (x)
   
 If the code object is not using "fast locals" (i.e. ``CO_OPTIMIZED`` isn't  If the code object is not using "fast locals" (i.e. ``CO_OPTIMIZED`` isn't
 set), local variables will be dereferenced using ``LOAD_NAME`` instead of  set), local variables will be referenced using ``LOAD_NAME`` and ``STORE_NAME``
 ``LOAD_FAST``, and if the referenced local name is a "cell" or "free"  instead of ``LOAD_FAST`` and ``STORE_FAST``, and if the referenced local name
 variable, ``LOAD_DEREF`` is used instead::  is a "cell" or "free" variable, ``LOAD_DEREF`` and ``STORE_DEREF`` are used
   instead::
   
     >>> from peak.util.assembler import CO_OPTIMIZED      >>> from peak.util.assembler import CO_OPTIMIZED
     >>> c = Code()      >>> c = Code()
Line 365 
Line 580 
     >>> c.co_cellvars = ('y',)      >>> c.co_cellvars = ('y',)
     >>> c.co_freevars = ('z',)      >>> c.co_freevars = ('z',)
     >>> c( Local('x'), Local('y'), Local('z') )      >>> c( Local('x'), Local('y'), Local('z') )
       >>> c( LocalAssign('x'), LocalAssign('y'), LocalAssign('z') )
     >>> dis(c.code())      >>> dis(c.code())
       0           0 LOAD_NAME                0 (x)        0           0 LOAD_NAME                0 (x)
                   3 LOAD_DEREF               0 (y)                    3 LOAD_DEREF               0 (y)
                   6 LOAD_DEREF               1 (z)                    6 LOAD_DEREF               1 (z)
                     9 STORE_NAME               0 (x)
                    12 STORE_DEREF              0 (y)
                    15 STORE_DEREF              1 (z)
   
   
   Obtaining Attributes
   --------------------
   
   The ``Getattr`` node type takes an expression and an attribute name.  The
   attribute name can be a constant string, in which case a ``LOAD_ATTR`` opcode
   is used, and constant folding is done if possible::
   
       >>> from peak.util.assembler import Getattr
   
       >>> c = Code()
       >>> c(Getattr(Local('x'), '__class__'))
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (x)
                     3 LOAD_ATTR                0 (__class__)
   
   
       >>> Getattr(Const(object), '__class__') # const expression, const result
       Const(<type 'type'>)
   
   Or the attribute name can be an expression, in which case a ``getattr()`` call
   is compiled instead::
   
       >>> c = Code()
       >>> c(Getattr(Local('x'), Local('y')))
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (<built-in function getattr>)
                     3 LOAD_FAST                0 (x)
                     6 LOAD_FAST                1 (y)
                     9 CALL_FUNCTION            2
   
   
 Calling Functions and Methods  Calling Functions and Methods
Line 439 
Line 689 
   
     >>> c = Code()      >>> c = Code()
     >>> c.return_()      >>> c.return_()
       >>> dis(c.code())
         0           0 LOAD_CONST               0 (None)
                     3 RETURN_VALUE
   
       >>> c = Code()
     >>> c( Return() )      >>> c( Return() )
     >>> dis(c.code())      >>> dis(c.code())
       0           0 LOAD_CONST               0 (None)        0           0 LOAD_CONST               0 (None)
                   3 RETURN_VALUE                    3 RETURN_VALUE
                   4 LOAD_CONST               0 (None)  
                   7 RETURN_VALUE  
   ``If`` Conditions
   -----------------
   
   The ``If()`` node type generates conditional code, roughly equivalent to a
   Python if/else statement::
   
       >>> from peak.util.assembler import If
       >>> c = Code()
       >>> c( If(Local('a'), Return(42), Return(55)) )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (a)
                     3 JUMP_IF_FALSE            5 (to 11)
                     6 POP_TOP
                     7 LOAD_CONST               1 (42)
                    10 RETURN_VALUE
               >>   11 POP_TOP
                    12 LOAD_CONST               2 (55)
                    15 RETURN_VALUE
   
   However, it can also be used like a Python 2.5+ conditional expression
   (regardless of the targeted Python version)::
   
       >>> c = Code()
       >>> c( Return(If(Local('a'), 42, 55)) )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (a)
                     3 JUMP_IF_FALSE            7 (to 13)
                     6 POP_TOP
                     7 LOAD_CONST               1 (42)
                    10 JUMP_FORWARD             4 (to 17)
               >>   13 POP_TOP
                    14 LOAD_CONST               2 (55)
               >>   17 RETURN_VALUE
   
   
   Note that ``If()`` does *not* do constant-folding on its condition; even if the
   condition is a constant, it will be tested at runtime.  This avoids issues with
   using mutable constants, e.g.::
   
       >>> c = Code()
       >>> c(If(Const([]), 42, 55))
       >>> dis(c.code())
         0           0 LOAD_CONST               1 ([])
                     3 JUMP_IF_FALSE            7 (to 13)
                     6 POP_TOP
                     7 LOAD_CONST               2 (42)
                    10 JUMP_FORWARD             4 (to 17)
               >>   13 POP_TOP
                    14 LOAD_CONST               3 (55)
   
   
 Labels and Jump Targets  Labels and Jump Targets
Line 455 
Line 759 
 current location.  For example::  current location.  For example::
   
     >>> c = Code()      >>> c = Code()
     >>> forward = c.JUMP_FORWARD()      >>> c.LOAD_CONST(99)
     >>> c( 1, 2, forward, Return(3) )      >>> forward = c.JUMP_IF_FALSE()
       >>> c( 1, Code.POP_TOP, forward, Return(3) )
     >>> dis(c.code())      >>> dis(c.code())
       0           0 JUMP_FORWARD             6 (to 9)        0           0 LOAD_CONST               1 (99)
                   3 LOAD_CONST               1 (1)                    3 JUMP_IF_FALSE            4 (to 10)
                   6 LOAD_CONST               2 (2)                    6 LOAD_CONST               2 (1)
              >>   9 LOAD_CONST               3 (3)                    9 POP_TOP
                  12 RETURN_VALUE              >>   10 LOAD_CONST               3 (3)
                    13 RETURN_VALUE
   
 However, there's an easier way to do the same thing, using ``Label`` objects::  However, there's an easier way to do the same thing, using ``Label`` objects::
   
Line 470 
Line 776 
     >>> c = Code()      >>> c = Code()
     >>> skip = Label()      >>> skip = Label()
   
     >>> c(skip.JUMP_FORWARD, 1, 2, skip, Return(3))      >>> c(99, skip.JUMP_IF_FALSE, 1, Code.POP_TOP, skip, Return(3))
     >>> dis(c.code())      >>> dis(c.code())
       0           0 JUMP_FORWARD             6 (to 9)        0           0 LOAD_CONST               1 (99)
                   3 LOAD_CONST               1 (1)                    3 JUMP_IF_FALSE            4 (to 10)
                   6 LOAD_CONST               2 (2)                    6 LOAD_CONST               2 (1)
              >>   9 LOAD_CONST               3 (3)                    9 POP_TOP
                  12 RETURN_VALUE              >>   10 LOAD_CONST               3 (3)
                    13 RETURN_VALUE
   
 This approach has the advantage of being easy to use in complex trees.  This approach has the advantage of being easy to use in complex trees.
 ``Label`` objects have attributes corresponding to every opcode that uses a  ``Label`` objects have attributes corresponding to every opcode that uses a
Line 492 
Line 799 
     AssertionError: Label previously defined      AssertionError: Label previously defined
   
   
   N-Way Comparisons
   -----------------
   
   You can generate N-way comparisons using the ``Compare()`` node type::
   
       >>> from peak.util.assembler import Compare
   
       >>> c = Code()
       >>> c(Compare(Local('a'), [('<', Local('b'))]))
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (a)
                     3 LOAD_FAST                1 (b)
                     6 COMPARE_OP               0 (<)
   
   3-way comparisons generate code that's a bit more complex.  Here's a three-way
   comparison (``a<b<c``)::
   
       >>> c = Code()
       >>> c.return_(Compare(Local('a'), [('<', Local('b')), ('<', Local('c'))]))
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (a)
                     3 LOAD_FAST                1 (b)
                     6 DUP_TOP
                     7 ROT_THREE
                     8 COMPARE_OP               0 (<)
                    11 JUMP_IF_FALSE           10 (to 24)
                    14 POP_TOP
                    15 LOAD_FAST                2 (c)
                    18 COMPARE_OP               0 (<)
                    21 JUMP_FORWARD             2 (to 26)
               >>   24 ROT_TWO
                    25 POP_TOP
               >>   26 RETURN_VALUE
   
   And a four-way (``a<b>c!=d``)::
   
       >>> c = Code()
       >>> c.return_(
       ...     Compare( Local('a'), [
       ...         ('<', Local('b')), ('>', Local('c')), ('!=', Local('d'))
       ...     ])
       ... )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (a)
                     3 LOAD_FAST                1 (b)
                     6 DUP_TOP
                     7 ROT_THREE
                     8 COMPARE_OP               0 (<)
                    11 JUMP_IF_FALSE           22 (to 36)
                    14 POP_TOP
                    15 LOAD_FAST                2 (c)
                    18 DUP_TOP
                    19 ROT_THREE
                    20 COMPARE_OP               4 (>)
                    23 JUMP_IF_FALSE           10 (to 36)
                    26 POP_TOP
                    27 LOAD_FAST                3 (d)
                    30 COMPARE_OP               3 (!=)
                    33 JUMP_FORWARD             2 (to 38)
               >>   36 ROT_TWO
                    37 POP_TOP
               >>   38 RETURN_VALUE
   
   
   Sequence Unpacking
   ------------------
   
   The ``UnpackSequence`` node type takes a sequence of code generation targets,
   and generates an ``UNPACK_SEQUENCE`` of the correct length, followed by the
   targets::
   
       >>> from peak.util.assembler import UnpackSequence
       >>> c = Code()
       >>> c((1,2), UnpackSequence([LocalAssign('x'), LocalAssign('y')]))
       >>> dis(c.code())   # x, y = 1, 2
         0           0 LOAD_CONST               1 (1)
                     3 LOAD_CONST               2 (2)
                     6 BUILD_TUPLE              2
                     9 UNPACK_SEQUENCE          2
                    12 STORE_FAST               0 (x)
                    15 STORE_FAST               1 (y)
   
   
   Yield Statements
   ----------------
   
   The ``YieldStmt`` node type generates the necessary opcode(s) for a ``yield``
   statement, based on the target Python version.  (In Python 2.5+, a ``POP_TOP``
   must be generated after a ``YIELD_VALUE`` in order to create a yield statement,
   as opposed to a yield expression.)  It also sets the code flags needed to make
   the resulting code object a generator::
   
       >>> from peak.util.assembler import YieldStmt
       >>> c = Code()
       >>> c(YieldStmt(1), YieldStmt(2), Return(None))
       >>> list(eval(c.code()))
       [1, 2]
   
   
   
 Constant Detection and Folding  Constant Detection and Folding
 ==============================  ==============================
   
Line 517 
Line 924 
     >>> const_value(Local('x'))      >>> const_value(Local('x'))
     Traceback (most recent call last):      Traceback (most recent call last):
       ...        ...
     NotAConstant: <bound method str.Local of 'x'>      NotAConstant: Local('x')
   
 Tuples of constants are recursively replaced by constant tuples::  Tuples of constants are recursively replaced by constant tuples::
   
Line 532 
Line 939 
     >>> const_value( (1,Global('y')) )      >>> const_value( (1,Global('y')) )
     Traceback (most recent call last):      Traceback (most recent call last):
       ...        ...
     NotAConstant: <bound method str.Global of 'y'>      NotAConstant: Global('y')
   
 As do any types not previously described here::  As do any types not previously described here::
   
Line 558 
Line 965 
 ``Const`` node instead of a ``Call`` node::  ``Const`` node instead of a ``Call`` node::
   
     >>> Call( Const(type), [1] )      >>> Call( Const(type), [1] )
     <bound method type.Const of <type 'int'>>      Const(<type 'int'>)
   
 Thus, you can also take the ``const_value()`` of such calls::  Thus, you can also take the ``const_value()`` of such calls::
   
Line 569 
Line 976 
 passed in to another ``Call``::  passed in to another ``Call``::
   
     >>> Call(Const(type), [Call( Const(dict), [], [('x',27)] )])      >>> Call(Const(type), [Call( Const(dict), [], [('x',27)] )])
     <bound method type.Const of <type 'dict'>>      Const(<type 'dict'>)
   
 Notice that this folding takes place eagerly, during AST construction.  If you  Notice that this folding takes place eagerly, during AST construction.  If you
 want to implement delayed folding after constant propagation or variable  want to implement delayed folding after constant propagation or variable
Line 607 
Line 1014 
 ``globals()``, in other words.  ``globals()``, in other words.
   
   
   Logical And/Or
   --------------
   
   You can evaluate logical and/or expressions using the ``And`` and ``Or`` node
   types::
   
       >>> from peak.util.assembler import And, Or
   
       >>> c = Code()
       >>> c.return_( And([Local('x'), Local('y')]) )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (x)
                     3 JUMP_IF_FALSE            4 (to 10)
                     6 POP_TOP
                     7 LOAD_FAST                1 (y)
               >>   10 RETURN_VALUE
   
       >>> c = Code()
       >>> c.return_( Or([Local('x'), Local('y')]) )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (x)
                     3 JUMP_IF_TRUE             4 (to 10)
                     6 POP_TOP
                     7 LOAD_FAST                1 (y)
               >>   10 RETURN_VALUE
   
   
   True or false constants are folded automatically, avoiding code generation
   for intermediate values that will never be used in the result::
   
       >>> c = Code()
       >>> c.return_( And([1, 2, Local('y')]) )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (y)
                     3 RETURN_VALUE
   
       >>> c = Code()
       >>> c.return_( And([1, 2, Local('y'), 0]) )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (y)
                     3 JUMP_IF_FALSE            4 (to 10)
                     6 POP_TOP
                     7 LOAD_CONST               1 (0)
               >>   10 RETURN_VALUE
   
       >>> c = Code()
       >>> c.return_( Or([1, 2, Local('y')]) )
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (1)
                     3 RETURN_VALUE
   
       >>> c = Code()
       >>> c.return_( Or([False, Local('y'), 3]) )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (y)
                     3 JUMP_IF_TRUE             4 (to 10)
                     6 POP_TOP
                     7 LOAD_CONST               1 (3)
               >>   10 RETURN_VALUE
   
   
 Custom Code Generation  Custom Code Generation
 ======================  ======================
   
Line 628 
Line 1096 
 As you can see, the ``Code.DUP_TOP()`` is called on the code instance, causing  As you can see, the ``Code.DUP_TOP()`` is called on the code instance, causing
 a ``DUP_TOP`` opcode to be output.  This is sometimes a handy trick for  a ``DUP_TOP`` opcode to be output.  This is sometimes a handy trick for
 accessing values that are already on the stack.  More commonly, however, you'll  accessing values that are already on the stack.  More commonly, however, you'll
 want to implement more sophisticated callables, perhaps something like::  want to implement more sophisticated callables.
   
   To make it easy to create diverse target types, a ``nodetype()`` decorator is
   provided::
   
     >>> from peak.util.assembler import ast_curry      >>> from peak.util.assembler import nodetype
   
   It allows you to create code generation target types using functions.  Your
   function should take one or more arguments, with a ``code=None`` optional
   argument in the last position.  It should check whether ``code is None`` when
   called, and if so, return a tuple of the preceding arguments.  If ``code``
   is not ``None``, then it should do whatever code generating tasks are required.
   For example::
   
     >>> def TryFinally(block1, block2, code=None):      >>> def TryFinally(block1, block2, code=None):
     ...     if code is None:      ...     if code is None:
     ...         return ast_curry(TryFinally, block1, block2)      ...         return block1, block2
     ...     code(      ...     code(
     ...         Code.SETUP_FINALLY,      ...         Code.SETUP_FINALLY,
     ...             block1,      ...             block1,
Line 642 
Line 1120 
     ...             block2,      ...             block2,
     ...         Code.END_FINALLY      ...         Code.END_FINALLY
     ...     )      ...     )
       >>> TryFinally = nodetype()(TryFinally)
   
   Note: although the nodetype() generator can be used above the function
   definition in either Python 2.3 or 2.4, it cannot be done in a doctest under
   Python 2.3, so this document doesn't attempt to demonstrate that.  Under
   2.4, you would do something like this::
   
       @nodetype()
       def TryFinally(...):
   
   and code that needs to also work under 2.3 should do something like this::
   
       nodetype()
       def TryFinally(...):
   
   But to keep the examples here working with doctest, we'll be doing our
   ``nodetype()`` calls after the end of the function definitions, e.g.::
   
     >>> def ExprStmt(value, code=None):      >>> def ExprStmt(value, code=None):
     ...     if code is None:      ...     if code is None:
     ...         return ast_curry(ExprStmt, value)      ...         return value,
     ...     code( value, Code.POP_TOP )      ...     code( value, Code.POP_TOP )
       >>> ExprStmt = nodetype()(ExprStmt)
   
     >>> c = Code()      >>> c = Code()
     >>> c( TryFinally(ExprStmt(1), ExprStmt(2)) )      >>> c( TryFinally(ExprStmt(1), ExprStmt(2)) )
Line 660 
Line 1156 
                  14 POP_TOP                   14 POP_TOP
                  15 END_FINALLY                   15 END_FINALLY
   
 The ``ast_curry()`` utility function returns an ``instancemethod`` chain that  The ``nodetype()`` decorator is virtually identical to the ``struct()``
 binds the given arguments to the given function, creating a hashable and  decorator in the DecoratorTools package, except that it does not support
 comparable data structure -- a trivial sort of "AST node".  Just follow the  ``*args``, does not create a field for the ``code`` argument, and generates a
 code pattern above, using a ``code=None`` final argument, and returning a  ``__call__()`` method that reinvokes the wrapped function to do the actual
 curried version of the function if ``code is None``.  Otherwise, your function  code generation.
 should simply do whatever is needed to "generate" the arguments.  
   Among the benefits of this decorator are:
 (This is exactly the same pattern that ``peak.util.assembler`` uses internally  
 to implement ``Const``, ``Call``, ``Local``, and other wrapper functions.)  * It gives your node types a great debugging format::
   
 The ``ast_curry()`` utility function isn't quite perfect; due to a quirk of the      >>> tf = TryFinally(ExprStmt(1), ExprStmt(2))
 ``instancemethod`` type, it can't save arguments whose value is ``None``: if      >>> tf
 you pass a ``None`` argument to ``ast_curry()``, it will be replaced with a      TryFinally(ExprStmt(1), ExprStmt(2))
 special ``nil`` object that tests as false, and generates a ``None`` constant  
 when code is generated for it.  If your function accepts any arguments that  * It makes named fields accessible::
 might have a value of ``None``, you must correctly handle the cases where you  
 receive a value of ``nil`` (found in ``peak.util.assembler``) instead of  
 ``None``.  
   
 However, if you can use ``ast_curry()`` to generate your AST nodes, you will  
 have objects that are hashable and comparable by default, as long as none of  
 your child nodes are unhashable or incomparable.  This can be useful for  
 algorithms that require comparing AST subtrees, such as common subexpression  
 elimination.  
   
       >>> tf.block1
       ExprStmt(1)
   
       >>> tf.block2
       ExprStmt(2)
   
   * Hashing and comparison work as expected (handy for algorithms that require
     comparing or caching AST subtrees, such as common subexpression
     elimination)::
   
       >>> ExprStmt(1) == ExprStmt(1)
       True
       >>> ExprStmt(1) == ExprStmt(2)
       False
   
   
   Please see the `struct decorator documentation`_ for info on how to customize
   node types further.
   
   .. _struct decorator documentation: http://peak.telecommunity.com/DevCenter/DecoratorTools#the-struct-decorator
   
   Note: hashing only works if all the values you return in your argument tuple
   are hashable, so you should try to convert them if possible.  For example, if
   an argument accepts any sequence, you should probably convert it to a tuple
   before returning it.  Most of the examples in this document, and the node types
   supplied by ``peak.util.assembler`` itself do this.
   
   
 Constant Folding in Custom Targets  Constant Folding in Custom Targets
Line 692 
Line 1205 
   
 If you want to incorporate constant-folding into your AST nodes, you can do  If you want to incorporate constant-folding into your AST nodes, you can do
 so by checking for constant values and folding them at either construction  so by checking for constant values and folding them at either construction
 or code generation time.  For example, this ``And`` node type folds constants  or code generation time.  For example, this ``And`` node type (a simpler
 during code generation, by not generating unnecessary branches when it can  version of the one included in ``peak.util.assembler``) folds constants during
   code generation, by not generating unnecessary branches when it can
 prove which way a branch will go::  prove which way a branch will go::
   
     >>> from peak.util.assembler import NotAConstant      >>> from peak.util.assembler import NotAConstant
   
     >>> def And(values, code=None):      >>> def And(values, code=None):
     ...     if code is None:      ...     if code is None:
     ...         return ast_curry(And, tuple(values))      ...         return tuple(values),
     ...     end = Label()      ...     end = Label()
     ...     for value in values[:-1]:      ...     for value in values[:-1]:
     ...         try:      ...         try:
Line 711 
Line 1225 
     ...         else:       # and false constants end the chain right away      ...         else:       # and false constants end the chain right away
     ...             return code(value, end)      ...             return code(value, end)
     ...     code(values[-1], end)      ...     code(values[-1], end)
       >>> And = nodetype()(And)
   
     >>> c = Code()      >>> c = Code()
     >>> c.return_( And([1, 2]) )      >>> c.return_( And([1, 2]) )
Line 735 
Line 1250 
   
 The above example only folds constants at code generation time, however.  You  The above example only folds constants at code generation time, however.  You
 can also do constant folding at AST construction time, using the  can also do constant folding at AST construction time, using the
 ``folding_curry()`` function.  For example::  ``fold_args()`` function.  For example::
   
     >>> from peak.util.assembler import folding_curry      >>> from peak.util.assembler import fold_args
   
     >>> def Getattr(ob, name, code=None):      >>> def Getattr(ob, name, code=None):
     ...     try:      ...     try:
Line 745 
Line 1260 
     ...     except NotAConstant:      ...     except NotAConstant:
     ...         return Call(Const(getattr), [ob, name])      ...         return Call(Const(getattr), [ob, name])
     ...     if code is None:      ...     if code is None:
     ...         return folding_curry(Getattr, ob, name)      ...         return fold_args(Getattr, ob, name)
     ...     code(ob)      ...     code(ob)
     ...     code.LOAD_ATTR(name)      ...     code.LOAD_ATTR(name)
       >>> Getattr = nodetype()(Getattr)
   
     >>> const_value(Getattr(1, '__class__'))      >>> const_value(Getattr(1, '__class__'))
     <type 'int'>      <type 'int'>
   
 The ``folding_curry()`` function is essentially the same as ``ast_curry()``,  The ``fold_args()`` function tries to evaluate the node immediately, if all of
 unless all of the arguments it's given are recognized as constants.  In that  its arguments are constants, by creating a temporary ``Code`` object, and
 case, ``folding_curry()`` will create a temporary ``Code`` object, and run the  running the supplied function against it, then doing an ``eval()`` on the
 curried function against it, doing an ``eval()`` on the generated code and  generated code and wrapping the result in a ``Const``.  However, if any of the
 wrapping the result in a ``Const``.  arguments are non-constant, the original arguments (less the function) are
   returned. This causes a normal node instance to be created instead of a
   ``Const``.
   
 This isn't a very *fast* way of doing partial evaluation, but it makes it  This isn't a very *fast* way of doing partial evaluation, but it makes it
 really easy to define new code generation targets without writing custom  really easy to define new code generation targets without writing custom
 constant-folding code for each one.  Just use ``folding_curry()`` instead of  constant-folding code for each one.  Just ``return fold_args(ThisType, *args)``
 ``ast_curry()`` if you want your node constructor to be able to do eager  instead of ``return args``, if you want your node constructor to be able to do
 evaluation.  If you need to, you can check your parameters in order to decide  eager evaluation.  If you need to, you can check your parameters in order to
 whether to call ``ast_curry()`` or ``folding_curry()``; this is in fact how  decide whether to call ``fold_args()`` or not; this is in fact how ``Call``
 ``Call`` implements its ``fold`` argument and the suppression of folding when  implements its ``fold`` argument and the suppression of folding when
 the call has no arguments.  the call has no arguments.
   
   (By the way, this same ``Getattr`` node type is also available
   
   
 Setting the Code's Calling Signature  Setting the Code's Calling Signature
 ====================================  ====================================
Line 800 
Line 1320 
     >>> c1 = Code.from_function(f1, copy_lineno=True)      >>> c1 = Code.from_function(f1, copy_lineno=True)
     >>> c1.co_firstlineno      >>> c1.co_firstlineno
     1      1
       >>> c1.co_filename is f1.func_code.co_filename
       True
   
 If you create a ``Code`` instance from a function that has nested positional  If you create a ``Code`` instance from a function that has nested positional
 arguments, the returned code object will include a prologue to unpack the  arguments, the returned code object will include a prologue to unpack the
Line 831 
Line 1353 
     >>> inspect.getargspec(f4)      >>> inspect.getargspec(f4)
     (['a', ['b', 'c'], ['d', ['e', 'f']]], None, None, None)      (['a', ['b', 'c'], ['d', ['e', 'f']]], None, None, None)
   
   You can also use the ``from_spec(name='<lambda>', args=(), var=None, kw=None)``
   classmethod to explicitly set a name and argument spec for a new code object::
   
       >>> c = Code.from_spec('a', ('b', ('c','d'), 'e'), 'f', 'g')
       >>> c.co_name
       'a'
   
       >>> c.co_varnames
       ['b', '.1', 'e', 'f', 'g', 'c', 'd']
   
       >>> c.co_argcount
       3
   
       >>> inspect.getargs(c.code())
       (['b', ['c', 'd'], 'e'], 'f', 'g')
   
   
 Code Attributes  Code Attributes
 ===============  ===============
Line 907 
Line 1445 
   
 stack_size  stack_size
     The predicted height of the runtime value stack, as of the current opcode.      The predicted height of the runtime value stack, as of the current opcode.
     Its value is automatically updated by most opcodes, but you may want to      Its value is automatically updated by most opcodes, but if you are doing
     save and restore it for things like try/finally blocks.  If you increase      something sufficiently tricky (as in the ``Switch`` demo, below) you may
     the value of this attribute, you should also update the ``co_stacksize``      need to explicitly set it.
     attribute if it is less than the new ``stack_size``.  
       The ``stack_size`` automatically becomes ``None`` after any unconditional
       jump operations, such as ``JUMP_FORWARD``, ``BREAK_LOOP``, or
       ``RETURN_VALUE``.  When the stack size is ``None``, the only operations
       that can be performed are the resolving of forward references (which will
       set the stack size to what it was when the reference was created), or
       manually setting the stack size.
   
 co_freevars  co_freevars
     A tuple of strings naming a function's "free" variables.  Defaults to an      A tuple of strings naming a function's "free" variables.  Defaults to an
Line 954 
Line 1498 
   
 co_stacksize  co_stacksize
     The maximum amount of stack space the code will require to run.  This      The maximum amount of stack space the code will require to run.  This
     value is usually updated automatically as you generate code.  However, if      value is updated automatically as you generate code or change
     you manually set a new ``stack_size`` that is larger than the current      the ``stack_size`` attribute.
     ``co_stacksize``, you should increase the ``co_stacksize`` to match, so  
     that ``co_stacksize`` is always the largest stack size the code will  
     generate at runtime.  
   Stack Size Tracking and Dead Code Detection
   ===========================================
   
   ``Code`` objects automatically track the predicted stack size as code is
   generated, by updating the ``stack_size`` attribute as each operation occurs.
   A history is kept so that backward jumps can be checked to ensure that the
   current stack height is the same as at the jump's target.  Similarly, when
   forward jumps are resolved, the stack size at the jump target is checked
   against the stack size at the jump's origin.  If there are multiple jumps to
   the same location, they must all have the same stack size at the origin and
   the destination.
   
   In addition, whenever any unconditional jump code is generated (i.e.
   ``JUMP_FORWARD``, ``BREAK_LOOP``, ``CONTINUE_LOOP``, ``JUMP_ABSOLUTE``, or
   ``RETURN_VALUE``), the predicted ``stack_size`` is set to ``None``.  This
   means that the ``Code`` object does not know what the stack size will be at
   the current location.  You cannot issue *any* instructions when the predicted
   stack size is ``None``, as you will receive an ``AssertionError``::
   
       >>> c = Code()
       >>> fwd = c.JUMP_FORWARD()
       >>> print c.stack_size  # forward jump marks stack size as unknown
       None
   
       >>> c.LOAD_CONST(42)
       Traceback (most recent call last):
         ...
       AssertionError: Unknown stack size at this location
   
   Instead, you must resolve a forward reference (or define a previously-jumped to
   label).  This will propagate the stack size at the source of the jump to the
   current location, updating the stack size::
   
       >>> fwd()
       >>> c.stack_size
       0
   
   Note, by the way, that this means it is impossible for you to generate static
   "dead code".  In other words, you cannot generate code that isn't reachable.
   You should therefore check if ``stack_size`` is ``None`` before generating
   code that might be unreachable.  For example, consider this ``If``
   implementation::
   
       >>> def If(cond, then, else_=Pass, code=None):
       ...     if code is None:
       ...         return cond, then, else_
       ...     else_clause = Label()
       ...     end_if = Label()
       ...     code(cond, else_clause.JUMP_IF_FALSE, Code.POP_TOP, then)
       ...     code(end_if.JUMP_FORWARD, else_clause, Code.POP_TOP, else_)
       ...     code(end_if)
       >>> If = nodetype()(If)
   
   It works okay if there's no dead code::
   
       >>> c = Code()
       >>> c( If(Local('a'), 42, 55) )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (a)
                     3 JUMP_IF_FALSE            7 (to 13)
                     6 POP_TOP
                     7 LOAD_CONST               1 (42)
                    10 JUMP_FORWARD             4 (to 17)
               >>   13 POP_TOP
                    14 LOAD_CONST               2 (55)
   
   But it breaks if you end the "then" block with a return::
   
       >>> c = Code()
       >>> c( If(23, Return(42), 55) )
       Traceback (most recent call last):
         ...
       AssertionError: Unknown stack size at this location
   
   What we need is something like this instead::
   
       >>> def If(cond, then, else_=Pass, code=None):
       ...     if code is None:
       ...         return cond, then, else_
       ...     else_clause = Label()
       ...     end_if = Label()
       ...     code(cond, else_clause.JUMP_IF_FALSE, Code.POP_TOP, then)
       ...     if code.stack_size is not None:
       ...         end_if.JUMP_FORWARD(code)
       ...     code(else_clause, Code.POP_TOP, else_, end_if)
       >>> If = nodetype()(If)
   
   As you can see, the dead code is now eliminated::
   
       >>> c = Code()
       >>> c( If(Local('a'), Return(42), 55) )
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (a)
                     3 JUMP_IF_FALSE            5 (to 11)
                     6 POP_TOP
                     7 LOAD_CONST               1 (42)
                    10 RETURN_VALUE
               >>   11 POP_TOP
                    12 LOAD_CONST               2 (55)
   
   
 Blocks, Loops, and Exception Handling  Blocks, Loops, and Exception Handling
Line 988 
Line 1631 
     >>> c.code()      >>> c.code()
     <code object <lambda> ...>      <code object <lambda> ...>
   
 ``Code`` objects also check that the stack level as of a ``POP_BLOCK`` is the  
 same as it was when the block was set up::  
   
     >>> c = Code()  
     >>> c.SETUP_LOOP()  
     >>> c.LOAD_CONST(23)  
     >>> c.POP_BLOCK()  
     Traceback (most recent call last):  
       ...  
     AssertionError: Stack level mismatch: actual=1 expected=0  
   
   
 Exception Stack Size Adjustment  Exception Stack Size Adjustment
 -------------------------------  -------------------------------
   
 When you ``POP_BLOCK`` for a ``SETUP_EXCEPT`` or ``SETUP_FINALLY``, the code's  When you issue a ``SETUP_EXCEPT`` or ``SETUP_FINALLY``, the code's maximum
 maximum stack size is raised to ensure that it's at least 3 items higher than  stack size is raised to ensure that it's at least 3 items higher than
 the current stack size.  That way, there will be room for the items that Python  the current stack size.  That way, there will be room for the items that Python
 puts on the stack when jumping to a block's exception handling code::  puts on the stack when jumping to a block's exception handling code::
   
     >>> c = Code()      >>> c = Code()
     >>> c.SETUP_FINALLY()      >>> c.SETUP_FINALLY()
     >>> c.stack_size, c.co_stacksize      >>> c.stack_size, c.co_stacksize
     (0, 0)  
     >>> c.POP_BLOCK()  
     >>> c.END_FINALLY()  
     >>> c.stack_size, c.co_stacksize  
     (0, 3)      (0, 3)
   
 As you can see, the current stack size is unchanged, but the maximum stack size  As you can see, the current stack size is unchanged, but the maximum stack size
Line 1024 
Line 1652 
     >>> c = Code()      >>> c = Code()
     >>> c(1,2,3,4, *[Code.POP_TOP]*4)   # push 4 things, then pop 'em      >>> c(1,2,3,4, *[Code.POP_TOP]*4)   # push 4 things, then pop 'em
     >>> c.SETUP_FINALLY()      >>> c.SETUP_FINALLY()
     >>> c.POP_BLOCK()  
     >>> c.END_FINALLY()  
     >>> c.stack_size, c.co_stacksize      >>> c.stack_size, c.co_stacksize
     (0, 4)      (0, 4)
   
Line 1034 
Line 1660 
   
     >>> c = Code()      >>> c = Code()
     >>> c.SETUP_LOOP()      >>> c.SETUP_LOOP()
     >>> break_to = c.POP_BLOCK()  
     >>> c.stack_size, c.co_stacksize      >>> c.stack_size, c.co_stacksize
     (0, 0)      (0, 0)
   
Line 1042 
Line 1667 
 Try/Except Blocks  Try/Except Blocks
 -----------------  -----------------
   
 In the case of ``SETUP_EXCEPT``, the *current* stack size is also increased by  In the case of ``SETUP_EXCEPT``, the *current* stack size is increased by 3
 3, because the code following the ``POP_BLOCK`` will be the exception handler  after a ``POP_BLOCK``, because the code that follows will be an exception
 and will thus always have exception items on the stack::  handler and will thus always have exception items on the stack::
   
     >>> c = Code()      >>> c = Code()
     >>> c.SETUP_EXCEPT()      >>> c.SETUP_EXCEPT()
Line 1101 
Line 1726 
             >>   10 LOAD_CONST               0 (None)              >>   10 LOAD_CONST               0 (None)
                  13 RETURN_VALUE                   13 RETURN_VALUE
   
 Labels have a ``POP_BLOCK`` attribute that you can pass in when generating  (Labels have a ``POP_BLOCK`` attribute that you can pass in when generating
 code.  code.)
   
   And, for generating typical try/except blocks, you can use the ``TryExcept``
   node type, which takes a body, a sequence of exception-type/handler pairs,
   and an optional "else" clause::
   
       >>> from peak.util.assembler import TryExcept
       >>> c = Code()
       >>> c.return_(
       ...     TryExcept(
       ...         Return(1),                                      # body
       ...         [(Const(KeyError),2), (Const(TypeError),3)],    # handlers
       ...         Return(4)                                       # else clause
       ...     )
       ... )
   
       >>> dis(c.code())
         0           0 SETUP_EXCEPT             8 (to 11)
                     3 LOAD_CONST               1 (1)
                     6 RETURN_VALUE
                     7 POP_BLOCK
                     8 JUMP_FORWARD            43 (to 54)
               >>   11 DUP_TOP
                    12 LOAD_CONST               2 (<...exceptions.KeyError...>)
                    15 COMPARE_OP              10 (exception match)
                    18 JUMP_IF_FALSE           10 (to 31)
                    21 POP_TOP
                    22 POP_TOP
                    23 POP_TOP
                    24 POP_TOP
                    25 LOAD_CONST               3 (2)
                    28 JUMP_FORWARD            27 (to 58)
               >>   31 POP_TOP
                    32 DUP_TOP
                    33 LOAD_CONST               4 (<...exceptions.TypeError...>)
                    36 COMPARE_OP              10 (exception match)
                    39 JUMP_IF_FALSE           10 (to 52)
                    42 POP_TOP
                    43 POP_TOP
                    44 POP_TOP
                    45 POP_TOP
                    46 LOAD_CONST               5 (3)
                    49 JUMP_FORWARD             6 (to 58)
               >>   52 POP_TOP
                    53 END_FINALLY
               >>   54 LOAD_CONST               6 (4)
                    57 RETURN_VALUE
               >>   58 RETURN_VALUE
   
   
 Try/Finally Blocks  Try/Finally Blocks
Line 1139 
Line 1811 
 adjusts the maximum expected stack size to accomodate up to three values being  adjusts the maximum expected stack size to accomodate up to three values being
 put on the stack by the Python interpreter for exception handling.  put on the stack by the Python interpreter for exception handling.
   
   For your convenience, the ``TryFinally`` node type can also be used to generate
   try/finally blocks::
   
       >>> from peak.util.assembler import TryFinally
       >>> c = Code()
       >>> c( TryFinally(ExprStmt(1), ExprStmt(2)) )
       >>> dis(c.code())
         0           0 SETUP_FINALLY            8 (to 11)
                     3 LOAD_CONST               1 (1)
                     6 POP_TOP
                     7 POP_BLOCK
                     8 LOAD_CONST               0 (None)
               >>   11 LOAD_CONST               2 (2)
                    14 POP_TOP
                    15 END_FINALLY
   
   
 Loops  Loops
 -----  -----
Line 1210 
Line 1898 
 And ``CONTINUE_LOOP`` is automatically replaced with a ``JUMP_ABSOLUTE`` if  And ``CONTINUE_LOOP`` is automatically replaced with a ``JUMP_ABSOLUTE`` if
 it occurs directly inside a loop block::  it occurs directly inside a loop block::
   
       >>> c.LOAD_CONST(57)
     >>> c.SETUP_LOOP()      >>> c.SETUP_LOOP()
       >>> fwd = c.JUMP_IF_TRUE()
     >>> c.CONTINUE_LOOP(c.here())      >>> c.CONTINUE_LOOP(c.here())
       >>> fwd()
     >>> c.BREAK_LOOP()      >>> c.BREAK_LOOP()
     >>> c.POP_BLOCK()()      >>> c.POP_BLOCK()()
     >>> dis(c.code())      >>> dis(c.code())
       0           0 SETUP_LOOP               5 (to 8)        0           0 LOAD_CONST               1 (57)
             >>    3 JUMP_ABSOLUTE            3                    3 SETUP_LOOP               8 (to 14)
                   6 BREAK_LOOP                    6 JUMP_IF_TRUE             3 (to 12)
                   7 POP_BLOCK              >>    9 JUMP_ABSOLUTE            9
               >>   12 BREAK_LOOP
                    13 POP_BLOCK
   
 In other words, ``CONTINUE_LOOP`` only really emits a ``CONTINUE_LOOP`` opcode  In other words, ``CONTINUE_LOOP`` only really emits a ``CONTINUE_LOOP`` opcode
 if it's inside some other kind of block within the loop, e.g. a "try" clause::  if it's inside some other kind of block within the loop, e.g. a "try" clause::
   
     >>> c = Code()      >>> c = Code()
       >>> c.LOAD_CONST(57)
     >>> c.SETUP_LOOP()      >>> c.SETUP_LOOP()
     >>> loop = c.here()      >>> loop = c.here()
     >>> c.SETUP_FINALLY()      >>> c.SETUP_FINALLY()
       >>> fwd = c.JUMP_IF_TRUE()
     >>> c.CONTINUE_LOOP(loop)      >>> c.CONTINUE_LOOP(loop)
       >>> fwd()
     >>> c.POP_BLOCK()      >>> c.POP_BLOCK()
     >>> c.END_FINALLY()      >>> c.END_FINALLY()
     >>> c.POP_BLOCK()()      >>> c.POP_BLOCK()()
     >>> dis(c.code())      >>> dis(c.code())
       0           0 SETUP_LOOP              12 (to 15)        0           0 LOAD_CONST               1 (57)
             >>    3 SETUP_FINALLY            7 (to 13)                    3 SETUP_LOOP              15 (to 21)
                   6 CONTINUE_LOOP            3              >>    6 SETUP_FINALLY           10 (to 19)
                   9 POP_BLOCK                    9 JUMP_IF_TRUE             3 (to 15)
                  10 LOAD_CONST               0 (None)                   12 CONTINUE_LOOP            6
             >>   13 END_FINALLY              >>   15 POP_BLOCK
                  14 POP_BLOCK                   16 LOAD_CONST               0 (None)
               >>   19 END_FINALLY
                    20 POP_BLOCK
   
   ``for`` Loops
   -------------
   
   There is a ``For()`` node type available for generating simple loops (without
   break/continue support).  It takes an iterable expression, an assignment
   clause, and a loop body::
   
       >>> from peak.util.assembler import For
       >>> y = Call(Const(range), (3,))
       >>> x = LocalAssign('x')
       >>> body = Suite([Local('x'), Code.PRINT_EXPR])
   
       >>> c = Code()
       >>> c(For(y, x, body))  # for x in range(3): print x
       >>> c.return_()
       >>> dis(c.code())
         0           0 LOAD_CONST               1 ([0, 1, 2])
                     3 GET_ITER
               >>    4 FOR_ITER                10 (to 17)
                     7 STORE_FAST               0 (x)
                    10 LOAD_FAST                0 (x)
                    13 PRINT_EXPR
                    14 JUMP_ABSOLUTE            4
               >>   17 LOAD_CONST               0 (None)
                    20 RETURN_VALUE
   
   The arguments are given in execution order: first the "in" value of the loop,
   then the assignment to a loop variable, and finally the body of the loop.  The
   distinction between the assignment and body, however, is only for clarity and
   convenience (to avoid needing to glue the assignment to the body with a
   ``Suite``).  If you already have a suite or only need one node for the entire
   loop body, you can do the same thing with only two arguments::
   
       >>> c = Code()
       >>> c(For(y, Code.PRINT_EXPR))
       >>> c.return_()
       >>> dis(c.code())
         0           0 LOAD_CONST               1 ([0, 1, 2])
                     3 GET_ITER
               >>    4 FOR_ITER                 4 (to 11)
                     7 PRINT_EXPR
                     8 JUMP_ABSOLUTE            4
               >>   11 LOAD_CONST               0 (None)
                    14 RETURN_VALUE
   
   Notice, by the way, that ``For()`` does NOT set up a loop block for you, so if
   you want to be able to use break and continue, you'll need to wrap the loop in
   a labelled SETUP_LOOP/POP_BLOCK pair, as described in the preceding sections.
   
   
   List Comprehensions
   -------------------
   
   In order to generate correct list comprehension code for the target Python
   version, you must use the ``ListComp()`` and ``LCAppend()`` node types.  This
   is because Python versions 2.4 and up store the list being built in a temporary
   variable, and use a special ``LIST_APPEND`` opcode to append values, while 2.3
   stores the list's ``append()`` method in the temporary variable, and calls it
   to append values.
   
   The ``ListComp()`` node wraps a code body (usually a ``For()`` loop) and
   manages the creation and destruction of a temporary variable (e.g. ``_[1]``,
   ``_[2]``, etc.).  The ``LCAppend()`` node type wraps a value or expression to
   be appended to the innermost active ``ListComp()`` in progress::
   
       >>> from peak.util.assembler import ListComp, LCAppend
       >>> c = Code()
       >>> simple = ListComp(For(y, x, LCAppend(Local('x'))))
       >>> c.return_(simple)
       >>> eval(c.code())
       [0, 1, 2]
   
       >>> c = Code()
       >>> c.return_(ListComp(For(y, x, LCAppend(simple))))
       >>> eval(c.code())
       [[0, 1, 2], [0, 1, 2], [0, 1, 2]]
   
   
   Closures and Nested Functions
   =============================
   
   Free and Cell Variables
   -----------------------
   
   To implement closures and nested scopes, your code objects must use "free" or
   "cell" variables in place of regular "fast locals".  A "free" variable is one
   that is defined in an outer scope, and a "cell" variable is one that's defined
   in the current scope, but will also be used by nested functions.
   
   The simplest way to set up free or cell variables is to use a code object's
   ``makefree(names)`` and ``makecells(names)`` methods::
   
       >>> c = Code()
       >>> c.co_cellvars
       ()
       >>> c.co_freevars
       ()
   
       >>> c.makefree(['x', 'y'])
       >>> c.makecells(['z'])
   
       >>> c.co_cellvars
       ('z',)
       >>> c.co_freevars
       ('x', 'y')
   
   When a name has been defined as a free or cell variable, the ``_DEREF`` opcode
   variants are used to generate ``Local()`` and ``LocalAssign()`` nodes::
   
       >>> c((Local('x'), Local('y')), LocalAssign('z'))
       >>> dis(c.code())
         0           0 LOAD_DEREF               1 (x)
                     3 LOAD_DEREF               2 (y)
                     6 BUILD_TUPLE              2
                     9 STORE_DEREF              0 (z)
   
   If you have already written code in a code object that operates on the relevant
   locals, the code is retroactively patched to use the ``_DEREF`` opcodes::
   
       >>> c = Code()
       >>> c((Local('x'), Local('y')), LocalAssign('z'))
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (x)
                     3 LOAD_FAST                1 (y)
                     6 BUILD_TUPLE              2
                     9 STORE_FAST               2 (z)
   
       >>> c.makefree(['x', 'y'])
       >>> c.makecells(['z'])
   
       >>> dis(c.code())
         0           0 LOAD_DEREF               1 (x)
                     3 LOAD_DEREF               2 (y)
                     6 BUILD_TUPLE              2
                     9 STORE_DEREF              0 (z)
   
   This means that you can defer the decision of which locals are free/cell
   variables until the code is ready to be generated.  In fact, by passing in
   a "parent" code object to the ``.code()`` method, you can get BytecodeAssembler
   to automatically call ``makefree()`` and ``makecells()`` for the correct
   variable names in the child and parent code objects, as we'll see in the next
   section.
   
   
   Nested Code Objects
   -------------------
   
   To create a code object for use in a nested scope, you can use the parent code
   object's ``nested()`` method.  It works just like the ``from_spec()``
   classmethod, except that the ``co_filename`` of the parent is copied to the
   child::
   
       >>> p = Code()
       >>> p.co_filename = 'testname'
   
       >>> c = p.nested('sub', ['a','b'], 'c', 'd')
   
       >>> c.co_name
       'sub'
   
       >>> c.co_filename
       'testname'
   
       >>> inspect.getargs(c.code(p))
       (['a', 'b'], 'c', 'd')
   
   Notice that you must pass the parent code object to the child's ``.code()``
   method to ensure that free/cell variables are properly set up.  When the
   ``code()`` method is given another code object as a parameter, it automatically
   converts any locally-read (but not written) to "free" variables in the child
   code, and ensures that those same variables become "cell" variables in the
   supplied parent code object::
   
       >>> p.LOAD_CONST(42)
       >>> p(LocalAssign('a'))
       >>> dis(p.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_FAST               0 (a)
   
       >>> c = p.nested()
       >>> c(Local('a'))
   
       >>> dis(c.code(p))
         0           0 LOAD_DEREF               0 (a)
   
       >>> dis(p.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_DEREF              0 (a)
   
   Notice that the ``STORE_FAST`` in the parent code object was automatically
   patched to a ``STORE_DEREF``, with an updated offset if applicable.  Any
   future use of ``Local('a')`` or ``LocalAssign('a')`` in the parent or child
   code objects will now refer to the free/cell variable, rather than the "local"
   variable::
   
       >>> p(Local('a'))
       >>> dis(p.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_DEREF              0 (a)
                     6 LOAD_DEREF               0 (a)
   
       >>> c(LocalAssign('a'))
       >>> dis(c.code(p))
         0           0 LOAD_DEREF               0 (a)
                     3 STORE_DEREF              0 (a)
   
   
   ``Function()``
   --------------
   
   The ``Function(body, name='<lambda>', args=(), var=None, kw=None, defaults=())``
   node type creates a function object from the specified body and the optional
   name, argument specs, and defaults.  The ``Function()`` node generates code to
   create the function object with the appropriate defaults and closure (if
   applicable), and any needed free/cell variables are automatically set up in the
   parent and child code objects.  The newly generated function will be on top of
   the stack at the end of the generated code::
   
       >>> from peak.util.assembler import Function
       >>> c = Code()
       >>> c.co_filename = '<string>'
       >>> c.return_(Function(Return(Local('a')), 'f', ['a'], defaults=[42]))
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 LOAD_CONST               2 (<... f ..., file "<string>", line -1>)
                     6 MAKE_FUNCTION            1
                     9 RETURN_VALUE
   
   Now that we've generated the code for a function returning a function, let's
   run it, to get the function we defined::
   
       >>> f = eval(c.code())
       >>> f
       <function f at ...>
   
       >>> inspect.getargspec(f)
       (['a'], None, None, (42,))
   
       >>> f()
       42
   
       >>> f(99)
       99
   
   Now let's create a doubly nested function, with some extras::
   
       >>> c = Code()
       >>> c.co_filename = '<string>'
       >>> c.return_(
       ...     Function(Return(Function(Return(Local('a')))),
       ...     'f', ['a', 'b'], 'c', 'd', [99, 66])
       ... )
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (99)
                     3 LOAD_CONST               2 (66)
                     6 LOAD_CONST               3 (<... f ..., file "<string>", line -1>)
                     9 MAKE_FUNCTION            2
                    12 RETURN_VALUE
   
       >>> f = eval(c.code())
       >>> f
       <function f at ...>
   
       >>> inspect.getargspec(f)
       (['a', 'b'], 'c', 'd', (99, 66))
   
       >>> dis(f)
         0           0 LOAD_CLOSURE             0 (a)
                     3 BUILD_TUPLE              1
                     6 LOAD_CONST               1 (<... <lambda> ..., file "<string>", line -1>)
                     9 MAKE_CLOSURE             0
                    12 RETURN_VALUE
   
       >>> dis(f())
         0           0 LOAD_DEREF               0 (a)
                     3 RETURN_VALUE
   
       >>> f(42)()
       42
   
       >>> f()()
       99
   
   As you can see, ``Function()`` not only takes care of setting up free/cell
   variables in all the relevant scopes, it also chooses whether to use
   ``MAKE_FUNCTION`` or ``MAKE_CLOSURE``, and generates code for the defaults.
   
   (Note, by the way, that the `defaults` argument should be a sequence of
   generatable expressions; in the examples here, we used numbers, but they could
   have been arbitrary expression nodes.)
   
   
 ----------------------  ----------------------
Line 1264 
Line 2251 
     >>> simple_code(1,1).co_stacksize      >>> simple_code(1,1).co_stacksize
     1      1
   
     >>> dis(simple_code(13,414))    # FAILURE EXPECTED IN PYTHON 2.3      >>> dis(simple_code(13,414))
      13           0 LOAD_CONST               0 (None)       13           0 LOAD_CONST               0 (None)
     414           3 RETURN_VALUE      414           3 RETURN_VALUE
   
Line 1277 
Line 2264 
     >>> simple_code(13,14,100).co_stacksize      >>> simple_code(13,14,100).co_stacksize
     100      100
   
     >>> dis(simple_code(13,572,120))    # FAILURE EXPECTED IN Python 2.3      >>> dis(simple_code(13,572,120))
      13           0 LOAD_CONST               0 (None)       13           0 LOAD_CONST               0 (None)
                   3 LOAD_CONST               0 (None)                    3 LOAD_CONST               0 (None)
     ...      ...
Line 1286 
Line 2273 
   
 Stack size tracking::  Stack size tracking::
   
     >>> c = Code()      >>> c = Code()          # 0
     >>> c.LOAD_CONST(1)      >>> c.LOAD_CONST(1)     # 1
     >>> c.POP_TOP()      >>> c.POP_TOP()         # 0
     >>> c.LOAD_CONST(2)      >>> c.LOAD_CONST(2)     # 1
     >>> c.LOAD_CONST(3)      >>> c.LOAD_CONST(3)     # 2
     >>> c.co_stacksize      >>> c.co_stacksize
     2      2
     >>> c.BINARY_ADD()      >>> c.stack_history
     >>> c.LOAD_CONST(4)      [0, ..., 1, 0, ..., 1]
       >>> c.BINARY_ADD()      # 1
       >>> c.LOAD_CONST(4)     # 2
     >>> c.co_stacksize      >>> c.co_stacksize
     2      2
       >>> c.stack_history
       [0, ..., 1, 0, 1, ..., 2, ..., 1]
     >>> c.LOAD_CONST(5)      >>> c.LOAD_CONST(5)
     >>> c.LOAD_CONST(6)      >>> c.LOAD_CONST(6)
     >>> c.co_stacksize      >>> c.co_stacksize
Line 1332 
Line 2323 
                   3 LOAD_ATTR                1 (bar)                    3 LOAD_ATTR                1 (bar)
                   6 DELETE_FAST              0 (baz)                    6 DELETE_FAST              0 (baz)
   
   Code iteration::
   
       >>> c.DUP_TOP()
       >>> c.return_(Code.POP_TOP)
       >>> list(c) == [
       ...     (0, op.LOAD_GLOBAL, 0),
       ...     (3, op.LOAD_ATTR, 1),
       ...     (6, op.DELETE_FAST, 0),
       ...     (9, op.DUP_TOP, None),
       ...     (10, op.POP_TOP, None),
       ...     (11, op.RETURN_VALUE, None)
       ... ]
       True
   
   Code patching::
   
       >>> c = Code()
       >>> c.LOAD_CONST(42)
       >>> c.STORE_FAST('x')
       >>> c.LOAD_FAST('x')
       >>> c.DELETE_FAST('x')
       >>> c.RETURN_VALUE()
   
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_FAST               0 (x)
                     6 LOAD_FAST                0 (x)
                     9 DELETE_FAST              0 (x)
                    12 RETURN_VALUE
   
   
       >>> c.co_varnames
       ['x']
       >>> c.co_varnames.append('y')
   
       >>> c._patch(
       ...     {op.LOAD_FAST:  op.LOAD_FAST,
       ...      op.STORE_FAST: op.STORE_FAST,
       ...      op.DELETE_FAST: op.DELETE_FAST},
       ...     {0: 1}
       ... )
   
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_FAST               1 (y)
                     6 LOAD_FAST                1 (y)
                     9 DELETE_FAST              1 (y)
                    12 RETURN_VALUE
   
       >>> c._patch({op.RETURN_VALUE: op.POP_TOP})
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_FAST               1 (y)
                     6 LOAD_FAST                1 (y)
                     9 DELETE_FAST              1 (y)
                    12 POP_TOP
   
   Converting locals to free/cell vars::
   
       >>> c = Code()
       >>> c.LOAD_CONST(42)
       >>> c.STORE_FAST('x')
       >>> c.LOAD_FAST('x')
   
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_FAST               0 (x)
                     6 LOAD_FAST                0 (x)
   
       >>> c.co_freevars = 'y', 'x'
       >>> c.co_cellvars = 'z',
   
       >>> c._locals_to_cells()
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_DEREF              2 (x)
                     6 LOAD_DEREF               2 (x)
   
       >>> c.DELETE_FAST('x')
       >>> c._locals_to_cells()
       Traceback (most recent call last):
         ...
       AssertionError: Can't delete local 'x' used in nested scope
   
       >>> c = Code()
       >>> c.LOAD_CONST(42)
       >>> c.STORE_FAST('x')
       >>> c.LOAD_FAST('x')
   
       >>> c.co_freevars
       ()
       >>> c.makefree(['x'])
       >>> c.co_freevars
       ('x',)
   
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_DEREF              0 (x)
                     6 LOAD_DEREF               0 (x)
   
       >>> c = Code()
       >>> c.LOAD_CONST(42)
       >>> c.STORE_FAST('x')
       >>> c.LOAD_FAST('x')
       >>> c.makecells(['x'])
       >>> c.co_freevars
       ()
       >>> c.co_cellvars
       ('x',)
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_DEREF              0 (x)
                     6 LOAD_DEREF               0 (x)
   
       >>> c = Code()
       >>> c.LOAD_CONST(42)
       >>> c.STORE_FAST('x')
       >>> c.LOAD_FAST('x')
       >>> c.makefree('x')
       >>> c.makecells(['y'])
       >>> c.co_freevars
       ('x',)
       >>> c.co_cellvars
       ('y',)
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (42)
                     3 STORE_DEREF              1 (x)
                     6 LOAD_DEREF               1 (x)
   
       >>> c = Code()
       >>> c.co_flags &= ~op.CO_OPTIMIZED
       >>> c.makecells(['q'])
       Traceback (most recent call last):
         ...
       AssertionError: Can't use cellvars in unoptimized scope
   
   
   
   Auto-free promotion with code parent:
   
       >>> p = Code()
       >>> c = Code()
       >>> c.LOAD_FAST('x')
       >>> dis(c.code(p))
         0           0 LOAD_DEREF               0 (x)
       >>> p.co_cellvars
       ('x',)
   
       >>> p = Code()
       >>> c = Code.from_function(lambda x,y,z=2: None)
       >>> c.LOAD_FAST('x')
       >>> c.LOAD_FAST('y')
       >>> c.LOAD_FAST('z')
   
       >>> dis(c.code(p))
         0           0 LOAD_FAST                0 (x)
                     3 LOAD_FAST                1 (y)
                     6 LOAD_FAST                2 (z)
       >>> p.co_cellvars
       ()
   
       >>> c.LOAD_FAST('q')
       >>> dis(c.code(p))
         0           0 LOAD_FAST                0 (x)
                     3 LOAD_FAST                1 (y)
                     6 LOAD_FAST                2 (z)
                     9 LOAD_DEREF               0 (q)
       >>> p.co_cellvars
       ('q',)
   
       >>> p = Code()
       >>> c = Code.from_function(lambda x,*y,**z: None)
       >>> c.LOAD_FAST('q')
       >>> c.LOAD_FAST('x')
       >>> c.LOAD_FAST('y')
       >>> c.LOAD_FAST('z')
       >>> dis(c.code(p))
         0           0 LOAD_DEREF               0 (q)
                     3 LOAD_FAST                0 (x)
                     6 LOAD_FAST                1 (y)
                     9 LOAD_FAST                2 (z)
       >>> p.co_cellvars
       ('q',)
   
       >>> p = Code()
       >>> c = Code.from_function(lambda x,*y: None)
       >>> c.LOAD_FAST('x')
       >>> c.LOAD_FAST('y')
       >>> c.LOAD_FAST('z')
       >>> dis(c.code(p))
         0           0 LOAD_FAST                0 (x)
                     3 LOAD_FAST                1 (y)
                     6 LOAD_DEREF               0 (z)
       >>> p.co_cellvars
       ('z',)
   
       >>> p = Code()
       >>> c = Code.from_function(lambda x,**y: None)
       >>> c.LOAD_FAST('x')
       >>> c.LOAD_FAST('y')
       >>> c.LOAD_FAST('z')
       >>> dis(c.code(p))
         0           0 LOAD_FAST                0 (x)
                     3 LOAD_FAST                1 (y)
                     6 LOAD_DEREF               0 (z)
       >>> p.co_cellvars
       ('z',)
   
   
   Stack tracking on jumps::
   
       >>> c = Code()
       >>> else_ = Label()
       >>> end = Label()
       >>> c(99, else_.JUMP_IF_TRUE, Code.POP_TOP, end.JUMP_FORWARD)
       >>> c(else_, Code.POP_TOP, end)
       >>> dis(c.code())
         0           0 LOAD_CONST               1 (99)
                     3 JUMP_IF_TRUE             4 (to 10)
                     6 POP_TOP
                     7 JUMP_FORWARD             1 (to 11)
               >>   10 POP_TOP
   
       >>> c.stack_size
       0
       >>> c.stack_history
       [0, 1, 1, 1, 1, 1, 1, 0, None, None, 1]
   
       >>> c = Code()
       >>> fwd = c.JUMP_FORWARD()
       >>> c.LOAD_CONST(42)    # forward jump marks stack size unknown
       Traceback (most recent call last):
         ...
       AssertionError: Unknown stack size at this location
   
       >>> c.stack_size = 0
       >>> c.LOAD_CONST(42)
       >>> fwd()
       Traceback (most recent call last):
         ...
       AssertionError: Stack level mismatch: actual=1 expected=0
   
       >>> from peak.util.assembler import For
       >>> c = Code()
       >>> c(For((), Code.POP_TOP, Pass))
       >>> c.return_()
       >>> dis(c.code())
         0           0 BUILD_TUPLE              0
                     3 GET_ITER
               >>    4 FOR_ITER                 4 (to 11)
                     7 POP_TOP
                     8 JUMP_ABSOLUTE            4
               >>   11 LOAD_CONST               0 (None)
                    14 RETURN_VALUE
   
       >>> c.stack_history
       [0, 1, 1, 1, 1, 2, 2, 2, 1, None, None, 0, 1, 1, 1]
   
   
   Yield value::
   
       >>> import sys
       >>> from peak.util.assembler import CO_GENERATOR
       >>> c = Code()
       >>> c.co_flags & CO_GENERATOR
       0
       >>> c(42, Code.YIELD_VALUE)
       >>> c.stack_size == int(sys.version>='2.5')
       True
       >>> (c.co_flags & CO_GENERATOR) == CO_GENERATOR
       True
   
   
   
 Sequence operators and stack tracking:  Sequence operators and stack tracking:
   
   
Line 1583 
Line 2848 
       0           0 LOAD_CONST               1 ({'x': 1})        0           0 LOAD_CONST               1 ({'x': 1})
                   3 RETURN_VALUE                    3 RETURN_VALUE
   
   Try/Except stack level tracking::
   
       >>> def class_or_type_of(expr):
       ...     return Suite([expr, TryExcept(
       ...         Suite([Getattr(Code.DUP_TOP, '__class__'), Code.ROT_TWO]),
       ...         [(Const(AttributeError), Call(Const(type), (Code.ROT_TWO,)))]
       ...     )])
   
       >>> def type_or_class(x): pass
       >>> c = Code.from_function(type_or_class)
       >>> c.return_(class_or_type_of(Local('x')))
       >>> dis(c.code())
         0           0 LOAD_FAST                0 (x)
                     3 SETUP_EXCEPT             9 (to 15)
                     6 DUP_TOP
                     7 LOAD_ATTR                0 (__class__)
                    10 ROT_TWO
                    11 POP_BLOCK
                    12 JUMP_FORWARD            26 (to 41)
               >>   15 DUP_TOP
                    16 LOAD_CONST               1 (<...exceptions.AttributeError...>)
                    19 COMPARE_OP              10 (exception match)
                    22 JUMP_IF_FALSE           14 (to 39)
                    25 POP_TOP
                    26 POP_TOP
                    27 POP_TOP
                    28 POP_TOP
                    29 LOAD_CONST               2 (<type 'type'>)
                    32 ROT_TWO
                    33 CALL_FUNCTION            1
                    36 JUMP_FORWARD             2 (to 41)
               >>   39 POP_TOP
                    40 END_FINALLY
               >>   41 RETURN_VALUE
   
       >>> type_or_class.func_code = c.code()
       >>> type_or_class(23)
       <type 'int'>
   
   
   
   
   
 Demo: "Computed Goto"/"Switch Statement"  Demo: "Computed Goto"/"Switch Statement"
Line 1593 
Line 2899 
   
     >>> from peak.util.assembler import LOAD_CONST, POP_BLOCK      >>> from peak.util.assembler import LOAD_CONST, POP_BLOCK
   
     >>> def Pass(code=None):  
     ...     if code is None:  
     ...         return Pass  
   
     >>> def NewConst(value, code=None):  
     ...     if code is None:  
     ...         return ast_curry(NewConst, value)  
     ...     code.emit_arg(LOAD_CONST, len(code.co_consts))  
     ...     code.co_consts.append(value)  
     ...     code.stackchange((0,1))  
   
     >>> import sys      >>> import sys
     >>> WHY_CONTINUE = {'2.3':5, '2.4':32, '2.5':32}[sys.version[:3]]      >>> WHY_CONTINUE = {'2.3':5, '2.4':32, '2.5':32}[sys.version[:3]]
   
     >>> def Switch(expr, cases, default=Pass, code=None):      >>> def Switch(expr, cases, default=Pass, code=None):
     ...     if code is None:      ...     if code is None:
     ...         return ast_curry(Switch, expr, tuple(cases), default)      ...         return expr, tuple(cases), default
     ...      ...
     ...     d = {}      ...     d = {}
     ...     else_block  = Label()      ...     else_block  = Label()
Line 1618 
Line 2913 
     ...      ...
     ...     code(      ...     code(
     ...         end_switch.SETUP_LOOP,      ...         end_switch.SETUP_LOOP,
     ...             Call(NewConst(d.get), [expr]),      ...             Call(Const(d.get), [expr]),
     ...         else_block.JUMP_IF_FALSE,      ...         else_block.JUMP_IF_FALSE,
     ...             WHY_CONTINUE, Code.END_FINALLY      ...             WHY_CONTINUE, Code.END_FINALLY
     ...     )      ...     )
     ...      ...
     ...     cursize = code.stack_size      ...     cursize = code.stack_size - 1   # adjust for removed WHY_CONTINUE
     ...     for key, value in cases:      ...     for key, value in cases:
     ...         d[const_value(key)] = code.here()      ...         d[const_value(key)] = code.here()
     ...         code(value, cleanup.JUMP_FORWARD)      ...         code.stack_size = cursize
       ...         code(value)
       ...         if code.stack_size is not None: # if the code can fall through,
       ...             code(cleanup.JUMP_FORWARD)  # jump forward to the cleanup
     ...      ...
     ...     code(      ...     code(
     ...         else_block,      ...         else_block,
Line 1635 
Line 2933 
     ...             Code.POP_BLOCK,      ...             Code.POP_BLOCK,
     ...         end_switch      ...         end_switch
     ...     )      ...     )
       >>> Switch = nodetype()(Switch)
   
     >>> c = Code()      >>> c = Code()
     >>> c.co_argcount=1      >>> c.co_argcount=1
Line 1650 
Line 2949 
     27      27
   
     >>> dis(c.code())      >>> dis(c.code())
       0           0 SETUP_LOOP              36 (to 39)        0           0 SETUP_LOOP              30 (to 33)
                   3 LOAD_CONST               1 (<...method get of dict...>)                    3 LOAD_CONST               1 (<...method get of dict...>)
                   6 LOAD_FAST                0 (x)                    6 LOAD_FAST                0 (x)
                   9 CALL_FUNCTION            1                    9 CALL_FUNCTION            1
                  12 JUMP_IF_FALSE           18 (to 33)                   12 JUMP_IF_FALSE           12 (to 27)
                  15 LOAD_CONST               2 (...)                   15 LOAD_CONST               2 (...)
                  18 END_FINALLY                   18 END_FINALLY
                  19 LOAD_CONST               3 (42)                   19 LOAD_CONST               3 (42)
                  22 RETURN_VALUE                   22 RETURN_VALUE
                  23 JUMP_FORWARD            12 (to 38)                   23 LOAD_CONST               4 ('foo')
                  26 LOAD_CONST               4 ('foo')                   26 RETURN_VALUE
                  29 RETURN_VALUE              >>   27 POP_TOP
                  30 JUMP_FORWARD             5 (to 38)                   28 LOAD_CONST               5 (27)
             >>   33 POP_TOP                   31 RETURN_VALUE
                  34 LOAD_CONST               5 (27)                   32 POP_BLOCK
                  37 RETURN_VALUE              >>   33 LOAD_CONST               0 (None)
             >>   38 POP_BLOCK                   36 RETURN_VALUE
             >>   39 LOAD_CONST               0 (None)  
                  42 RETURN_VALUE  
   
   
 TODO  TODO
 ====  ====
   
 * AST introspection  
     * ast_type(node): called function, Const, or node.__class__  
       * tuples are Const if their contents are; no other types are Const  
     * ast_children(node): tuple of argument values for curried types, const value,  
       or empty tuple.  If node is a tuple, the value must be flattened.  
     * is_const(node): ast_type(node) is Const  
   
 * Inline builtins (getattr, operator.getitem, etc.) to opcodes  
     * Getattr/Op/Unary("symbol", arg1 [, arg2]) node types -> Call() if folding  
     * Call() translates functions back to Ops if inlining  
   
 * Pretty printing and short-naming of ASTs  
   
 * Test NAME vs. FAST operators flag checks/sets  * Test NAME vs. FAST operators flag checks/sets
   
 * Test code flags generation/cloning  * Test code flags generation/cloning
   
   * Exhaustive tests of all opcodes' stack history effects
   
   * Test wide jumps and wide argument generation in general


Generate output suitable for use with a patch program
Legend:
Removed from v.2198  
changed lines
  Added in v.2571

cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help