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

Initial checkin

parent 955f0de2
This diff is collapsed.
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())
This diff is collapsed.
##############################################################################
#
# 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
This diff is collapsed.
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.
This diff is collapsed.
__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])
# -*- 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 diff is collapsed.
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