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