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