class Decode::Language::Ruby::Parser
The Ruby
source code parser.
Constants
- KIND_ATTRIBUTE
- NAME_ATTRIBUTE
- SCOPE_ATTRIBUTE
Public Class Methods
new(language)
click to toggle source
# File lib/decode/language/ruby/parser.rb, line 41 def initialize(language) @language = language end
Public Instance Methods
definitions_for(input, &block)
click to toggle source
Extract definitions from the given input file.
# File lib/decode/language/ruby/parser.rb, line 46 def definitions_for(input, &block) top, comments = ::Parser::CurrentRuby.parse_with_comments(input) if top walk_definitions(top, comments, &block) end end
extract_comments_for(node, comments)
click to toggle source
# File lib/decode/language/ruby/parser.rb, line 54 def extract_comments_for(node, comments) prefix = [] while comment = comments.first break if comment.location.line >= node.location.line if last_comment = prefix.last if last_comment.location.line != (comment.location.line - 1) prefix.clear end end prefix << comments.shift end # The last comment must butt up against the node: if comment = prefix.last if comment.location.line == (node.location.line - 1) return prefix.map do |comment| comment.text.sub(/\A\#\s?/, '') end end end end
kind_for(node, comments = nil)
click to toggle source
# File lib/decode/language/ruby/parser.rb, line 219 def kind_for(node, comments = nil) comments&.each do |comment| if match = comment.match(KIND_ATTRIBUTE) return match[:kind].to_sym end end return nil end
name_for(node, comments = nil)
click to toggle source
# File lib/decode/language/ruby/parser.rb, line 197 def name_for(node, comments = nil) comments&.each do |comment| if match = comment.match(NAME_ATTRIBUTE) return match[:value].to_sym end end case node.type when :sym return node.children[0] when :send return node.children[1] when :block return node.children[0].children[1] end end
scope_for(comments, parent = nil) { |scope| ... }
click to toggle source
# File lib/decode/language/ruby/parser.rb, line 233 def scope_for(comments, parent = nil, &block) comments&.each do |comment| if match = comment.match(SCOPE_ATTRIBUTE) return match[:names].split(/\s+/).map(&:to_sym).inject(nil) do |memo, name| scope = Scope.new(name, parent: memo, language: @language) yield scope scope end end end return parent end
segments_for(input, &block)
click to toggle source
Extract segments from the given input file.
# File lib/decode/language/ruby/parser.rb, line 248 def segments_for(input, &block) top, comments = ::Parser::CurrentRuby.parse_with_comments(input) # We delete any leading comments: line = 0 while comment = comments.first if comment.location.line == line comments.pop line += 1 else break end end # Now we iterate over the syntax tree and generate segments: walk_segments(top, comments, &block) end
walk_definitions(node, comments, parent = nil) { |definition| ... }
click to toggle source
Walk over the syntax tree and extract relevant definitions with their associated comments.
# File lib/decode/language/ruby/parser.rb, line 80 def walk_definitions(node, comments, parent = nil, &block) case node.type when :begin node.children.each do |child| walk_definitions(child, comments, parent, &block) end when :module definition = Module.new( node, node.children[0].children[1], comments: extract_comments_for(node, comments), parent: parent, language: @language ) yield definition if children = node.children[1] walk_definitions(children, comments, definition, &block) end when :class definition = Class.new( node, node.children[0].children[1], comments: extract_comments_for(node, comments), parent: parent, language: @language ) yield definition if children = node.children[2] walk_definitions(children, comments, definition, &block) end when :sclass definition = Singleton.new( node, node.children[0], comments: extract_comments_for(node, comments), parent: parent, language: @language ) yield definition if children = node.children[1] walk_definitions(children, comments, definition, &block) end when :def definition = Method.new( node, node.children[0], comments: extract_comments_for(node, comments), parent: parent, language: @language ) yield definition when :defs definition = Function.new( node, node.children[1], comments: extract_comments_for(node, comments), parent: parent, language: @language ) yield definition when :casgn definition = Constant.new( node, node.children[1], comments: extract_comments_for(node, comments), parent: parent, language: @language ) yield definition when :send name = node.children[1] case name when :attr, :attr_reader, :attr_writer, :attr_accessor definition = Attribute.new( node, name_for(node.children[2]), comments: extract_comments_for(node, comments), parent: parent, language: @language ) yield definition else extracted_comments = extract_comments_for(node, comments) if kind = kind_for(node, extracted_comments) definition = Call.new( node, name_for(node, extracted_comments), comments: extracted_comments, parent: parent, language: @language ) yield definition end end when :block extracted_comments = extract_comments_for(node, comments) if name = name_for(node, extracted_comments) definition = Block.new( node, name, comments: extracted_comments, parent: scope_for(extracted_comments, parent, &block), language: @language ) if kind = kind_for(node, extracted_comments) definition = definition.convert(kind) end yield definition if children = node.children[2] walk_definitions(children, comments, definition, &block) end end end end
walk_segments(node, comments) { |segment| ... }
click to toggle source
# File lib/decode/language/ruby/parser.rb, line 267 def walk_segments(node, comments, &block) case node.type when :begin segment = nil node.children.each do |child| if segment.nil? segment = Segment.new( extract_comments_for(child, comments), @language, child ) elsif next_comments = extract_comments_for(child, comments) yield segment if segment segment = Segment.new(next_comments, @language, child) else segment.expand(child) end end yield segment if segment else # One top level segment: segment = Segment.new( extract_comments_for(node, comments), @language, node ) yield segment end end