[pypy-commit] pypy default: (fijal and mattip mostly) Implemented support for multidimensional arrays in numpypy, this support extends to most features, such as ufuncs.

alex_gaynor noreply at buildbot.pypy.org
Thu Nov 24 16:21:28 CET 2011


Author: Alex Gaynor <alex.gaynor at gmail.com>
Branch: 
Changeset: r49739:fcfe2115dc2c
Date: 2011-11-24 09:21 -0600
http://bitbucket.org/pypy/pypy/changeset/fcfe2115dc2c/

Log:	(fijal and mattip mostly) Implemented support for multidimensional
	arrays in numpypy, this support extends to most features, such as
	ufuncs.

diff --git a/py/_code/code.py b/py/_code/code.py
--- a/py/_code/code.py
+++ b/py/_code/code.py
@@ -307,7 +307,7 @@
                     self._striptext = 'AssertionError: '
         self._excinfo = tup
         self.type, self.value, tb = self._excinfo
-        self.typename = self.type.__name__
+        self.typename = getattr(self.type, "__name__", "???")
         self.traceback = py.code.Traceback(tb)
 
     def __repr__(self):
diff --git a/pypy/module/micronumpy/__init__.py b/pypy/module/micronumpy/__init__.py
--- a/pypy/module/micronumpy/__init__.py
+++ b/pypy/module/micronumpy/__init__.py
@@ -5,7 +5,7 @@
     applevel_name = 'numpypy'
 
     interpleveldefs = {
-        'array': 'interp_numarray.SingleDimArray',
+        'array': 'interp_numarray.NDimArray',
         'dtype': 'interp_dtype.W_Dtype',
         'ufunc': 'interp_ufuncs.W_Ufunc',
 
diff --git a/pypy/module/micronumpy/compile.py b/pypy/module/micronumpy/compile.py
--- a/pypy/module/micronumpy/compile.py
+++ b/pypy/module/micronumpy/compile.py
@@ -6,10 +6,10 @@
 from pypy.interpreter.baseobjspace import InternalSpaceCache, W_Root
 from pypy.module.micronumpy.interp_dtype import W_Float64Dtype, W_BoolDtype
 from pypy.module.micronumpy.interp_numarray import (Scalar, BaseArray,
-     descr_new_array, scalar_w, SingleDimArray)
+     descr_new_array, scalar_w, NDimArray)
 from pypy.module.micronumpy import interp_ufuncs
 from pypy.rlib.objectmodel import specialize
-
+import re
 
 class BogusBytecode(Exception):
     pass
@@ -23,11 +23,18 @@
 class WrongFunctionName(Exception):
     pass
 
+class TokenizerError(Exception):
+    pass
+
+class BadToken(Exception):
+    pass
+
 SINGLE_ARG_FUNCTIONS = ["sum", "prod", "max", "min", "all", "any", "unegative"]
 
 class FakeSpace(object):
     w_ValueError = None
     w_TypeError = None
+    w_IndexError = None
     w_None = None
 
     w_bool = "bool"
@@ -36,6 +43,7 @@
     w_list = "list"
     w_long = "long"
     w_tuple = 'tuple'
+    w_slice = "slice"
 
     def __init__(self):
         """NOT_RPYTHON"""
@@ -43,13 +51,26 @@
         self.w_float64dtype = W_Float64Dtype(self)
 
     def issequence_w(self, w_obj):
-        return isinstance(w_obj, ListObject) or isinstance(w_obj, SingleDimArray)
+        return isinstance(w_obj, ListObject) or isinstance(w_obj, NDimArray)
 
     def isinstance_w(self, w_obj, w_tp):
+        if w_obj.tp == w_tp:
+            return True
         return False
 
     def decode_index4(self, w_idx, size):
