Class: ProductionBreakpoints::Parser
- Inherits:
-
Object
- Object
- ProductionBreakpoints::Parser
- Defined in:
- lib/ruby-production-breakpoints/parser.rb
Overview
FIXME: this class is a mess, figure out interface and properly separate private / public
Instance Attribute Summary collapse
-
#root_node ⇒ Object
readonly
Returns the value of attribute root_node.
-
#source_lines ⇒ Object
readonly
Returns the value of attribute source_lines.
Instance Method Summary collapse
- #find_definition_namespace(target) ⇒ Object
- #find_definition_node(start_line, end_line) ⇒ Object
- #find_definition_symbol(start_line, end_line) ⇒ Object
- #find_lineage(target) ⇒ Object
-
#find_node(node, type, first, last, depth: 0) ⇒ Object
FIXME: set a max depth here to pretent unbounded recursion? probably should.
-
#initialize(source_file) ⇒ Parser
constructor
A new instance of Parser.
-
#inject_metaprogramming_handlers(handler, def_start, def_end) ⇒ Object
This method is a litle weird and pretty deep into metaprogramming, so i’ll try to explain it.
- #ruby_source(start_line, end_line) ⇒ Object
Constructor Details
#initialize(source_file) ⇒ Parser
Returns a new instance of Parser.
8 9 10 11 12 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 8 def initialize(source_file) @root_node = RubyVM::AbstractSyntaxTree.parse_file(source_file) @source_lines = File.read(source_file).lines @logger = ProductionBreakpoints.config.logger end |
Instance Attribute Details
#root_node ⇒ Object (readonly)
Returns the value of attribute root_node.
6 7 8 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 6 def root_node @root_node end |
#source_lines ⇒ Object (readonly)
Returns the value of attribute source_lines.
6 7 8 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 6 def source_lines @source_lines end |
Instance Method Details
#find_definition_namespace(target) ⇒ Object
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 32 def find_definition_namespace(target) lineage = find_lineage(target) namespaces = [] lineage.each do |n| next unless n.type == :MODULE || n.type == :CLASS symbols = n.children.select { |c| c.is_a?(RubyVM::AbstractSyntaxTree::Node) && c.type == :COLON2 } if symbols.size != 1 @logger.error("Couldn't determine symbol location for parent namespace") end symbol = symbols.first symstr = @source_lines[symbol.first_lineno - 1][symbol.first_column..symbol.last_column].strip namespaces << symstr end namespaces.join('::') end |
#find_definition_node(start_line, end_line) ⇒ Object
59 60 61 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 59 def find_definition_node(start_line, end_line) _find_definition_node(@root_node, start_line, end_line) end |
#find_definition_symbol(start_line, end_line) ⇒ Object
52 53 54 55 56 57 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 52 def find_definition_symbol(start_line, end_line) def_node = _find_definition_node(@root_node, start_line, end_line) def_column_start = def_node.first_column def_column_end = _find_args_start(def_node).first_column @source_lines[def_node.first_lineno - 1][(def_column_start + 3 + 1)..def_column_end].strip.to_sym end |
#find_lineage(target) ⇒ Object
26 27 28 29 30 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 26 def find_lineage(target) lineage = _find_lineage(@root_node, target) lineage.pop # FIXME: verify leafy node is equal to target or throw an error? lineage end |
#find_node(node, type, first, last, depth: 0) ⇒ Object
FIXME: set a max depth here to pretent unbounded recursion? probably should
15 16 17 18 19 20 21 22 23 24 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 15 def find_node(node, type, first, last, depth: 0) child_nodes = node.children.select { |c| c.is_a?(RubyVM::AbstractSyntaxTree::Node) } # @logger.debug("D: #{depth} #{node.type} has #{child_nodes.size} children and spans #{node.first_lineno}:#{node.first_column} to #{node.last_lineno}:#{node.last_column}") if node.type == type && first >= node.first_lineno && last <= node.last_lineno return node end child_nodes.map { |n| find_node(n, type, first, last, depth: depth + 1) }.flatten end |
#inject_metaprogramming_handlers(handler, def_start, def_end) ⇒ Object
This method is a litle weird and pretty deep into metaprogramming, so i’ll try to explain it
Given the source method some_method, and a range of lines to apply the breakpoint to, we will inject calls two breakpoint methods. We will pass these calls the string representation of the original source code. If the string of original source is part of the “handle” block, it will run withing the binding of the method up to that point, and allow for us to run our custom handler method to apply our debugging automation.
Any remaining code in the method also needs to be eval’d, as we want it to be recognized in the original binding, and the same binding as we’ve used for evaluating our handler. This allows us to keep local variables persisted “between blocks”, as we want our breakpoint code to have no impact to the original bindings and source code.
A generated breakpoint is shown below, the resulting string. is what will be evaluated on the method that we will prepend to the original parent in order to initiate our override.
def some_method
a = 1
sleep 0.5
b = a + 1
ProductionBreakpoints.installed_breakpoints[:test_breakpoint_install].handle(Kernel.binding)
end
84 85 86 87 88 89 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 84 def (handler, def_start, def_end) source = @source_lines.dup source.insert(def_end - 1, "#{handler}\n") # FIXME: columns? and indenting? source[(def_start - 1)..(def_end)].join end |
#ruby_source(start_line, end_line) ⇒ Object
91 92 93 |
# File 'lib/ruby-production-breakpoints/parser.rb', line 91 def ruby_source(start_line, end_line) @source_lines[(start_line - 1)..(end_line - 1)].join end |