класс SyntaxSuggest::CodeFrontier
В алгоритме есть три основных этапа:
-
Очистка/форматирование исходного кода
-
Поиск невалидных блоков
-
Форматирование невалидных блоков в осмысленный вид
Граница кода является критической частью второго шага
## Где мы были
После создания блока кода он добавляется на границу. Затем он сортируется по отступу, и граница может быть отфильтрована. Крупные блоки, полностью охватывающие меньший блок, приведут к удалению меньшего блока.
CodeFrontier#<<(block) # Adds block to frontier CodeFrontier#pop # Removes block from frontier
## Куда мы можем пойти
Внутри граница отслеживает «непосещённые» строки, доступные через `next_indent_line` при вызове. Этот метод возвращает строку кода с максимальным отступом.
Возвращённая строка кода может использоваться для создания CodeBlock, а затем этот блок кода добавляется обратно на границу. Затем строки удаляются из «непосещённых», чтобы мы не создавали один и тот же блок дважды.
CodeFrontier#next_indent_line # Shows next line CodeFrontier#register_indent_block(block) # Removes lines from unvisited
## Когда останавливаться
Граница знает, как проверять весь документ на синтаксические ошибки. Когда блоки добавляются на границу, они удаляются из документа. Когда все блоки кода, содержащие синтаксические ошибки, добавлены на границу, документ будет парситься без синтаксических ошибок, и поиск можно остановить.
CodeFrontier#holds_all_syntax_errors? # Returns true when frontier holds all syntax errors
## Фильтрация ложных срабатываний
После завершения поиска на границе могут быть несколько блоков, которые не содержат синтаксическую ошибку. Чтобы ограничить результат наименьшим подмножеством «невалидных блоков», вызовите:
CodeFrontier#detect_invalid_blocks
Методы класса (public)
# File lib/syntax_suggest/code_frontier.rb, line 162
def self.combination(array)
guesses = []
1.upto(array.length).each do |size|
guesses.concat(array.combination(size).to_a)
end
guesses
end Пример:
combination([:a, :b, :c, :d]) # => [[:a], [:b], [:c], [:d], [:a, :b], [:a, :c], [:a, :d], [:b, :c], [:b, :d], [:c, :d], [:a, :b, :c], [:a, :b, :d], [:a, :c, :d], [:b, :c, :d], [:a, :b, :c, :d]]
# File lib/syntax_suggest/code_frontier.rb, line 53 def initialize(code_lines:, unvisited: UnvisitedLines.new(code_lines: code_lines)) @code_lines = code_lines @unvisited = unvisited @queue = PriorityEngulfQueue.new @check_next = true end
Методы экземпляра (public)
# File lib/syntax_suggest/code_frontier.rb, line 148 def <<(block) @unvisited.visit_block(block) @queue.push(block) @check_next = true if block.invalid? self end
Добавление блока на границу
Этот метод гарантирует, что граница всегда отсортирована (по порядку отступов) и что строки каждого блока кода удаляются из хэша отступов, чтобы мы не переоценивали одну и ту же строку несколько раз.
# File lib/syntax_suggest/code_frontier.rb, line 61 def count @queue.length end
# File lib/syntax_suggest/code_frontier.rb, line 172
def detect_invalid_blocks
self.class.combination(@queue.to_a.select(&:invalid?)).detect do |block_array|
holds_all_syntax_errors?(block_array, can_cache: false)
end || []
end Учитывая, что мы знаем, что синтаксическая ошибка существует где-то в нашей границе, мы хотим найти наименьший возможный набор блоков, содержащих все синтаксические ошибки.
# File lib/syntax_suggest/code_frontier.rb, line 111
def expand?
return false if @queue.empty?
return true if @unvisited.empty?
frontier_indent = @queue.peek.current_indent
unvisited_indent = next_indent_line.indent
if ENV["SYNTAX_SUGGEST_DEBUG"]
puts "```"
puts @queue.peek.to_s
puts "```"
puts " @frontier indent: #{frontier_indent}"
puts " @unvisited indent: #{unvisited_indent}"
end
# Expand all blocks before moving to unvisited lines
frontier_indent >= unvisited_indent
end # File lib/syntax_suggest/code_frontier.rb, line 89
def holds_all_syntax_errors?(block_array = @queue, can_cache: true)
return false if can_cache && can_skip_check?
without_lines = block_array.to_a.flat_map do |block|
block.lines
end
SyntaxSuggest.valid_without?(
without_lines: without_lines,
code_lines: @code_lines
)
end Возвращает true, если документ валиден со всеми удалёнными строками. По умолчанию проверяет все блоки, присутствующие в массиве границы, но также может быть использован для произвольных массивов блоков кода.
# File lib/syntax_suggest/code_frontier.rb, line 107 def next_indent_line @unvisited.peek end
# File lib/syntax_suggest/code_frontier.rb, line 103 def pop @queue.pop end
Возвращает блок кода с наибольшим возможным отступом.
# File lib/syntax_suggest/code_frontier.rb, line 140 def register_engulf_block(block) end
Когда один элемент полностью охватывает другой, мы удаляем меньший блок с границы. Это предотвращает двойные расширения и всяческое странное поведение. Однако эта гарантия довольно дорогостоящая.
# File lib/syntax_suggest/code_frontier.rb, line 132 def register_indent_block(block) @unvisited.visit_block(block) self end
Отслеживает, какие строки были добавлены в блоки и какие ещё не посещены.
Методы экземпляра (private)
# File lib/syntax_suggest/code_frontier.rb, line 74
def can_skip_check?
check_next = @check_next
@check_next = false
if check_next
false
else
true
end
end Оптимизация производительности
Парсинг с помощью риппера дорогостоящий. Если мы знаем, что у нас нет блоков с невалидным синтаксисом, то мы знаем, что ещё не нашли неправильный синтаксис.
Когда невалидный блок добавляется на границу, проверяется состояние документа.
Ruby Core © 1993–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.