Skip to content
This repository has been archived by the owner on May 31, 2020. It is now read-only.

Commit

Permalink
Merge pull request #854 from BPYap/nonlocal-statement-support
Browse files Browse the repository at this point in the history
Nonlocal statement support
  • Loading branch information
freakboy3742 authored Aug 7, 2018
2 parents 2b781d1 + 5c9d190 commit 0028447
Show file tree
Hide file tree
Showing 9 changed files with 419 additions and 87 deletions.
10 changes: 7 additions & 3 deletions python/common/org/python/types/Closure.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.python.types;

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

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

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

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

public java.util.Map get_locals(int level) {
return this.locals_list.get(level - 1);
}
}
20 changes: 20 additions & 0 deletions python/common/org/python/types/Generator.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public class Generator extends org.python.types.Object {
java.lang.reflect.Method expression;
public int yield_point;
public java.util.Map<java.lang.String, org.python.Object> stack;
public org.python.types.Closure closure; // null for non-closure Generator

private boolean just_started = true;
public org.python.Object message;
Expand All @@ -27,6 +28,25 @@ public Generator(
this.yield_point = 0;
this.stack = stack;
this.message = new org.python.types.NoneType();
this.closure = null;
}

public Generator(
java.lang.String name,
java.lang.reflect.Method expression,
java.util.Map<java.lang.String, org.python.Object> stack,
org.python.types.Closure closure
) {
// System.out.println("GENERATOR: " + expression);
// for (org.python.Object obj: stack) {
// System.out.println(" : " + obj);
// }
this.name = name;
this.expression = expression;
this.yield_point = 0;
this.stack = stack;
this.message = new org.python.types.NoneType();
this.closure = closure;
}

public void yield(java.util.Map<java.lang.String, org.python.Object> stack, int yield_point) {
Expand Down
21 changes: 0 additions & 21 deletions tests/structures/test_for.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 @@ -133,25 +131,6 @@ def test_multiple_values_iterator(self):
""")

def test_recursive(self):
self.assertCodeExecution("""
def process(data):
print('process: ', data)
for datum in data:
process(datum)
print('data processed: ', data)
data = [[], [[], [], []], [[]]]
process(data)
""", run_in_function=False)

# FIXME: this is the same as the previous test, but the in-function
# version fails because recursive functions defined *in* a function
# don't work. Once they *do* work, the previous test can be used
# without the run_in_function=False qualifier, and this test can be
# deleted.
@expectedFailure
def test_recursive_in_function(self):
self.assertCodeExecution("""
def process(data):
print('process: ', data)
Expand Down
148 changes: 148 additions & 0 deletions tests/structures/test_nonlocal.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from unittest import expectedFailure

from ..utils import TranspileTestCase


class NonlocalTests(TranspileTestCase):
def test_nonlocal_func(self):
self.assertCodeExecution("""
def func():
a = 'a from outer'
b = 'b from outer'
def nested_func():
nonlocal a
print(a)
a = 'a from inner'
print(a)
b = 'b from inner'
print(b)
nested_func()
print(a)
print(b)
func()
def func2():
a = 'a from outer'
b = 'b from outer'
def nested_func2():
nonlocal a
print(a)
a = 'a from inner'
print(a)
def nested_nested_func():
nonlocal b
print(b)
b = 'b from innest'
print(b)
nested_nested_func()
nested_func2()
print(a)
print(b)
func2()
""")

self.assertCodeExecution("""
def func():
a = None
def nested():
nonlocal a
a = 'changed by nested'
print(a)
def nested2():
print(a)
return (nested, nested2)
nested, nested2 = func()
nested2()
nested()
nested2()
""")

@expectedFailure
def test_nonlocal_class(self):
self.assertCodeExecution("""
def func():
a = 'a from outer'
b = 'b from outer'
class Inner():
nonlocal a
print(a)
a = 'a from inner'
b = 'b from inner'
print(a)
print(b)
Inner()
print(a)
print(b)
func()
""")

@expectedFailure
def test_nonlocal_method(self):
self.assertCodeExecution("""
def func():
a = 'a from outer'
b = 'b from outer'
class Klass:
def method(self):
nonlocal a
print(a)
a = 'a from inner'
print(a)
print(b)
Klass().method()
print(a)
print(b)
# make sure closure variables are not exposed
print(hasattr(Klass(), '$closure-a'))
print(hasattr(Klass(), '$closure-b'))
func()
""")

def test_nonlocal_generator(self):
self.assertCodeExecution("""
def func():
a = 'a from outer'
b = 'b from outer'
def gen():
nonlocal a
print(a)
print(b)
a = 'a from inner'
yield a
print(next(gen()))
print(a)
print(b)
func()
def func2():
a = 'a from outer'
b = 'b from outer'
def gen():
nonlocal a
print(a)
print(b)
a = 'a from inner'
yield a
print(next(gen()))
print(a)
print(b)
yield
next(func2())
""")
4 changes: 3 additions & 1 deletion voc/python/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -863,7 +863,9 @@ def visit_Global(self, node):
@node_visitor
def visit_Nonlocal(self, node):
# identifier* names):
raise NotImplementedError('No handler for Nonlocal')
for name in node.names:
self.context.nonlocal_vars.append(name)
self.context.local_vars.pop(name, None)

@node_visitor
def visit_Pass(self, node):
Expand Down
12 changes: 6 additions & 6 deletions voc/python/klass.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
)
from .blocks import Block, IgnoreBlock
from .methods import (
InitMethod, ClosureInitMethod, GeneratorMethod, Method, CO_GENERATOR
InitMethod, ClosureInitMethod,
GeneratorMethod, Method, CO_GENERATOR,
)
from .types import java, python
from .types.primitives import (
Expand Down Expand Up @@ -72,7 +73,7 @@ def module(self):
return self._parent.module

def store_module(self):
# Stores the current module as a local variable
# Stores the current module as a local variable
if ('#module') not in self.local_vars:
self.add_opcodes(
JavaOpcodes.GETSTATIC('python/sys', 'modules', 'Lorg/python/types/Dict;'),
Expand Down Expand Up @@ -253,7 +254,7 @@ def add_function(self, name, code, parameter_signatures, return_signature):
generator=code.co_name,
parameters=parameter_signatures,
returns=return_signature,
static=True,
static=True
)

else:
Expand All @@ -263,7 +264,7 @@ def add_function(self, name, code, parameter_signatures, return_signature):
code=code,
parameters=parameter_signatures,
returns=return_signature,
static=True,
static=True
)

# Add the method to the list that need to be
Expand Down Expand Up @@ -368,7 +369,7 @@ def transpile(self):
class ClosureClass(Class):
CONSTRUCTOR = ClosureInitMethod

def __init__(self, parent, name, closure_var_names, verbosity=0):
def __init__(self, parent, name, verbosity=0):
super().__init__(
parent=parent,
name=name,
Expand All @@ -377,4 +378,3 @@ def __init__(self, parent, name, closure_var_names, verbosity=0):
verbosity=verbosity,
include_default_constructor=False,
)
self.closure_var_names = closure_var_names
Loading

0 comments on commit 0028447

Please sign in to comment.