[pypy-svn] r17484 - in pypy/dist/pypy: annotation doc rpython rpython/test translator/c/test

tismer at codespeak.net tismer at codespeak.net
Mon Sep 12 02:36:45 CEST 2005


Author: tismer
Date: Mon Sep 12 02:36:42 2005
New Revision: 17484

Modified:
   pypy/dist/pypy/annotation/builtin.py
   pypy/dist/pypy/doc/coding-guide.txt
   pypy/dist/pypy/rpython/rlist.py
   pypy/dist/pypy/rpython/rrange.py
   pypy/dist/pypy/rpython/test/test_rrange.py
   pypy/dist/pypy/translator/c/test/test_typed.py
Log:
completed the implementation of rrange.
It is true that ranges with a variable step are seldom animals.
Anyway I tried to make RPython complete in this area, without adding
too much overhead or slowing down the common cases.

- augmented the annotator to allow for variable step in ranges. This is
  flagged by setting range_step to zero, a disallowed value in the constant case.
- special-cased variable step in rrange.py. Only if it is variable, a three-element
  object is allocated for range and its iterator, no overhead created otherwise.
- added a runtime-check for variable step. Range always issues ValueError if step
  is zero. The case is rare enough to not add special cases here.
- tried to make the additional code as small as possible and did not influence
  the speed of the const case in any way.
- added a couple of new tests that ensure correct dynamic handling of the variable step.
- updated the coding guide, mostly by removing dropped restrictions.

I honestly hope not to raise complaints by this, although it was mostly for my own pleasure.

considering if and how to map the range/xrange implementations of StdObjSpace to it.


Modified: pypy/dist/pypy/annotation/builtin.py
==============================================================================
--- pypy/dist/pypy/annotation/builtin.py	(original)
+++ pypy/dist/pypy/annotation/builtin.py	Mon Sep 12 02:36:42 2005
@@ -53,13 +53,15 @@
     else:
         raise Exception, "range() takes 1 to 3 arguments"
     if not s_step.is_constant():
-        raise Exception, "range() step argument should be a constant"
-    step = s_step.const
-    if step == 0:
-        raise Exception, "range() with step zero"
-    elif step > 0:
-        nonneg = s_start.nonneg
+        step = 0 # this case signals a variable step
     else:
+        step = s_step.const
+        if step == 0:
+            raise Exception, "range() with step zero"
+    nonneg = False # so far
+    if step > 0:
+        nonneg = s_start.nonneg
+    elif step < 0:
         nonneg = s_stop.nonneg or (s_stop.is_constant() and s_stop.const >= -1)
     return getbookkeeper().newlist(SomeInteger(nonneg=nonneg), range_step=step)
 

Modified: pypy/dist/pypy/doc/coding-guide.txt
==============================================================================
--- pypy/dist/pypy/doc/coding-guide.txt	(original)
+++ pypy/dist/pypy/doc/coding-guide.txt	Mon Sep 12 02:36:42 2005
@@ -109,12 +109,14 @@
 
 **control structures**
 
-  all allowed
+  all allowed but yield
 
 **range**
 
-  does not create an array. It is only allowed in for loops. The step argument
-  must be a constant.
+  ``range`` and ``xrange`` are identical. ``range`` does not necessarily create an array,
+  only if the result is modified. It is allowed everywhere and completely
+  implemented. The only visible difference to CPython is the inaccessability
+  of the ``xrange`` fields start, stop and step.
 
 **definitions**
 
@@ -149,20 +151,21 @@
 
   lists are used as an allocated array; list.append() does naive resizing, so as
   far as possible use list comprehensions (see below).  list.extend() or the +=
-  operator are allowed and efficient.  Unless there is really a use case for it,
-  repetition is limited to initialization purposes: '[single_value] * length'.
+  operator are allowed and efficient.  
+  Repetition via `*` or `*=` is fully supported as well.
 
 **dicts**
 
-  dicts with string keys only (preferably the kind of strings that are usually
-  interned in CPython, i.e. short strings that look like identifiers).  The
-  implementation could safely decide that all dict keys should be interned.
+  dicts with a unique key type only, provided it is hashable. 
+  String keys have been the only allowed key types for a while, but this was generalized. 
+  After some re-optimization,
+  the implementation could safely decide that all dict keys should be interned.
 
 
 **list comprehensions**
 
-  may be used to create allocated, initialized array. the array size must be
-  computable in advance, which implies that we don't allow an if clause.
+  may be used to create allocated, initialized arrays.
+  After list over-allocation was introduced, there is no longer any restriction.
 
 **functions**
 

Modified: pypy/dist/pypy/rpython/rlist.py
==============================================================================
--- pypy/dist/pypy/rpython/rlist.py	(original)
+++ pypy/dist/pypy/rpython/rlist.py	Mon Sep 12 02:36:42 2005
@@ -31,7 +31,7 @@
         from pypy.rpython import rrange
         listitem = self.listdef.listitem
         s_value = listitem.s_value
