[Subversion] / PEAK / src / peak / config / modules.py  

View of /PEAK/src/peak/config/modules.py

Parent Directory | Revision Log
Revision: 1633 - (download) (as text)
Sun Jan 25 02:32:15 2004 UTC (20 years, 3 months ago) by pje
File size: 33608 byte(s)
Added "state of the PEAK union" doc (STATUS.txt).  Moved old changes to
HISTORY.txt.  Updated copyright and package info.  Moved ancient and
improbable TODO's out to individual packages.  Readjust a3 TODO targets so
we can release it a lot sooner, preferably within 1-3 weeks.
"""Module Inheritance and Patching

    The APIs defined here let you create modules which derive from other
    modules, by defining a module-level '__bases__' attribute which lists
    the modules you wish to inherit from.  For example::

        from peak.api import *

        import BaseModule1, BaseModule2

        __bases__ = BaseModule1, BaseModule2


        class MyClass:
            ...

        binding.setupModule()

    The 'setupModule()' call will convert the calling module, 'BaseModule1',
    and 'BaseModule2' into specially altered bytecode objects and execute
    them (in "method-resolution order") rewriting the calling module's
    dictionary in the process.  The result is rather like normal class
    inheritance, except that classes (even nested classes) are merged by name,
    and metaclass constraints are inherited.  So an inheriting module need
    not list all the classes from its base module in order to change them
    by altering a base class in that module.

    Note: All the modules listed in '__bases__' must either call
    'setupModule()' themselves, or be located in an on-disk '.py', '.pyc', or
    '.pyo' file.  This is because PEAK cannot otherwise get access to
    their bytecode in a way that is compatible with the many "import hook"
    systems that exist for Python.  (E.g. running bytecode from zip files or
    frozen into an executable, etc.)  So if you are using such a code
    distribution technique, you must ensure that the base modules call
    'setupModule()', even if they do not have a '__bases__' setting or use
    any other PEAK code.

    Function Rebinding

        All functions inherited via "module inheritance" using 'setupModule()'
        (including those which are instance or class methods) have their
        globals rebound to point to the inheriting module.  This means that if
        a function or method references a global in the base module you're
        inheriting from, you can override that global in the inheriting module,
        without having to recode the function that referenced it.  (This is
        especially useful for 'super()' calls, which usually use global
        references to class names!)

        In addition to rebinding general globals, functions which reference
        the global name '__proceed__' are also specially rebound so that
        '__proceed__' is the previous definition of that function, if any, in
        the inheritance list.  (It is 'None' if there is no previous
        definition.)  This allows you to do the rough equivalent of a 'super()'
        call (or AspectJ "around advice") without having to explicitly import
        the old version of a function.  Note that '__proceed__' is always
        either a function or 'None', so you must include 'self' as a parameter
        when calling it from a method definition.


    Pickling Instances of Nested Classes and the '__name__' Attribute

        One more bonus of using 'setupModule()' is that instances of nested
        classes defined in modules using 'setupModule()' will be pickleable.
        Ordinarily, nested class instances aren't pickleable because Python
        doesn't know how to find them, using only 'someClass.__name__' and
        'someClass.__module__'.

        PEAK overcomes this problem by renaming nested classes so that
        they are defined with their full dotted name (e.g. 'Foo.Bar' for
        class 'Bar' nested in class 'Foo'), and saving a reference to the class
        under its dotted name in the module dictionary.  This means that
        'someClass.__name__' may not be what you'd normally expect, and that
        doing 'del someClass' may not delete all references to a class.  But
        pickling and unpickling should work normally.

        Note that some PEAK classes and metaclasses provide a "short
        form" of the class name for use when appropriate.  For example,
        Feature classes have an 'attrName' class attribute.  In a pinch, you
        can also use '__name__.split(".")[-1]' to get the undotted form of
        a class' name.


    Special Considerations for Mutables and Dynamic Initialization

        Both inheritance and patching are implemented by running hacked,
        module-level code under a "simulator" that intercepts the setting of
        variables.  This works great for static definitions like 'class'
        and 'def' statements, constant assignments, 'import', etc.  It also
        works reasonably well for many other kinds of static initialization
        of immutable objects

        Mutable values, however, may require special considerations.  For
        example, if a module sets up some kind of registry as a module-level
        variable, and an inheriting module overrides the definition, things
        can get tricky.  If the base module writes values into that registry as
        part of module initialization, those values will also be written into
        the registry defined by the derived module.

        Another possible issue is if the base module performs other externally
        visible, non-idempotent operations, such as registering classes or
        functions in another module's registry, printing things to the console,
        etc.  The simple workaround for all these considerations, however, is
        to move your dynamic initialization code to a module-level '__init__'
        function.

    Module-level '__init__()' Functions

        The last thing 'setupModule()' does before returning, is to check for a
        module-level '__init__()' function, and call it with no arguments, if
        it exists.  This allows you to do any dynamic initialization operations
        (such as modifying or resetting global mutables) *after* inheritance
        has taken effect.  As with any other function defined in the module,
        '__proceed__' refers to the previous (i.e. preceding base module)
        definition of the function or 'None'.  This lets you can chain to your
        predecessors' initialization code, if needed/desired.

        Note, by the way, that if you have an 'if __name__=="__main__"' block
        in your module, it would probably be best if you move it inside the
        '__init__()' function, as this ensures that it will not be run
        repeatedly if you do not wish it to be.  It will also allow other
        modules to inherit that code and wrap around it, if they so desire.


    Package Inheritance

        Packages (i.e. '__init__' modules) can also set '__bases__' and
        call 'setupModule()'.  Their package '__path__' will be extended
        to include the '__path__' contents of their '__bases__', in an
        MRO-like order.  This means that to derive a package from another,
        you do not need to create a separate inheriting module for every
        individual module or subpackage, only those that you wish to make
        modifications to.  If package 'foo' contains modules 'foo.bar'
        and 'foo.baz', and you want your 'spam' package to derive from
        'foo', you need only create a 'spam/__init__.py' that contains::

            import foo
            __bases__ = foo,

            # ...

            from peak.api import config
            config.setupModule()

        At this point, 'spam.baz' or 'spam.bar' will automatically be
        importable, based on the 'foo' versions of their code.  This
        will work even if the 'foo' versions don't call 'setupModule()',
        although in that case you won't be able to override their contents.

        To override a module within the 'spam' package, just create it,
        and use module inheritance to specify the base in the original
        package.  For example, you can extend 'foo.bar' by creating
        'spam.bar' as follows::

            import foo.bar
            __bases__ = foo.bar,

            # ...

            from peak.api import config
            config.setupModule()




    Limitations of Package Inheritance

        Because "package inheritance" is effectively just a '__path__' hack,
        it is really only good for "single inheritance".  Python will not
        automatically merge the modules or packages found on a package's
        '__path__'.  So if you need multiple inheritance, you will need
        to create a module or subpackage for each module or subpackage
        that exists in more than one base package, and explicitly specify
        the right '__bases__' for it.  If a module or subpackage only
        appears in one base, however, and you have nothing to add to it,
        you can omit it from the inheriting package.


    Using Relative Imports in Packages

        If you want to extend a package, it's important to use only
        relative imports.  This is because the code of a module
        is executed as-is in the derived module.  If you do an
        absolute import, e.g. 'import foo.baz' in package 'foo.bar',
        then 'spam.bar' will still import 'foo.baz', not 'spam.baz'.
        It's better to 'import baz' for an item in the same package,
        or use 'peak.utils.imports.lazyModule' like this::

            from peak.utils.imports import lazyModule
            baz = lazyModule(__name__, 'baz')

        While this is more verbose in the simple case, it works for
        more complex relative paths than Python allows; for example
        you can do this::

            eggs = lazyModule(__name__, '../ni/eggs')

        which isn't possible with a regular 'import' statement.








    Import Paths

        To make using relative imports easier, '__bases__' can include
        "/"-separated relative path strings instead of modules, e.g.::

            __bases__ = '../../foo/bar',

        A / at the beginning of the path makes it absolute, so '/foo/bar'
        would also work.


    To-do Items

        * The simulator should issue warnings for a variety of questionable
          situations, such as...

          - Code matching the following pattern, which doesn't do what it looks
            like it does, and should probably be considered a "serious order
            disagreement"::

            BaseModule:

                class Foo: ...

                class Bar: ...

            DerivedModule:

                class Bar: ...

                class Foo(Bar): ...

        * This docstring is woefully inadequate to describe all the interesting
          subtleties of module inheritance; a tutorial is really needed.  But
          there *does* need to be a reference-style explanation as well, that
          describes the precise semantics of interpretation for assignments,
          'def', and 'class', in modules running under simulator control.




        * Allow 'declareModule()' to bootstrap non-existent modules; this might
          let us create "virtual packages" made by assembling other packages
          and modules.

        * Need a strategy for handling "del" operations; they are currently
          untrapped.  This might be okay under most circumstances, but need to
          consider edge cases.

        * 'makeClass()' should probably become part of the core API, where
          it can be used to resolve __metaclass__ conflicts during the first
          pass of importing a module (prior to running 'setupModule()')
"""





























