class DeadEnd::CodeSearch
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"]
Attributes
code_lines[R]
frontier[R]
invalid_blocks[R]
record_dir[R]
Public Class Methods
new(source, record_dir: ENV["DEAD_END_RECORD_DIR"] || ENV["DEBUG"] ? "tmp" : nil)
click to toggle source
# File lib/dead_end/code_search.rb, line 31 def initialize(source, record_dir: ENV["DEAD_END_RECORD_DIR"] || ENV["DEBUG"] ? "tmp" : nil) @source = source 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 = source.lines.map.with_index do |line, i| CodeLine.new(line: line, index: i) end @code_lines = TrailingSlashJoin.new(code_lines: code_lines).call @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
Public Instance Methods
call()
click to toggle source
Main search loop
# File lib/dead_end/code_search.rb, line 139 def call sweep_heredocs sweep_comments until frontier.holds_all_syntax_errors? @tick += 1 if frontier.expand? expand_invalid_block else visit_new_blocks end end @invalid_blocks.concat(frontier.detect_invalid_blocks ) @invalid_blocks.sort_by! {|block| block.starts_at } self end
expand_invalid_block()
click to toggle source
Given an already existing block in the frontier, expand it to see if it contains our invalid syntax
# File lib/dead_end/code_search.rb, line 111 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: )
click to toggle source
# File lib/dead_end/code_search.rb, line 74 def push(block, name: ) record(block: block, name: name) if block.valid? block.mark_invisible frontier << block else frontier << block end end
record(block:, name: "record")
click to toggle source
Used for debugging
# File lib/dead_end/code_search.rb, line 53 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
sweep(block:, name: )
click to toggle source
Removes the block without putting it back in the frontier
# File lib/dead_end/code_search.rb, line 86 def sweep(block:, name: ) record(block: block, name: name) block.lines.each(&:mark_invisible) frontier.register_indent_block(block) end
sweep_comments()
click to toggle source
# File lib/dead_end/code_search.rb, line 131 def sweep_comments lines = @code_lines.select(&:is_comment?) return if lines.empty? block = CodeBlock.new(lines: lines) sweep(block: block, name: "comments") end
sweep_heredocs()
click to toggle source
# File lib/dead_end/code_search.rb, line 122 def sweep_heredocs HeredocBlockParse.new( source: @source, code_lines: @code_lines ).call.each do |block| push(block, name: "heredoc") end end
visit_new_blocks()
click to toggle source
Parses the most indented lines into blocks that are marked and added to the frontier
# File lib/dead_end/code_search.rb, line 95 def visit_new_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