diff --git a/lib/dead_end.rb b/lib/dead_end.rb index f93a276..566e7d0 100644 --- a/lib/dead_end.rb +++ b/lib/dead_end.rb @@ -147,6 +147,7 @@ def self.valid?(source) require_relative "dead_end/lex_all" require_relative "dead_end/block_expand" +require_relative "dead_end/insertion_sort" require_relative "dead_end/around_block_scan" require_relative "dead_end/ripper_errors" require_relative "dead_end/display_invalid_blocks" diff --git a/lib/dead_end/code_frontier.rb b/lib/dead_end/code_frontier.rb index e068a0b..d33f154 100644 --- a/lib/dead_end/code_frontier.rb +++ b/lib/dead_end/code_frontier.rb @@ -52,14 +52,14 @@ module DeadEnd class CodeFrontier def initialize(code_lines:) @code_lines = code_lines - @frontier = [] + @frontier = InsertionSort.new @unvisited_lines = @code_lines.sort_by(&:indent_index) @has_run = false @check_next = true end def count - @frontier.count + @frontier.to_a.length end # Performance optimization @@ -89,7 +89,7 @@ def count def holds_all_syntax_errors?(block_array = @frontier, can_cache: true) return false if can_cache && can_skip_check? - without_lines = block_array.flat_map do |block| + without_lines = block_array.to_a.flat_map do |block| block.lines end @@ -101,7 +101,7 @@ def holds_all_syntax_errors?(block_array = @frontier, can_cache: true) # Returns a code block with the largest indentation possible def pop - @frontier.pop + @frontier.to_a.pop end def next_indent_line @@ -109,15 +109,15 @@ def next_indent_line end def expand? - return false if @frontier.empty? - return true if @unvisited_lines.empty? + return false if @frontier.to_a.empty? + return true if @unvisited_lines.to_a.empty? - frontier_indent = @frontier.last.current_indent + frontier_indent = @frontier.to_a.last.current_indent unvisited_indent = next_indent_line.indent if ENV["DEBUG"] puts "```" - puts @frontier.last.to_s + puts @frontier.to_a.last.to_s puts "```" puts " @frontier indent: #{frontier_indent}" puts " @unvisited indent: #{unvisited_indent}" @@ -141,13 +141,13 @@ def <<(block) register_indent_block(block) # Make sure we don't double expand, if a code block fully engulfs another code block, keep the bigger one - @frontier.reject! { |b| + @frontier.to_a.reject! { |b| b.starts_at >= block.starts_at && b.ends_at <= block.ends_at } @check_next = true if block.invalid? @frontier << block - @frontier.sort! + # @frontier.sort! self end @@ -167,7 +167,7 @@ def self.combination(array) # Given that we know our syntax error exists somewhere in our frontier, we want to find # the smallest possible set of blocks that contain all the syntax errors def detect_invalid_blocks - self.class.combination(@frontier.select(&:invalid?)).detect do |block_array| + self.class.combination(@frontier.to_a.select(&:invalid?)).detect do |block_array| holds_all_syntax_errors?(block_array, can_cache: false) end || [] end diff --git a/lib/dead_end/insertion_sort.rb b/lib/dead_end/insertion_sort.rb new file mode 100644 index 0000000..6dbadf8 --- /dev/null +++ b/lib/dead_end/insertion_sort.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module DeadEnd + # Sort elements on insert + # + # Instead of constantly calling `sort!`, put + # the element where it belongs the first time + # around + # + # Example: + # + # sorted = InsertionSort.new + # sorted << 33 + # sorted << 44 + # sorted << 1 + # puts sorted.to_a + # # => [1, 44, 33] + # + class InsertionSort + def initialize + @array = [] + end + + def <<(value) + insert_in = @array.length + @array.each.with_index do |existing, index| + case value <=> existing + when -1 + insert_in = index + break + when 0 + insert_in = index + break + when 1 + # Keep going + end + end + + @array.insert(insert_in, value) + end + + def to_a + @array + end + end +end diff --git a/spec/unit/insertion_sort_spec.rb b/spec/unit/insertion_sort_spec.rb new file mode 100644 index 0000000..e2987ae --- /dev/null +++ b/spec/unit/insertion_sort_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require_relative "../spec_helper" + +module DeadEnd + class CurrentIndex + attr_reader :current_indent + + def initialize(value) + @current_indent = value + end + + def <=>(other) + @current_indent <=> other.current_indent + end + end + + RSpec.describe CodeFrontier do + it "works manually" do + frontier = InsertionSort.new + frontier << CurrentIndex.new(0) + frontier << CurrentIndex.new(1) + + expect(frontier.to_a.map(&:current_indent)).to eq([0, 1]) + + frontier << CurrentIndex.new(1) + expect(frontier.to_a.map(&:current_indent)).to eq([0, 1, 1]) + + frontier << CurrentIndex.new(0) + expect(frontier.to_a.map(&:current_indent)).to eq([0, 0, 1, 1]) + + frontier << CurrentIndex.new(10) + expect(frontier.to_a.map(&:current_indent)).to eq([0, 0, 1, 1, 10]) + + frontier << CurrentIndex.new(2) + expect(frontier.to_a.map(&:current_indent)).to eq([0, 0, 1, 1, 2, 10]) + end + + it "handles lots of values" do + frontier = InsertionSort.new + values = [18, 18, 0, 18, 0, 18, 18, 18, 18, 16, 18, 8, 18, 8, 8, 8, 16, 6, 0, 0, 16, 16, 4, 14, 14, 12, 12, 12, 10, 12, 12, 12, 12, 8, 10, 10, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 8, 10, 6, 6, 6, 6, 6, 6, 8, 10, 8, 8, 10, 8, 10, 8, 10, 8, 6, 8, 8, 6, 8, 6, 6, 8, 0, 8, 0, 0, 8, 8, 0, 8, 0, 8, 8, 0, 8, 8, 8, 0, 8, 0, 8, 8, 8, 8, 8, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 8, 6, 8, 6, 6, 6, 6, 8, 6, 8, 6, 6, 4, 4, 6, 6, 4, 6, 4, 6, 6, 4, 6, 4, 4, 6, 6, 6, 6, 4, 4, 4, 2, 4, 4, 4, 4, 4, 4, 6, 6, 0, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 0, 0, 6, 6, 2] + values.each do |v| + frontier << CurrentIndex.new(v) + end + + expect(frontier.to_a.map(&:current_indent)).to eq(values.sort) + end + end +end