from __future__ import generators
import sys
from types import ModuleType
from peak.util.EigenData import AlreadyRead
from peak.util._Code import codeIndex
from peak.util.imports import lazyModule, joinPath, getModuleHooks
from protocols.advice import isClassAdvisor

# Make the default value of '__proceed__' a built-in, so that code written for
# an inheriting module won't fail with a NameError if there's no base module
# definition of a function

import __builtin__; __builtin__.__proceed__ = None


__all__ = [
    'patchModule', 'setupModule', 'setupObject', 'ModuleInheritanceWarning',
    'declareModule', 'SpecificationError',
]

patchMap = {}


def setupObject(obj, **attrs):

    """Set attributes without overwriting values defined in a derived module"""

    for k,v in attrs.items():
        if not hasattr(obj,k):
            setattr(obj,k,v)











def toBases(bases,name=''):

    if isinstance(bases,(str,ModuleType)):
        bases = bases,

    for b in bases:

        if isinstance(b,str):
            b = lazyModule(name,b)

        yield b


def moduleBases(module, name=''):
    return tuple(toBases(getattr(module,'__bases__',()), name))


class SpecificationError(Exception):
    pass






















def getLegacyCode(module):

    # XXX this won't work with zipfiles yet

    from imp import PY_COMPILED, PY_SOURCE, get_magic, get_suffixes

    file = module.__file__

    for (ext,mode,typ) in get_suffixes():

        if file.endswith(ext):

            if typ==PY_COMPILED:

                f = open(file, mode)

                if f.read(4) == get_magic():
                    f.read(4)   # skip timestamp
                    import marshal
                    code = marshal.load(f)
                    f.close()
                    return code

                # Not magic!
                f.close()
                raise AssertionError("Bad magic for %s" % file)


            elif typ==PY_SOURCE:

                f = open(file, mode)
                code = f.read()
                f.close()

                if code and not code.endswith('\n'):
                    code += '\n'

                return compile(code, file, 'exec')

    raise AssertionError("Can't retrieve code for %s" % module)

