transform: transform the `with` statement
[nit.git] / src / transform.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 # Thansformations that simplify the AST of expressions
16 # This module transform complex AST `AExpr` nodes into simplier ones
17 module transform
18
19 import astbuilder
20 import astvalidation
21 import semantize
22 intrude import semantize::scope
23
24 redef class ToolContext
25 var transform_phase: Phase = new TransformPhase(self, [typing_phase, auto_super_init_phase])
26
27 # --no-shortcut-range
28 var opt_no_shortcut_range: OptionBool = new OptionBool("Always insantiate a range and its iterator on 'for' loops", "--no-shortcut-range")
29
30 redef init
31 do
32 super
33 self.option_context.add_option(self.opt_no_shortcut_range)
34 end
35 end
36
37 private class TransformPhase
38 super Phase
39
40 redef fun process_npropdef(npropdef: APropdef)
41 do
42 var val
43
44 var m = npropdef.mpropdef
45 if m == null then return
46 var v = new TransformVisitor(self, m)
47 v.enter_visit(npropdef)
48
49 val = new ASTValidationVisitor
50 val.enter_visit(npropdef)
51 end
52 end
53
54 private class TransformVisitor
55 super Visitor
56
57 var phase: TransformPhase
58 var mmodule: MModule is noinit
59 var mclassdef: MClassDef is noinit
60 var mpropdef: MPropDef
61 var builder: ASTBuilder is noinit
62
63 init
64 do
65 self.mclassdef = mpropdef.mclassdef
66 self.mmodule = mclassdef.mmodule
67 self.builder = new ASTBuilder(mmodule, mpropdef.mclassdef.bound_mtype)
68 end
69
70 redef fun visit(node)
71 do
72 if node isa AAnnotations then return
73 node.full_transform_visitor(self)
74 end
75
76 # Get a primitive method or display a fatal error on `location`.
77 fun get_method(location: AExpr, name: String, recv: MClass): MMethod
78 do
79 return phase.toolcontext.modelbuilder.force_get_primitive_method(location, name, recv, mmodule)
80 end
81 end
82
83 redef class ANode
84 private fun full_transform_visitor(v: TransformVisitor)
85 do
86 visit_all(v)
87 accept_transform_visitor(v)
88 end
89 private fun accept_transform_visitor(v: TransformVisitor)
90 do
91 end
92 end
93
94 redef class AExpr
95 redef fun full_transform_visitor(v: TransformVisitor)
96 do
97 var na = comprehension
98 if na != null then
99 # We are building a comprehension array `array`
100 # Replace `self` with `array.push(self)`
101 var place = detach_with_placeholder
102 var recv = na.nnew.make_var_read
103 var nadd = v.builder.make_call(recv, na.push_callsite.as(not null), [self])
104 place.replace_with(nadd)
105 end
106 super
107 end
108 end
109
110 redef class AVardeclExpr
111 # `var x = y` is replaced with `x = y`
112 #
113 # Declarations are only useful for scope rules
114 # Once names are associated with real objects, ther declaration become useless
115 # Therefore, if there is no initial value, then just ignore it
116 # Else, replace it with a simple assignment
117 redef fun accept_transform_visitor(v)
118 do
119 var nexpr = n_expr
120 if nexpr == null then
121 # do nothing
122 # note: not detached because the collection is currently under iteration
123 else
124 var nvar = v.builder.make_var_assign(self.variable.as(not null), nexpr)
125 replace_with(nvar)
126 end
127 end
128 end
129
130 redef class AIfexprExpr
131 # is replaced with `AIfExpr`
132 # Expression if and statement-if use two distinct classes for historical reasons
133 # However, on can replace the `AIfexprExpr` with the simpler `AIfExpr`
134 redef fun accept_transform_visitor(v)
135 do
136 var nif = v.builder.make_if(n_expr, self.mtype)
137 nif.n_then.add(n_then)
138 nif.n_else.add(n_else)
139
140 replace_with(nif)
141 end
142 end
143
144 redef class AOrExpr
145 # `x or y` is replaced with `if x then x else y`
146 redef fun accept_transform_visitor(v)
147 do
148 var nif = v.builder.make_if(n_expr, self.mtype)
149 nif.n_then.add(n_expr.make_var_read)
150 nif.n_else.add(n_expr2)
151
152 replace_with(nif)
153 end
154 end
155
156 redef class AImpliesExpr
157 redef fun accept_transform_visitor(v)
158 do
159 # TODO
160 end
161 end
162
163 redef class AAndExpr
164 # `x and y` is replaced with `if x then y else x`
165 redef fun accept_transform_visitor(v)
166 do
167 var nif = v.builder.make_if(n_expr, self.mtype)
168 nif.n_then.add(n_expr2)
169 nif.n_else.add(n_expr.make_var_read)
170
171 replace_with(nif)
172 end
173 end
174
175 redef class AWhileExpr
176 redef fun accept_transform_visitor(v)
177 do
178 var nloop = v.builder.make_loop
179 var nif = v.builder.make_if(n_expr, null)
180 nloop.add nif
181
182 var nblock = n_block
183 if nblock != null then nif.n_then.add nblock
184
185 var escapemark = self.break_mark.as(not null)
186 var nbreak = v.builder.make_break(escapemark)
187 nif.n_else.add nbreak
188
189 nloop.break_mark = self.break_mark
190 nloop.continue_mark = self.continue_mark
191
192 replace_with(nloop)
193 end
194 end
195
196 redef class AForExpr
197 redef fun accept_transform_visitor(v)
198 do
199 var escapemark = self.break_mark
200 assert escapemark != null
201
202 var nblock = v.builder.make_block
203
204 var nexpr = n_expr
205
206 # Shortcut on explicit range
207 # Avoid the instantiation of the range and the iterator
208 if self.variables.length == 1 and nexpr isa ARangeExpr and not v.phase.toolcontext.opt_no_shortcut_range.value then
209 var variable = variables.first
210 nblock.add v.builder.make_var_assign(variable, nexpr.n_expr)
211 var to = nexpr.n_expr2
212 nblock.add to
213
214 var nloop = v.builder.make_loop
215 nloop.break_mark = escapemark
216 nblock.add nloop
217
218 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])
219
220 var nif = v.builder.make_if(is_ok, null)
221 nloop.add nif
222
223 var nthen = nif.n_then
224 var ndo = v.builder.make_do
225 ndo.break_mark = escapemark.continue_mark
226 nthen.add ndo
227
228 ndo.add self.n_block.as(not null)
229
230 var one = v.builder.make_int(1)
231 var succ = v.builder.make_call(v.builder.make_var_read(variable, variable.declared_type.as(not null)), method_successor.as(not null), [one])
232 nthen.add v.builder.make_var_assign(variable, succ)
233
234 var nbreak = v.builder.make_break(escapemark)
235 nif.n_else.add nbreak
236
237 replace_with(nblock)
238 return
239 end
240
241 nblock.add nexpr
242
243 var iter = v.builder.make_call(nexpr.make_var_read, method_iterator.as(not null), null)
244 nblock.add iter
245
246 var nloop = v.builder.make_loop
247 nloop.break_mark = escapemark
248 nblock.add nloop
249
250 var is_ok = v.builder.make_call(iter.make_var_read, method_is_ok.as(not null), null)
251
252 var nif = v.builder.make_if(is_ok, null)
253 nloop.add nif
254
255 var nthen = nif.n_then
256 var ndo = v.builder.make_do
257 ndo.break_mark = escapemark.continue_mark
258 nthen.add ndo
259
260 if self.variables.length == 1 then
261 var item = v.builder.make_call(iter.make_var_read, method_item.as(not null), null)
262 ndo.add v.builder.make_var_assign(variables.first, item)
263 else if self.variables.length == 2 then
264 var key = v.builder.make_call(iter.make_var_read, method_key.as(not null), null)
265 ndo.add v.builder.make_var_assign(variables[0], key)
266 var item = v.builder.make_call(iter.make_var_read, method_item.as(not null), null)
267 ndo.add v.builder.make_var_assign(variables[1], item)
268 else
269 abort
270 end
271
272 ndo.add self.n_block.as(not null)
273
274 nthen.add v.builder.make_call(iter.make_var_read, method_next.as(not null), null)
275
276 var nbreak = v.builder.make_break(escapemark)
277 nif.n_else.add nbreak
278
279 var method_finish = self.method_finish
280 if method_finish != null then
281 nblock.add v.builder.make_call(iter.make_var_read, method_finish, null)
282 end
283
284 replace_with(nblock)
285 end
286 end
287
288 redef class AWithExpr
289 # is replaced with a do/end and injected calls to `start` and `finish`
290 #
291 # Basically, the following
292 #
293 # ~~~nitish
294 # with expr do
295 # block
296 # end label l
297 # ~~~
298 #
299 # is transformed into
300 #
301 # ~~~nitish
302 # var x = expr
303 # do
304 # x.start
305 # block
306 # end label l
307 # x.finish
308 # ~~~
309 #
310 # The point is that `finish` is called even if the block is escaped.
311 redef fun accept_transform_visitor(v)
312 do
313 var escapemark = self.break_mark
314 assert escapemark != null
315
316 var nblock = v.builder.make_block
317
318 var nexpr = n_expr
319
320 nblock.add nexpr
321
322 var ndo = v.builder.make_do
323 ndo.break_mark = escapemark
324
325 var start = v.builder.make_call(nexpr.make_var_read, method_start.as(not null), null)
326
327 ndo.add start
328
329 ndo.add self.n_block.as(not null)
330
331 nblock.add ndo
332
333 nblock.add v.builder.make_call(nexpr.make_var_read, method_finish.as(not null), null)
334
335 replace_with(nblock)
336 end
337 end
338
339 redef class AArrayExpr
340 # `[x,y]` is replaced with
341 #
342 # ~~~nitish
343 # var t = new Array[X].with_capacity(2)
344 # t.add(x)
345 # t.add(y)
346 # t
347 # ~~~
348 redef fun full_transform_visitor(v)
349 do
350 var nblock = v.builder.make_block
351
352 var nnew = v.builder.make_new(with_capacity_callsite.as(not null), [v.builder.make_int(n_exprs.length)])
353 self.nnew = nnew
354
355 nblock.add nnew
356
357 super
358
359 for nexpr in self.n_exprs do
360 nblock.add nexpr
361 end
362 var nres = nnew.make_var_read
363 nblock.add nres
364
365 replace_with(nblock)
366 end
367
368 private var nnew: ANewExpr is noinit
369 end
370
371 redef class ACrangeExpr
372 # `[x..y]` is replaced with `new Range[X](x,y)`
373 redef fun accept_transform_visitor(v)
374 do
375 if parent isa AForExpr then return # to permit shortcut ranges
376 replace_with(v.builder.make_new(init_callsite.as(not null), [n_expr, n_expr2]))
377 end
378 end
379
380 redef class AOrangeExpr
381 # `[x..y[` is replaced with `new Range[X].without_last(x,y)`
382 redef fun accept_transform_visitor(v)
383 do
384 if parent isa AForExpr then return # to permit shortcut ranges
385 replace_with(v.builder.make_new(init_callsite.as(not null), [n_expr, n_expr2]))
386 end
387 end
388
389 redef class AParExpr
390 # `(x)` is replaced with `x`
391 redef fun accept_transform_visitor(v)
392 do
393 replace_with(n_expr)
394 end
395 end
396
397 redef class ASendReassignFormExpr
398 # `x.foo(y)+=z` is replaced with
399 #
400 # ~~~nitish
401 # x.foo(y) = x.foo(y) + z
402 # ~~~
403 #
404 # witch is, in reality:
405 #
406 # ~~~nitish
407 # x."foo="(y, x.foo(y)."+"(z))
408 # ~~~
409 redef fun accept_transform_visitor(v)
410 do
411 var nblock = v.builder.make_block
412 nblock.add(n_expr)
413
414 var read_args = new Array[AExpr]
415 var write_args = new Array[AExpr]
416 for a in raw_arguments do
417 nblock.add(a)
418 read_args.add(a.make_var_read)
419 write_args.add(a.make_var_read)
420 end
421
422 var nread = v.builder.make_call(n_expr.make_var_read, callsite.as(not null), read_args)
423
424 var nnewvalue = v.builder.make_call(nread, reassign_callsite.as(not null), [n_value])
425
426 write_args.add(nnewvalue)
427 var nwrite = v.builder.make_call(n_expr.make_var_read, write_callsite.as(not null), write_args)
428 nblock.add(nwrite)
429
430 replace_with(nblock)
431 end
432 end
433
434 redef class AVarReassignExpr
435 # `v += z` is replaced with `v = v + z`
436 redef fun accept_transform_visitor(v)
437 do
438 var variable = self.variable.as(not null)
439
440 var nread = v.builder.make_var_read(variable, read_type.as(not null))
441
442 var nnewvalue = v.builder.make_call(nread, reassign_callsite.as(not null), [n_value])
443 var nwrite = v.builder.make_var_assign(variable, nnewvalue)
444
445 replace_with(nwrite)
446 end
447 end
448
449 redef class AAttrReassignExpr
450 # `x.a += z` is replaced with `x.a = x.a + z`
451 redef fun accept_transform_visitor(v)
452 do
453 var nblock = v.builder.make_block
454 nblock.add(n_expr)
455 var attribute = self.mproperty.as(not null)
456
457 var nread = v.builder.make_attr_read(n_expr.make_var_read, attribute)
458 var nnewvalue = v.builder.make_call(nread, reassign_callsite.as(not null), [n_value])
459 var nwrite = v.builder.make_attr_assign(n_expr.make_var_read, attribute, nnewvalue)
460 nblock.add(nwrite)
461
462 replace_with(nblock)
463 end
464 end