類別 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
公開類別方法
範例
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
公開實例方法
將區塊加入 frontier
此方法確保邊界始終保持排序(依縮排順序),且每個程式區塊的行都會從縮排雜湊中移除,因此我們不會重複評估同一行多次。
# 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