dead_end 3.1.1 → 3.1.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64abeaee0636d3ded8df0f0dc124674a70f954287b37302dff7151f04b746557
4
- data.tar.gz: 02e6eea73a9e9e832b898f9294c1ce3f7f38f9b8996554a8f6527abf7027d115
3
+ metadata.gz: f35aa207ae1ff3904d39f5c0ddca5b8a0f68ae157f5c0a999cd3f2f1530e461b
4
+ data.tar.gz: b08307eb152fc12b84493409bfd8a9b8f23905180ad5ed9168a58d152913bc6e
5
5
  SHA512:
6
- metadata.gz: e7381458f44ef92f5053ac3171ac8bc59e89a805d793dde1c48ce1eda855dcea624ef6dc7a1faa3da9c20abc53983675fc66a7b08e3e02893801b4b4ff0edf65
7
- data.tar.gz: 8b92dee56fac32777f31b76e25a0b778b2a4ffa1ff6914f24ef835bbf5ab79898f4a3a5607a9bebc641f86b05f2a88224504f0f8aaab83c3262b1d067ea32af0
6
+ metadata.gz: 02b4ec64732eea9dbb55594c130d2fade4838a855bf3366408a5846683c3d2866e21844c7d3a2797adc865e177e7edf3bffaad0b87f555627f75fcac3d2aa219
7
+ data.tar.gz: '029add7304c46c97a8a04b564f5798a2e5d43afe24472ae51268226e2dd5e71c56b9c48bf889232d4c3f2759b6edb7ea4c4f332688d6995ddaac0f7ec8ae5803'
data/.circleci/config.yml CHANGED
@@ -51,7 +51,18 @@ jobs:
51
51
  steps:
52
52
  - checkout
53
53
  - ruby/install:
54
- version: '3.1.0-preview1'
54
+ version: '3.1.2'
55
+ - run: ruby -v
56
+ - ruby/install-deps
57
+ - <<: *unit
58
+
59
+ "ruby-3-2":
60
+ docker:
61
+ - image: 'cimg/base:stable'
62
+ steps:
63
+ - checkout
64
+ - ruby/install:
65
+ version: '3.2.0-preview1'
55
66
  - run: ruby -v
56
67
  - ruby/install-deps
57
68
  - <<: *unit
@@ -73,4 +84,5 @@ workflows:
73
84
  - "ruby-2-7"
74
85
  - "ruby-3-0"
75
86
  - "ruby-3-1"
87
+ - "ruby-3-2"
76
88
  - "lint"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,9 @@
1
1
  ## HEAD (unreleased)
2
2
 
