class 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
Открытые методы класса
# 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
Открытые методы экземпляра
# 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
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
Отслеживает, какие строки уже добавлены в блоки, а какие ещё не посещены.
Закрытые методы экземпляра
# 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 Оптимизация производительности
Разбор с помощью ripper требует больших затрат. Если мы знаем, что у нас нет блоков с некорректным синтаксисом, значит, мы ещё не могли обнаружить ошибочный синтаксис.
Когда некорректный блок добавляется во фронтир, проверяется состояние документа.
Ruby Core © 1993–2025 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.