Merge: Multi iterators
authorJean Privat <jean@pryen.org>
Wed, 7 Oct 2015 17:53:39 +0000 (13:53 -0400)
committerJean Privat <jean@pryen.org>
Wed, 7 Oct 2015 17:53:39 +0000 (13:53 -0400)
Introduce multi-iterators in `for`.

A single `for` can now iterates over more than one collection at once. An iterator is used for each collection and the iteration is finished once the shortest iterator is finished.

~~~nit
for i in [10, 20, 30], j in [1..10] do print i+j # outputs 11 22 33
~~~

As expected by POLA, multi-iterators are also naturally usable on maps and comprehension arrays

~~~nit
var m = new HashMap[Int,String]
m[1] = "one"
m[2] = "two"
for k, v in m, i in [1..10] do print "{k}:{v}:{i}"
# outputs 1:one:1 2:two:2

var a = [for i in [10, 20, 30], j in [1..10] do i + j]
print a # outputs [11, 22, 33]
~~~

Close #1735

Pull-Request: #1748
Reviewed-by: Alexis Laferrière <alexis.laf@xymus.net>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>

1  2 
src/compiler/abstract_compiler.nit
src/highlight.nit
src/semantize/scope.nit
src/semantize/typing.nit
src/transform.nit

@@@ -1147,7 -1147,6 +1147,7 @@@ abstract class AbstractCompilerVisito
  
        fun compile_callsite(callsite: CallSite, arguments: Array[RuntimeVariable]): nullable RuntimeVariable
        do
 +              if callsite.is_broken then return null
                var initializers = callsite.mpropdef.initializers
                if not initializers.is_empty then
                        var recv = arguments.first
        fun stmt(nexpr: nullable AExpr)
        do
                if nexpr == null then return
 -              if nexpr.mtype == null and not nexpr.is_typed then
 +              if nexpr.is_broken then
                        # Untyped expression.
                        # Might mean dead code or invalid code
                        # so aborts
  redef class AForExpr
        redef fun stmt(v)
        do
-               var cl = v.expr(self.n_expr, null)
-               var it_meth = self.method_iterator
-               assert it_meth != null
-               var it = v.compile_callsite(it_meth, [cl])
-               assert it != null
+               for g in n_groups do
+                       var cl = v.expr(g.n_expr, null)
+                       var it_meth = g.method_iterator
+                       assert it_meth != null
+                       var it = v.compile_callsite(it_meth, [cl])
+                       assert it != null
+                       g.it = it
+               end
                v.add("for(;;) \{")
-               var isok_meth = self.method_is_ok
-               assert isok_meth != null
-               var ok = v.compile_callsite(isok_meth, [it])
-               assert ok != null
-               v.add("if(!{ok}) break;")
-               if self.variables.length == 1 then
-                       var item_meth = self.method_item
-                       assert item_meth != null
-                       var i = v.compile_callsite(item_meth, [it])
-                       assert i != null
-                       v.assign(v.variable(variables.first), i)
-               else if self.variables.length == 2 then
-                       var key_meth = self.method_key
-                       assert key_meth != null
-                       var i = v.compile_callsite(key_meth, [it])
-                       assert i != null
-                       v.assign(v.variable(variables[0]), i)
-                       var item_meth = self.method_item
-                       assert item_meth != null
-                       i = v.compile_callsite(item_meth, [it])
-                       assert i != null
-                       v.assign(v.variable(variables[1]), i)
-               else
-                       abort
+               for g in n_groups do
+                       var it = g.it
+                       var isok_meth = g.method_is_ok
+                       assert isok_meth != null
+                       var ok = v.compile_callsite(isok_meth, [it])
+                       assert ok != null
+                       v.add("if(!{ok}) break;")
+                       if g.variables.length == 1 then
+                               var item_meth = g.method_item
+                               assert item_meth != null
+                               var i = v.compile_callsite(item_meth, [it])
+                               assert i != null
+                               v.assign(v.variable(g.variables.first), i)
+                       else if g.variables.length == 2 then
+                               var key_meth = g.method_key
+                               assert key_meth != null
+                               var i = v.compile_callsite(key_meth, [it])
+                               assert i != null
+                               v.assign(v.variable(g.variables[0]), i)
+                               var item_meth = g.method_item
+                               assert item_meth != null
+                               i = v.compile_callsite(item_meth, [it])
+                               assert i != null
+                               v.assign(v.variable(g.variables[1]), i)
+                       else
+                               abort
+                       end
                end
                v.stmt(self.n_block)
                v.add_escape_label(continue_mark)
