Class: KatakataIrb::Scope

Inherits:
BaseScope show all
Defined in:
lib/katakata_irb/scope.rb

Constant Summary

Constants inherited from BaseScope

BaseScope::BREAK_RESULT, BaseScope::NEXT_RESULT, BaseScope::PATTERNMATCH_BREAK, BaseScope::RETURN_RESULT

Instance Attribute Summary collapse

Attributes inherited from BaseScope

#self_object

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from BaseScope

#module_own_constant?, type_by_name, type_of

Constructor Details

#initialize(parent, table = {}, trace_ivar: true, trace_lvar: true, self_type: nil, nesting: nil) ⇒ Scope

Returns a new instance of Scope.



115
116
117
118
119
120
121
122
123
124
125
# File 'lib/katakata_irb/scope.rb', line 115

def initialize(parent, table = {}, trace_ivar: true, trace_lvar: true, self_type: nil, nesting: nil)
  @parent = parent
  @level = parent.level + 1
  @trace_ivar = trace_ivar
  @trace_lvar = trace_lvar
  @module_nesting = nesting ? [nesting, *parent.module_nesting] : parent.module_nesting
  @self_type = self_type
  @terminated = false
  @jump_branches = []
  @mergeable_changes = @table = table.transform_values { [level, _1] }
end

Instance Attribute Details

#levelObject (readonly)

Returns the value of attribute level.



111
112
113
# File 'lib/katakata_irb/scope.rb', line 111

def level
  @level
end

#mergeable_changesObject (readonly)

Returns the value of attribute mergeable_changes.



111
112
113
# File 'lib/katakata_irb/scope.rb', line 111

def mergeable_changes
  @mergeable_changes
end

#module_nestingObject (readonly)

Returns the value of attribute module_nesting.



111
112
113
# File 'lib/katakata_irb/scope.rb', line 111

def module_nesting
  @module_nesting
end

#parentObject (readonly)

Returns the value of attribute parent.



111
112
113
# File 'lib/katakata_irb/scope.rb', line 111

def parent
  @parent
end

Class Method Details

.from_binding(binding, locals) ⇒ Object



113
# File 'lib/katakata_irb/scope.rb', line 113

def self.from_binding(binding, locals) = new(BaseScope.new(binding, binding.eval('self'), locals))

Instance Method Details

#[](name) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/katakata_irb/scope.rb', line 184

def [](name)
  type = BaseScope.type_by_name(name)
  if type == :const
    return get_const(nil, nil, name) || KatakataIrb::Types::NIL if name.include?('::')

    module_nesting.each do |(nesting, path)|
      value = get_const nesting, [*path, name]
      return value if value
    end
    return KatakataIrb::Types::NIL
  elsif type == :cvar
    return get_cvar(nil, nil, nil, name) if name.include?('::')

    nesting, path = module_nesting.first
    return get_cvar(nesting, path, name)
  end
  level, value = @table[name]
  if level
    value
  elsif trace? name
    @parent[name]
  elsif type == :ivar
    self_instance_variable_get name
  end
end

#[]=(name, value) ⇒ Object



220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# File 'lib/katakata_irb/scope.rb', line 220

def []=(name, value)
  type = BaseScope.type_by_name(name)
  if type == :const
    if name.include?('::')
      @table[name] = [0, value]
    else
      parent_module, parent_path = module_nesting.first
      set_const parent_module, [*parent_path, name], value
    end
    return
  elsif type == :cvar
    if name.include?('::')
      @table[name] = [0, value]
    else
      parent_module, parent_path = module_nesting.first
      set_cvar parent_module, parent_path, name, value
    end
    return
  end
  variable_level = level_of name, type
  @table[name] = [variable_level, value] if variable_level
end

#base_scopeObject



278
279
280
# File 'lib/katakata_irb/scope.rb', line 278

def base_scope
  @parent.mutable? ? @parent.base_scope : @parent
end

#class_variablesObject



331
332
333
334
335
336
# File 'lib/katakata_irb/scope.rb', line 331

def class_variables
  cvars = table_class_variables
  m, = module_nesting.first
  cvars |= m.class_variables.map(&:to_s) if m.is_a? Module
  cvars
end

#conditional(&block) ⇒ Object



355
356
357
# File 'lib/katakata_irb/scope.rb', line 355

def conditional(&block)
  run_branches(block, ->(_s) {}).first || KatakataIrb::Types::NIL
end

#constantsObject