-        if listitem.range_step and not listitem.mutated:
+        if listitem.range_step is not None and not listitem.mutated:
             return rrange.RangeRepr(listitem.range_step)
         elif (s_value.__class__ is annmodel.SomeObject and s_value.knowntype == object):
             return robject.pyobj_repr

Modified: pypy/dist/pypy/rpython/rrange.py
==============================================================================
--- pypy/dist/pypy/rpython/rrange.py	(original)
+++ pypy/dist/pypy/rpython/rrange.py	Mon Sep 12 02:36:42 2005
@@ -13,20 +13,35 @@
 #    struct range {
 #        Signed start, stop;    // step is always constant
 #    }
+#
+#    struct rangest {
+#        Signed start, stop, step;    // rare case, for completeness
+#    }
 
 RANGE = GcStruct("range", ("start", Signed), ("stop", Signed))
 RANGEITER = GcStruct("range", ("next", Signed), ("stop", Signed))
 
+RANGEST = GcStruct("range", ("start", Signed), ("stop", Signed),("step", Signed))
+RANGESTITER = GcStruct("range", ("next", Signed), ("stop", Signed), ("step", Signed))
 
 class RangeRepr(Repr):
-    lowleveltype = Ptr(RANGE)
-
     def __init__(self, step):
         self.step = step
+        if step != 0:
+            self.lowleveltype = Ptr(RANGE)
+        else:
+            self.lowleveltype = Ptr(RANGEST)
+
+    def _getstep(self, v_rng, hop):
+        return hop.genop('getfield', [v_rng, hop.inputconst(Void, 'step')],
+                         resulttype=Signed)
 
     def rtype_len(self, hop):
         v_rng, = hop.inputargs(self)
-        cstep = hop.inputconst(Signed, self.step)
+        if self.step != 0:
+            cstep = hop.inputconst(Signed, self.step)
+        else:
+            cstep = self._getstep(v_rng, hop)
         return hop.gendirectcall(ll_rangelen, v_rng, cstep)
 
     def make_iterator_repr(self):
@@ -42,7 +57,10 @@
             spec = dum_nocheck
         v_func = hop.inputconst(Void, spec)
         v_lst, v_index = hop.inputargs(r_rng, Signed)
-        cstep = hop.inputconst(Signed, r_rng.step)
+        if r_rng.step != 0:
+            cstep = hop.inputconst(Signed, r_rng.step)
+        else:
+            cstep = r_rng._getstep(v_lst, hop)
         if hop.args_s[1].nonneg:
             llfn = ll_rangeitem_nonneg
         else:
@@ -94,6 +112,15 @@
     l.stop = stop
     return l
 
+def ll_newrangest(start, stop, step):
+    if step == 0:
+        raise ValueError
+    l = malloc(RANGEST)
+    l.start = start
+    l.stop = stop
+    l.step = step
+    return l
+
 def rtype_builtin_range(hop):
     vstep = hop.inputconst(Signed, 1)
     if hop.nb_args == 1:
@@ -103,10 +130,15 @@
         vstart, vstop = hop.inputargs(Signed, Signed)
     else:
         vstart, vstop, vstep = hop.inputargs(Signed, Signed, Signed)
-        assert isinstance(vstep, Constant)
-
+    const_step = isinstance(vstep, Constant)
+    if const_step and vstep.value == 0:
+        # not really needed, annotator catches it. Just in case...
+        raise TyperError("range cannot have a const step of zero")
     if isinstance(hop.r_result, RangeRepr):
-        return hop.gendirectcall(ll_newrange, vstart, vstop)
+        if const_step:
+            return hop.gendirectcall(ll_newrange, vstart, vstop)
+        else:
+            return hop.gendirectcall(ll_newrangest, vstart, vstop, vstep)
     else:
         # cannot build a RANGE object, needs a real list
         r_list = hop.r_result
@@ -116,6 +148,8 @@
 rtype_builtin_xrange = rtype_builtin_range
 
 def ll_range2list(LISTPTR, start, stop, step):
+    if step == 0:
+        raise ValueError
     length = _ll_rangelen(start, stop, step)
     l = ll_newlist(LISTPTR, length)
     idx = 0
@@ -131,10 +165,12 @@
 #  Iteration.
 
 class RangeIteratorRepr(IteratorRepr):
-    lowleveltype = Ptr(RANGEITER)
-
     def __init__(self, r_rng):
         self.r_rng = r_rng
+        if r_rng.step != 0:
+            self.lowleveltype = Ptr(RANGEITER)
+        else:
+            self.lowleveltype = Ptr(RANGESTITER)
 
     def newiter(self, hop):
         v_rng, = hop.inputargs(self.r_rng)