def getCodeListForModule(module, code=None):

    if hasattr(module,'__codeList__'):
        return module.__codeList__

    if code is None:
        code = getLegacyCode(module)

    name = module.__name__
    code = prepForSimulation(code)
    codeList = module.__codeList__ = patchMap.get(name,[])+[code]

    bases = moduleBases(module, name)

    path = getattr(module,'__path__',[])

    for baseModule in bases:

        if not isinstance(baseModule,ModuleType):
            raise TypeError (
                "%s is not a module in %s __bases__" % (baseModule,name)
            )

        for p in getattr(baseModule,'__path__',()):
            if p in path: path.remove(p)
            path.append(p)

        for c in getCodeListForModule(baseModule):
            if c in codeList: codeList.remove(c)
            codeList.append(c)

    if path:
        module.__path__ = path

    return codeList






declarations = {}   # just keeps track

def declareModule(name, relativePath=None, bases=(), patches=()):

    """Package Inheritance Shortcut and Third-party Patches

    This function lets you "pre-declare" that a module should have
    some set of bases or "patch modules" applied to it.  This lets
    you work around the single-inheritance limitation of package
    inheritance, and it also lets you apply "patch modules" to
    third-party code that doesn't call 'setupModule()'.

    To use this, you must call it *before* the module has been
    imported, even lazily.  You can call it again as many times
    as you like, as long as the base and patch lists remain the
    same for a given module.  Also note that the module must *exist*;
    that is, there must be some Python-findable source or bytecode
    for the specified module.  It can be "inherited" via package
    inheritance from its containing package's base package; you just
    can't make up a phony module name and have it work.  (This limitation
    might get lifted later, if it turns out to be useful.)

    'bases' are placed in the target module's '__bases__', *after*
    any bases declared in the package.  'patches' are applied as
    though they were the first modules to call 'patchModule()'.
    So the overall "MRO" for the resulting module looks like this:

        1. patches by modules calling 'patchModule()'

        2. modules specified by 'declareModule(patches=)'

        3. the module itself

        4. any '__bases__' declared by the module

        5. base modules supplied by 'declareModule(bases=)'

    Note that both 'bases' and 'patches' may be modules,
    relative paths to modules (relative to the declared module,
    *not* the 'name' parameter), or tuples of modules or paths.

    Using 'declareModule()' makes it easier to do multiple inheritance
    with packages.  For example, suppose you have packages 'square'
    and 'circle', and want to make a package 'curvyBox' that inherits
    from both.

    Further suppose that the 'square' package contains a 'square.rect'
    module, and a 'square.fill' module, and 'circle' contains a
    'circle.curve' module, and a 'circle.fill' module.  Because of
    the way the Python package '__path__' attribute works, package
    inheritance won't combine the '.fill' modules; instead, it will
    pick the first '.fill' module found.

    To solve this, we could create a 'curvyBox.fill' module that inherited
    from both 'square.fill' and 'circle.fill'.  But if there are many such
    modules or subpackages, and they will be empty but for '__bases__', we
    can use 'declareModule()' to avoid having to create individual
    subdirectories and '.py' files.

    This can be as simple as creating 'curvyBox.py' (or
    'curvybox/__init__.py'), and writing this code::

        from peak.api import *

        __bases__ = '../square', '../circle'

        config.declareModule(__name__, 'fill',
            bases = ('../../circle/fill',)  # relative to 'curvyBox.fill'
        )

        config.setupModule()

    This will add 'circle.fill' to the '__bases__' of 'curvyBox.fill' (which
    will be the inherited 'square.fill' module.

    Another usage of 'declareModule()' is to patch a third-party module::

        import my_additions
        config.declareModule('third.party.module', patches=(my_additions,))
    """


    if relativePath:
        name = joinPath(name, relativePath)

    if name in declarations:

        if declarations[name]==(bases,patches):
            return lazyModule(name)  # already declared it this way

        raise SpecificationError(
            ("%s has already been declared differently" % name),
            patches, declarations[name]
        )

    declarations[name] = bases, patches


    def load(module):

        if hasattr(module,'__codeList__'):  # first load might leave code
            del module.__codeList__

        plist = patchMap.setdefault(name, [])
        for patch in toBases(patches, name):
            plist.extend(getCodeListForModule(patch))

        module.__bases__ = moduleBases(module,name) + tuple(toBases(bases,name))
        buildModule(module)


    # ensure that we are run before any other 'whenImported' hooks
    getModuleHooks(name).insert(0, load)

    return lazyModule(name)








