Class: Ibex::Runtime::RepairSearch
- Inherits:
-
Object
- Object
- Ibex::Runtime::RepairSearch
- Defined in:
- lib/ibex/runtime/repair_search.rb,
sig/ibex/runtime/repair_search.rbs
Overview
Bounded Dijkstra search over LR state stacks. Semantic actions never run. rubocop:disable Metrics/ClassLength -- queue policy and LR transitions form one bounded search invariant.
Defined Under Namespace
Classes: Configuration
Constant Summary collapse
- NEED_INPUT =
Object.new.freeze
- LIMIT =
Object.new.freeze
Instance Method Summary collapse
- #action_for(state, token_id) ⇒ Object
- #advance(stack, token_id) ⇒ RepairAdvance, ...
- #apply_reduction(states, production_id) ⇒ Array[Integer]?
- #candidate_edits(configuration, kind, token_id, cost) ⇒ Array[RepairEdit]
- #configuration_with(source, stack: source.stack, input_index: source.input_index, shifts: source.shifts, cost: source.cost, edits: source.edits, goal: source.goal) ⇒ Configuration
- #expand(configuration) ⇒ RepairPlan, ...
- #expand_candidate(configuration, kind, token_id, cost) ⇒ Object?
- #expand_candidates(configuration, kind) ⇒ RepairPlan, ...
- #expand_edits(configuration, current) ⇒ RepairPlan, ...
-
#initialize(tables, policy, tokens, complete:) ⇒ RepairSearch
constructor
A new instance of RepairSearch.
- #plan(edits) ⇒ RepairPlan
- #pop ⇒ Configuration, Object
- #priority_for(configuration) ⇒ Array[untyped]
- #push(configuration) ⇒ void
- #push_delete(configuration, current) ⇒ void
- #search(state_stack) ⇒ RepairPlan, ...
-
#semantic_value_risk(edit) ⇒ Integer
Prefer punctuation edits when equal-cost repairs are otherwise ambiguous; inventing or discarding a word-like token is more likely to fabricate or lose an application semantic value.
- #table_lookup(table, row, column) ⇒ Object
- #token_name(token_id) ⇒ String
- #visit(configuration) ⇒ RepairPlan, ...
Constructor Details
#initialize(tables, policy, tokens, complete:) ⇒ RepairSearch
Returns a new instance of RepairSearch.
26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/ibex/runtime/repair_search.rb', line 26 def initialize(tables, policy, tokens, complete:) @tables = tables @policy = policy @tokens = tokens @complete = complete @configurations = 0 @heap = RepairPriorityQueue.new @best = {} reserved = [Parser::EOF_TOKEN, Parser::ERROR_TOKEN] @candidate_ids = tables.fetch(:token_names).keys.reject { |id| reserved.include?(id) }.sort.freeze end |
Instance Method Details
#action_for(state, token_id) ⇒ Object
235 236 237 238 239 240 |
# File 'lib/ibex/runtime/repair_search.rb', line 235 def action_for(state, token_id) explicit = table_lookup(@tables.fetch(:actions), state, token_id) return explicit if explicit @tables.fetch(:default_actions, Parser::EMPTY_ROW)[state] || [:error] end |
#advance(stack, token_id) ⇒ RepairAdvance, ...
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/ibex/runtime/repair_search.rb', line 196 def advance(stack, token_id) states = stack.dup seen = {} #: Hash[Array[Integer], bool] loop do return nil if seen[states] seen[states.dup.freeze] = true @configurations += 1 return LIMIT if @configurations > @policy.max_configurations action = action_for(states.last, token_id) case action.first when :shift states << action.fetch(1) return nil if states.length > @policy.max_stack return RepairAdvance.new(status: :shift, stack: states.freeze) when :reduce then return nil unless apply_reduction(states, action.fetch(1)) when :accept then return RepairAdvance.new(status: :accept, stack: states.freeze) else return nil end end end |
#apply_reduction(states, production_id) ⇒ Array[Integer]?
221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/ibex/runtime/repair_search.rb', line 221 def apply_reduction(states, production_id) production = @tables.fetch(:productions).fetch(production_id) length = production.fetch(:length) return if length >= states.length states.pop(length) goto = table_lookup(@tables.fetch(:gotos), states.last, production.fetch(:lhs)) return unless goto states << goto states if states.length <= @policy.max_stack end |
#candidate_edits(configuration, kind, token_id, cost) ⇒ Array[RepairEdit]
163 164 165 166 167 168 169 170 171 172 |
# File 'lib/ibex/runtime/repair_search.rb', line 163 def candidate_edits(configuration, kind, token_id, cost) edit = RepairEdit.new( kind: kind, position: configuration.input_index, token_id: token_id, token_name: token_name(token_id), cost: cost ) (configuration.edits + [edit]).freeze end |
#configuration_with(source, stack: source.stack, input_index: source.input_index, shifts: source.shifts, cost: source.cost, edits: source.edits, goal: source.goal) ⇒ Configuration
251 252 253 254 255 256 |
# File 'lib/ibex/runtime/repair_search.rb', line 251 def configuration_with(source, stack: source.stack, input_index: source.input_index, shifts: source.shifts, cost: source.cost, edits: source.edits, goal: source.goal) Configuration.new( stack: stack, input_index: input_index, shifts: shifts, cost: cost, edits: edits, goal: goal ) end |
#expand(configuration) ⇒ RepairPlan, ...
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/ibex/runtime/repair_search.rb', line 81 def (configuration) current = @tokens.fetch(configuration.input_index) unchanged = advance(configuration.stack, current.token_id) return LIMIT if unchanged.equal?(LIMIT) if unchanged.is_a?(RepairAdvance) if unchanged.status == :accept && !configuration.edits.empty? push(configuration_with(configuration, stack: unchanged.stack, goal: true)) return end if unchanged.status == :shift shifts = configuration.shifts + 1 if shifts >= @policy.success_shifts && !configuration.edits.empty? push(configuration_with(configuration, stack: unchanged.stack, shifts: shifts, goal: true)) return end push( configuration_with( configuration, stack: unchanged.stack, input_index: configuration.input_index + 1, shifts: shifts ) ) end end return unless configuration.cost < @policy.max_cost (configuration, current) end |
#expand_candidate(configuration, kind, token_id, cost) ⇒ Object?
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/ibex/runtime/repair_search.rb', line 138 def (configuration, kind, token_id, cost) advanced = advance(configuration.stack, token_id) return LIMIT if advanced.equal?(LIMIT) return unless advanced edits = candidate_edits(configuration, kind, token_id, cost) return unless advanced.is_a?(RepairAdvance) shifts = kind == :replace ? configuration.shifts + 1 : configuration.shifts input_index = kind == :replace ? configuration.input_index + 1 : configuration.input_index push( configuration_with( configuration, stack: advanced.stack, input_index: input_index, shifts: shifts, cost: configuration.cost + cost, edits: edits, goal: advanced.status == :accept || shifts >= @policy.success_shifts ) ) nil end |
#expand_candidates(configuration, kind) ⇒ RepairPlan, ...
126 127 128 129 130 131 132 133 134 135 |
# File 'lib/ibex/runtime/repair_search.rb', line 126 def (configuration, kind) cost = kind == :insert ? @policy.insert_cost : @policy.replace_cost return if configuration.cost + cost > @policy.max_cost @candidate_ids.each do |token_id| result = (configuration, kind, token_id, cost) return LIMIT if result.equal?(LIMIT) end nil end |
#expand_edits(configuration, current) ⇒ RepairPlan, ...
112 113 114 115 116 117 118 119 120 121 122 |
# File 'lib/ibex/runtime/repair_search.rb', line 112 def (configuration, current) push_delete(configuration, current) unless current.eof? inserted = (configuration, :insert) return inserted if inserted unless current.eof? replaced = (configuration, :replace) return replaced if replaced end nil end |
#plan(edits) ⇒ RepairPlan
259 260 261 |
# File 'lib/ibex/runtime/repair_search.rb', line 259 def plan(edits) RepairPlan.new(edits: edits, configurations: @configurations) end |
#pop ⇒ Configuration, Object
281 282 283 284 285 286 287 288 289 290 291 292 |
# File 'lib/ibex/runtime/repair_search.rb', line 281 def pop loop do entry = @heap.pop return LIMIT unless entry priority, configuration = entry return LIMIT unless configuration.is_a?(Configuration) key = [configuration.stack, configuration.input_index, configuration.shifts, configuration.goal] return configuration if @best[key] == priority end end |
#priority_for(configuration) ⇒ Array[untyped]
295 296 297 298 299 300 301 302 303 304 305 306 |
# File 'lib/ibex/runtime/repair_search.rb', line 295 def priority_for(configuration) edit_key = configuration.edits.map do |edit| rank = { delete: 0, insert: 1, replace: 2 }.fetch(edit.kind) [edit.position, rank, edit.token_id] end goal_rank = configuration.goal ? 0 : 1 risk = configuration.edits.sum { |edit| semantic_value_risk(edit) } [ configuration.cost, risk, edit_key, goal_rank, -configuration.shifts, configuration.input_index, configuration.stack ] end |
#push(configuration) ⇒ void
This method returns an undefined value.
269 270 271 272 273 274 275 276 277 278 |
# File 'lib/ibex/runtime/repair_search.rb', line 269 def push(configuration) priority = priority_for(configuration) key = [configuration.stack, configuration.input_index, configuration.shifts, configuration.goal] previous = @best[key] comparison = previous <=> priority if previous return if comparison && comparison <= 0 @best[key] = priority @heap.push(priority, configuration) end |
#push_delete(configuration, current) ⇒ void
This method returns an undefined value.
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 |
# File 'lib/ibex/runtime/repair_search.rb', line 175 def push_delete(configuration, current) cost = configuration.cost + @policy.delete_cost return if cost > @policy.max_cost edit = RepairEdit.new( kind: :delete, position: configuration.input_index, token_id: current.token_id, token_name: current.token_name, cost: @policy.delete_cost ) push( configuration_with( configuration, input_index: configuration.input_index + 1, cost: cost, edits: (configuration.edits + [edit]).freeze ) ) end |
#search(state_stack) ⇒ RepairPlan, ...
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/ibex/runtime/repair_search.rb', line 39 def search(state_stack) empty_edits = [] #: Array[RepairEdit] push( Configuration.new( stack: state_stack.dup.freeze, input_index: 0, shifts: 0, cost: 0, edits: empty_edits.freeze, goal: false ) ) needs_input = false until @heap.empty? configuration = pop return nil if configuration.equal?(LIMIT) next unless configuration.is_a?(Configuration) result = visit(configuration) return nil if result.equal?(LIMIT) return result if result.is_a?(RepairPlan) needs_input = true if result.equal?(NEED_INPUT) end needs_input ? NEED_INPUT : nil end |
#semantic_value_risk(edit) ⇒ Integer
Prefer punctuation edits when equal-cost repairs are otherwise ambiguous; inventing or discarding a word-like token is more likely to fabricate or lose an application semantic value.
312 313 314 |
# File 'lib/ibex/runtime/repair_search.rb', line 312 def semantic_value_risk(edit) edit.token_name.delete_prefix(":").match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/) ? 1 : 0 end |
#table_lookup(table, row, column) ⇒ Object
243 244 245 246 247 |
# File 'lib/ibex/runtime/repair_search.rb', line 243 def table_lookup(table, row, column) return table.lookup(row, column) if table.respond_to?(:lookup) table.fetch(row, Parser::EMPTY_ROW)[column] end |
#token_name(token_id) ⇒ String
264 265 266 |
# File 'lib/ibex/runtime/repair_search.rb', line 264 def token_name(token_id) @tables.fetch(:token_names).fetch(token_id, token_id.to_s) end |
#visit(configuration) ⇒ RepairPlan, ...
69 70 71 72 73 74 75 76 77 78 |
# File 'lib/ibex/runtime/repair_search.rb', line 69 def visit(configuration) @configurations += 1 return LIMIT if @configurations > @policy.max_configurations return plan(configuration.edits) if configuration.goal if configuration.input_index >= @tokens.length return @complete ? nil : NEED_INPUT end (configuration) end |