lib/yard/parser/source_parser.rb in yard-0.2.3.5 vs lib/yard/parser/source_parser.rb in yard-0.4.0
- old
+ new
@@ -1,47 +1,101 @@
require 'stringio'
require 'continuation' unless RUBY18
module YARD
module Parser
+ # Raised when an object is recognized but cannot be documented. This
+ # generally occurs when the Ruby syntax used to declare an object is
+ # too dynamic in nature.
class UndocumentableError < Exception; end
+
+ # Raised when the parser sees a Ruby syntax error
class ParserSyntaxError < UndocumentableError; end
+
+ # A LoadOrderError occurs when a handler needs to modify a
+ # {CodeObjects::NamespaceObject} (usually by adding a child to it)
+ # that has not yet been resolved.
+ #
+ # @see Handers::Base#ensure_loaded!
class LoadOrderError < Exception; end
- # Responsible for parsing a source file into the namespace
+ # Responsible for parsing a source file into the namespace. Parsing
+ # also invokes handlers to process the parsed statements and generate
+ # any code objects that may be recognized.
+ #
+ # @see Handlers::Base
+ # @see CodeObjects::Base
class SourceParser
class << self
+ # The default parser type to use for blocks of code. Change this
+ # attribute to apply a new parser type for all newly parsed
+ # blocks of code.
+ #
+ # @return [Symbol] the parser type
attr_accessor :parser_type
+ # Sets the parser and makes sure it's a valid type
+ #
+ # @param [Symbol] value the new parser type
+ # @return [nil]
def parser_type=(value)
@parser_type = validated_parser_type(value)
end
+ # Parses a path or set of paths
+ #
+ # @param [String, Array<String>] paths a path, glob, or list of paths to
+ # parse
+ # @param [Fixnum] level the logger level to use during parsing. See
+ # {YARD::Logger}
+ # @return the parser object that was used to parse the source.
def parse(paths = "lib/**/*.rb", level = log.level)
log.debug("Parsing #{paths} with `#{parser_type}` parser")
files = [paths].flatten.map {|p| p.include?("*") ? Dir[p] : p }.flatten
log.enter_level(level) do
parse_in_order(*files.uniq)
end
end
+ # Parses a string +content+
+ #
+ # @param [String] content the block of code to parse
+ # @param [Symbol] ptype the parser type to use. See {parser_type}.
+ # @return the parser object that was used to parse +content+
def parse_string(content, ptype = parser_type)
new(ptype).parse(StringIO.new(content))
end
+ # Tokenizes but does not parse the block of code
+ #
+ # @param [String] content the block of code to tokenize
+ # @param [Symbol] ptype the parser type to use. See {parser_type}.
+ # @return [Array] a list of tokens
def tokenize(content, ptype = parser_type)
new(ptype).tokenize(content)
end
+ # Returns the validated parser type. Basically, enforces that :ruby
+ # type is never set from Ruby 1.8
+ #
+ # @param [Symbol] type the parser type to set
+ # @return [Symbol] the validated parser type
def validated_parser_type(type)
RUBY18 && type == :ruby ? :ruby18 : type
end
private
+ # Parses a list of files in a queue. If a {LoadOrderError} is caught,
+ # the file is moved to the back of the queue with a Continuation object
+ # that can continue processing the file.
+ #
+ # @param [Array<String>] files a list of files to queue for parsing
+ # @return [nil]
def parse_in_order(*files)
+ files = files.sort_by {|x| x.length if x }
while file = files.shift
begin
if file.is_a?(Array) && file.last.is_a?(Continuation)
log.debug("Re-processing #{file.first}")
file.last.call
@@ -57,23 +111,32 @@
end
end
self.parser_type = :ruby
- attr_reader :file, :parser_type
+ # The filename being parsed by the parser.
+ attr_reader :file
+
+ # The parser type associated with the parser instance. This should
+ # be set by the {#initialize constructor}.
+ attr_reader :parser_type
+ # Creates a new parser object for code parsing with a specific parser type.
+ #
+ # @param [Symbol] parser_type the parser type to use
+ # @param [Boolean] load_order_errors whether or not to raise the {LoadOrderError}
def initialize(parser_type = SourceParser.parser_type, load_order_errors = false)
@load_order_errors = load_order_errors
@file = '(stdin)'
self.parser_type = parser_type
end
- ##
- # Creates a new SourceParser that parses a file and returns
- # analysis information about it.
+ # The main parser method. This should not be called directly. Instead,
+ # use the class methods {parse} and {parse_string}.
#
# @param [String, #read, Object] content the source file to parse
+ # @return the parser object used to parse the source
def parse(content = __FILE__)
case content
when String
@file = content
content = IO.read(content)
@@ -89,10 +152,14 @@
log.warn("Cannot parse `#{file}': #{e.message}")
rescue ParserSyntaxError => e
log.warn(e.message.capitalize)
end
+ # Tokenizes but does not parse the block of code using the current {#parser_type}
+ #
+ # @param [String] content the block of code to tokenize
+ # @return [Array] a list of tokens
def tokenize(content)
case parser_type
when :c
raise NotImplementedError, "no support for C/C++ files"
when :ruby18
@@ -104,27 +171,37 @@
end
end
private
+ # Runs a {Handlers::Processor} object to post process the parsed statements.
+ # @return [nil]
def post_process
post = Handlers::Processor.new(@file, @load_order_errors, @parser_type)
post.process(@parser.enumerator)
end
def parser_type=(value)
@parser_type = self.class.validated_parser_type(value)
end
+ # Guesses the parser type to use depending on the file extension.
+ #
+ # @param [String] filename the filename to use to guess the parser type
+ # @return [Symbol] a parser type that matches the filename
def parser_type_for_filename(filename)
case (File.extname(filename)[1..-1] || "").downcase
when "c", "cpp", "cxx"
:c
else # when "rb", "rbx", "erb"
parser_type == :ruby18 ? :ruby18 : :ruby
end
end
+ # Selects a parser to use to parse +content+.
+ #
+ # @param [String] content the block of code to parse
+ # @return the resulting parser object
def parse_statements(content)
case parser_type
when :c
raise NotImplementedError, "no support for C/C++ files"
when :ruby18