def setupModule():

    """setupModule() - Build module, w/patches and inheritance

    'setupModule()' should be called only at the very end of a module's
    code.  This is because any code which follows 'setupModule()' will be
    executed twice.  (Actually, the code before 'setupModule()' gets
    executed twice, also, but the module dictionary is reset in between,
    so its execution is cleaner.)
    """

    frame = sys._getframe(1)
    dict = frame.f_globals

    if dict.has_key('__PEAK_Simulator__'):
        return

    code = frame.f_code
    name = dict['__name__']
    module = sys.modules[name]

    buildModule(module, code)



















def buildModule(module, code=None):

    codelist = getCodeListForModule(module, code)
    d = module.__dict__

    if len(codelist)>1:
        saved = {}
        for name in '__file__', '__path__', '__name__', '__codeList__':
            try:
                saved[name] = d[name]
            except KeyError:
                pass

        d.clear()
        d.update(saved)

        sim = Simulator(d)   # Must happen after!

        map(sim.execute, codelist)
        sim.finish()

    if '__init__' in d:
        d['__init__']()


















def patchModule(moduleName):

    """"Patch" a module - like a runtime (aka "monkey") patch, only better

        Usage::

            from peak.api import config

            # ... body of module

            config.patchModule('moduleToPatch')

    'patchModule()' works much like 'setupModule()'.  The main difference
    is that it applies the current module as a patch to the supplied module
    name.  The module to be patched must not have been imported yet, and it
    must call 'setupModule()'.  The result will be as though the patched
    module had been replaced with a derived module, using the standard module
    inheritance rules to derive the new module.

    Note that more than one patching module may patch a single target module,
    in which case the order of importing is significant.  Patch modules
    imported later take precedence over those imported earlier.  (The target
    module must always be imported last.)

    Patch modules may patch other patch modules, but there is little point
    to doing this, since both patch modules will still have to be explicitly
    imported before their mutual target for the patches to take effect.
    """

    frame = sys._getframe(1)
    dict = frame.f_globals

    if dict.has_key('__PEAK_Simulator__'):
        return

    if dict.has_key('__bases__'):
        raise SpecificationError(
            "Patch modules cannot use '__bases__'"
        )


    if sys.modules.has_key(moduleName):
        raise AlreadyRead(
            "%s is already imported and cannot be patched" % moduleName
        )

    code = frame.f_code
    name = dict['__name__']
    module = sys.modules[name]

    codelist = getCodeListForModule(module, code)
    patchMap.setdefault(moduleName, [])[0:0] = codelist



