class SyntaxSuggest::CodeFrontier

该算法主要分为三个阶段

  1. 清理/格式化输入源

  2. 搜索无效代码块

  3. 将无效代码块格式化为有意义的内容

代码前沿是第二步的关键部分

## 了解我们去过哪里

一旦生成代码块,它就会被添加到前沿。然后它将按缩进排序,并且可以过滤前沿。完全包围较小代码块的大代码块会导致较小的代码块被移除。

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

公共类方法

combination(array) 点击切换源代码

示例

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 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
new(code_lines:, unvisited: UnvisitedLines.new(code_lines: code_lines)) 点击切换源代码
# File 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

公共实例方法

<<(block) 点击切换源代码

将代码块添加到前沿

此方法确保前沿始终保持排序(按缩进顺序),并且每个代码块的行都从缩进哈希中删除,这样我们就不会多次重新评估同一行。

# File syntax_suggest/code_frontier.rb, line 148
def <<(block)
  @unvisited.visit_block(block)

  @queue.push(block)

  @check_next = true if block.invalid?

  self
end
count() 点击切换源代码
# File syntax_suggest/code_frontier.rb, line 61
def count
  @queue.length
end
detect_invalid_blocks() 点击切换源代码

假设我们知道语法错误存在于我们的前沿的某个位置,我们希望找到包含所有语法错误的最小可能代码块集

# File 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
expand?() 点击切换源代码
# File 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
holds_all_syntax_errors?(block_array = @queue, can_cache: true) 点击切换源代码

如果删除所有行后文档有效,则返回 true。默认情况下,它会检查前沿数组中存在的所有代码块,但也可以用于任意代码块数组

# File 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
next_indent_line() 点击切换源代码
# File syntax_suggest/code_frontier.rb, line 107
def next_indent_line
  @unvisited.peek
end
pop() 点击切换源代码

返回具有最大可能缩进的代码块

# File syntax_suggest/code_frontier.rb, line 103
def pop
  @queue.pop
end
register_engulf_block(block) 点击切换源代码

当一个元素完全包含另一个元素时,我们从前沿删除较小的代码块。这可以防止重复扩展和各种奇怪的行为。然而,维护此保证的成本很高

# File syntax_suggest/code_frontier.rb, line 140
def register_engulf_block(block)
end
register_indent_block(block) 点击切换源代码

跟踪哪些行已添加到代码块,哪些行尚未访问。

# File syntax_suggest/code_frontier.rb, line 132
def register_indent_block(block)
  @unvisited.visit_block(block)
  self
end

私有实例方法

can_skip_check?() 点击切换源代码

性能优化

使用 ripper 解析的成本很高。如果我们知道没有任何代码块具有无效语法,那么我们就知道我们还没有找到不正确的语法。

当一个无效代码块被添加到前沿时,检查文档状态

# File 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