3
+ ## 3.1.2
4
+
5
+ - Fixed internal class AroundBlockScan, minor changes in outputs (https://github.com/zombocom/dead_end/pull/131)
6
+
3
7
  ## 3.1.1
4
8
 
5
9
  - Fix case where Ripper lexing identified incorrect code as a keyword (https://github.com/zombocom/dead_end/pull/122)
data/Gemfile CHANGED
@@ -10,3 +10,5 @@ gem "rspec", "~> 3.0"
10
10
  gem "stackprof"
11
11
  gem "standard"
12
12
  gem "ruby-prof"
13
+
14
+ gem "benchmark-ips"
data/Gemfile.lock CHANGED
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dead_end (3.1.1)
4
+ dead_end (3.1.2)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  ast (2.4.2)
10
+ benchmark-ips (2.9.2)
10
11
  diff-lcs (1.4.4)
11
12
  parallel (1.21.0)
12
13
  parser (3.0.2.0)
@@ -54,6 +55,7 @@ PLATFORMS
54
55
  ruby
55
56
 
56
57
  DEPENDENCIES
58
+ benchmark-ips
57
59
  dead_end!
58
60
  rake (~> 12.0)
59
61
  rspec (~> 3.0)
@@ -62,4 +64,4 @@ DEPENDENCIES
62
64
  standard
63
65
 
64
66
  BUNDLED WITH
65
- 2.3.4
67
+ 2.3.14
data/README.md CHANGED
@@ -168,7 +168,7 @@ Here's an example:
168
168
 
169
169
  ## Use internals
170
170
 
171
- To use the `dead_end` gem without monkeypatching you can `require 'dead_en/api'`. This will allow you to load `dead_end` and use its internals without mutating `require`.
171
+ To use the `dead_end` gem without monkeypatching you can `require 'dead_end/api'`. This will allow you to load `dead_end` and use its internals without mutating `require`.
172
172
 
173
173
  Stable internal interface(s):
174
174
 
data/dead_end.gemspec CHANGED
@@ -1,6 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/dead_end/version"
3
+ begin
4
+ require_relative "lib/dead_end/version"
5
+ rescue LoadError # Fallback to load version file in ruby core repository
6
+ require_relative "version"
7
+ end
4
8
 
5
9
  Gem::Specification.new do |spec|
6
10
  spec.name = "dead_end"
data/lib/dead_end/api.rb CHANGED
@@ -59,7 +59,7 @@ module DeadEnd
59
59
  # DeadEnd.call [Private]
60
60
  #
61
61
  # Main private interface
62
- def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: nil, timeout: TIMEOUT_DEFAULT, io: $stderr)
62
+ def self.call(source:, filename: DEFAULT_VALUE, terminal: DEFAULT_VALUE, record_dir: DEFAULT_VALUE, timeout: TIMEOUT_DEFAULT, io: $stderr)
63
63
  search = nil
64
64
  filename = nil if filename == DEFAULT_VALUE
65
65
  Timeout.timeout(timeout) do
@@ -189,8 +189,10 @@ require_relative "code_line"
189
189
  require_relative "code_block"
190
190
  require_relative "block_expand"
191
191
  require_relative "ripper_errors"
192
- require_relative "insertion_sort"
192
+ require_relative "priority_queue"
193
+ require_relative "unvisited_lines"
193
194
  require_relative "around_block_scan"
195
+ require_relative "priority_engulf_queue"
194
196
  require_relative "pathname_from_message"
195
197
  require_relative "display_invalid_blocks"
196
198
  require_relative "parse_blocks_from_indent_line"
@@ -37,10 +37,20 @@ module DeadEnd
37
37
  @after_array = []
38
38
  @before_array = []
39
39
  @stop_after_kw = false
40
+
41
+ @skip_hidden = false
42
+ @skip_empty = false
40
43
  end
41
44
 
42
45
  def skip(name)
43
- @skip_array << name
46
+ case name
47
+ when :hidden?
48
+ @skip_hidden = true
49
+ when :empty?
50
+ @skip_empty = true
51
+ else
52
+ raise "Unsupported skip #{name}"
53
+ end
44
54
  self
45
55
  end
46
56
 
@@ -49,14 +59,15 @@ module DeadEnd
49
59
  self
50
60
  end
51
61
 
52
- def scan_while(&block)
62
+ def scan_while
53
63
  stop_next = false
54
64
 
55
65
  kw_count = 0
56
66
  end_count = 0
57
- @before_index = before_lines.reverse_each.take_while do |line|
67
+ index = before_lines.reverse_each.take_while do |line|
58
68
  next false if stop_next
59
- next true if @skip_array.detect { |meth| line.send(meth) }
69
+ next true if @skip_hidden && line.hidden?
70
+ next true if @skip_empty && line.empty?
60
71
 
61
72
  kw_count += 1 if line.is_kw?
62
73
  end_count += 1 if line.is_end?
@@ -64,15 +75,20 @@ module DeadEnd
64
75
  stop_next = true
65
76
  end
66
77
 
67
- block.call(line)
78
+ yield line
68
79
  end.last&.index
69
80
 
81
+ if index && index < before_index
82
+ @before_index = index
83
+ end
84
+
70
85
  stop_next = false
71
86
  kw_count = 0
72
87
  end_count = 0
73
- @after_index = after_lines.take_while do |line|
88
+ index = after_lines.take_while do |line|
74
89
  next false if stop_next
75
- next true if @skip_array.detect { |meth| line.send(meth) }
90
+ next true if @skip_hidden && line.hidden?
91
+ next true if @skip_empty && line.empty?
76
92
 
77
93
  kw_count += 1 if line.is_kw?
78
94
  end_count += 1 if line.is_end?
@@ -80,8 +96,12 @@ module DeadEnd
80
96
  stop_next = true
81
97
  end
82
98
 
83
- block.call(line)
99
+ yield line
84
100
  end.last&.index
101
+
102
+ if index && index > after_index
103
+ @after_index = index
104
+ end
85
105
  self
86
106
  end
87
107
 
@@ -178,7 +198,11 @@ module DeadEnd
178
198
  end
179
199
 
180
200
  def code_block
181
- CodeBlock.new(lines: @code_lines[before_index..after_index])
201
+ CodeBlock.new(lines: lines)
202
+ end
203
+
204
+ def lines
205
+ @code_lines[before_index..after_index]
182
206
  end
183
207
 
184
208
  def before_index
@@ -190,7 +214,7 @@ module DeadEnd
190
214
  end
191
215
 
192
216
  private def before_lines
193
- @code_lines[0...@orig_before_index] || []
217
+ @code_lines[0...before_index] || []
194
218
  end
195
219
 
196
220
  private def after_lines
@@ -36,7 +36,7 @@ module DeadEnd
36
36
  end
37
37
 
38
38
  def call(block)
39
- if (next_block = expand_neighbors(block, grab_empty: true))
39
+ if (next_block = expand_neighbors(block))
40
40
  return next_block
41
41
  end
42
42
 
@@ -51,25 +51,24 @@ module DeadEnd
51
51
  .code_block
52
52
  end
53
53
 
54
- def expand_neighbors(block, grab_empty: true)
55
- scan = AroundBlockScan.new(code_lines: @code_lines, block: block)
54
+ def expand_neighbors(block)
55
+ expanded_lines = AroundBlockScan.new(code_lines: @code_lines, block: block)
56
56
  .skip(:hidden?)
57
57
  .stop_after_kw
58
58
  .scan_neighbors
59
+ .scan_while { |line| line.empty? } # Slurp up empties
60
+ .lines
59
61
 
60
- # Slurp up empties
61
- if grab_empty
62
- scan = AroundBlockScan.new(code_lines: @code_lines, block: scan.code_block)
63
- .scan_while { |line| line.empty? || line.hidden? }
64
- end
65
-
66
- new_block = scan.code_block
67
-
68
- if block.lines == new_block.lines
62
+ if block.lines == expanded_lines
69
63
  nil
70
64
  else
71
- new_block
65
+ CodeBlock.new(lines: expanded_lines)
72
66
  end
73
67
  end
68
+
69
+ # Managable rspec errors
70
+ def inspect
71
+ "#<DeadEnd::CodeBlock:0x0000123843lol >"
72
+ end
74
73
  end
75
74
  end
@@ -18,11 +18,22 @@ module DeadEnd
18
18
  #
19
19
  class CodeBlock
20
20
  UNSET = Object.new.freeze
21
- attr_reader :lines
21
+ attr_reader :lines, :starts_at, :ends_at
22
22
 
23
23
  def initialize(lines: [])
24
24
  @lines = Array(lines)
25
25
  @valid = UNSET
26
+ @deleted = false
27
+ @starts_at = @lines.first.number
28
+ @ends_at = @lines.last.number
29
+ end
30
+
31
+ def delete
32
+ @deleted = true
33
+ end
34
+
35
+ def deleted?
36
+ @deleted
26
37
  end
27
38
 
28
39
  def visible_lines
@@ -41,14 +52,6 @@ module DeadEnd
41
52
  @lines.all?(&:hidden?)
42
53
  end
43
54
 
44
- def starts_at
45
- @starts_at ||= @lines.first&.line_number
46
- end
47
-
48
- def ends_at
49
- @ends_at ||= @lines.last&.line_number
50
- end
51
-
52
55
  # This is used for frontier ordering, we are searching from
53
56
  # the largest indentation to the smallest. This allows us to
54
57
  # populate an array with multiple code blocks then call `sort!`
@@ -50,18 +50,16 @@ module DeadEnd
50
50
  # CodeFrontier#detect_invalid_blocks
51
51
  #
52
52
  class CodeFrontier
53
- def initialize(code_lines:)
53
+ def initialize(code_lines:, unvisited: UnvisitedLines.new(code_lines: code_lines))
54
54
  @code_lines = code_lines
55
- @frontier = InsertionSort.new
56
- @unvisited_lines = @code_lines.sort_by(&:indent_index)
57
- @visited_lines = {}
55
+ @unvisited = unvisited
56
+ @queue = PriorityEngulfQueue.new
58
57
 
59
- @has_run = false
60
58
  @check_next = true
61
59
  end
62
60
 
63
61
  def count
64
- @frontier.to_a.length
62
+ @queue.length
65
63
  end
66
64
 
67
65
  # Performance optimization
@@ -88,7 +86,7 @@ module DeadEnd
88
86
  # removed. By default it checks all blocks in present in
89
87
  # the frontier array, but can be used for arbitrary arrays
90
88
  # of codeblocks as well
91
- def holds_all_syntax_errors?(block_array = @frontier, can_cache: true)
89
+ def holds_all_syntax_errors?(block_array = @queue, can_cache: true)
92
90
  return false if can_cache && can_skip_check?
93
91
 
94
92
  without_lines = block_array.to_a.flat_map do |block|
@@ -103,23 +101,23 @@ module DeadEnd
103
101
 
104
102
  # Returns a code block with the largest indentation possible
105
103
  def pop
106
- @frontier.to_a.pop
104
+ @queue.pop
107
105
  end
108
106
 
109
107
  def next_indent_line
110
- @unvisited_lines.last
108
+ @unvisited.peek
111
109
  end
112
110
 
113
111
  def expand?
114
- return false if @frontier.to_a.empty?
115
- return true if @unvisited_lines.to_a.empty?
112
+ return false if @queue.empty?
113
+ return true if @unvisited.empty?
116
114
 
117
- frontier_indent = @frontier.to_a.last.current_indent
115
+ frontier_indent = @queue.peek.current_indent
118
116
  unvisited_indent = next_indent_line.indent
119
117
 
120
118
  if ENV["DEBUG"]
121
119
  puts "```"
122
- puts @frontier.to_a.last.to_s
120
+ puts @queue.peek.to_s
123
121
  puts "```"
124
122
  puts " @frontier indent: #{frontier_indent}"
125
123
  puts " @unvisited indent: #{unvisited_indent}"
@@ -129,33 +127,30 @@ module DeadEnd
129
127
  frontier_indent >= unvisited_indent
130
128
  end
131
129
 
130
+ # Keeps track of what lines have been added to blocks and which are not yet
131
+ # visited.
132
132
  def register_indent_block(block)
133
- block.lines.each do |line|
134
- next if @visited_lines[line]
135
- @visited_lines[line] = true
136
-
137
- index = @unvisited_lines.bsearch_index { |l| line.indent_index <=> l.indent_index }
138
- @unvisited_lines.delete_at(index)
139
- end
133
+ @unvisited.visit_block(block)
140
134
  self
141
135
  end
142
136
 
137
+ # When one element fully encapsulates another we remove the smaller
138
+ # block from the frontier. This prevents double expansions and all-around
139
+ # weird behavior. However this guarantee is quite expensive to maintain
140
+ def register_engulf_block(block)
141
+ end
142
+
143
143
  # Add a block to the frontier
144
144
  #
145
145
  # This method ensures the frontier always remains sorted (in indentation order)
146
146
  # and that each code block's lines are removed from the indentation hash so we
147
147
  # don't re-evaluate the same line multiple times.
148
148
  def <<(block)
149
- register_indent_block(block)
149
+ @unvisited.visit_block(block)
150
150
 
151
- # Make sure we don't double expand, if a code block fully engulfs another code block, keep the bigger one
152
- @frontier.to_a.reject! { |b|
153
- b.starts_at >= block.starts_at && b.ends_at <= block.ends_at
154
- }
151
+ @queue.push(block)
155
152
 
156
153
  @check_next = true if block.invalid?
157
- @frontier << block
158
- # @frontier.sort!
159
154
 
160
155
  self
161
156
  end
@@ -175,7 +170,7 @@ module DeadEnd
175
170
  # Given that we know our syntax error exists somewhere in our frontier, we want to find
176
171
  # the smallest possible set of blocks that contain all the syntax errors
177
172
  def detect_invalid_blocks
178
- self.class.combination(@frontier.to_a.select(&:invalid?)).detect do |block_array|
173
+ self.class.combination(@queue.to_a.select(&:invalid?)).detect do |block_array|
179
174
  holds_all_syntax_errors?(block_array, can_cache: false)
180
175
  end || []
181
176
  end
@@ -41,7 +41,13 @@ module DeadEnd
41
41
 
42
42
  attr_reader :invalid_blocks, :record_dir, :code_lines
43
43
 
44
- def initialize(source, record_dir: ENV["DEAD_END_RECORD_DIR"] || ENV["DEBUG"] ? "tmp" : nil)
44
+ def initialize(source, record_dir: DEFAULT_VALUE)
45
+ record_dir = if record_dir == DEFAULT_VALUE
46
+ ENV["DEAD_END_RECORD_DIR"] || ENV["DEBUG"] ? "tmp" : nil
47
+ else
48
+ record_dir
49
+ end
50
+
45
51
  if record_dir
46
52
  @record_dir = DeadEnd.record_dir(record_dir)
47
53
  @write_count = 0
@@ -63,7 +69,7 @@ module DeadEnd
63
69
  def record(block:, name: "record")
64
70
  return unless @record_dir
65
71
  @name_tick[name] += 1
66
- filename = "#{@write_count += 1}-#{name}-#{@name_tick[name]}.txt"
72
+ filename = "#{@write_count += 1}-#{name}-#{@name_tick[name]}-(#{block.starts_at}__#{block.ends_at}).txt"
67
73
  if ENV["DEBUG"]
68
74
  puts "\n\n==== #{filename} ===="
69
75
  puts "\n```#{block.starts_at}..#{block.ends_at}"
@@ -78,7 +84,7 @@ module DeadEnd
78
84
  highlight_lines: block.lines
79
85
  ).call
