85140d075f9f40d9e9262bed284dc89d957d8b23
[nit.git] / src / syntax / scope.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2008 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 # Manage nested escapable blocks (while, for and closure) and escape statements (break and continue)
18 package scope
19
20 import syntax_base
21 import flow
22
23 # All-in-one context for scoped things.
24 # It features:
25 # * variable and labels visibility
26 # * control for 'break' and 'continue'
27 # This class manage itself the entree and the exit of scopes:
28 # * use push or push_escapable to enter in a new scope block
29 # * use pop to remove the last scope block (do not foget do pop!)
30 # Warning: ScopeContext are created empty: you must perform a first push to register variables
31 class ScopeContext
32 # Stack of scope blocks
33 var _stack: Array[ScopeBlock] = new Array[ScopeBlock]
34
35 # Known variables
36 # (all variables, even out of scope ones)
37 # Used for debuging
38 var _variables: Array[Variable] = new Array[Variable]
39
40 # Return the variable associated with a name
41 fun [](n: Symbol): nullable Variable
42 do
43 var i = _stack.length - 1
44 while i >= 0 do
45 var b = _stack[i]
46 var va = b.get_variable(n)
47 if va != null then return va
48 i -= 1
49 end
50 return null
51 end
52
53 # Register a variable with its name in the current scope
54 # Display an error if name conflict
55 fun add_variable(v: Variable)
56 do
57 var old_var = self[v.name]
58 if old_var != null then
59 _visitor.error(v.decl, "Error: '{v}' already defined at {old_var.decl.location.relative_to(v.decl.location)}.")
60 end
61 _stack.last.add_variable(v)
62 _variables.add(v)
63 end
64
65 # Push a simple unlabeled variable scope block
66 fun push(node: ANode)
67 do
68 var block = new ScopeBlock(node)
69 _stack.push(block)
70 end
71
72 # Push a specific escapable block
73 # Display error message if there is a problem with the label
74 fun push_escapable(block: EscapableBlock, n_label: nullable ALabel)
75 do
76 _stack.push(block)
77 if n_label != null then
78 var lab = n_label.n_id.to_symbol
79 var i = _stack.length - 1
80 while i >= 0 do
81 var b = _stack[i]
82 if b isa EscapableBlock and b.lab == lab then
83 visitor.error(n_label, "Syntax error: label {lab} already defined at {b.lab_location.relative_to(n_label.location)}.")
84 return
85 end
86 i -= 1
87 end
88 block._lab = lab
89 block._lab_location = n_label.location
90 end
91 end
92
93 # Return the last stacked block that accepts unlabelled break/continue
94 fun head: nullable EscapableBlock
95 do
96 var i = _stack.length - 1
97 while i >= 0 do
98 var h = _stack[i]
99 if h isa EscapableBlock and not (h isa BreakOnlyEscapableBlock) then return h
100 i -= 1
101 end
102 return null
103 end
104
105 # Return the block associed to a label
106 # Output an error end return null if the label is not known
107 fun get_by_label(nl: ALabel): nullable EscapableBlock
108 do
109 var i = _stack.length - 1
110 var lab = nl.n_id.to_symbol
111 while i >= 0 do
112 var b = _stack[i]
113 if b isa EscapableBlock and b.lab == lab then return b
114 i -= 1
115 end
116 visitor.error(nl, "Syntax error: invalid label {lab}.")
117 return null
118 end
119
120 # Remove the last block (the last stacked)
121 fun pop
122 do
123 var n = _stack.pop
124 end
125
126 readable var _visitor: AbsSyntaxVisitor
127 init (v: AbsSyntaxVisitor)
128 do
129 _visitor = v
130 end
131 end
132
133 ###############################################################################
134
135 # A single scope block. Thez are stacked in a ScopeContext
136 # This block is used only to store local variables
137 class ScopeBlock
138 # The syntax node of the block
139 readable var _node: ANode
140
141 # List of local variables of the block
142 # Lazily constructed since many blocks does not have local variables
143 var _dico: nullable HashMap[Symbol, Variable] = null
144
145 private fun add_variable(v: Variable)
146 do
147 var dico = _dico
148 if dico == null then
149 dico = new HashMap[Symbol, Variable]
150 _dico = dico
151 end
152 dico[v.name] = v
153 end
154
155 private fun get_variable(n: Symbol): nullable Variable
156 do
157 var dico = _dico
158 if dico == null then return null
159 if not dico.has_key(n) then return null
160 return dico[n]
161 end
162
163 init(node: ANode)
164 do
165 _node = node
166 end
167 end
168
169 # A escapable block correspond to a scope block where break and/or continue can by used.
170 # EscapableBlocks can also be labeled.
171 # 'for' and 'while' use this class
172 # labeled 'do' uses the BreakOnlyEscapableBlock subclass
173 # closures uses the EscapableClosure subclass
174 class EscapableBlock
175 super ScopeBlock
176 # The label of the block (if any)
177 # Set by the push in EscapableContext
178 readable var _lab: nullable Symbol
179
180 # The location of the label (if any)
181 readable var _lab_location: nullable Location
182
183 # Is self a break closure ?
184 fun is_break_block: Bool do return false
185
186 # Collected expressions used in breaks.
187 # null if break does not accept values.
188 # break_list is used to store expressions used in break statments and perform type checks latter
189 fun break_list: nullable Array[AExpr] do return null
190
191 # The static type required by the continue statement (if any)
192 fun continue_stype: nullable MMType do return null
193
194 # Alternatives flow contexts for breaks
195 readable var _break_flow_contexts: Array[FlowContext] = new Array[FlowContext]
196
197 init(node: ANode)
198 do
199 super(node)
200 end
201 end
202
203 # specific EscapableBlock where only labelled break can be used
204 class BreakOnlyEscapableBlock
205 super EscapableBlock
206 redef fun is_break_block: Bool do return true
207
208 init(node: ANode) do super
209 end
210
211 # specific EscapableBlock for closures
212 class EscapableClosure
213 super EscapableBlock
214 # The associated closure
215 readable var _closure: MMClosure
216
217 redef fun is_break_block do return _closure.is_break
218
219 redef readable var _break_list: nullable Array[AExpr]
220
221 redef fun continue_stype do return _closure.signature.return_type
222
223 init(node: ANode, closure: MMClosure, break_list: nullable Array[AExpr])
224 do
225 super(node)
226 _closure = closure
227 _break_list = break_list
228 end
229 end
230
231 ###############################################################################
232
233 abstract class AEscapeExpr
234 super ALabelable
235 # The associated escapable block
236 readable var _escapable: nullable EscapableBlock
237
238 # The name of the keyword
239 fun kwname: String is abstract
240
241 # Compute, set and return the associated escapable block
242 fun compute_escapable_block(lctx: ScopeContext): nullable EscapableBlock
243 do
244 var block: nullable EscapableBlock
245 var nl = n_label
246 if nl != null then
247 block = lctx.get_by_label(nl)
248 else
249 block = lctx.head
250 if block == null then
251 lctx.visitor.error(self, "Syntax Error: '{kwname}' statment outside block.")
252 end
253 end
254 _escapable = block
255 return block
256 end
257 end
258
259 redef class AContinueExpr
260 super AEscapeExpr
261 redef fun kwname do return "continue"
262 end
263
264 redef class ABreakExpr
265 super AEscapeExpr
266 redef fun kwname do return "break"
267 end
268