Commit 6429477d authored by Evan Simpson's avatar Evan Simpson

Initial checkin

parent 955f0de2
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
__version__='$Revision: 1.1 $'[11:-2]
import Globals
from Globals import Persistent, HTMLFile, package_home
from DocumentTemplate.DT_Util import TemplateDict
from string import join, strip
import re
import os
_www = os.path.join(package_home(globals()), 'www')
defaultBindings = {'name_context': 'context',
'name_container': 'self',
'name_m_self': 'm_self',
'name_ns': '',
'name_subpath': 'traverse_subpath'}
_marker = [] # Create a new marker
class NameAssignments:
# Note that instances of this class are intended to be immutable
# and persistent but not inherit from ExtensionClass.
_exprs = (('name_context', 'self.aq_parent'),
('name_container', 'self.aq_inner.aq_parent'),
('name_m_self', 'self'),
('name_ns', 'self._getNamespace(caller_namespace, kw)'),
('name_subpath', 'self._getTraverseSubpath()'),
)
_isLegalName = re.compile('_$|[a-zA-Z][a-zA-Z0-9_]*$').match
_asgns = {}
__allow_access_to_unprotected_subobjects__ = 1
def __init__(self, mapping):
# mapping is presumably the REQUEST or compatible equivalent.
# Note that we take care not to store expression texts in the ZODB.
asgns = {}
_isLegalName = self._isLegalName
for name, expr in self._exprs:
if mapping.has_key(name):
assigned_name = strip(mapping[name])
if not assigned_name:
continue
if not _isLegalName(assigned_name):
raise ValueError, ('"%s" is not a valid variable name.'
% assigned_name)
asgns[name] = assigned_name
self._asgns = asgns
def isAnyNameAssigned(self):
if len(self._asgns) > 0:
return 1
return 0
def isNameAssigned(self, name):
return self._asgns.has_key(name)
def getAssignedName(self, name, default=_marker):
val = self._asgns.get(name, default)
if val is _marker:
raise KeyError, name
return val
def getAssignedNamesInOrder(self):
# Returns the assigned names in the same order as that of
# self._exprs.
rval = []
asgns = self._asgns
for name, expr in self._exprs:
if asgns.has_key(name):
assigned_name = asgns[name]
rval.append(assigned_name)
return rval
def _generateCodeBlock(self, bindtext, assigned_names):
# Returns a tuple: exec-able code that can compute the value of
# the bindings and eliminate clashing keyword arguments,
# and the number of names bound.
text = ['bound_data.append(%s)\n' % bindtext]
for assigned_name in assigned_names:
text.append('if kw.has_key("%s"):\n' % assigned_name)
text.append(' del kw["%s"]\n' % assigned_name)
codetext = join(text, '')
return (compile(codetext, '<string>', 'exec'), len(assigned_names))
def _createCodeBlockForMapping(self):
# Generates a code block which generates the "bound_data"
# variable and removes excessive arguments from the "kw"
# variable. bound_data will be a mapping, for use as a
# global namespace.
exprtext = []
assigned_names = []
asgns = self._asgns
for name, expr in self._exprs:
if asgns.has_key(name):
assigned_name = asgns[name]
assigned_names.append(assigned_name)
exprtext.append('"%s":%s,' % (assigned_name, expr))
text = '{%s}' % join(exprtext, '')
return self._generateCodeBlock(text, assigned_names)
def _createCodeBlockForTuple(self, argNames):
# Generates a code block which generates the "bound_data"
# variable and removes excessive arguments from the "kw"
# variable. bound_data will be a tuple, for use as
# positional arguments.
assigned_names = []
exprtext = []
asgns = self._asgns
for argName in argNames:
passedLastBoundArg = 1
for name, expr in self._exprs:
# Provide a value for the available exprs.
if asgns.has_key(name):
assigned_name = asgns[name]
if assigned_name == argName:
# The value for this argument will be filled in.
exprtext.append('%s,' % expr)
assigned_names.append(assigned_name)
passedLastBoundArg = 0
break
if passedLastBoundArg:
# Found last of bound args.
break
text = '(%s)' % join(exprtext, '')
return self._generateCodeBlock(text, assigned_names)
class Bindings (Persistent):
manage_options = (
{'label':'Bindings', 'action':'ZBindingsHTML_editForm'},
)
__ac_permissions__ = (
('View management screens', ('ZBindingsHTML_editForm',
'getBindingAssignments',)),
('Change Python Scripts', ('ZBindingsHTML_editAction',
'ZBindings_edit')),
)
ZBindingsHTML_editForm = HTMLFile('scriptBindings', _www)
def ZBindings_edit(self, mapping):
names = self._setupBindings(mapping)
self._prepareBindCode()
self._editedBindings()
def ZBindingsHTML_editAction(self, REQUEST):
'''Changes binding names.
'''
self.ZBindings_edit(REQUEST)
message = "Bindings changed."
return self.manage_main(self, REQUEST, manage_tabs_message=message)
def _editedBindings(self):
# Override to receive notification when the bindings are edited.
pass
def _setupBindings(self, names={}):
self._bind_names = names = NameAssignments(names)
return names
def getBindingAssignments(self):
if not hasattr(self, '_bind_names'):
self._setupBindings()
return self._bind_names
def __before_publishing_traverse__(self, self2, request):
path = request['TraversalRequestNameStack']
names = self.getBindingAssignments()
if (not names.isNameAssigned('name_subpath') or
(path and hasattr(self.aq_explicit, path[-1])) ):
return
subpath = path[:]
path[:] = []
subpath.reverse()
request.set('traverse_subpath', subpath)
def _createBindCode(self, names):
return names._createCodeBlockForMapping()
def _prepareBindCode(self):
# Creates:
# - a code block that quickly generates "bound_data" and
# modifies the "kw" variable.
# - a count of the bound arguments.
# Saves them in _v_bindcode and _v_bindcount.
# Returns .
names = self.getBindingAssignments()
if names.isAnyNameAssigned():
bindcode, bindcount = self._createBindCode(names)
else:
bindcode, bindcount = None, 0
self._v_bindcode = bindcode
self._v_bindcount = bindcount
return bindcode
def _getBindCount(self):
bindcount = getattr(self, '_v_bindcount', _marker)
if bindcount is _marker:
self._prepareBindCode()
bindcount = self._v_bindcount
return bindcount
def _getTraverseSubpath(self):
# Utility for bindcode.
if hasattr(self, 'REQUEST'):
return self.REQUEST.other.get('traverse_subpath', [])
else:
return []
def _getNamespace(self, caller_namespace, kw):
# Utility for bindcode.
if caller_namespace is None:
# Try to get the caller's namespace by scanning
# the keyword arguments for an argument with the
# same name as the assigned name for name_ns.
names = self.getBindingAssignments()
assigned_name = names.getAssignedName('name_ns')
caller_namespace = kw.get(assigned_name, None)
# Create a local namespace.
my_namespace = TemplateDict()
if caller_namespace is not None:
# Include the caller's namespace.
my_namespace._push(caller_namespace)
return my_namespace
def __call__(self, *args, **kw):
'''Calls the script.
'''
return self._bindAndExec(args, kw, None)
def __render_with_namespace__(self, namespace):
'''Calls the script with the specified namespace.'''
namevals = {}
# Try to find unbound parameters in the namespace, if the
# namespace is bound.
if self.getBindingAssignments().isNameAssigned('name_ns'):
for name in self.func_code.co_varnames:
try:
namevals[name] = namespace[name]
except KeyError:
pass
return self._bindAndExec((), namevals, namespace)
render = __call__
def _bindAndExec(self, args, kw, caller_namespace):
'''Prepares the bound information and calls _exec(), possibly
with a namespace.
'''
bindcode = getattr(self, '_v_bindcode', _marker)
if bindcode is _marker:
bindcode = self._prepareBindCode()
bound_data = None
if bindcode is not None:
bound_data = []
exec bindcode
bound_data = bound_data[0]
return self._exec(bound_data, args, kw)
Globals.default__class_init__(Bindings)
1999-12-13 Evan Simpson <evan@4-am.com>
* Version 0.1.7
* Nested functions and lambdas are now supported, with full safety.
* You can access all of the dtml-var format functions through a builtin
dictionary called special_formats (eg: special_formats['html-quote']).
* Handing off to Digital Creations for inclusion in CVS.
* Packaged with packProduct script, which excludes parent directories
and .pyc files. Makes for a smaller package, and doesn't step on
ownership/permissions of lib/python/Products path elements.
1999-12-01 Evan Simpson <evan@4-am.com>
* Added COPYRIGHT.txt, making Wide Open Source licence (BSD-style)
explicit. (Mike Goldman provided the text, I provided the silly name).
* Jeff Rush donated a PrincipiaSearchSource method, so that
PythonMethod objects can be zcataloged to the same degree
as DTML Methods.
* Also from Jeff Rush, a document_src method, so that the source of
PythonMethods can be viewed via a "View Source" link if desired.
* If a PM has a 'traverse_subpath' parameter, you can now directly
traverse it. The elements of the subpath will then be put into a list
in 'traverse_subpath'. (thanks to Anthony Baxter)
1999-11-11 Evan Simpson <evan@4-am.com>
* Version 0.1.6
* Fix to builtins messed up DTML Methods, so I re-fixed it.
1999-11-05 Evan Simpson <evan@4-am.com>
* Version 0.1.5
* Killed *%#&$@ weird bug in which having 'add' documents in 'www'
subdirectory prevented rename, paste, or import of existing
PythonMethods! See use of '_www'.
* Range, test, and several other Zope 'builtins' had an unbound 'self'
argument unless called on _, but that's fixed.
* Safe multiplication was utterly broken (thanks to the guard); now
it works. Is anyone using the safe version??
1999-10-18 Evan Simpson <evan@4-am.com>
* Eliminated bug which delayed stringification of printed values.
1999-10-08 Evan Simpson <evan@4-am.com>
* Version 0.1.4
* Fixed mis-design noticed by Michel Pelletier, and refactored
MakeFunction. Now both kinds of Python Method have the bugfix
from 0.1.3, and shouldn't provoke a transaction when called.
1999-10-07 Evan Simpson <evan@4-am.com>
* Version 0.1.3
* Fixed parameter bug with 'self' and no defaults
1999-09-24 Evan Simpson <evan@4-am.com>
* Version 0.1.2
* Added WebDAV/FTP access code donated by Michel Pelletier
* Made parameters part of WebDAV/FTP text
* Eliminated initialization of globals to None
* Added 'global_exists' global function instead
* Killed bug with unused parameters
* Put switch in Guarded.py to allow both regular and
dangerous (XXX) PythonMethods to live side-by-side.
This means that people who patched version 0.1.1
will have to re-create any unsafe PMs they use (Sorry).
1999-09-10 Evan Simpson <evan@4-am.com>
* Version 0.1.1
* Incorporated DT_Util builtins and guards
* Fixed direct access via URL
* Fixed methodAdd.dtml
* rstrip function body
* Major changes to zbytecodehacks
''' RemotePS.py
External Method that allows you to remotely (via XML-RPC, for instance)
execute restricted Python code.
For example, create an External Method 'restricted_exec' in your Zope
root, and you can remotely call:
foobarsize = s.foo.bar.restricted_exec('len(context.objectIds())')
'''
from Products.PythonScripts.PythonScript import PythonScript
from string import join
def restricted_exec(self, body, varmap=None):
ps = PythonScript('temp')
if varmap is None:
varmap = {}
ps.ZPythonScript_edit(join(varmap.keys(), ','), body)
return apply(ps.__of__(self), varmap.values())
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
__version__='$Revision: 1.1 $'[11:-2]
from zbytecodehacks.VSExec import SafeBlock, GuardedBinaryOps, \
UntupleFunction, RedirectWrites, WriteGuard, RedirectReads, ReadGuard, \
LoopLimits, bind
from DocumentTemplate.VSEval import careful_mul
from DocumentTemplate.DT_Util import TemplateDict, \
careful_pow, d, ValidationError
from DocumentTemplate.DT_Var import special_formats
from AccessControl import getSecurityManager, getModuleSecurity
import standard
standard.__allow_access_to_unprotected_subobjects__ = 1
safefuncs = TemplateDict()
safebin = {}
for name in ('None', 'abs', 'chr', 'divmod', 'float', 'hash', 'hex', 'int',
'len', 'max', 'min', 'oct', 'ord', 'round', 'str'):
safebin[name] = d[name]
for name in ('range', 'pow', 'DateTime', 'test', 'namespace', 'render'):
safebin[name] = getattr(safefuncs, name)
for name in ('ArithmeticError', 'AttributeError', 'EOFError',
'EnvironmentError', 'FloatingPointError', 'IOError',
'ImportError', 'IndexError', 'KeyError',
'LookupError', 'NameError', 'OSError', 'OverflowError',
'RuntimeError', 'StandardError', 'SyntaxError',
'TypeError', 'ValueError', 'ZeroDivisionError',
'apply', 'callable', 'cmp', 'complex', 'isinstance',
'issubclass', 'long', 'repr'):
safebin[name] = __builtins__[name]
def same_type(arg1, *args):
'''Compares the type of two or more objects.'''
base = getattr(arg1, 'aq_base', arg1)
t = type(base)
for arg in args:
argbase = getattr(arg, 'aq_base', arg)
if type(argbase) is not t:
return 0
return 1
safebin['same_type'] = same_type
_marker = [] # Create a new marker object.
def aq_validate(*args):
return apply(getSecurityManager().validate, args[:4])
def __careful_getattr__(inst, name, default=_marker):
if name[:1]!='_':
try:
# Try to get the attribute normally so that we don't
# accidentally acquire when we shouldn't.
v = getattr(inst, name)
# Filter out the objects we can't access.
if hasattr(inst, 'aq_acquire'):
return inst.aq_acquire(name, aq_validate)
except AttributeError:
if default is not _marker:
return default
raise
if getSecurityManager().validate(inst,inst,name,v):
return v
raise ValidationError, name
safebin['getattr'] = __careful_getattr__
def __careful_setattr__(object, name, value):
setattr(WriteGuard(object), name, value)
safebin['setattr'] = __careful_setattr__
def __careful_delattr__(object, name):
delattr(WriteGuard(object), name)
safebin['delattr'] = __careful_delattr__
def __careful_filter__(f, seq, skip_unauthorized=0):
if type(seq) is type(''):
return filter(f, seq)
v = getSecurityManager().validate
result = []
a = result.append
for el in seq:
if v(seq, seq, None, el):
if f(el): a(el)
elif not skip_unauthorized:
raise ValidationError, 'unauthorized access to element'
return result
safebin['filter'] = __careful_filter__
def __careful_list__(seq):
if type(seq) is type(''):
raise TypeError, 'cannot convert string to list'
return list(seq)
safebin['list'] = __careful_list__
def __careful_tuple__(seq):
if type(seq) is type(''):
raise TypeError, 'cannot convert string to tuple'
return tuple(seq)
safebin['tuple'] = __careful_tuple__
def __careful_map__(f, *seqs):
for seq in seqs:
if type(seq) is type(''):
raise TypeError, 'cannot map a string'
return apply(map, tuple([f] + map(ReadGuard, seqs)))
safebin['map'] = __careful_map__
import sys
from string import split
def __careful_import__(mname, globals={}, locals={}, fromlist=None):
mnameparts = split(mname, '.')
firstmname = mnameparts[0]
validate = getSecurityManager().validate
module = load_module(None, None, mnameparts, validate, globals, locals)
if module is not None:
mtype = type(module)
if fromlist is None:
fromlist = ()
try:
for name in fromlist:
if name == '*':
raise ImportError, ('"from %s import *" is not allowed'
% mname)
v = getattr(module, name, None)
if v is None:
v = load_module(module, mname, [name], validate,
globals, locals)
if not validate(module, module, name, v):
raise "Unauthorized"
else:
return __import__(mname, globals, locals, fromlist)
except "Unauthorized":
raise ImportError, ('import of "%s" from "%s" is unauthorized'
% (name, mname))
raise ImportError, 'import of "%s" is unauthorized' % mname
safebin['__import__'] = __careful_import__
def load_module(module, mname, mnameparts, validate, globals, locals):
modules = sys.modules
modsec = getModuleSecurity()
while mnameparts:
nextname = mnameparts.pop(0)
if mname is None:
mname = nextname
else:
mname = '%s.%s' % (mname, nextname)
nextmodule = modules.get(mname, None)
if nextmodule is None:
if not modsec.has_key(mname):
return
__import__(mname, globals, locals)
nextmodule = modules[mname]
nextmodule.ZopeSecurity = modsec[mname]
if module and not validate(module, module, nextname, nextmodule):
return
module = nextmodule
return module
class Guard:
mul = careful_mul
pow = careful_pow
theGuard = Guard()
class GuardedBlock(SafeBlock):
Mungers = SafeBlock.Mungers + [GuardedBinaryOps(theGuard),
RedirectWrites(), RedirectReads(), LoopLimits]
class _ReadGuardWrapper:
#def validate(self, *args):
# return apply(getSecurityManager().validate, args[:4])
def __getattr__(self, name):
ob = self.__dict__['_ob']
return __careful_getattr__(ob, name)
def __getitem__(self, i):
ob = self.__dict__['_ob']
v = ob[i]
if type(ob) is type(''): return v
if getSecurityManager().validate(ob, ob, None, v):
return v
raise ValidationError, 'unauthorized access to element %s' % `i`
ReadGuard = bind(ReadGuard, Wrapper=_ReadGuardWrapper)
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
"""Module Security module
"""
__version__='$Revision: 1.1 $'[11:-2]
import AccessControl
class ModuleSecurityInfo: #(AccessControl.ClassSecurityInfo):
def __init__(self, module_name=None):
if module_name is not None:
moduleSecurity[module_name] = self
def stub(self, *args, **kwargs):
pass
def __getattr__(self, name):
return self.stub
moduleSecurity = {}
def getModuleSecurity():
return moduleSecurity
AccessControl.getModuleSecurity = getModuleSecurity
AccessControl.ModuleSecurityInfo = ModuleSecurityInfo
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
"""Python Scripts Product
This product provides support for Script objects containing restricted
Python code.
"""
__version__='$Revision: 1.1 $'[11:-2]
import sys, os, traceback, re
from Globals import MessageDialog, HTMLFile, package_home
import AccessControl, OFS, Guarded
from OFS.SimpleItem import SimpleItem
from DateTime.DateTime import DateTime
from string import join, strip, rstrip, split, replace, lower
from urllib import quote
from Bindings import Bindings, defaultBindings
from Script import Script
from AccessControl import getSecurityManager
from OFS.History import Historical, html_diff
from zLOG import LOG, ERROR, INFO
_www = os.path.join(package_home(globals()), 'www')
manage_addPythonScriptForm=HTMLFile('pyScriptAdd', _www)
def manage_addPythonScript(self, id, REQUEST=None):
"""Add a Python script to a folder.
"""
id = str(id)
id = self._setObject(id, PythonScript(id))
if REQUEST is not None:
try: u = self.DestinationURL()
except: u = REQUEST['URL1']
REQUEST.RESPONSE.redirect('%s/%s/manage_main' % (u, quote(id)))
return ''
class PythonScript(Script, Historical):
"""Web-callable scripts written in a safe subset of Python.
The function may include standard python code, so long as it does
not attempt to use the "exec" statement or certain restricted builtins.
"""
meta_type='Python Script'
_proxy_roles = ()
_params = _body = ''
manage_options = (
{'label':'Edit', 'action':'ZPythonScriptHTML_editForm'},
{'label':'Upload', 'action':'ZPythonScriptHTML_uploadForm'},
) + Bindings.manage_options + (
{'label':'Try It', 'action':'ZScriptHTML_tryForm'},
{'label':'Proxy', 'action':'manage_proxyForm'},
) + Historical.manage_options + SimpleItem.manage_options
__ac_permissions__ = (
('View management screens',
('ZPythonScriptHTML_editForm', 'ZPythonScript_changePrefs',
'manage_main', 'ZScriptHTML_tryForm')),
('Change Python Scripts',
('ZPythonScript_edit', 'PUT', 'manage_FTPput',
'ZPythonScript_setTitle', 'ZPythonScriptHTML_upload',
'ZPythonScriptHTML_uploadForm', 'manage_historyCopy',
'manage_beforeHistoryCopy', 'manage_afterHistoryCopy')),
('Change proxy roles', ('manage_proxyForm', 'manage_proxy')),
('View', ('__call__','','ZPythonScriptHTML_tryAction')),
)
def __init__(self, id):
self.id = id
self.ZBindings_edit(defaultBindings)
self._makeFunction(1)
ZPythonScriptHTML_editForm = HTMLFile('pyScriptEdit', _www)
manage = manage_main = ZPythonScriptHTML_editForm
manage_proxyForm = HTMLFile('pyScriptProxy', _www)
ZScriptHTML_tryForm = HTMLFile('scriptTry', _www)
ZPythonScriptHTML_uploadForm = HTMLFile('pyScriptUpload', _www)
def ZPythonScriptHTML_editAction(self, REQUEST, title, params, body):
"""Change the script's main parameters."""
self.ZPythonScript_setTitle(title)
self.ZPythonScript_edit(params, body)
message = "Content changed."
if getattr(self, '_v_warnings', None):
message = ("<strong>Warning:</strong> <i>%s</i>"
% join(self._v_warnings, '<br>'))
return self.ZPythonScriptHTML_editForm(self, REQUEST,
manage_tabs_message=message)
def ZPythonScript_setTitle(self, title):
self.title = str(title)
def ZPythonScript_edit(self, params, body):
self._validateProxy()
if type(body) is not type(''):
body = body.read()
if self._params <> params or self._body <> body:
self._params = str(params)
self._body = rstrip(body)
self._makeFunction(1)
def ZPythonScriptHTML_upload(self, REQUEST, file=''):
"""Replace the body of the script with the text in file."""
if type(file) is not type(''): file = file.read()
self._setText(file)
message = 'Content changed.'
return self.ZPythonScriptHTML_editForm(self, REQUEST,
manage_tabs_message=message)
def ZScriptHTML_tryParams(self):
"""Parameters to test the script with."""
param_names = []
for name in split(self._params, ','):
name = strip(name)
if name and name[0] != '*':
param_names.append(name)
return param_names
def ZPythonScriptHTML_changePrefs(self, REQUEST, height=None, width=None,
dtpref_cols='50', dtpref_rows='20'):
"""Change editing preferences."""
LOG('PythonScript', INFO, 'Change prefs h: %s, w: %s, '
'cols: %s, rows: %s' % (height, width, dtpref_cols, dtpref_rows))
szchh = {'Taller': 1, 'Shorter': -1, None: 0}
szchw = {'Wider': 5, 'Narrower': -5, None: 0}
try: rows = int(height)
except: rows = max(1, int(dtpref_rows) + szchh.get(height, 0))
try: cols = int(width)
except: cols = max(40, int(dtpref_cols) + szchw.get(width, 0))
LOG('PythonScript', INFO, 'to cols: %s, rows: %s' % (cols, rows))
e = (DateTime('GMT') + 365).rfc822()
setc = REQUEST['RESPONSE'].setCookie
setc('dtpref_rows', str(rows), path='/', expires=e)
setc('dtpref_cols', str(cols), path='/', expires=e)
REQUEST.form.update({'dtpref_cols': cols, 'dtpref_rows': rows})
return apply(self.manage_main, (self, REQUEST), REQUEST.form)
def manage_historyCompare(self, rev1, rev2, REQUEST,
historyComparisonResults=''):
return PythonScript.inheritedAttribute('manage_historyCompare')(
self, rev1, rev2, REQUEST,
historyComparisonResults=html_diff(rev1.read(), rev2.read()) )
def _checkCBlock(self, MakeBlock):
params = self._params
body = self._body or ' pass'
# If the body isn't indented, indent it one space.
nbc = re.search('\S', body).start()
if nbc and body[nbc - 1] in ' \t':
defblk = 'def f(%s):\n%s\n' % (params, body)
else:
# Waaa: triple-quoted strings will get indented too.
defblk = 'def f(%s):\n %s\n' % (params, replace(body, '\n', '\n '))
blk = MakeBlock(defblk, self.id, self.meta_type)
self._v_errors, self._v_warnings = blk.errors, blk.warnings
if blk.errors:
if hasattr(self, '_v_f'): del self._v_f
self._setFuncSignature((), (), 0)
else:
self._t = blk.t
def _newfun(self, allowSideEffect, g, **kws):
from Guarded import UntupleFunction
self._v_f = f = apply(UntupleFunction, (self._t, g), kws)
if allowSideEffect:
fc = f.func_code
self._setFuncSignature(f.func_defaults, fc.co_varnames,
fc.co_argcount)
return f
def _makeFunction(self, allowSideEffect=0):
from Guarded import GuardedBlock, theGuard, safebin
from Guarded import WriteGuard, ReadGuard
if allowSideEffect:
self._checkCBlock(GuardedBlock)
if getattr(self, '_v_errors', None):
raise "Python Script Error", ('<pre>%s</pre>' %
join(self._v_errors, '\n') )
return self._newfun(allowSideEffect, {'$guard': theGuard,
'$write_guard': WriteGuard,
'$read_guard': ReadGuard},
__builtins__=safebin)
def _editedBindings(self):
f = getattr(self, '_v_f', None)
if f is None:
return
self._makeFunction(1)
def _exec(self, globals, args, kw):
"""Call a Python Script
Calling a Python Script is an actual function invocation.
"""
f = getattr(self, '_v_f', None)
if f is None:
f = self._makeFunction()
__traceback_info__ = globals, args, kw, self.func_defaults
if globals is not None:
# Updating func_globals directly *should* be thread-safe.
f.func_globals.update(globals)
security=getSecurityManager()
security.addContext(self)
try:
return apply(f, args, kw)
finally:
security.removeContext(self)
def manage_haveProxy(self,r): return r in self._proxy_roles
def _validateProxy(self, roles=None):
if roles is None: roles=self._proxy_roles
if not roles: return
user=u=getSecurityManager().getUser()
if user is not None:
user=user.hasRole
for r in roles:
if r and not user(None, (r,)):
user=None
break
if user is not None: return
raise 'Forbidden', ('You are not authorized to change <em>%s</em> '
'because you do not have proxy roles.\n<!--%s, %s-->'
% (self.id, u, roles))
def manage_proxy(self, roles=(), REQUEST=None):
"Change Proxy Roles"
self._validateProxy(roles)
self._validateProxy()
self._proxy_roles=tuple(roles)
if REQUEST: return MessageDialog(
title ='Success!',
message='Your changes have been saved',
action ='manage_main')
def PUT(self, REQUEST, RESPONSE):
""" Handle HTTP PUT requests """
self.dav__init(REQUEST, RESPONSE)
self._setText(REQUEST.get('BODY', ''))
RESPONSE.setStatus(204)
return RESPONSE
manage_FTPput = PUT
def _setText(self, text):
self._validateProxy()
mdata = self._metadata_map()
st = 0
try:
while 1:
# Find the next non-empty line
m = _nonempty_line.search(text, st)
if not m:
# There were no non-empty body lines
body = ''
break
line = strip(m.group(0))
if line[:2] != '##':
# We have found the first line of the body
body = text[m.start(0):]
break
st = m.end(0)
# Parse this header line
if len(line) == 2 or line[2] == ' ' or '=' not in line:
# Null header line
continue
k, v = split(line[2:], '=', 1)
k = lower(strip(k))
v = strip(v)
if not mdata.has_key(k):
SyntaxError, 'Unrecognized header line "%s"' % line
if v == mdata[k]:
# Unchanged value
continue
# Set metadata value
if k == 'title':
self.title = v
elif k == 'parameters':
self._params = v
self._body = rstrip(body)
self._makeFunction(1)
except:
LOG(self.meta_type, ERROR, '_setText failed', error=sys.exc_info())
raise
def manage_FTPget(self):
"Get source for FTP download"
self.REQUEST.RESPONSE.setHeader('Content-Type', 'text/plain')
return self.read()
def _metadata_map(self):
return {
'title': self.title,
'parameters': self._params,
}
def read(self):
# Construct metadata header lines, indented the same as the body.
m = _first_indent.search(self._body)
if m: prefix = m.group(0) + '##'
else: prefix = '##'
hlines = ['%s %s "%s"' % (prefix, self.meta_type, self.id)]
for kv in self._metadata_map().items():
hlines.append('%s=%s' % kv)
hlines.append('')
return join(hlines, '\n' + prefix) + '\n' + self._body
def params(self): return self._params
def body(self): return self._body
def get_size(self): return len(self._body)
getSize = get_size
def PrincipiaSearchSource(self):
"Support for searching - the document's contents are searched."
return "%s\n%s" % (self._params, self._body)
def document_src(self, REQUEST=None, RESPONSE=None):
"""Return unprocessed document source."""
if RESPONSE is not None:
RESPONSE.setHeader('Content-Type', 'text/plain')
return self.read()
_first_indent = re.compile('(?m)^ *(?! |$)')
_nonempty_line = re.compile('(?m)^(.*\S.*)$')
Python Scripts
The Python Scripts product provides support for restricted execution of
Python scripts, exposing them as callable objects within the Zope
environment.
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
"""Script module
This provides generic script support
"""
__version__='$Revision: 1.1 $'[11:-2]
import os
from Globals import package_home, HTMLFile
from OFS.SimpleItem import SimpleItem
from string import join
from urllib import quote
from Bindings import Bindings
class FuncCode:
def __init__(self, varnames, argcount):
self.co_varnames=varnames
self.co_argcount=argcount
def __cmp__(self, other):
if other is None: return 1
try: return cmp((self.co_argcount, self.co_varnames),
(other.co_argcount, other.co_varnames))
except: return 1
_www = os.path.join(package_home(globals()), 'www')
class Script(SimpleItem, Bindings):
"""Web-callable script mixin
"""
index_html = None
func_defaults=()
func_code=None
__ac_permissions__ = (
('View management screens', ('ZScriptHTML_tryForm',)),
('View', ('__call__','','ZPythonScriptHTML_tryAction')),
)
ZScriptHTML_tryForm = HTMLFile('scriptTry', _www)
def ZScriptHTML_tryAction(self, REQUEST, argvars):
"""Apply the test parameters.
"""
vv = []
for argvar in argvars:
vv.append("%s=%s" % (quote(argvar.name), quote(argvar.value)))
raise "Redirect", "%s?%s" % (REQUEST['URL1'], join(vv, '&'))
def _setFuncSignature(self, defaults, varnames, argcount):
# Generate a change only if we have to.
if self.func_defaults != defaults:
self.func_defaults = defaults
code = FuncCode(varnames, argcount)
if self.func_code != code:
self.func_code = code
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
__doc__='''Python Scripts Product Initialization
$Id: __init__.py,v 1.1 2000/11/30 22:18:03 evan Exp $'''
__version__='$Revision: 1.1 $'[11:-2]
import ModuleSecurity
import PythonScript
import standard
__roles__ = None
__allow_access_to_unprotected_subobjects__ = 1
def initialize(context):
context.registerClass(
instance_class=PythonScript.PythonScript,
constructors=(PythonScript.manage_addPythonScriptForm,
PythonScript.manage_addPythonScript),
icon='www/pyscript.gif')
##############################################################################
#
# Zope Public License (ZPL) Version 1.0
# -------------------------------------
#
# Copyright (c) Digital Creations. All rights reserved.
#
# This license has been certified as Open Source(tm).
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# 1. Redistributions in source code must retain the above copyright
# notice, this list of conditions, and the following disclaimer.
#
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions, and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
#
# 3. Digital Creations requests that attribution be given to Zope
# in any manner possible. Zope includes a "Powered by Zope"
# button that is installed by default. While it is not a license
# violation to remove this button, it is requested that the
# attribution remain. A significant investment has been put
# into Zope, and this effort will continue if the Zope community
# continues to grow. This is one way to assure that growth.
#
# 4. All advertising materials and documentation mentioning
# features derived from or use of this software must display
# the following acknowledgement:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# In the event that the product being advertised includes an
# intact Zope distribution (with copyright and license included)
# then this clause is waived.
#
# 5. Names associated with Zope or Digital Creations must not be used to
# endorse or promote products derived from this software without
# prior written permission from Digital Creations.
#
# 6. Modified redistributions of any form whatsoever must retain
# the following acknowledgment:
#
# "This product includes software developed by Digital Creations
# for use in the Z Object Publishing Environment
# (http://www.zope.org/)."
#
# Intact (re-)distributions of any official Zope release do not
# require an external acknowledgement.
#
# 7. Modifications are encouraged but must be packaged separately as
# patches to official Zope releases. Distributions that do not
# clearly separate the patches from the original work must be clearly
# labeled as unofficial distributions. Modifications which do not
# carry the name Zope may be packaged in any form, as long as they
# conform to all of the clauses above.
#
#
# Disclaimer
#
# THIS SOFTWARE IS PROVIDED BY DIGITAL CREATIONS ``AS IS'' AND ANY
# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL DIGITAL CREATIONS OR ITS
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#
#
# This software consists of contributions made by Digital Creations and
# many individuals on behalf of Digital Creations. Specific
# attributions are listed in the accompanying credits file.
#
##############################################################################
"""Python Scripts standard utility module
This module provides helpful functions and classes for use in Python
Scripts. It can be accessed from Python with the statement
"import Products.PythonScripts.standard"
"""
__version__='$Revision: 1.1 $'[11:-2]
from AccessControl import ModuleSecurityInfo
security = ModuleSecurityInfo()
security.public('special_formats')
from DocumentTemplate.DT_Var import special_formats
from Globals import HTML
from AccessControl import getSecurityManager
security.public('DTML')
class DTML(HTML):
"""DTML objects are DocumentTemplate.HTML objects that allow
dynamic, temporary creation of restricted DTML."""
def __call__(self, client=None, REQUEST={}, RESPONSE=None, **kw):
"""Render the DTML given a client object, REQUEST mapping,
Response, and key word arguments."""
security=getSecurityManager()
security.addContext(self)
try:
return apply(HTML.__call__, (self, client, REQUEST), kw)
finally: security.removeContext(self)
def validate(self, inst, parent, name, value, md):
return getSecurityManager().validate(inst, parent, name, value)
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html lang="en">
<head>
<title>Add Python Script</title>
</head>
<body bgcolor="#FFFFFF" link="#000099" vlink="#555555" alink="#77003B">
<h2>Add Python Script</h2>
<P>
Python Scripts allow you to add functionality to Zope by writing
scripts in the Python programming language
that are exposed as callable Zope objects.
</P>
<form action="&dtml-URL1;" method="POST">
<input type="hidden" name="manage_addPythonScript:default_method" value="">
<p>
<strong>ID:</strong> <input type="text" name="id" size="20">
</p>
<p>
<input type="submit" value="Add and Edit" name="addedit">
<input type="submit" value="Cancel" name="manage_workspace:method">
</p>
</form>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html lang="en">
<head>
<title>Edit <dtml-var title_or_id></title>
</head>
<body bgcolor="#ffffff" link="#000099" vlink="#555555" alink="#77003b">
<dtml-var manage_tabs>
<form action="&dtml-URL1;" method="POST">
<input type="hidden" name=":default_method" value="ZPythonScriptHTML_changePrefs">
<table cellspacing="2">
<tr>
<th align="left" valign="top"><em>Title</em></th>
<td align="left" valign="top">
<input type="text" name="title" size="50" value="&dtml-title;">
</td>
</tr>
<tr> <th align="left"><em>Parameter&nbsp;list</em></th>
<td align="left"><input name="params" size="30" value="&dtml-params;">
</td></tr>
<dtml-with getBindingAssignments>
<dtml-if getAssignedNamesInOrder>
<tr> <th align="left"><em>Bound&nbsp;names</em></th>
<td align="left">
<dtml-in getAssignedNamesInOrder>
<dtml-var sequence-item html_quote><dtml-unless sequence-end>, </dtml-unless>
</dtml-in>
</td></tr>
</dtml-if>
</dtml-with>
<tr>
<th align="left" valign="top"><em>Last&nbsp;modified</em></th>
<td align="left" valign="top"><dtml-var bobobase_modification_time></td>
</tr>
</table>
<!-- style="width: 2em" -->
<b>Text area </b>
<input name="height" type="submit" value="Taller">
<input name="height" type="submit" value="Shorter">
<input name="width" type="submit" value="Wider">
<input name="width" type="submit" value="Narrower">
<br>
<textarea name="body:text" wrap="off" style="width: 100%"
cols=<dtml-var dtpref_cols html_quote missing="50">
rows=<dtml-var dtpref_rows html_quote missing="20">
>&dtml-body;</textarea><br>
<input name="ZPythonScriptHTML_editAction:method" type="submit"
value="Save Changes">
</form>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML lang="en">
<HEAD>
<TITLE>Edit</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<dtml-var manage_tabs>
<P>
Proxy roles allow you to control the access that a
Script has. Proxy roles replace the roles of the user who is executing
the Script. This can be used to both expand and limit
access to resources.
</P>
<P>Use the form below to select which roles this Script will have.
</P>
<FORM ACTION="manage_proxy" METHOD="POST">
<TABLE CELLSPACING="2">
<TR>
<TH ALIGN="LEFT" VALIGN="TOP">Id</TH>
<TD ALIGN="LEFT" VALIGN="TOP"><dtml-var id></TD>
</TR>
<TR>
<TH ALIGN="LEFT" VALIGN="TOP"><EM>Title</EM></TH>
<TD ALIGN="LEFT" VALIGN="TOP"><dtml-var title></TD>
</TR>
<TR>
<TH ALIGN="LEFT" VALIGN="TOP">Proxy Roles</TH>
<TD VALIGN="TOP">
<SELECT NAME="roles:list" SIZE="7" MULTIPLE>
<dtml-in valid_roles>
<dtml-if expr="_vars['sequence-item'] != 'Shared'">
<OPTION
<dtml-if expr="manage_haveProxy(_vars['sequence-item'])">
SELECTED</dtml-if>
><dtml-var sequence-item></OPTION>
</dtml-if>
</dtml-in valid_roles>
</SELECT>
</TD>
</TR>
<TR>
<TD></TD>
<TD>
<INPUT NAME=SUBMIT TYPE="SUBMIT" VALUE="Change">
</TD>
</TR>
</TABLE>
</FORM>
</BODY>
</HTML>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<HTML lang="en">
<HEAD>
<TITLE>Upload</TITLE>
</HEAD>
<BODY BGCOLOR="#FFFFFF" LINK="#000099" VLINK="#555555">
<dtml-var manage_tabs>
<P>
You may upload the source for &dtml-title_and_id; using the form below.
Choose an existing file from your local computer by pushing the
<I>Browse</I> button. The contents of the file should be a
valid script with an optional &quot;##data&quot; block at the start.
You may click the following link to <a href="document_src">view or
download</a> the current source.
<FORM ACTION="ZPythonScriptHTML_upload" METHOD="POST"
ENCTYPE="multipart/form-data">
<TABLE CELLSPACING="2">
<TR>
<TH ALIGN="LEFT" VALIGN="TOP">File</TH>
<TD ALIGN="LEFT" VALIGN="TOP">
<INPUT TYPE="file" NAME="file" SIZE="25" VALUE="">
</TD>
</TR>
<TR>
<TD></TD>
<TD><BR><INPUT TYPE="SUBMIT" VALUE="Change"></TD>
</TR>
</TABLE>
</FORM>
</BODY>
</HTML>
<html>
<head>
<title>Bindings - &dtml-title_or_id;</title>
</head>
<body bgcolor="#ffffff" link="#000099" vlink="#555555" alink="#77003b">
<dtml-var manage_tabs>
<h2>Bindings - &dtml-title_or_id;</h2>
<form action="ZBindingsHTML_editAction" method="POST">
<dtml-with getBindingAssignments>
<p><em>Each of the following items describes a piece of information about
this script's calling environment. If you supply a variable name for
an item, or accept the recommended name, the information will
automatically be provided under that name when the script is called.
</em></p>
<table cellpadding="2">
<tr>
<th align="left" valign="top">Container</th>
<td align="left" valign="top" nowrap><input type="text" name="name_container"
value="<dtml-var expr="getAssignedName('name_container', '')" html_quote>">
<br>
Recommended: <code>self</code>
</td><td align="left" valign="top">
This is the <dtml-with expr="aq_inner.aq_parent">&dtml-meta_type;
"&dtml.missing.html_quote-title_or_id;"</dtml-with>, in which this
script is located. This doesn't change unless you move the script.
If the script is in a ZClass, the Container is the class instance.
</td>
</tr>
<tr><td>&nbsp;</td></tr>
<tr>
<th align="left" valign="top">Context</th>
<td align="left" valign="top"><input type="text" name="name_context"
value="<dtml-var expr="getAssignedName('name_context', '')" html_quote>">
<br>
Recommended: <code>context</code>
</td><td align="left" valign="top">
This is the object on which the script is being called, also known as the
"acquisition parent" of the script. This <em>may</em> be the container, but
varies according to the path through which the script is accessed.
</td>
</tr>
<tr><td>&nbsp;</td></tr>
<tr>
<th align="left" valign="top">Script</th>
<td align="left" valign="top"><input type="text" name="name_m_self"
value="<dtml-var expr="getAssignedName('name_m_self', '')" html_quote>">
<br>
Recommended: <code>m_self</code>
</td><td align="left" valign="top">
This is the object &quot;&dtml-title_or_id;&quot; itself.
</td>
</tr>
<tr><td>&nbsp;</td></tr>
<tr>
<th align="left" valign="top">Namespace</th>
<td align="left" valign="top"><input type="text" name="name_ns"
value="<dtml-var expr="getAssignedName('name_ns', '')" html_quote>">
<br>
Rec: <code>_</code> (an underscore)
</td><td align="left" valign="top">
When the script is called from DTML, this is the caller's DTML namespace,
otherwise it is an empty namespace.
</td>
</tr>
<tr><td>&nbsp;</td></tr>
<tr>
<th align="left" valign="top">Subpath</th>
<td align="left" valign="top" nowrap><input type="text" name="name_subpath"
value="<dtml-var expr="getAssignedName('name_subpath', '')" html_quote>">
<br>
Rec: <code>traverse_subpath</code>
</td><td align="left" valign="top">
When the script is published directly from a URL, this is the
portion of the URL path after the script's name, split at slash separators
into a list of strings. Otherwise, it is an empty list.
</td>
</tr>
<tr>
<td align="left">
<input name="submit" type="submit" value="Change">
</td>
</tr>
</table>
</dtml-with>
</form>
</body>
</html>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html lang="en">
<head>
<title>Try <dtml-var title_or_id></title>
</head>
<body bgcolor="#ffffff" link="#000099" vlink="#555555" alink="#77003b">
<dtml-var manage_tabs>
<h2>Try <dtml-var title_or_id></h2>
<form action="&dtml-URL1;/ZScriptHTML_tryAction" method="POST">
<table cellspacing="2">
<tr><th>Variable</th><th>Value</th></tr>
<dtml-in ZScriptHTML_tryParams>
<tr>
<td align="left" valign="top">
<input name="argvars.name:records" type="text" value="&dtml-sequence-item;">
</td>
<td align="left" valign="top">
<input name="argvars.value:records" type="text">
</td>
</tr>
<dtml-else>
<dtml-raise type="Redirect">&dtml-URL1;</dtml-raise>
</dtml-in>
</table>
<input name="submit" type="submit" value="Go"><br>
</form>
</body>
</html>
1999-12-11 Evan Simpson <evan@4-am.com>
* Tupleizing and UntupleFunction now handle nested function definitions
* Subdirectories reverted to v0.5 bytecodehacks, since the only change
I've ever made was to accidentally change line endings in them to CRLF
1999-09-09 Evan Simpson <evan@4-am.com>
* Tupleizing a function now omits globals
* New UntupleFunction re-applies globals, with automatic initialization
of variables to None, since there's no way to check if they exist.
It also includes $functions, and checks __builtins__ handling.
* Moved all bytecode manipulation into Munge_window class, which uses
op.execute to maintain information about who did what to the stack.
* Added Munger which re-enables creation of dictionary literals.
* Made all Mungers load frequently used functions from the global dict
into the local instead of storing them in co_consts.
* Simplified GuardedOps and turned off test Guard.
* Wrote lots of docstring.
1999-08-28 Evan Simpson <evan@4-am.com>
* Ripped out Fleshy acquisition-style class and added
CycleHandle in an attempt to improve speed.
* code_editor.py: Added "as_tuple" to Function and EditableCode
to provide (hopefully) pickleable representations. Their __init__s
now accept these tuples.
* Added VSExec.py (the point of all this), which provides facilities
for compiling arbitrary blocks of code with heavy restrictions.
1999-06-11 Michael Hudson <mwh21@cam.ac.uk>
* a monumental amount has changed. I haven't been keeping the
ChangeLog up to date, sorry.
1999-05-16 Michael Hudson <mwh21@cam.ac.uk>
* doc/bch.tex: documented macro and macros.
* macros.py: added basic library of macros.
* setq2.py: does same job as setq.py, but uses the new macro
package.
* macro.py It's a macro packages of sorts. Needs documentation.
1999-05-15 Michael Hudson <mwh21@cam.ac.uk>
* inline.py: Substantially rewritten to use find_function_call,
and to support keyword arguments. No varags yet.
* setq.py Added changes written by Christian Tismer (now converts
globals to locals)
1999-05-13 Michael Hudson <mwh21@cam.ac.uk>
* Release 0.11 - cleaned up production of documentation following
advice from the documentation master, Fred L. Drake.
1999-05-12 Michael Hudson <mwh21@cam.ac.uk>
* Release 0.10.
* doc/ There's documentation (gasp)
1999-05-10 Michael Hudson <mwh21@cam.ac.uk>
* inline.py: Python now has inline functions! Bet you never
expected that.
* It's all changing again! Much polish, some docstrings,
everything rewritten to use code_editor that wasn't already, many
style fixes.
1999-05-06 Michael Hudson <mwh21@cam.ac.uk>
* attr_freeze.py: implement an attribute freezer that works.
* xapply2.py: implement xapply for functions (again!) using the
new code editing framework from code_editor.py.
* code_editor.py: That's more like it!
1999-05-04 Michael Hudson <mwh21@cam.ac.uk>
* attr_freeze.py: implements a (buggy) attempt at freezing
attribute references.
* read_code.py,opbases.py,ops.py,write_ops.py,
common.py,__init__.py: Much stuff added/changed. It not pretty or
internally consistent yet. I might bash on it some more some
time. I'm afraid I don't feel like explaining myself properly yet
either.
1999-05-02 Michael Hudson <mwh21@cam.ac.uk>
* README,ChangeLog: Added.
* xapply.py: Added, following prompting by Christian Tismer.
* closure.py: Made improvements suggested by Tim Peters.
SUBDIRS=tests code_gen doc
clean: clean-local clean-recursive
release: version src-release doc-release
clean-local:
$(RM) *.pyc *~ *.pyo
clean-recursive:
for i in $(SUBDIRS); do \
(cd $$i && make clean); \
done
src-release: clean
cd .. && ./mkdist.sh
doc-release: clean
cd doc && make release
Welcome to the bytecodehacks!
There's docmentation in doc/; To build it you need an unpacked python
source distribution somewhere, and for html output you also need
latex2html.
Build docs like this:
$(path to Python source)/Doc/tools/mkhowto --$(format) (--a4) bch.tex
You can get built html docs at
ftp://starship.python.net/pub/crew/mwh/bytecodehacks-doc-$(VERSION).tar.gz.
The bytecodehacks rewrite the bytecode of functions to do unlikely
things in Python.
The package (and that's how it's distributed) splits into two parts -
the byte code editing routines and the "bytecodehacks" that are
usuable without a degree in python arcanery, although one might help
understand some of the consequences.
Some highlights:
bytecodehacks.closure - bind global references to constants
bytecodehacks.xapply - a sort-of lazy apply
bytecodehacks.setq - this one should interest people!
bytecodehacks.inline - Python gets inline functions
bytecodehacks.macro - Python gets semantic (hygenic) macros!
Please note that these modules are not really bullet-proof, more a
proof-of-concept than anything else.
The are also public domain; do what you like with them. If you find
bugs, or more imaginative uses for these techniques, I'd surely like
to know!
Thanks for taking an interest.
'''Safe execution of arbitrary code through bytecode munging
CodeBlock is a base class for bytecode munging of code strings. Derived
classes should override 'forbid' and 'Mungers'. The class constructor
takes a function name, a parameter string, and a function body string. It
combines the name, parameters, and body into a function (indented one
space), compiles it, and then examines its bytecode.
Any bytecode whose opcode is a key in 'forbid' with a true value may not
appear in the compiled code.
Each object in 'Mungers' is called with the code window object (at start)
and the variable name information for the function. Mungers can use this
opportunity to examine the code and variables, and perform setup operations.
The value returned from the call is discarded if false, otherwise it is
assumed to be a collection of bytecode-specific functions.
The code window object is passed over the bytecodes, maintaining a stack
of "responsible" opcodes. At any given position, the size of the stack
should be the same as the size of the run-time stack would be when that
part of the code is executing, and the top of the stack would have been
put there by the "responsible" opcode.
At each position, the mungers are examined to see if they have a function
corresponding to the current opcode. If so, the function is called with
the code window object. The function can then use the code window object to
examine or change the bytecode.
Once all processing is done, and if there have not been any errors, the
compiled, edited function is converted into a pickleable tuple, which is
stored as attribute 't' of the resulting instance. In order for this to
work, mungers must not place unpickleable objects into the list of constants.
In order for unpickling to be robust, they should not place any function
objects there either. If a function must be provided for use in the
bytecode, it should be loaded from the global dictionary, preferably with an
'illegal' name.
'''
import sys
from string import replace, join
from types import FunctionType
import ops, code_editor, new
from closure import bind
class Warning(Exception): pass
class ForbiddenOp(Exception): pass
general_special_globals = {}
class Munge_window:
def __init__(self, code, use_stack):
self.code = code
self.opn = -1
self.use_stack = use_stack
if use_stack:
self.stack = []
self.last_empty_stack = None
def insert_code(self, rcode, at = None):
opn = self.opn
# rcode is passed as a (callables, args) pair
rcode = map(apply, rcode[0], rcode[1])
if at is None:
at = opn
self.code[at:at]=rcode
self.opn = opn + len(rcode)
return rcode
def set_code(self, rct = 1, rcode = ()):
'Replace "rct" bytecodes at current position with "rcode"'
opn = self.opn
# rcode is passed as a (callables, args) pair
if rcode:
rcode = map(apply, rcode[0], rcode[1])
self.code[opn:opn+rct] = rcode
if self.use_stack:
stack = self.stack
for op in rcode:
try:
op.execute(stack)
except:
del stack[:]
if not stack:
self.last_empty_stack = op
self.opn = opn + len(rcode) - 1
def advance(self):
self.opn = opn = self.opn + 1
code = self.code
if opn < len(code):
self.op = code.opcodes[opn]
self.opname = self.op.__class__.__name__
return 1
elif opn == len(code):
# Hack!
self.op = None
self.opname = 'finalize'
return 1
def do_op(self):
if self.use_stack and self.op:
op = self.op
stack = self.stack
try:
op.execute(stack)
except:
del stack[:]
if not stack:
self.last_empty_stack = op
def after_code_for(self, stack_idx):
stack = self.stack
if stack_idx < 0 and len(stack) + stack_idx < 0:
whichop = self.last_empty_stack
if whichop is None:
return 0
else:
whichop = stack[stack_idx]
return self.code.index(whichop)+1
def assert_stack_size(self, size, ctxt):
if self.use_stack and len(self.stack) != size:
raise Warning, ('PythonMethod Bug: %s objects were on the stack '
'%s when only %s should be.' % (len(self.stack), ctxt, size))
def clear_stack(self):
if self.use_stack:
del self.stack[:]
self.last_empty_stack = self.op
class CodeBlock:
'''Compile a string containing Python code, with restrictions'''
forbid = {}
Mungers = ()
globals = {'__builtins__': None}
forbidden = "Forbidden operation %s at line %d"
def __init__(self, src, name='<function>', filename='<string>'):
self.f = None
defns = {'__builtins__': None}
self.warnings, self.errors = [], []
try:
exec src in defns
except SyntaxError:
import traceback
self.errors = traceback.format_exception_only(SyntaxError,
sys.exc_info()[1])
return
for v in defns.values():
if type(v) is FunctionType:
block = v
break
else:
raise SyntaxError, 'string did not define a function'
# Crack open the resulting function and munge it.
f = code_editor.Function(block)
f.func_name = name
f.func_globals = self.globals
f.func_code.set_filename(filename)
self.munge_data = {}
self.munge(f.func_code)
if not self.errors: self.t = f.as_tuple()
def munge(self, fc, depth=0):
# Recurse into nested functions first
for subcode in fc.subcodes:
self.munge(subcode, depth+1)
# Make the current recursion depth accessible
self.depth = depth
code = fc.co_code
warnings, errors = self.warnings, self.errors
# Initialize the Munge objects
mungers = []
window = Munge_window(code, 1)
margs = (self, window, fc)
for M in self.Mungers:
try:
mungers.append(apply(M, margs))
except Exception, e:
errors.append(e.__class__.__name__ + ', ' + str(e))
# Try to collect all initialization errors before failing
if errors:
return
# Mungers which only perform an initial pass should return false
mungers = filter(None, mungers)
line = 0 ; forbid = self.forbid
while window.advance():
op, opname = window.op, window.opname
if isinstance(op, ops.SET_LINENO):
line = op.arg
window.do_op()
elif op and forbid.get(op.op, 0):
errors.append(self.forbidden % (opname, line))
window.do_op() #???
else:
for m in mungers:
handler = getattr(m, opname, None)
if handler:
try:
# Return true to prevent further munging
handled = handler(window)
except ForbiddenOp:
errors.append(self.forbidden % (opname, line))
except Exception, e:
raise
errors.append(e.__class__.__name__ + ', ' + str(e))
else:
if not handled:
continue
break
else:
window.do_op()
def __call__(self, *args, **kargs):
F = code_editor.Function(self.t)
F.func_globals = self.globals
self.__call__ = f = F.make_function()
return apply(f, args, kargs)
def _print_handler(printlist, *txt):
add = printlist.append
if len(txt):
if printlist and printlist[-1:] != ['\n']:
add(' ')
add(str(txt[0]))
else:
add('\n')
def _join_printed(printlist):
return join(printlist, '')
_join_printed = bind(_join_printed, join=join, map=map, str=str)
general_special_globals['$print_handler'] = _print_handler
general_special_globals['$join_printed'] = _join_printed
general_special_globals['$printed'] = None
class Printing:
'''Intercept print statements
Print statements are either converted to no-ops, or replaced with
calls to a handler. The default handler _print_handler appends the
intended output to a list. 'printed' is made into a reserved name
which can only be used to read the result of _join_printed.
_print_handler and _join_printed should be provided in the global
dictionary as $print_handler and $join_printed'''
lfnames = (('$printed',), ('$print_handler',), ('$join_printed',))
print_prep = ( (ops.LOAD_FAST, ops.LOAD_FAST), (lfnames[1], lfnames[0]) )
get_printed = ( (ops.LOAD_FAST, ops.LOAD_FAST, ops.CALL_FUNCTION),
(lfnames[2], lfnames[0], (1,)) )
init_printed = ( (ops.BUILD_LIST, ops.STORE_GLOBAL),
((0,), lfnames[0]) )
make_local = (ops.LOAD_GLOBAL, ops.STORE_FAST)
call_print1 = ( (ops.CALL_FUNCTION, ops.POP_TOP), ((1,), ()) )
call_print2 = ( (ops.CALL_FUNCTION, ops.POP_TOP), ((2,), ()) )
def __init__(self, cb, w, fc):
if 'printed' in fc.co_varnames:
raise SyntaxError, '"printed" is a reserved word.'
self.warnings, self.depth = cb.warnings, cb.depth
self.md = cb.munge_data['Printing'] = cb.munge_data.get('Printing', {})
self.names_used = names_used = {}
names = fc.co_names
if 'printed' in names:
names_used[0] = names.index('printed')
names_used[2] = 1
else:
self.LOAD_GLOBAL = None
def finalize(self, w):
names_used = self.names_used
if names_used:
# Load special names used by the function (and $printed always)
ln = self.lfnames
names_used[0] = 1
for i in names_used.keys():
w.insert_code( (self.make_local, (ln[i], ln[i])), 0)
# Inform higher-level mungers of name usage
self.md.update(names_used)
if self.md and self.depth == 0:
# Initialize the $printed variable in the base function
w.insert_code(self.init_printed, 0)
if not self.md.has_key(2):
self.warnings.append(
"Prints, but never reads 'printed' variable.")
def PRINT_ITEM(self, w):
# Load the printing function before the code for the operand.
w.insert_code(self.print_prep, w.after_code_for(-2))
w.assert_stack_size(1, "at a 'print' statement")
# Instead of printing, call our function and discard the result.
w.set_code(1, self.call_print2)
self.names_used[1] = 1
return 1
def PRINT_NEWLINE(self, w):
w.assert_stack_size(0, "at a 'print' statement")
w.insert_code(self.print_prep)
w.set_code(1, self.call_print1)
self.names_used[1] = 1
return 1
def LOAD_GLOBAL(self, w):
if w.op.arg==self.names_used[0]:
# Construct the print result instead of getting non-existent var.
w.set_code(1, self.get_printed)
return 1
general_special_globals['$loop_watcher'] = lambda: None
class LoopLimits:
'''Try to prevent "excessive" iteration and recursion
Loop ends, 'continue' statements, and function entry points all
call a hook function, which can keep track of iteration and call
depth, and raise an exception to terminate processing.
The hook function should be provided in the global
dictionary as $loop_watcher'''
lwname = ('$loop_watcher',)
notify_watcher = ( (ops.LOAD_FAST, ops.CALL_FUNCTION, ops.POP_TOP),
(lwname, (0,), ()) )
make_local = (ops.LOAD_GLOBAL, ops.STORE_FAST)
def __init__(self, cb, w, fc):
pass
def finalize(self, w):
# Localize the watcher, and call it.
w.insert_code(self.notify_watcher, 0)
w.insert_code( (self.make_local, (self.lwname, self.lwname)), 0)
def JUMP_ABSOLUTE(self, w):
# Call the watcher before looping
w.insert_code(self.notify_watcher)
return 1
def PublicNames(cb, w, fc):
'''Restrict access to all but public names
Forbid use of any multi-character name starting with _
'''
protected = []
for name in fc.co_names:
if name[:1]=='_' and len(name)>1:
protected.append(name)
if protected:
raise SyntaxError, ('Names starting with "_" are not allowed (%s).'
% join(protected, ', '))
class AllowMapBuild:
'''Allow literal mappings to be constructed unmunged
Optimize construction of literal dictionaries, which requires
STORE_SUBSCR, by checking the stack.'''
def __init__(self, cb, w, fc):
pass
def STORE_SUBSCR(self, w):
if isinstance(w.stack[-2], ops.BUILD_MAP):
w.do_op()
return 1
def _get_call(w):
load_guard = ((ops.LOAD_FAST, ops.LOAD_ATTR), (('$guard',), (guard,)))
# Load the binary guard function before its parameters are computed.
iops = w.insert_code(load_guard, w.after_code_for(-3))
# Fix the execution stack to refer to the loaded function.
if w.use_stack: w.stack[-2:-2] = iops[1:]
# Call guard function instead of performing binary op
w.set_code(1, cf2)
return 1
def _SLICE(w):
load_guard = ((ops.LOAD_FAST, ops.LOAD_ATTR, ops.ROT_TWO),
(('$guard',), ('getslice',), ()))
# Load the Slice guard and switch it with the argument.
w.insert_code(load_guard, w.opn+1)
# Call the slice guard after performing the slice.
w.insert_code(cf1, w.opn+4)
return 1
# Code for calling a function with 1 or 2 parameters, respectively
cf1 = ((ops.CALL_FUNCTION,), ((1,),))
cf2 = ((ops.CALL_FUNCTION,), ((2,),))
class _GuardedOps:
def __call__(self, cb, w, fc):
g = self.guard_name
localize_guard = ( (ops.LOAD_GLOBAL, ops.STORE_FAST),
((g,),(g,)) )
# Insert setup code; no need to fix stack
w.insert_code(localize_guard, 0)
return self
def GuardedBinaryOps(guards):
'''Allow operations to be Guarded, by replacing them with guard calls.
Construct a munging object which will replace specific opcodes with
calls to methods of a guard object. The guard object must appear in
the global dictionary as $guard.'''
gops = _GuardedOps()
gops.guard_name = '$guard'
opmap = ( ('mul', 'BINARY_MULTIPLY')
,('div', 'BINARY_DIVIDE')
,('power', 'BINARY_POWER') )
for guard, defname in opmap:
if hasattr(guards, guard):
setattr(gops, defname, bind(_get_call, guard=guard, cf2=cf2))
if hasattr(guards, 'getslice'):
gops.SLICE_3 = gops.SLICE_2 = gops.SLICE_1 = gops.SLICE_0 = bind(_SLICE, cf1=cf1)
return gops
def _wrap(w):
load_guard = ((ops.LOAD_FAST,), ((guard,),))
# Load the guard function before the guarded object, call after.
w.insert_code(load_guard, w.after_code_for(spos - 1))
if spos == 0:
w.set_code(0, cf1)
else:
iops = w.insert_code(cf1, w.after_code_for(spos))
# Fix the execution stack.
if w.use_stack: w.stack[spos] = iops[0]
def _WriteGuardWrapper():
def model_handler(self, *args):
try:
f = getattr(self.__dict__['_ob'], secattr)
except AttributeError:
raise TypeError, error_msg
apply(f, args)
d = {}
for name, error_msg in (
('setitem', 'object does not support item assignment'),
('delitem', 'object does not support item deletion'),
('setattr', 'attribute-less object (assign or del)'),
('delattr', 'attribute-less object (assign or del)'),
('setslice', 'object does not support slice assignment'),
('delslice', 'object does not support slice deletion'),
):
fname = '__%s__' % name
d[fname] = bind(model_handler, fname,
secattr='__guarded_%s__' % name,
error_msg=error_msg)
return new.classobj('Wrapper', (), d)
def _WriteGuard(ob):
if type(ob) in safetypes or getattr(ob, '_guarded_writes', None):
return ob
w = Wrapper()
w.__dict__['_ob'] = ob
return w
WriteGuard = bind(_WriteGuard, safetypes=(type([]), type({})),
Wrapper=_WriteGuardWrapper())
def RedirectWrites():
'''Redirect STORE_* and DELETE_* on objects to methods
Construct a munging object which will wrap all objects which are the
target of a STORE or DELETE op by passing them to a guard function.
The guard function must appear in the global dictionary as $write_guard.'''
gops = _GuardedOps()
gops.guard_name = guard = '$write_guard'
opmap = ( ('STORE_SUBSCR', -2)
,('DELETE_SUBSCR', -2)
,('STORE_ATTR', -1)
,('DELETE_ATTR', -1)
,('STORE_SLICE_0', -1)
,('STORE_SLICE_1', -2)
,('STORE_SLICE_2', -2)
,('STORE_SLICE_3', -3)
,('DELETE_SLICE_0', -1)
,('DELETE_SLICE_1', -2)
,('DELETE_SLICE_2', -2)
,('DELETE_SLICE_3', -3) )
for defname, spos in opmap:
setattr(gops, defname, bind(_wrap, spos=spos, cf2=cf2, guard=guard))
return gops
def _ReadGuard(ob):
if type(ob) in safetypes or hasattr(ob, '_guarded_reads'):
return ob
w = Wrapper()
w.__dict__['_ob'] = ob
return w
ReadGuard = bind(_ReadGuard, safetypes=(type(''),))
def RedirectReads():
'''Redirect LOAD_* on objects to methods
Construct a munging object which will wrap all objects which are the
target of a LOAD op by passing them to a guard function.
The guard function must appear in the global dictionary as $read_guard.'''
gops = _GuardedOps()
gops.guard_name = guard = '$read_guard'
opmap = ( ('BINARY_SUBSCR', -2)
,('LOAD_ATTR', -1)
,('SLICE_0', -1)
,('SLICE_1', -2)
,('SLICE_2', -2)
,('SLICE_3', -3) )
for defname, spos in opmap:
setattr(gops, defname, bind(_wrap, spos=spos, cf2=cf2, guard=guard))
return gops
class SafeBlock(CodeBlock):
forbid = {ops.STORE_NAME.op: 1, ops.DELETE_NAME.op: 1}
for opclass in ops._bytecodes.values():
if forbid.get(opclass.op, 1):
opname = opclass.__name__
if opname[:5] == 'EXEC_':
forbid[opclass.op] = 1
Mungers = [Printing, PublicNames, AllowMapBuild]
def UntupleFunction(t, special_globals, **globals):
import new
globals.update(general_special_globals)
globals.update(special_globals)
globals['global_exists'] = defined = globals.has_key
if not defined('__builtins__'):
globals['__builtins__'] = {}
t = list(t)
# Handle nested functions and lambdas
t_code = t[2]
if len(t_code) == 13:
sub_codes = [t_code]
funstack = [sub_codes]
while funstack:
if len(t_code) == 13:
# This has nested code objects, so make it mutable
sub_codes[0] = t_parent = t_code = list(t_code)
# Put the list of nested codes on the stack for processing
sub_codes = list(t_code.pop())
funstack.append(sub_codes)
else:
# This code tuple is fully processed, so untuple it
func_code = apply(new.code, tuple(t_code))
# Find the first placeholder () in the parent's constants
t_consts = list(t_parent[5])
# Replace the placeholder with the code object
t_consts[t_consts.index(())] = func_code
t_parent[5] = tuple(t_consts)
# Clear it from the stack
del sub_codes[0]
# Get the next code tuple to process
if not sub_codes:
# Back up one level
funstack.pop()
sub_codes = funstack[-1]
if len(funstack) > 1:
t_parent = funstack[-2][0]
else:
funstack = None
t_code = sub_codes[0]
f = new.function(apply(new.code, tuple(t_code)), globals, t[0])
f.func_defaults = t[3] and tuple(t[3])
f.func_doc = t[1]
return f
def test(p, c):
sb = SafeBlock('f', p, c)
print sb.errors, sb.warnings
f = code_editor.Function(sb.t)
for c in f.func_code.co_code:
print c
for subcode in f.func_code.subcodes:
for c in subcode.co_code:
print ' ', c
return sb
if __name__ == '__main__':
sb = test('x', '''\
print x
def plus1(x):
print x+1
plus1(x)
return printed''')
f = UntupleFunction(sb.t, {})
#from dis import dis
#dis(f)
print f(2),
print f(3),
__all__=[
'closure',
'xapply',
'common',
'inline',
'code_editor',
'opbases',
'ops',
'attr_freeze',
'code_gen']
from code_editor import Function
from ops import LOAD_GLOBAL, LOAD_ATTR, LOAD_CONST
def freeze_one_attr(cs,code,attr,value):
looking_for=0
is_global=1
inserted=0
i = 0
while i < len(cs):
op=cs[i]
if is_global:
if op.__class__ is LOAD_GLOBAL:
if code.co_names[op.arg]==attr[looking_for]:
looking_for=looking_for+1
is_global=0
else:
if op.__class__ is LOAD_ATTR \
and code.co_names[op.arg]==attr[looking_for]:
looking_for=looking_for+1
if looking_for == len(attr):
inserted=1
newop=LOAD_CONST(len(code.co_consts))
cs[i-len(attr)+1:i+1]=[newop]
i=i-len(attr)
looking_for=0
is_global=1
else:
looking_for=0
is_global=1
i=i+1
if inserted:
code.co_consts.append(value)
return cs
class Ref:
def __init__(self,name=()):
self.name=name
def __getattr__(self,attr):
return Ref(self.name+(attr,))
def __call__(self):
return self.name
def __repr__(self):
return `self.name`
def freeze_attrs(func,*vars):
func=Function(func)
code=func.func_code
cs=code.co_code
if len(vars)%2 <> 0:
raise TypeError, "wrong number of arguments"
for i in range(0,len(vars),2):
freeze_one_attr(cs,code,vars[i](),vars[i+1])
return func.make_function()
"""\
closure
implements a form of closures by abusing the co_consts field of a code
object.
exports: bind, bind_locals, bind_now
and contains two examples: make_adder, make_balance
"""
from code_editor import Function
from ops import *
def scan_for_STORE(func,name):
for i in func.func_code.co_code:
if i.__class__ in [STORE_FAST,STORE_NAME,STORE_GLOBAL] \
and i.name == name:
return 1
return 0
def bind(function,newname=None,**vars):
"""\
bind(function[,newname],var1=value1,var2=value2,...) -> function
returns a new function (optionally renamed) where every reference to
one of var1, var2, etc is replaced by a reference to the respective
valueN."""
func = Function(function)
code = func.func_code
cs = func.func_code.co_code
name2index = {}
mutated = {}
for name in vars.keys():
mutated[name] = scan_for_STORE(func,name)
if 0 in code.co_consts:
zeroIndex = code.co_consts.index(0)
else:
zeroIndex = len(code.co_consts)
code.co_consts.append(0)
i = 0
while i < len(cs):
op = cs[i]
i = i + 1
# should LOAD_NAME be here??? tricky, I'd say
if op.__class__ in [LOAD_GLOBAL,LOAD_NAME,LOAD_FAST]:
if not vars.has_key(op.name):
continue
if mutated[name]:
if not name2index.has_key(op.name):
name2index[op.name]=len(code.co_consts)
code.co_consts.append([vars[op.name]])
cs[i-1:i] = [LOAD_CONST(name2index[op.name]),
LOAD_CONST(zeroIndex),
BINARY_SUBSCR()]
i = i + 2
else:
if not name2index.has_key(op.name):
name2index[op.name]=len(code.co_consts)
code.co_consts.append(vars[op.name])
cs[i-1] = LOAD_CONST(name2index[op.name])
elif op.__class__ in [STORE_FAST,STORE_NAME,STORE_GLOBAL]:
if not vars.has_key(op.name):
continue
if not mutated[name]:
continue # shouldn't be reached
cs[i-1:i] = [LOAD_CONST(name2index[op.name]),
LOAD_CONST(zeroIndex),
STORE_SUBSCR()]
i = i + 2
if newname is not None:
func.func_name = newname
return func.make_function()
bind=Function(bind)
bind.func_code.co_varnames[0]='$function'
bind.func_code.co_varnames[1]='$newname'
bind=bind.make_function()
def bind_locals(func):
"""bind_locals(func) -> function
returns a new function where every global variable reference in func
is replaced, if possible, by a reference to a local variable in the
callers context."""
try:
raise ""
except:
import sys
frame = sys.exc_traceback.tb_frame.f_back
name = func.func_name+'+'
l = apply(bind,(func,name),frame.f_locals)
frame = None
return l
def bind_now(func):
"""bind_now(func) -> function
returns a new function where every global variable reference in func
is replaced, if possible, by a reference to a variable in the callers
context."""
try:
raise ""
except:
import sys
frame = sys.exc_traceback.tb_frame.f_back
l = apply(bind,(func,),frame.f_locals)
g = apply(bind,(l,),frame.f_globals)
frame = None
return g
## examples
def make_adder(n):
"""make_adder(n) -> function
return a monadic function that adds n to its argument."""
def adder(x):
return x+n
return bind_locals(adder)
def make_balance(initial_amount):
"""make_balance(initial_amount) -> function
demonstrates an object with state, sicp style."""
def withdraw(amount):
if current[0]<amount:
raise "debt!"
else:
current[0]=current[0]-amount
return current[0]
return bind(withdraw,current=[initial_amount])
# the third attempt; maybe it'll work sometime.
# interface I want:
# mc=MutableCode(<code object>)
# behaves like list of opcodes, eg
# len(mc) => number of bytecodes in code
# mc[i] returns some representation of the ith opcode
# mc.assemble() => codestring, or maybe code object.
import types,StringIO,struct,new
import ops
from cyclehandle import CycleHandle
class CodeString(CycleHandle):
def __init__(self,cs=None,bytecodes=None):
self._set_workers(CodeStringWorker(cs, bytecodes))
class CodeStringWorker:
def __init__(self,cs,bytecodes):
self.labels=[]
self.byte2op={}
self.opcodes=[]
if bytecodes is None:
bytecodes = ops._bytecodes
if type(cs) is type(""):
self.disassemble_no_code(cs,bytecodes)
else:
self.disassemble(cs,bytecodes)
def disassemble(self,code,bytecodes):
self.labels = []
self.byte2op = {}
self.opcodes = []
self.code = code
cs=StringIO.StringIO(code.co_code)
i, op, n = 0, 0, len(code.co_code)
while i < n:
self.byte2op[i]=op
byte=cs.read(1)
self.opcodes.append(bytecodes[byte](cs,self))
i = cs.tell()
op = op + 1
del self.code
for label in self.labels:
label.resolve(self)
def disassemble_no_code(self,codestring,bytecodes):
self.labels = []
self.byte2op = {}
self.opcodes = []
cs=StringIO.StringIO(codestring)
i, op, n = 0, 0, len(codestring)
while i < n:
self.byte2op[i]=op
byte=cs.read(1)
self.opcodes.append(bytecodes[byte](cs,self))
i = cs.tell()
op = op + 1
for label in self.labels:
label.resolve(self)
def add_label(self,label):
self.labels.append(label)
def find_labels(self,index):
labelled=self.opcodes[index]
pointees=[]
for l in self.labels:
if l.op == labelled:
pointees.append(l)
return pointees
def __getitem__(self,index):
return self.opcodes[index]
def __setitem__(self,index,value):
# find labels that point to the removed opcode
pointees=self.find_labels(index)
if self.opcodes[index].is_jump():
self.labels.remove(self.opcodes[index].label)
self.opcodes[index]=value
for l in pointees:
l.op=value
if value.is_jump():
self.labels.append(value.label)
def __delitem__(self,index):
# labels that pointed to the deleted item get attached to the
# following insn (unless it's the last insn - in which case I
# don't know what you're playing at, but I'll just point the
# label at what becomes the last insn)
pointees=self.find_labels(index)
if index + 1 == len(self.opcodes):
replacement = self.opcodes[index]
else:
replacement = self.opcodes[index + 1]
for l in pointees:
l.op=replacement
going=self.opcodes[index]
if going.is_jump():
self.labels.remove(going.label)
del self.opcodes[index]
def __getslice__(self,lo,hi):
return self.opcodes[lo:hi]
def __setslice__(self,lo,hi,values):
# things that point into the block being stomped on get
# attached to the start of the new block (if there are labels
# pointing into the block, rather than at its start, a warning
# is printed, 'cause that's a bit dodgy)
pointees=[]
opcodes = self.opcodes
indices=range(len(opcodes))[lo:hi]
for i in indices:
if opcodes[i].is_jump():
self.labels.remove(opcodes[i].label)
p=self.find_labels(i)
if p and i <> lo:
print "What're you playing at?"
pointees.extend(p)
codes = []
for value in values:
if value.is_jump():
self.labels.append(value.label)
codes.append(value)
opcodes[lo:hi]=codes
replacement = opcodes[min(lo, len(opcodes)-1)]
for l in pointees:
l.op = replacement
def __delslice__(self,lo,hi):
self.__setslice__(lo, hi, [])
def __len__(self):
return len(self.opcodes)
def append(self,value):
self.opcodes.append(value)
if value.is_jump():
self.labels.append(value.label)
def insert(self,index,value):
self.opcodes.insert(index,value)
if value.is_jump():
self.labels.append(value.label)
def remove(self,x):
del self[self.opcodes.index(x)]
def index(self,x):
return self.opcodes.index(x)
def assemble(self):
out=StringIO.StringIO()
for i in self:
i.byte=out.tell()
out.write(i.assemble(self))
for l in self.labels:
l.write_refs(out)
out.seek(0)
return out.read()
def set_code(self, code):
self.code = code
class EditableCode(CycleHandle):
def __init__(self,code=None):
self._set_workers(EditableCodeWorker(code))
class EditableCodeWorker:
# bits for co_flags
CO_OPTIMIZED = 0x0001
CO_NEWLOCALS = 0x0002
CO_VARARGS = 0x0004
CO_VARKEYWORDS = 0x0008
AUTO_RATIONALIZE = 0
def __init__(self,code=None):
if code is None:
self.init_defaults()
elif type(code) in (type(()), type([])):
self.init_tuple(code)
else:
self.init_code(code)
self.co_code.set_code(self)
def name_index(self,name):
if name not in self.co_names:
self.co_names.append(name)
return self.co_names.index(name)
def local_index(self,name):
if name not in self.co_varnames:
self.co_varnames.append(name)
return self.co_varnames.index(name)
def rationalize(self):
from rationalize import rationalize
rationalize(self)
def init_defaults(self):
self.co_argcount = 0
self.co_stacksize = 0 # ???
self.co_flags = 0 # ???
self.co_consts = []
self.co_names = []
self.co_varnames = []
self.co_filename = '<edited code>'
self.co_name = 'no name'
self.co_firstlineno = 0
self.co_lnotab = '' # ???
self.co_code = CodeString()
self.subcodes = []
def init_code(self,code):
self.co_argcount = code.co_argcount
self.co_stacksize = code.co_stacksize
self.co_flags = code.co_flags
self.co_consts = consts = list(code.co_consts)
self.co_names = list(code.co_names)
self.co_varnames = list(code.co_varnames)
self.co_filename = code.co_filename
self.co_name = code.co_name
self.co_firstlineno = code.co_firstlineno
self.co_lnotab = code.co_lnotab
self.co_code = CodeString(code)
self.subcodes = subcodes = []
from types import CodeType
for i in range(len(consts)):
if type(consts[i]) == CodeType:
subcodes.append(EditableCode(consts[i]))
consts[i] = ()
def init_tuple(self,tup):
self.subcodes = []
if len(tup)==13:
self.subcodes = map(EditableCode, tup[-1])
tup = tup[:-1]
( self.co_argcount, ignored, self.co_stacksize, self.co_flags
, self.co_code, co_consts, co_names, co_varnames, self.co_filename
, self.co_name, self.co_firstlineno, self.co_lnotab
) = tup
self.co_consts = list(co_consts)
self.co_names = list(co_names)
self.co_varnames = list(co_varnames)
self.co_code = CodeString(self)
def make_code(self):
if self.AUTO_RATIONALIZE:
self.rationalize()
else:
# hack to deal with un-arg-ed names
for op in self.co_code:
if (op.has_name() or op.has_local()) and not hasattr(op, "arg"):
if op.has_name():
op.arg = self.name_index(op.name)
else:
op.arg = self.local_index(op.name)
return apply(new.code, self.as_tuple()[:12])
def set_function(self, function):
self.function = function
def set_name(self, name):
self.co_name = name
def set_filename(self, filename):
self.co_filename = filename
def as_tuple(self):
# the assembling might change co_names or co_varnames - so
# make sure it's done *before* we start gathering them.
bytecode = self.co_code.assemble()
subcodes = []
for subcode in self.subcodes:
subcodes.append(subcode.as_tuple())
return (
self.co_argcount,
len(self.co_varnames),
self.co_stacksize,
self.co_flags,
bytecode,
tuple(self.co_consts),
tuple(self.co_names),
tuple(self.co_varnames),
self.co_filename,
self.co_name,
self.co_firstlineno,
self.co_lnotab,
tuple(subcodes))
class Function(CycleHandle):
def __init__(self,func=None):
self._set_workers(FunctionWorker(func))
class FunctionWorker:
def __init__(self,func):
if func is None:
self.init_defaults()
elif type(func) in (type(()), type([])):
self.init_tuple(func)
else:
self.init_func(func)
self.func_code.set_function(self)
def init_defaults(self):
self.__name = "no name"
self.__doc = None
self.func_code = EditableCode()
self.func_defaults = []
self.func_globals = {} # ???
def init_func(self,func):
self.__name = func.func_name
self.__doc = func.func_doc
self.func_code = EditableCode(func.func_code)
self.func_defaults = func.func_defaults
self.func_globals = func.func_globals
def init_tuple(self,tup):
( self.__name, self.__doc, func_code, self.func_defaults
, self.func_globals
) = tup
self.func_code = EditableCode(func_code)
def __getattr__(self,attr):
# for a function 'f.__name__ is f.func_name'
# so lets hack around to support that...
if attr in ['__name__','func_name']:
return self.__name
if attr in ['__doc__','func_doc']:
return self.__doc
raise AttributeError, attr
def __setattr__(self,attr,value):
if attr in ['__name__','func_name']:
self.__name = value
elif attr in ['__doc__','func_doc']:
self.__doc = value
else:
self.__dict__[attr]=value
def make_function(self):
self.func_code.set_name(self.__name)
newfunc = new.function(
self.func_code.make_code(),
self.func_globals,
self.__name)
if not self.func_defaults:
defs=None
else:
defs=tuple(self.func_defaults)
newfunc.func_defaults = defs
newfunc.func_doc = self.__doc
return newfunc
def __call__(self,*args,**kw):
return apply(self.make_function(),args,kw)
def set_method(self, method):
self.method = method
def as_tuple(self):
self.func_code.set_name(self.__name)
if not self.func_defaults:
defs=None
else:
defs=tuple(self.func_defaults)
return (self.__name, self.__doc, self.func_code.as_tuple(), defs, {})
class InstanceMethod(CycleHandle):
def __init__(self,meth=None):
self._set_workers(InstanceMethodWorker(meth))
class InstanceMethodWorker:
def __init__(self,meth):
if meth is None:
self.init_defaults()
else:
self.init_meth(meth)
self.im_func.set_method(self)
def init_defaults(self):
self.im_class = None
self.im_func = Function()
self.im_self = None
def init_meth(self,meth):
self.im_class = meth.im_class
self.im_func = Function(meth.im_func)
self.im_self = meth.im_self
def make_instance_method(self):
return new.instancemethod(
self.im_func.make_function(),
self.im_self,self.im_class)
class FunctionOrMethod:
def __init__(self,functionormethod):
if type(functionormethod) is types.FunctionType:
self.is_method = 0
self.function = Function(functionormethod)
elif type(functionormethod) is types.UnboundMethodType:
self.is_method = 1
self.method = InstanceMethod(functionormethod)
self.function = self.method.im_func
def make_function_or_method(self):
if self.is_method:
return self.method.make_instance_method()
else:
return self.function.make_function()
# -*- python -*-
STOP_CODE:
pass
POP_TOP:
stack.pop()
ROT_TWO:
stack[-2:]=[stack[-1],stack[-2]]
ROT_THREE:
stack[-3:]=[
stack[-1],
stack[-3],
stack[-2]]
DUP_TOP:
stack.append(stack[-1])
UNARY_POSITIVE:
UNARY_NEGATIVE:
UNARY_NOT:
UNARY_CONVERT:
UNARY_INVERT:
stack[-1:]=[self]
BINARY_POWER:
BINARY_MULTIPLY:
BINARY_DIVIDE:
BINARY_MODULO:
BINARY_ADD:
BINARY_SUBTRACT:
BINARY_SUBSCR:
BINARY_LSHIFT:
BINARY_RSHIFT:
BINARY_AND:
BINARY_XOR:
BINARY_OR:
stack[-2:]=[self]
SLICE_0:
stack[-1:]=[self]
SLICE_1:
SLICE_2:
stack[-2:]=[self]
SLICE_3:
stack[-3:]=[self]
STORE_SLICE_0:
del stack[-2:]
STORE_SLICE_1:
STORE_SLICE_2:
del stack[-3:]
STORE_SLICE_3:
del stack[-4:]
DELETE_SLICE_0:
del stack[-1:]
DELETE_SLICE_1:
DELETE_SLICE_2:
del stack[-2:]
DELETE_SLICE_3:
del stack[-3:]
STORE_SUBSCR:
del stack[-3:]
DELETE_SUBSCR:
del stack[-2:]
PRINT_EXPR:
PRINT_ITEM:
stack.pop()
PRINT_NEWLINE:
pass
BREAK_LOOP:
raise "No jumps here!"
LOAD_LOCALS:
stack.append(self)
RETURN_VALUE:
stack[:] = []
EXEC_STMT:
pass
POP_BLOCK:
pass
END_FINALLY:
pass
BUILD_CLASS:
stack[-3:] = [self]
STORE_NAME:
DELETE_NAME:
stack.pop()
UNPACK_TUPLE:
UNPACK_LIST:
stack.append([self] * self.arg)
STORE_ATTR:
DELETE_ATTR:
STORE_GLOBAL:
DELETE_GLOBAL:
stack.pop()
LOAD_CONST:
LOAD_NAME:
stack.append(self)
BUILD_TUPLE:
BUILD_LIST:
if self.arg>0:
stack[-self.arg:]=[self]
else:
stack.append(self)
BUILD_MAP:
stack.append(self)
LOAD_ATTR:
stack[-1] = self
COMPARE_OP:
stack[-2:]=[self] # ????
IMPORT_NAME:
stack.append(self)
IMPORT_FROM:
pass
JUMP_FORWARD:
JUMP_IF_TRUE:
JUMP_IF_FALSE:
JUMP_ABSOLUTE:
raise "jumps not handled here!"
FOR_LOOP:
raise "loop alert"
LOAD_GLOBAL:
stack.append(self)
SETUP_LOOP:
raise "loop alert!"
SETUP_EXCEPT:
SETUP_FINALLY:
pass # ??
LOAD_FAST:
stack.append(self)
STORE_FAST:
DELETE_FAST:
stack.pop()
SET_LINENO:
pass
RAISE_VARARGS:
raise "Exception!"
CALL_FUNCTION:
num_keyword_args=self.arg>>8
num_regular_args=self.arg&0xFF
stack[-2*num_keyword_args-num_regular_args-1:]=[self]
MAKE_FUNCTION:
stack[-self.arg-1:]=[self]
BUILD_SLICE:
stack[-self.arg:]=[self]
import os,string
file=open(os.path.join(os.path.dirname(__file__ ),'op_execute_methods'),'r')
lines=string.split(file.read(),'\n')[1:]
exec_funcs={}
n=len(lines)
for i in range(n):
if (not lines[i]) or lines[i][0]==' ':
continue
j=i
body=[]
while j<n:
if lines[j][0]==' ':
while lines[j] and lines[j][0]==' ':
body.append(lines[j])
j=j+1
break
j=j+1
body=' '+string.join(body,'\n ')
exec_funcs[lines[i][:-1]]=body
#!/usr/local/bin/python
from bytecodehacks.code_gen import write_ops
write_ops.Main()
import dis,re,sys,os,string
from bytecodehacks.code_gen import opexfuncread
temphead="""\
# this file is autogenerated by running
# from bytecodehacks.code_gen import write_ops
# write_ops.Main()
from bytecodehacks import opbases
from bytecodehacks.label import Label
_opbases = opbases
_Label = Label
del Label
del opbases
_bytecodes={}
"""
noargtemplate="""\
class %(name)s(_opbases.%(base)s):
op = %(index)d
opc = '\\%(index)03o'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.%(base)s.__init__(self,cs,code)
def execute(self,stack):
%(exec_body)s
_bytecodes[%(name)s.opc]=%(name)s
"""
argtemplate="""\
class %(name)s(_opbases.%(base)s):
op = %(index)d
opc = '\\%(index)03o'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.%(base)s.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
%(exec_body)s
_bytecodes[%(name)s.opc]=%(name)s
"""
jumptemplate="""\
class %(name)s(_opbases.%(base)s):
op = %(index)d
opc = '\\%(index)03o'
def __init__(self,csorarg=None,code=None):
if csorarg is not None:
if code is not None:
_opbases.%(base)s.__init__(self,csorarg,code)
else:
self.label = _Label()
self.user_init(csorarg)
else:
self.label = _Label()
def execute(self,stack):
%(exec_body)s
_bytecodes[%(name)s.opc]=%(name)s
"""
idprog=re.compile('^[_a-zA-Z][_a-zA-Z0-9]*$')
notopprog=re.compile('^<[0-9]+>$')
def main(file=sys.stdout):
file.write(temphead)
trans=string.maketrans('+','_')
for index in range(len(dis.opname)):
name=string.translate(dis.opname[index],trans)
if notopprog.match(name):
continue
if not idprog.match(name):
name="Opcode_%d"%index
s = "generating %s ..."%name
pad = " " * (30-len(s))
print s,pad,
base="GenericOneByteCode"
if index < dis.HAVE_ARGUMENT:
template = noargtemplate
base="GenericOneByteCode"
elif index in dis.hasjrel:
template = jumptemplate
base="JRel"
elif index in dis.hasjabs:
template = jumptemplate
base="JAbs"
elif index in dis.hasname:
template = argtemplate
base="NameOpcode"
elif index in dis.haslocal:
template = argtemplate
base="LocalOpcode"
else:
template = argtemplate
base="GenericThreeByteCode"
exec_body=opexfuncread.exec_funcs[name]
file.write(template%locals())
print "done"
def Main():
from bytecodehacks import __init__
main(open(os.path.join(os.path.dirname(__init__.__file__),'ops.py'),'w'))
import new
def copy_code_with_changes(codeobject,
argcount=None,
nlocals=None,
stacksize=None,
flags=None,
code=None,
consts=None,
names=None,
varnames=None,
filename=None,
name=None,
firstlineno=None,
lnotab=None):
if argcount is None: argcount = codeobject.co_argcount
if nlocals is None: nlocals = codeobject.co_nlocals
if stacksize is None: stacksize = codeobject.co_stacksize
if flags is None: flags = codeobject.co_flags
if code is None: code = codeobject.co_code
if consts is None: consts = codeobject.co_consts
if names is None: names = codeobject.co_names
if varnames is None: varnames = codeobject.co_varnames
if filename is None: filename = codeobject.co_filename
if name is None: name = codeobject.co_name
if firstlineno is None: firstlineno = codeobject.co_firstlineno
if lnotab is None: lnotab = codeobject.co_lnotab
return new.code(argcount,
nlocals,
stacksize,
flags,
code,
consts,
names,
varnames,
filename,
name,
firstlineno,
lnotab)
code_attrs=['argcount',
'nlocals',
'stacksize',
'flags',
'code',
'consts',
'names',
'varnames',
'filename',
'name',
'firstlineno',
'lnotab']
class CycleHandle:
'''CycleHandles are proxies for cycle roots
A CycleHandle subclass should create one or more workers, and pass them
to _set_workers. These workers can then participate in cycles, as long
as deleting all of the worker's attributes will break the cycle. When a
CycleHandle instance goes away, it deletes all attributes of all of
its workers. You could also explicitly call drop_workers.
For example,
>>> class Ham:
... def __del__(self):
... print 'A ham has died!'
...
>>> ct = CycleHandle()
>>> ct._set_workers(Ham(), Ham())
>>> ct._workers[0].ham2 = ct._workers[1]
>>> ct._workers[1].ham1 = ct._workers[0]
>>> del ct
A ham has died!
A ham has died!
'''
_workers = ()
def _set_workers(self, *workers):
self.__dict__['_workers'] = workers
def _not_mutable(self, *x):
raise TypeError, 'CycleHandle is not mutable'
__delattr__ = _not_mutable
def __setattr__(self, attr, val):
for worker in self._workers:
if hasattr(worker, '__setattr__'):
return getattr(worker, '__setattr__')(attr, val)
_not_mutable_defs = ('__delslice__', '__setslice__', '__delitem__',
'__setitem__')
def __getattr__(self, attr):
for worker in self._workers:
if hasattr(worker, attr):
return getattr(worker, attr)
if attr in self._not_mutable_defs:
return self._not_mutable
raise AttributeError, attr
def _drop_workers(self):
for worker in self._workers:
worker.__dict__.clear()
self.__dict__['_workers'] = ()
def __del__(self, drop_workers=_drop_workers):
drop_workers(self)
def _test():
import doctest, cyclehandle
return doctest.testmod(cyclehandle)
if __name__ == "__main__":
_test()
from code_editor import Function
from ops import *
import dis,new,string
PRECONDITIONS = 1
POSTCONDITIONS = 2
INVARIANTS = 4
EVERYTHING = PRECONDITIONS|POSTCONDITIONS|INVARIANTS
if __debug__:
__strength__ = PRECONDITIONS|POSTCONDITIONS
else:
__strength__ = 0
# TODO: docs, sort out inheritance.
if __debug__:
def add_contracts(target_class,contract_class,strength=None):
if strength is None:
strength = __strength__
newmethods = {}
contractmethods = contract_class.__dict__
if strength & INVARIANTS:
inv = contractmethods.get("class_invariants",None)
for name, meth in target_class.__dict__.items():
if strength & PRECONDITIONS:
pre = contractmethods.get("pre_"+name,None)
if pre is not None:
meth = add_precondition(meth,pre)
if strength & POSTCONDITIONS:
post = contractmethods.get("post_"+name,None)
if post is not None:
meth = add_postcondition(meth,post)
if (strength & INVARIANTS) and inv \
and type(meth) is type(add_contracts):
if name <> '__init__':
meth = add_precondition(meth,inv)
meth = add_postcondition(meth,inv)
newmethods[name] = meth
return new.classobj(target_class.__name__,
target_class.__bases__,
newmethods)
def add_precondition(meth,cond):
meth = Function(meth)
cond = Function(cond)
mcs = meth.func_code.co_code
ccs = cond.func_code.co_code
nlocals = len(meth.func_code.co_varnames)
nconsts = len(meth.func_code.co_consts)
nnames = len(meth.func_code.co_names)
nargs = meth.func_code.co_argcount
retops = []
for op in ccs:
if op.__class__ is RETURN_VALUE:
# RETURN_VALUEs have to be replaced by JUMP_FORWARDs
newop = JUMP_FORWARD()
ccs[ccs.index(op)] = newop
retops.append(newop)
elif op.op in dis.hasname:
op.arg = op.arg + nnames
elif op.op in dis.haslocal:
if op.arg >= nargs:
op.arg = op.arg + nlocals
elif op.op in dis.hasconst:
op.arg = op.arg + nconsts
new = POP_TOP()
mcs.insert(0,new)
mcs[0:0] = ccs.opcodes
for op in retops:
op.label.op = new
meth.func_code.co_consts.extend(cond.func_code.co_consts)
meth.func_code.co_varnames.extend(cond.func_code.co_varnames)
meth.func_code.co_names.extend(cond.func_code.co_names)
return meth.make_function()
def add_postcondition(meth,cond):
""" a bit of a monster! """
meth = Function(meth)
cond = Function(cond)
mcode = meth.func_code
ccode = cond.func_code
mcs = mcode.co_code
ccs = ccode.co_code
nlocals = len(mcode.co_varnames)
nconsts = len(mcode.co_consts)
nnames = len(mcode.co_names)
nargs = ccode.co_argcount
cretops = []
Result_index = len(meth.func_code.co_varnames)
mcode.co_varnames.append('Result')
old_refs = find_old_refs(cond)
for op in ccs:
if op.__class__ is RETURN_VALUE:
newop = JUMP_FORWARD()
ccs[ccs.index(op)] = newop
cretops.append(newop)
elif op.op in dis.hasname:
if cond.func_code.co_names[op.arg] == 'Result' \
and op.__class__ is LOAD_GLOBAL:
ccs[ccs.index(op)] = LOAD_FAST(Result_index)
else:
op.arg = op.arg + nnames
elif op.op in dis.haslocal:
if op.arg >= nargs:
op.arg = op.arg + nlocals + 1 # + 1 for Result
elif op.op in dis.hasconst:
op.arg = op.arg + nconsts
# lets generate the prologue code to save values for `Old'
# references and point the LOAD_FASTs inserted by
# find_old_refs to the right locations.
prologue = []
for ref, load_op in old_refs:
if ref[0] in mcode.co_varnames:
prologue.append(LOAD_FAST(mcode.co_varnames.index(ref[0])))
else:
prologue.append(LOAD_GLOBAL(mcode.name_index(ref[0])))
for name in ref[1:]:
prologue.append(LOAD_ATTR(mcode.name_index(name)))
lname = string.join(ref,'.')
lindex = len(mcode.co_varnames)
mcode.co_varnames.append(lname)
prologue.append(STORE_FAST(lindex))
load_op.arg = lindex
mcs[0:0] = prologue
mretops = []
for op in mcs:
if op.__class__ is RETURN_VALUE:
newop = JUMP_FORWARD()
mcs[mcs.index(op)] = newop
mretops.append(newop)
n = len(mcs)
# insert the condition code
mcs[n:n] = ccs.opcodes
# store the returned value in Result
store_result = STORE_FAST(Result_index)
mcs.insert(n, store_result)
# target the returns in the method to this store
for op in mretops:
op.label.op = store_result
# the post condition will leave a value on the stack; lose it.
# could just strip off the LOAD_CONST & RETURN_VALLUE at the
# end of the function and scan for RETURN_VALUES in the
# postcondition as a postcondition shouldn't be returning
# things (certainly not other than None).
new = POP_TOP()
mcs.append(new)
# redirect returns in the condition to the POP_TOP just
# inserted...
for op in cretops:
op.label.op = new
# actually return Result...
mcs.append(LOAD_FAST(Result_index))
mcs.append(RETURN_VALUE())
# and add the new constants and names (to avoid core dumps!)
mcode.co_consts .extend(ccode.co_consts )
mcode.co_varnames.extend(ccode.co_varnames)
mcode.co_names .extend(ccode.co_names )
return meth.make_function()
def find_old_refs(func):
chaining = 0
refs = []
ref = []
code = func.func_code
cs = code.co_code
i = 0
while i < len(cs):
op=cs[i]
if not chaining:
if op.__class__ is LOAD_GLOBAL:
if code.co_names[op.arg]=='Old':
chaining=1
else:
if op.__class__ is LOAD_ATTR:
ref.append(code.co_names[op.arg])
else:
newop = LOAD_FAST(0)
cs[i-len(ref)-1:i] = [newop]
i = i - len(ref)
refs.append((ref,newop))
ref = []
chaining = 0
i=i+1
return refs
else: # if not __debug__
def add_contracts(target_class,contracts_class):
return target_class
# example
class Uncontracted:
def __init__(self,x,y):
self.x=x
self.y=y
def do(self):
# self.x = self.x + 1 # sneaky!
return self.x/self.y
class Contracts:
def pre___init__(self,x,y):
assert y <> 0
def post_do(self):
assert Old.self.x == self.x
assert Old.self.y == self.y
assert Result > 0, "Result was %s"%`Result`
def class_invariants(self):
assert self.x > 0
Contracted = add_contracts(Uncontracted,Contracts)
from code_editor import Function
from ops import *
def find_function_call(infunc,calledfuncname, allowkeywords=0, startindex=0):
i = startindex
code = infunc.func_code
cs = code.co_code
def match(op,name = calledfuncname):
return getattr(op,'name',None) == name
while i < len(cs):
op = code.co_code[i]
if match(op):
try:
if allowkeywords:
return simulate_stack_with_keywords(code,i)
else:
return simulate_stack(code,i)
except:
i = i + 1
i = i + 1
if allowkeywords:
return None,0
else:
return None
def call_stack_length_usage(arg):
num_keyword_args=arg>>8
num_regular_args=arg&0xFF
return 2*num_keyword_args + num_regular_args
def simulate_stack(code,index_start):
stack = []
cs = code.co_code
i, n = index_start, len(cs)
while i < n:
op = cs[i]
if op.__class__ is CALL_FUNCTION and op.arg+1==len(stack):
stack.append(op)
return stack
elif op.is_jump():
i = cs.index(op.label.op)+1
else:
op.execute(stack)
i = i + 1
raise "no call found!"
def simulate_stack_with_keywords(code,index_start):
stack = []
cs = code.co_code
i, n = index_start, len(cs)
while i < n:
op = cs[i]
if op.__class__ is CALL_FUNCTION \
and call_stack_length_usage(op.arg)+1==len(stack):
stack.append(op)
return stack, op.arg>>8
elif op.is_jump():
i = cs.index(op.label.op)+1
else:
op.execute(stack)
i = i + 1
raise "no call found!"
from code_editor import Function
from ops import *
from find_function_call import find_function_call
def iifize(func):
func = Function(func)
cs = func.func_code.co_code
while 1:
stack = find_function_call(func,"iif")
if stack is None:
break
load, test, consequent, alternative, call = stack
cs.remove(load)
jump1 = JUMP_IF_FALSE(alternative)
cs.insert(cs.index(test)+1,jump1)
jump2 = JUMP_FORWARD(call)
cs.insert(cs.index(consequent)+1,jump2)
cs.remove(call)
cs = None
return func.make_function()
import dis
from code_editor import Function
from find_function_call import find_function_call
from ops import \
LOAD_GLOBAL, RETURN_VALUE, SET_LINENO, CALL_FUNCTION, \
JUMP_FORWARD, STORE_FAST
INLINE_MAX_DEPTH = 100
def inline(func, **funcs):
func = Function(func)
code = func.func_code
for name, function in funcs.items():
count = inline1(func, name, function)
if count <> 0:
fcode=function.func_code
code.co_consts=code.co_consts+list(fcode.co_consts)
code.co_varnames=code.co_varnames+list(fcode.co_varnames)
code.co_names=code.co_names+list(fcode.co_names)
code.co_stacksize=code.co_stacksize+fcode.co_stacksize
return func.make_function()
def munge_code(function,code):
f = Function(function)
fcs = f.func_code.co_code
i, n = 0, len(fcs)
retops = []
while i < n:
op = fcs[i]
if op.__class__ is RETURN_VALUE:
# RETURN_VALUEs have to be replaced by JUMP_FORWARDs
newop = JUMP_FORWARD()
fcs[i] = newop
retops.append(newop)
elif op.op in dis.hasname:
op.arg = op.arg + len(code.co_names)
elif op.op in dis.haslocal:
op.arg = op.arg + len(code.co_varnames)
elif op.op in dis.hasconst:
op.arg = op.arg + len(code.co_consts)
# should we hack out SET_LINENOs? doesn't seem worth it.
i = i + 1
return fcs.opcodes, retops
def inline1(func,funcname,function):
code = func.func_code
cs = code.co_code
count = 0
defaults_added = 0
while count < INLINE_MAX_DEPTH:
stack, numkeywords = find_function_call(func,funcname,allowkeywords=1)
if stack is None:
return count
count = count + 1
load_func, posargs, kwargs, function_call = \
stack[0], stack[1:-2*numkeywords-1], stack[-2*numkeywords-1:-1], stack[-1]
kw={}
for i in range(0,len(kwargs),2):
name = code.co_consts[kwargs[i].arg]
valuesrc = kwargs[i+1]
kw[name] = valuesrc
varnames = list(function.func_code.co_varnames)
for i in kw.keys():
if i in varnames:
if varnames.index(i) < len(posargs):
raise TypeError, "keyword parameter redefined"
else:
raise TypeError, "unexpected keyword argument: %s"%i
# no varargs yet!
# flags = function.func_code.co_flags
# varargs = flags & (1<<2)
# varkeys = flags & (1<<3)
args_got = len(kw) + len(posargs)
args_expected = function.func_code.co_argcount
if args_got > args_expected:
raise TypeError,"too many arguments; expected %d, got %d"%(ac,len(lf) + len(posargs))
elif args_got < args_expected:
# default args?
raise TypeError,"not enough arguments; expected %d, got %d"%(ac,len(lf) + len(posargs))
cs.remove(load_func)
local_index = len(code.co_varnames)
for insn in posargs:
new = STORE_FAST(local_index)
cs.insert(cs.index(insn)+1,new)
labels = cs.find_labels(cs.index(new)+1)
for label in labels:
label.op = new
local_index = local_index + 1
for name, insn in kw.items():
new = STORE_FAST(varnames.index(name) + len(code.co_varnames))
cs.insert(cs.index(insn)+1,new)
labels = cs.find_labels(cs.index(new)+1)
for label in labels:
label.op = new
newops, retops = munge_code(function,code)
call_index = cs.index(function_call)
nextop = cs[call_index + 1]
cs[call_index:call_index + 1] = newops
for op in retops:
op.label.op = nextop
raise RuntimeError, "are we trying to inline a recursive function here?"
import struct
class Label:
def __init__(self,byte=None):
self.byte=byte
self.__op=None
self.absrefs=[]
self.relrefs=[]
def resolve(self,code):
self.__op=code.opcodes[code.byte2op[self.byte]]
def add_absref(self,byte):
# request that the absolute address of self.op be written to
# the argument of the opcode starting at byte in the
# codestring
self.absrefs.append(byte)
def add_relref(self,byte):
# request that the relative address of self.op be written to
# the argument of the opcode starting at byte in the
# codestring
self.relrefs.append(byte)
def __setattr__(self,attr,value):
if attr == 'op':
self.__op = value
else:
self.__dict__[attr] = value
def __getattr__(self,attr):
if attr == 'op':
return self.__op
else:
raise AttributeError, attr
def write_refs(self,cs):
address=self.__op.byte
for byte in self.absrefs:
cs.seek(byte+1)
cs.write(struct.pack('<h',address))
for byte in self.relrefs:
offset=address-byte-3
cs.seek(byte+1)
cs.write(struct.pack('<h',offset))
import dis
from code_editor import Function
from find_function_call import find_function_call
from ops import *
MAX_MACRO_DEPTH = 100
_macros = {}
def add_macro(arg1,arg2=None):
if arg2 is None:
_macros[arg1.func_name] = arg1
else:
_macros[arg1]=arg2
def expand(func, macros = None):
func = Function(func)
code = func.func_code
if macros is None:
macros = _macros
insertions = {}
trips = 0
while trips < MAX_MACRO_DEPTH:
outercount = 0
for name,macro in macros.items():
count = expand1(func, name, macro)
outercount = outercount + count
if count <> 0 and not insertions.has_key(macro):
fcode=macro.func_code
code.co_consts=code.co_consts+list(fcode.co_consts)
code.co_varnames=code.co_varnames+list(fcode.co_varnames)
code.co_names=code.co_names+list(fcode.co_names)
code.co_stacksize=code.co_stacksize+fcode.co_stacksize
insertions[macro] = 0
if not outercount:
return func.make_function()
trips = trips + 1
raise RuntimeError, "unbounded recursion?!"
def expand_these(func,**macros):
return expand(func,macros)
def remove_epilogue(cs):
try:
last,butone,buttwo = cs[-3:]
except:
return
if last.__class__ is buttwo.__class__ is RETURN_VALUE:
if butone.__class__ is LOAD_CONST:
if cs.code.co_consts[butone.arg] is None:
if not (cs.find_labels(-1) or cs.find_labels(-2)):
del cs[-2:]
def munge_code(function,code,imported_locals):
f = Function(function)
fcs = f.func_code.co_code
if fcs[0].__class__ is SET_LINENO:
del fcs[1:1 + 2*len(imported_locals)]
else:
del fcs[0:2*len(imported_locals)]
# a nicety: let's see if the last couple of opcodes are necessary
# (Python _always_ adds a LOAD_CONST None, RETURN_VALUE to the end
# of a function, and I'd like to get rid of that if we can).
remove_epilogue(fcs)
i, n = 0, len(fcs)
retops = []
while i < n:
op = fcs[i]
if op.__class__ is RETURN_VALUE:
# RETURN_VALUEs have to be replaced by JUMP_FORWARDs
newop = JUMP_FORWARD()
fcs[i] = newop
retops.append(newop)
elif op.op in dis.hasname:
op.arg = op.arg + len(code.co_names)
elif op.op in dis.haslocal:
localname = f.func_code.co_varnames[op.arg]
op.arg = imported_locals.get(localname,op.arg + len(code.co_varnames))
elif op.op in dis.hasconst:
op.arg = op.arg + len(code.co_consts)
# should we hack out SET_LINENOs? doesn't seem worth it.
i = i + 1
return fcs.opcodes, retops
def expand1(func,name,macro):
code = func.func_code
cs = code.co_code
count = 0
macrocode = macro.func_code
while count < MAX_MACRO_DEPTH:
stack = find_function_call(func,name)
if stack is None:
return count
count = count + 1
load_func, args, function_call = \
stack[0], stack[1:-1], stack[-1]
args_got = len(args)
args_expected = macrocode.co_argcount
if args_got > args_expected:
raise TypeError,"too many arguments; expected %d, got %d"%(args_expected,args_got)
elif args_got < args_expected:
# default args?
raise TypeError,"not enough arguments; expected %d, got %d"%(args_expected,args_got)
cs.remove(load_func)
arg_names = macrocode.co_varnames[:macrocode.co_argcount]
import_args = []
normal_args = []
for i in range(len(arg_names)):
if arg_names[i][0] == '.':
import_args.append(args[i])
else:
normal_args.append(args[i])
imported_locals = {}
for insn in import_args:
cs.remove(insn)
if insn.__class__ is LOAD_GLOBAL:
name = code.co_names[insn.arg]
var = global_to_local(code, name)
elif insn.__class__ is not LOAD_FAST:
raise TypeError, "imported arg must be local"
else:
var = insn.arg
argindex = macrocode.co_argcount + import_args.index(insn)
argname = macrocode.co_varnames[argindex]
imported_locals[argname] = var
local_index = len(code.co_varnames)
for insn in normal_args:
new = STORE_FAST(local_index + args.index(insn))
cs.insert(cs.index(insn)+1,new)
labels = cs.find_labels(cs.index(new)+1)
for label in labels:
label.op = new
newops, retops = munge_code(macro,code,imported_locals)
call_index = cs.index(function_call)
nextop = cs[call_index + 1]
cs[call_index:call_index + 1] = newops
for op in retops:
if cs.index(nextop) - cs.index(op) == 1:
cs.remove(op)
else:
op.label.op = nextop
raise RuntimeError, "are we trying to expand a recursive macro here?"
def global_to_local(code, name):
"""\
internal function to make a global variable into
a local one, for the case that setq is the first
reference to a variable.
Modifies a code object in-place.
Return value is index into variable table
"""
cs = code.co_code
index = len(code.co_varnames)
code.co_varnames.append(name)
for i in range(len(cs)):
op = cs[i]
if op.__class__ not in [LOAD_GLOBAL,STORE_GLOBAL]:
continue
thisname = code.co_names[op.arg]
if thisname <> name:
continue
if op.__class__ is LOAD_GLOBAL:
cs[i] = LOAD_FAST(index)
else:
cs[i] = STORE_FAST(index)
return index
from macro import add_macro
def main():
def setq((x),v):
x = v
return v
add_macro(setq)
def pre_incr((x)):
x = x + 1
return x
add_macro(pre_incr)
def post_incr((x)):
t = x
x = x + 1
return t
add_macro(post_incr)
def pre_decr((x)):
x = x - 1
return x
add_macro(pre_decr)
def post_decr((x)):
t = x
x = x + 1
return t
add_macro(post_decr)
def add_set((x),v):
x = x + v
return x
add_macro(add_set)
def sub_set((x),v):
x = x - v
return x
add_macro(sub_set)
def mul_set((x),v):
x = x * v
return x
add_macro(mul_set)
def div_set((x),v):
x = x / v
return x
add_macro(div_set)
def mod_set((x),v):
x = x % v
return x
add_macro(mod_set)
main()
def test():
from macro import expand
def f(x):
i = 0
while pre_incr(i) < len(x):
if setq(c, x[i]) == 3:
print c, 42
x = expand(f)
return x
x(range(10))
import struct,dis,new
from label import Label
class ByteCode:
pass
class GenericOneByteCode(ByteCode):
def __init__(self,cs,code):
pass
def __repr__(self):
return self.__class__.__name__
def assemble(self,code):
return self.opc
def is_jump(self):
return 0
def has_name(self):
return 0
def has_name_or_local(self):
return self.has_name() or self.has_local()
def has_local(self):
return 0
class GenericThreeByteCode(GenericOneByteCode):
def __init__(self,cs,code):
GenericOneByteCode.__init__(self,cs,code)
arg=cs.read(2)
self.arg=struct.unpack('<h',arg)[0]
def __repr__(self):
return "%s %d"%(self.__class__.__name__, self.arg)
def assemble(self,code):
return self.opc+struct.pack('<h',self.arg)
def user_init(self,arg):
self.arg = arg
class Jump(GenericThreeByteCode):
def __repr__(self):
return "%s %s"%(self.__class__.__name__, `self.label`)
def is_jump(self):
return 1
def user_init(self,arg):
self.label.op = arg
class JRel(Jump):
def __init__(self,cs,code):
GenericThreeByteCode.__init__(self,cs,code)
self.label = Label(cs.tell()+self.arg)
code.add_label(self.label)
def assemble(self,code):
self.label.add_relref(self.byte)
return self.opc+'\000\000'
class JAbs(Jump):
def __init__(self,cs,code):
GenericThreeByteCode.__init__(self,cs,code)
self.label = Label(self.arg)
code.add_label(self.label)
def assemble(self,code):
self.label.add_absref(self.byte)
return self.opc+'\000\000'
class _NamedOpcode(GenericThreeByteCode):
def user_init(self,arg):
if type(arg) == type(1):
self.arg = arg
else:
self.name = arg
def __repr__(self):
if hasattr(self,"name"):
return "%s : %s"%(self.__class__.__name__,self.name)
else:
return "%s : %d"%(self.__class__.__name__,self.arg)
class NameOpcode(_NamedOpcode):
def __init__(self,cs,code):
GenericThreeByteCode.__init__(self,cs,code)
self.name = code.code.co_names[self.arg]
def has_name(self):
return 1
def assemble(self,code):
if hasattr(self,"name") and not hasattr(self,"arg"):
self.arg = code.code.name_index(self.name)
return GenericThreeByteCode.assemble(self,code)
class LocalOpcode(_NamedOpcode):
def __init__(self,cs,code):
GenericThreeByteCode.__init__(self,cs,code)
self.name = code.code.co_varnames[self.arg]
def has_local(self):
return 1
def assemble(self,code):
if hasattr(self,"name") and not hasattr(self,"arg"):
self.arg = code.code.local_index(self.name)
return GenericThreeByteCode.assemble(self,code)
# this file is autogenerated by running
# from bytecodehacks.code_gen import write_ops
# write_ops.Main()
import opbases
from label import Label
_opbases = opbases
_Label = Label
del Label
del opbases
_bytecodes={}
class STOP_CODE(_opbases.GenericOneByteCode):
op = 0
opc = '\000'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
pass
_bytecodes[STOP_CODE.opc]=STOP_CODE
class POP_TOP(_opbases.GenericOneByteCode):
op = 1
opc = '\001'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack.pop()
_bytecodes[POP_TOP.opc]=POP_TOP
class ROT_TWO(_opbases.GenericOneByteCode):
op = 2
opc = '\002'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[stack[-1],stack[-2]]
_bytecodes[ROT_TWO.opc]=ROT_TWO
class ROT_THREE(_opbases.GenericOneByteCode):
op = 3
opc = '\003'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-3:]=[
stack[-1],
stack[-3],
stack[-2]]
_bytecodes[ROT_THREE.opc]=ROT_THREE
class DUP_TOP(_opbases.GenericOneByteCode):
op = 4
opc = '\004'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack.append(stack[-1])
_bytecodes[DUP_TOP.opc]=DUP_TOP
class UNARY_POSITIVE(_opbases.GenericOneByteCode):
op = 10
opc = '\012'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-1:]=[self]
_bytecodes[UNARY_POSITIVE.opc]=UNARY_POSITIVE
class UNARY_NEGATIVE(_opbases.GenericOneByteCode):
op = 11
opc = '\013'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-1:]=[self]
_bytecodes[UNARY_NEGATIVE.opc]=UNARY_NEGATIVE
class UNARY_NOT(_opbases.GenericOneByteCode):
op = 12
opc = '\014'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-1:]=[self]
_bytecodes[UNARY_NOT.opc]=UNARY_NOT
class UNARY_CONVERT(_opbases.GenericOneByteCode):
op = 13
opc = '\015'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-1:]=[self]
_bytecodes[UNARY_CONVERT.opc]=UNARY_CONVERT
class UNARY_INVERT(_opbases.GenericOneByteCode):
op = 15
opc = '\017'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-1:]=[self]
_bytecodes[UNARY_INVERT.opc]=UNARY_INVERT
class BINARY_POWER(_opbases.GenericOneByteCode):
op = 19
opc = '\023'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_POWER.opc]=BINARY_POWER
class BINARY_MULTIPLY(_opbases.GenericOneByteCode):
op = 20
opc = '\024'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_MULTIPLY.opc]=BINARY_MULTIPLY
class BINARY_DIVIDE(_opbases.GenericOneByteCode):
op = 21
opc = '\025'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_DIVIDE.opc]=BINARY_DIVIDE
class BINARY_MODULO(_opbases.GenericOneByteCode):
op = 22
opc = '\026'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_MODULO.opc]=BINARY_MODULO
class BINARY_ADD(_opbases.GenericOneByteCode):
op = 23
opc = '\027'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_ADD.opc]=BINARY_ADD
class BINARY_SUBTRACT(_opbases.GenericOneByteCode):
op = 24
opc = '\030'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_SUBTRACT.opc]=BINARY_SUBTRACT
class BINARY_SUBSCR(_opbases.GenericOneByteCode):
op = 25
opc = '\031'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_SUBSCR.opc]=BINARY_SUBSCR
class SLICE_0(_opbases.GenericOneByteCode):
op = 30
opc = '\036'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-1:]=[self]
_bytecodes[SLICE_0.opc]=SLICE_0
class SLICE_1(_opbases.GenericOneByteCode):
op = 31
opc = '\037'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[SLICE_1.opc]=SLICE_1
class SLICE_2(_opbases.GenericOneByteCode):
op = 32
opc = '\040'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[SLICE_2.opc]=SLICE_2
class SLICE_3(_opbases.GenericOneByteCode):
op = 33
opc = '\041'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-3:]=[self]
_bytecodes[SLICE_3.opc]=SLICE_3
class STORE_SLICE_0(_opbases.GenericOneByteCode):
op = 40
opc = '\050'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-2:]
_bytecodes[STORE_SLICE_0.opc]=STORE_SLICE_0
class STORE_SLICE_1(_opbases.GenericOneByteCode):
op = 41
opc = '\051'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-3:]
_bytecodes[STORE_SLICE_1.opc]=STORE_SLICE_1
class STORE_SLICE_2(_opbases.GenericOneByteCode):
op = 42
opc = '\052'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-3:]
_bytecodes[STORE_SLICE_2.opc]=STORE_SLICE_2
class STORE_SLICE_3(_opbases.GenericOneByteCode):
op = 43
opc = '\053'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-4:]
_bytecodes[STORE_SLICE_3.opc]=STORE_SLICE_3
class DELETE_SLICE_0(_opbases.GenericOneByteCode):
op = 50
opc = '\062'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-1:]
_bytecodes[DELETE_SLICE_0.opc]=DELETE_SLICE_0
class DELETE_SLICE_1(_opbases.GenericOneByteCode):
op = 51
opc = '\063'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-2:]
_bytecodes[DELETE_SLICE_1.opc]=DELETE_SLICE_1
class DELETE_SLICE_2(_opbases.GenericOneByteCode):
op = 52
opc = '\064'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-2:]
_bytecodes[DELETE_SLICE_2.opc]=DELETE_SLICE_2
class DELETE_SLICE_3(_opbases.GenericOneByteCode):
op = 53
opc = '\065'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-3:]
_bytecodes[DELETE_SLICE_3.opc]=DELETE_SLICE_3
class STORE_SUBSCR(_opbases.GenericOneByteCode):
op = 60
opc = '\074'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-3:]
_bytecodes[STORE_SUBSCR.opc]=STORE_SUBSCR
class DELETE_SUBSCR(_opbases.GenericOneByteCode):
op = 61
opc = '\075'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
del stack[-2:]
_bytecodes[DELETE_SUBSCR.opc]=DELETE_SUBSCR
class BINARY_LSHIFT(_opbases.GenericOneByteCode):
op = 62
opc = '\076'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_LSHIFT.opc]=BINARY_LSHIFT
class BINARY_RSHIFT(_opbases.GenericOneByteCode):
op = 63
opc = '\077'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_RSHIFT.opc]=BINARY_RSHIFT
class BINARY_AND(_opbases.GenericOneByteCode):
op = 64
opc = '\100'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_AND.opc]=BINARY_AND
class BINARY_XOR(_opbases.GenericOneByteCode):
op = 65
opc = '\101'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_XOR.opc]=BINARY_XOR
class BINARY_OR(_opbases.GenericOneByteCode):
op = 66
opc = '\102'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-2:]=[self]
_bytecodes[BINARY_OR.opc]=BINARY_OR
class PRINT_EXPR(_opbases.GenericOneByteCode):
op = 70
opc = '\106'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack.pop()
_bytecodes[PRINT_EXPR.opc]=PRINT_EXPR
class PRINT_ITEM(_opbases.GenericOneByteCode):
op = 71
opc = '\107'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack.pop()
_bytecodes[PRINT_ITEM.opc]=PRINT_ITEM
class PRINT_NEWLINE(_opbases.GenericOneByteCode):
op = 72
opc = '\110'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
pass
_bytecodes[PRINT_NEWLINE.opc]=PRINT_NEWLINE
class BREAK_LOOP(_opbases.GenericOneByteCode):
op = 80
opc = '\120'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
raise "No jumps here!"
_bytecodes[BREAK_LOOP.opc]=BREAK_LOOP
class LOAD_LOCALS(_opbases.GenericOneByteCode):
op = 82
opc = '\122'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack.append(self)
_bytecodes[LOAD_LOCALS.opc]=LOAD_LOCALS
class RETURN_VALUE(_opbases.GenericOneByteCode):
op = 83
opc = '\123'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[:] = []
_bytecodes[RETURN_VALUE.opc]=RETURN_VALUE
class EXEC_STMT(_opbases.GenericOneByteCode):
op = 85
opc = '\125'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
pass
_bytecodes[EXEC_STMT.opc]=EXEC_STMT
class POP_BLOCK(_opbases.GenericOneByteCode):
op = 87
opc = '\127'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
pass
_bytecodes[POP_BLOCK.opc]=POP_BLOCK
class END_FINALLY(_opbases.GenericOneByteCode):
op = 88
opc = '\130'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
pass
_bytecodes[END_FINALLY.opc]=END_FINALLY
class BUILD_CLASS(_opbases.GenericOneByteCode):
op = 89
opc = '\131'
def __init__(self,cs=None,code=None):
if cs is not None:
_opbases.GenericOneByteCode.__init__(self,cs,code)
def execute(self,stack):
stack[-3:] = [self]
_bytecodes[BUILD_CLASS.opc]=BUILD_CLASS
class STORE_NAME(_opbases.NameOpcode):
op = 90
opc = '\132'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.pop()
_bytecodes[STORE_NAME.opc]=STORE_NAME
class DELETE_NAME(_opbases.NameOpcode):
op = 91
opc = '\133'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.pop()
_bytecodes[DELETE_NAME.opc]=DELETE_NAME
class UNPACK_TUPLE(_opbases.GenericThreeByteCode):
op = 92
opc = '\134'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.append([self] * self.arg)
_bytecodes[UNPACK_TUPLE.opc]=UNPACK_TUPLE
class UNPACK_LIST(_opbases.GenericThreeByteCode):
op = 93
opc = '\135'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.append([self] * self.arg)
_bytecodes[UNPACK_LIST.opc]=UNPACK_LIST
class STORE_ATTR(_opbases.NameOpcode):
op = 95
opc = '\137'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.pop()
_bytecodes[STORE_ATTR.opc]=STORE_ATTR
class DELETE_ATTR(_opbases.NameOpcode):
op = 96
opc = '\140'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.pop()
_bytecodes[DELETE_ATTR.opc]=DELETE_ATTR
class STORE_GLOBAL(_opbases.NameOpcode):
op = 97
opc = '\141'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.pop()
_bytecodes[STORE_GLOBAL.opc]=STORE_GLOBAL
class DELETE_GLOBAL(_opbases.NameOpcode):
op = 98
opc = '\142'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.pop()
_bytecodes[DELETE_GLOBAL.opc]=DELETE_GLOBAL
class LOAD_CONST(_opbases.GenericThreeByteCode):
op = 100
opc = '\144'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.append(self)
_bytecodes[LOAD_CONST.opc]=LOAD_CONST
class LOAD_NAME(_opbases.NameOpcode):
op = 101
opc = '\145'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.append(self)
_bytecodes[LOAD_NAME.opc]=LOAD_NAME
class BUILD_TUPLE(_opbases.GenericThreeByteCode):
op = 102
opc = '\146'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
if self.arg>0:
stack[-self.arg:]=[self]
else:
stack.append(self)
_bytecodes[BUILD_TUPLE.opc]=BUILD_TUPLE
class BUILD_LIST(_opbases.GenericThreeByteCode):
op = 103
opc = '\147'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
if self.arg>0:
stack[-self.arg:]=[self]
else:
stack.append(self)
_bytecodes[BUILD_LIST.opc]=BUILD_LIST
class BUILD_MAP(_opbases.GenericThreeByteCode):
op = 104
opc = '\150'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.append(self)
_bytecodes[BUILD_MAP.opc]=BUILD_MAP
class LOAD_ATTR(_opbases.NameOpcode):
op = 105
opc = '\151'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack[-1] = self
_bytecodes[LOAD_ATTR.opc]=LOAD_ATTR
class COMPARE_OP(_opbases.GenericThreeByteCode):
op = 106
opc = '\152'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack[-2:]=[self] # ????
_bytecodes[COMPARE_OP.opc]=COMPARE_OP
class IMPORT_NAME(_opbases.NameOpcode):
op = 107
opc = '\153'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.append(self)
_bytecodes[IMPORT_NAME.opc]=IMPORT_NAME
class IMPORT_FROM(_opbases.NameOpcode):
op = 108
opc = '\154'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
pass
_bytecodes[IMPORT_FROM.opc]=IMPORT_FROM
class JUMP_FORWARD(_opbases.JRel):
op = 110
opc = '\156'
def __init__(self,csorarg=None,code=None):
if csorarg is not None:
if code is not None:
_opbases.JRel.__init__(self,csorarg,code)
else:
self.label = _Label()
self.user_init(csorarg)
else:
self.label = _Label()
def execute(self,stack):
raise "jumps not handled here!"
_bytecodes[JUMP_FORWARD.opc]=JUMP_FORWARD
class JUMP_IF_FALSE(_opbases.JRel):
op = 111
opc = '\157'
def __init__(self,csorarg=None,code=None):
if csorarg is not None:
if code is not None:
_opbases.JRel.__init__(self,csorarg,code)
else:
self.label = _Label()
self.user_init(csorarg)
else:
self.label = _Label()
def execute(self,stack):
raise "jumps not handled here!"
_bytecodes[JUMP_IF_FALSE.opc]=JUMP_IF_FALSE
class JUMP_IF_TRUE(_opbases.JRel):
op = 112
opc = '\160'
def __init__(self,csorarg=None,code=None):
if csorarg is not None:
if code is not None:
_opbases.JRel.__init__(self,csorarg,code)
else:
self.label = _Label()
self.user_init(csorarg)
else:
self.label = _Label()
def execute(self,stack):
raise "jumps not handled here!"
_bytecodes[JUMP_IF_TRUE.opc]=JUMP_IF_TRUE
class JUMP_ABSOLUTE(_opbases.JAbs):
op = 113
opc = '\161'
def __init__(self,csorarg=None,code=None):
if csorarg is not None:
if code is not None:
_opbases.JAbs.__init__(self,csorarg,code)
else:
self.label = _Label()
self.user_init(csorarg)
else:
self.label = _Label()
def execute(self,stack):
raise "jumps not handled here!"
_bytecodes[JUMP_ABSOLUTE.opc]=JUMP_ABSOLUTE
class FOR_LOOP(_opbases.JRel):
op = 114
opc = '\162'
def __init__(self,csorarg=None,code=None):
if csorarg is not None:
if code is not None:
_opbases.JRel.__init__(self,csorarg,code)
else:
self.label = _Label()
self.user_init(csorarg)
else:
self.label = _Label()
def execute(self,stack):
raise "loop alert"
_bytecodes[FOR_LOOP.opc]=FOR_LOOP
class LOAD_GLOBAL(_opbases.NameOpcode):
op = 116
opc = '\164'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.NameOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.append(self)
_bytecodes[LOAD_GLOBAL.opc]=LOAD_GLOBAL
class SETUP_LOOP(_opbases.JRel):
op = 120
opc = '\170'
def __init__(self,csorarg=None,code=None):
if csorarg is not None:
if code is not None:
_opbases.JRel.__init__(self,csorarg,code)
else:
self.label = _Label()
self.user_init(csorarg)
else:
self.label = _Label()
def execute(self,stack):
raise "loop alert!"
_bytecodes[SETUP_LOOP.opc]=SETUP_LOOP
class SETUP_EXCEPT(_opbases.JRel):
op = 121
opc = '\171'
def __init__(self,csorarg=None,code=None):
if csorarg is not None:
if code is not None:
_opbases.JRel.__init__(self,csorarg,code)
else:
self.label = _Label()
self.user_init(csorarg)
else:
self.label = _Label()
def execute(self,stack):
pass # ??
_bytecodes[SETUP_EXCEPT.opc]=SETUP_EXCEPT
class SETUP_FINALLY(_opbases.JRel):
op = 122
opc = '\172'
def __init__(self,csorarg=None,code=None):
if csorarg is not None:
if code is not None:
_opbases.JRel.__init__(self,csorarg,code)
else:
self.label = _Label()
self.user_init(csorarg)
else:
self.label = _Label()
def execute(self,stack):
pass # ??
_bytecodes[SETUP_FINALLY.opc]=SETUP_FINALLY
class LOAD_FAST(_opbases.LocalOpcode):
op = 124
opc = '\174'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.LocalOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.append(self)
_bytecodes[LOAD_FAST.opc]=LOAD_FAST
class STORE_FAST(_opbases.LocalOpcode):
op = 125
opc = '\175'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.LocalOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.pop()
_bytecodes[STORE_FAST.opc]=STORE_FAST
class DELETE_FAST(_opbases.LocalOpcode):
op = 126
opc = '\176'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.LocalOpcode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack.pop()
_bytecodes[DELETE_FAST.opc]=DELETE_FAST
class SET_LINENO(_opbases.GenericThreeByteCode):
op = 127
opc = '\177'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
pass
_bytecodes[SET_LINENO.opc]=SET_LINENO
class RAISE_VARARGS(_opbases.GenericThreeByteCode):
op = 130
opc = '\202'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
raise "Exception!"
_bytecodes[RAISE_VARARGS.opc]=RAISE_VARARGS
class CALL_FUNCTION(_opbases.GenericThreeByteCode):
op = 131
opc = '\203'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
num_keyword_args=self.arg>>8
num_regular_args=self.arg&0xFF
stack[-2*num_keyword_args-num_regular_args-1:]=[self]
_bytecodes[CALL_FUNCTION.opc]=CALL_FUNCTION
class MAKE_FUNCTION(_opbases.GenericThreeByteCode):
op = 132
opc = '\204'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack[-self.arg-1:]=[self]
_bytecodes[MAKE_FUNCTION.opc]=MAKE_FUNCTION
class BUILD_SLICE(_opbases.GenericThreeByteCode):
op = 133
opc = '\205'
def __init__(self,csorarg,code=None):
if code is not None:
_opbases.GenericThreeByteCode.__init__(self,csorarg,code)
else:
self.user_init(csorarg)
def execute(self,stack):
stack[-self.arg:]=[self]
_bytecodes[BUILD_SLICE.opc]=BUILD_SLICE
import code_editor
from ops import *
import operator
CONDJUMP = [ JUMP_IF_TRUE, JUMP_IF_FALSE ]
UNCONDJUMP = [ JUMP_FORWARD, JUMP_ABSOLUTE ]
UNCOND = UNCONDJUMP + [ BREAK_LOOP, STOP_CODE, RETURN_VALUE, \
RAISE_VARARGS ]
PYBLOCK = [ SETUP_LOOP, SETUP_EXCEPT, SETUP_FINALLY ]
PYENDBLOCK = [ POP_BLOCK ]
binaryops = {
'BINARY_ADD': operator.add,
'BINARY_SUBTRACT': operator.sub,
'BINARY_MULTIPLY': operator.mul,
'BINARY_DIVIDE': operator.div,
'BINARY_MODULO': operator.mod,
'BINARY_POWER': pow,
'BINARY_LSHIFT': operator.lshift,
'BINARY_RSHIFT': operator.rshift,
'BINARY_AND': operator.and_,
'BINARY_OR': operator.or_,
'BINARY_XOR': operator.xor
}
unaryops = {
'UNARY_POS': operator.pos,
'UNARY_NEG': operator.neg,
'UNARY_NOT': operator.not_
}
def rationalize(code):
calculateConstants(code)
strip_setlineno(code)
simplifyjumps(code)
removeconstjump(code)
simplifyjumps(code)
eliminateUnusedNames(code)
eliminateUnusedLocals(code)
def calculateConstants(co):
"""Precalculate results of operations involving constants."""
cs = co.co_code
cc = co.co_consts
stack = []
i = 0
while i < len(cs):
op = cs[i]
if binaryops.has_key(op.__class__.__name__):
if stack[-1].__class__ is stack[-2].__class__ is LOAD_CONST:
arg1 = cc[stack[-2].arg]
arg2 = cc[stack[-1].arg]
result = binaryops[op.__class__.__name__](arg1,arg2)
if result in cc:
arg = cc.index(result)
else:
arg = len(cc)
cc.append(result)
cs.remove(stack[-2])
cs.remove(stack[-1])
i = i - 2
cs[i] = LOAD_CONST(arg)
stack.pop()
stack.pop()
stack.append(cs[i])
else:
op.execute(stack)
elif unaryops.has_key(op.__class__.__name__):
if stack[-1].__class__ is LOAD_CONST:
arg1 = cc[stack[-1].arg]
result = unaryops[op.__class__.__name__](arg1)
if result in cc:
arg = cc.index(result)
else:
arg = len(cc)
cc.append(result)
cs.remove(stack[-1])
i = i - 1
cs[i] = LOAD_CONST(arg)
stack.pop()
stack.append(cs[i])
else:
op.execute(stack)
else:
# this is almost certainly wrong
try:
op.execute(stack)
except: pass
i = i + 1
def strip_setlineno(co):
"""Take in an EditableCode object and strip the SET_LINENO bytecodes"""
i = 0
while i < len(co.co_code):
op = co.co_code[i]
if op.__class__ is SET_LINENO:
co.co_code.remove(op)
else:
i = i + 1
def simplifyjumps(co):
cs = co.co_code
i = 0
pyblockstack = [None]
loopstack = [None]
trystack = [None]
firstlook = 1
while i < len(cs):
op = cs[i]
# new pyblock?
if firstlook:
if op.__class__ in PYBLOCK:
pyblockstack.append(op)
if op.__class__ is SETUP_LOOP:
loopstack.append(op.label.op)
else:
trystack.append(op.label.op)
# end of pyblock?
elif op.__class__ == POP_BLOCK:
op2 = pyblockstack.pop()
if op2.__class__ == SETUP_LOOP:
loopstack.pop()
else:
trystack.pop()
# Is the code inaccessible
if i >= 1:
if cs[i-1].__class__ in UNCOND and not (cs.find_labels(i) or \
op.__class__ in PYENDBLOCK):
cs.remove(op)
firstlook = 1
continue
# are we jumping from the statement before?
if cs[i-1].__class__ in UNCONDJUMP:
if cs[i-1].label.op == op:
cs.remove(cs[i-1])
firstlook = 1
continue
# break before end of loop?
elif cs[i-1].__class__ == BREAK_LOOP:
if op.__class__ == POP_BLOCK:
cs.remove(cs[i-1])
firstlook = 1
continue
# Do we have an unconditional jump to an unconditional jump?
if op.__class__ in UNCONDJUMP:
if op.label.op.__class__ in UNCONDJUMP:
refop = op.label.op
if op.__class__ == JUMP_FORWARD:
newop = JUMP_ABSOLUTE()
newop.label.op = refop.label.op
cs[i] = newop
else:
op.label.op = refop.label.op
firstlook = 0
continue
# Do we have a conditional jump to a break?
if op.__class__ in CONDJUMP and loopstack[-1]:
destindex = cs.index(op.label.op)
preendindex = cs.index(loopstack[-1])-2
if cs[i+2].__class__ == BREAK_LOOP and cs[preendindex].__class__ \
== POP_TOP:
if op.__class__ == JUMP_IF_FALSE:
newop = JUMP_IF_TRUE()
else:
newop = JUMP_IF_FALSE()
newop.label.op = cs[preendindex]
cs[i] = newop
cs.remove(cs[i+1])
cs.remove(cs[i+1])
cs.remove(cs[i+1])
firstlook = 0
continue
elif cs[destindex+1].__class__ == BREAK_LOOP and \
cs[preendindex].__class__ == POP_TOP:
op.label.op = cs[preendindex]
cs.remove(cs[destindex])
cs.remove(cs[destindex])
cs.remove(cs[destindex])
firstlook = 0
continue
firstlook = 1
i = i+1
def removeconstjump(co):
cs = co.co_code
cc = co.co_consts
i = 0
while i < len(cs):
op = cs[i]
if op.__class__ in CONDJUMP and cs[i-1].__class__ == LOAD_CONST:
if (op.__class__ == JUMP_IF_FALSE and cc[cs[i-1].arg]) or \
(op.__class__ == JUMP_IF_TRUE and not cc[cs[i-1].arg]):
cs.remove(cs[i-1])
cs.remove(cs[i-1])
cs.remove(cs[i-1])
i = i-2
else:
cs.remove(cs[i-1])
cs.remove(cs[i])
newop = JUMP_FORWARD()
newop.label.op = cs[cs.index(op.label.op)+1]
cs[i-1] = newop
i = i-1
i = i+1
def eliminateUnusedNames(code):
used_names = {}
for op in code.co_code:
if op.has_name():
if hasattr(op,"arg"):
arg = op.arg
else:
arg = op.arg = code.name_index(op.name)
used_names[arg] = 1
used_names = used_names.keys()
used_names.sort()
name_mapping = {}
for i in range(len(used_names)):
name_mapping[used_names[i]]=i
newnames = []
for i in range(len(code.co_names)):
if i in used_names:
newnames.append(code.co_names[i])
code.co_names = newnames
for op in code.co_code:
if op.has_name():
op.arg = name_mapping[op.arg]
def eliminateUnusedLocals(code):
used_names = {}
for op in code.co_code:
if op.has_local():
if hasattr(op,"arg"):
arg = op.arg
else:
arg = op.arg = code.local_index(op.name)
used_names[arg] = 1
used_names = used_names.keys()
used_names.sort()
name_mapping = {}
for i in range(len(used_names)):
name_mapping[used_names[i]]=i
newnames = []
for i in range(len(code.co_varnames)):
if i in used_names:
newnames.append(code.co_varnames[i])
code.co_varnames = newnames
for op in code.co_code:
if op.has_local():
op.arg = name_mapping[op.arg]
from code_editor import Function
from find_function_call import find_function_call
from ops import *
def make_tail_recursive(func):
func = Function(func)
code = func.func_code
cs = code.co_code
index = 0
while 1:
stack = find_function_call(func,func.func_name,startindex=index)
if stack is None:
break
index = cs.index(stack[-1])
if cs[index + 1].__class__ is RETURN_VALUE:
cs.remove(stack[0])
newop = JUMP_ABSOLUTE()
cs[index - 1:index] = [newop]
newop.label.op = cs[0]
del stack[0],stack[-1]
nlocals = len(code.co_varnames)
code.co_varnames = code.co_varnames + code.co_varnames
for op in stack:
cs.insert(cs.index(op)+1,STORE_FAST(stack.index(op)+nlocals))
iindex = cs.index(newop)
for i in range(len(stack)):
cs.insert(iindex,STORE_FAST(i))
cs.insert(iindex,LOAD_FAST(i+nlocals))
index = iindex
return func.make_function()
def _facr(n,c,p):
if c <= n:
return _facr(n,c+1,c*p)
return p
def facr(n,_facr=_facr):
return _facr(n,1,1l)
_factr = make_tail_recursive(_facr)
def factr(n,_factr=_factr):
return _factr(n,1,1l)
def faci(n):
p = 1l; c = 1;
while c <= n:
p = c*p
c = c+1
return p
import time
def suite(n,c=10,T=time.time):
r = [0,0,0]
for i in range(c):
t=T(); facr(n); r[0] = T()-t + r[0]
t=T(); factr(n); r[1] = T()-t + r[1]
t=T(); faci(n); r[2] = T()-t + r[2]
print " recursive: 1.000000000000 (arbitrarily)"
print "tail recursive:",r[1]/r[0]
print " iterative:",r[2]/r[0]
"""\
xapply
Inspired by Don Beaudry's functor module.
xapply exports one public function, the eponymous xapply. xapply can
be thought of as `lazy apply' or `partial argument resolution'.
It takes a function and part of it's argument list, and returns a
function with the first parameters filled in. An example:
def f(x,y):
return x+y
add1 = xapply(f,1)
add1(2) => 3
This xapply is not yet as general as that from the functor module, but
the functions return are as fast as normal function, i.e. twice as
fast as functors.
This may be generalised at some point in the future.
"""
import new,string,re,types
from ops import LOAD_FAST, LOAD_CONST
from code_editor import Function, InstanceMethod
def xapply_munge(code, args, except0=0):
nconsts = len(code.co_consts)
nvars = len(args)
code.co_consts.extend(list(args))
if except0:
var2constlim = nvars+1
var2constoff = nconsts-1
else:
var2constlim = nvars
var2constoff = nconsts
cs = code.co_code
for i in range(len(cs)):
op = cs[i]
if op.__class__ is LOAD_FAST:
if op.arg == 0 and except0:
continue
if op.arg < var2constlim:
cs[i] = LOAD_CONST(op.arg + var2constoff)
else:
op.arg = op.arg - nvars
code.co_varnames = code.co_varnames[nvars:]
code.co_argcount = code.co_argcount - nvars
def xapply_func(func,args):
f = Function(func)
xapply_munge(f.func_code,args,0)
return f.make_function()
def xapply_meth(meth,args):
im = InstanceMethod(meth)
xapply_munge(im.im_func.func_code,args,1)
return im.make_instance_method()
def xapply(callable,*args):
""" xapply(callable,arg1,arg2,...) -> callable
if
f=xapply(callable,arg1,arg2,...,argn)
then
f(arg<n+1>,....argm)
is equivalent to
callable(arg1,...,argn,arg<n+1>,..argm)
callable currently must be a function or instance method, and keyword
arguments are currently not allowed.
"""
callable_type=type(callable)
if callable_type is types.FunctionType:
return xapply_func(callable,args)
elif callable_type is types.UnboundMethodType:
return xapply_meth(callable,args)
else:
raise "nope"
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment