frontend: handle multi-iterators
[nit.git] / src / semantize / scope.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2012 Jean Privat <jean@pryen.org>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 # Identification and scoping of local variables and labels.
18 module scope
19
20 import phase
21
22 redef class ToolContext
23 # Run `APropdef::do_scope` on each propdef.
24 var scope_phase: Phase = new ScopePhase(self, null)
25 end
26
27 private class ScopePhase
28 super Phase
29 redef fun process_npropdef(npropdef) do npropdef.do_scope(toolcontext)
30 end
31
32
33 # A local variable (including parameters, automatic variables and self)
34 class Variable
35 # The name of the variable (as used in the program)
36 var name: String is writable
37
38 # Alias of `name`
39 redef fun to_s do return self.name
40
41 # The declaration of the variable, if any
42 var location: nullable Location = null is writable
43
44 # Is the local variable not read and need a warning?
45 var warn_unread = false is writable
46 end
47
48 # Mark where break and continue will branch.
49 # Marks are either associated with a label of with a for_loop structure
50 class EscapeMark
51 # The name of the label (unless the mark is an anonymous loop mark)
52 var name: nullable String
53
54 # The associated `continue` mark, if any.
55 # If the mark attached to a loop (loop, while, for), a distinct mark is used.
56 private var continue_mark: nullable EscapeMark = null
57
58 # Each break/continue attached to the mark
59 var escapes = new Array[AEscapeExpr]
60 end
61
62 # Visit a npropdef and:
63 # * Identify variables and labels
64 # * Associate each break and continue to its escapemark
65 # * Transform `ACallFormExpr` that access a variable into `AVarFormExpr`
66 # FIXME: Should the class be private?
67 private class ScopeVisitor
68 super Visitor
69
70 # The tool context used to display errors
71 var toolcontext: ToolContext
72
73 var selfvariable = new Variable("self")
74
75 init
76 do
77 scopes.add(new Scope)
78 end
79
80 # All stacked scope. `scopes.first` is the current scope
81 var scopes = new List[Scope]
82
83 # Shift and check the last scope
84 fun shift_scope
85 do
86 assert not scopes.is_empty
87 var scope = scopes.shift
88 for v in scope.variables.values do
89 if v.warn_unread then
90 toolcontext.advice(v.location, "unread-variable", "Warning: local variable {v.name} is never read.")
91 end
92 end
93 end
94
95 # Register a local variable.
96 # Display an error on toolcontext if a variable with the same name is masked.
97 fun register_variable(node: ANode, variable: Variable): Bool
98 do
99 var name = variable.name
100 var found = search_variable(name)
101 if found != null then
102 self.error(node, "Error: a variable named `{name}` already exists.")
103 return false
104 end
105 scopes.first.variables[name] = variable
106 variable.location = node.location
107 return true
108 end
109
110 # Look for a variable named `name`.
111 # Return null if no such a variable is found.
112 fun search_variable(name: String): nullable Variable
113 do
114 for scope in scopes do
115 var res = scope.get_variable(name)
116 if res != null then
117 return res
118 end
119 end
120 return null
121 end
122
123 redef fun visit(n)
124 do
125 n.accept_scope_visitor(self)
126 end
127
128 # Enter in a statement block `node` as inside a new scope.
129 # The block can be optionally attached to an `escapemark`.
130 fun enter_visit_block(node: nullable AExpr, escapemark: nullable EscapeMark)
131 do
132 if node == null then return
133 var scope = new Scope
134 scope.escapemark = escapemark
135 scopes.unshift(scope)
136 enter_visit(node)
137 shift_scope
138 end
139
140 # Look for a label `name`.
141 # Return null if no such a label is found.
142 fun search_label(name: String): nullable EscapeMark
143 do
144 for scope in scopes do
145 var res = scope.escapemark
146 if res != null and res.name == name then
147 return res
148 end
149 end
150 return null
151 end
152
153 # Create a new escape mark (possibly with a label)
154 # Display an error on toolcontext if a label with the same name is masked.
155 fun make_escape_mark(nlabel: nullable ALabel, for_loop: Bool): EscapeMark
156 do
157 var name: nullable String
158 if nlabel != null then
159 var nid = nlabel.n_id
160 if nid == null then
161 var res = search_label("")
162 if res != null then
163 self.error(nlabel, "Syntax Error: anonymous label already defined.")
164 end
165 name = ""
166 else
167 name = nid.text
168 var found = self.search_label(name)
169 if found != null then
170 self.error(nlabel, "Syntax Error: label `{name}` already defined.")
171 end
172 end
173 else
174 name = null
175 end
176 var res = new EscapeMark(name)
177 if for_loop then res.continue_mark = new EscapeMark(name)
178 return res
179 end
180
181 # Look for an escape mark optionally associated with a label.
182 # If a label is given, the escapemark of this label is returned.
183 # If there is no label, the nearest escapemark that is `for loop` is returned.
184 # If there is no valid escapemark, then an error is displayed ans null is returned.
185 # Return null if no such a label is found.
186 fun get_escapemark(node: ANode, nlabel: nullable ALabel): nullable EscapeMark
187 do
188 if nlabel != null then
189 var nid = nlabel.n_id
190 if nid == null then
191 var res = search_label("")
192 if res == null then
193 self.error(nlabel, "Syntax Error: invalid anonymous label.")
194 return null
195 end
196 return res
197 end
198 var name = nid.text
199 var res = search_label(name)
200 if res == null then
201 self.error(nlabel, "Syntax Error: invalid label `{name}`.")
202 return null
203 end
204 return res
205 else
206 for scope in scopes do
207 var res = scope.escapemark
208 if res != null then
209 return res
210 end
211 end
212 self.error(node, "Syntax Error: `break` statement outside block.")
213 return null
214 end
215 end
216
217 # Display an error
218 fun error(node: ANode, message: String)
219 do
220 self.toolcontext.error(node.hot_location, message)
221 end
222 end
223
224 private class Scope
225 var variables = new HashMap[String, Variable]
226
227 var escapemark: nullable EscapeMark = null
228
229 fun get_variable(name: String): nullable Variable
230 do
231 if self.variables.has_key(name) then
232 return self.variables[name]
233 else
234 return null
235 end
236 end
237 end
238
239 redef class ANode
240 private fun accept_scope_visitor(v: ScopeVisitor)
241 do
242 visit_all(v)
243 end
244 end
245
246 redef class APropdef
247 # Entry point of the scope analysis
248 fun do_scope(toolcontext: ToolContext)
249 do
250 var v = new ScopeVisitor(toolcontext)
251 v.enter_visit(self)
252 v.shift_scope
253 end
254 end
255
256 redef class AParam
257 # The variable associated with the parameter
258 var variable: nullable Variable
259 redef fun accept_scope_visitor(v)
260 do
261 super
262 var nid = self.n_id
263 var variable = new Variable(nid.text)
264 v.register_variable(nid, variable)
265 self.variable = variable
266 end
267 end
268
269 redef class AVardeclExpr
270 # The variable associated with the variable declaration
271 var variable: nullable Variable
272 redef fun accept_scope_visitor(v)
273 do
274 super
275 var nid = self.n_id
276 var variable = new Variable(nid.text)
277 v.register_variable(nid, variable)
278 variable.warn_unread = true # wait for some read mark.
279 self.variable = variable
280 end
281 end
282
283 redef class ASelfExpr
284 # The variable associated with the self receiver
285 var variable: nullable Variable
286 redef fun accept_scope_visitor(v)
287 do
288 super
289 self.variable = v.selfvariable
290 end
291 end
292
293 redef class AEscapeExpr
294 # The escape mark associated with the break/continue
295 var escapemark: nullable EscapeMark
296 end
297
298 redef class AContinueExpr
299 redef fun accept_scope_visitor(v)
300 do
301 super
302 var escapemark = v.get_escapemark(self, self.n_label)
303 if escapemark == null then return # Skip error
304 escapemark = escapemark.continue_mark
305 if escapemark == null then
306 v.error(self, "Error: cannot 'continue', only 'break'.")
307 return
308 end
309 escapemark.escapes.add(self)
310 self.escapemark = escapemark
311 end
312 end
313
314 redef class ABreakExpr
315 redef fun accept_scope_visitor(v)
316 do
317 super
318 var escapemark = v.get_escapemark(self, self.n_label)
319 if escapemark == null then return # Skip error
320 escapemark.escapes.add(self)
321 self.escapemark = escapemark
322 end
323 end
324
325
326 redef class ADoExpr
327 # The break escape mark associated with the 'do' block
328 var break_mark: nullable EscapeMark
329
330 redef fun accept_scope_visitor(v)
331 do
332 self.break_mark = v.make_escape_mark(n_label, false)
333 v.enter_visit_block(n_block, self.break_mark)
334 end
335 end
336
337 redef class AIfExpr
338 redef fun accept_scope_visitor(v)
339 do
340 v.enter_visit(n_expr)
341 v.enter_visit_block(n_then, null)
342 v.enter_visit_block(n_else, null)
343 end
344 end
345
346 redef class AWhileExpr
347 # The break escape mark associated with the 'while'
348 var break_mark: nullable EscapeMark
349
350 # The continue escape mark associated with the 'while'
351 var continue_mark: nullable EscapeMark
352
353 redef fun accept_scope_visitor(v)
354 do
355 var escapemark = v.make_escape_mark(n_label, true)
356 self.break_mark = escapemark
357 self.continue_mark = escapemark.continue_mark
358 v.enter_visit(n_expr)
359 v.enter_visit_block(n_block, escapemark)
360 end
361 end
362
363 redef class ALoopExpr
364 # The break escape mark associated with the 'loop'
365 var break_mark: nullable EscapeMark
366
367 # The continue escape mark associated with the 'loop'
368 var continue_mark: nullable EscapeMark
369
370 redef fun accept_scope_visitor(v)
371 do
372 var escapemark = v.make_escape_mark(n_label, true)
373 self.break_mark = escapemark
374 self.continue_mark = escapemark.continue_mark
375 v.enter_visit_block(n_block, escapemark)
376 end
377 end
378
379 redef class AForExpr
380 # The break escape mark associated with the 'for'
381 var break_mark: nullable EscapeMark
382
383 # The continue escape mark associated with the 'for'
384 var continue_mark: nullable EscapeMark
385
386 redef fun accept_scope_visitor(v)
387 do
388 for g in n_groups do
389 v.enter_visit(g.n_expr)
390 end
391
392 # Protect automatic variables
393 v.scopes.unshift(new Scope)
394
395 for g in n_groups do
396 # Create the automatic variables
397 var variables = new Array[Variable]
398 g.variables = variables
399 for nid in g.n_ids do
400 var va = new Variable(nid.text)
401 v.register_variable(nid, va)
402 variables.add(va)
403 end
404 end
405
406 var escapemark = v.make_escape_mark(n_label, true)
407 self.break_mark = escapemark
408 self.continue_mark = escapemark.continue_mark
409 v.enter_visit_block(n_block, escapemark)
410
411 v.shift_scope
412 end
413 end
414
415 redef class AForGroup
416 # The automatic variables in order
417 var variables: nullable Array[Variable]
418 end
419
420 redef class AWithExpr
421 # The break escape mark associated with the 'with'
422 var break_mark: nullable EscapeMark
423
424 redef fun accept_scope_visitor(v)
425 do
426 v.scopes.unshift(new Scope)
427
428 var escapemark = v.make_escape_mark(n_label, true)
429 self.break_mark = escapemark
430
431 v.enter_visit(n_expr)
432 v.enter_visit_block(n_block, escapemark)
433
434 v.shift_scope
435 end
436 end
437
438 redef class AAssertExpr
439 redef fun accept_scope_visitor(v)
440 do
441 v.enter_visit(n_expr)
442 v.enter_visit_block(n_else, null)
443 end
444 end
445
446 redef class AVarFormExpr
447 # The associated variable
448 var variable: nullable Variable is writable
449 end
450
451 redef class ACallFormExpr
452 redef fun accept_scope_visitor(v)
453 do
454 if n_expr isa AImplicitSelfExpr then
455 var name = n_qid.n_id.text
456 var variable = v.search_variable(name)
457 if variable != null then
458 var n: AExpr
459 if not n_args.n_exprs.is_empty or n_args isa AParExprs then
460 v.error(self, "Error: `{name}` is a variable, not a method.")
461 return
462 end
463 n = variable_create(variable)
464 n.variable = variable
465 replace_with(n)
466 n.accept_scope_visitor(v)
467 return
468 end
469 end
470
471 super
472 end
473
474 # Create a variable access corresponding to the call form
475 private fun variable_create(variable: Variable): AVarFormExpr is abstract
476 end
477
478 redef class ACallExpr
479 redef fun variable_create(variable)
480 do
481 variable.warn_unread = false
482 return new AVarExpr.init_avarexpr(n_qid.n_id)
483 end
484 end
485
486 redef class ACallAssignExpr
487 redef fun variable_create(variable)
488 do
489 return new AVarAssignExpr.init_avarassignexpr(n_qid.n_id, n_assign, n_value)
490 end
491 end
492
493 redef class ACallReassignExpr
494 redef fun variable_create(variable)
495 do
496 variable.warn_unread = false
497 return new AVarReassignExpr.init_avarreassignexpr(n_qid.n_id, n_assign_op, n_value)
498 end
499 end