class SyntaxSuggest::CodeFrontier
算法有三个主要阶段
-
净化/格式化输入源码
-
搜索无效代码块
-
将无效代码块格式化为有意义的内容
Code frontier 是第二步的关键部分
## 了解我们去过哪里
一旦生成了一个代码块,它就会被添加到 frontier。然后,它将按缩进排序,并且 frontier 可以被过滤。完全包含较小块的大块将导致较小块被移除。
CodeFrontier#<<(block) # Adds block to frontier CodeFrontier#pop # Removes block from frontier
## 了解我们可以去哪里
在内部,frontier 会跟踪“未访问”的行,这些行可以通过调用 `next_indent_line` 来暴露。此方法返回具有最高缩进的代码行。
返回的代码行可用于构建一个 CodeBlock,然后将该代码块添加回 frontier。然后,从“未访问”中移除这些行,以免我们重复创建相同的块。
CodeFrontier#next_indent_line # Shows next line CodeFrontier#register_indent_block(block) # Removes lines from unvisited
## 了解何时停止
frontier 知道如何检查整个文档是否存在语法错误。当块被添加到 frontier 时,它们将从文档中移除。当所有包含语法错误的代码都已添加到 frontier 中时,文档将可以解析而不会出现语法错误,搜索就可以停止。
CodeFrontier#holds_all_syntax_errors? # Returns true when frontier holds all syntax errors
## 过滤误报
搜索完成后,frontier 可能有多个块不包含语法错误。要将结果限制为“无效块”的最小子集,请调用
CodeFrontier#detect_invalid_blocks
Public Class Methods
Source
# 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]]
Source
# 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 Instance Methods
Source
# 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
将块添加到 frontier
此方法确保 frontier 始终保持排序(按缩进顺序),并且每个代码块的行都会从缩进哈希中移除,以免我们重复评估同一行。
Source
# 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
鉴于我们知道语法错误存在于我们的 frontier 中,我们希望找到包含所有语法错误的最小可能块集
Source
# 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
Source
# 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。默认情况下,它会检查 frontier 数组中存在的所有块,但也可以用于任意代码块数组
Source
# File lib/syntax_suggest/code_frontier.rb, line 107 def next_indent_line @unvisited.peek end
Source
# File lib/syntax_suggest/code_frontier.rb, line 103 def pop @queue.pop end
返回一个缩进程度最高的代码块
Source
# File lib/syntax_suggest/code_frontier.rb, line 140 def register_engulf_block(block) end
当一个元素完全包含另一个元素时,我们从 frontier 中移除较小的块。这可以防止重复扩展和各种奇怪的行为。但是,维护此保证的成本相当高
Source
# File lib/syntax_suggest/code_frontier.rb, line 132 def register_indent_block(block) @unvisited.visit_block(block) self end
跟踪哪些行已添加到块中,哪些尚未访问。
私有实例方法
Source
# 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 进行解析成本很高。如果我们知道没有任何块存在无效语法,那么我们就知道我们还没有找到不正确的语法。
当无效块添加到 frontier 时,检查文档状态