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