80
86
 
81
- f.write(document)
87
+ f.write(" Block lines: #{block.starts_at..block.ends_at} (#{name}) \n\n#{document}")
82
88
  end
83
89
  end
84
90
 
@@ -89,25 +95,13 @@ module DeadEnd
89
95
  frontier << block
90
96
  end
91
97
 
92
- # Removes the block without putting it back in the frontier
93
- def sweep(block:, name:)
94
- record(block: block, name: name)
95
-
96
- block.lines.each(&:mark_invisible)
97
- frontier.register_indent_block(block)
98
- end
99
-
100
98
  # Parses the most indented lines into blocks that are marked
101
99
  # and added to the frontier
102
- def visit_new_blocks
100
+ def create_blocks_from_untracked_lines
103
101
  max_indent = frontier.next_indent_line&.indent
104
102
 
105
103
  while (line = frontier.next_indent_line) && (line.indent == max_indent)
106
-
107
104
  @parse_blocks_from_indent_line.each_neighbor_block(frontier.next_indent_line) do |block|
108
- record(block: block, name: "add")
109
-
110
- block.mark_invisible if block.valid?
111
105
  push(block, name: "add")
112
106
  end
113
107
  end
@@ -115,13 +109,12 @@ module DeadEnd
115
109
 
116
110
  # Given an already existing block in the frontier, expand it to see
