1 # This file is part of NIT ( http://www.nitlanguage.org ).
3 # Copyright 2012 Jean Privat <jean@pryen.org>
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
17 # Identification and scoping of local variables and labels.
22 redef class ToolContext
23 var scope_phase
: Phase = new ScopePhase(self, null)
26 private class ScopePhase
28 redef fun process_npropdef
(npropdef
) do npropdef
.do_scope
(toolcontext
)
32 # A local variable (including parameters, automatic variables and self)
34 # The name of the variable (as used in the program)
38 redef fun to_s
do return self.name
40 # The declaration of the variable, if any
41 var location
: nullable Location = null
43 # Is the local variable not read and need a warning?
44 var warn_unread
= false is writable
47 # Mark where break and continue will branch.
48 # Marks are either associated with a label of with a for_loop structure
50 # The name of the label (unless the mark is an anonymous loop mark)
51 var name
: nullable String
53 # Is the mark attached to a loop (loop, while, for)
54 # Such a mark is a candidate to a labelless 'continue' or 'break'
57 # Each 'continue' attached to the mark
58 var continues
= new Array[AContinueExpr]
60 # Each 'break' attached to the mark
61 var breaks
= new Array[ABreakExpr]
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
72 # The tool context used to display errors
73 var toolcontext
: ToolContext
75 var selfvariable
= new Variable("self")
77 init(toolcontext
: ToolContext)
79 self.toolcontext
= toolcontext
83 # All stacked scope. `scopes.first` is the current scope
84 private var scopes
= new List[Scope]
86 # Shift and check the last scope
89 assert not scopes
.is_empty
90 var scope
= scopes
.shift
91 for v
in scope
.variables
.values
do
93 toolcontext
.advice
(v
.location
, "unread-variable", "Warning: local variable {v.name} is never read.")
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
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")
108 scopes
.first
.variables
[name
] = variable
109 variable
.location
= node
.location
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
117 for scope
in scopes
do
118 var res
= scope
.get_variable
(name
)
128 n
.accept_scope_visitor
(self)
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)
135 if node
== null then return
136 var scope
= new Scope
137 scope
.escapemark
= escapemark
138 scopes
.unshift
(scope
)
143 # Look for a label `name`.
144 # Return null if no such a label is found.
145 fun search_label
(name
: String): nullable EscapeMark
147 for scope
in scopes
do
148 var res
= scope
.escapemark
149 if res
!= null and res
.name
== name
then
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
160 var name
: nullable String
161 if nlabel
!= null then
162 var nid
= nlabel
.n_id
164 var res
= search_label
("")
166 self.error
(nlabel
, "Syntax error: anonymous label already defined.")
171 var found
= self.search_label
(name
)
172 if found
!= null then
173 self.error
(nlabel
, "Syntax error: label {name} already defined.")
179 var res
= new EscapeMark(name
, for_loop
)
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
190 if nlabel
!= null then
191 var nid
= nlabel
.n_id
193 var res
= search_label
("")
195 self.error
(nlabel
, "Syntax error: invalid anonymous label.")
201 var res
= search_label
(name
)
203 self.error
(nlabel
, "Syntax error: invalid label {name}.")
208 for scope
in scopes
do
209 var res
= scope
.escapemark
214 self.error
(node
, "Syntax Error: 'break' statement outside block.")
220 fun error
(node
: ANode, message
: String)
222 self.toolcontext
.error
(node
.hot_location
, message
)
227 var variables
= new HashMap[String, Variable]
229 var escapemark
: nullable EscapeMark = null
231 fun get_variable
(name
: String): nullable Variable
233 if self.variables
.has_key
(name
) then
234 return self.variables
[name
]
242 private fun accept_scope_visitor
(v
: ScopeVisitor)
249 # Entry point of the scope analysis
250 fun do_scope
(toolcontext
: ToolContext)
252 var v
= new ScopeVisitor(toolcontext
)
259 # The variable associated with the parameter
260 var variable
: nullable Variable
261 redef fun accept_scope_visitor
(v
)
265 var variable
= new Variable(nid
.text
)
266 v
.register_variable
(nid
, variable
)
267 self.variable
= variable
271 redef class AVardeclExpr
272 # The variable associated with the variable declaration
273 var variable
: nullable Variable
274 redef fun accept_scope_visitor
(v
)
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
285 redef class ASelfExpr
286 # The variable associated with the self receiver
287 var variable
: nullable Variable
288 redef fun accept_scope_visitor
(v
)
291 self.variable
= v
.selfvariable
295 redef class AContinueExpr
296 # The escape mark associated with the continue
297 var escapemark
: nullable EscapeMark
298 redef fun accept_scope_visitor
(v
)
301 var escapemark
= v
.get_escapemark
(self, self.n_label
)
302 if escapemark
== null then return # Skip error
303 if not escapemark
.for_loop
then
304 v
.error
(self, "Error: cannot 'continue', only 'break'.")
306 escapemark
.continues
.add
(self)
307 self.escapemark
= escapemark
311 redef class ABreakExpr
312 # The escape mark associated with the break
313 var escapemark
: nullable EscapeMark
314 redef fun accept_scope_visitor
(v
)
317 var escapemark
= v
.get_escapemark
(self, self.n_label
)
318 if escapemark
== null then return # Skip error
319 escapemark
.breaks
.add
(self)
320 self.escapemark
= escapemark
326 # The escape mark associated with the 'do' block
327 var escapemark
: nullable EscapeMark
328 redef fun accept_scope_visitor
(v
)
330 self.escapemark
= v
.make_escape_mark
(n_label
, false)
331 v
.enter_visit_block
(n_block
, self.escapemark
)
336 redef fun accept_scope_visitor
(v
)
338 v
.enter_visit
(n_expr
)
339 v
.enter_visit_block
(n_then
, null)
340 v
.enter_visit_block
(n_else
, null)
344 redef class AWhileExpr
345 # The escape mark associated with the 'while'
346 var escapemark
: nullable EscapeMark
347 redef fun accept_scope_visitor
(v
)
349 var escapemark
= v
.make_escape_mark
(n_label
, true)
350 self.escapemark
= escapemark
351 v
.enter_visit
(n_expr
)
352 v
.enter_visit_block
(n_block
, escapemark
)
356 redef class ALoopExpr
357 # The escape mark associated with the 'loop'
358 var escapemark
: nullable EscapeMark
359 redef fun accept_scope_visitor
(v
)
361 var escapemark
= v
.make_escape_mark
(n_label
, true)
362 self.escapemark
= escapemark
363 v
.enter_visit_block
(n_block
, escapemark
)
368 # The automatic variables in order
369 var variables
: nullable Array[Variable]
371 # The escape mark associated with the 'for'
372 var escapemark
: nullable EscapeMark
374 redef fun accept_scope_visitor
(v
)
376 v
.enter_visit
(n_expr
)
378 # Protect automatic variables
379 v
.scopes
.unshift
(new Scope)
381 # Create the automatic variables
382 var variables
= new Array[Variable]
383 self.variables
= variables
385 var va
= new Variable(nid
.text
)
386 v
.register_variable
(nid
, va
)
390 var escapemark
= v
.make_escape_mark
(n_label
, true)
391 self.escapemark
= escapemark
392 v
.enter_visit_block
(n_block
, escapemark
)
398 redef class AVarFormExpr
399 # The associated variable
400 var variable
: nullable Variable
403 redef class ACallFormExpr
404 redef fun accept_scope_visitor
(v
)
406 if n_expr
isa AImplicitSelfExpr then
408 var variable
= v
.search_variable
(name
)
409 if variable
!= null then
411 if not n_args
.n_exprs
.is_empty
or n_args
isa AParExprs then
412 v
.error
(self, "Error: {name} is variable, not a function.")
415 n
= variable_create
(variable
)
416 n
.variable
= variable
418 n
.accept_scope_visitor
(v
)
426 # Create a variable access corresponding to the call form
427 private fun variable_create
(variable
: Variable): AVarFormExpr is abstract
430 redef class ACallExpr
431 redef fun variable_create
(variable
)
433 variable
.warn_unread
= false
434 return new AVarExpr.init_avarexpr
(n_id
)
438 redef class ACallAssignExpr
439 redef fun variable_create
(variable
)
441 return new AVarAssignExpr.init_avarassignexpr
(n_id
, n_assign
, n_value
)
445 redef class ACallReassignExpr
446 redef fun variable_create
(variable
)
448 variable
.warn_unread
= false
449 return new AVarReassignExpr.init_avarreassignexpr
(n_id
, n_assign_op
, n_value
)