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