117
111
  # if it contains our invalid syntax
118
- def expand_invalid_block
112
+ def expand_existing
119
113
  block = frontier.pop
120
114
  return unless block
121
115
 
122
- record(block: block, name: "pop")
116
+ record(block: block, name: "before-expand")
123
117
 
124
- # block = block.expand_until_next_boundry
125
118
  block = @block_expand.call(block)
126
119
  push(block, name: "expand")
127
120
  end
@@ -132,9 +125,9 @@ module DeadEnd
132
125
  @tick += 1
133
126
 
134
127
  if frontier.expand?
135
- expand_invalid_block
128
+ expand_existing
136
129
  else
137
- visit_new_blocks
130
+ create_blocks_from_untracked_lines
138
131
  end
139
132
  end
140
133
 
@@ -42,13 +42,18 @@ module DeadEnd
42
42
 
43
43
  neighbors = scan.code_block.lines
44
44
 
45
- until neighbors.empty?
46
- lines = [neighbors.pop]
47
- while (block = CodeBlock.new(lines: lines)) && block.invalid? && neighbors.any?
48
- lines.prepend neighbors.pop
49
- end
45
+ block = CodeBlock.new(lines: neighbors)
46
+ if neighbors.length <= 2 || block.valid?
47
+ yield block
48
+ else
49
+ until neighbors.empty?
50
+ lines = [neighbors.pop]
51
+ while (block = CodeBlock.new(lines: lines)) && block.invalid? && neighbors.any?
52
+ lines.prepend neighbors.pop
53
+ end
50
54
 
