1
1
# frozen_string_literal: true
2
2
3
3
module SyntaxTree
4
- # WithEnvironment is a module intended to be included in classes inheriting
5
- # from Visitor. The module overrides a few visit methods to automatically keep
6
- # track of local variables and arguments defined in the current environment .
4
+ # WithScope is a module intended to be included in classes inheriting from
5
+ # Visitor. The module overrides a few visit methods to automatically keep
6
+ # track of local variables and arguments defined in the current scope .
7
7
# Example usage:
8
8
#
9
9
# class MyVisitor < Visitor
10
- # include WithEnvironment
10
+ # include WithScope
11
11
#
12
12
# def visit_ident(node)
13
13
# # Check if we're visiting an identifier for an argument, a local
14
14
# # variable or something else
15
- # local = current_environment .find_local(node)
15
+ # local = current_scope .find_local(node)
16
16
#
17
17
# if local.type == :argument
18
18
# # handle identifiers for arguments
@@ -24,11 +24,11 @@ module SyntaxTree
24
24
# end
25
25
# end
26
26
#
27
- module WithEnvironment
28
- # The environment class is used to keep track of local variables and
29
- # arguments inside a particular scope
30
- class Environment
31
- # This class tracks the occurrences of a local variable or argument
27
+ module WithScope
28
+ # The scope class is used to keep track of local variables and arguments
29
+ # inside a particular scope.
30
+ class Scope
31
+ # This class tracks the occurrences of a local variable or argument.
32
32
class Local
33
33
# [Symbol] The type of the local (e.g. :argument, :variable)
34
34
attr_reader :type
@@ -55,20 +55,20 @@ def add_usage(location)
55
55
end
56
56
end
57
57
58
- # [Integer] a unique identifier for this environment
58
+ # [Integer] a unique identifier for this scope
59
59
attr_reader :id
60
60
61
+ # [scope | nil] The parent scope
62
+ attr_reader :parent
63
+
61
64
# [Hash[String, Local]] The local variables and arguments defined in this
62
- # environment
65
+ # scope
63
66
attr_reader :locals
64
67
65
- # [Environment | nil] The parent environment
66
- attr_reader :parent
67
-
68
68
def initialize ( id , parent = nil )
69
69
@id = id
70
- @locals = { }
71
70
@parent = parent
71
+ @locals = { }
72
72
end
73
73
74
74
# Adding a local definition will either insert a new entry in the locals
@@ -97,7 +97,7 @@ def add_local_usage(identifier, type)
97
97
resolve_local ( name , type ) . add_usage ( identifier . location )
98
98
end
99
99
100
- # Try to find the local given its name in this environment or any of its
100
+ # Try to find the local given its name in this scope or any of its
101
101
# parents.
102
102
def find_local ( name )
103
103
locals [ name ] || parent &.find_local ( name )
@@ -117,44 +117,35 @@ def resolve_local(name, type)
117
117
end
118
118
end
119
119
120
+ attr_reader :current_scope
121
+
120
122
def initialize ( *args , **kwargs , &block )
121
123
super
122
- @environment_id = 0
123
- end
124
-
125
- def current_environment
126
- @current_environment ||= Environment . new ( next_environment_id )
127
- end
128
124
129
- def with_new_environment ( parent_environment = nil )
130
- previous_environment = @current_environment
131
- @current_environment =
132
- Environment . new ( next_environment_id , parent_environment )
133
- yield
134
- ensure
135
- @current_environment = previous_environment
125
+ @current_scope = Scope . new ( 0 )
126
+ @next_scope_id = 0
136
127
end
137
128
138
- # Visits for nodes that create new environments , such as classes, modules
129
+ # Visits for nodes that create new scopes , such as classes, modules
139
130
# and method definitions.
140
131
def visit_class ( node )
141
- with_new_environment { super }
132
+ with_scope { super }
142
133
end
143
134
144
135
def visit_module ( node )
145
- with_new_environment { super }
136
+ with_scope { super }
146
137
end
147
138
148
- # When we find a method invocation with a block, only the code that
149
- # happens inside of the block needs a fresh environment . The method
150
- # invocation itself happens in the same environment .
139
+ # When we find a method invocation with a block, only the code that happens
140
+ # inside of the block needs a fresh scope . The method invocation
141
+ # itself happens in the same scope .
151
142
def visit_method_add_block ( node )
152
143
visit ( node . call )
153
- with_new_environment ( current_environment ) { visit ( node . block ) }
144
+ with_scope ( current_scope ) { visit ( node . block ) }
154
145
end
155
146
156
147
def visit_def ( node )
157
- with_new_environment { super }
148
+ with_scope { super }
158
149
end
159
150
160
151
# Visit for keeping track of local arguments, such as method and block
@@ -163,48 +154,45 @@ def visit_params(node)
163
154
add_argument_definitions ( node . requireds )
164
155
165
156
node . posts . each do |param |
166
- current_environment . add_local_definition ( param , :argument )
157
+ current_scope . add_local_definition ( param , :argument )
167
158
end
168
159
169
160
node . keywords . each do |param |
170
- current_environment . add_local_definition ( param . first , :argument )
161
+ current_scope . add_local_definition ( param . first , :argument )
171
162
end
172
163
173
164
node . optionals . each do |param |
174
- current_environment . add_local_definition ( param . first , :argument )
165
+ current_scope . add_local_definition ( param . first , :argument )
175
166
end
176
167
177
168
super
178
169
end
179
170
180
171
def visit_rest_param ( node )
181
172
name = node . name
182
- current_environment . add_local_definition ( name , :argument ) if name
173
+ current_scope . add_local_definition ( name , :argument ) if name
183
174
184
175
super
185
176
end
186
177
187
178
def visit_kwrest_param ( node )
188
179
name = node . name
189
- current_environment . add_local_definition ( name , :argument ) if name
180
+ current_scope . add_local_definition ( name , :argument ) if name
190
181
191
182
super
192
183
end
193
184
194
185
def visit_blockarg ( node )
195
186
name = node . name
196
- current_environment . add_local_definition ( name , :argument ) if name
187
+ current_scope . add_local_definition ( name , :argument ) if name
197
188
198
189
super
199
190
end
200
191
201
192
# Visit for keeping track of local variable definitions
202
193
def visit_var_field ( node )
203
194
value = node . value
204
-
205
- if value . is_a? ( SyntaxTree ::Ident )
206
- current_environment . add_local_definition ( value , :variable )
207
- end
195
+ current_scope . add_local_definition ( value , :variable ) if value . is_a? ( Ident )
208
196
209
197
super
210
198
end
@@ -215,12 +203,9 @@ def visit_var_field(node)
215
203
def visit_var_ref ( node )
216
204
value = node . value
217
205
218
- if value . is_a? ( SyntaxTree ::Ident )
219
- definition = current_environment . find_local ( value . value )
220
-
221
- if definition
222
- current_environment . add_local_usage ( value , definition . type )
223
- end
206
+ if value . is_a? ( Ident )
207
+ definition = current_scope . find_local ( value . value )
208
+ current_scope . add_local_usage ( value , definition . type ) if definition
224
209
end
225
210
226
211
super
@@ -233,13 +218,21 @@ def add_argument_definitions(list)
233
218
if param . is_a? ( SyntaxTree ::MLHSParen )
234
219
add_argument_definitions ( param . contents . parts )
235
220
else
236
- current_environment . add_local_definition ( param , :argument )
221
+ current_scope . add_local_definition ( param , :argument )
237
222
end
238
223
end
239
224
end
240
225
241
- def next_environment_id
242
- @environment_id += 1
226
+ def next_scope_id
227
+ @next_scope_id += 1
228
+ end
229
+
230
+ def with_scope ( parent_scope = nil )
231
+ previous_scope = @current_scope
232
+ @current_scope = Scope . new ( next_scope_id , parent_scope )
233
+ yield
234
+ ensure
235
+ @current_scope = previous_scope
243
236
end
244
237
end
245
238
end
0 commit comments