Class: Ibex::LALR::ConflictSearch
- Inherits:
-
Object
- Object
- Ibex::LALR::ConflictSearch
- Includes:
- ConflictSearchLimits
- Defined in:
- lib/ibex/lalr/conflict_search.rb,
sig/ibex/lalr/conflict_search.rbs
Overview
Searches the parser state space for one input accepted through both sides of a conflict. rubocop:disable Metrics/ClassLength -- inline type contracts make the focused search implementation longer.
Defined Under Namespace
Classes: Configuration
Constant Summary collapse
- DEFAULT_MAX_TOKENS =
ConflictSearchLimits::DEFAULT_MAX_TOKENS
- DEFAULT_MAX_CONFIGURATIONS =
ConflictSearchLimits::DEFAULT_MAX_CONFIGURATIONS
Instance Attribute Summary collapse
- #explored ⇒ Integer readonly
Instance Method Summary collapse
- #accept_with_eof(left_config, right_config) ⇒ [ search_entry, search_entry ]?
- #accepted_pair?(left, right) ⇒ Boolean
- #accepted_result(prefix, suffix, left, right, left_action, right_action) ⇒ search_result?
- #actions_for(state_id, token_id, forced, branch_conflicts) ⇒ Array[IR::parser_action]
- #actions_from(conflict) ⇒ Array[IR::parser_action]
- #advance(initial, token_id, forced_action: nil, stop_at: nil, branch_conflicts: false) ⇒ Array[search_entry]
- #apply_action(results, queue, current, action, token_id) ⇒ void
- #call ⇒ search_outcome
- #configuration(states, nodes) ⇒ Configuration
- #conflict_actions ⇒ Array[IR::parser_action]
- #conflict_configurations(current) ⇒ Array[Configuration]
- #count_configuration ⇒ Integer
- #enqueue_prefixes(queue, visited, current, prefix) ⇒ void
- #enqueue_suffixes(queue, visited, left_config, right_config, suffix) ⇒ void
- #exhausted? ⇒ Boolean
-
#initialize(automaton, state, conflict, max_tokens: DEFAULT_MAX_TOKENS, max_configurations: DEFAULT_MAX_CONFIGURATIONS) ⇒ ConflictSearch
constructor
A new instance of ConflictSearch.
- #interpretation(action, current) ⇒ IR::interpretation
- #outcome(status, result = nil) ⇒ search_outcome
- #pair_key(left, right) ⇒ [ Array[Integer], Array[Integer] ]
- #reduce(current, production_id) ⇒ Configuration?
- #required_lookahead ⇒ Integer
- #search_common_suffix(left, right, prefix, left_action, right_action) ⇒ search_result?
- #shift(current, state_id, token_id) ⇒ Configuration
- #shifted_pair?(left, right) ⇒ Boolean
- #shifted_results(current, token_id) ⇒ Array[Configuration]
- #symbol_name(id) ⇒ String
- #unify_from(current, prefix) ⇒ search_result?
Methods included from ConflictSearchLimits
#conflict_lookahead_length, #room_for_token?, validate!, validate_limit!, #within_token_budget?
Constructor Details
#initialize(automaton, state, conflict, max_tokens: DEFAULT_MAX_TOKENS, max_configurations: DEFAULT_MAX_CONFIGURATIONS) ⇒ ConflictSearch
Returns a new instance of ConflictSearch.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/ibex/lalr/conflict_search.rb', line 34 def initialize(automaton, state, conflict, max_tokens: DEFAULT_MAX_TOKENS, max_configurations: DEFAULT_MAX_CONFIGURATIONS) ConflictSearchLimits.validate!(max_tokens: max_tokens, max_configurations: max_configurations) @automaton = automaton @grammar = automaton.grammar @state = state @conflict = conflict @lookahead = @grammar.symbol(conflict[:symbol])&.id @max_tokens = max_tokens @max_configurations = max_configurations @explored = 0 @input_tokens = @grammar.terminals.reject(&:reserved).sort_by(&:name).map(&:id) entry = conflict[:entries]&.first || @grammar.start @initial_state = automaton.entry_states.fetch(entry) end |
Instance Attribute Details
#explored ⇒ Integer (readonly)
30 31 32 |
# File 'lib/ibex/lalr/conflict_search.rb', line 30 def explored @explored end |
Instance Method Details
#accept_with_eof(left_config, right_config) ⇒ [ search_entry, search_entry ]?
143 144 145 146 147 |
# File 'lib/ibex/lalr/conflict_search.rb', line 143 def accept_with_eof(left_config, right_config) left = advance(left_config, 0, branch_conflicts: true).select { |entry| entry.first == :accepted } right = advance(right_config, 0, branch_conflicts: true).select { |entry| entry.first == :accepted } left.product(right).first end |
#accepted_pair?(left, right) ⇒ Boolean
199 200 201 |
# File 'lib/ibex/lalr/conflict_search.rb', line 199 def accepted_pair?(left, right) left.first == :accepted && right.first == :accepted end |
#accepted_result(prefix, suffix, left, right, left_action, right_action) ⇒ search_result?
174 175 176 177 178 179 180 181 182 183 |
# File 'lib/ibex/lalr/conflict_search.rb', line 174 def accepted_result(prefix, suffix, left, right, left_action, right_action) return unless within_token_budget?(prefix, suffix) sentence = prefix.dup lookahead = required_lookahead sentence << lookahead unless lookahead.zero? sentence.concat(suffix) { sentence_ids: sentence, lookahead_index: prefix.length, interpretations: [interpretation(left_action, left.last), interpretation(right_action, right.last)] } end |
#actions_for(state_id, token_id, forced, branch_conflicts) ⇒ Array[IR::parser_action]
253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 |
# File 'lib/ibex/lalr/conflict_search.rb', line 253 def actions_for(state_id, token_id, forced, branch_conflicts) return [forced] if forced state = @automaton.states.fetch(state_id) actions = [state.actions[token_id] || state.default_action].compact return actions unless branch_conflicts token_name = symbol_name(token_id) state.conflicts.each do |conflict| next unless conflict[:symbol] == token_name actions.concat(actions_from(conflict)) end actions.uniq end |
#actions_from(conflict) ⇒ Array[IR::parser_action]
270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 |
# File 'lib/ibex/lalr/conflict_search.rb', line 270 def actions_from(conflict) case conflict[:type] when :shift_reduce shift_reduce = conflict #: IR::shift_reduce_conflict shift = { type: :shift, state: shift_reduce[:shift_to] } #: IR::shift_action reduce = { type: :reduce, production: shift_reduce[:reduce] } #: IR::reduce_action [shift, reduce] when :reduce_reduce reduce_reduce = conflict #: IR::reduce_reduce_conflict reduce_reduce[:reductions].map do |production| { type: :reduce, production: production } #: IR::reduce_action end else [] end end |
#advance(initial, token_id, forced_action: nil, stop_at: nil, branch_conflicts: false) ⇒ Array[search_entry]
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/ibex/lalr/conflict_search.rb', line 228 def advance(initial, token_id, forced_action: nil, stop_at: nil, branch_conflicts: false) queue = [[initial, forced_action]] #: Array[[Configuration, IR::parser_action?]] visited = {} #: Hash[[Array[Integer], bool], bool] results = [] #: Array[search_entry] until queue.empty? || exhausted? current, forced = queue.shift if stop_at == current.states.last results << [:conflict, current] next end key = [current.states, !forced.nil?] #: [Array[Integer], bool] next if visited[key] visited[key] = true count_configuration actions_for(current.states.last, token_id, forced, branch_conflicts).each do |action| apply_action(results, queue, current, action, token_id) end end results end |
#apply_action(results, queue, current, action, token_id) ⇒ void
This method returns an undefined value.
289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/ibex/lalr/conflict_search.rb', line 289 def apply_action(results, queue, current, action, token_id) case action[:type] when :shift shift_action = action #: IR::shift_action results << [:shifted, shift(current, shift_action[:state], token_id)] when :reduce reduce_action = action #: IR::reduce_action reduced = reduce(current, reduce_action[:production]) queue << [reduced, nil] if reduced when :accept results << [:accepted, current] end end |
#call ⇒ search_outcome
51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/ibex/lalr/conflict_search.rb', line 51 def call return outcome(:not_found) unless @lookahead initial = configuration([@initial_state], Array.new(0)) queue = [[initial, Array.new(0)]] #: Array[[Configuration, Array[Integer]]] visited = { [@initial_state] => true } until queue.empty? || exhausted? current, prefix = queue.shift conflict_configurations(current).each do |candidate| result = unify_from(candidate, prefix) return outcome(:found, result) if result end enqueue_prefixes(queue, visited, current, prefix) end outcome(exhausted? ? :exhausted : :not_found) end |
#configuration(states, nodes) ⇒ Configuration
326 |
# File 'lib/ibex/lalr/conflict_search.rb', line 326 def configuration(states, nodes) = Configuration.new(states: states.freeze, nodes: nodes.freeze) |
#conflict_actions ⇒ Array[IR::parser_action]
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/ibex/lalr/conflict_search.rb', line 209 def conflict_actions case @conflict[:type] when :shift_reduce conflict = @conflict #: IR::shift_reduce_conflict shift = { type: :shift, state: conflict[:shift_to] } #: IR::shift_action reduce = { type: :reduce, production: conflict[:reduce] } #: IR::reduce_action [shift, reduce] when :reduce_reduce conflict = @conflict #: IR::reduce_reduce_conflict conflict[:reductions].map do |production| { type: :reduce, production: production } #: IR::reduce_action end else [] end end |
#conflict_configurations(current) ⇒ Array[Configuration]
85 86 87 88 |
# File 'lib/ibex/lalr/conflict_search.rb', line 85 def conflict_configurations(current) advance(current, required_lookahead, stop_at: @state.id, branch_conflicts: true) .filter_map { |status, candidate| candidate if status == :conflict } end |
#count_configuration ⇒ Integer
345 |
# File 'lib/ibex/lalr/conflict_search.rb', line 345 def count_configuration = (@explored += 1) |
#enqueue_prefixes(queue, visited, current, prefix) ⇒ void
This method returns an undefined value.
92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/ibex/lalr/conflict_search.rb', line 92 def enqueue_prefixes(queue, visited, current, prefix) return unless room_for_token?(prefix, []) @input_tokens.each do |token_id| advance(current, token_id, branch_conflicts: true).each do |status, candidate| next unless status == :shifted next if visited[candidate.states] visited[candidate.states] = true queue << [candidate, prefix + [token_id]] end end end |
#enqueue_suffixes(queue, visited, left_config, right_config, suffix) ⇒ void
This method returns an undefined value.
152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/ibex/lalr/conflict_search.rb', line 152 def enqueue_suffixes(queue, visited, left_config, right_config, suffix) @input_tokens.each do |token_id| left = shifted_results(left_config, token_id) right = shifted_results(right_config, token_id) left.product(right).each do |left_candidate, right_candidate| key = pair_key(left_candidate, right_candidate) next if visited[key] visited[key] = true queue << [left_candidate, right_candidate, suffix + [token_id]] end end end |
#exhausted? ⇒ Boolean
69 |
# File 'lib/ibex/lalr/conflict_search.rb', line 69 def exhausted? = @explored >= @max_configurations |
#interpretation(action, current) ⇒ IR::interpretation
186 187 188 189 190 191 192 193 194 195 196 |
# File 'lib/ibex/lalr/conflict_search.rb', line 186 def interpretation(action, current) details = { kind: action[:type], tree: current.nodes.last } #: IR::interpretation if action[:type] == :shift shift_action = action #: IR::shift_action details[:state] = shift_action[:state] elsif action[:type] == :reduce reduce_action = action #: IR::reduce_action details[:production] = reduce_action[:production] end details end |
#outcome(status, result = nil) ⇒ search_outcome
74 75 76 77 78 79 80 81 82 |
# File 'lib/ibex/lalr/conflict_search.rb', line 74 def outcome(status, result = nil) { status: status, result: result, explored: @explored, exhausted: status == :exhausted, bounds: { max_tokens: @max_tokens, max_configurations: @max_configurations } } end |
#pair_key(left, right) ⇒ [ Array[Integer], Array[Integer] ]
340 341 342 |
# File 'lib/ibex/lalr/conflict_search.rb', line 340 def pair_key(left, right) [left.states, right.states] end |
#reduce(current, production_id) ⇒ Configuration?
310 311 312 313 314 315 316 317 318 319 320 321 322 323 |
# File 'lib/ibex/lalr/conflict_search.rb', line 310 def reduce(current, production_id) production = @grammar.productions.fetch(production_id) length = production.rhs.length return if length >= current.states.length states = current.states.take(current.states.length - length) children = current.nodes.last(length) nodes = current.nodes.take(current.nodes.length - length) target = @automaton.states.fetch(states.last).gotos[production.lhs] return unless target symbol = symbol_name(production.lhs) configuration(states + [target], nodes + [{ symbol: symbol, production: production_id, children: children }]) end |
#required_lookahead ⇒ Integer
329 330 331 |
# File 'lib/ibex/lalr/conflict_search.rb', line 329 def required_lookahead @lookahead || raise(Ibex::Error, "missing conflict lookahead") end |
#search_common_suffix(left, right, prefix, left_action, right_action) ⇒ search_result?
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/ibex/lalr/conflict_search.rb', line 125 def search_common_suffix(left, right, prefix, left_action, right_action) return accepted_result(prefix, [], left, right, left_action, right_action) if accepted_pair?(left, right) return unless shifted_pair?(left, right) queue = [[left.last, right.last, Array.new(0)]] #: Array[[Configuration, Configuration, Array[Integer]]] visited = { pair_key(left.last, right.last) => true } until queue.empty? || exhausted? left_config, right_config, suffix = queue.shift accepted = accept_with_eof(left_config, right_config) return accepted_result(prefix, suffix, accepted[0], accepted[1], left_action, right_action) if accepted next unless room_for_token?(prefix, suffix) enqueue_suffixes(queue, visited, left_config, right_config, suffix) end nil end |
#shift(current, state_id, token_id) ⇒ Configuration
304 305 306 307 |
# File 'lib/ibex/lalr/conflict_search.rb', line 304 def shift(current, state_id, token_id) symbol = symbol_name(token_id) configuration(current.states + [state_id], current.nodes + [{ symbol: symbol, token: symbol }]) end |
#shifted_pair?(left, right) ⇒ Boolean
204 205 206 |
# File 'lib/ibex/lalr/conflict_search.rb', line 204 def shifted_pair?(left, right) left.first == :shifted && right.first == :shifted end |
#shifted_results(current, token_id) ⇒ Array[Configuration]
167 168 169 170 |
# File 'lib/ibex/lalr/conflict_search.rb', line 167 def shifted_results(current, token_id) advance(current, token_id, branch_conflicts: true) .filter_map { |status, candidate| candidate if status == :shifted } end |
#symbol_name(id) ⇒ String
334 335 336 337 |
# File 'lib/ibex/lalr/conflict_search.rb', line 334 def symbol_name(id) symbol = @grammar.symbol_by_id(id) || raise(Ibex::Error, "missing grammar symbol id #{id}") symbol.name end |
#unify_from(current, prefix) ⇒ search_result?
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/ibex/lalr/conflict_search.rb', line 107 def unify_from(current, prefix) return unless within_token_budget?(prefix, []) conflict_actions.combination(2) do |left_action, right_action| next unless left_action && right_action left_results = advance(current, required_lookahead, forced_action: left_action, branch_conflicts: true) right_results = advance(current, required_lookahead, forced_action: right_action, branch_conflicts: true) left_results.product(right_results).each do |left, right| result = search_common_suffix(left, right, prefix, left_action, right_action) return result if result end end nil end |