-        return (self.int_w(w_idx), 0, 0, 1)
+        if isinstance(w_idx, IntObject):
+            return (self.int_w(w_idx), 0, 0, 1)
+        else:
+            assert isinstance(w_idx, SliceObject)
+            start, stop, step = w_idx.start, w_idx.stop, w_idx.step
+            if step == 0:
+                return (0, size, 1, size)
+            if start < 0:
+                start += size
+            if stop < 0:
+                stop += size
+            return (start, stop, step, size//step)
 
     @specialize.argtype(1)
     def wrap(self, obj):
@@ -59,7 +80,9 @@
             return BoolObject(obj)
         elif isinstance(obj, int):
             return IntObject(obj)
-        raise Exception
+        elif isinstance(obj, W_Root):
+            return obj
+        raise NotImplementedError
 
     def newlist(self, items):
         return ListObject(items)
@@ -67,6 +90,7 @@
     def listview(self, obj):
         assert isinstance(obj, ListObject)
         return obj.items
+    fixedview = listview
 
     def float(self, w_obj):
         assert isinstance(w_obj, FloatObject)
@@ -107,6 +131,12 @@
         assert isinstance(what, tp)
         return what
 
+    def len_w(self, w_obj):
+        if isinstance(w_obj, ListObject):
+            return len(w_obj.items)
+        # XXX array probably
+        assert False
+
 class FloatObject(W_Root):
     tp = FakeSpace.w_float
     def __init__(self, floatval):
@@ -127,6 +157,13 @@
     def __init__(self, items):
         self.items = items
 
+class SliceObject(W_Root):
+    tp = FakeSpace.w_slice
+    def __init__(self, start, stop, step):
+        self.start = start
+        self.stop = stop
+        self.step = step
+
 class InterpreterState(object):
     def __init__(self, code):
         self.code = code
@@ -161,7 +198,7 @@
         interp.variables[self.name] = self.expr.execute(interp)
 
     def __repr__(self):
-        return "%% = %r" % (self.name, self.expr)
+        return "%r = %r" % (self.name, self.expr)
 
 class ArrayAssignment(Node):
     def __init__(self, name, index, expr):
@@ -171,8 +208,11 @@
 
     def execute(self, interp):
         arr = interp.variables[self.name]
-        w_index = self.index.execute(interp).eval(0).wrap(interp.space)
-        w_val = self.expr.execute(interp).eval(0).wrap(interp.space)
+        w_index = self.index.execute(interp).eval(arr.start_iter()).wrap(interp.space)
+        # cast to int
+        if isinstance(w_index, FloatObject):
+            w_index = IntObject(int(w_index.floatval))
+        w_val = self.expr.execute(interp).eval(arr.start_iter()).wrap(interp.space)
         arr.descr_setitem(interp.space, w_index, w_val)
 
     def __repr__(self):
@@ -180,7 +220,7 @@
 
 class Variable(Node):
     def __init__(self, name):
-        self.name = name
+        self.name = name.strip(" ")
 
     def execute(self, interp):
         return interp.variables[self.name]
@@ -196,11 +236,11 @@
 
     def execute(self, interp):
         w_lhs = self.lhs.execute(interp)
+        if isinstance(self.rhs, SliceConstant):
+            w_rhs = self.rhs.wrap(interp.space)
+        else:
+            w_rhs = self.rhs.execute(interp)
         assert isinstance(w_lhs, BaseArray)
-        if isinstance(self.rhs, SliceConstant):
-            # XXX interface has changed on multidim branch
-            raise NotImplementedError
-        w_rhs = self.rhs.execute(interp)
         if self.name == '+':
             w_res = w_lhs.descr_add(interp.space, w_rhs)
         elif self.name == '*':
@@ -209,12 +249,10 @@
             w_res = w_lhs.descr_sub(interp.space, w_rhs)            
         elif self.name == '->':
             if isinstance(w_rhs, Scalar):
-                index = int(interp.space.float_w(
-                    w_rhs.value.wrap(interp.space)))
-                dtype = interp.space.fromcache(W_Float64Dtype)
-                return Scalar(dtype, w_lhs.get_concrete().eval(index))
-            else:
-                raise NotImplementedError
+                w_rhs = w_rhs.eval(w_rhs.start_iter()).wrap(interp.space)
+                assert isinstance(w_rhs, FloatObject)
+                w_rhs = IntObject(int(w_rhs.floatval))
+            w_res = w_lhs.descr_getitem(interp.space, w_rhs)
         else:
             raise NotImplementedError
         if not isinstance(w_res, BaseArray):
@@ -248,7 +286,8 @@
         w_list = interp.space.newlist(
             [interp.space.wrap(float(i)) for i in range(self.v)])
         dtype = interp.space.fromcache(W_Float64Dtype)
-        return descr_new_array(interp.space, None, w_list, w_dtype=dtype)
+        return descr_new_array(interp.space, None, w_list, w_dtype=dtype,
+                               w_order=None)
 
     def __repr__(self):
         return 'Range(%s)' % self.v
@@ -270,17 +309,24 @@
     def execute(self, interp):
         w_list = self.wrap(interp.space)
         dtype = interp.space.fromcache(W_Float64Dtype)
-        return descr_new_array(interp.space, None, w_list, w_dtype=dtype)
+        return descr_new_array(interp.space, None, w_list, w_dtype=dtype,
+                               w_order=None)
 
     def __repr__(self):
         return "[" + ", ".join([repr(item) for item in self.items]) + "]"
 
 class SliceConstant(Node):
-    def __init__(self):
-        pass
+    def __init__(self, start, stop, step):
+        # no negative support for now
+        self.start = start
+        self.stop = stop
+        self.step = step
+
+    def wrap(self, space):
+        return SliceObject(self.start, self.stop, self.step)
 
     def __repr__(self):
-        return 'slice()'
+        return 'slice(%s,%s,%s)' % (self.start, self.stop, self.step)
 
 class Execute(Node):
     def __init__(self, expr):
@@ -294,7 +340,7 @@
 
 class FunctionCall(Node):
     def __init__(self, name, args):
-        self.name = name
+        self.name = name.strip(" ")
         self.args = args
 
     def __repr__(self):
@@ -337,95 +383,172 @@
         else:
             raise WrongFunctionName
 
+_REGEXES = [
+    ('-?[\d\.]+', 'number'),
+    ('\[', 'array_left'),
+    (':', 'colon'),
+    ('\w+', 'identifier'),
+    ('\]', 'array_right'),
+    ('(->)|[\+\-\*\/]', 'operator'),
+    ('=', 'assign'),
+    (',', 'coma'),
+    ('\|', 'pipe'),
+    ('\(', 'paren_left'),
+    ('\)', 'paren_right'),
+]
+REGEXES = []
+
+for r, name in _REGEXES:
+    REGEXES.append((re.compile(r' *(' + r + ')'), name))
+del _REGEXES
+
+class Token(object):
+    def __init__(self, name, v):
+        self.name = name
+        self.v = v
+
+    def __repr__(self):
+        return '(%s, %s)' % (self.name, self.v)
+
+empty = Token('', '')
+
+class TokenStack(object):
+    def __init__(self, tokens):
+        self.tokens = tokens
+        self.c = 0
+
+    def pop(self):
+        token = self.tokens[self.c]
+        self.c += 1
+        return token
+
+    def get(self, i):
+        if self.c + i >= len(self.tokens):
+            return empty
+        return self.tokens[self.c + i]
+
+    def remaining(self):
+        return len(self.tokens) - self.c
+
+    def push(self):
+        self.c -= 1
+
+    def __repr__(self):
+        return repr(self.tokens[self.c:])
+
 class Parser(object):
-    def parse_identifier(self, id):
-        id = id.strip(" ")
-        #assert id.isalpha()
-        return Variable(id)
+    def tokenize(self, line):
+        tokens = []
+        while True:
+            for r, name in REGEXES:
+                m = r.match(line)
+                if m is not None:
+                    g = m.group(0)
+                    tokens.append(Token(name, g))
+                    line = line[len(g):]
+                    if not line:
+                        return TokenStack(tokens)
+                    break
+            else:
+                raise TokenizerError(line)
 
-    def parse_expression(self, expr):
-        tokens = [i for i in expr.split(" ") if i]
-        if len(tokens) == 1:
-            return self.parse_constant_or_identifier(tokens[0])
+    def parse_number_or_slice(self, tokens):
+        start_tok = tokens.pop()
+        if start_tok.name == 'colon':
+            start = 0
+        else:
+            if tokens.get(0).name != 'colon':
+                return FloatConstant(start_tok.v)
+            start = int(start_tok.v)
+            tokens.pop()
+        if not tokens.get(0).name in ['colon', 'number']:
+            stop = -1
+            step = 1
+        else:
+            next = tokens.pop()
+            if next.name == 'colon':
+                stop = -1
+                step = int(tokens.pop().v)
+            else:
+                stop = int(next.v)
+                if tokens.get(0).name == 'colon':
+                    tokens.pop()
+                    step = int(tokens.pop().v)
+                else:
+                    step = 1
+        return SliceConstant(start, stop, step)
+            
+        
+    def parse_expression(self, tokens):
         stack = []
-        tokens.reverse()
-        while tokens:
+        while tokens.remaining():
             token = tokens.pop()
-            if token == ')':
-                raise NotImplementedError
-            elif self.is_identifier_or_const(token):
-                if stack:
-                    name = stack.pop().name
-                    lhs = stack.pop()
-                    rhs = self.parse_constant_or_identifier(token)
-                    stack.append(Operator(lhs, name, rhs))
+            if token.name == 'identifier':
+                if tokens.remaining() and tokens.get(0).name == 'paren_left':
+                    stack.append(self.parse_function_call(token.v, tokens))
                 else:
-                    stack.append(self.parse_constant_or_identifier(token))
+                    stack.append(Variable(token.v))
+            elif token.name == 'array_left':
+                stack.append(ArrayConstant(self.parse_array_const(tokens)))
+            elif token.name == 'operator':
+                stack.append(Variable(token.v))
+            elif token.name == 'number' or token.name == 'colon':
+                tokens.push()
+                stack.append(self.parse_number_or_slice(tokens))
+            elif token.name == 'pipe':
+                stack.append(RangeConstant(tokens.pop().v))
+                end = tokens.pop()
+                assert end.name == 'pipe'
             else:
-                stack.append(Variable(token))
-        assert len(stack) == 1
-        return stack[-1]
+                tokens.push()
+                break
+        stack.reverse()
+        lhs = stack.pop()
+        while stack:
+            op = stack.pop()
+            assert isinstance(op, Variable)
+            rhs = stack.pop()
+            lhs = Operator(lhs, op.name, rhs)
+        return lhs
 
-    def parse_constant(self, v):
-        lgt = len(v)-1
-        assert lgt >= 0
-        if ':' in v:
-            # a slice
-            assert v == ':'
-            return SliceConstant()
-        if v[0] == '[':
-            return ArrayConstant([self.parse_constant(elem)
-                                  for elem in v[1:lgt].split(",")])
-        if v[0] == '|':
-            return RangeConstant(v[1:lgt])
-        return FloatConstant(v)
-
-    def is_identifier_or_const(self, v):
-        c = v[0]
-        if ((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or
-            (c >= '0' and c <= '9') or c in '-.[|:'):
-            if v == '-' or v == "->":
-                return False
-            return True
-        return False
-
-    def parse_function_call(self, v):
-        l = v.split('(')
-        assert len(l) == 2
-        name = l[0]
-        cut = len(l[1]) - 1
-        assert cut >= 0
-        args = [self.parse_constant_or_identifier(id)
-                for id in l[1][:cut].split(",")]
+    def parse_function_call(self, name, tokens):
+        args = []
+        tokens.pop() # lparen
+        while tokens.get(0).name != 'paren_right':
+            args.append(self.parse_expression(tokens))
         return FunctionCall(name, args)
 
-    def parse_constant_or_identifier(self, v):
-        c = v[0]
-        if (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z'):
-            if '(' in v:
-                return self.parse_function_call(v)
-            return self.parse_identifier(v)
-        return self.parse_constant(v)
-
-    def parse_array_subscript(self, v):
-        v = v.strip(" ")
-        l = v.split("[")
-        lgt = len(l[1]) - 1
-        assert lgt >= 0
-        rhs = self.parse_constant_or_identifier(l[1][:lgt])
-        return l[0], rhs
+    def parse_array_const(self, tokens):
+        elems = []
+        while True:
+            token = tokens.pop()
+            if token.name == 'number':
+                elems.append(FloatConstant(token.v))
+            elif token.name == 'array_left':
+                elems.append(ArrayConstant(self.parse_array_const(tokens)))
+            else:
+                raise BadToken()
+            token = tokens.pop()
+            if token.name == 'array_right':
+                return elems
+            assert token.name == 'coma'
         
-    def parse_statement(self, line):
-        if '=' in line:
-            lhs, rhs = line.split("=")
-            lhs = lhs.strip(" ")
-            if '[' in lhs:
-                name, index = self.parse_array_subscript(lhs)
-                return ArrayAssignment(name, index, self.parse_expression(rhs))
-            else: 
-                return Assignment(lhs, self.parse_expression(rhs))
-        else:
-            return Execute(self.parse_expression(line))
+    def parse_statement(self, tokens):
+        if (tokens.get(0).name == 'identifier' and
+            tokens.get(1).name == 'assign'):
+            lhs = tokens.pop().v
+            tokens.pop()
+            rhs = self.parse_expression(tokens)
+            return Assignment(lhs, rhs)
+        elif (tokens.get(0).name == 'identifier' and
+              tokens.get(1).name == 'array_left'):
+            name = tokens.pop().v
+            tokens.pop()
+            index = self.parse_expression(tokens)
+            tokens.pop()
+            tokens.pop()
+            return ArrayAssignment(name, index, self.parse_expression(tokens))
+        return Execute(self.parse_expression(tokens))
 
     def parse(self, code):
         statements = []
@@ -434,7 +557,8 @@
                 line = line.split('#', 1)[0]
             line = line.strip(" ")
             if line:
-                statements.append(self.parse_statement(line))
+                tokens = self.tokenize(line)
+                statements.append(self.parse_statement(tokens))
         return Code(statements)
 
 def numpy_compile(code):
diff --git a/pypy/module/micronumpy/interp_numarray.py b/pypy/module/micronumpy/interp_numarray.py
--- a/pypy/module/micronumpy/interp_numarray.py
+++ b/pypy/module/micronumpy/interp_numarray.py
@@ -1,45 +1,347 @@
 from pypy.interpreter.baseobjspace import Wrappable
-from pypy.interpreter.error import OperationError
-from pypy.interpreter.gateway import interp2app, unwrap_spec
+from pypy.interpreter.error import OperationError, operationerrfmt
+from pypy.interpreter.gateway import interp2app, unwrap_spec, NoneNotWrapped
 from pypy.interpreter.typedef import TypeDef, GetSetProperty
 from pypy.module.micronumpy import interp_ufuncs, interp_dtype, signature
 from pypy.rlib import jit
 from pypy.rpython.lltypesystem import lltype
 from pypy.tool.sourcetools import func_with_new_name
+from pypy.rlib.rstring import StringBuilder
+from pypy.rlib.objectmodel import instantiate
 
 
-numpy_driver = jit.JitDriver(greens = ['signature'],
-                             reds = ['result_size', 'i', 'self', 'result'])
-all_driver = jit.JitDriver(greens=['signature'], reds=['i', 'size', 'self', 'dtype'])
-any_driver = jit.JitDriver(greens=['signature'], reds=['i', 'size', 'self', 'dtype'])
-slice_driver = jit.JitDriver(greens=['signature'], reds=['i', 'j', 'step', 'stop', 'source', 'dest'])
+numpy_driver = jit.JitDriver(
+    greens=['shapelen', 'signature'],
+    reds=['result_size', 'i', 'ri', 'self', 'result']
+)
+all_driver = jit.JitDriver(
+    greens=['shapelen', 'signature'],
+    reds=['i', 'self', 'dtype']
+)
+any_driver = jit.JitDriver(
+    greens=['shapelen', 'signature'],
+    reds=['i', 'self', 'dtype']
+)
+slice_driver = jit.JitDriver(
+    greens=['shapelen', 'signature'],
+    reds=['self', 'source', 'source_iter', 'res_iter']
+)
 
-def descr_new_array(space, w_subtype, w_size_or_iterable, w_dtype=None):
-    l = space.listview(w_size_or_iterable)
+def _find_shape_and_elems(space, w_iterable):
+    shape = [space.len_w(w_iterable)]
+    batch = space.listview(w_iterable)
+    while True:
+        new_batch = []
+        if not batch:
+            return shape, []
+        if not space.issequence_w(batch[0]):
+            for elem in batch:
+                if space.issequence_w(elem):
+                    raise OperationError(space.w_ValueError, space.wrap(
+                        "setting an array element with a sequence"))
+            return shape, batch
+        size = space.len_w(batch[0])
+        for w_elem in batch:
+            if not space.issequence_w(w_elem) or space.len_w(w_elem) != size:
+                raise OperationError(space.w_ValueError, space.wrap(
+                    "setting an array element with a sequence"))
+            new_batch += space.listview(w_elem)
+        shape.append(size)
+        batch = new_batch
+
+def shape_agreement(space, shape1, shape2):
+    ret = _shape_agreement(shape1, shape2)
+    if len(ret) < max(len(shape1), len(shape2)):
+        raise OperationError(space.w_ValueError,
+            space.wrap("operands could not be broadcast together with shapes (%s) (%s)" % (
+                ",".join([str(x) for x in shape1]),
+                ",".join([str(x) for x in shape2]),
+            ))
+        )
+    return ret
+
+def _shape_agreement(shape1, shape2):
+    """ Checks agreement about two shapes with respect to broadcasting. Returns
+    the resulting shape.
+    """
+    lshift = 0
+    rshift = 0
+    if len(shape1) > len(shape2):
+        m = len(shape1)
+        n = len(shape2)
+        rshift = len(shape2) - len(shape1)
+        remainder = shape1
+    else:
+        m = len(shape2)
+        n = len(shape1)
+        lshift = len(shape1) - len(shape2)
+        remainder = shape2
+    endshape = [0] * m
+    indices1 = [True] * m
+    indices2 = [True] * m
+    for i in range(m - 1, m - n - 1, -1):
+        left = shape1[i + lshift]
+        right = shape2[i + rshift]
+        if left == right:
+            endshape[i] = left
+        elif left == 1:
+            endshape[i] = right
+            indices1[i + lshift] = False
+        elif right == 1:
+            endshape[i] = left
+            indices2[i + rshift] = False
+        else:
+            return []
+            #raise OperationError(space.w_ValueError, space.wrap(
+            #    "frames are not aligned"))
+    for i in range(m - n):
+        endshape[i] = remainder[i]
+    return endshape
+
+def descr_new_array(space, w_subtype, w_item_or_iterable, w_dtype=None,
+                    w_order=NoneNotWrapped):
+    # find scalar
+    if not space.issequence_w(w_item_or_iterable):
+        w_dtype = interp_ufuncs.find_dtype_for_scalar(space,
+                                                      w_item_or_iterable,
+                                                      w_dtype)
+        dtype = space.interp_w(interp_dtype.W_Dtype,
+           space.call_function(space.gettypefor(interp_dtype.W_Dtype), w_dtype))
+        return scalar_w(space, dtype, w_item_or_iterable)
+    if w_order is None:
+        order = 'C'
+    else:
+        order = space.str_w(w_order)
+        if order != 'C':  # or order != 'F':
+            raise operationerrfmt(space.w_ValueError, "Unknown order: %s",
+                                  order)
+    shape, elems_w = _find_shape_and_elems(space, w_item_or_iterable)
+    # they come back in C order
+    size = len(elems_w)
     if space.is_w(w_dtype, space.w_None):
         w_dtype = None
-        for w_item in l:
-            w_dtype = interp_ufuncs.find_dtype_for_scalar(space, w_item, w_dtype)
+        for w_elem in elems_w:
+            w_dtype = interp_ufuncs.find_dtype_for_scalar(space, w_elem,
+                                                          w_dtype)
             if w_dtype is space.fromcache(interp_dtype.W_Float64Dtype):
                 break
-        if w_dtype is None:
-            w_dtype = space.w_None
-
+    if w_dtype is None:
+        w_dtype = space.w_None
     dtype = space.interp_w(interp_dtype.W_Dtype,
         space.call_function(space.gettypefor(interp_dtype.W_Dtype), w_dtype)
     )
-    arr = SingleDimArray(len(l), dtype=dtype)
-    i = 0
-    for w_elem in l:
-        dtype.setitem_w(space, arr.storage, i, w_elem)
-        i += 1
+    arr = NDimArray(size, shape[:], dtype=dtype, order=order)
+    shapelen = len(shape)
+    arr_iter = arr.start_iter(arr.shape)
+    for i in range(len(elems_w)):
+        w_elem = elems_w[i]
+        dtype.setitem_w(space, arr.storage, arr_iter.offset, w_elem)
+        arr_iter = arr_iter.next(shapelen)
     return arr
 
+# Iterators for arrays
+# --------------------
+# all those iterators with the exception of BroadcastIterator iterate over the
+# entire array in C order (the last index changes the fastest). This will
+# yield all elements. Views iterate over indices and look towards strides and
+# backstrides to find the correct position. Notably the offset between
+# x[..., i + 1] and x[..., i] will be strides[-1]. Offset between
+# x[..., k + 1, 0] and x[..., k, i_max] will be backstrides[-2] etc.
+
+# BroadcastIterator works like that, but for indexes that don't change source
+# in the original array, strides[i] == backstrides[i] == 0
+
+class BaseIterator(object):
+    def next(self, shapelen):
+        raise NotImplementedError
+
+    def done(self):
+        raise NotImplementedError
+
+    def get_offset(self):
+        raise NotImplementedError
+
+class ArrayIterator(BaseIterator):
+    def __init__(self, size):
+        self.offset = 0
+        self.size = size
+
+    def next(self, shapelen):
+        arr = instantiate(ArrayIterator)
+        arr.size = self.size
+        arr.offset = self.offset + 1
+        return arr
+
+    def done(self):
+        return self.offset >= self.size
+
+    def get_offset(self):
+        return self.offset
+
+class ViewIterator(BaseIterator):
+    def __init__(self, arr):
+        self.indices = [0] * len(arr.shape)
+        self.offset  = arr.start
+        self.arr     = arr
+        self._done   = False
+
+    @jit.unroll_safe
+    def next(self, shapelen):
+        offset = self.offset
+        indices = [0] * shapelen
+        for i in range(shapelen):
+            indices[i] = self.indices[i]
+        done = False
+        for i in range(shapelen - 1, -1, -1):
+            if indices[i] < self.arr.shape[i] - 1:
+                indices[i] += 1
+                offset += self.arr.strides[i]
+                break
+            else:
+                indices[i] = 0
+                offset -= self.arr.backstrides[i]
+        else:
+            done = True
+        res = instantiate(ViewIterator)
+        res.offset = offset
+        res.indices = indices
+        res.arr = self.arr
+        res._done = done
+        return res
+
+    def done(self):
+        return self._done
+
+    def get_offset(self):
+        return self.offset
+
+class BroadcastIterator(BaseIterator):
+    '''Like a view iterator, but will repeatedly access values
+       for all iterations across a res_shape, folding the offset
+       using mod() arithmetic
+    '''
+    def __init__(self, arr, res_shape):
+        self.indices = [0] * len(res_shape)
+        self.offset  = arr.start
+        #strides are 0 where original shape==1
+        self.strides = []
+        self.backstrides = []
+        for i in range(len(arr.shape)):
+            if arr.shape[i]==1:
+                self.strides.append(0)
+                self.backstrides.append(0)
+            else:
+                self.strides.append(arr.strides[i])
+                self.backstrides.append(arr.backstrides[i])
+        self.res_shape = res_shape
+        self.strides = [0] * (len(res_shape) - len(arr.shape)) + self.strides
+        self.backstrides = [0] * (len(res_shape) - len(arr.shape)) + self.backstrides
+        self._done = False
+
+    @jit.unroll_safe
+    def next(self, shapelen):
+        offset = self.offset
+        indices = [0] * shapelen
+        _done = False
+        for i in range(shapelen):
+            indices[i] = self.indices[i]
+        for i in range(shapelen - 1, -1, -1):
+            if indices[i] < self.res_shape[i] - 1:
+                indices[i] += 1
+                offset += self.strides[i]
+                break
+            else:
+                indices[i] = 0
+                offset -= self.backstrides[i]
+        else:
+            _done = True
+        res = instantiate(BroadcastIterator)
+        res.indices = indices
+        res.offset = offset
+        res._done = _done
+        res.strides = self.strides
+        res.backstrides = self.backstrides
+        res.res_shape = self.res_shape
+        return res
+
+    def done(self):
+        return self._done
+
+    def get_offset(self):
+        return self.offset
+
+class Call2Iterator(BaseIterator):
+    def __init__(self, left, right):
+        self.left = left
+        self.right = right
+
+    def next(self, shapelen):
+        return Call2Iterator(self.left.next(shapelen),
+                             self.right.next(shapelen))
+
+    def done(self):
+        if isinstance(self.left, ConstantIterator):
+            return self.right.done()
+        return self.left.done()
+
+    def get_offset(self):
+        if isinstance(self.left, ConstantIterator):
+            return self.right.get_offset()
+        return self.left.get_offset()
+
+class Call1Iterator(BaseIterator):
+    def __init__(self, child):
+        self.child = child
+
+    def next(self, shapelen):
+        return Call1Iterator(self.child.next(shapelen))
+
+    def done(self):
+        return self.child.done()
+
+    def get_offset(self):
+        return self.child.get_offset()
+
+class ConstantIterator(BaseIterator):
+    def next(self, shapelen):
+        return self
+
+    def done(self):
+        return False
+
+    def get_offset(self):
+        return 0
+
 class BaseArray(Wrappable):
-    _attrs_ = ["invalidates", "signature"]
+    _attrs_ = ["invalidates", "signature", "shape", "strides", "backstrides",
+               "start", 'order']
 
-    def __init__(self):
+    _immutable_fields_ = ['shape[*]', "strides[*]", "backstrides[*]", 'start',
+                          "order"]
+
+    strides = None
+    start = 0
+
+    def __init__(self, shape, order):
         self.invalidates = []
+        self.shape = shape
+        self.order = order
+        if self.strides is None:
+            strides = []
+            backstrides = []
+            s = 1
+            shape_rev = shape[:]
+            if order == 'C':
+                shape_rev.reverse()
+            for sh in shape_rev:
+                strides.append(s)
+                backstrides.append(s * (sh - 1))
+                s *= sh
+            if order == 'C':
+                strides.reverse()
+                backstrides.reverse()
+            self.strides = strides[:]
+            self.backstrides = backstrides[:]
 
     def invalidated(self):
         if self.invalidates:
@@ -99,7 +401,7 @@
 
     def _reduce_ufunc_impl(ufunc_name):
         def impl(self, space):
-            return getattr(interp_ufuncs.get(space), ufunc_name).descr_reduce(space, self)
+            return getattr(interp_ufuncs.get(space), ufunc_name).reduce(space, self, multidim=True)
         return func_with_new_name(impl, "reduce_%s_impl" % ufunc_name)
 
     descr_sum = _reduce_ufunc_impl("add")
@@ -108,23 +410,30 @@
     descr_min = _reduce_ufunc_impl("minimum")
 
     def _reduce_argmax_argmin_impl(op_name):
-        reduce_driver = jit.JitDriver(greens=['signature'],
-                         reds = ['i', 'size', 'result', 'self', 'cur_best', 'dtype'])
-        def loop(self, size):
+        reduce_driver = jit.JitDriver(
+            greens=['shapelen', 'signature'],
+            reds=['result', 'idx', 'i', 'self', 'cur_best', 'dtype']
+        )
+        def loop(self):
+            i = self.start_iter()
+            cur_best = self.eval(i)
+            shapelen = len(self.shape)
+            i = i.next(shapelen)
+            dtype = self.find_dtype()
             result = 0
-            cur_best = self.eval(0)
-            i = 1
-            dtype = self.find_dtype()
-            while i < size:
+            idx = 1
+            while not i.done():
                 reduce_driver.jit_merge_point(signature=self.signature,
+                                              shapelen=shapelen,
                                               self=self, dtype=dtype,
-                                              size=size, i=i, result=result,
+                                              i=i, result=result, idx=idx,
                                               cur_best=cur_best)
                 new_best = getattr(dtype, op_name)(cur_best, self.eval(i))
                 if dtype.ne(new_best, cur_best):
-                    result = i
+                    result = idx
                     cur_best = new_best
-                i += 1
+                i = i.next(shapelen)
+                idx += 1
             return result
         def impl(self, space):
             size = self.find_size()
@@ -132,31 +441,35 @@
                 raise OperationError(space.w_ValueError,
                     space.wrap("Can't call %s on zero-size arrays" \
                             % op_name))
-            return space.wrap(loop(self, size))
+            return space.wrap(loop(self))
         return func_with_new_name(impl, "reduce_arg%s_impl" % op_name)
 
     def _all(self):
-        size = self.find_size()
         dtype = self.find_dtype()
-        i = 0
-        while i < size:
-            all_driver.jit_merge_point(signature=self.signature, self=self, dtype=dtype, size=size, i=i)
+        i = self.start_iter()
+        shapelen = len(self.shape)
+        while not i.done():
+            all_driver.jit_merge_point(signature=self.signature,
+                                       shapelen=shapelen, self=self,
+                                       dtype=dtype, i=i)
             if not dtype.bool(self.eval(i)):
                 return False
-            i += 1
+            i = i.next(shapelen)
         return True
     def descr_all(self, space):
         return space.wrap(self._all())
 
     def _any(self):
-        size = self.find_size()
         dtype = self.find_dtype()
-        i = 0
-        while i < size:
-            any_driver.jit_merge_point(signature=self.signature, self=self, size=size, dtype=dtype, i=i)
+        i = self.start_iter()
+        shapelen = len(self.shape)
+        while not i.done():
+            any_driver.jit_merge_point(signature=self.signature,
+                                       shapelen=shapelen, self=self,
+                                       dtype=dtype, i=i)
             if dtype.bool(self.eval(i)):
                 return True
-            i += 1
+            i = i.next(shapelen)
         return False
     def descr_any(self, space):
         return space.wrap(self._any())
@@ -173,25 +486,6 @@
             assert isinstance(w_res, BaseArray)
             return w_res.descr_sum(space)
 
-    def _getnums(self, comma):
-        dtype = self.find_dtype()
-        if self.find_size() > 1000:
-            nums = [
-                dtype.str_format(self.eval(index))
-                for index in range(3)
-            ]
-            nums.append("..." + "," * comma)
-            nums.extend([
-                dtype.str_format(self.eval(index))
-                for index in range(self.find_size() - 3, self.find_size())
-            ])
-        else:
-            nums = [
-                dtype.str_format(self.eval(index))
-                for index in range(self.find_size())
-            ]
-        return nums
-
     def get_concrete(self):
         raise NotImplementedError
 
@@ -199,7 +493,7 @@
         return space.wrap(self.find_dtype())
 
     def descr_get_shape(self, space):
-        return space.newtuple([self.descr_len(space)])
+        return space.newtuple([space.wrap(i) for i in self.shape])
 
     def descr_get_size(self, space):
         return space.wrap(self.find_size())
@@ -211,89 +505,263 @@
         return self.get_concrete().descr_len(space)
 
     def descr_repr(self, space):
-        # Simple implementation so that we can see the array. Needs work.
+        res = StringBuilder()
+        res.append("array(")
         concrete = self.get_concrete()
-        res = "array([" + ", ".join(concrete._getnums(False)) + "]"
         dtype = concrete.find_dtype()
+        if not concrete.find_size():
+            res.append('[]')
+            if len(self.shape) > 1:
+                # An empty slice reports its shape
+                res.append(", shape=(")
+                self_shape = str(self.shape)
+                res.append_slice(str(self_shape), 1, len(self_shape) - 1)
+                res.append(')')
+        else:
+            concrete.to_str(space, 1, res, indent='       ')
         if (dtype is not space.fromcache(interp_dtype.W_Float64Dtype) and
-            dtype is not space.fromcache(interp_dtype.W_Int64Dtype)) or not self.find_size():
-            res += ", dtype=" + dtype.name
-        res += ")"
-        return space.wrap(res)
+            dtype is not space.fromcache(interp_dtype.W_Int64Dtype)) or \
+            not self.find_size():
+            res.append(", dtype=" + dtype.name)
+        res.append(")")
+        return space.wrap(res.build())
+
+    def to_str(self, space, comma, builder, indent=' ', use_ellipsis=False):
+        '''Modifies builder with a representation of the array/slice
+        The items will be seperated by a comma if comma is 1
+        Multidimensional arrays/slices will span a number of lines,
+        each line will begin with indent.
+        '''
+        size = self.find_size()
+        if size < 1:
+            builder.append('[]')
+            return
+        if size > 1000:
+            # Once this goes True it does not go back to False for recursive
+            # calls
+            use_ellipsis = True
+        dtype = self.find_dtype()
+        ndims = len(self.shape)
+        i = 0
+        start = True
+        builder.append('[')
+        if ndims > 1:
+            if use_ellipsis:
+                for i in range(3):
+                    if start:
+                        start = False
+                    else:
+                        builder.append(',' * comma + '\n')
+                        if ndims == 3:
+                            builder.append('\n' + indent)
+                        else:
+                            builder.append(indent)
+                    # create_slice requires len(chunks) > 1 in order to reduce
+                    # shape
+                    view = self.create_slice(space, [(i, 0, 0, 1), (0, self.shape[1], 1, self.shape[1])])
+                    view.to_str(space, comma, builder, indent=indent + ' ', use_ellipsis=use_ellipsis)
+                builder.append('\n' + indent + '..., ')
+                i = self.shape[0] - 3
+            while i < self.shape[0]:
+                if start:
+                    start = False
+                else:
+                    builder.append(',' * comma + '\n')
+                    if ndims == 3:
+                        builder.append('\n' + indent)
+                    else:
+                        builder.append(indent)
+                # create_slice requires len(chunks) > 1 in order to reduce
+                # shape
+                view = self.create_slice(space, [(i, 0, 0, 1), (0, self.shape[1], 1, self.shape[1])])
+                view.to_str(space, comma, builder, indent=indent + ' ', use_ellipsis=use_ellipsis)
+                i += 1
+        elif ndims == 1:
+            spacer = ',' * comma + ' '
+            item = self.start
+            # An iterator would be a nicer way to walk along the 1d array, but
+            # how do I reset it if printing ellipsis? iterators have no
+            # "set_offset()"
+            i = 0
+            if use_ellipsis:
+                for i in range(3):
+                    if start:
+                        start = False
+                    else:
+                        builder.append(spacer)
+                    builder.append(dtype.str_format(self.getitem(item)))
+                    item += self.strides[0]
+                # Add a comma only if comma is False - this prevents adding two
+                # commas
+                builder.append(spacer + '...' + ',' * (1 - comma))
+                # Ugly, but can this be done with an iterator?
+                item = self.start + self.backstrides[0] - 2 * self.strides[0]
+                i = self.shape[0] - 3
+            while i < self.shape[0]:
+                if start:
+                    start = False
+                else:
+                    builder.append(spacer)
+                builder.append(dtype.str_format(self.getitem(item)))
+                item += self.strides[0]
+                i += 1
+        else:
+            builder.append('[')
+        builder.append(']')
 
     def descr_str(self, space):
-        # Simple implementation so that we can see the array. Needs work.
+        ret = StringBuilder()
         concrete = self.get_concrete()
-        return space.wrap("[" + " ".join(concrete._getnums(True)) + "]")
+        concrete.to_str(space, 0, ret, ' ')
+        return space.wrap(ret.build())
+
+    def _index_of_single_item(self, space, w_idx):
+        if space.isinstance_w(w_idx, space.w_int):
+            idx = space.int_w(w_idx)
+            if not self.shape:
+                if idx != 0:
+                    raise OperationError(space.w_IndexError,
+                                         space.wrap("index out of range"))
+                return 0
+            if idx < 0:
+                idx = self.shape[0] + idx
+            if idx < 0 or idx >= self.shape[0]:
+                raise OperationError(space.w_IndexError,
+                                     space.wrap("index out of range"))
+            return self.start + idx * self.strides[0]
+        index = [space.int_w(w_item)
+                 for w_item in space.fixedview(w_idx)]
+        item = self.start
+        for i in range(len(index)):
+            v = index[i]
+            if v < 0:
+                v += self.shape[i]
+            if v < 0 or v >= self.shape[i]:
+                raise operationerrfmt(space.w_IndexError,
+                    "index (%d) out of range (0<=index<%d", i, self.shape[i],
+                )
+            item += v * self.strides[i]
+        return item
+
+    def _single_item_result(self, space, w_idx):
+        """ The result of getitem/setitem is a single item if w_idx
+        is a list of scalars that match the size of shape
+        """
+        shape_len = len(self.shape)
+        if shape_len == 0:
+            if not space.isinstance_w(w_idx, space.w_int):
+                raise OperationError(space.w_IndexError, space.wrap(
+                    "wrong index"))
+            return True
+        if shape_len == 1:
+            if space.isinstance_w(w_idx, space.w_int):
+                return True
+            if space.isinstance_w(w_idx, space.w_slice):
+                return False
+        elif (space.isinstance_w(w_idx, space.w_slice) or
+              space.isinstance_w(w_idx, space.w_int)):
+            return False
+        lgt = space.len_w(w_idx)
+        if lgt > shape_len:
+            raise OperationError(space.w_IndexError,
+                                 space.wrap("invalid index"))
+        if lgt < shape_len:
+            return False
+        for w_item in space.fixedview(w_idx):
+            if space.isinstance_w(w_item, space.w_slice):
+                return False
+        return True
+
+    def _prepare_slice_args(self, space, w_idx):
+        if (space.isinstance_w(w_idx, space.w_int) or
+            space.isinstance_w(w_idx, space.w_slice)):
+            return [space.decode_index4(w_idx, self.shape[0])]
+        return [space.decode_index4(w_item, self.shape[i]) for i, w_item in
+                enumerate(space.fixedview(w_idx))]
 
     def descr_getitem(self, space, w_idx):
-        # TODO: indexing by arrays and lists
-        if space.isinstance_w(w_idx, space.w_tuple):
-            length = space.len_w(w_idx)
-            if length == 0:
-                return space.wrap(self)
-            if length > 1: # only one dimension for now.
-                raise OperationError(space.w_IndexError,
-                                     space.wrap("invalid index"))
-            w_idx = space.getitem(w_idx, space.wrap(0))
-        start, stop, step, slice_length = space.decode_index4(w_idx, self.find_size())
-        if step == 0:
-            # Single index
-            return self.get_concrete().eval(start).wrap(space)
-        else:
-            # Slice
-            new_sig = signature.Signature.find_sig([
-                SingleDimSlice.signature, self.signature
-            ])
-            res = SingleDimSlice(start, stop, step, slice_length, self, new_sig)
-            return space.wrap(res)
+        if self._single_item_result(space, w_idx):
+            concrete = self.get_concrete()
+            item = concrete._index_of_single_item(space, w_idx)
+            return concrete.getitem(item).wrap(space)
+        chunks = self._prepare_slice_args(space, w_idx)
+        return space.wrap(self.create_slice(space, chunks))
 
     def descr_setitem(self, space, w_idx, w_value):
-        # TODO: indexing by arrays and lists
         self.invalidated()
-        if space.isinstance_w(w_idx, space.w_tuple):
-            length = space.len_w(w_idx)
-            if length > 1: # only one dimension for now.
-                raise OperationError(space.w_IndexError,
-                                     space.wrap("invalid index"))
-            if length == 0:
-                w_idx = space.newslice(space.wrap(0),
-                                      space.wrap(self.find_size()),
-                                      space.wrap(1))
+        concrete = self.get_concrete()
+        if self._single_item_result(space, w_idx):
+            item = concrete._index_of_single_item(space, w_idx)
+            concrete.setitem_w(space, item, w_value)
+            return
+        if isinstance(w_value, BaseArray):
+            # for now we just copy if setting part of an array from
+            # part of itself. can be improved.
+            if (concrete.get_root_storage() ==
+                w_value.get_concrete().get_root_storage()):
+                w_value = space.call_function(space.gettypefor(BaseArray), w_value)
+                assert isinstance(w_value, BaseArray)
+        else:
+            w_value = convert_to_array(space, w_value)
+        chunks = self._prepare_slice_args(space, w_idx)
+        view = self.create_slice(space, chunks)
+        view.setslice(space, w_value)
+
+    def create_slice(self, space, chunks):
+        if len(chunks) == 1:
+            start, stop, step, lgt = chunks[0]
+            if step == 0:
+                shape = self.shape[1:]
+                strides = self.strides[1:]
+                backstrides = self.backstrides[1:]
             else:
-                w_idx = space.getitem(w_idx, space.wrap(0))
-        start, stop, step, slice_length = space.decode_index4(w_idx,
-                                                              self.find_size())
-        if step == 0:
-            # Single index
-            self.get_concrete().setitem_w(space, start, w_value)
+                shape = [lgt] + self.shape[1:]
+                strides = [self.strides[0] * step] + self.strides[1:]
+                backstrides = [(lgt - 1) * self.strides[0] * step] + self.backstrides[1:]
+            start *= self.strides[0]
+            start += self.start
         else:
-            concrete = self.get_concrete()
-            if isinstance(w_value, BaseArray):
-                # for now we just copy if setting part of an array from
-                # part of itself. can be improved.
-                if (concrete.get_root_storage() ==
-                    w_value.get_concrete().get_root_storage()):
-                    w_value = space.call_function(space.gettypefor(BaseArray), w_value)
-                    assert isinstance(w_value, BaseArray)
-            else:
-                w_value = convert_to_array(space, w_value)
-            concrete.setslice(space, start, stop, step,
-                                               slice_length, w_value)
+            shape = []
+            strides = []
+            backstrides = []
+            start = self.start
+            i = -1
+            for i, (start_, stop, step, lgt) in enumerate(chunks):
+                if step != 0:
+                    shape.append(lgt)
+                    strides.append(self.strides[i] * step)
+                    backstrides.append(self.strides[i] * (lgt - 1) * step)
+                start += self.strides[i] * start_
+            # add a reminder
+            s = i + 1
+            assert s >= 0
+            shape += self.shape[s:]
+            strides += self.strides[s:]
+            backstrides += self.backstrides[s:]
+        new_sig = signature.Signature.find_sig([
+            NDimSlice.signature, self.signature,
+        ])
+        return NDimSlice(self, new_sig, start, strides[:], backstrides[:],
+                         shape[:])
 
     def descr_mean(self, space):
-        return space.wrap(space.float_w(self.descr_sum(space))/self.find_size())
+        return space.wrap(space.float_w(self.descr_sum(space)) / self.find_size())
 
-    def _sliceloop(self, start, stop, step, source, dest):
-        i = start
-        j = 0
-        while (step > 0 and i < stop) or (step < 0 and i > stop):
-            slice_driver.jit_merge_point(signature=source.signature, step=step,
-                                         stop=stop, i=i, j=j, source=source,
-                                         dest=dest)
-            dest.setitem(i, source.eval(j).convert_to(dest.find_dtype()))
-            j += 1
-            i += step
+    def descr_nonzero(self, space):
+        try:
+            if self.find_size() > 1:
+                raise OperationError(space.w_ValueError, space.wrap(
+                    "The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()"))
+        except ValueError:
+            pass
+        return space.wrap(space.is_true(self.get_concrete().eval(
+            self.start_iter(self.shape)).wrap(space)))
+
+    def getitem(self, item):
+        raise NotImplementedError
+
+    def start_iter(self, res_shape=None):
+        raise NotImplementedError
 
 def convert_to_array(space, w_obj):
     if isinstance(w_obj, BaseArray):
@@ -309,36 +777,49 @@
         return scalar_w(space, dtype, w_obj)
 
 def scalar_w(space, dtype, w_obj):
+    assert isinstance(dtype, interp_dtype.W_Dtype)
     return Scalar(dtype, dtype.unwrap(space, w_obj))
 
 class Scalar(BaseArray):
     """
-    Intermediate class representing a float literal.
+    Intermediate class representing a literal.
     """
     signature = signature.BaseSignature()
 
-    _attrs_ = ["dtype", "value"]
+    _attrs_ = ["dtype", "value", "shape"]
 
     def __init__(self, dtype, value):
-        BaseArray.__init__(self)
+        BaseArray.__init__(self, [], 'C')
         self.dtype = dtype
         self.value = value
 
     def find_size(self):
         raise ValueError
 
+    def get_concrete(self):
+        return self
+
     def find_dtype(self):
         return self.dtype
 
-    def eval(self, i):
+    def getitem(self, item):
         return self.value
 
+    def eval(self, iter):
+        return self.value
+
+    def start_iter(self, res_shape=None):
+        return ConstantIterator()
+
+    def to_str(self, space, comma, builder, indent=' ', use_ellipsis=False):
+        builder.append(self.dtype.str_format(self.value))
+
 class VirtualArray(BaseArray):
     """
     Class for representing virtual arrays, such as binary ops or ufuncs
     """
-    def __init__(self, signature, res_dtype):
-        BaseArray.__init__(self)
+    def __init__(self, signature, shape, res_dtype, order):
+        BaseArray.__init__(self, shape, order)
         self.forced_result = None
         self.signature = signature
         self.res_dtype = res_dtype
@@ -351,13 +832,18 @@
         i = 0
         signature = self.signature
         result_size = self.find_size()
-        result = SingleDimArray(result_size, self.find_dtype())
-        while i < result_size:
+        result = NDimArray(result_size, self.shape, self.find_dtype())
+        shapelen = len(self.shape)
+        i = self.start_iter()
+        ri = result.start_iter()
+        while not ri.done():
             numpy_driver.jit_merge_point(signature=signature,
-                                         result_size=result_size, i=i,
+                                         shapelen=shapelen,
+                                         result_size=result_size, i=i, ri=ri,
                                          self=self, result=result)
-            result.dtype.setitem(result.storage, i, self.eval(i))
-            i += 1
+            result.dtype.setitem(result.storage, ri.offset, self.eval(i))
+            i = i.next(shapelen)
+            ri = ri.next(shapelen)
         return result
 
     def force_if_needed(self):
@@ -369,10 +855,13 @@
         self.force_if_needed()
         return self.forced_result
 
-    def eval(self, i):
+    def eval(self, iter):
         if self.forced_result is not None:
-            return self.forced_result.eval(i)
-        return self._eval(i)
+            return self.forced_result.eval(iter)
+        return self._eval(iter)
+
+    def getitem(self, item):
+        return self.get_concrete().getitem(item)
 
     def setitem(self, item, value):
         return self.get_concrete().setitem(item, value)
@@ -388,8 +877,9 @@
 
 
 class Call1(VirtualArray):
-    def __init__(self, signature, res_dtype, values):
-        VirtualArray.__init__(self, signature, res_dtype)
+    def __init__(self, signature, shape, res_dtype, values, order):
+        VirtualArray.__init__(self, signature, shape, res_dtype,
+                              values.order)
         self.values = values
 
     def _del_sources(self):
@@ -401,40 +891,53 @@
     def _find_dtype(self):
         return self.res_dtype
 
-    def _eval(self, i):
-        val = self.values.eval(i).convert_to(self.res_dtype)
-
+    def _eval(self, iter):
+        assert isinstance(iter, Call1Iterator)
+        val = self.values.eval(iter.child).convert_to(self.res_dtype)
         sig = jit.promote(self.signature)
         assert isinstance(sig, signature.Signature)
         call_sig = sig.components[0]
         assert isinstance(call_sig, signature.Call1)
         return call_sig.func(self.res_dtype, val)
 
+    def start_iter(self, res_shape=None):
+        if self.forced_result is not None:
+            return self.forced_result.start_iter(res_shape)
+        return Call1Iterator(self.values.start_iter(res_shape))
+
 class Call2(VirtualArray):
     """
     Intermediate class for performing binary operations.
     """
-    def __init__(self, signature, calc_dtype, res_dtype, left, right):
-        VirtualArray.__init__(self, signature, res_dtype)
+    def __init__(self, signature, shape, calc_dtype, res_dtype, left, right):
+        # XXX do something if left.order != right.order
+        VirtualArray.__init__(self, signature, shape, res_dtype, left.order)
         self.left = left
         self.right = right
         self.calc_dtype = calc_dtype
+        self.size = 1
+        for s in self.shape:
+            self.size *= s
 
     def _del_sources(self):
         self.left = None
         self.right = None
 
     def _find_size(self):
-        try:
-            return self.left.find_size()
-        except ValueError:
-            pass
-        return self.right.find_size()
+        return self.size
 
-    def _eval(self, i):
-        lhs = self.left.eval(i).convert_to(self.calc_dtype)
-        rhs = self.right.eval(i).convert_to(self.calc_dtype)
+    def start_iter(self, res_shape=None):
+        if self.forced_result is not None:
+            return self.forced_result.start_iter(res_shape)
+        if res_shape is None:
+            res_shape = self.shape  # we still force the shape on children
+        return Call2Iterator(self.left.start_iter(res_shape),
+                             self.right.start_iter(res_shape))
 
+    def _eval(self, iter):
+        assert isinstance(iter, Call2Iterator)
+        lhs = self.left.eval(iter.left).convert_to(self.calc_dtype)
+        rhs = self.right.eval(iter.right).convert_to(self.calc_dtype)
         sig = jit.promote(self.signature)
         assert isinstance(sig, signature.Signature)
         call_sig = sig.components[0]
@@ -446,8 +949,10 @@
     Class for representing views of arrays, they will reflect changes of parent
     arrays. Example: slices
     """
-    def __init__(self, parent, signature):
-        BaseArray.__init__(self)
+    def __init__(self, parent, signature, strides, backstrides, shape):
+        self.strides = strides
+        self.backstrides = backstrides
+        BaseArray.__init__(self, shape, parent.order)
         self.signature = signature
         self.parent = parent
         self.invalidates = parent.invalidates
@@ -459,39 +964,40 @@
         self.parent.get_concrete()
         return self
 
-    def eval(self, i):
-        return self.parent.eval(self.calc_index(i))
+    def getitem(self, item):
+        return self.parent.getitem(item)
+
+    def eval(self, iter):
+        return self.parent.getitem(iter.get_offset())
 
     @unwrap_spec(item=int)
     def setitem_w(self, space, item, w_value):
-        return self.parent.setitem_w(space, self.calc_index(item), w_value)
+        return self.parent.setitem_w(space, item, w_value)
 
     def setitem(self, item, value):
         # This is currently not possible to be called from anywhere.
         raise NotImplementedError
 
     def descr_len(self, space):
-        return space.wrap(self.find_size())
+        if self.shape:
+            return space.wrap(self.shape[0])
+        return space.wrap(1)
 
-    def calc_index(self, item):
-        raise NotImplementedError
+class VirtualView(VirtualArray):
+    pass
 
-class SingleDimSlice(ViewArray):
+class NDimSlice(ViewArray):
     signature = signature.BaseSignature()
 
-    def __init__(self, start, stop, step, slice_length, parent, signature):
-        ViewArray.__init__(self, parent, signature)
-        if isinstance(parent, SingleDimSlice):
-            self.start = parent.calc_index(start)
-            self.stop = parent.calc_index(stop)
-            self.step = parent.step * step
-            self.parent = parent.parent
-        else:
-            self.start = start
-            self.stop = stop
-            self.step = step
-            self.parent = parent
-        self.size = slice_length
+    def __init__(self, parent, signature, start, strides, backstrides,
+                 shape):
+        if isinstance(parent, NDimSlice):
+            parent = parent.parent
+        ViewArray.__init__(self, parent, signature, strides, backstrides, shape)
+        self.start = start
+        self.size = 1
+        for sh in shape:
+            self.size *= sh
 
     def get_root_storage(self):
         return self.parent.get_concrete().get_root_storage()
@@ -502,20 +1008,41 @@
     def find_dtype(self):
         return self.parent.find_dtype()
 
-    def setslice(self, space, start, stop, step, slice_length, arr):
-        start = self.calc_index(start)
-        if stop != -1:
-            stop = self.calc_index(stop)
-        step = self.step * step
-        self._sliceloop(start, stop, step, arr, self.parent)
+    def setslice(self, space, w_value):
+        res_shape = shape_agreement(space, self.shape, w_value.shape)
+        self._sliceloop(w_value, res_shape)
 
-    def calc_index(self, item):
-        return (self.start + item * self.step)
+    def _sliceloop(self, source, res_shape):
+        source_iter = source.start_iter(res_shape)
+        res_iter = self.start_iter(res_shape)
+        shapelen = len(res_shape)
+        while not res_iter.done():
+            slice_driver.jit_merge_point(signature=source.signature,
+                                         shapelen=shapelen,
+                                         self=self, source=source,
+                                         res_iter=res_iter,
+                                         source_iter=source_iter)
+            self.setitem(res_iter.offset, source.eval(source_iter).convert_to(
+                self.find_dtype()))
+            source_iter = source_iter.next(shapelen)
+            res_iter = res_iter.next(shapelen)
 
+    def start_iter(self, res_shape=None):
+        if res_shape is not None and res_shape != self.shape:
+            return BroadcastIterator(self, res_shape)
+        # XXX there is a possible optimization here with SingleDimViewIterator
+        #     ignore for now
+        return ViewIterator(self)
 
-class SingleDimArray(BaseArray):
-    def __init__(self, size, dtype):
-        BaseArray.__init__(self)
+    def setitem(self, item, value):
+        self.parent.setitem(item, value)
+
+class NDimArray(BaseArray):
+    """ A class representing contiguous array. We know that each iteration
+    by say ufunc will increase the data index by one
+    """
+    def __init__(self, size, shape, dtype, order='C'):
+        BaseArray.__init__(self, shape, order)
         self.size = size
         self.dtype = dtype
         self.storage = dtype.malloc(size)
@@ -533,11 +1060,17 @@
     def find_dtype(self):
         return self.dtype
 
-    def eval(self, i):
-        return self.dtype.getitem(self.storage, i)
+    def getitem(self, item):
+        return self.dtype.getitem(self.storage, item)
+
+    def eval(self, iter):
+        return self.dtype.getitem(self.storage, iter.get_offset())
 
     def descr_len(self, space):
-        return space.wrap(self.size)
+        if len(self.shape):
+            return space.wrap(self.shape[0])
+        raise OperationError(space.w_TypeError, space.wrap(
+            "len() of unsized object"))
 
     def setitem_w(self, space, item, w_value):
         self.invalidated()
@@ -547,26 +1080,42 @@
         self.invalidated()
         self.dtype.setitem(self.storage, item, value)
 
-    def setslice(self, space, start, stop, step, slice_length, arr):
-        self._sliceloop(start, stop, step, arr, self)
+    def start_iter(self, res_shape=None):
+        if self.order == 'C':
+            if res_shape is not None and res_shape != self.shape:
+                return BroadcastIterator(self, res_shape)
+            return ArrayIterator(self.size)
+        raise NotImplementedError  # use ViewIterator simply, test it
 
     def __del__(self):
         lltype.free(self.storage, flavor='raw', track_allocation=False)
 
- at unwrap_spec(size=int)
-def zeros(space, size, w_dtype=None):
+def _find_size_and_shape(space, w_size):
+    if space.isinstance_w(w_size, space.w_int):
+        size = space.int_w(w_size)
+        shape = [size]
+    else:
+        size = 1
+        shape = []
+        for w_item in space.fixedview(w_size):
+            item = space.int_w(w_item)
+            size *= item
+            shape.append(item)
+    return size, shape
+
+def zeros(space, w_size, w_dtype=None):
     dtype = space.interp_w(interp_dtype.W_Dtype,
         space.call_function(space.gettypefor(interp_dtype.W_Dtype), w_dtype)
     )
-    return space.wrap(SingleDimArray(size, dtype=dtype))
+    size, shape = _find_size_and_shape(space, w_size)
+    return space.wrap(NDimArray(size, shape[:], dtype=dtype))
 
- at unwrap_spec(size=int)
-def ones(space, size, w_dtype=None):
+def ones(space, w_size, w_dtype=None):
     dtype = space.interp_w(interp_dtype.W_Dtype,
         space.call_function(space.gettypefor(interp_dtype.W_Dtype), w_dtype)
     )
-
-    arr = SingleDimArray(size, dtype=dtype)
+    size, shape = _find_size_and_shape(space, w_size)
+    arr = NDimArray(size, shape[:], dtype=dtype)
     one = dtype.adapt_val(1)
     arr.dtype.fill(arr.storage, one, 0, size)
     return space.wrap(arr)
@@ -583,6 +1132,7 @@
     __pos__ = interp2app(BaseArray.descr_pos),
     __neg__ = interp2app(BaseArray.descr_neg),
     __abs__ = interp2app(BaseArray.descr_abs),
+    __nonzero__ = interp2app(BaseArray.descr_nonzero),
 
     __add__ = interp2app(BaseArray.descr_add),
     __sub__ = interp2app(BaseArray.descr_sub),
diff --git a/pypy/module/micronumpy/interp_support.py b/pypy/module/micronumpy/interp_support.py
--- a/pypy/module/micronumpy/interp_support.py
+++ b/pypy/module/micronumpy/interp_support.py
@@ -9,7 +9,7 @@
 
 @unwrap_spec(s=str)
 def fromstring(space, s):
-    from pypy.module.micronumpy.interp_numarray import SingleDimArray
+    from pypy.module.micronumpy.interp_numarray import NDimArray
     length = len(s)
 
     if length % FLOAT_SIZE == 0:
@@ -19,7 +19,7 @@
             "string length %d not divisable by %d" % (length, FLOAT_SIZE)))
 
     dtype = space.fromcache(W_Float64Dtype)
-    a = SingleDimArray(number, dtype=dtype)
+    a = NDimArray(number, [number], dtype=dtype)
 
     start = 0
     end = FLOAT_SIZE
@@ -31,4 +31,4 @@
         start += FLOAT_SIZE
         end += FLOAT_SIZE
 
-    return space.wrap(a)
\ No newline at end of file
+    return space.wrap(a)
diff --git a/pypy/module/micronumpy/interp_ufuncs.py b/pypy/module/micronumpy/interp_ufuncs.py
--- a/pypy/module/micronumpy/interp_ufuncs.py
+++ b/pypy/module/micronumpy/interp_ufuncs.py
@@ -1,6 +1,6 @@
 from pypy.interpreter.baseobjspace import Wrappable
 from pypy.interpreter.error import OperationError, operationerrfmt
-from pypy.interpreter.gateway import interp2app
+from pypy.interpreter.gateway import interp2app, unwrap_spec
 from pypy.interpreter.typedef import TypeDef, GetSetProperty, interp_attrproperty
 from pypy.module.micronumpy import interp_dtype, signature
 from pypy.rlib import jit
@@ -9,8 +9,8 @@
 
 
 reduce_driver = jit.JitDriver(
-    greens = ["signature"],
-    reds = ["i", "size", "self", "dtype", "value", "obj"]
+    greens = ['shapelen', "signature"],
+    reds = ["i", "self", "dtype", "value", "obj"]
 )
 
 class W_Ufunc(Wrappable):
@@ -45,8 +45,10 @@
         return self.call(space, __args__.arguments_w)
 
     def descr_reduce(self, space, w_obj):
+        return self.reduce(space, w_obj, multidim=False)
+
+    def reduce(self, space, w_obj, multidim):
         from pypy.module.micronumpy.interp_numarray import convert_to_array, Scalar
-
         if self.argcount != 2:
             raise OperationError(space.w_ValueError, space.wrap("reduce only "
                 "supported for binary functions"))
@@ -62,28 +64,33 @@
             space, obj.find_dtype(),
             promote_to_largest=True
         )
-        start = 0
+        start = obj.start_iter(obj.shape)
+        shapelen = len(obj.shape)
+        if shapelen > 1 and not multidim:
+            raise OperationError(space.w_NotImplementedError,
+                space.wrap("not implemented yet"))
         if self.identity is None:
             if size == 0:
                 raise operationerrfmt(space.w_ValueError, "zero-size array to "
                     "%s.reduce without identity", self.name)
-            value = obj.eval(0).convert_to(dtype)
-            start += 1
+            value = obj.eval(start).convert_to(dtype)
+            start = start.next(shapelen)
         else:
             value = self.identity.convert_to(dtype)
         new_sig = signature.Signature.find_sig([
             self.reduce_signature, obj.signature
         ])
-        return self.reduce(new_sig, start, value, obj, dtype, size).wrap(space)
+        return self.reduce_loop(new_sig, shapelen, start, value, obj,
+                           dtype).wrap(space)
 
-    def reduce(self, signature, start, value, obj, dtype, size):
-        i = start
-        while i < size:
-            reduce_driver.jit_merge_point(signature=signature, self=self,
+    def reduce_loop(self, signature, shapelen, i, value, obj, dtype):
+        while not i.done():
+            reduce_driver.jit_merge_point(signature=signature,
+                                          shapelen=shapelen, self=self,
                                           value=value, obj=obj, i=i,
-                                          dtype=dtype, size=size)
+                                          dtype=dtype)
             value = self.func(dtype, value, obj.eval(i).convert_to(dtype))
-            i += 1
+            i = i.next(shapelen)
         return value
 
 class W_Ufunc1(W_Ufunc):
@@ -111,7 +118,7 @@
             return self.func(res_dtype, w_obj.value.convert_to(res_dtype)).wrap(space)
 
         new_sig = signature.Signature.find_sig([self.signature, w_obj.signature])
-        w_res = Call1(new_sig, res_dtype, w_obj)
+        w_res = Call1(new_sig, w_obj.shape, res_dtype, w_obj, w_obj.order)
         w_obj.add_invalidates(w_res)
         return w_res
 
@@ -130,7 +137,7 @@
 
     def call(self, space, args_w):
         from pypy.module.micronumpy.interp_numarray import (Call2,
-            convert_to_array, Scalar)
+            convert_to_array, Scalar, shape_agreement)
 
         [w_lhs, w_rhs] = args_w
         w_lhs = convert_to_array(space, w_lhs)
@@ -153,7 +160,9 @@
         new_sig = signature.Signature.find_sig([
             self.signature, w_lhs.signature, w_rhs.signature
         ])
-        w_res = Call2(new_sig, calc_dtype, res_dtype, w_lhs, w_rhs)
+        new_shape = shape_agreement(space, w_lhs.shape, w_rhs.shape)
+        w_res = Call2(new_sig, new_shape, calc_dtype,
+                      res_dtype, w_lhs, w_rhs)
         w_lhs.add_invalidates(w_res)
         w_rhs.add_invalidates(w_res)
         return w_res
diff --git a/pypy/module/micronumpy/signature.py b/pypy/module/micronumpy/signature.py
--- a/pypy/module/micronumpy/signature.py
+++ b/pypy/module/micronumpy/signature.py
@@ -49,4 +49,4 @@
     _immutable_fields_ = ["func"]
 
     def __init__(self, func):
-        self.func = func
\ No newline at end of file
+        self.func = func
diff --git a/pypy/module/micronumpy/test/test_base.py b/pypy/module/micronumpy/test/test_base.py
--- a/pypy/module/micronumpy/test/test_base.py
+++ b/pypy/module/micronumpy/test/test_base.py
@@ -1,6 +1,6 @@
 from pypy.conftest import gettestobjspace
 from pypy.module.micronumpy import interp_dtype
-from pypy.module.micronumpy.interp_numarray import SingleDimArray, Scalar
+from pypy.module.micronumpy.interp_numarray import NDimArray, Scalar
 from pypy.module.micronumpy.interp_ufuncs import (find_binop_result_dtype,
         find_unaryop_result_dtype)
 
@@ -13,7 +13,7 @@
     def test_binop_signature(self, space):
         float64_dtype = space.fromcache(interp_dtype.W_Float64Dtype)
 
-        ar = SingleDimArray(10, dtype=float64_dtype)
+        ar = NDimArray(10, [10], dtype=float64_dtype)
         v1 = ar.descr_add(space, ar)
         v2 = ar.descr_add(space, Scalar(float64_dtype, 2.0))
         assert v1.signature is not v2.signature
@@ -22,7 +22,7 @@
         v4 = ar.descr_add(space, ar)
         assert v1.signature is v4.signature
 
-        bool_ar = SingleDimArray(10, dtype=space.fromcache(interp_dtype.W_BoolDtype))
+        bool_ar = NDimArray(10, [10], dtype=space.fromcache(interp_dtype.W_BoolDtype))
         v5 = ar.descr_add(space, bool_ar)
         assert v5.signature is not v1.signature
         assert v5.signature is not v2.signature
@@ -30,13 +30,13 @@
         assert v5.signature is v6.signature
 
     def test_slice_signature(self, space):
-        ar = SingleDimArray(10, dtype=space.fromcache(interp_dtype.W_Float64Dtype))
-        v1 = ar.descr_getitem(space, space.wrap(slice(1, 5, 1)))
+        ar = NDimArray(10, [10], dtype=space.fromcache(interp_dtype.W_Float64Dtype))
+        v1 = ar.descr_getitem(space, space.wrap(slice(1, 3, 1)))
         v2 = ar.descr_getitem(space, space.wrap(slice(4, 6, 1)))
         assert v1.signature is v2.signature
 
-        v3 = ar.descr_add(space, v1)
-        v4 = ar.descr_add(space, v2)
+        v3 = v2.descr_add(space, v1)
+        v4 = v1.descr_add(space, v2)
         assert v3.signature is v4.signature
 
 class TestUfuncCoerscion(object):
diff --git a/pypy/module/micronumpy/test/test_compile.py b/pypy/module/micronumpy/test/test_compile.py
--- a/pypy/module/micronumpy/test/test_compile.py
+++ b/pypy/module/micronumpy/test/test_compile.py
@@ -5,7 +5,7 @@
 class TestCompiler(object):
     def compile(self, code):
         return numpy_compile(code)
-    
+
     def test_vars(self):
         code = """
         a = 2
@@ -25,7 +25,7 @@
         st = interp.code.statements[0]
         assert st.expr.items == [FloatConstant(1), FloatConstant(2),
                                  FloatConstant(3)]
-    
+
     def test_array_literal2(self):
         code = "a = [[1],[2],[3]]"
         interp = self.compile(code)
@@ -102,10 +102,11 @@
         code = """
         a = [1,2,3,4]
         b = [4,5,6,5]
-        a + b
+        c = a + b
+        c -> 3
         """
         interp = self.run(code)
-        assert interp.results[0]._getnums(False) == ["5.0", "7.0", "9.0", "9.0"]
+        assert interp.results[-1].value.val == 9
 
     def test_array_getitem(self):
         code = """
@@ -115,7 +116,7 @@
         """
         interp = self.run(code)
         assert interp.results[0].value.val == 3 + 6
-        
+
     def test_range_getitem(self):
         code = """
         r = |20| + 3
@@ -161,10 +162,32 @@
         assert interp.results[0].value.val == 256
 
     def test_slice(self):
-        py.test.skip("in progress")
         interp = self.run("""
         a = [1,2,3,4]
         b = a -> :
         b -> 3
         """)
-        assert interp.results[0].value.val == 3
+        assert interp.results[0].value.val == 4
+
+    def test_slice_step(self):
+        interp = self.run("""
+        a = |30|
+        b = a -> ::2
+        b -> 3
+        """)
+        assert interp.results[0].value.val == 6
+
+    def test_multidim_getitem(self):
+        interp = self.run("""
+        a = [[1,2]]
+        a -> 0 -> 1
+        """)
+        assert interp.results[0].value.val == 2
+
+    def test_multidim_getitem_2(self):
+        interp = self.run("""
+        a = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
+        b = a + a
+        b -> 1 -> 1
+        """)
+        assert interp.results[0].value.val == 8
diff --git a/pypy/module/micronumpy/test/test_numarray.py b/pypy/module/micronumpy/test/test_numarray.py
--- a/pypy/module/micronumpy/test/test_numarray.py
+++ b/pypy/module/micronumpy/test/test_numarray.py
@@ -1,6 +1,157 @@
+
+import py
 from pypy.module.micronumpy.test.test_base import BaseNumpyAppTest
+from pypy.module.micronumpy.interp_numarray import NDimArray, shape_agreement
+from pypy.module.micronumpy import signature
+from pypy.interpreter.error import OperationError
 from pypy.conftest import gettestobjspace
 
+class MockDtype(object):
+    signature = signature.BaseSignature()
+    def malloc(self, size):
+        return None
+
+class TestNumArrayDirect(object):
+    def newslice(self, *args):
+        return self.space.newslice(*[self.space.wrap(arg) for arg in args])
+
+    def newtuple(self, *args):
+        args_w = []
+        for arg in args:
+            if isinstance(arg, int):
+                args_w.append(self.space.wrap(arg))
+            else:
+                args_w.append(arg)
+        return self.space.newtuple(args_w)
+
+    def test_strides_f(self):
+        a = NDimArray(100, [10, 5, 3], MockDtype(), 'F')
+        assert a.strides == [1, 10, 50]
+        assert a.backstrides == [9, 40, 100]
+
+    def test_strides_c(self):
+        a = NDimArray(100, [10, 5, 3], MockDtype(), 'C')
+        assert a.strides == [15, 3, 1]
+        assert a.backstrides == [135, 12, 2]
+
+    def test_create_slice_f(self):
+        space = self.space
+        a = NDimArray(10 * 5 * 3, [10, 5, 3], MockDtype(), 'F')
+        s = a.create_slice(space, [(3, 0, 0, 1)])
+        assert s.start == 3
+        assert s.strides == [10, 50]
+        assert s.backstrides == [40, 100]
+        s = a.create_slice(space, [(1, 9, 2, 4)])
+        assert s.start == 1
+        assert s.strides == [2, 10, 50]
+        assert s.backstrides == [6, 40, 100]
+        s = a.create_slice(space, [(1, 5, 3, 2), (1, 2, 1, 1), (1, 0, 0, 1)])
+        assert s.shape == [2, 1]
+        assert s.strides == [3, 10]
+        assert s.backstrides == [3, 0]
+        s = a.create_slice(space, [(0, 10, 1, 10), (2, 0, 0, 1)])
+        assert s.start == 20
+        assert s.shape == [10, 3]
+
+    def test_create_slice_c(self):
+        space = self.space
+        a = NDimArray(10 * 5 * 3, [10, 5, 3], MockDtype(), 'C')
+        s = a.create_slice(space, [(3, 0, 0, 1)])
+        assert s.start == 45
+        assert s.strides == [3, 1]
+        assert s.backstrides == [12, 2]
+        s = a.create_slice(space, [(1, 9, 2, 4)])
+        assert s.start == 15
+        assert s.strides == [30, 3, 1]
+        assert s.backstrides == [90, 12, 2]
+        s = a.create_slice(space, [(1, 5, 3, 2), (1, 2, 1, 1), (1, 0, 0, 1)])
+        assert s.start == 19
+        assert s.shape == [2, 1]
+        assert s.strides == [45, 3]
+        assert s.backstrides == [45, 0]
+        s = a.create_slice(space, [(0, 10, 1, 10), (2, 0, 0, 1)])
+        assert s.start == 6
+        assert s.shape == [10, 3]
+
+    def test_slice_of_slice_f(self):
+        space = self.space
+        a = NDimArray(10 * 5 * 3, [10, 5, 3], MockDtype(), 'F')
+        s = a.create_slice(space, [(5, 0, 0, 1)])
+        assert s.start == 5
+        s2 = s.create_slice(space, [(3, 0, 0, 1)])
+        assert s2.shape == [3]
+        assert s2.strides == [50]
+        assert s2.parent is a
+        assert s2.backstrides == [100]
+        assert s2.start == 35
+        s = a.create_slice(space, [(1, 5, 3, 2)])
+        s2 = s.create_slice(space, [(0, 2, 1, 2), (2, 0, 0, 1)])
+        assert s2.shape == [2, 3]
+        assert s2.strides == [3, 50]
+        assert s2.backstrides == [3, 100]
+        assert s2.start == 1 * 15 + 2 * 3
+
+    def test_slice_of_slice_c(self):
+        space = self.space
+        a = NDimArray(10 * 5 * 3, [10, 5, 3], MockDtype(), order='C')
+        s = a.create_slice(space, [(5, 0, 0, 1)])
+        assert s.start == 15 * 5
+        s2 = s.create_slice(space, [(3, 0, 0, 1)])
+        assert s2.shape == [3]
+        assert s2.strides == [1]
+        assert s2.parent is a
+        assert s2.backstrides == [2]
+        assert s2.start == 5 * 15 + 3 * 3
+        s = a.create_slice(space, [(1, 5, 3, 2)])
+        s2 = s.create_slice(space, [(0, 2, 1, 2), (2, 0, 0, 1)])
+        assert s2.shape == [2, 3]
+        assert s2.strides == [45, 1]
+        assert s2.backstrides == [45, 2]
+        assert s2.start == 1 * 15 + 2 * 3
+
+    def test_negative_step_f(self):
+        space = self.space
+        a = NDimArray(10 * 5 * 3, [10, 5, 3], MockDtype(), 'F')
+        s = a.create_slice(space, [(9, -1, -2, 5)])
+        assert s.start == 9
+        assert s.strides == [-2, 10, 50]
+        assert s.backstrides == [-8, 40, 100]
+
+    def test_negative_step_c(self):
+        space = self.space
+        a = NDimArray(10 * 5 * 3, [10, 5, 3], MockDtype(), order='C')
+        s = a.create_slice(space, [(9, -1, -2, 5)])
+        assert s.start == 135
+        assert s.strides == [-30, 3, 1]
+        assert s.backstrides == [-120, 12, 2]
+
+    def test_index_of_single_item_f(self):
+        a = NDimArray(10 * 5 * 3, [10, 5, 3], MockDtype(), 'F')
+        r = a._index_of_single_item(self.space, self.newtuple(1, 2, 2))
+        assert r == 1 + 2 * 10 + 2 * 50
+        s = a.create_slice(self.space, [(0, 10, 1, 10), (2, 0, 0, 1)])
+        r = s._index_of_single_item(self.space, self.newtuple(1, 0))
+        assert r == a._index_of_single_item(self.space, self.newtuple(1, 2, 0))
+        r = s._index_of_single_item(self.space, self.newtuple(1, 1))
+        assert r == a._index_of_single_item(self.space, self.newtuple(1, 2, 1))
+
+    def test_index_of_single_item_c(self):
+        a = NDimArray(10 * 5 * 3, [10, 5, 3], MockDtype(), 'C')
+        r = a._index_of_single_item(self.space, self.newtuple(1, 2, 2))
+        assert r == 1 * 3 * 5 + 2 * 3 + 2
+        s = a.create_slice(self.space, [(0, 10, 1, 10), (2, 0, 0, 1)])
+        r = s._index_of_single_item(self.space, self.newtuple(1, 0))
+        assert r == a._index_of_single_item(self.space, self.newtuple(1, 2, 0))
+        r = s._index_of_single_item(self.space, self.newtuple(1, 1))
+        assert r == a._index_of_single_item(self.space, self.newtuple(1, 2, 1))
+
+    def test_shape_agreement(self):
+        assert shape_agreement(self.space, [3], [3]) == [3]
+        assert shape_agreement(self.space, [1, 2, 3], [1, 2, 3]) == [1, 2, 3]
+        py.test.raises(OperationError, shape_agreement, self.space, [2], [3])
+        assert shape_agreement(self.space, [4, 4], []) == [4, 4]
+        assert shape_agreement(self.space, [8, 1, 6, 1], [7, 1, 5]) == [8, 7, 6, 5]
+        assert shape_agreement(self.space, [5, 2], [4, 3, 5, 2]) == [4, 3, 5, 2]
 
 class AppTestNumArray(BaseNumpyAppTest):
     def test_type(self):
@@ -50,63 +201,16 @@
         b = a.copy()
         for i in xrange(5):
             assert b[i] == a[i]
+        a[3] = 22
+        assert b[3] == 3
 
     def test_iterator_init(self):
         from numpypy import array
         a = array(range(5))
         assert a[3] == 3
-
-    def test_repr(self):
-        from numpypy import array, zeros
-        a = array(range(5), float)
-        assert repr(a) == "array([0.0, 1.0, 2.0, 3.0, 4.0])"
-        a = array([], float)
-        assert repr(a) == "array([], dtype=float64)"
-        a = zeros(1001)
-        assert repr(a) == "array([0.0, 0.0, 0.0, ..., 0.0, 0.0, 0.0])"
-        a = array(range(5), long)
-        assert repr(a) == "array([0, 1, 2, 3, 4])"
-        a = array([], long)
-        assert repr(a) == "array([], dtype=int64)"
-        a = array([True, False, True, False], "?")
-        assert repr(a) == "array([True, False, True, False], dtype=bool)"
-
-    def test_repr_slice(self):
-        from numpypy import array, zeros
-        a = array(range(5), float)
-        b = a[1::2]
-        assert repr(b) == "array([1.0, 3.0])"
-        a = zeros(2002)
-        b = a[::2]
-        assert repr(b) == "array([0.0, 0.0, 0.0, ..., 0.0, 0.0, 0.0])"
-
-    def test_str(self):
-        from numpypy import array, zeros
-        a = array(range(5), float)
-        assert str(a) == "[0.0 1.0 2.0 3.0 4.0]"
-        assert str((2*a)[:]) == "[0.0 2.0 4.0 6.0 8.0]"
-        a = zeros(1001)
-        assert str(a) == "[0.0 0.0 0.0 ..., 0.0 0.0 0.0]"
-
-        a = array(range(5), dtype=long)
-        assert str(a) == "[0 1 2 3 4]"
-        a = array([True, False, True, False], dtype="?")
-        assert str(a) == "[True False True False]"
-
-        a = array(range(5), dtype="int8")
-        assert str(a) == "[0 1 2 3 4]"
-
-        a = array(range(5), dtype="int16")
-        assert str(a) == "[0 1 2 3 4]"
-
-    def test_str_slice(self):
-        from numpypy import array, zeros
-        a = array(range(5), float)
-        b = a[1::2]
-        assert str(b) == "[1.0 3.0]"
-        a = zeros(2002)
-        b = a[::2]
-        assert str(b) == "[0.0 0.0 0.0 ..., 0.0 0.0 0.0]"
+        a = array(1)
+        assert a[0] == 1
+        assert a.shape == ()
 
     def test_getitem(self):
         from numpypy import array
@@ -140,8 +244,8 @@
         a = array(range(5))
         raises(IndexError, "a[(1,2)] = [0,1]")
         for i in xrange(5):
-            a[(i,)] = i+1
-            assert a[i] == i+1
+            a[(i,)] = i + 1
+            assert a[i] == i + 1
         a[()] = range(5)
         for i in xrange(5):
             assert a[i] == i
@@ -171,7 +275,7 @@
         assert a[3] == 1.
         assert a[4] == 11.
         a = zeros(10)
-        a[::2][::-1][::2] = array(range(1,4))
+        a[::2][::-1][::2] = array(range(1, 4))
         assert a[8] == 1.
         assert a[4] == 2.
         assert a[0] == 3.
@@ -191,6 +295,11 @@
         assert a[1] == 0.
         assert a[3] == 0.
 
+    def test_scalar(self):
+        from numpypy import array
+        a = array(3)
+        assert a[0] == 3
+
     def test_len(self):
         from numpypy import array
         a = array(range(5))
@@ -222,7 +331,7 @@
     def test_add_other(self):
         from numpypy import array
         a = array(range(5))
-        b = array(range(4, -1, -1))
+        b = array([i for i in reversed(range(5))])
         c = a + b
         for i in range(5):
             assert c[i] == 4
@@ -346,8 +455,8 @@
         a = array(range(5), float)
         b = a ** a
         for i in range(5):
-            print b[i], i**i
-            assert b[i] == i**i
+            print b[i], i ** i
+            assert b[i] == i ** i
 
     def test_pow_other(self):
         from numpypy import array
@@ -366,7 +475,7 @@
 
     def test_mod(self):
         from numpypy import array
-        a = array(range(1,6))
+        a = array(range(1, 6))
         b = a % a
         for i in range(5):
             assert b[i] == 0
@@ -394,7 +503,7 @@
 
     def test_pos(self):
         from numpypy import array
-        a = array([1.,-2.,3.,-4.,-5.])
+        a = array([1., -2., 3., -4., -5.])
         b = +a
         for i in range(5):
             assert b[i] == a[i]
@@ -405,7 +514,7 @@
 
     def test_neg(self):
         from numpypy import array
-        a = array([1.,-2.,3.,-4.,-5.])
+        a = array([1., -2., 3., -4., -5.])
         b = -a
         for i in range(5):
             assert b[i] == -a[i]
@@ -416,7 +525,7 @@
 
     def test_abs(self):
         from numpypy import array
-        a = array([1.,-2.,3.,-4.,-5.])
+        a = array([1., -2., 3., -4., -5.])
         b = abs(a)
         for i in range(5):
             assert b[i] == abs(a[i])
@@ -445,7 +554,7 @@
         s = a[1:5]
         assert len(s) == 4
         for i in range(4):
-            assert s[i] == a[i+1]
+            assert s[i] == a[i + 1]
 
         s = (a + a)[1:2]
         assert len(s) == 1
@@ -459,7 +568,7 @@
         s = a[1:9:2]
         assert len(s) == 4
         for i in range(4):
-            assert s[i] == a[2*i+1]
+            assert s[i] == a[2 * i + 1]
 
     def test_slice_update(self):
         from numpypy import array
@@ -470,13 +579,12 @@
         a[2] = 20
         assert s[2] == 20
 
-
     def test_slice_invaidate(self):
         # check that slice shares invalidation list with
         from numpypy import array
         a = array(range(5))
         s = a[0:2]
-        b = array([10,11])
+        b = array([10, 11])
         c = s + b
         a[0] = 100
         assert c[0] == 10
@@ -503,7 +611,7 @@
 
     def test_prod(self):
         from numpypy import array
-        a = array(range(1,6))
+        a = array(range(1, 6))
         assert a.prod() == 120.0
         assert a[:4].prod() == 24.0
 
@@ -517,7 +625,7 @@
     def test_max_add(self):
         from numpypy import array
         a = array([-1.2, 3.4, 5.7, -3.0, 2.7])
-        assert (a+a).max() == 11.4
+        assert (a + a).max() == 11.4
 
     def test_min(self):
         from numpypy import array
@@ -529,12 +637,23 @@
     def test_argmax(self):
         from numpypy import array
         a = array([-1.2, 3.4, 5.7, -3.0, 2.7])
-        assert a.argmax() == 2
+        r = a.argmax()
+        assert r == 2
         b = array([])
-        raises(ValueError, "b.argmax()")
+        raises(ValueError, b.argmax)
 
         a = array(range(-5, 5))
-        assert a.argmax() == 9
+        r = a.argmax()
+        assert r == 9
+        b = a[::2]
+        r = b.argmax()
+        assert r == 4
+        r = (a + a).argmax()
+        assert r == 9
+        a = array([1, 0, 0])
+        assert a.argmax() == 0
+        a = array([0, 0, 1])
+        assert a.argmax() == 2
 
     def test_argmin(self):
         from numpypy import array
@@ -608,6 +727,201 @@
             for i in xrange(5):
                 assert c[i] == func(b[i], 3)
 
+    def test_nonzero(self):
+        from numpypy import array
+        a = array([1, 2])
+        raises(ValueError, bool, a)
+        raises(ValueError, bool, a == a)
+        assert bool(array(1))
+        assert not bool(array(0))
+        assert bool(array([1]))
+        assert not bool(array([0]))
+
+class AppTestMultiDim(BaseNumpyAppTest):
+    def test_init(self):
+        import numpypy
+        a = numpypy.zeros((2, 2))
+        assert len(a) == 2
+
+    def test_shape(self):
+        import numpypy
+        assert numpypy.zeros(1).shape == (1,)
+        assert numpypy.zeros((2, 2)).shape == (2, 2)
+        assert numpypy.zeros((3, 1, 2)).shape == (3, 1, 2)
+        assert numpypy.array([[1], [2], [3]]).shape == (3, 1)
+        assert len(numpypy.zeros((3, 1, 2))) == 3
+        raises(TypeError, len, numpypy.zeros(()))
+        raises(ValueError, numpypy.array, [[1, 2], 3])
+
+    def test_getsetitem(self):
+        import numpypy
+        a = numpypy.zeros((2, 3, 1))
+        raises(IndexError, a.__getitem__, (2, 0, 0))
+        raises(IndexError, a.__getitem__, (0, 3, 0))
+        raises(IndexError, a.__getitem__, (0, 0, 1))
+        assert a[1, 1, 0] == 0
+        a[1, 2, 0] = 3
+        assert a[1, 2, 0] == 3
+        assert a[1, 1, 0] == 0
+        assert a[1, -1, 0] == 3
+
+    def test_slices(self):
+        import numpypy
+        a = numpypy.zeros((4, 3, 2))
+        raises(IndexError, a.__getitem__, (4,))
+        raises(IndexError, a.__getitem__, (3, 3))
+        raises(IndexError, a.__getitem__, (slice(None), 3))
+        a[0, 1, 1] = 13
+        a[1, 2, 1] = 15
+        b = a[0]
+        assert len(b) == 3
+        assert b.shape == (3, 2)
+        assert b[1, 1] == 13
+        b = a[1]
+        assert b.shape == (3, 2)
+        assert b[2, 1] == 15
+        b = a[:, 1]
+        assert b.shape == (4, 2)
+        assert b[0, 1] == 13
+        b = a[:, 1, :]
+        assert b.shape == (4, 2)
+        assert b[0, 1] == 13
+        b = a[1, 2]
+        assert b[1] == 15
+        b = a[:]
+        assert b.shape == (4, 3, 2)
+        assert b[1, 2, 1] == 15
+        assert b[0, 1, 1] == 13
+        b = a[:][:, 1][:]
+        assert b[2, 1] == 0.0
+        assert b[0, 1] == 13
+        raises(IndexError, b.__getitem__, (4, 1))
+        assert a[0][1][1] == 13
+        assert a[1][2][1] == 15
+
+    def test_init_2(self):
+        import numpypy
+        raises(ValueError, numpypy.array, [[1], 2])
+        raises(ValueError, numpypy.array, [[1, 2], [3]])
+        raises(ValueError, numpypy.array, [[[1, 2], [3, 4], 5]])
+        raises(ValueError, numpypy.array, [[[1, 2], [3, 4], [5]]])
+        a = numpypy.array([[1, 2], [4, 5]])
+        assert a[0, 1] == 2
+        assert a[0][1] == 2
+        a = numpypy.array(([[[1, 2], [3, 4], [5, 6]]]))
+        assert (a[0, 1] == [3, 4]).all()
+
+    def test_setitem_slice(self):
+        import numpypy
+        a = numpypy.zeros((3, 4))
+        a[1] = [1, 2, 3, 4]
+        assert a[1, 2] == 3
+        raises(TypeError, a[1].__setitem__, [1, 2, 3])
+        a = numpypy.array([[1, 2], [3, 4]])
+        assert (a == [[1, 2], [3, 4]]).all()
+        a[1] = numpypy.array([5, 6])
+        assert (a == [[1, 2], [5, 6]]).all()
+        a[:, 1] = numpypy.array([8, 10])
+        assert (a == [[1, 8], [5, 10]]).all()
+        a[0, :: -1] = numpypy.array([11, 12])
+        assert (a == [[12, 11], [5, 10]]).all()
+
+    def test_ufunc(self):
+        from numpypy import array
+        a = array([[1, 2], [3, 4], [5, 6]])
+        assert ((a + a) == array([[1 + 1, 2 + 2], [3 + 3, 4 + 4], [5 + 5, 6 + 6]])).all()
+
+    def test_getitem_add(self):
+        from numpypy import array
+        a = array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]])
+        assert (a + a)[1, 1] == 8
+
+    def test_ufunc_negative(self):
+        from numpypy import array, negative
+        a = array([[1, 2], [3, 4]])
+        b = negative(a + a)
+        assert (b == [[-2, -4], [-6, -8]]).all()
+
+    def test_getitem_3(self):
+        from numpypy import array
+        a = array([[1, 2], [3, 4], [5, 6], [7, 8], [9, 10], [11, 12], [13, 14]])
+        b = a[::2]
+        print a
+        print b
+        assert (b == [[1, 2], [5, 6], [9, 10], [13, 14]]).all()
+        c = b + b
+        assert c[1][1] == 12
+
+    def test_multidim_ones(self):
+        from numpypy import ones
+        a = ones((1, 2, 3))
+        assert a[0, 1, 2] == 1.0
+
+    def test_broadcast_ufunc(self):
+        from numpypy import array
+        a = array([[1, 2], [3, 4], [5, 6]])
+        b = array([5, 6])
+        c = ((a + b) == [[1 + 5, 2 + 6], [3 + 5, 4 + 6], [5 + 5, 6 + 6]])
+        assert c.all()
+
+    def test_broadcast_setslice(self):
+        from numpypy import zeros, ones
+        a = zeros((100, 100))
+        b = ones(100)
+        a[:, :] = b
+        assert a[13, 15] == 1
+
+    def test_broadcast_shape_agreement(self):
+        from numpypy import zeros, array
+        a = zeros((3, 1, 3))
+        b = array(((10, 11, 12), (20, 21, 22), (30, 31, 32)))
+        c = ((a + b) == [b, b, b])
+        assert c.all()
+        a = array((((10,11,12), ), ((20, 21, 22), ), ((30,31,32), )))
+        assert(a.shape == (3, 1, 3))
+        d = zeros((3, 3))
+        c = ((a + d) == [b, b, b])
+        c = ((a + d) == array([[[10., 11., 12.]]*3, [[20.,21.,22.]]*3, [[30.,31.,32.]]*3]))
+        assert c.all()
+
+    def test_broadcast_scalar(self):
+        from numpypy import zeros
+        a = zeros((4, 5), 'd')
+        a[:, 1] = 3
+        assert a[2, 1] == 3
+        assert a[0, 2] == 0
+        a[0, :] = 5
+        assert a[0, 3] == 5
+        assert a[2, 1] == 3
+        assert a[3, 2] == 0
+
+    def test_broadcast_call2(self):
+        from numpypy import zeros, ones
+        a = zeros((4, 1, 5))
+        b = ones((4, 3, 5))
+        b[:] = (a + a)
+        assert (b == zeros((4, 3, 5))).all()
+
+    def test_argmax(self):
+        from numpypy import array
+        a = array([[1, 2], [3, 4], [5, 6]])
+        assert a.argmax() == 5
+        assert a[:2,].argmax() == 3
+
+    def test_broadcast_wrong_shapes(self):
+        from numpypy import zeros
+        a = zeros((4, 3, 2))
+        b = zeros((4, 2))
+        exc = raises(ValueError, lambda: a + b)
+        assert str(exc.value) == "operands could not be broadcast together with shapes (4,3,2) (4,2)"
+
+    def test_reduce(self):
+        from numpypy import array
+        a = array([[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]])
+        assert a.sum() == (13 * 12) / 2
+        b = a[1:, 1::2]
+        c = b + b
+        assert c.sum() == (6 + 8 + 10 + 12) * 2
 
 class AppTestSupport(object):
     def setup_class(cls):
@@ -621,3 +935,94 @@
         for i in range(4):
             assert a[i] == i + 1
         raises(ValueError, fromstring, "abc")
+
+class AppTestRepr(BaseNumpyAppTest):
+    def test_repr(self):
+        from numpypy import array, zeros
+        a = array(range(5), float)
+        assert repr(a) == "array([0.0, 1.0, 2.0, 3.0, 4.0])"
+        a = array([], float)
+        assert repr(a) == "array([], dtype=float64)"
+        a = zeros(1001)
+        assert repr(a) == "array([0.0, 0.0, 0.0, ..., 0.0, 0.0, 0.0])"
+        a = array(range(5), long)
+        assert repr(a) == "array([0, 1, 2, 3, 4])"
+        a = array([], long)
+        assert repr(a) == "array([], dtype=int64)"
+        a = array([True, False, True, False], "?")
+        assert repr(a) == "array([True, False, True, False], dtype=bool)"
+
+    def test_repr_multi(self):
+        from numpypy import array, zeros
+        a = zeros((3, 4))
+        assert repr(a) == '''array([[0.0, 0.0, 0.0, 0.0],
+       [0.0, 0.0, 0.0, 0.0],
+       [0.0, 0.0, 0.0, 0.0]])'''
+        a = zeros((2, 3, 4))
+        assert repr(a) == '''array([[[0.0, 0.0, 0.0, 0.0],
+        [0.0, 0.0, 0.0, 0.0],
+        [0.0, 0.0, 0.0, 0.0]],
+
+       [[0.0, 0.0, 0.0, 0.0],
+        [0.0, 0.0, 0.0, 0.0],
+        [0.0, 0.0, 0.0, 0.0]]])'''
+
+    def test_repr_slice(self):
+        from numpypy import array, zeros
+        a = array(range(5), float)
+        b = a[1::2]
+        assert repr(b) == "array([1.0, 3.0])"
+        a = zeros(2002)
+        b = a[::2]
+        assert repr(b) == "array([0.0, 0.0, 0.0, ..., 0.0, 0.0, 0.0])"
+        a = array((range(5), range(5, 10)), dtype="int16")
+        b = a[1, 2:]
+        assert repr(b) == "array([7, 8, 9], dtype=int16)"
+        # an empty slice prints its shape
+        b = a[2:1, ]
+        assert repr(b) == "array([], shape=(0, 5), dtype=int16)"
+
+    def test_str(self):
+        from numpypy import array, zeros
+        a = array(range(5), float)
+        assert str(a) == "[0.0 1.0 2.0 3.0 4.0]"
+        assert str((2 * a)[:]) == "[0.0 2.0 4.0 6.0 8.0]"
+        a = zeros(1001)
+        assert str(a) == "[0.0 0.0 0.0 ..., 0.0 0.0 0.0]"
+
+        a = array(range(5), dtype=long)
+        assert str(a) == "[0 1 2 3 4]"
+        a = array([True, False, True, False], dtype="?")
+        assert str(a) == "[True False True False]"
+
+        a = array(range(5), dtype="int8")
+        assert str(a) == "[0 1 2 3 4]"
+
+        a = array(range(5), dtype="int16")
+        assert str(a) == "[0 1 2 3 4]"
+
+        a = array((range(5), range(5, 10)), dtype="int16")
+        assert str(a) == "[[0 1 2 3 4]\n [5 6 7 8 9]]"
+
+        a = array(3, dtype=int)
+        assert str(a) == "3"
+
+        a = zeros((400, 400), dtype=int)
+        assert str(a) == "[[0 0 0 ..., 0 0 0]\n [0 0 0 ..., 0 0 0]\n [0 0 0 ..., 0 0 0]\n ..., \n [0 0 0 ..., 0 0 0]\n [0 0 0 ..., 0 0 0]\n [0 0 0 ..., 0 0 0]]"
+        a = zeros((2, 2, 2))
+        r = str(a)
+        assert r == '[[[0.0 0.0]\n  [0.0 0.0]]\n\n [[0.0 0.0]\n  [0.0 0.0]]]'
+
+    def test_str_slice(self):
+        from numpypy import array, zeros
+        a = array(range(5), float)
+        b = a[1::2]
+        assert str(b) == "[1.0 3.0]"
+        a = zeros(2002)
+        b = a[::2]
+        assert str(b) == "[0.0 0.0 0.0 ..., 0.0 0.0 0.0]"
+        a = array((range(5), range(5, 10)), dtype="int16")
+        b = a[1, 2:]
+        assert str(b) == "[7 8 9]"
+        b = a[2:1, ]
+        assert str(b) == "[]"
diff --git a/pypy/module/micronumpy/test/test_zjit.py b/pypy/module/micronumpy/test/test_zjit.py
--- a/pypy/module/micronumpy/test/test_zjit.py
+++ b/pypy/module/micronumpy/test/test_zjit.py
@@ -1,29 +1,54 @@
+
+""" Tests that check if JIT-compiled numpy operations produce reasonably
+good assembler
+"""
+
+import py
+
+from pypy.jit.metainterp import pyjitpl
 from pypy.jit.metainterp.test.support import LLJitMixin
+from pypy.jit.metainterp.warmspot import reset_stats
 from pypy.module.micronumpy import interp_ufuncs, signature
-from pypy.module.micronumpy.compile import (FakeSpace,
-    FloatObject, IntObject, numpy_compile, BoolObject)
-from pypy.module.micronumpy.interp_numarray import (SingleDimArray,
-    SingleDimSlice)
+from pypy.module.micronumpy.compile import (numpy_compile, FakeSpace,
+    FloatObject, IntObject, BoolObject, Parser, InterpreterState)
+from pypy.module.micronumpy.interp_numarray import NDimArray, NDimSlice
 from pypy.rlib.nonconst import NonConstant
 from pypy.rpython.annlowlevel import llstr, hlstr
-from pypy.jit.metainterp.warmspot import reset_stats
-from pypy.jit.metainterp import pyjitpl
-
-import py
 
 
 class TestNumpyJIt(LLJitMixin):
     graph = None
     interp = None
-        
-    def run(self, code):
+
+    def setup_class(cls):
+        default = """
+        a = [1,2,3,4]
+        c = a + b
+        sum(c) -> 1::1
+        a -> 3:1:2
+        """
+
+        d = {}
+        p = Parser()
+        allcodes = [p.parse(default)]
+        for name, meth in cls.__dict__.iteritems():
+            if name.startswith("define_"):
+                code = meth()
+                d[name[len("define_"):]] = len(allcodes)
+                allcodes.append(p.parse(code))
+        cls.code_mapping = d
+        cls.codes = allcodes
+
+    def run(self, name):
         space = FakeSpace()
-        
-        def f(code):
-            interp = numpy_compile(hlstr(code))
+        i = self.code_mapping[name]
+        codes = self.codes
+
+        def f(i):
+            interp = InterpreterState(codes[i])
             interp.run(space)
             res = interp.results[-1]
-            w_res = res.eval(0).wrap(interp.space)
+            w_res = res.eval(res.start_iter()).wrap(interp.space)
             if isinstance(w_res, BoolObject):
                 return float(w_res.boolval)
             elif isinstance(w_res, FloatObject):
@@ -34,62 +59,73 @@
                 return -42.
 
         if self.graph is None:
-            interp, graph = self.meta_interp(f, [llstr(code)],
+            interp, graph = self.meta_interp(f, [i],
                                              listops=True,
                                              backendopt=True,
                                              graph_and_interp_only=True)
             self.__class__.interp = interp
             self.__class__.graph = graph
-
         reset_stats()
         pyjitpl._warmrunnerdesc.memory_manager.alive_loops.clear()
-        return self.interp.eval_graph(self.graph, [llstr(code)])
+        return self.interp.eval_graph(self.graph, [i])
 
-    def test_add(self):
-        result = self.run("""
+    def define_add():
+        return """
         a = |30|
         b = a + a
         b -> 3
-        """)
+        """
+
+    def test_add(self):
+        result = self.run("add")
         self.check_loops({'getarrayitem_raw': 2, 'float_add': 1,
-                          'setarrayitem_raw': 1, 'int_add': 1,
-                          'int_lt': 1, 'guard_true': 1, 'jump': 1})
+                          'setarrayitem_raw': 1, 'int_add': 3,
+                          'int_ge': 1, 'guard_false': 1, 'jump': 1})
         assert result == 3 + 3
 
-    def test_floatadd(self):
-        result = self.run("""
+    def define_float_add():
+        return """
         a = |30| + 3
         a -> 3
-        """)
+        """
+
+    def test_floatadd(self):
+        result = self.run("float_add")
         assert result == 3 + 3
         self.check_loops({"getarrayitem_raw": 1, "float_add": 1,
-                          "setarrayitem_raw": 1, "int_add": 1,
-                          "int_lt": 1, "guard_true": 1, "jump": 1})
+                          "setarrayitem_raw": 1, "int_add": 2,
+                          "int_ge": 1, "guard_false": 1, "jump": 1})
 
-    def test_sum(self):
-        result = self.run("""
+    def define_sum():
+        return """
         a = |30|
         b = a + a
         sum(b)
-        """)
+        """
+
+    def test_sum(self):
+        result = self.run("sum")
         assert result == 2 * sum(range(30))
         self.check_loops({"getarrayitem_raw": 2, "float_add": 2,
-                          "int_add": 1,
-                          "int_lt": 1, "guard_true": 1, "jump": 1})
+                          "int_add": 2,
+                          "int_ge": 1, "guard_false": 1, "jump": 1})
 
-    def test_prod(self):
-        result = self.run("""
+    def define_prod():
+        return """
         a = |30|
         b = a + a
         prod(b)
-        """)
+        """
+
+    def test_prod(self):
+        result = self.run("prod")
         expected = 1
         for i in range(30):
             expected *= i * 2
         assert result == expected
         self.check_loops({"getarrayitem_raw": 2, "float_add": 1,
-                          "float_mul": 1, "int_add": 1,
-                          "int_lt": 1, "guard_true": 1, "jump": 1})
+                          "float_mul": 1, "int_add": 2,
+                          "int_ge": 1, "guard_false": 1, "jump": 1})
 
     def test_max(self):
         py.test.skip("broken, investigate")
@@ -117,50 +153,59 @@
                           "float_mul": 1, "int_add": 1,
                           "int_lt": 1, "guard_true": 1, "jump": 1})
 
-    def test_any(self):
-        result = self.run("""
+    def define_any():
+        return """
         a = [0,0,0,0,0,0,0,0,0,0,0]
         a[8] = -12
         b = a + a
         any(b)
-        """)
+        """
+
+    def test_any(self):
+        result = self.run("any")
         assert result == 1
         self.check_loops({"getarrayitem_raw": 2, "float_add": 1,
-                          "float_ne": 1, "int_add": 1,
-                          "int_lt": 1, "guard_true": 1, "jump": 1,
-                          "guard_false": 1})
+                          "float_ne": 1, "int_add": 2,
+                          "int_ge": 1, "jump": 1,
+                          "guard_false": 2})
 
-    def test_already_forced(self):
-        result = self.run("""
+    def define_already_forced():
+        return """
         a = |30|
         b = a + 4.5
         b -> 5 # forces
         c = b * 8
         c -> 5
-        """)
+        """
+
+    def test_already_forced(self):
+        result = self.run("already_forced")
         assert result == (5 + 4.5) * 8
         # This is the sum of the ops for both loops, however if you remove the
         # optimization then you end up with 2 float_adds, so we can still be
         # sure it was optimized correctly.
         self.check_loops({"getarrayitem_raw": 2, "float_mul": 1, "float_add": 1,
-                           "setarrayitem_raw": 2, "int_add": 2,
-                           "int_lt": 2, "guard_true": 2, "jump": 2})
+                           "setarrayitem_raw": 2, "int_add": 4,
+                           "int_ge": 2, "guard_false": 2, "jump": 2})
 
-    def test_ufunc(self):
-        result = self.run("""
+    def define_ufunc():
+        return """
         a = |30|
         b = a + a
         c = unegative(b)
         c -> 3
-        """)
+        """
+
+    def test_ufunc(self):
+        result = self.run("ufunc")
         assert result == -6
         self.check_loops({"getarrayitem_raw": 2, "float_add": 1, "float_neg": 1,
-                          "setarrayitem_raw": 1, "int_add": 1,
-                          "int_lt": 1, "guard_true": 1, "jump": 1,
+                          "setarrayitem_raw": 1, "int_add": 3,
+                          "int_ge": 1, "guard_false": 1, "jump": 1,
         })
 
-    def test_specialization(self):
-        self.run("""
+    def define_specialization():
+        return """
         a = |30|
         b = a + a
         c = unegative(b)
@@ -177,49 +222,97 @@
         d = a * a
         unegative(d)
         d -> 3
-        """)
+        """
+
+    def test_specialization(self):
+        self.run("specialization")
         # This is 3, not 2 because there is a bridge for the exit.
         self.check_loop_count(3)
 
+    def define_slice():
+        return """
+        a = |30|
+        b = a -> ::3
+        c = b + b
+        c -> 3
+        """
+
+    def test_slice(self):
+        result = self.run("slice")
+        assert result == 18
+        py.test.skip("Few remaining arraylen_gc left")
+        self.check_loops({'int_mul': 2, 'getarrayitem_raw': 2, 'float_add': 1,
+                          'setarrayitem_raw': 1, 'int_add': 3,
+                          'int_lt': 1, 'guard_true': 1, 'jump': 1})
+
+    def define_multidim():
+        return """
+        a = [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]
+        b = a + a
+        b -> 1 -> 1
+        """
+
+    def test_multidim(self):
+        result = self.run('multidim')
+        assert result == 8
+        self.check_loops({'float_add': 1, 'getarrayitem_raw': 2,
+                          'guard_false': 1, 'int_add': 3, 'int_ge': 1,
+                          'jump': 1, 'setarrayitem_raw': 1})
+        # int_add might be 1 here if we try slightly harder with
+        # reusing indexes or some optimization
+
+    def define_multidim_slice():
+        return """
+        a = [[1, 2, 3, 4], [3, 4, 5, 6], [5, 6, 7, 8], [7, 8, 9, 10], [9, 10, 11, 12], [11, 12, 13, 14], [13, 14, 15, 16], [16, 17, 18, 19]]
+        b = a -> ::2
+        c = b + b
+        c -> 1 -> 1
+        """
+
+    def test_multidim_slice(self):
+        result = self.run('multidim_slice')
+        assert result == 12
+        py.test.skip("improve")
+        # XXX the bridge here is scary. Hopefully jit-targets will fix that,
+        #     otherwise it looks kind of good
+        self.check_loops({})
+
+    def define_broadcast():
+        return """
+        a = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]
+        b = [1, 2, 3, 4]
+        c = a + b
+        c -> 1 -> 2
+        """
+
+    def test_broadcast(self):
+        result = self.run("broadcast")
+        assert result == 10
+        py.test.skip("improve")
+        self.check_loops({})
 
 class TestNumpyOld(LLJitMixin):
     def setup_class(cls):
+        py.test.skip("old")
         from pypy.module.micronumpy.compile import FakeSpace
         from pypy.module.micronumpy.interp_dtype import W_Float64Dtype
-        
+
         cls.space = FakeSpace()
         cls.float64_dtype = cls.space.fromcache(W_Float64Dtype)
-    
-    def test_slice(self):
-        def f(i):
-            step = 3
-            ar = SingleDimArray(step*i, dtype=self.float64_dtype)
-            new_sig = signature.Signature.find_sig([
-                SingleDimSlice.signature, ar.signature
-            ])
-            s = SingleDimSlice(0, step*i, step, i, ar, new_sig)
-            v = interp_ufuncs.get(self.space).add.call(self.space, [s, s])
-            return v.get_concrete().eval(3).val
-
-        result = self.meta_interp(f, [5], listops=True, backendopt=True)
-        self.check_loops({'int_mul': 1, 'getarrayitem_raw': 2, 'float_add': 1,
-                          'setarrayitem_raw': 1, 'int_add': 1,
-                          'int_lt': 1, 'guard_true': 1, 'jump': 1})
-        assert result == f(5)
 
     def test_slice2(self):
         def f(i):
             step1 = 2
             step2 = 3
-            ar = SingleDimArray(step2*i, dtype=self.float64_dtype)
+            ar = NDimArray(step2*i, dtype=self.float64_dtype)
             new_sig = signature.Signature.find_sig([
-                SingleDimSlice.signature, ar.signature
+                NDimSlice.signature, ar.signature
             ])
-            s1 = SingleDimSlice(0, step1*i, step1, i, ar, new_sig)
+            s1 = NDimSlice(0, step1*i, step1, i, ar, new_sig)
             new_sig = signature.Signature.find_sig([
-                SingleDimSlice.signature, s1.signature
+                NDimSlice.signature, s1.signature
             ])
-            s2 = SingleDimSlice(0, step2*i, step2, i, ar, new_sig)
+            s2 = NDimSlice(0, step2*i, step2, i, ar, new_sig)
             v = interp_ufuncs.get(self.space).add.call(self.space, [s1, s2])
             return v.get_concrete().eval(3).val
 
@@ -235,8 +328,8 @@
 
         def f(i):
             step = NonConstant(3)
-            ar = SingleDimArray(step*i, dtype=float64_dtype)
-            ar2 = SingleDimArray(i, dtype=float64_dtype)
+            ar = NDimArray(step*i, dtype=float64_dtype)
+            ar2 = NDimArray(i, dtype=float64_dtype)
             ar2.get_concrete().setitem(1, float64_dtype.box(5.5))
             arg = ar2.descr_add(space, ar2)
             ar.setslice(space, 0, step*i, step, i, arg)
@@ -262,7 +355,7 @@
                 dtype = float64_dtype
             else:
                 dtype = int32_dtype
-            ar = SingleDimArray(n, dtype=dtype)
+            ar = NDimArray(n, [n], dtype=dtype)
             i = 0
             while i < n:
                 ar.get_concrete().setitem(i, int32_dtype.box(7))
diff --git a/pypy/rlib/rsre/rpy.py b/pypy/rlib/rsre/rpy.py
new file mode 100644
--- /dev/null
+++ b/pypy/rlib/rsre/rpy.py
@@ -0,0 +1,49 @@
+
+from pypy.rlib.rsre import rsre_char
+from pypy.rlib.rsre.rsre_core import match
+
+def get_hacked_sre_compile(my_compile):
+    """Return a copy of the sre_compile module for which the _sre
+    module is a custom module that has _sre.compile == my_compile
+    and CODESIZE == rsre_char.CODESIZE.
+    """
+    import sre_compile, __builtin__, new
+    sre_hacked = new.module("_sre_hacked")
+    sre_hacked.compile = my_compile
+    sre_hacked.MAGIC = sre_compile.MAGIC
+    sre_hacked.CODESIZE = rsre_char.CODESIZE
+    sre_hacked.getlower = rsre_char.getlower
+    def my_import(name, *args):
+        if name == '_sre':
+            return sre_hacked
+        else:
+            return default_import(name, *args)
+    src = sre_compile.__file__
+    if src.lower().endswith('.pyc') or src.lower().endswith('.pyo'):
+        src = src[:-1]
+    mod = new.module("sre_compile_hacked")
+    default_import = __import__
+    try:
+        __builtin__.__import__ = my_import
+        execfile(src, mod.__dict__)
+    finally:
+        __builtin__.__import__ = default_import
+    return mod
+
+class GotIt(Exception):
+    pass
+def my_compile(pattern, flags, code, *args):
+    raise GotIt(code, flags, args)
+sre_compile_hacked = get_hacked_sre_compile(my_compile)
+
+def get_code(regexp, flags=0, allargs=False):
+    try:
+        sre_compile_hacked.compile(regexp, flags)
+    except GotIt, e:
+        pass
+    else:
+        raise ValueError("did not reach _sre.compile()!")
+    if allargs:
+        return e.args
+    else:
+        return e.args[0]
diff --git a/pypy/rlib/rsre/rsre_core.py b/pypy/rlib/rsre/rsre_core.py
--- a/pypy/rlib/rsre/rsre_core.py
+++ b/pypy/rlib/rsre/rsre_core.py
@@ -154,7 +154,6 @@
         return (fmarks[groupnum], fmarks[groupnum+1])
 
     def group(self, groupnum=0):
-        "NOT_RPYTHON"   # compatibility
         frm, to = self.span(groupnum)
         if 0 <= frm <= to:
             return self._string[frm:to]
diff --git a/pypy/rlib/rsre/test/test_match.py b/pypy/rlib/rsre/test/test_match.py
--- a/pypy/rlib/rsre/test/test_match.py
+++ b/pypy/rlib/rsre/test/test_match.py
@@ -1,54 +1,8 @@
 import re
-from pypy.rlib.rsre import rsre_core, rsre_char
+from pypy.rlib.rsre import rsre_core
+from pypy.rlib.rsre.rpy import get_code
 
 
-def get_hacked_sre_compile(my_compile):
-    """Return a copy of the sre_compile module for which the _sre
-    module is a custom module that has _sre.compile == my_compile
-    and CODESIZE == rsre_char.CODESIZE.
-    """
-    import sre_compile, __builtin__, new
-    sre_hacked = new.module("_sre_hacked")
-    sre_hacked.compile = my_compile
-    sre_hacked.MAGIC = sre_compile.MAGIC
-    sre_hacked.CODESIZE = rsre_char.CODESIZE
-    sre_hacked.getlower = rsre_char.getlower
-    def my_import(name, *args):
-        if name == '_sre':
-            return sre_hacked
-        else:
-            return default_import(name, *args)
-    src = sre_compile.__file__
-    if src.lower().endswith('.pyc') or src.lower().endswith('.pyo'):
-        src = src[:-1]
-    mod = new.module("sre_compile_hacked")
-    default_import = __import__
-    try:
-        __builtin__.__import__ = my_import
-        execfile(src, mod.__dict__)
-    finally:
-        __builtin__.__import__ = default_import
-    return mod
-
-class GotIt(Exception):
-    pass
-def my_compile(pattern, flags, code, *args):
-    print code
-    raise GotIt(code, flags, args)
-sre_compile_hacked = get_hacked_sre_compile(my_compile)
-
-def get_code(regexp, flags=0, allargs=False):
-    try:
-        sre_compile_hacked.compile(regexp, flags)
-    except GotIt, e:
-        pass
-    else:
-        raise ValueError("did not reach _sre.compile()!")
-    if allargs:
-        return e.args
-    else:
-        return e.args[0]
-
 def get_code_and_re(regexp):
     return get_code(regexp), re.compile(regexp)
 
diff --git a/pypy/rpython/rlist.py b/pypy/rpython/rlist.py
--- a/pypy/rpython/rlist.py
+++ b/pypy/rpython/rlist.py
@@ -668,6 +668,7 @@
     ll_delitem_nonneg(dum_nocheck, l, index)
     return res
 
+ at jit.look_inside_iff(lambda l: jit.isvirtual(l))
 def ll_reverse(l):
     length = l.ll_length()
     i = 0
@@ -678,7 +679,6 @@
         l.ll_setitem_fast(length_1_i, tmp)
         i += 1
         length_1_i -= 1
-ll_reverse.oopspec = 'list.reverse(l)'
 
 def ll_getitem_nonneg(func, l, index):
     ll_assert(index >= 0, "unexpectedly negative list getitem index")


More information about the pypy-commit mailing list