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