Class: Opal::Nodes::ScopeNode

Inherits:
Base
  • Object
show all
Defined in:
opal/lib/opal/nodes/scope.rb

Instance Attribute Summary collapse

Attributes inherited from Base

#compiler, #sexp, #type

Instance Method Summary collapse

Methods inherited from Base

#add_gvar, #add_ivar, #add_local, #add_temp, #children, children, #class_variable_owner, #class_variable_owner_nesting_level, #comments, #compile, #compile_to_fragments, #error, #expr, #expr?, #expr_or_nil, #fragment, handle, handlers, #helper, #process, #push, #recv, #recv?, #s, #scope, #source_location, #stmt, #stmt?, #top_scope, truthy_optimize?, #unshift, #while_loop, #with_temp, #wrap

Methods included from Helpers

#current_indent, #empty_line, #indent, #js_truthy, #js_truthy_optimize, #line, #mid_to_jsid, #property, #valid_name?

Constructor Details

#initializeScopeNode

Returns a new instance of ScopeNode.



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'opal/lib/opal/nodes/scope.rb', line 34

def initialize(*)
  super

  @locals   = []
  @temps    = []
  @args     = []
  @ivars    = []
  @gvars    = []
  @parent   = nil
  @queue    = []
  @unique   = 'a'
  @while_stack = []
  @identity = nil
  @defs     = nil

  @methods = []

  @uses_block = false
  @in_ensure = false

  # used by classes to store all ivars used in direct def methods
  @proto_ivars = []
end

Instance Attribute Details

#await_encounteredObject

Returns the value of attribute await_encountered.



413
414
415
# File 'opal/lib/opal/nodes/scope.rb', line 413

def await_encountered
  @await_encountered
end

#block_nameObject

The given block name for a def scope



15
16
17
# File 'opal/lib/opal/nodes/scope.rb', line 15

def block_name
  @block_name
end

#catch_returnObject

Returns the value of attribute catch_return.



30
31
32
# File 'opal/lib/opal/nodes/scope.rb', line 30

def catch_return
  @catch_return
end

#defsObject

true if singleton def, false otherwise



25
26
27
# File 'opal/lib/opal/nodes/scope.rb', line 25

def defs
  @defs
end

#gvarsObject (readonly)

Returns the value of attribute gvars.



20
21
22
# File 'opal/lib/opal/nodes/scope.rb', line 20

def gvars
  @gvars
end

#has_breakObject

Returns the value of attribute has_break.



30
31
32
# File 'opal/lib/opal/nodes/scope.rb', line 30

def has_break
  @has_break
end

#has_retryObject

Returns the value of attribute has_retry.



30
31
32
# File 'opal/lib/opal/nodes/scope.rb', line 30

def has_retry
  @has_retry
end

#identityObject (readonly)

Returns the value of attribute identity.



271
272
273
# File 'opal/lib/opal/nodes/scope.rb', line 271

def identity
  @identity
end

#ivarsObject (readonly)

Returns the value of attribute ivars.



19
20
21
# File 'opal/lib/opal/nodes/scope.rb', line 19

def ivars
  @ivars
end

#localsObject (readonly)

Returns the value of attribute locals.



18
19
20
# File 'opal/lib/opal/nodes/scope.rb', line 18

def locals
  @locals
end

#methodsObject (readonly)

used by modules to know what methods to donate to includees



28
29
30
# File 'opal/lib/opal/nodes/scope.rb', line 28

def methods
  @methods
end

#midObject

Returns the value of attribute mid.



22
23
24
# File 'opal/lib/opal/nodes/scope.rb', line 22

def mid
  @mid
end

#nameObject

The class or module name if this scope is a class scope



12
13
14
# File 'opal/lib/opal/nodes/scope.rb', line 12

def name
  @name
end

#parentObject

Every scope can have a parent scope



9
10
11
# File 'opal/lib/opal/nodes/scope.rb', line 9

def parent
  @parent
end

#rescue_else_sexpObject

Returns the value of attribute rescue_else_sexp.



32
33
34
# File 'opal/lib/opal/nodes/scope.rb', line 32

def rescue_else_sexp
  @rescue_else_sexp
end

#scope_nameObject (readonly)

Returns the value of attribute scope_name.



17
18
19
# File 'opal/lib/opal/nodes/scope.rb', line 17

def scope_name
  @scope_name
end

Instance Method Details

#accepts_using?Boolean

Returns:

  • (Boolean)


