类 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
公共类方法
示例
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 162 def self.combination(array) guesses = [] 1.upto(array.length).each do |size| guesses.concat(array.combination(size).to_a) end guesses end
# 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
如果文档在删除所有行后仍然有效,则返回 true。默认情况下,它检查边界数组中存在的所有代码块,但也可以用于任意代码块数组。
# 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
# 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
私有实例方法
性能优化
使用 ripper 进行解析非常昂贵。如果我们知道没有任何代码块包含无效语法,那么我们就知道还没有找到错误的语法。
当将无效代码块添加到边界时,检查文档状态。
# 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