-               var next_meth = self.method_next
-               assert next_meth != null
-               v.compile_callsite(next_meth, [it])
+               for g in n_groups do
+                       var next_meth = g.method_next
+                       assert next_meth != null
+                       v.compile_callsite(next_meth, [g.it])
+               end
                v.add("\}")
                v.add_escape_label(break_mark)
  
-               var method_finish = self.method_finish
-               if method_finish != null then
-                       # TODO: Find a way to call this also in long escape (e.g. return)
-                       v.compile_callsite(method_finish, [it])
+               for g in n_groups do
+                       var method_finish = g.method_finish
+                       if method_finish != null then
+                               # TODO: Find a way to call this also in long escape (e.g. return)
+                               v.compile_callsite(method_finish, [g.it])
+                       end
                end
        end
  end
  
+ redef class AForGroup
+       # C variable representing the iterator
+       private var it: RuntimeVariable is noinit
+ end
  redef class AAssertExpr
        redef fun stmt(v)
        do
@@@ -3651,7 -3665,6 +3666,7 @@@ redef class ASendExp
        do
                var recv = v.expr(self.n_expr, null)
                var callsite = self.callsite.as(not null)
 +              if callsite.is_broken then return null
                var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments)
                return v.compile_callsite(callsite, args)
        end
@@@ -3662,7 -3675,6 +3677,7 @@@ redef class ASendReassignFormExp
        do
                var recv = v.expr(self.n_expr, null)
                var callsite = self.callsite.as(not null)
 +              if callsite.is_broken then return
                var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.raw_arguments)
  
                var value = v.expr(self.n_value, null)
@@@ -3685,7 -3697,6 +3700,7 @@@ redef class ASuperExp
  
                var callsite = self.callsite
                if callsite != null then
 +                      if callsite.is_broken then return null
                        var args
  
                        if self.n_args.n_exprs.is_empty then
@@@ -3735,7 -3746,6 +3750,7 @@@ redef class ANewExp
  
                var callsite = self.callsite
                if callsite == null then return recv
 +              if callsite.is_broken then return null
  
                var args = v.varargize(callsite.mpropdef, callsite.signaturemap, recv, self.n_args.n_exprs)
                var res2 = v.compile_callsite(callsite, args)
  # Here we load an process all modules passed on the command line
  var mmodules = modelbuilder.parse(arguments)
  
 -if mmodules.is_empty then return
 +if mmodules.is_empty then
 +      toolcontext.check_errors
 +      toolcontext.errors_info
 +      if toolcontext.error_count > 0 then exit(1) else exit(0)
 +end
 +
  modelbuilder.run_phases
  
  for mmodule in mmodules do
diff --combined src/highlight.nit
@@@ -373,7 -373,7 +373,7 @@@ redef class MClassDe
                        res.new_field("class").text(mclass.name)
                else
                        res.new_field("redef class").text(mclass.name)
 -                      res.new_field("intro").add mclass.intro.linkto_text("in {mclass.intro.mmodule.to_s}")
 +                      res.new_field("intro").add mclass.intro.linkto_text("in {mclass.intro_mmodule.to_s}")
                end
                var mdoc = self.mdoc
                if mdoc == null then mdoc = mclass.intro.mdoc
@@@ -584,6 -584,7 +584,6 @@@ redef class MSignatur
  end
  
  redef class CallSite
 -      super HInfoBoxable
        redef fun infobox(v)
        do
                var res = new HInfoBox(v, "call {mpropdef}")
@@@ -741,7 -742,7 +741,7 @@@ redef class AVardeclExp
        end
  end
  
- redef class AForExpr
+ redef class AForGroup
        redef fun decorate_tag(v, res, token)
        do
                if not token isa TId then return null
diff --combined src/semantize/scope.nit
@@@ -18,7 -18,6 +18,7 @@@
  module scope
  
  import phase
 +import modelbuilder
  
  redef class ToolContext
        # Run `APropdef::do_scope` on each propdef.
@@@ -192,7 -191,6 +192,7 @@@ private class ScopeVisito
                                var res = search_label("")
                                if res == null then
                                        self.error(nlabel, "Syntax Error: invalid anonymous label.")
 +                                      node.is_broken = true
                                        return null
                                end
                                return res
                        var res = search_label(name)
                        if res == null then
                                self.error(nlabel, "Syntax Error: invalid label `{name}`.")
 +                              node.is_broken = true
                                return null
                        end
                        return res
        fun error(node: ANode, message: String)
        do
                self.toolcontext.error(node.hot_location, message)
 +              node.is_broken = true
        end
  end
  
