Skip to content

Commit

Permalink
Implemented simple closures, which also enables simple non-argument d…
Browse files Browse the repository at this point in the history
…ecorators.
  • Loading branch information
freakboy3742 committed Oct 2, 2016
1 parent 3541618 commit 2b40540
Show file tree
Hide file tree
Showing 8 changed files with 202 additions and 74 deletions.
11 changes: 5 additions & 6 deletions python/common/org/python/types/Closure.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package org.python.types;

public class Closure extends org.python.types.Object {
public java.util.List<org.python.Object> default_args;
public java.util.Map<java.lang.String, org.python.Object> default_kwargs;
public java.util.Map<java.lang.String, org.python.Object> closure_vars;

/**
* A utility method to update the internal value of this object.
Expand All @@ -13,8 +12,9 @@ public class Closure extends org.python.types.Object {
void setValue(org.python.Object obj) {
}

public Closure(org.python.Object [] args, java.util.Map<java.lang.String, org.python.Object> kwargs) {
super(args, kwargs);
public Closure(java.util.Map<java.lang.String, org.python.Object> vars) {
super();
this.closure_vars = vars;
}

@org.python.Method(
Expand All @@ -23,5 +23,4 @@ public Closure(org.python.Object [] args, java.util.Map<java.lang.String, org.py
public org.python.Object __repr__() {
return new org.python.types.Str(String.format("<function %s at 0x%x>", this.typeName(), this.hashCode()));
}

}
}
46 changes: 46 additions & 0 deletions tests/structures/test_decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from unittest import expectedFailure

from ..utils import TranspileTestCase


class DecoratorTests(TranspileTestCase):
def test_simple_decorator(self):
self.assertCodeExecution("""
def thing(fn):
def _dec(value):
print("Start decoration.")
result = fn(value)
print("End decoration.")
return result * 42
return _dec
@thing
def calculate(value):
print("Do a calculation")
return 37 * value
print("Decorated result is", calculate(5))
print("Done.")
""")

@expectedFailure
def test_decorator_with_argument(self):
self.assertCodeExecution("""
def thing(multiplier):
def _dec(fn):
def _fn(value):
print("Start decoration.")
result = fn(value)
print("End decoration.")
return result * multiplier
return _fn
return _dec
@thing(42)
def calculate(value):
print("Do a calculation")
return 37 * value
print("Decorated result is", calculate(5))
print("Done.")
""")
3 changes: 0 additions & 3 deletions tests/structures/test_function.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from unittest import expectedFailure

from ..utils import TranspileTestCase


Expand Down Expand Up @@ -55,7 +53,6 @@ def myinner(value2):
print('Done.')
""", run_in_function=False)

@expectedFailure
def test_closure(self):
self.assertCodeExecution("""
def myfunc(value):
Expand Down
32 changes: 32 additions & 0 deletions voc/python/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ def visit_FunctionDef(self, node):

name_visitor = NameVisitor()

# Build the definition of the function
default_vars = []
parameter_signatures = []
for i, arg in enumerate(node.args.args):
Expand Down Expand Up @@ -289,13 +290,34 @@ def visit_FunctionDef(self, node):
'annotation': name_visitor.evaluate(node.returns).annotation
}

# Now actually define the function.
for decorator in node.decorator_list:
self.visit(decorator)
self.context.add_opcodes(
JavaOpcodes.CHECKCAST('org/python/Callable'),
JavaOpcodes.ICONST_1(),
JavaOpcodes.ANEWARRAY('org/python/Object'),
JavaOpcodes.DUP(),
JavaOpcodes.ICONST_0(),
)

function = self.context.add_function(
name=node.name,
code=code,
parameter_signatures=parameter_signatures,
return_signature=return_signature
)

for decorator in node.decorator_list[::-1]:
self.context.add_opcodes(
JavaOpcodes.AASTORE(),
JavaOpcodes.ACONST_NULL(),
JavaOpcodes.INVOKEINTERFACE('org/python/Callable', 'invoke', '([Lorg/python/Object;Ljava/util/Map;)Lorg/python/Object;'),
)

# Store the callable object as an accessible symbol.
self.context.store_name(node.name)

# Free all the variables used for default storage.
for default in default_vars:
free_name(self.context, default)
Expand Down Expand Up @@ -926,6 +948,9 @@ def visit_ListComp(self, node):
}
)

# Store the callable object as an accessible symbol.
self.context.store_name(listcomp.name)

self.push_context(listcomp)

LocalsVisitor(listcomp).visit(node)
Expand Down Expand Up @@ -1070,6 +1095,8 @@ def visit_SetComp(self, node):
'annotation': 'org/python/types/Set'
}
)
# Store the callable object as an accessible symbol.
self.context.store_name(setcomp.name)

self.push_context(setcomp)

Expand Down Expand Up @@ -1197,6 +1224,8 @@ def visit_DictComp(self, node):
'annotation': 'org/python/types/Dict'
}
)
# Store the callable object as an accessible symbol.
self.context.store_name(dictcomp.name)

self.push_context(dictcomp)

Expand Down Expand Up @@ -1335,6 +1364,9 @@ def visit_GeneratorExp(self, node):
}
)

# Store the callable object as an accessible symbol.
self.context.store_name(genexp.name)

self.push_context(genexp)

LocalsVisitor(genexp).visit(node)
Expand Down
2 changes: 1 addition & 1 deletion voc/python/blocks.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,7 @@ def add_callable(self, function, closure=False):
JavaOpcodes.ATHROW(),
END_TRY()
)
free_name(self, '#EXCEPTION#'),
free_name(self, '#EXCEPTION#')

def stack_depth(self):
"Evaluate the maximum stack depth required by a sequence of Java opcodes"
Expand Down
46 changes: 14 additions & 32 deletions voc/python/klass.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
)
from .blocks import Block, IgnoreBlock
from .methods import (
CO_GENERATOR, InitMethod, Function, Method
CO_GENERATOR, InitMethod, ClosureInitMethod, Function, Method
)
from .utils import (
ALOAD_name, ASTORE_name, free_name
Expand Down Expand Up @@ -41,11 +41,7 @@ def __init__(self, module, name, namespace=None, bases=None, extends=None, imple
# Mark this class as being a VOC generated class.
self.fields["__VOC__"] = "Lorg/python/Object;"

# Make sure there is a default constructor
default_init = InitMethod(self)
default_init.visitor_setup()
default_init.visitor_teardown()
self.methods.append(default_init)
self.methods.append(self.constructor())

@property
def descriptor(self):
Expand Down Expand Up @@ -198,22 +194,14 @@ def delete_name(self, name):
JavaOpcodes.INVOKEVIRTUAL('org/python/types/Type', '__delattr__', '(Ljava/lang/String;)V'),
)

def add_function(self, name, code, parameter_signatures, return_signature):
# parts = name.split('$')[-1].split('.')
# method_name = parts[-1]
# class_name = parts[-2]

# if class_name != self.klass.name:
# raise Exception("Method %s being added to %s!" % (class_name, self.klass.name))
def constructor(self):
# Make sure there is a Java constructor
return InitMethod(self)

# print (code)
def add_function(self, name, code, parameter_signatures, return_signature):
if False: # FIXME code.co_flags & CO_GENERATOR:
raise Exception("Can't handle Generator instance methods (yet)")
else:
# return_type = annotations.get('return', 'org/python/Object')
# if return_type is None:
# return_type = 'void'

method = Method(
self,
name=name,
Expand All @@ -223,17 +211,13 @@ def add_function(self, name, code, parameter_signatures, return_signature):
static=True,
)


# Add the method to the list that need to be
# transpiled into Java methods
self.methods.append(method)

# Add a definition of the callable object
self.add_callable(method)

# Store the callable object as an accessible symbol.
self.store_name(method.name)

if method.name == '__init__':
self.init_method = method

Expand Down Expand Up @@ -327,19 +311,17 @@ def transpile(self):


class ClosureClass(Class):
def __init__(self, module, closure_var_names, name=None, extends=None, bases=None, implements=None, public=True, final=False, methods=None, init=None, verbosity=0):
self.closure_var_names = closure_var_names
def __init__(self, module, name, closure_var_names, verbosity=0):
super().__init__(
module=module,
name=name,
namespace=module.namespace,
bases=bases,
extends=extends,
implements=implements,
public=public,
final=final,
methods=methods,
init=init,
extends='org/python/types/Closure',
implements=['org/python/Callable'],
verbosity=verbosity,
include_default_constructor=False,
)
self.closure_var_names = closure_var_names

def constructor(self):
# Make sure there is a default constructor
return ClosureInitMethod(self)
Loading

0 comments on commit 2b40540

Please sign in to comment.