from peak.util.Code import *

from peak.util.Code import BUILD_CLASS, STORE_NAME, MAKE_CLOSURE, \
    MAKE_FUNCTION, LOAD_CONST, STORE_GLOBAL, CALL_FUNCTION, IMPORT_STAR, \
    IMPORT_NAME, JUMP_ABSOLUTE, POP_TOP, ROT_FOUR, LOAD_ATTR, LOAD_GLOBAL, \
    LOAD_CONST, ROT_TWO, LOAD_LOCALS, STORE_SLICE, DELETE_SLICE, STORE_ATTR, \
    STORE_SUBSCR, DELETE_SUBSCR, DELETE_ATTR, DELETE_NAME, DELETE_GLOBAL

from peak.util.Meta import makeClass
from warnings import warn, warn_explicit

class ModuleInheritanceWarning(UserWarning):
    pass

mutableOps = (
    STORE_SLICE,  STORE_SLICE+1,  STORE_SLICE+2,  STORE_SLICE+3,
    DELETE_SLICE, DELETE_SLICE+1, DELETE_SLICE+2, DELETE_SLICE+3,
    STORE_ATTR,   DELETE_ATTR,    STORE_SUBSCR,   DELETE_SUBSCR,
)








class Simulator:

    def __init__(self, dict):
        self.advisors  = {}
        self.defined   = {}
        self.locked    = {}
        self.funcs     = {}
        self.lastFunc  = {}
        self.classes   = {}
        self.classPath = {}
        self.setKind   = {}
        self.dict      = dict

    def execute(self, code):

        d = self.dict

        try:
            d['__PEAK_Simulator__'] = self
            exec code in d

        finally:
            del d['__PEAK_Simulator__']
            self.locked.update(self.defined)
            self.defined.clear()
            self.setKind.clear()
            self.classPath.clear()

    def finish(self):
        for k,v in self.lastFunc.items():
            bind_func(v,__proceed__=None)










    def ASSIGN_VAR(self, value, qname):

        locked = self.locked

        if locked.has_key(qname):

            if self.setKind.get(qname)==STORE_NAME:
                warn(
                    "Redefinition of variable locked by derived module",
                    ModuleInheritanceWarning, 2
                )

            return locked[qname]

        self.defined[qname] = value
        self.setKind[qname] = STORE_NAME
        return value
























    def DEFINE_FUNCTION(self, value, qname):

        if self.setKind.get(qname,IMPORT_STAR) != IMPORT_STAR:
            warn(
                ("Redefinition of %s" % qname),
                ModuleInheritanceWarning, 2
            )

        self.setKind[qname] = MAKE_FUNCTION

        lastFunc, locked, funcs = self.lastFunc, self.locked, self.funcs

        if lastFunc.has_key(qname):
            bind_func(lastFunc[qname],__proceed__=value); del lastFunc[qname]

        if '__proceed__' in value.func_code.co_names:
            lastFunc[qname] = value

        if locked.has_key(qname):
            return locked[qname]

        if funcs.has_key(qname):
            return funcs[qname]

        funcs[qname] = value
        return value















    def IMPORT_STAR(self, module, locals, prefix):

        locked = self.locked
        have = locked.has_key
        defined = self.defined
        setKind = self.setKind
        checkKind = setKind.get

        def warnIfOverwrite(qname):
            if checkKind(qname,IMPORT_STAR) != IMPORT_STAR:
                warn(
                    ("%s may be overwritten by 'import *'" % qname),
                    ModuleInheritanceWarning, 3
                )
            setKind[qname]=IMPORT_STAR

        all = getattr(module,'__all__',None)

        if all is None:

            for k,v in module.__dict__.items():
                if not k.startswith('_'):
                    qname = prefix+k
                    if not have(qname):
                        warnIfOverwrite(qname)
                        locals[k] = defined[qname] = v

        else:
            for k in all:
                qname = prefix+k
                warnIfOverwrite(qname)
                if not have(qname):
                    warnIfOverwrite(qname)
                    locals[k] = defined[qname] = getattr(module,k)







    def DEFINE_CLASS(self, name, bases, cdict, qname):

        if self.setKind.get(qname,IMPORT_STAR) != IMPORT_STAR:
            warn(
                ("Redefinition of %s" % qname),
                ModuleInheritanceWarning, 2
            )

        self.setKind[qname] = BUILD_CLASS

        mc = cdict.get('__metaclass__')

        if mc is not None:

            while isClassAdvisor(mc):
                cb = getattr(mc,'callback',None)
                if cb is not None:
                    self.advisors.setdefault(qname,[]).append(cb)
                mc = mc.previousMetaclass

            if mc is None:
                del cdict['__metaclass__']
            else:
                cdict['__metaclass__'] = mc


        classes = self.classes
        get = self.classPath.get
        oldDPaths = []
        basePaths = tuple([get(id(base)) for base in bases])
        dictPaths = [(k,get(id(v))) for (k,v) in cdict.items() if get(id(v))]










        if classes.has_key(qname):

            oldClass, oldBases, oldPaths, oldItems, oldDPaths = classes[qname]
            addBases = []; addBase = addBases.append
            addPaths = []; addPath = addPaths.append

            for b,p in zip(oldBases, oldPaths):
                if p is None or p not in basePaths:
                    addBase(classes.get(p,(b,))[0])
                    addPath(p)

            bases = tuple(addBases) + bases
            basePaths = tuple(addPaths) + basePaths

            have = cdict.has_key
            for k,v in oldItems:
                if not have(k): cdict[k]=v

            for k,v in oldDPaths:
                cdict[k] = classes[v][0]

        if '.' in qname:    # try to set __name__ if nested class
            cdict['__name__'] = qname

        newClass = makeClass(qname,bases,cdict)

        classes[qname] = newClass, bases, basePaths, cdict.items(), \
            dict(dictPaths+oldDPaths).items()

        # Make sure that module and name are correct for pickling

        newClass.__module__ = self.dict['__name__']
        self.classPath[id(newClass)] = qname

        # Apply callbacks
        if qname in self.advisors:
            cbs = self.advisors[qname][:]
            while cbs:
                newClass = cbs.pop()(newClass)


        locked = self.locked
        if locked.has_key(qname):
            return locked[qname]

        # Save the class where pickle can find it
        self.dict[qname] = newClass

        return newClass

































