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