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