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