From 97a9ee742ff83ad2d12edc3f70b5ea1328368ead Mon Sep 17 00:00:00 2001 From: schneems Date: Wed, 16 Dec 2020 13:33:17 -0600 Subject: [PATCH 1/5] Memoize CodeBlock#valid? --- lib/dead_end/code_block.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/dead_end/code_block.rb b/lib/dead_end/code_block.rb index 786fb9f..ee7df64 100644 --- a/lib/dead_end/code_block.rb +++ b/lib/dead_end/code_block.rb @@ -17,10 +17,12 @@ module DeadEnd # # class CodeBlock + UNSET = Object.new.freeze attr_reader :lines def initialize(lines: []) @lines = Array(lines) + @valid = UNSET end def visible_lines @@ -68,7 +70,8 @@ def invalid? end def valid? - DeadEnd.valid?(self.to_s) + return @valid if @valid != UNSET + @valid = DeadEnd.valid?(self.to_s) end def to_s From abdbe935af61292de0ed9ea93920dd352d191d5f Mon Sep 17 00:00:00 2001 From: schneems Date: Wed, 16 Dec 2020 13:38:16 -0600 Subject: [PATCH 2/5] Remove unused methods from CodeLine --- lib/dead_end/code_line.rb | 9 --------- spec/unit/code_line_spec.rb | 14 -------------- 2 files changed, 23 deletions(-) diff --git a/lib/dead_end/code_line.rb b/lib/dead_end/code_line.rb index 5c874b7..b633560 100644 --- a/lib/dead_end/code_line.rb +++ b/lib/dead_end/code_line.rb @@ -92,15 +92,6 @@ def is_end? @is_end end - def mark_invalid - @invalid = true - self - end - - def marked_invalid? - @invalid - end - def mark_invisible @line = "" self diff --git a/spec/unit/code_line_spec.rb b/spec/unit/code_line_spec.rb index 1ba22f3..ab5ff2c 100644 --- a/spec/unit/code_line_spec.rb +++ b/spec/unit/code_line_spec.rb @@ -41,20 +41,6 @@ module DeadEnd expect(line.is_kw?).to be_truthy end - it "can be marked as invalid or valid" do - code_lines = code_line_array(<<~EOM) - def foo - Array(value) |x| - end - end - EOM - - expect(code_lines[0].marked_invalid?).to be_falsey - code_lines[0].mark_invalid - expect(code_lines[0].marked_invalid?).to be_truthy - - end - it "ignores marked lines" do code_lines = code_line_array(<<~EOM) def foo From a2dde12a1ec502512331191f47ebcf731fc06ced Mon Sep 17 00:00:00 2001 From: schneems Date: Wed, 16 Dec 2020 14:40:00 -0600 Subject: [PATCH 3/5] Empty lines have zero indentation --- lib/dead_end/code_line.rb | 9 +++++++-- spec/unit/code_line_spec.rb | 9 +++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/dead_end/code_line.rb b/lib/dead_end/code_line.rb index b633560..8c6ea78 100644 --- a/lib/dead_end/code_line.rb +++ b/lib/dead_end/code_line.rb @@ -36,9 +36,14 @@ class CodeLine def initialize(line: , index:) @original_line = line.freeze @line = @original_line - @empty = line.strip.empty? + if line.strip.empty? + @empty = true + @indent = 0 + else + @empty = false + @indent = SpaceCount.indent(line) + end @index = index - @indent = SpaceCount.indent(line) @status = nil # valid, invalid, unknown @invalid = false diff --git a/spec/unit/code_line_spec.rb b/spec/unit/code_line_spec.rb index ab5ff2c..92e7bd5 100644 --- a/spec/unit/code_line_spec.rb +++ b/spec/unit/code_line_spec.rb @@ -96,5 +96,14 @@ def foo expect(code_lines.map(&:indent)).to eq([0, 2, 4, 2, 0]) end + + it "doesn't count empty lines as having an indentation" do + code_lines = code_line_array(<<~EOM) + + + EOM + + expect(code_lines.map(&:indent)).to eq([0, 0]) + end end end From d181e44928a95468f2ab378ce0b896b02946efcb Mon Sep 17 00:00:00 2001 From: schneems Date: Wed, 16 Dec 2020 14:40:27 -0600 Subject: [PATCH 4/5] Better example text --- spec/unit/code_search_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/unit/code_search_spec.rb b/spec/unit/code_search_spec.rb index 6e1fac1..048cc1a 100644 --- a/spec/unit/code_search_spec.rb +++ b/spec/unit/code_search_spec.rb @@ -277,7 +277,7 @@ def foo it "stacked ends 2" do search = CodeSearch.new(<<~EOM) - def lol + def cat blerg end @@ -285,7 +285,7 @@ def lol end # one end # two - def lol + def dog end EOM search.call From dbb85207b7343b8fb9991a942f097feaf3971b19 Mon Sep 17 00:00:00 2001 From: schneems Date: Wed, 16 Dec 2020 13:51:03 -0600 Subject: [PATCH 5/5] More efficient frontier unvisited line structure Previously we were keeping track of the lines that were not in the frontier via an @indent_hash with the keys as indentation and value as an array. This was inefficient and caused us to constantly be sorting the keys. Instead we can get the same information by only sorting once. As a bonus perf optimization CodeLine#indent_index generates a key for stable sort and memoizes it so it only has to be allocated once per line. --- CHANGELOG.md | 2 +- lib/dead_end/code_frontier.rb | 45 +++++++++++++---------------------- lib/dead_end/code_line.rb | 4 ++++ lib/dead_end/code_search.rb | 6 ++--- spec/unit/code_search_spec.rb | 2 +- 5 files changed, 25 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b49269..bff4b5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ ## HEAD (unreleased) -- Replace some references in tests with foobar style code (https://github.com/zombocom/dead_end/pull/38) +- Fix bug where empty lines were interpreted to have a zero indentation (https://github.com/zombocom/dead_end/pull/39) ## 1.0.1 diff --git a/lib/dead_end/code_frontier.rb b/lib/dead_end/code_frontier.rb index 1ba6291..4634159 100644 --- a/lib/dead_end/code_frontier.rb +++ b/lib/dead_end/code_frontier.rb @@ -10,20 +10,20 @@ module DeadEnd # sorted and then the frontier can be filtered. Large blocks that totally contain a # smaller block will cause the smaller block to be evicted. # - # CodeFrontier#<< - # CodeFrontier#pop + # CodeFrontier#<<(block) # Adds block to frontier + # CodeFrontier#pop # Removes block from frontier # # ## Knowing where we can go # - # Internally it keeps track of an "indent hash" which is exposed via `next_indent_line` + # Internally it keeps track of "unvisited" lines which is exposed via `next_indent_line` # when called this will return a line of code with the most indentation. # - # This line of code can be used to build a CodeBlock via and then when that code block - # is added back to the frontier, then the lines in the code block are removed from the - # indent hash so we don't double-create the same block. + # This line of code can be used to build a CodeBlock and then when that code block + # is added back to the frontier, then the lines are removed from the + # "unvisited" so we don't double-create the same block. # - # CodeFrontier#next_indent_line - # CodeFrontier#register_indent_block + # CodeFrontier#next_indent_line # Shows next line + # CodeFrontier#register_indent_block(block) # Removes lines from unvisited # # ## Knowing when to stop # @@ -42,13 +42,7 @@ class CodeFrontier def initialize(code_lines: ) @code_lines = code_lines @frontier = [] - @indent_hash = {} - code_lines.each do |line| - next if line.empty? - - @indent_hash[line.indent] ||= [] - @indent_hash[line.indent] << line - end + @unvisited_lines = @code_lines.sort_by(&:indent_index) end def count @@ -75,38 +69,31 @@ def pop return @frontier.pop end - def indent_hash_indent - @indent_hash.keys.sort.last - end - def next_indent_line - indent = @indent_hash.keys.sort.last - @indent_hash[indent]&.first + @unvisited_lines.last end def expand? return false if @frontier.empty? - return true if @indent_hash.empty? + return true if @unvisited_lines.empty? frontier_indent = @frontier.last.current_indent - hash_indent = @indent_hash.keys.sort.last + unvisited_indent= next_indent_line.indent if ENV["DEBUG"] puts "```" puts @frontier.last.to_s puts "```" puts " @frontier indent: #{frontier_indent}" - puts " @hash indent: #{hash_indent}" + puts " @unvisited indent: #{unvisited_indent}" end - frontier_indent >= hash_indent + # Expand all blocks before moving to unvisited lines + frontier_indent >= unvisited_indent end def register_indent_block(block) - block.lines.each do |line| - @indent_hash[line.indent]&.delete(line) - end - @indent_hash.select! {|k, v| !v.empty?} + @unvisited_lines -= block.lines self end diff --git a/lib/dead_end/code_line.rb b/lib/dead_end/code_line.rb index 8c6ea78..86148f7 100644 --- a/lib/dead_end/code_line.rb +++ b/lib/dead_end/code_line.rb @@ -77,6 +77,10 @@ def trailing_slash? @is_trailing_slash end + def indent_index + @indent_index ||= [indent, index] + end + def <=>(b) self.index <=> b.index end diff --git a/lib/dead_end/code_search.rb b/lib/dead_end/code_search.rb index 7fc97e1..8c357b7 100644 --- a/lib/dead_end/code_search.rb +++ b/lib/dead_end/code_search.rb @@ -75,7 +75,7 @@ def push(block, name: ) record(block: block, name: name) if block.valid? - block.lines.each(&:mark_invisible) + block.mark_invisible frontier << block else frontier << block @@ -92,7 +92,7 @@ def sweep(block:, name: ) # Parses the most indented lines into blocks that are marked # and added to the frontier - def add_invalid_blocks + def visit_new_blocks max_indent = frontier.next_indent_line&.indent while (line = frontier.next_indent_line) && (line.indent == max_indent) @@ -145,7 +145,7 @@ def call if frontier.expand? expand_invalid_block else - add_invalid_blocks + visit_new_blocks end end diff --git a/spec/unit/code_search_spec.rb b/spec/unit/code_search_spec.rb index 048cc1a..9fec231 100644 --- a/spec/unit/code_search_spec.rb +++ b/spec/unit/code_search_spec.rb @@ -445,7 +445,7 @@ def foo puts 'lol' end EOM - search.add_invalid_blocks + search.visit_new_blocks expect(search.code_lines.join).to eq(<<~EOM) def foo