Class: Ibex::Frontend::DiagnosticRecovery
- Inherits:
-
Object
- Object
- Ibex::Frontend::DiagnosticRecovery
- Defined in:
- lib/ibex/frontend/diagnostic_recovery.rb,
sig/ibex/frontend/diagnostic_recovery.rbs
Overview
Re-runs the generated parser after suppressing only conservative source regions.
Constant Summary collapse
- DECLARATION_STARTS =
%w[ pragma include import token prechigh preclow options expect expect_rr start recover on_error_reduce test lexer convert display type param printer ].freeze
Instance Method Summary collapse
- #alternative_range(failure, start, finish) ⇒ Range[Integer]?
- #alternative_slice(previous, following, colon, finish) ⇒ Range[Integer]?
- #append_diagnostic(diagnostic) ⇒ void
- #declaration_finish(start, rule_marker, starts) ⇒ Integer
- #declaration_range(failure, rule_marker) ⇒ Range[Integer]?
- #declaration_start?(index) ⇒ Boolean
- #diagnostic_from(error, token, phase:) ⇒ Diagnostic
- #diagnostic_key(diagnostic) ⇒ [ String, String, Integer, Integer, String ]
- #diagnostic_sort_key(diagnostic) ⇒ [ String, Integer, Integer, String, String ]
- #expected_and_received(message) ⇒ [ Array[String], String? ]
- #grammar_end_index(rule_marker) ⇒ Integer
-
#initialize(tokens, mode:, max_diagnostics:) ⇒ DiagnosticRecovery
constructor
A new instance of DiagnosticRecovery.
- #lhs_candidate?(index, lhs_column) ⇒ Boolean
- #matching_declaration_end(start, fallback, closer) ⇒ Integer
- #parse ⇒ [ AST::Root | AST::Fragment | nil, Array[Diagnostic] ]
- #parse_until_stable ⇒ AST::Root, ...
- #rule_definition_starts(rule_marker, grammar_end) ⇒ Array[Integer]
- #rule_marker_index ⇒ Integer?
- #rule_range(failure, rule_marker) ⇒ Range[Integer]?
- #sorted_diagnostics ⇒ Array[Diagnostic]
- #suppress(range) ⇒ Boolean
- #suppression_range(token, error) ⇒ Range[Integer]?
- #token_index(token) ⇒ Integer?
- #token_index_for_message(message) ⇒ Integer?
- #top_level_pipes(start, finish) ⇒ Array[Integer]
- #working_tokens ⇒ Array[Token]
Constructor Details
#initialize(tokens, mode:, max_diagnostics:) ⇒ DiagnosticRecovery
Returns a new instance of DiagnosticRecovery.
18 19 20 21 22 23 24 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 18 def initialize(tokens, mode:, max_diagnostics:) @tokens = tokens @mode = mode @max_diagnostics = max_diagnostics @diagnostics = [] #: Array[Diagnostic] @suppressed = {} #: Hash[Integer, bool] end |
Instance Method Details
#alternative_range(failure, start, finish) ⇒ Range[Integer]?
199 200 201 202 203 204 205 206 207 208 209 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 199 def alternative_range(failure, start, finish) colon = ((start + 1)...finish).find { |index| @tokens[index]&.type == :":" } return unless colon pipes = top_level_pipes(colon + 1, finish) return if pipes.empty? previous = pipes.reverse.find { |index| index < failure } following = pipes.find { |index| index >= failure } alternative_slice(previous, following, colon, finish) end |
#alternative_slice(previous, following, colon, finish) ⇒ Range[Integer]?
212 213 214 215 216 217 218 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 212 def alternative_slice(previous, following, colon, finish) if previous following ? (previous...following) : (previous...finish) elsif following (colon + 1)..following end end |
#append_diagnostic(diagnostic) ⇒ void
This method returns an undefined value.
61 62 63 64 65 66 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 61 def append_diagnostic(diagnostic) key = diagnostic_key(diagnostic) return if @diagnostics.any? { |existing| diagnostic_key(existing) == key } @diagnostics << diagnostic end |
#declaration_finish(start, rule_marker, starts) ⇒ Integer
134 135 136 137 138 139 140 141 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 134 def declaration_finish(start, rule_marker, starts) value = @tokens.fetch(start).value return matching_declaration_end(start, rule_marker, value == "prechigh" ? "preclow" : "prechigh") if %w[prechigh preclow].include?(value) return matching_declaration_end(start, rule_marker, "end") if %w[convert lexer].include?(value) starts.find { |index| index > start } || rule_marker end |
#declaration_range(failure, rule_marker) ⇒ Range[Integer]?
118 119 120 121 122 123 124 125 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 118 def declaration_range(failure, rule_marker) starts = (0...rule_marker).select { |index| declaration_start?(index) } start = starts.reverse.find { |index| index <= failure } return unless start finish = declaration_finish(start, rule_marker, starts) start...finish end |
#declaration_start?(index) ⇒ Boolean
128 129 130 131 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 128 def declaration_start?(index) token = @tokens[index] token&.type == :identifier && DECLARATION_STARTS.include?(token.value) end |
#diagnostic_from(error, token, phase:) ⇒ Diagnostic
245 246 247 248 249 250 251 252 253 254 255 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 245 def diagnostic_from(error, token, phase:) matching = @tokens.find { |candidate| error..start_with?("#{candidate.location}:") } token = matching || token || @tokens.last location = token&.location || Location.new(file: "(grammar)", line: 1, column: 1) prefix = "#{location}: " = error..delete_prefix(prefix) expected, received = expected_and_received() Diagnostic.new(code: "frontend.#{phase}_error", phase: phase, message: , location: location, span: token&.span, expected: expected, received: received, rendered: error.) end |
#diagnostic_key(diagnostic) ⇒ [ String, String, Integer, Integer, String ]
69 70 71 72 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 69 def diagnostic_key(diagnostic) location = diagnostic.location [diagnostic.phase.to_s, location.file, location.line, location.column, diagnostic.] end |
#diagnostic_sort_key(diagnostic) ⇒ [ String, Integer, Integer, String, String ]
83 84 85 86 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 83 def diagnostic_sort_key(diagnostic) location = diagnostic.location [location.file, location.line, location.column, diagnostic.phase.to_s, diagnostic.code] end |
#expected_and_received(message) ⇒ [ Array[String], String? ]
258 259 260 261 262 263 264 265 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 258 def expected_and_received() match = .match(/\Aexpected (.+), got (.+)\z/) return [[], nil] unless match expected = match[1] received = match[2] [expected ? [expected] : [], received] end |
#grammar_end_index(rule_marker) ⇒ Integer
161 162 163 164 165 166 167 168 169 170 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 161 def grammar_end_index(rule_marker) depth = 0 ((rule_marker + 1)...@tokens.length).each do |index| token = @tokens.fetch(index) depth += 1 if token.type == :"(" depth -= 1 if token.type == :")" && depth.positive? return index if depth.zero? && token.type == :identifier && token.value == "end" end @tokens.length - 1 end |
#lhs_candidate?(index, lhs_column) ⇒ Boolean
190 191 192 193 194 195 196 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 190 def lhs_candidate?(index, lhs_column) token = @tokens[index] following = @tokens[index + 1] return false unless token&.type == :identifier && following&.type == :":" lhs_column.nil? || token.location.column <= lhs_column end |
#matching_declaration_end(start, fallback, closer) ⇒ Integer
144 145 146 147 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 144 def matching_declaration_end(start, fallback, closer) index = ((start + 1)...fallback).find { |candidate| @tokens[candidate]&.value == closer } index ? index + 1 : fallback end |
#parse ⇒ [ AST::Root | AST::Fragment | nil, Array[Diagnostic] ]
27 28 29 30 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 27 def parse ast = parse_until_stable [ast, sorted_diagnostics] end |
#parse_until_stable ⇒ AST::Root, ...
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 35 def parse_until_stable attempts = 0 loop do parser = GeneratedParser.new(working_tokens, mode: @mode) begin return parser.parse rescue Ibex::Error => e return if @diagnostics.length >= @max_diagnostics diagnostic = diagnostic_from(e, parser.diagnostic_token, phase: :syntax) append_diagnostic(diagnostic) range = suppression_range(parser.diagnostic_token, e) return unless range && suppress(range) attempts += 1 return if attempts >= @tokens.length end end end |
#rule_definition_starts(rule_marker, grammar_end) ⇒ Array[Integer]
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 173 def rule_definition_starts(rule_marker, grammar_end) starts = [] #: Array[Integer] depth = 0 lhs_column = nil #: Integer? ((rule_marker + 1)...grammar_end).each do |index| token = @tokens.fetch(index) depth += 1 if token.type == :"(" depth -= 1 if token.type == :")" && depth.positive? next unless depth.zero? && lhs_candidate?(index, lhs_column) lhs_column ||= token.location.column starts << index end starts end |
#rule_marker_index ⇒ Integer?
113 114 115 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 113 def rule_marker_index @tokens.index { |token| token.type == :identifier && token.value == "rule" } end |
#rule_range(failure, rule_marker) ⇒ Range[Integer]?
150 151 152 153 154 155 156 157 158 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 150 def rule_range(failure, rule_marker) grammar_end = grammar_end_index(rule_marker) definitions = rule_definition_starts(rule_marker, grammar_end) start = definitions.reverse.find { |index| index <= failure } return unless start finish = definitions.find { |index| index > start } || grammar_end alternative_range(failure, start, finish) || (start...finish) end |
#sorted_diagnostics ⇒ Array[Diagnostic]
75 76 77 78 79 80 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 75 def sorted_diagnostics @diagnostics.uniq { |diagnostic| diagnostic_key(diagnostic) } .sort_by { |diagnostic| diagnostic_sort_key(diagnostic) } .first(@max_diagnostics) .freeze end |
#suppress(range) ⇒ Boolean
232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 232 def suppress(range) changed = false range.each do |index| next if index.negative? || index >= @tokens.length || @tokens[index]&.type == :eof next if @suppressed[index] @suppressed[index] = true changed = true end changed end |
#suppression_range(token, error) ⇒ Range[Integer]?
89 90 91 92 93 94 95 96 97 98 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 89 def suppression_range(token, error) index = token_index(token) || (error.) return unless index rule_marker = rule_marker_index return declaration_range(index, rule_marker) if rule_marker && index < rule_marker return rule_range(index, rule_marker) if rule_marker nil end |
#token_index(token) ⇒ Integer?
101 102 103 104 105 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 101 def token_index(token) return unless token @tokens.index { |candidate| candidate.equal?(token) } end |
#token_index_for_message(message) ⇒ Integer?
108 109 110 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 108 def () @tokens.index { |token| .start_with?("#{token.location}:") } end |
#top_level_pipes(start, finish) ⇒ Array[Integer]
221 222 223 224 225 226 227 228 229 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 221 def top_level_pipes(start, finish) depth = 0 (start...finish).filter_map do |index| token = @tokens.fetch(index) depth += 1 if token.type == :"(" depth -= 1 if token.type == :")" && depth.positive? index if depth.zero? && token.type == :| end end |
#working_tokens ⇒ Array[Token]
56 57 58 |
# File 'lib/ibex/frontend/diagnostic_recovery.rb', line 56 def working_tokens @tokens.each_with_index.filter_map { |token, index| token unless @suppressed[index] } end |