359
360
361
362
363
# File 'opal/lib/opal/nodes/scope.rb', line 359

def accepts_using?
  # IterNode of a special kind of Module.new {} is accepted...
  # though we don't check for it that thoroughly.
  [TopNode, ModuleNode, ClassNode, IterNode].include? self.class
end

#add_arg(arg) ⇒ Object



172
173
174
175
# File 'opal/lib/opal/nodes/scope.rb', line 172

def add_arg(arg)
  @args << arg unless @args.include? arg
  arg
end

#add_proto_ivar(ivar) ⇒ Object



168
169
170
# File 'opal/lib/opal/nodes/scope.rb', line 168

def add_proto_ivar(ivar)
  @proto_ivars << ivar unless @proto_ivars.include? ivar
end

#add_scope_gvar(gvar) ⇒ Object



164
165
166
# File 'opal/lib/opal/nodes/scope.rb', line 164

def add_scope_gvar(gvar)
  @gvars << gvar unless @gvars.include? gvar
end

#add_scope_ivar(ivar) ⇒ Object



156
157
158
159
160
161
162
# File 'opal/lib/opal/nodes/scope.rb', line 156

def add_scope_ivar(ivar)
  if def_in_class?
    @parent.add_proto_ivar ivar
  else
    @ivars << ivar unless @ivars.include? ivar
  end
end

#add_scope_local(local) ⇒ Object



177
178
179
180
181
# File 'opal/lib/opal/nodes/scope.rb', line 177

def add_scope_local(local)
  return if has_local? local

  @locals << local
end

#add_scope_temp(tmp) ⇒ Object



194
195
196
197
198
# File 'opal/lib/opal/nodes/scope.rb', line 194

def add_scope_temp(tmp)
  return if has_temp?(tmp)

  @temps.push(tmp)
end

#class?Boolean

Returns true if this is strictly a class scope

Returns:

  • (Boolean)


73
74
75
# File 'opal/lib/opal/nodes/scope.rb', line 73

def class?
  @type == :class
end

#class_scope?Boolean

Returns true if this scope is a class/module body scope

Returns:

  • (Boolean)


68
69
70
# File 'opal/lib/opal/nodes/scope.rb', line 68

def class_scope?
  @type == :class || @type == :module
end

#collect_refinements_temps(temps = []) ⇒ Object



365
366
367
368
369
# File 'opal/lib/opal/nodes/scope.rb', line 365

def collect_refinements_temps(temps = [])
  temps << @refinements_temp if @refinements_temp
  return parent.collect_refinements_temps(temps) if parent
  temps
end

#current_rescueObject



322
323
324
# File 'opal/lib/opal/nodes/scope.rb', line 322

def current_rescue
  @rescues.last
end

#def?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'opal/lib/opal/nodes/scope.rb', line 96

def def?
  @type == :def || @type == :defs
end

#def_in_class?Boolean

Is this a normal def method directly inside a class? This is used for optimizing ivars as we can set them to nil in the class body

Returns:

  • (Boolean)


121
122
123
# File 'opal/lib/opal/nodes/scope.rb', line 121

def def_in_class?
  !@defs && @type == :def && @parent && @parent.class?
end

#defines_lambdaObject



108
109
110
111
112
# File 'opal/lib/opal/nodes/scope.rb', line 108

def defines_lambda
  @lambda_definition = true
  yield
  @lambda_definition = false
end

#find_parent_defObject



273
274
275
276
277
278
279
280
281
282
# File 'opal/lib/opal/nodes/scope.rb', line 273

def find_parent_def
  scope = self
  while scope = scope.parent
    if scope.def? || scope.lambda?
      return scope
    end
  end

  nil
end

#gen_retry_idObject



354
355
356
357
# File 'opal/lib/opal/nodes/scope.rb', line 354

def gen_retry_id
  @next_retry_id ||= 'retry_0'
  @next_retry_id = @next_retry_id.succ
end

#has_local?(local) ⇒ Boolean

Returns:

  • (Boolean)


183
184
185
186
187
# File 'opal/lib/opal/nodes/scope.rb', line 183

def has_local?(local)
  return true if @locals.include?(local) || @args.include?(local) || @temps.include?(local)
  return @parent.has_local?(local) if @parent && @type == :iter
  false
end

#has_rescue_else?Boolean

Returns:

  • (Boolean)


308
309
310
# File 'opal/lib/opal/nodes/scope.rb', line 308

def has_rescue_else?
  !rescue_else_sexp.nil?
end

