Class: Ibex::Impact::ActionImpact

Inherits:
Object
  • Object
show all
Defined in:
lib/ibex/impact/action_impact.rb,
sig/ibex/impact/action_impact.rbs

Overview

Checks only structured action metadata; action source remains opaque.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(before, after, affected_names: nil) ⇒ ActionImpact

Returns a new instance of ActionImpact.

RBS:

  • (IR::Grammar before, IR::Grammar after, ?affected_names: Array[String]?) -> void

Parameters:



14
15
16
17
18
19
20
# File 'lib/ibex/impact/action_impact.rb', line 14

def initialize(before, after, affected_names: nil)
  @before = before
  @after = after
  @affected_names = affected_names
  @findings = compare
  freeze
end

Instance Attribute Details

#findingsArray[Hash[Symbol, Object?]] (readonly)

RBS:

  • @before: IR::Grammar

  • @after: IR::Grammar

  • @affected_names: Array[String]?

Returns:

  • (Array[Hash[Symbol, Object?]])


11
12
13
# File 'lib/ibex/impact/action_impact.rb', line 11

def findings
  @findings
end

Instance Method Details

#action_location_identity(production) ⇒ [ Integer, Integer ]?

RBS:

  • (IR::Production production) -> [Integer, Integer]?

Parameters:

Returns:

  • ([ Integer, Integer ], nil)


104
105
106
107
108
109
110
111
112
113
# File 'lib/ibex/impact/action_impact.rb', line 104

def action_location_identity(production)
  location = production.origin[:loc] || production.action&.location #: IR::location?
  return unless location

  line = location[:line]
  column = location[:column]
  return unless line && column

  [line, column]
end

#compareArray[Hash[Symbol, Object?]]

RBS:

  • () -> Array[Hash[Symbol, Object?]]

Returns:

  • (Array[Hash[Symbol, Object?]])


30
31
32
33
34
# File 'lib/ibex/impact/action_impact.rb', line 30

def compare
  names = @after.nonterminals.map(&:name)
  names &= @affected_names if @affected_names
  names.sort.flat_map { |name| compare_rule(name) }.sort_by { |finding| finding.fetch(:production) }
end

#compare_rule(name) ⇒ Array[Hash[Symbol, Object?]]

RBS:

  • (String name) -> Array[Hash[Symbol, Object?]]

Parameters:

  • name (String)

Returns:

  • (Array[Hash[Symbol, Object?]])


37
38
39
40
41
42
43
44
45
# File 'lib/ibex/impact/action_impact.rb', line 37

def compare_rule(name)
  before = productions_for(@before, name)
  after = productions_for(@after, name)
  pair_productions(before, after).filter_map do |previous, production|
    next if previous.rhs.length == production.rhs.length

    finding_for(previous, production)
  end
end

#finding_for(before, after) ⇒ Hash[Symbol, Object?]

RBS:

  • (IR::Production before, IR::Production after) -> Hash[Symbol, Object?]

Parameters:

Returns:

  • (Hash[Symbol, Object?])


116
117
118
119
120
121
122
123
124
125
# File 'lib/ibex/impact/action_impact.rb', line 116

def finding_for(before, after)
  action = after.action
  reason, severity = finding_reason(action, before.rhs.length, after.rhs.length)
  {
    production: production_name(@after, after), severity: severity,
    reason: reason,
    context_length: { before: before.rhs.length, after: after.rhs.length },
    loc: after.action&.location || after.origin[:loc]
  }
end

#finding_reason(action, before_length, after_length) ⇒ [ String, String ]

RBS:

  • (IR::Action?, Integer, Integer) -> [String, String]

Parameters:

Returns:

  • ([ String, String ])


128
129
130
131
132
133
# File 'lib/ibex/impact/action_impact.rb', line 128

def finding_reason(action, before_length, after_length)
  return %w[named_ref_index_out_of_range high] if out_of_range?(action, after_length)
  return %w[context_length_stale high] if action&.context_length == before_length

  %w[rhs_length_changed medium]
end

#out_of_range?(action, length) ⇒ Boolean