51
- yield block if block
55
+ yield block if block
56
+ end
52
57
  end
53
58
  end
54
59
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeadEnd
4
+ # Keeps track of what elements are in the queue in
5
+ # priority and also ensures that when one element
6
+ # engulfs/covers/eats another that the larger element
7
+ # evicts the smaller element
8
+ class PriorityEngulfQueue
9
+ def initialize
10
+ @queue = PriorityQueue.new
11
+ end
12
+
13
+ def to_a
14
+ @queue.to_a
15
+ end
16
+
17
+ def empty?
18
+ @queue.empty?
19
+ end
20
+
21
+ def length
22
+ @queue.length
23
+ end
24
+
25
+ def peek
26
+ @queue.peek
27
+ end
28
+
29
+ def pop
30
+ @queue.pop
31
+ end
32
+
33
+ def push(block)
34
+ prune_engulf(block)
35
+ @queue << block
36
+ flush_deleted
37
+
38
+ self
39
+ end
40
+
41
+ private def flush_deleted
42
+ while @queue&.peek&.deleted?
43
+ @queue.pop
44
+ end
45
+ end
46
+
47
+ private def prune_engulf(block)
48
+ # If we're about to pop off the same block, we can skip deleting
49
+ # things from the frontier this iteration since we'll get it
50
+ # on the next iteration
51
+ return if @queue.peek && (block <=> @queue.peek) == 1
52
+
53
+ if block.starts_at != block.ends_at # A block of size 1 cannot engulf another
54
+ @queue.to_a.each { |b|
55
+ if b.starts_at >= block.starts_at && b.ends_at <= block.ends_at
56
+ b.delete
57
+ true
58
+ end
59
+ }
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeadEnd
4
+ # Holds elements in a priority heap on insert
5
+ #
6
+ # Instead of constantly calling `sort!`, put
7
+ # the element where it belongs the first time
8
+ # around
9
+ #
10
+ # Example:
11
+ #
12
+ # queue = PriorityQueue.new
13
+ # queue << 33
14
+ # queue << 44
15
+ # queue << 1
16
+ #
17
+ # puts queue.peek # => 44
18
+ #
19
+ class PriorityQueue
20
+ attr_reader :elements
21
+
22
+ def initialize
23
+ @elements = []
24
+ end
25
+
26
+ def <<(element)
27
+ @elements << element
28
+ bubble_up(last_index, element)
29
+ end
30
+
31
+ def pop
32
+ exchange(0, last_index)
33
+ max = @elements.pop
34
+ bubble_down(0)
35
+ max
36
+ end
37
+
38
+ def length
39
+ @elements.length
40
+ end
41
+
42
+ def empty?
43
+ @elements.empty?
44
+ end
45
+
46
+ def peek
47
+ @elements.first
48
+ end
49
+
50
+ def to_a
51
+ @elements
52
+ end
53
+
54
+ # Used for testing, extremely not performant
55
+ def sorted
56
+ out = []
57
+ elements = @elements.dup
58
+ while (element = pop)
59
+ out << element
60
+ end
61
+ @elements = elements
62
+ out.reverse
63
+ end
64
+
65
+ private def last_index
66
+ @elements.size - 1
67
+ end
68
+
69
+ private def bubble_up(index, element)
70
+ return if index <= 0
71
+
72
+ parent_index = (index - 1) / 2
73
+ parent = @elements[parent_index]
74
+
75
+ return if (parent <=> element) >= 0
76
+
77
+ exchange(index, parent_index)
78
+ bubble_up(parent_index, element)
79
+ end
80
+
81
+ private def bubble_down(index)
82
+ child_index = (index * 2) + 1
83
+
84
+ return if child_index > last_index
85
+
86
+ not_the_last_element = child_index < last_index
87
+ left_element = @elements[child_index]
88
+ right_element = @elements[child_index + 1]
89
+
90
+ child_index += 1 if not_the_last_element && (right_element <=> left_element) == 1
91
+
92
+ return if (@elements[index] <=> @elements[child_index]) >= 0
93
+
94
+ exchange(index, child_index)
95
+ bubble_down(child_index)
96
+ end
97
+
98
+ def exchange(source, target)
99
+ a = @elements[source]
100
+ b = @elements[target]
101
+ @elements[source] = b
102
+ @elements[target] = a
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DeadEnd
4
+ # Tracks which lines various code blocks have expanded to
5
+ # and which are still unexplored
6
+ class UnvisitedLines
7
+ def initialize(code_lines:)
8
+ @unvisited = code_lines.sort_by(&:indent_index)
9
+ @visited_lines = {}
10
+ @visited_lines.compare_by_identity
11
+ end
12
+
13
+ def empty?
14
+ @unvisited.empty?
15
+ end
16
+
17
+ def peek
18
+ @unvisited.last
19
+ end
20
+
21
+ def pop
22
+ @unvisited.pop
23
+ end
24
+
25
+ def visit_block(block)
26
+ block.lines.each do |line|
27
+ next if @visited_lines[line]
28
+ @visited_lines[line] = true
29
+ end
30
+
31
+ while @visited_lines[@unvisited.last]
32
+ @unvisited.pop
33
+ end
34
+ end
35
+ end
36
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DeadEnd
4
- VERSION = "3.1.1"
4
+ VERSION = "3.1.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dead_end
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.1
4
+ version: 3.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - schneems
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-01-04 00:00:00.000000000 Z
11
+ date: 2022-05-18 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: When you get an "unexpected end" in your syntax this gem helps you find
14
14
  it
