Class: SyntaxErrorSearch::CodeSearch

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

Overview

Searches code for a syntax error

The bulk of the heavy lifting is done in:

- CodeFrontier (Holds information for generating blocks and determining if we can stop searching)
- ParseBlocksFromLine (Creates blocks into the frontier)
- BlockExpand (Expands existing blocks to search more code

## Syntax error detection

When the frontier holds the syntax error, we can stop searching

search = CodeSearch.new(<<~EOM)
  def dog
    def lol
  end
EOM

search.call

search.invalid_blocks.map(&:to_s) # =>
# => ["def lol\n"]

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(string, record_dir: ENV["SYNTAX_SEARCH_RECORD_DIR"]) ⇒ CodeSearch

Returns a new instance of CodeSearch.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/syntax_search/code_search.rb', line 31

def initialize(string, record_dir: ENV["SYNTAX_SEARCH_RECORD_DIR"])
  if record_dir
    @time = Time.now.strftime('%Y-%m-%d-%H-%M-%s-%N')
    @record_dir = Pathname(record_dir).join(@time).tap {|p| p.mkpath }
    @write_count = 0
  end
  @code_lines = string.lines.map.with_index do |line, i|
    CodeLine.new(line: line, index: i)
  end
  @frontier = CodeFrontier.new(code_lines: @code_lines)
  @invalid_blocks = []
  @name_tick = Hash.new {|hash, k| hash[k] = 0 }
  @tick = 0
  @block_expand = BlockExpand.new(code_lines: code_lines)
  @parse_blocks_from_indent_line = ParseBlocksFromIndentLine.new(code_lines: @code_lines)
end

Instance Attribute Details

#code_linesObject (readonly)

Returns the value of attribute code_lines.



29
30
31
# File 'lib/syntax_search/code_search.rb', line 29

def code_lines
  @code_lines
end

#invalid_blocksObject (readonly)

Returns the value of attribute invalid_blocks.



29
30
31
# File 'lib/syntax_search/code_search.rb', line 29

def invalid_blocks
  @invalid_blocks
end

#record_dirObject (readonly)

Returns the value of attribute record_dir.



29
30
31
# File 'lib/syntax_search/code_search.rb', line 29

def record_dir
  @record_dir
end

Instance Method Details

#add_invalid_blocksObject

Parses the most indented lines into blocks that are marked and added to the frontier



83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/syntax_search/code_search.rb', line 83

def add_invalid_blocks
  max_indent = frontier.next_indent_line&.indent

  while (line = frontier.next_indent_line) && (line.indent == max_indent)

    @parse_blocks_from_indent_line.each_neighbor_block(frontier.next_indent_line) do |block|
      record(block: block, name: "add")

      block.mark_invisible if block.valid?
      push(block, name: "add")
    end
  end
end

#callObject

Main search loop



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/syntax_search/code_search.rb', line 111

def call
  until frontier.holds_all_syntax_errors?
    @tick += 1

    if frontier.expand?
      expand_invalid_block
    else
      add_invalid_blocks
    end
  end

  @invalid_blocks.concat(frontier.detect_invalid_blocks )
  @invalid_blocks.sort_by! {|block| block.starts_at }
  self
end

#expand_invalid_blockObject

Given an already existing block in the frontier, expand it to see if it contains our invalid syntax



99
100
101
102
103
104
105
106
107
108
# File 'lib/syntax_search/code_search.rb', line 99

def expand_invalid_block
  block = frontier.pop
  return unless block

  record(block: block, name: "pop")

  # block = block.expand_until_next_boundry
  block = @block_expand.call(block)
  push(block, name: "expand")
end

#push(block, name:) ⇒ Object



70
71
72
73
74
75
76
77
78
79
# File 'lib/syntax_search/code_search.rb', line 70

def push(block, name: )
  record(block: block, name: name)

  if block.valid?
    block.lines.each(&:mark_invisible)
    frontier << block
  else
    frontier << block
  end
end

#record(block:, name: "record") ⇒ Object

Used for debugging



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/syntax_search/code_search.rb', line 49

def record(block:, name: "record")
  return if !@record_dir
  @name_tick[name] += 1
  filename = "#{@write_count += 1}-#{name}-#{@name_tick[name]}.txt"
  if ENV["DEBUG"]
    puts "\n\n==== #{filename} ===="
    puts "\n```#{block.starts_at}:#{block.ends_at}"
    puts "#{block.to_s}"
    puts "```"
    puts "  block indent:     #{block.current_indent}"
  end
  @record_dir.join(filename).open(mode: "a") do |f|
    display = DisplayInvalidBlocks.new(
      blocks: block,
      terminal: false,
      code_lines: @code_lines,
    )
    f.write(display.indent display.code_with_lines)
  end
end