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