RBS:

  • (IR::Action?, Integer) -> bool

Parameters:

Returns:

  • (Boolean)


136
137
138
# File 'lib/ibex/impact/action_impact.rb', line 136

def out_of_range?(action, length)
  action&.named_refs&.any? { |reference| reference.fetch(:index) >= length } || false
end

#pair_by_location(unmatched, after, pairs) ⇒ Array[[ IR::Production, IR::Production ]]

RBS:

  • (Array[IR::Production] unmatched, Array[IR::Production] after, Array[[IR::Production, IR::Production]]) -> Array[[IR::Production, IR::Production]]

Parameters:

Returns:



72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/ibex/impact/action_impact.rb', line 72

def pair_by_location(unmatched, after, pairs)
  after.each do |production|
    next if pairs.any? { |_, candidate| candidate.equal?(production) }

    index = unmatched.index do |candidate|
      location = action_location_identity(production)
      location && location == action_location_identity(candidate)
    end
    next unless index

    pairs << [unmatched.delete_at(index), production]
  end

  pairs
end

#pair_exact_productions(unmatched, after) ⇒ Array[[ IR::Production, IR::Production ]]

RBS:

  • (Array[IR::Production] unmatched, Array[IR::Production] after) -> Array[[IR::Production, IR::Production]]

Parameters:

Returns:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/ibex/impact/action_impact.rb', line 55

def pair_exact_productions(unmatched, after)
  pairs = [] #: Array[[IR::Production, IR::Production]]

  after.each do |production|
    index = unmatched.index do |candidate|
      production_signature(@before, candidate) == production_signature(@after, production)
    end
    next unless index

    pairs << [unmatched.delete_at(index), production]
  end

  pairs
end

#pair_productions(before, after) ⇒ Array[[ IR::Production, IR::Production ]]

RBS:

  • (Array[IR::Production] before, Array[IR::Production] after) -> Array[[IR::Production, IR::Production]]

Parameters:

Returns:



48
49
50
51
52
# File 'lib/ibex/impact/action_impact.rb', line 48

def pair_productions(before, after)
  unmatched = before.dup
  pairs = pair_exact_productions(unmatched, after)
  pair_by_location(unmatched, after, pairs)
end

#production_name(grammar, production) ⇒ String

RBS:

  • (IR::Grammar grammar, IR::Production production) -> String

Parameters:

Returns:

  • (String)


141
142
143
144
145
# File 'lib/ibex/impact/action_impact.rb', line 141

def production_name(grammar, production)
  lhs = grammar.symbol_by_id(production.lhs)&.name || production.lhs.to_s
  rhs = production.rhs.map { |id| grammar.symbol_by_id(id)&.name || id.to_s }
  "#{lhs} -> #{rhs.join(' ')}"
end

#production_signature(grammar, production) ⇒ [ Array[String], String? ]

RBS:

  • (IR::Grammar grammar, IR::Production production) -> [Array[String], String?]

Parameters:

Returns:

  • ([ Array[String], String? ])


97
98
99
100
101
# File 'lib/ibex/impact/action_impact.rb', line 97

def production_signature(grammar, production)
  rhs = production.rhs.map { |id| grammar.symbol_by_id(id)&.name || id.to_s }
  precedence = grammar.symbol_by_id(production.precedence_override)&.name
  [rhs, precedence]
end

#productions_for(grammar, name) ⇒ Array[IR::Production]

RBS:

  • (IR::Grammar grammar, String name) -> Array[IR::Production]

Parameters:

Returns:



89
90
91
92
93
94
# File 'lib/ibex/impact/action_impact.rb', line 89

def productions_for(grammar, name)
  lhs = grammar.symbol(name)&.id
  return [] unless lhs

  grammar.productions.select { |production| production.lhs == lhs }
end

#to_aArray[Hash[Symbol, Object?]]

RBS:

  • () -> Array[Hash[Symbol, Object?]]

Returns:

  • (Array[Hash[Symbol, Object?]])


23
24
25
# File 'lib/ibex/impact/action_impact.rb', line 23

def to_a
  @findings
end