def prepForSimulation(code, path='', depth=0):

    code = Code(code)
    idx = codeIndex(code); opcode, operand = idx.opcode, idx.operand
    offset = idx.offset
    name_index = code.name_index
    const_index = code.const_index
    append = code.co_code.append

    Simulator = name_index('__PEAK_Simulator__')
    DefFunc   = name_index('DEFINE_FUNCTION')
    DefClass  = name_index('DEFINE_CLASS')
    Assign    = name_index('ASSIGN_VAR')
    ImpStar   = name_index('IMPORT_STAR')

    names   = code.co_names
    consts  = code.co_consts
    co_code = code.co_code

    emit = code.append
    patcher = iter(code); patch = patcher.write; go = patcher.go
    spc = '    ' * depth

    for op in mutableOps:
        for i in idx.opcodeLocations(op):
            warn_explicit(
                "Modification to mutable during initialization",
                ModuleInheritanceWarning,
                code.co_filename,
                idx.byteLine(offset(i)),
            )

    for op in (DELETE_NAME, DELETE_GLOBAL):
        for i in idx.opcodeLocations(op):
            warn_explicit(
                "Deletion of global during initialization",
                ModuleInheritanceWarning,
                code.co_filename,
                idx.byteLine(offset(i)),
            )

    ### Fix up IMPORT_STAR operations

    for i in idx.opcodeLocations(IMPORT_STAR):

        backpatch = offset(i)

        if opcode(i-1) != IMPORT_NAME:
            line = idx.byteLine(backpatch)
            raise AssertionError(
                "Unrecognized 'import *' at line %(line)d" % locals()
            )

        patchTarget = len(co_code)
        go(offset(i-1))
        patch(JUMP_ABSOLUTE, patchTarget, 0)

        # rewrite the IMPORT_NAME
        emit(IMPORT_NAME, operand(i-1))

        # Call __PEAK_Simulator__.IMPORT_STAR(module, locals, prefix)
        emit(LOAD_GLOBAL, Simulator)
        emit(LOAD_ATTR, ImpStar)
        append(ROT_TWO)
        append(LOAD_LOCALS)
        emit(LOAD_CONST, const_index(path))
        emit(CALL_FUNCTION, 3)
        emit(JUMP_ABSOLUTE, backpatch)

        # Replace IMPORT_STAR w/remove of the return val from IMPORT_STAR()
        co_code[offset(i)] = POP_TOP

        #print "%(line)04d import * (into %(path)s)" % locals()









    ### Fix up all other operation types

    for i in list(idx.opcodeLocations(STORE_NAME))+list(
        idx.opcodeLocations(STORE_GLOBAL)
    ):

        op     = opcode(i)
        arg    = operand(i)
        prevOp = opcode(i-1)
        qname = name = names[arg]

        backpatch = offset(i)
        patchTarget = len(co_code)

        if path and opcode(i)==STORE_NAME:
            qname = path+name

        namArg = const_index(qname)

        # common prefix - get the simulator object
        emit(LOAD_GLOBAL, Simulator)




















        ### Handle class operations

        if prevOp == BUILD_CLASS:

            bind = "class"

            if opcode(i-2)!=CALL_FUNCTION or \
               opcode(i-3) not in (MAKE_CLOSURE, MAKE_FUNCTION) or \
               opcode(i-4)!=LOAD_CONST:

                line = idx.byteLine(backpatch)
                raise AssertionError(
                    "Unrecognized class %(qname)s at line %(line)d" % locals()
                )

            const = operand(i-4)
            suite = consts[const]
            consts[const] = prepForSimulation(suite, qname+'.', depth+1)

            backpatch -= 1  # back up to the BUILD_CLASS instruction...
            nextI = offset(i+1)

            # and fill up the space to the next instruction with POP_TOP, so
            # that if you disassemble the code it looks reasonable...

            for j in range(backpatch,nextI):
                co_code[j] = POP_TOP

            # get the DEFINE_CLAS method
            emit(LOAD_ATTR, DefClass)

            # Move it before the (name,bases,dict) args
            append(ROT_FOUR)

            # Get the absolute name, and call method w/4 args
            emit(LOAD_CONST, namArg)
            emit(CALL_FUNCTION, 4)




        ### Non-class definition

        else:
            if prevOp in (MAKE_FUNCTION, MAKE_CLOSURE):
                bind = "def"
                # get the DEFINE_FUNCTION method
                emit(LOAD_ATTR, DefFunc)
            else:
                bind = "assign"
                # get the ASSIGN_VAR method
                emit(LOAD_ATTR, Assign)

            # Move it before the value, get the absolute name, and call method
            append(ROT_TWO)
            emit(LOAD_CONST, namArg)
            emit(CALL_FUNCTION, 2)



        # Common patch epilog

        go(backpatch)
        patch(JUMP_ABSOLUTE, patchTarget, 0)

        emit(op, arg)
        emit(JUMP_ABSOLUTE, offset(i+1))

        #print "%(line)04d %(spc)s%(bind)s %(qname)s" % locals()

    code.co_stacksize += 5  # add a little margin for error
    return code.code()


bind_func(prepForSimulation, **globals())
bind_func(prepForSimulation, **getattr(__builtins__,'__dict__',__builtins__))






if __name__=='__main__':
    from glob import glob
    for file in glob('ick.py'):
        print
        print "File: %s" % file,
        source = open(file,'r').read().rstrip()+'\n'
        try:
            code = compile(source,file,'exec')
        except SyntaxError:
            print "Syntax Error!"
        else:
            print
        code = prepForSimulation(code)
    print




























cvs-admin@eby-sarna.com

Powered by ViewCVS 1.0-dev

ViewCVS and CVS Help