lib/yard/parser/c/c_parser.rb
# frozen_string_literal: true module YARD module Parser module C class CParser < Base def initialize(source, file = '(stdin)') @file = file @namespaces = {} @content = source @index = 0 @line = 1 @state = nil @newline = true @statements = [] @last_comment = nil @last_statement = nil end def parse parse_toplevel enumerator end def enumerator @statements end def tokenize raise NotImplementedError, "no tokenization support for C/C++ files" end private def parse_toplevel advance_loop do case char when /['"]/; consume_quote(char) when '#'; consume_directive when '/'; consume_comment when /\s/; consume_whitespace when '}'; advance # Skip possible C++ namespace closing brackets. else consume_toplevel_statement end end end def consume_quote(type = '"') advance advance_loop do case char when "\n"; advance; nextline when '\\'; advance(2) when type; advance; return else advance end end end def consume_directive return(advance) unless @newline @last_comment = nil @last_statement = nil advance_loop do if char == '\\' && nextchar =~ /[\r\n]/ advance_loop { advance; break(nextline) if char == "\n" } elsif char == "\n" return end advance end end def consume_toplevel_statement @newline = false start = @index line = @line decl = consume_until(/[{;]/) return nil if decl =~ /\A\s*\Z/ # Skip C++ namespace - treat content as top level statement. return nil if decl =~ /\A(namespace)/ statement = ToplevelStatement.new(nil, @file, line) @statements << statement attach_comment(statement) stmts = nil if prevchar == '{' stmts = consume_body_statements if decl =~ /\A(typedef|enum|class|#{struct}|union)/ consume_until(';') end end statement.source = @content[start..@index] statement.block = stmts statement.declaration = decl # rubocop:disable Lint/UselessSetterCall end def consume_body_statements stmts = [] brace_level = 1 loop do strip_non_statement_data start = @index line = @line consume_until(/[{};]/) brace_level += 1 if prevchar == '{' brace_level -= 1 if prevchar == '}' break if prevchar.empty? || (brace_level <= 0 && prevchar == '}') src = @content[start...@index] next unless src && src !~ /\A\s*\Z|\A\}\Z/ stmt = BodyStatement.new(src, @file, line) attach_comment(stmt) stmts << stmt end stmts end def strip_non_statement_data start = @index loop do start = @index case char when /\s/; consume_whitespace when '#'; consume_directive when '/'; consume_comment end break if start == @index end end def consume_whitespace advance_loop { nextline if char == "\n"; break if char =~ /\S/; advance } end def consume_comment(add_comment = true) return(advance) unless nextchar == '*' || nextchar == '/' line = @line type = nextchar == '*' ? :multi : :line advance(2) comment = String.new("") advance_loop do comment << char if type == :multi nextline if char == "\n" if char(2) == '*/' if add_comment comment << '/' stmt = Comment.new(comment, @file, line) stmt.type = type attach_comment(stmt) @statements << stmt end return advance(2) end elsif char == "\n" if add_comment stmt = Comment.new(comment[0...-1], @file, line) stmt.type = type attach_comment(stmt) @statements << stmt end return end advance end end def consume_until(end_char, bracket_level = 0, brace_level = 0, add_comment = true) end_char = /#{end_char}/ if end_char.is_a?(String) start = @index advance_loop do chr = char case chr when /\s/; consume_whitespace when /['"]/; consume_quote(char) when '#'; consume_directive when '/'; consume_comment(add_comment) when '{'; advance; brace_level += 1 when '}'; advance; brace_level -= 1 when '('; advance; bracket_level += 1 when ')'; advance; bracket_level -= 1 else advance end @newline = false if chr !~ /\s/ if chr =~ end_char break if chr == '{' || chr == '(' break if bracket_level <= 0 && brace_level <= 0 end end @content[start...@index] end def attach_comment(statement) if Comment === statement if @last_statement && @last_statement.line == statement.line @last_statement.comments = statement statement.statement = @last_statement end @last_comment = statement @last_statement = nil else if @last_comment statement.comments = @last_comment @last_comment.statement = statement end @last_statement = statement @last_comment = nil end end def advance(num = 1) @index += num end def back(num = 1) @index -= num end def advance_loop yield while @index <= @content.size end def nextline @line += 1 @newline = true end def char(num = 1) @content[@index, num] end def prevchar(num = 1) @content[@index - 1, num] end def nextchar(num = 1) @content[@index + 1, num] end def struct /struct\s[a-zA-Z0-9]+\s\{/ end end end end end