338
339
340
341
342
# File 'lib/katakata_irb/scope.rb', line 338

def constants
  module_nesting.flat_map do |nest,|
    nest.constants
  end.map(&:to_s) | table_constants
end

#get_const(nesting, path, key = nil) ⇒ Object



172
173
174
175
176
# File 'lib/katakata_irb/scope.rb', line 172

def get_const(nesting, path, key = nil)
  key ||= [nesting.__id__, path].join('::')
  _l, value = @table[key]
  value || @parent.get_const(nesting, path, key)
end

#get_cvar(nesting, path, name, key = nil) ⇒ Object



178
179
180
181
182
# File 'lib/katakata_irb/scope.rb', line 178

def get_cvar(nesting, path, name, key = nil)
  key ||= [name, nesting.__id__, path].join('::')
  _l, value = @table[key]
  value || @parent.get_cvar(nesting, path, name, key)
end

#global_variablesObject



247
248
249
250
251
252
# File 'lib/katakata_irb/scope.rb', line 247

def global_variables
  gvar_keys = @table.keys.select do |name|
    BaseScope.type_by_name(name) == :gvar
  end
  gvar_keys | @parent.global_variables
end

#has_own?(name) ⇒ Boolean

Returns:

  • (Boolean)


378
379
380
# File 'lib/katakata_irb/scope.rb', line 378

def has_own?(name)
  @table.key? name
end

#instance_variablesObject



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
# File 'lib/katakata_irb/scope.rb', line 288

def instance_variables
  self_singleton_types = self_type.types.grep(KatakataIrb::Types::SingletonType)
  singleton_classes = self_type.types.grep(KatakataIrb::Types::InstanceType).map(&:klass).select(&:singleton_class?)
  base_self = base_scope.self_object
  self_instance_variables = singleton_classes.flat_map do |singleton_class|
    if singleton_class.respond_to? :attached_object
      singleton_class.attached_object.instance_variables.map(&:to_s)
    elsif singleton_class == base_self.singleton_class
      base_self.instance_variables.map(&:to_s)
    else
      []
    end
  end
  [
    self_singleton_types.flat_map { _1.module_or_class.instance_variables.map(&:to_s) },
    self_instance_variables || [],
    table_instance_variables
  ].inject(:|)
end

#level_of(name, var_type) ⇒ Object



161
162
163
164
165
166
167
168
169
170
# File 'lib/katakata_irb/scope.rb', line 161

def level_of(name, var_type)
  case var_type
  when :ivar
    return level unless @trace_ivar
  when :gvar
    return 0
  end
  variable_level, = @table[name]
  variable_level || parent.level_of(name, var_type)
end

#local_variablesObject



254
255
256
257
258
259
260
# File 'lib/katakata_irb/scope.rb', line 254

def local_variables
  lvar_keys = @table.keys.select do |name|
    BaseScope.type_by_name(name) == :lvar
  end
  lvar_keys |= @parent.local_variables if @trace_lvar
  lvar_keys
end

#merge_jumpsObject



344
345
346
347
348
349
350
351
352
353
# File 'lib/katakata_irb/scope.rb', line 344

def merge_jumps
  if terminated?
    @terminated = false
    @table = @mergeable_changes
    merge @jump_branches
    @terminated = true
  else
    merge [*@jump_branches, {}]
  end
end

#mutable?Boolean

Returns:

  • (Boolean)


127
# File 'lib/katakata_irb/scope.rb', line 127

def mutable? = true

#never(&block) ⇒ Object



359
360
361
# File 'lib/katakata_irb/scope.rb', line 359

def never(&block)
  block.call Scope.new(self, { BREAK_RESULT => nil, NEXT_RESULT => nil, PATTERNMATCH_BREAK => nil, RETURN_RESULT => nil })
end

#run_branches(*blocks) ⇒ Object



363
364
365
366
367
368
369
370
371
372
373
374
375
376
# File 'lib/katakata_irb/scope.rb', line 363

def run_branches(*blocks)
  results = []
  branches = []
  blocks.each do |block|
    scope = Scope.new self
    result = block.call scope
    next if scope.terminated?
    results << result
    branches << scope.mergeable_changes
  end
  terminate if branches.empty?
  merge branches
  results
end

#self_instance_variable_get(name) ⇒ Object



308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/katakata_irb/scope.rb', line 308

