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 # Run `APropdef::do_scope` on each propdef.
24 var scope_phase
: Phase = new ScopePhase(self, null)
27 private class ScopePhase
29 redef fun process_npropdef
(npropdef
) do npropdef
.do_scope
(toolcontext
)
33 # A local variable (including parameters, automatic variables and self)
35 # The name of the variable (as used in the program)
39 redef fun to_s
do return self.name
41 # The declaration of the variable, if any
42 var location
: nullable Location = null
44 # Is the local variable not read and need a warning?
45 var warn_unread
= false is writable
48 # Mark where break and continue will branch.
49 # Marks are either associated with a label of with a for_loop structure
51 # The name of the label (unless the mark is an anonymous loop mark)
52 var name
: nullable String
54 # The associated `continue` mark, if any.
55 # If the mark attached to a loop (loop, while, for), a distinct mark is used.
56 private var continue_mark
: nullable EscapeMark = null
58 # Each break/continue attached to the mark
59 var escapes
= new Array[AEscapeExpr]
62 # Visit a npropdef and:
63 # * Identify variables and labels
64 # * Associate each break and continue to its escapemark
65 # * Transform `ACallFormExpr` that access a variable into `AVarFormExpr`
66 # FIXME: Should the class be private?
67 private class ScopeVisitor
70 # The tool context used to display errors
71 var toolcontext
: ToolContext
73 var selfvariable
= new Variable("self")
80 # All stacked scope. `scopes.first` is the current scope
81 var scopes
= new List[Scope]
83 # Shift and check the last scope
86 assert not scopes
.is_empty
87 var scope
= scopes
.shift
88 for v
in scope
.variables
.values
do
90 toolcontext
.advice
(v
.location
, "unread-variable", "Warning: local variable {v.name} is never read.")
95 # Register a local variable.
96 # Display an error on toolcontext if a variable with the same name is masked.
97 fun register_variable
(node
: ANode, variable
: Variable): Bool
99 var name
= variable
.name
100 var found
= search_variable
(name
)
101 if found
!= null then
102 self.error
(node
, "Error: A variable named `{name}' already exists")
105 scopes
.first
.variables
[name
] = variable
106 variable
.location
= node
.location
110 # Look for a variable named `name`.
111 # Return null if no such a variable is found.
112 fun search_variable
(name
: String): nullable Variable
114 for scope
in scopes
do
115 var res
= scope
.get_variable
(name
)
125 n
.accept_scope_visitor
(self)
128 # Enter in a statement block `node` as inside a new scope.
129 # The block can be optionally attached to an `escapemark`.
130 fun enter_visit_block
(node
: nullable AExpr, escapemark
: nullable EscapeMark)
132 if node
== null then return
133 var scope
= new Scope
134 scope
.escapemark
= escapemark
135 scopes
.unshift
(scope
)
140 # Look for a label `name`.
141 # Return null if no such a label is found.
142 fun search_label
(name
: String): nullable EscapeMark
144 for scope
in scopes
do
145 var res
= scope
.escapemark
146 if res
!= null and res
.name
== name
then
153 # Create a new escape mark (possibly with a label)
154 # Display an error on toolcontext if a label with the same name is masked.
155 fun make_escape_mark
(nlabel
: nullable ALabel, for_loop
: Bool): EscapeMark
157 var name
: nullable String
158 if nlabel
!= null then
159 var nid
= nlabel
.n_id
161 var res
= search_label
("")
163 self.error
(nlabel
, "Syntax error: anonymous label already defined.")
168 var found
= self.search_label
(name
)
169 if found
!= null then
170 self.error
(nlabel
, "Syntax error: label {name} already defined.")
176 var res
= new EscapeMark(name
)
177 if for_loop
then res
.continue_mark
= new EscapeMark(name
)
181 # Look for an escape mark optionally associated with a label.
182 # If a label is given, the escapemark of this label is returned.
183 # If there is no label, the nearest escapemark that is `for loop` is returned.
184 # If there is no valid escapemark, then an error is displayed ans null is returned.
185 # Return null if no such a label is found.
186 fun get_escapemark
(node
: ANode, nlabel
: nullable ALabel): nullable EscapeMark
188 if nlabel
!= null then
189 var nid
= nlabel
.n_id
191 var res
= search_label
("")
193 self.error
(nlabel
, "Syntax error: invalid anonymous label.")
199 var res
= search_label
(name
)
201 self.error
(nlabel
, "Syntax error: invalid label {name}.")
206 for scope
in scopes
do
207 var res
= scope
.escapemark
212 self.error
(node
, "Syntax Error: 'break' statement outside block.")
218 fun error
(node
: ANode, message
: String)
220 self.toolcontext
.error
(node
.hot_location
, message
)
225 var variables
= new HashMap[String, Variable]
227 var escapemark
: nullable EscapeMark = null
229 fun get_variable
(name
: String): nullable Variable
231 if self.variables
.has_key
(name
) then
232 return self.variables
[name
]
240 private fun accept_scope_visitor
(v
: ScopeVisitor)
247 # Entry point of the scope analysis
248 fun do_scope
(toolcontext
: ToolContext)
250 var v
= new ScopeVisitor(toolcontext
)
257 # The variable associated with the parameter
258 var variable
: nullable Variable
259 redef fun accept_scope_visitor
(v
)
263 var variable
= new Variable(nid
.text
)
264 v
.register_variable
(nid
, variable
)
265 self.variable
= variable
269 redef class AVardeclExpr
270 # The variable associated with the variable declaration
271 var variable
: nullable Variable
272 redef fun accept_scope_visitor
(v
)
276 var variable
= new Variable(nid
.text
)
277 v
.register_variable
(nid
, variable
)
278 variable
.warn_unread
= true # wait for some read mark.
279 self.variable
= variable
283 redef class ASelfExpr
284 # The variable associated with the self receiver
285 var variable
: nullable Variable
286 redef fun accept_scope_visitor
(v
)
289 self.variable
= v
.selfvariable
293 redef class AEscapeExpr
294 # The escape mark associated with the break/continue
295 var escapemark
: nullable EscapeMark
298 redef class AContinueExpr
299 redef fun accept_scope_visitor
(v
)
302 var escapemark
= v
.get_escapemark
(self, self.n_label
)
303 if escapemark
== null then return # Skip error
304 escapemark
= escapemark
.continue_mark
305 if escapemark
== null then
306 v
.error
(self, "Error: cannot 'continue', only 'break'.")
309 escapemark
.escapes
.add
(self)
310 self.escapemark
= escapemark
314 redef class ABreakExpr
315 redef fun accept_scope_visitor
(v
)
318 var escapemark
= v
.get_escapemark
(self, self.n_label
)
319 if escapemark
== null then return # Skip error
320 escapemark
.escapes
.add
(self)
321 self.escapemark
= escapemark
327 # The break escape mark associated with the 'do' block
328 var break_mark
: nullable EscapeMark
330 redef fun accept_scope_visitor
(v
)
332 self.break_mark
= v
.make_escape_mark
(n_label
, false)
333 v
.enter_visit_block
(n_block
, self.break_mark
)
338 redef fun accept_scope_visitor
(v
)
340 v
.enter_visit
(n_expr
)
341 v
.enter_visit_block
(n_then
, null)
342 v
.enter_visit_block
(n_else
, null)
346 redef class AWhileExpr
347 # The break escape mark associated with the 'while'
348 var break_mark
: nullable EscapeMark
350 # The continue escape mark associated with the 'while'
351 var continue_mark
: nullable EscapeMark
353 redef fun accept_scope_visitor
(v
)
355 var escapemark
= v
.make_escape_mark
(n_label
, true)
356 self.break_mark
= escapemark
357 self.continue_mark
= escapemark
.continue_mark
358 v
.enter_visit
(n_expr
)
359 v
.enter_visit_block
(n_block
, escapemark
)
363 redef class ALoopExpr
364 # The break escape mark associated with the 'loop'
365 var break_mark
: nullable EscapeMark
367 # The continue escape mark associated with the 'loop'
368 var continue_mark
: nullable EscapeMark
370 redef fun accept_scope_visitor
(v
)
372 var escapemark
= v
.make_escape_mark
(n_label
, true)
373 self.break_mark
= escapemark
374 self.continue_mark
= escapemark
.continue_mark
375 v
.enter_visit_block
(n_block
, escapemark
)
380 # The automatic variables in order
381 var variables
: nullable Array[Variable]
383 # The break escape mark associated with the 'for'
384 var break_mark
: nullable EscapeMark
386 # The continue escape mark associated with the 'for'
387 var continue_mark
: nullable EscapeMark
389 redef fun accept_scope_visitor
(v
)
391 v
.enter_visit
(n_expr
)
393 # Protect automatic variables
394 v
.scopes
.unshift
(new Scope)
396 # Create the automatic variables
397 var variables
= new Array[Variable]
398 self.variables
= variables
400 var va
= new Variable(nid
.text
)
401 v
.register_variable
(nid
, va
)
405 var escapemark
= v
.make_escape_mark
(n_label
, true)
406 self.break_mark
= escapemark
407 self.continue_mark
= escapemark
.continue_mark
408 v
.enter_visit_block
(n_block
, escapemark
)
414 redef class AVarFormExpr
415 # The associated variable
416 var variable
: nullable Variable
419 redef class ACallFormExpr
420 redef fun accept_scope_visitor
(v
)
422 if n_expr
isa AImplicitSelfExpr then
424 var variable
= v
.search_variable
(name
)
425 if variable
!= null then
427 if not n_args
.n_exprs
.is_empty
or n_args
isa AParExprs then
428 v
.error
(self, "Error: {name} is variable, not a function.")
431 n
= variable_create
(variable
)
432 n
.variable
= variable
434 n
.accept_scope_visitor
(v
)
442 # Create a variable access corresponding to the call form
443 private fun variable_create
(variable
: Variable): AVarFormExpr is abstract
446 redef class ACallExpr
447 redef fun variable_create
(variable
)
449 variable
.warn_unread
= false
450 return new AVarExpr.init_avarexpr
(n_id
)
454 redef class ACallAssignExpr
455 redef fun variable_create
(variable
)
457 return new AVarAssignExpr.init_avarassignexpr
(n_id
, n_assign
, n_value
)
461 redef class ACallReassignExpr
462 redef fun variable_create
(variable
)
464 variable
.warn_unread
= false
465 return new AVarReassignExpr.init_avarreassignexpr
(n_id
, n_assign_op
, n_value
)