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>
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
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
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)
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
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
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
end
redef class CallSite
- super HInfoBoxable
redef fun infobox(v)
do
var res = new HInfoBox(v, "call {mpropdef}")
end
end
- redef class AForExpr
+ redef class AForGroup
redef fun decorate_tag(v, res, token)
do
if not token isa TId then return null
module scope
import phase
+import modelbuilder
redef class ToolContext
# Run `APropdef::do_scope` on each propdef.
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
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
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
# 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
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
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
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
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
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
end
var args = n_args.to_a
- callsite.check_signature(v, args)
+ callsite.check_signature(v, node, args)
end
end
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
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
# ~~~
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)])
# `[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
# `[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