def self_instance_variable_get(name)
  self_objects = self_type.types.grep(KatakataIrb::Types::SingletonType).map(&:module_or_class)
  singleton_classes = self_type.types.grep(KatakataIrb::Types::InstanceType).map(&:klass).select(&:singleton_class?)
  base_self = base_scope.self_object
  singleton_classes.each do |singleton_class|
    if singleton_class.respond_to? :attached_object
      self_objects << singleton_class.attached_object
    elsif singleton_class == base_self.singleton_class
      self_objects << base_self
    end
  end
  types = self_objects.map do |object|
    BaseScope.type_of(fallback: KatakataIrb::Types::NIL) { object.instance_variable_get name }
  end
  KatakataIrb::Types::UnionType[*types]
end

#self_typeObject



243
244
245
# File 'lib/katakata_irb/scope.rb', line 243

def self_type
  @self_type || @parent.self_type
end

#set_const(nesting, path, value) ⇒ Object



210
211
212
213
# File 'lib/katakata_irb/scope.rb', line 210

def set_const(nesting, path, value)
  key = [nesting.__id__, path].join('::')
  @table[key] = [0, value]
end

#set_cvar(nesting, path, name, value) ⇒ Object



215
216
217
218
# File 'lib/katakata_irb/scope.rb', line 215

def set_cvar(nesting, path, name, value)
  key = [name, nesting.__id__, path].join('::')
  @table[key] = [0, value]
end

#store_jump(type, value, changes) ⇒ Object



139
140
141
142
143
144
145
146
147
# File 'lib/katakata_irb/scope.rb', line 139

def store_jump(type, value, changes)
  return if terminated?
  if has_own?(type)
    changes[type] = [level, value]
    @jump_branches << changes
  elsif @parent.mutable?
    @parent.store_jump(type, value, changes)
  end
end

#table_class_variablesObject



325
326
327
328
329
# File 'lib/katakata_irb/scope.rb', line 325

def table_class_variables
  cvars = @table.keys.filter_map { _1.split('::', 2).first if BaseScope.type_by_name(_1) == :cvar }
  cvars |= @parent.table_class_variables if @parent.mutable?
  cvars
end

#table_constantsObject



262
263
264
265
266
267
268
269
# File 'lib/katakata_irb/scope.rb', line 262

def table_constants
  constants = module_nesting.flat_map do |mod, path|
    prefix = [mod.__id__, *path].join('::') + '::'
    @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
  end.uniq
  constants |= @parent.table_constants if @parent.mutable?
  constants
end

#table_instance_variablesObject



282
283
284
285
286
# File 'lib/katakata_irb/scope.rb', line 282

def table_instance_variables
  ivars = @table.keys.select { BaseScope.type_by_name(_1) == :ivar }
  ivars |= @parent.table_instance_variables if @parent.mutable? && @trace_ivar
  ivars
end

#table_module_constants(mod) ⇒ Object



271
272
273
274
275
276
# File 'lib/katakata_irb/scope.rb', line 271

def table_module_constants(mod)
  prefix = "#{mod.__id__}::"
  constants = @table.keys.select { _1.start_with? prefix }.map { _1.delete_prefix(prefix).split('::').first }
  constants |= @parent.table_constants if @parent.mutable?
  constants
end

#terminateObject



149
150
151
152
153
# File 'lib/katakata_irb/scope.rb', line 149

def terminate
  return if terminated?
  @terminated = true
  @table = @mergeable_changes.dup
end

#terminate_with(type, value) ⇒ Object



133
134
135
136
137
# File 'lib/katakata_irb/scope.rb', line 133

def terminate_with(type, value)
  return if terminated?
  store_jump type, value, @mergeable_changes
  terminate
end

#terminated?Boolean

Returns:

  • (Boolean)


129
130
131
# File 'lib/katakata_irb/scope.rb', line 129

def terminated?
  @terminated
end

#trace?(name) ⇒ Boolean

Returns:

  • (Boolean)


155
156
157
158
159
# File 'lib/katakata_irb/scope.rb', line 155

def trace?(name)
  return false unless @parent
  type = BaseScope.type_by_name(name)
  type == :ivar ? @trace_ivar : type == :lvar ? @trace_lvar : true
end

#update(child_scope) ⇒ Object



382
383
384
385
386
387
# File 'lib/katakata_irb/scope.rb', line 382

def update(child_scope)
  current_level = level
  child_scope.mergeable_changes.each do |name, (level, value)|
    self[name] = value if level <= current_level
  end
end