#has_temp?(tmp) ⇒ Boolean

Returns:

  • (Boolean)


200
201
202
# File 'opal/lib/opal/nodes/scope.rb', line 200

def has_temp?(tmp)
  @temps.include? tmp
end

#identify!(name = nil) ⇒ Object



249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# File 'opal/lib/opal/nodes/scope.rb', line 249

def identify!(name = nil)
  return @identity if @identity

  if valid_name? mid
    # There are some special utf8 chars that can be used as valid JS
    # identifiers, some examples:
    #
    # utf8_pond = 'ⵌ'
    # utf8_question = 'ʔ̣'
    # utf8_exclamation 'ǃ'
    #
    # For now we're just using $$, to maintain compatibility with older IEs.
    @identity = "$$#{mid}"
  else
    # Parent scope is the defining module/class
    name ||= [(parent && (parent.name || parent.scope_name)), mid].compact.join('_')
    @identity = @compiler.unique_temp(name)
  end

  @identity
end

#in_ensureObject



340
341
342
343
344
345
346
347
348
# File 'opal/lib/opal/nodes/scope.rb', line 340

def in_ensure
  return unless block_given?

  @in_ensure = true
  result = yield
  @in_ensure = false

  result
end

#in_ensure?Boolean

Returns:

  • (Boolean)


350
351
352
# File 'opal/lib/opal/nodes/scope.rb', line 350

def in_ensure?
  @in_ensure
end

#in_resbodyObject



326
327
328
329
330
331
332
333
334
# File 'opal/lib/opal/nodes/scope.rb', line 326

def in_resbody
  return unless block_given?

  @in_resbody = true
  result = yield
  @in_resbody = false

  result
end

#in_resbody?Boolean

Returns:

  • (Boolean)


336
337
338
# File 'opal/lib/opal/nodes/scope.rb', line 336

def in_resbody?
  @in_resbody
end

#in_rescue(node) ⇒ Object



312
313
314
315
316
317
318
319
320
# File 'opal/lib/opal/nodes/scope.rb', line 312

def in_rescue(node)
  @rescues ||= []

  @rescues.push(node)
  result = yield
  @rescues.pop

  result
end

#in_scopeObject



58
59
60
61
62
63
64
65
# File 'opal/lib/opal/nodes/scope.rb', line 58

def in_scope
  indent do
    @parent = compiler.scope
    compiler.scope = self
    yield self
    compiler.scope = @parent
  end
end

#in_while?Boolean

Returns:

  • (Boolean)


236
237
238
# File 'opal/lib/opal/nodes/scope.rb', line 236

def in_while?
  !@while_stack.empty?
end

#is_lambda!Object

rubocop:disable Naming/PredicateName



104
105
106
# File 'opal/lib/opal/nodes/scope.rb', line 104

def is_lambda! # rubocop:disable Naming/PredicateName
  @is_lambda = true
end

#iter?Boolean

True if a block/iter scope

Returns:

  • (Boolean)


92
93
94
# File 'opal/lib/opal/nodes/scope.rb', line 92

def iter?
  @type == :iter
end

#lambda?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'opal/lib/opal/nodes/scope.rb', line 100

def lambda?
  iter? && @is_lambda
end

#lambda_definition?Boolean

Returns:

  • (Boolean)


114
115
116
# File 'opal/lib/opal/nodes/scope.rb', line 114

def lambda_definition?
  @lambda_definition
end

#module?Boolean

True if this is a module scope

Returns:

  • (Boolean)


78
79
80
# File 'opal/lib/opal/nodes/scope.rb', line 78

def module?
  @type == :module
end

#nestingObject

Returns '$nesting', but also ensures we compile the nesting chain



390
391
392
393
# File 'opal/lib/opal/nodes/scope.rb', line 390

def nesting
  @define_nesting = true
  '$nesting'
end

#new_refinements_tempObject



371
372
373
374
375
# File 'opal/lib/opal/nodes/scope.rb', line 371

def new_refinements_temp
  var = compiler.unique_temp("$refn")
  add_scope_local(var)
  var
end

#new_tempObject



204
205
206
207
208
209
210
# File 'opal/lib/opal/nodes/scope.rb', line 204

def new_temp
  return @queue.pop unless @queue.empty?

  tmp = next_temp
  @temps << tmp
  tmp
end

#next_tempObject



212
213
214
215
216
217
218
219
220
# File 'opal/lib/opal/nodes/scope.rb', line 212

