Class: SyntaxErrorSearch::CodeFrontier

Inherits:
Object
  • Object
show all
Defined in:
lib/syntax_search/code_frontier.rb

Overview

The main function of the frontier is to hold the edges of our search and to evaluate when we can stop searching.

## Knowing where we’ve been

Once a code block is generated it is added onto the frontier where it will be 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

## Knowing where we can go

Internally it keeps track of an “indent hash” 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.

CodeFrontier#next_indent_line
CodeFrontier#register_indent_block

## Knowing when to stop

The frontier holds the syntax error when removing all code blocks from the original source document allows it to be parsed as syntatically valid:

CodeFrontier#holds_all_syntax_errors?

## Filtering false positives

Once the search is completed, the frontier will have many blocks that do not contain the syntax error. To filter to the smallest subset that does call:

CodeFrontier#detect_invalid_blocks

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(code_lines:) ⇒ CodeFrontier

Returns a new instance of CodeFrontier.



42
43
44
45
46
47
48
49
50
51
52
# File 'lib/syntax_search/code_frontier.rb', line 42

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
end

Class Method Details

.combination(array) ⇒ Object

Example:

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]]


135
136
137
138
139
140
141
# File 'lib/syntax_search/code_frontier.rb', line 135

def self.combination(array)
  guesses = []
  1.upto(array.length).each do |size|
    guesses.concat(array.combination(size).to_a)
  end
  guesses
end

Instance Method Details

#<<(block) ⇒ Object

Add a block to the frontier

This method ensures the frontier always remains sorted (in indentation order) and that each code block’s lines are removed from the indentation hash so we don’t re-evaluate the same line multiple times.



118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/syntax_search/code_frontier.rb', line 118

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|
    b.starts_at >= block.starts_at && b.ends_at <= block.ends_at
  }
  @frontier << block
  @frontier.sort!

  self
end

#countObject



54
55
56
# File 'lib/syntax_search/code_frontier.rb', line 54

def count
  @frontier.count
end

#detect_invalid_blocksObject

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



145
146
147
148
149
# File 'lib/syntax_search/code_frontier.rb', line 145

def detect_invalid_blocks
  self.class.combination(@frontier).detect do |block_array|
    holds_all_syntax_errors?(block_array)
  end || []
end

#expand?Boolean

Returns:



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/syntax_search/code_frontier.rb', line 87

def expand?
  return false if @frontier.empty?
  return true if @indent_hash.empty?

  frontier_indent = @frontier.last.current_indent
  hash_indent = @indent_hash.keys.sort.last

  if ENV["DEBUG"]
    puts "```"
    puts @frontier.last.to_s
    puts "```"
    puts "  @frontier indent: #{frontier_indent}"
    puts "  @hash indent:     #{hash_indent}"
  end

  frontier_indent >= hash_indent
end

#holds_all_syntax_errors?(block_array = @frontier) ⇒ Boolean

Returns true if the document is valid with all lines removed. By default it checks all blocks in present in the frontier array, but can be used for arbitrary arrays of codeblocks as well

Returns:



62
63
64
65
66
67
68
69
70
71
# File 'lib/syntax_search/code_frontier.rb', line 62

def holds_all_syntax_errors?(block_array = @frontier)
  without_lines = block_array.map do |block|
    block.lines
  end

  SyntaxErrorSearch.valid_without?(
    without_lines: without_lines,
    code_lines: @code_lines
  )
end

#indent_hash_indentObject



78
79
80
# File 'lib/syntax_search/code_frontier.rb', line 78

def indent_hash_indent
  @indent_hash.keys.sort.last
end

#next_indent_lineObject



82
83
84
85
# File 'lib/syntax_search/code_frontier.rb', line 82

def next_indent_line
  indent = @indent_hash.keys.sort.last
  @indent_hash[indent]&.first
end

#popObject

Returns a code block with the largest indentation possible



74
75
76
# File 'lib/syntax_search/code_frontier.rb', line 74

def pop
  return @frontier.pop
end

#register_indent_block(block) ⇒ Object



105
106
107
108
109
110
111
# File 'lib/syntax_search/code_frontier.rb', line 105

def register_indent_block(block)
  block.lines.each do |line|
    @indent_hash[line.indent]&.delete(line)
  end
  @indent_hash.select! {|k, v| !v.empty?}
  self
end