@@ -143,19 +179,24 @@
 
     def rtype_next(self, hop):
         v_iter, = hop.inputargs(self)
-        cstep = hop.inputconst(Signed, self.r_rng.step)
+        args = hop.inputconst(Signed, self.r_rng.step),
         if self.r_rng.step > 0:
             llfn = ll_rangenext_up
-        else:
+        elif self.r_rng.step < 0:
             llfn = ll_rangenext_down
+        else:
+            llfn = ll_rangenext_updown
+            args = ()
         hop.has_implicit_exception(StopIteration) # record that we know about it
         hop.exception_is_here()
-        return hop.gendirectcall(llfn, v_iter, cstep)
+        return hop.gendirectcall(llfn, v_iter, *args)
 
 def ll_rangeiter(ITERPTR, rng):
     iter = malloc(ITERPTR.TO)
     iter.next = rng.start
     iter.stop = rng.stop
+    if ITERPTR.TO is RANGESTITER:
+        iter.step = rng.step
     return iter
 
 def ll_rangenext_up(iter, step):
@@ -171,3 +212,10 @@
         raise StopIteration
     iter.next = next + step
     return next
+
+def ll_rangenext_updown(iter):
+    step = iter.step
+    if step > 0:
+        return ll_rangenext_up(iter, step)
+    else:
+        return ll_rangenext_down(iter, step)

Modified: pypy/dist/pypy/rpython/test/test_rrange.py
==============================================================================
--- pypy/dist/pypy/rpython/test/test_rrange.py	(original)
+++ pypy/dist/pypy/rpython/test/test_rrange.py	Mon Sep 12 02:36:42 2005
@@ -3,10 +3,14 @@
 from pypy.rpython.test.test_llinterp import interpret
 
 def test_rlist_range():
-    def test1(start, stop, step):
+    def test1(start, stop, step, varstep):
         expected = range(start, stop, step)
         length = len(expected)
-        l = ll_newrange(start, stop)
+        if varstep:
+            l = ll_newrangest(start, stop, step)
+            step = l.step
+        else:
+            l = ll_newrange(start,stop)
         assert ll_rangelen(l, step) == length
         lst = [ll_rangeitem(dum_nocheck, l, i, step) for i in range(length)]
         assert lst == expected
@@ -18,7 +22,8 @@
     for start in (-10, 0, 1, 10):
         for stop in (-8, 0, 4, 8, 25):
             for step in (1, 2, 3, -1, -2):
-                test1(start, stop, step)
+                for varstep in False,True:
+                    test1(start, stop, step, varstep)
 
 # ____________________________________________________________
 
@@ -76,3 +81,26 @@
     start, stop = 10, 17
     res = interpret(dummyfn, [start, stop])
     assert res == dummyfn(start, stop)
+
+def check_failed(func, *args):
+    try:
+        interpret(func, *args)
+    except:
+        return True
+    else:
+        return False
+
+def test_range_extra():
+    def failingfn_const():
+        r = range(10, 17, 0)
+        return r[-1]
+    assert check_failed(failingfn_const, [])
+
+    def failingfn_var(step):
+        r = range(10, 17, step)
+        return r[-1]
+    step = 3
+    res = interpret(failingfn_var, [step])
+    assert res == failingfn_var(step)
+    step = 0
+    assert check_failed(failingfn_var, [step])

Modified: pypy/dist/pypy/translator/c/test/test_typed.py
==============================================================================
--- pypy/dist/pypy/translator/c/test/test_typed.py	(original)
+++ pypy/dist/pypy/translator/c/test/test_typed.py	Mon Sep 12 02:36:42 2005
@@ -11,7 +11,7 @@
 
 class TestTypedTestCase(_TestAnnotatedTestCase):
 
-    def getcompiled(self, func):
+    def getcompiled(self, func, view=False):
         t = Translator(func, simplifying=True)
         # builds starting-types from func_defs 
         argstypelist = []
@@ -24,6 +24,8 @@
         a.simplify()
         t.specialize()
         t.checkgraphs()
+        if view:
+            t.view()
         return skip_missing_compiler(t.ccompile)
 
     def test_call_five(self):
@@ -367,4 +369,14 @@
         f = self.getcompiled(fn)
         assert f(0) == fn(0)
         assert f(-1) == fn(-1)
-        raises(IndexError, f, 42)
\ No newline at end of file
+        raises(IndexError, f, 42)
+
+    def test_range_step(self):
+        def fn(step=int):
+            r = range(10, 37, step)
+            # we always raise on step = 0
+            return r[-2]
+        f = self.getcompiled(fn)#, view=True)
+        assert f(1) == fn(1)
+        assert f(3) == fn(3)
+        raises(ValueError, f, 0)



More information about the Pypy-commit mailing list