implemented T487 'with' with multiple managers
authorHaoyu Bai <baihaoyu@gmail.com>
Tue, 25 May 2010 17:26:37 +0000 (01:26 +0800)
committerHaoyu Bai <baihaoyu@gmail.com>
Tue, 25 May 2010 17:26:37 +0000 (01:26 +0800)
Cython/Compiler/Parsing.py
tests/run/withstat.pyx

index 72471e8d0ce924a60c23168a324eb216e8c4c54e..124129421a3521964a44b3197e1c760264ecec20 100644 (file)
@@ -1503,50 +1503,62 @@ def p_include_statement(s, ctx):
         return Nodes.PassStatNode(pos)
 
 def p_with_statement(s):
-    pos = s.position()
     s.next() # 'with'
-#    if s.sy == 'IDENT' and s.systring in ('gil', 'nogil'):
+    if s.systring == 'template':
+        node = p_with_template(s)
+    else:
+        node = p_with_items(s)
+    return node
+
+def p_with_items(s):
+    pos = s.position()
     if s.sy == 'IDENT' and s.systring == 'nogil':
         state = s.systring
         s.next()
-        body = p_suite(s)
-        return Nodes.GILStatNode(pos, state = state, body = body)
-    elif s.systring == 'template':
-        templates = []
-        s.next()
-        s.expect('[')
-        #s.next()
-        templates.append(s.systring)
-        s.next()
-        while s.systring == ',':
-            s.next()
-            templates.append(s.systring)
-            s.next()
-        s.expect(']')
-        if s.sy == ':':
+        if s.sy == ',':
             s.next()
-            s.expect_newline("Syntax error in template function declaration")
-            s.expect_indent()
-            body_ctx = Ctx()
-            body_ctx.templates = templates
-            func_or_var = p_c_func_or_var_declaration(s, pos, body_ctx)
-            s.expect_dedent()
-            return func_or_var
+            body = p_with_items(s)
         else:
-            error(pos, "Syntax error in template function declaration")
+            body = p_suite(s)
+        return Nodes.GILStatNode(pos, state = state, body = body)
     else:
         manager = p_test(s)
         target = None
         if s.sy == 'IDENT' and s.systring == 'as':
             s.next()
-            allow_multi = (s.sy == '(')
-            target = p_target(s, ':')
-            if not allow_multi and isinstance(target, ExprNodes.TupleNode):
-                s.error("Multiple with statement target values not allowed without paranthesis")
-        body = p_suite(s)
+            target = p_starred_expr(s)
+        if s.sy == ',':
+            s.next()
+            body = p_with_items(s)
+        else:
+            body = p_suite(s)
     return Nodes.WithStatNode(pos, manager = manager, 
                               target = target, body = body)
-    
+
+def p_with_template(s):
+    pos = s.position()
+    templates = []
+    s.next()
+    s.expect('[')
+    templates.append(s.systring)
+    s.next()
+    while s.systring == ',':
+        s.next()
+        templates.append(s.systring)
+        s.next()
+    s.expect(']')
+    if s.sy == ':':
+        s.next()
+        s.expect_newline("Syntax error in template function declaration")
+        s.expect_indent()
+        body_ctx = Ctx()
+        body_ctx.templates = templates
+        func_or_var = p_c_func_or_var_declaration(s, pos, body_ctx)
+        s.expect_dedent()
+        return func_or_var
+    else:
+        error(pos, "Syntax error in template function declaration")
+
 def p_simple_statement(s, first_statement = 0):
     #print "p_simple_statement:", s.sy, s.systring ###
     if s.sy == 'global':
index bea6437e37648d76e8bdade74571c0b4bfa06818..fad2eaff1a84c2b4859ec4d23cb92a008dd14a8d 100644 (file)
@@ -118,3 +118,121 @@ def typed():
     with c as i:
         i += 11
         print i
+
+def multimanager():
+    """
+    >>> multimanager()
+    enter
+    enter
+    enter
+    enter
+    enter
+    enter
+    2
+    value
+    1 2 3 4 5
+    nested
+    exit <type 'NoneType'> <type 'NoneType'> <type 'NoneType'>
+    exit <type 'NoneType'> <type 'NoneType'> <type 'NoneType'>
+    exit <type 'NoneType'> <type 'NoneType'> <type 'NoneType'>
+    exit <type 'NoneType'> <type 'NoneType'> <type 'NoneType'>
+    exit <type 'NoneType'> <type 'NoneType'> <type 'NoneType'>
+    exit <type 'NoneType'> <type 'NoneType'> <type 'NoneType'>
+    """
+    with ContextManager(1), ContextManager(2) as x, ContextManager(u'value') as y,\
+            ContextManager(3), ContextManager((1, 2, (3, (4, 5)))) as (a, b, (c, (d, e))):
+        with ContextManager(u'nested') as nested:
+            print x
+            print y
+            print a, b, c, d, e
+            print nested
+
+# Tests borrowed from pyregr test_with.py,
+# modified to follow the constraints of Cython.
+import unittest
+
+class Dummy(object):
+    def __init__(self, value=None, gobble=False):
+        if value is None:
+            value = self
+        self.value = value
+        self.gobble = gobble
+        self.enter_called = False
+        self.exit_called = False
+
+    def __enter__(self):
+        self.enter_called = True
+        return self.value
+
+    def __exit__(self, *exc_info):
+        self.exit_called = True
+        self.exc_info = exc_info
+        if self.gobble:
+            return True
+
+class InitRaises(object):
+    def __init__(self): raise RuntimeError()
+
+class EnterRaises(object):
+    def __enter__(self): raise RuntimeError()
+    def __exit__(self, *exc_info): pass
+
+class ExitRaises(object):
+    def __enter__(self): pass
+    def __exit__(self, *exc_info): raise RuntimeError()
+
+class NestedWith(unittest.TestCase):
+    """
+    >>> NestedWith().runTest()
+    """
+
+    def runTest(self):
+        self.testNoExceptions()
+        self.testExceptionInExprList()
+        self.testExceptionInEnter()
+        self.testExceptionInExit()
+        self.testEnterReturnsTuple()
+
+    def testNoExceptions(self):
+        with Dummy() as a, Dummy() as b:
+            self.assertTrue(a.enter_called)
+            self.assertTrue(b.enter_called)
+        self.assertTrue(a.exit_called)
+        self.assertTrue(b.exit_called)
+
+    def testExceptionInExprList(self):
+        try:
+            with Dummy() as a, InitRaises():
+                pass
+        except:
+            pass
+        self.assertTrue(a.enter_called)
+        self.assertTrue(a.exit_called)
+
+    def testExceptionInEnter(self):
+        try:
+            with Dummy() as a, EnterRaises():
+                self.fail('body of bad with executed')
+        except RuntimeError:
+            pass
+        else:
+            self.fail('RuntimeError not reraised')
+        self.assertTrue(a.enter_called)
+        self.assertTrue(a.exit_called)
+
+    def testExceptionInExit(self):
+        body_executed = False
+        with Dummy(gobble=True) as a, ExitRaises():
+            body_executed = True
+        self.assertTrue(a.enter_called)
+        self.assertTrue(a.exit_called)
+        self.assertTrue(body_executed)
+        self.assertNotEqual(a.exc_info[0], None)
+
+    def testEnterReturnsTuple(self):
+        with Dummy(value=(1,2)) as (a1, a2), \
+             Dummy(value=(10, 20)) as (b1, b2):
+            self.assertEquals(1, a1)
+            self.assertEquals(2, a2)
+            self.assertEquals(10, b1)
+            self.assertEquals(20, b2)