@@@ -381,9 -377,6 +381,6 @@@ redef class ALoopExp
  end
  
  redef class AForExpr
-       # The automatic variables in order
-       var variables: nullable Array[Variable]
        # The break escape mark associated with the 'for'
        var break_mark: nullable EscapeMark
  
  
        redef fun accept_scope_visitor(v)
        do
-               v.enter_visit(n_expr)
+               for g in n_groups do
+                       v.enter_visit(g.n_expr)
+               end
  
                # Protect automatic variables
                v.scopes.unshift(new Scope)
  
-               # Create the automatic variables
-               var variables = new Array[Variable]
-               self.variables = variables
-               for nid in n_ids do
-                       var va = new Variable(nid.text)
-                       v.register_variable(nid, va)
-                       variables.add(va)
+               for g in n_groups do
+                       # Create the automatic variables
+                       var variables = new Array[Variable]
+                       g.variables = variables
+                       for nid in g.n_ids do
+                               var va = new Variable(nid.text)
+                               v.register_variable(nid, va)
+                               variables.add(va)
+                       end
                end
  
                var escapemark = v.make_escape_mark(n_label, true)
        end
  end
  
+ redef class AForGroup
+       # The automatic variables in order
+       var variables: nullable Array[Variable]
+ end
  redef class AWithExpr
        # The break escape mark associated with the 'with'
        var break_mark: nullable EscapeMark
diff --combined src/semantize/typing.nit
@@@ -389,7 -389,7 +389,7 @@@ private class TypeVisito
                        end
                end
  
 -              var callsite = new CallSite(node, recvtype, mmodule, anchor, recv_is_self, mproperty, mpropdef, msignature, erasure_cast)
 +              var callsite = new CallSite(node.hot_location, recvtype, mmodule, anchor, recv_is_self, mproperty, mpropdef, msignature, erasure_cast)
                return callsite
        end
  
  
        fun error(node: ANode, message: String)
        do
 -              self.modelbuilder.toolcontext.error(node.hot_location, message)
 +              self.modelbuilder.error(node, message)
        end
  
        fun get_variable(node: AExpr, variable: Variable): nullable MType
@@@ -621,10 -621,8 +621,10 @@@ en
  
  # A specific method call site with its associated informations.
  class CallSite
 -      # The associated node for location
 -      var node: ANode
 +      super MEntity
 +
 +      # The associated location of the callsite
 +      var location: Location
  
        # The static type of the receiver (possibly unresolved)
        var recv: MType
        # If null then no specific association is required.
        var signaturemap: nullable SignatureMap = null
  
 -      private fun check_signature(v: TypeVisitor, args: Array[AExpr]): Bool
 +      private fun check_signature(v: TypeVisitor, node: ANode, args: Array[AExpr]): Bool
        do
 -              var map = v.check_signature(self.node, args, self.mproperty, self.msignature)
 +              var map = v.check_signature(node, args, self.mproperty, self.msignature)
                signaturemap = map
 +              if map == null then is_broken = true
                return map == null
        end
  end
@@@ -784,9 -781,6 +784,9 @@@ private class PostTypingVisito
        redef fun visit(n) do
                n.visit_all(self)
                n.accept_post_typing(type_visitor)
 +              if n isa AExpr and n.mtype == null and not n.is_typed then
 +                      n.is_broken = true
 +              end
        end
  end
  
@@@ -1126,6 -1120,24 +1126,24 @@@ redef class ALoopExp
  end
  
  redef class AForExpr
+       redef fun accept_typing(v)
+       do
+               v.has_loop = true
+               for g in n_groups do
+                       var mtype = v.visit_expr(g.n_expr)
+                       if mtype == null then return
+                       g.do_type_iterator(v, mtype)
+               end
+               v.visit_stmt(n_block)
+               self.mtype = n_block.mtype
+               self.is_typed = true
+       end
+ end
+ redef class AForGroup
        var coltype: nullable MClassType
  
        var method_iterator: nullable CallSite
                        self.method_successor = v.get_method(self, vtype, "successor", false)
                end
        end
-       redef fun accept_typing(v)
-       do
-               v.has_loop = true
-               var mtype = v.visit_expr(n_expr)
-               if mtype == null then return
-               self.do_type_iterator(v, mtype)
-               v.visit_stmt(n_block)
-               self.mtype = n_block.mtype
-               self.is_typed = true
-       end
  end
  
  redef class AWithExpr