@@ -51,13 +51,15 @@ files:
51
51
  - lib/dead_end/display_code_with_line_numbers.rb
52
52
  - lib/dead_end/display_invalid_blocks.rb
53
53
  - lib/dead_end/explain_syntax.rb
54
- - lib/dead_end/insertion_sort.rb
55
54
  - lib/dead_end/left_right_lex_count.rb
56
55
  - lib/dead_end/lex_all.rb
57
56
  - lib/dead_end/lex_value.rb
58
57
  - lib/dead_end/parse_blocks_from_indent_line.rb
59
58
  - lib/dead_end/pathname_from_message.rb
59
+ - lib/dead_end/priority_engulf_queue.rb
60
+ - lib/dead_end/priority_queue.rb
60
61
  - lib/dead_end/ripper_errors.rb
62
+ - lib/dead_end/unvisited_lines.rb
61
63
  - lib/dead_end/version.rb
62
64
  homepage: https://github.com/zombocom/dead_end.git
63
65
  licenses:
@@ -80,7 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
80
82
  - !ruby/object:Gem::Version
81
83
  version: '0'
82
84
  requirements: []
83
- rubygems_version: 3.2.32
85
+ rubygems_version: 3.3.14
84
86
  signing_key:
85
87
  specification_version: 4
86
88
  summary: Find syntax errors in your source in a snap
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DeadEnd
4
- # Sort elements on insert
5
- #
6
- # Instead of constantly calling `sort!`, put
7
- # the element where it belongs the first time
8
- # around
9
- #
10
- # Example:
11
- #
12
- # sorted = InsertionSort.new
13
- # sorted << 33
14
- # sorted << 44
15
- # sorted << 1
16
- # puts sorted.to_a
17
- # # => [1, 44, 33]
18
- #
19
- class InsertionSort
20
- def initialize
21
- @array = []
22
- end
23
-
24
- def <<(value)
25
- insert_in = @array.length
26
- @array.each.with_index do |existing, index|
27
- case value <=> existing
28
- when -1
29
- insert_in = index
30
- break
31
- when 0
32
- insert_in = index
33
- break
34
- when 1
35
- # Keep going
36
- end
37
- end
38
-
39
- @array.insert(insert_in, value)
40
- end
41
-
42
- def to_a
43
- @array
44
- end
45
- end
46
- end