def next_temp
  tmp = nil
  loop do
    tmp = "$#{@unique}"
    @unique = @unique.succ
    break unless has_local?(tmp)
  end
  tmp
end

#pop_whileObject



232
233
234
# File 'opal/lib/opal/nodes/scope.rb', line 232

def pop_while
  @while_stack.pop
end

#prepare_block(block_name = nil) ⇒ Object



401
402
403
404
405
406
407
408
409
410
411
# File 'opal/lib/opal/nodes/scope.rb', line 401

def prepare_block(block_name = nil)
  scope_name = scope.identity
  self.block_name = block_name if block_name

  add_temp "#{self.block_name} = #{scope_name}.$$p || nil"

  unless @block_prepared
    line "delete #{scope_name}.$$p;"
    @block_prepared = true
  end
end

#push_whileObject



226
227
228
229
230
# File 'opal/lib/opal/nodes/scope.rb', line 226

def push_while
  info = {}
  @while_stack.push info
  info
end

#queue_temp(name) ⇒ Object



222
223
224
# File 'opal/lib/opal/nodes/scope.rb', line 222

def queue_temp(name)
  @queue << name
end

#refinements_tempObject



377
378
379
380
381
# File 'opal/lib/opal/nodes/scope.rb', line 377

def refinements_temp
  prev, curr = @refinements_temp, new_refinements_temp
  @refinements_temp = curr
  [prev, curr]
end

#relative_accessObject

Returns '$$', but also ensures we compile it



396
397
398
399
# File 'opal/lib/opal/nodes/scope.rb', line 396

def relative_access
  @define_relative_access = @define_nesting = true
  '$$'
end

#sclass?Boolean

Returns:

  • (Boolean)


82
83
84
# File 'opal/lib/opal/nodes/scope.rb', line 82

def sclass?
  @type == :sclass
end

#scope_localsObject



189
190
191
192
# File 'opal/lib/opal/nodes/scope.rb', line 189

def scope_locals
  locals = @locals | @args | (@parent && @type == :iter ? @parent.scope_locals : [])
  locals.reject { |i| i.to_s.start_with?('$') }
end

#selfObject

Returns 'self', but also ensures that the self variable is set



384
385
386
387
# File 'opal/lib/opal/nodes/scope.rb', line 384

def self
  @define_self = true
  'self'
end

#super_chainObject



284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'opal/lib/opal/nodes/scope.rb', line 284

def super_chain
  chain, scope, defn, mid = [], self, 'null', 'null'

  while scope
    if scope.type == :iter
      chain << scope.identify!
      scope = scope.parent if scope.parent

    elsif %i[def defs].include?(scope.type)
      defn = scope.identify!
      mid  = "'#{scope.mid}'"
      break
    else
      break
    end
  end

  [chain, defn, mid]
end

#to_varsObject

Vars to use inside each scope



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'opal/lib/opal/nodes/scope.rb', line 127

def to_vars
  vars = @temps.dup
  vars.push(*@locals.map { |l| "#{l} = nil" })

  iv = ivars.map do |ivar|
    "if (self#{ivar} == null) self#{ivar} = nil;\n"
  end

  gv = gvars.map do |gvar|
    "if ($gvars#{gvar} == null) $gvars#{gvar} = nil;\n"
  end

  if class? && !@proto_ivars.empty?
    vars << '$proto = self.$$prototype'
  end

  indent = @compiler.parser_indent
  str  = vars.empty? ? '' : "var #{vars.join ', '};\n"
  str += "#{indent}#{iv.join indent}" unless ivars.empty?
  str += "#{indent}#{gv.join indent}" unless gvars.empty?

  if class? && !@proto_ivars.empty?
    pvars = @proto_ivars.map { |i| "$proto#{i}" }.join(' = ')
    str = "#{str}\n#{indent}#{pvars} = nil;"
  end

  fragment(str)
end

#top?Boolean

Returns true if this is a top scope (main file body)

Returns:

  • (Boolean)


87
88
89
# File 'opal/lib/opal/nodes/scope.rb', line 87

def top?
  @type == :top
end

#uses_block!Object



240
241
242
243
244
245
246
247
# File 'opal/lib/opal/nodes/scope.rb', line 240

def uses_block!
  if @type == :iter && @parent
    @parent.uses_block!
  else
    @uses_block = true
    identify!
  end
end

#uses_block?Boolean

Returns:

  • (Boolean)


304
305
306
# File 'opal/lib/opal/nodes/scope.rb', line 304

def uses_block?
  @uses_block
end