@@@ -1726,7 -1724,7 +1730,7 @@@ redef class ASendExp
  
                var args = compute_raw_arguments
  
 -              callsite.check_signature(v, args)
 +              callsite.check_signature(v, node, args)
  
                if callsite.mproperty.is_init then
                        var vmpropdef = v.mpropdef
@@@ -1845,7 -1843,7 +1849,7 @@@ redef class ASendReassignFormExp
  
                var args = compute_raw_arguments
  
 -              callsite.check_signature(v, args)
 +              callsite.check_signature(v, node, args)
  
                var readtype = callsite.msignature.return_mtype
                if readtype == null then
  
                args = args.to_a # duplicate so raw_arguments keeps only the getter args
                args.add(self.n_value)
 -              wcallsite.check_signature(v, args)
 +              wcallsite.check_signature(v, node, args)
  
                self.is_typed = true
        end
@@@ -1983,12 -1981,12 +1987,12 @@@ redef class ASuperExp
                var msignature = superprop.new_msignature or else superprop.msignature.as(not null)
                msignature = v.resolve_for(msignature, recvtype, true).as(MSignature)
  
 -              var callsite = new CallSite(self, recvtype, v.mmodule, v.anchor, true, superprop.mproperty, superprop, msignature, false)
 +              var callsite = new CallSite(hot_location, recvtype, v.mmodule, v.anchor, true, superprop.mproperty, superprop, msignature, false)
                self.callsite = callsite
  
                var args = self.n_args.to_a
                if args.length > 0 then
 -                      callsite.check_signature(v, args)
 +                      callsite.check_signature(v, self, args)
                else
                        # Check there is at least enough parameters
                        if mpropdef.msignature.arity < msignature.arity then
@@@ -2087,7 -2085,7 +2091,7 @@@ redef class ANewExp
                end
  
                var args = n_args.to_a
 -              callsite.check_signature(v, args)
 +              callsite.check_signature(v, node, args)
        end
  end
  
diff --combined src/transform.nit
@@@ -107,7 -107,7 +107,7 @@@ redef class AExp
  
                visit_all(v)
  
 -              if mtype == null and not is_typed then return # Skip broken
 +              if is_broken then return # Skip broken
  
                accept_transform_visitor(v)
        end
@@@ -213,89 -213,97 +213,97 @@@ redef class AForExp
                var escapemark = self.break_mark
                assert escapemark != null
  
+               # Main block that will contain the whole for and will replace `self`
                var nblock = v.builder.make_block
  
+               # Part before the loop
+               var before = v.builder.make_block
+               nblock.add before
+               # The loop
+               var nloop = v.builder.make_loop
+               nloop.break_mark = escapemark
+               nblock.add nloop
+               # Part before the body inside the loop
+               var begin = v.builder.make_block
+               nloop.add begin
+               # The `do` block with then user code
+               var ndo = v.builder.make_do
+               ndo.break_mark = escapemark.continue_mark
+               nloop.add ndo
+               ndo.add self.n_block.as(not null)
+               # Fill up each part
+               for g in n_groups do
+                       g.transform_in(v, before, begin, nloop, nblock, escapemark)
+               end
+               replace_with(nblock)
+       end
+ end
+ redef class AForGroup
+       private fun transform_in(v: TransformVisitor, before, begin, next, finish: AExpr, escapemark: EscapeMark)
+       do
                var nexpr = n_expr
  
                # Shortcut on explicit range
                # Avoid the instantiation of the range and the iterator
                if self.variables.length == 1 and nexpr isa ARangeExpr and not v.phase.toolcontext.opt_no_shortcut_range.value then
+                       # Before: evaluate bounds
                        var variable = variables.first
-                       nblock.add v.builder.make_var_assign(variable, nexpr.n_expr)
+                       before.add v.builder.make_var_assign(variable, nexpr.n_expr)
                        var to = nexpr.n_expr2
-                       nblock.add to
-                       var nloop = v.builder.make_loop
-                       nloop.break_mark = escapemark
-                       nblock.add nloop
+                       before.add to
  
+                       # Begin: check variable
                        var is_ok = v.builder.make_call(v.builder.make_var_read(variable, variable.declared_type.as(not null)), method_lt.as(not null), [to.make_var_read])
                        var nif = v.builder.make_if(is_ok, null)
-                       nloop.add nif
-                       var nthen = nif.n_then
-                       var ndo = v.builder.make_do
-                       ndo.break_mark = escapemark.continue_mark
-                       nthen.add ndo
-                       ndo.add self.n_block.as(not null)
+                       begin.add nif
+                       nif.n_else.add v.builder.make_break(escapemark)
  
+                       # Next: increment one
                        var one = v.builder.make_int(1)
                        var succ = v.builder.make_call(v.builder.make_var_read(variable, variable.declared_type.as(not null)), method_successor.as(not null), [one])
-                       nthen.add v.builder.make_var_assign(variable, succ)
-                       var nbreak = v.builder.make_break(escapemark)
-                       nif.n_else.add nbreak
-                       replace_with(nblock)
+                       next.add v.builder.make_var_assign(variable, succ)
                        return
                end
  
-               nblock.add nexpr
+               # Before: evaluate expr, make the iterator
+               before.add nexpr
                var iter = v.builder.make_call(nexpr.make_var_read, method_iterator.as(not null), null)
-               nblock.add iter
-               var nloop = v.builder.make_loop
-               nloop.break_mark = escapemark
-               nblock.add nloop
+               before.add iter
  
+               # Begin: check iterator `is_ok`
                var is_ok = v.builder.make_call(iter.make_var_read, method_is_ok.as(not null), null)
                var nif = v.builder.make_if(is_ok, null)
-               nloop.add nif
+               begin.add nif
+               nif.n_else.add v.builder.make_break(escapemark)
  
-               var nthen = nif.n_then
-               var ndo = v.builder.make_do
-               ndo.break_mark = escapemark.continue_mark
-               nthen.add ndo
-               if self.variables.length == 1 then
+               # Begin: assign automatic variables
+               if variables.length == 1 then
                        var item = v.builder.make_call(iter.make_var_read, method_item.as(not null), null)
-                       ndo.add v.builder.make_var_assign(variables.first, item)
-               else if self.variables.length == 2 then
+                       begin.add v.builder.make_var_assign(variables.first, item)
+               else if variables.length == 2 then
                        var key = v.builder.make_call(iter.make_var_read, method_key.as(not null), null)
-                       ndo.add v.builder.make_var_assign(variables[0], key)
+                       begin.add v.builder.make_var_assign(variables[0], key)
                        var item = v.builder.make_call(iter.make_var_read, method_item.as(not null), null)
-                       ndo.add v.builder.make_var_assign(variables[1], item)
+                       begin.add v.builder.make_var_assign(variables[1], item)
                else
                        abort
                end
  
-               ndo.add self.n_block.as(not null)
-               nthen.add v.builder.make_call(iter.make_var_read, method_next.as(not null), null)
+               # Next: call next
+               next.add v.builder.make_call(iter.make_var_read, method_next.as(not null), null)
  
-               var nbreak = v.builder.make_break(escapemark)
-               nif.n_else.add nbreak
-               var method_finish = self.method_finish
+               # Finish: call finish
+               var method_finish = method_finish
                if method_finish != null then
-                       nblock.add v.builder.make_call(iter.make_var_read, method_finish, null)
+                       finish.add v.builder.make_call(iter.make_var_read, method_finish, null)
                end
-               replace_with(nblock)
        end
  end
  
@@@ -361,8 -369,6 +369,8 @@@ redef class AArrayExp
        # ~~~
        redef fun full_transform_visitor(v)
        do
 +              if is_broken then return # Skip broken
 +
                var nblock = v.builder.make_block
  
                var nnew = v.builder.make_new(with_capacity_callsite.as(not null), [v.builder.make_int(n_exprs.length)])
@@@ -388,7 -394,7 +396,7 @@@ redef class ACrangeExp
        # `[x..y]` is replaced with `new Range[X](x,y)`
        redef fun accept_transform_visitor(v)
        do
-               if parent isa AForExpr then return # to permit shortcut ranges
+               if parent isa AForGroup then return # to permit shortcut ranges
                replace_with(v.builder.make_new(init_callsite.as(not null), [n_expr, n_expr2]))
        end
  end
@@@ -397,7 -403,7 +405,7 @@@ redef class AOrangeExp
        # `[x..y[` is replaced with `new Range[X].without_last(x,y)`
        redef fun accept_transform_visitor(v)
        do
-               if parent isa AForExpr then return # to permit shortcut ranges
+               if parent isa AForGroup then return # to permit shortcut ranges
                replace_with(v.builder.make_new(init_callsite.as(not null), [n_expr, n_expr2]))
        end
  end