mirror of
https://github.com/ThrowTheSwitch/CMock.git
synced 2026-06-06 05:25:29 +00:00
3caf511b8f
nanopb has a habit of generating macros which end with an escaped empty line (for example for messages which are empty). They look like this: #define some_msg_t_FIELDLIST(X, a) \ #define some_msg_t_CALLBACK NULL This caused CMock to strip all of the newlines after the backslash instead of only one, which the backslash was escaping. This in turn caused both the macros to be in one line, causing a compile error in the generated mock.
628 lines
26 KiB
Ruby
628 lines
26 KiB
Ruby
# ==========================================
|
|
# CMock Project - Automatic Mock Generation for C
|
|
# Copyright (c) 2007 Mike Karlesky, Mark VanderVoord, Greg Williams
|
|
# [Released under MIT License. Please refer to license.txt for details]
|
|
# ==========================================
|
|
|
|
class CMockHeaderParser
|
|
attr_accessor :funcs, :c_attr_noconst, :c_attributes, :treat_as_void, :treat_externs, :treat_inlines, :inline_function_patterns
|
|
|
|
def initialize(cfg)
|
|
@c_strippables = cfg.strippables
|
|
@c_attr_noconst = cfg.attributes.uniq - ['const']
|
|
@c_attributes = ['const'] + c_attr_noconst
|
|
@c_calling_conventions = cfg.c_calling_conventions.uniq
|
|
@treat_as_array = cfg.treat_as_array
|
|
@treat_as_void = (['void'] + cfg.treat_as_void).uniq
|
|
@function_declaration_parse_base_match = '([\w\s\*\(\),\[\]]*?\w[\w\s\*\(\),\[\]]*?)\(([\w\s\*\(\),\.\[\]+\-\/]*)\)'
|
|
@declaration_parse_matcher = /#{@function_declaration_parse_base_match}$/m
|
|
@standards = (%w[int short char long unsigned signed] + cfg.treat_as.keys).uniq
|
|
@array_size_name = cfg.array_size_name
|
|
@array_size_type = (%w[int size_t] + cfg.array_size_type).uniq
|
|
@when_no_prototypes = cfg.when_no_prototypes
|
|
@local_as_void = @treat_as_void
|
|
@verbosity = cfg.verbosity
|
|
@treat_externs = cfg.treat_externs
|
|
@treat_inlines = cfg.treat_inlines
|
|
@inline_function_patterns = cfg.inline_function_patterns
|
|
@c_strippables += ['extern'] if @treat_externs == :include # we'll need to remove the attribute if we're allowing externs
|
|
@c_strippables += ['inline'] if @treat_inlines == :include # we'll need to remove the attribute if we're allowing inlines
|
|
end
|
|
|
|
def parse(name, source)
|
|
parse_project = {
|
|
:module_name => name.gsub(/\W/, ''),
|
|
:typedefs => [],
|
|
:functions => [],
|
|
:normalized_source => nil
|
|
}
|
|
|
|
function_names = []
|
|
|
|
all_funcs = parse_functions(import_source(source, parse_project)).map { |item| [item] }
|
|
all_funcs += parse_cpp_functions(import_source(source, parse_project, true))
|
|
all_funcs.map do |decl|
|
|
func = parse_declaration(parse_project, *decl)
|
|
unless function_names.include? func[:name]
|
|
parse_project[:functions] << func
|
|
function_names << func[:name]
|
|
end
|
|
end
|
|
|
|
parse_project[:normalized_source] = if @treat_inlines == :include
|
|
transform_inline_functions(source)
|
|
else
|
|
''
|
|
end
|
|
|
|
{ :includes => nil,
|
|
:functions => parse_project[:functions],
|
|
:typedefs => parse_project[:typedefs],
|
|
:normalized_source => parse_project[:normalized_source] }
|
|
end
|
|
|
|
private if $ThisIsOnlyATest.nil? ################
|
|
|
|
# Remove C/C++ comments from a string
|
|
# +source+:: String which will have the comments removed
|
|
def remove_comments_from_source(source)
|
|
# remove comments (block and line, in three steps to ensure correct precedence)
|
|
source.gsub!(/(?<!\*)\/\/(?:.+\/\*|\*(?:$|[^\/])).*$/, '') # remove line comments that comment out the start of blocks
|
|
source.gsub!(/\/\*.*?\*\//m, '') # remove block comments
|
|
source.gsub!(/\/\/.*$/, '') # remove line comments (all that remain)
|
|
end
|
|
|
|
def remove_nested_pairs_of_braces(source)
|
|
# remove nested pairs of braces because no function declarations will be inside of them (leave outer pair for function definition detection)
|
|
if RUBY_VERSION.split('.')[0].to_i > 1
|
|
# we assign a string first because (no joke) if Ruby 1.9.3 sees this line as a regex, it will crash.
|
|
r = '\\{([^\\{\\}]*|\\g<0>)*\\}'
|
|
source.gsub!(/#{r}/m, '{ }')
|
|
else
|
|
while source.gsub!(/\{[^\{\}]*\{[^\{\}]*\}[^\{\}]*\}/m, '{ }')
|
|
end
|
|
end
|
|
|
|
source
|
|
end
|
|
|
|
# Return the number of pairs of braces/square brackets in the function provided by the user
|
|
# +source+:: String containing the function to be processed
|
|
def count_number_of_pairs_of_braces_in_function(source)
|
|
is_function_start_found = false
|
|
curr_level = 0
|
|
total_pairs = 0
|
|
|
|
source.each_char do |c|
|
|
if c == '{'
|
|
curr_level += 1
|
|
total_pairs += 1
|
|
is_function_start_found = true
|
|
elsif c == '}'
|
|
curr_level -= 1
|
|
end
|
|
|
|
break if is_function_start_found && curr_level == 0 # We reached the end of the inline function body
|
|
end
|
|
|
|
if curr_level != 0
|
|
total_pairs = 0 # Something is fishy about this source, not enough closing braces?
|
|
end
|
|
|
|
total_pairs
|
|
end
|
|
|
|
# Transform inline functions to regular functions in the source by the user
|
|
# +source+:: String containing the source to be processed
|
|
def transform_inline_functions(source)
|
|
inline_function_regex_formats = []
|
|
square_bracket_pair_regex_format = /\{[^\{\}]*\}/ # Regex to match one whole block enclosed by two square brackets
|
|
|
|
# Convert user provided string patterns to regex
|
|
# Use word bounderies before and after the user regex to limit matching to actual word iso part of a word
|
|
@inline_function_patterns.each do |user_format_string|
|
|
user_regex = Regexp.new(user_format_string)
|
|
word_boundary_before_user_regex = /\b/
|
|
cleanup_spaces_after_user_regex = /[ ]*\b/
|
|
inline_function_regex_formats << Regexp.new(word_boundary_before_user_regex.source + user_regex.source + cleanup_spaces_after_user_regex.source)
|
|
end
|
|
|
|
# let's clean up the encoding in case they've done anything weird with the characters we might find
|
|
source = source.force_encoding('ISO-8859-1').encode('utf-8', :replace => nil)
|
|
|
|
# Comments can contain words that will trigger the parser (static|inline|<user_defined_static_keyword>)
|
|
remove_comments_from_source(source)
|
|
|
|
# smush multiline macros into single line (checking for continuation character at end of line '\')
|
|
# If the user uses a macro to declare an inline function,
|
|
# smushing the macros makes it easier to recognize them as a macro and if required,
|
|
# remove them later on in this function
|
|
source.gsub!(/\s*\\(\n|\s*)/m, ' ')
|
|
|
|
# Just looking for static|inline in the gsub is a bit too aggressive (functions that are named like this, ...), so we try to be a bit smarter
|
|
# Instead, look for an inline pattern (f.e. "static inline") and parse it.
|
|
# Below is a small explanation on how the general mechanism works:
|
|
# - Everything before the match should just be copied, we don't want
|
|
# to touch anything but the inline functions.
|
|
# - Remove the implementation of the inline function (this is enclosed
|
|
# in square brackets) and replace it with ";" to complete the
|
|
# transformation to normal/non-inline function.
|
|
# To ensure proper removal of the function body, we count the number of square-bracket pairs
|
|
# and remove the pairs one-by-one.
|
|
# - Copy everything after the inline function implementation and start the parsing of the next inline function
|
|
# There are ofcourse some special cases (inline macro declarations, inline function declarations, ...) which are handled and explained below
|
|
inline_function_regex_formats.each do |format|
|
|
inspected_source = ''
|
|
regex_matched = false
|
|
loop do
|
|
inline_function_match = source.match(/#{format}/) # Search for inline function declaration
|
|
|
|
if inline_function_match.nil? # No inline functions so nothing to do
|
|
# Join pre and post match stripped parts for the next inline function detection regex
|
|
source = inspected_source + source if regex_matched == true
|
|
break
|
|
end
|
|
|
|
regex_matched = true
|
|
# 1. Determine if we are dealing with a user defined macro to declare inline functions
|
|
# If the end of the pre-match string is a macro-declaration-like string,
|
|
# we are dealing with a user defined macro to declare inline functions
|
|
if /(#define\s*)\z/ =~ inline_function_match.pre_match
|
|
# Remove the macro from the source
|
|
stripped_pre_match = inline_function_match.pre_match.sub(/(#define\s*)\z/, '')
|
|
stripped_post_match = inline_function_match.post_match.sub(/\A(.*[\n]?)/, '')
|
|
inspected_source += stripped_pre_match
|
|
source = stripped_post_match
|
|
next
|
|
end
|
|
|
|
# 2. Determine if we are dealing with an inline function declaration iso function definition
|
|
# If the start of the post-match string is a function-declaration-like string (something ending with semicolon after the function arguments),
|
|
# we are dealing with a inline function declaration
|
|
if /\A#{@function_declaration_parse_base_match}\s*;/m =~ inline_function_match.post_match
|
|
# Only remove the inline part from the function declaration, leaving the function declaration won't do any harm
|
|
inspected_source += inline_function_match.pre_match
|
|
source = inline_function_match.post_match
|
|
next
|
|
end
|
|
|
|
# 3. If we get here, we found an inline function declaration AND inline function body.
|
|
# Remove the function body to transform it into a 'normal' function declaration.
|
|
if /\A#{@function_declaration_parse_base_match}\s*\{/m =~ inline_function_match.post_match
|
|
total_pairs_to_remove = count_number_of_pairs_of_braces_in_function(inline_function_match.post_match)
|
|
|
|
break if total_pairs_to_remove == 0 # Bad source?
|
|
|
|
inline_function_stripped = inline_function_match.post_match
|
|
|
|
total_pairs_to_remove.times do
|
|
inline_function_stripped.sub!(/\s*#{square_bracket_pair_regex_format}/, ';') # Remove inline implementation (+ some whitespace because it's prettier)
|
|
end
|
|
inspected_source += inline_function_match.pre_match
|
|
source = inline_function_stripped
|
|
next
|
|
end
|
|
|
|
# 4. If we get here, it means the regex match, but it is not related to the function (ex. static variable in header)
|
|
# Leave this code as it is.
|
|
inspected_source += inline_function_match.pre_match + inline_function_match[0]
|
|
source = inline_function_match.post_match
|
|
end
|
|
end
|
|
|
|
source
|
|
end
|
|
|
|
def import_source(source, parse_project, cpp = false)
|
|
# let's clean up the encoding in case they've done anything weird with the characters we might find
|
|
source = source.force_encoding('ISO-8859-1').encode('utf-8', :replace => nil)
|
|
|
|
# void must be void for cmock _ExpectAndReturn calls to process properly, not some weird typedef which equates to void
|
|
# to a certain extent, this action assumes we're chewing on pre-processed header files, otherwise we'll most likely just get stuff from @treat_as_void
|
|
@local_as_void = @treat_as_void
|
|
void_types = source.scan(/typedef\s+(?:\(\s*)?void(?:\s*\))?\s+([\w]+)\s*;/)
|
|
if void_types
|
|
@local_as_void += void_types.flatten.uniq.compact
|
|
end
|
|
|
|
# If user wants to mock inline functions,
|
|
# remove the (user specific) inline keywords before removing anything else to avoid missing an inline function
|
|
if @treat_inlines == :include
|
|
@inline_function_patterns.each do |user_format_string|
|
|
source.gsub!(/#{user_format_string}/, '') # remove user defined inline function patterns
|
|
end
|
|
end
|
|
|
|
# smush multiline macros into single line (checking for continuation character at end of line '\')
|
|
source.gsub!(/\s*\\\s*/m, ' ')
|
|
|
|
remove_comments_from_source(source)
|
|
|
|
# remove assembler pragma sections
|
|
source.gsub!(/^\s*#\s*pragma\s+asm\s+.*?#\s*pragma\s+endasm/m, '')
|
|
|
|
# remove gcc's __attribute__ tags
|
|
source.gsub!(/__attribute(?:__)?\s*\(\(+.*\)\)+/, '')
|
|
|
|
# remove preprocessor statements and extern "C"
|
|
source.gsub!(/extern\s+\"C\"\s*\{/, '')
|
|
source.gsub!(/^\s*#.*/, '')
|
|
|
|
# enums, unions, structs, and typedefs can all contain things (e.g. function pointers) that parse like function prototypes, so yank them
|
|
# forward declared structs are removed before struct definitions so they don't mess up real thing later. we leave structs keywords in function prototypes
|
|
source.gsub!(/^[\w\s]*struct[^;\{\}\(\)]+;/m, '') # remove forward declared structs
|
|
source.gsub!(/^[\w\s]*(enum|union|struct|typedef)[\w\s]*\{[^\}]+\}[\w\s\*\,]*;/m, '') # remove struct, union, and enum definitions and typedefs with braces
|
|
# remove problem keywords
|
|
source.gsub!(/(\W)(?:register|auto|restrict)(\W)/, '\1\2')
|
|
source.gsub!(/(\W)(?:static)(\W)/, '\1\2') unless cpp
|
|
|
|
source.gsub!(/\s*=\s*['"a-zA-Z0-9_\.]+\s*/, '') # remove default value statements from argument lists
|
|
source.gsub!(/^(?:[\w\s]*\W)?typedef\W[^;]*/m, '') # remove typedef statements
|
|
source.gsub!(/\)(\w)/, ') \1') # add space between parenthese and alphanumeric
|
|
source.gsub!(/(^|\W+)(?:#{@c_strippables.join('|')})(?=$|\W+)/, '\1') unless @c_strippables.empty? # remove known attributes slated to be stripped
|
|
|
|
# scan standalone function pointers and remove them, because they can just be ignored
|
|
source.gsub!(/\w+\s*\(\s*\*\s*\w+\s*\)\s*\([^)]*\)\s*;/, ';')
|
|
|
|
# scan for functions which return function pointers, because they are a pain
|
|
source.gsub!(/([\w\s\*]+)\(*\(\s*\*([\w\s\*]+)\s*\(([\w\s\*,]*)\)\)\s*\(([\w\s\*,]*)\)\)*/) do |_m|
|
|
functype = "cmock_#{parse_project[:module_name]}_func_ptr#{parse_project[:typedefs].size + 1}"
|
|
unless cpp # only collect once
|
|
parse_project[:typedefs] << "typedef #{Regexp.last_match(1).strip}(*#{functype})(#{Regexp.last_match(4)});"
|
|
"#{functype} #{Regexp.last_match(2).strip}(#{Regexp.last_match(3)});"
|
|
end
|
|
end
|
|
|
|
source = remove_nested_pairs_of_braces(source) unless cpp
|
|
|
|
if @treat_inlines == :include
|
|
# Functions having "{ }" at this point are/were inline functions,
|
|
# User wants them in so 'disguise' them as normal functions with the ";"
|
|
source.gsub!('{ }', ';')
|
|
end
|
|
|
|
# remove function definitions by stripping off the arguments right now
|
|
source.gsub!(/\([^\)]*\)\s*\{[^\}]*\}/m, ';')
|
|
|
|
# drop extra white space to make the rest go faster
|
|
source.gsub!(/^\s+/, '') # remove extra white space from beginning of line
|
|
source.gsub!(/\s+$/, '') # remove extra white space from end of line
|
|
source.gsub!(/\s*\(\s*/, '(') # remove extra white space from before left parens
|
|
source.gsub!(/\s*\)\s*/, ')') # remove extra white space from before right parens
|
|
source.gsub!(/\s+/, ' ') # remove remaining extra white space
|
|
|
|
# split lines on semicolons and remove things that are obviously not what we are looking for
|
|
src_lines = source.split(/\s*;\s*/)
|
|
src_lines = src_lines.uniq unless cpp # must retain closing braces for class/namespace
|
|
src_lines.delete_if { |line| line.strip.empty? } # remove blank lines
|
|
src_lines.delete_if { |line| !(line =~ /[\w\s\*]+\(+\s*\*[\*\s]*[\w\s]+(?:\[[\w\s]*\]\s*)+\)+\s*\((?:[\w\s\*]*,?)*\s*\)/).nil? } # remove function pointer arrays
|
|
|
|
unless @treat_externs == :include
|
|
src_lines.delete_if { |line| !(line =~ /(?:^|\s+)(?:extern)\s+/).nil? } # remove extern functions
|
|
end
|
|
|
|
unless @treat_inlines == :include
|
|
src_lines.delete_if { |line| !(line =~ /(?:^|\s+)(?:inline)\s+/).nil? } # remove inline functions
|
|
end
|
|
|
|
src_lines.delete_if(&:empty?) # drop empty lines
|
|
end
|
|
|
|
# Rudimentary C++ parser - does not handle all situations - e.g.:
|
|
# * A namespace function appears after a class with private members (should be parsed)
|
|
# * Anonymous namespace (shouldn't parse anything - no matter how nested - within it)
|
|
# * A class nested within another class
|
|
def parse_cpp_functions(source)
|
|
funcs = []
|
|
|
|
ns = []
|
|
pub = false
|
|
source.each do |line|
|
|
# Search for namespace, class, opening and closing braces
|
|
line.scan(/(?:(?:\b(?:namespace|class)\s+(?:\S+)\s*)?{)|}/).each do |item|
|
|
if item == '}'
|
|
ns.pop
|
|
else
|
|
token = item.strip.sub(/\s+/, ' ')
|
|
ns << token
|
|
|
|
pub = false if token.start_with? 'class'
|
|
pub = true if token.start_with? 'namespace'
|
|
end
|
|
end
|
|
|
|
pub = true if line =~ /public:/
|
|
pub = false if line =~ /private:/ || line =~ /protected:/
|
|
|
|
# ignore non-public and non-static
|
|
next unless pub
|
|
next unless line =~ /\bstatic\b/
|
|
|
|
line.sub!(/^.*static/, '')
|
|
next unless line =~ @declaration_parse_matcher
|
|
|
|
tmp = ns.reject { |item| item == '{' }
|
|
|
|
# Identify class name, if any
|
|
cls = nil
|
|
if tmp[-1].start_with? 'class '
|
|
cls = tmp.pop.sub(/class (\S+) {/, '\1')
|
|
end
|
|
|
|
# Assemble list of namespaces
|
|
tmp.each { |item| item.sub!(/(?:namespace|class) (\S+) {/, '\1') }
|
|
|
|
funcs << [line.strip.gsub(/\s+/, ' '), tmp, cls]
|
|
end
|
|
funcs
|
|
end
|
|
|
|
def parse_functions(source)
|
|
funcs = []
|
|
source.each { |line| funcs << line.strip.gsub(/\s+/, ' ') if line =~ @declaration_parse_matcher }
|
|
if funcs.empty?
|
|
case @when_no_prototypes
|
|
when :error
|
|
raise 'ERROR: No function prototypes found!'
|
|
when :warn
|
|
puts 'WARNING: No function prototypes found!' unless @verbosity < 1
|
|
end
|
|
end
|
|
funcs
|
|
end
|
|
|
|
def parse_type_and_name(arg)
|
|
# Split up words and remove known attributes. For pointer types, make sure
|
|
# to remove 'const' only when it applies to the pointer itself, not when it
|
|
# applies to the type pointed to. For non-pointer types, remove any
|
|
# occurrence of 'const'.
|
|
arg.gsub!(/(\w)\*/, '\1 *') # pull asterisks away from preceding word
|
|
arg.gsub!(/\*(\w)/, '* \1') # pull asterisks away from following word
|
|
arg_array = arg.split
|
|
arg_info = divine_ptr_and_const(arg)
|
|
arg_info[:name] = arg_array[-1]
|
|
|
|
attributes = arg.include?('*') ? @c_attr_noconst : @c_attributes
|
|
attr_array = []
|
|
type_array = []
|
|
|
|
arg_array[0..-2].each do |word|
|
|
if attributes.include?(word)
|
|
attr_array << word
|
|
elsif @c_calling_conventions.include?(word)
|
|
arg_info[:c_calling_convention] = word
|
|
else
|
|
type_array << word
|
|
end
|
|
end
|
|
|
|
if arg_info[:const_ptr?]
|
|
attr_array << 'const'
|
|
type_array.delete_at(type_array.rindex('const'))
|
|
end
|
|
|
|
arg_info[:modifier] = attr_array.join(' ')
|
|
arg_info[:type] = type_array.join(' ').gsub(/\s+\*/, '*') # remove space before asterisks
|
|
arg_info
|
|
end
|
|
|
|
def parse_args(arg_list)
|
|
args = []
|
|
arg_list.split(',').each do |arg|
|
|
arg.strip!
|
|
return args if arg =~ /^\s*((\.\.\.)|(void))\s*$/ # we're done if we reach void by itself or ...
|
|
|
|
arg_info = parse_type_and_name(arg)
|
|
arg_info.delete(:modifier) # don't care about this
|
|
arg_info.delete(:c_calling_convention) # don't care about this
|
|
|
|
# in C, array arguments implicitly degrade to pointers
|
|
# make the translation explicit here to simplify later logic
|
|
if @treat_as_array[arg_info[:type]] && !(arg_info[:ptr?])
|
|
arg_info[:type] = "#{@treat_as_array[arg_info[:type]]}*"
|
|
arg_info[:type] = "const #{arg_info[:type]}" if arg_info[:const?]
|
|
arg_info[:ptr?] = true
|
|
end
|
|
|
|
args << arg_info
|
|
end
|
|
|
|
# Try to find array pair in parameters following this pattern : <type> * <name>, <@array_size_type> <@array_size_name>
|
|
args.each_with_index do |val, index|
|
|
next_index = index + 1
|
|
next unless args.length > next_index
|
|
|
|
if (val[:ptr?] == true) && args[next_index][:name].match(@array_size_name) && @array_size_type.include?(args[next_index][:type])
|
|
val[:array_data?] = true
|
|
args[next_index][:array_size?] = true
|
|
end
|
|
end
|
|
|
|
args
|
|
end
|
|
|
|
def divine_ptr(arg)
|
|
return false unless arg.include? '*'
|
|
# treat "const char *" and similar as a string, not a pointer
|
|
return false if /(^|\s)(const\s+)?char(\s+const)?\s*\*(?!.*\*)/ =~ arg
|
|
|
|
true
|
|
end
|
|
|
|
def divine_const(arg)
|
|
# a non-pointer arg containing "const" is a constant
|
|
# an arg containing "const" before the last * is a pointer to a constant
|
|
if arg.include?('*') ? (/(^|\s|\*)const(\s(\w|\s)*)?\*(?!.*\*)/ =~ arg) : (/(^|\s)const(\s|$)/ =~ arg)
|
|
true
|
|
else
|
|
false
|
|
end
|
|
end
|
|
|
|
def divine_ptr_and_const(arg)
|
|
divination = {}
|
|
|
|
divination[:ptr?] = divine_ptr(arg)
|
|
divination[:const?] = divine_const(arg)
|
|
|
|
# an arg containing "const" after the last * is a constant pointer
|
|
divination[:const_ptr?] = /\*(?!.*\*)\s*const(\s|$)/ =~ arg ? true : false
|
|
|
|
divination
|
|
end
|
|
|
|
def clean_args(arg_list, parse_project)
|
|
if @local_as_void.include?(arg_list.strip) || arg_list.empty?
|
|
'void'
|
|
else
|
|
c = 0
|
|
# magically turn brackets into asterisks, also match for parentheses that come from macros
|
|
arg_list.gsub!(/(\w+)(?:\s*\[[^\[\]]*\])+/, '*\1')
|
|
# remove space to place asterisks with type (where they belong)
|
|
arg_list.gsub!(/\s+\*/, '*')
|
|
# pull asterisks away from arg to place asterisks with type (where they belong)
|
|
arg_list.gsub!(/\*(\w)/, '* \1')
|
|
|
|
# scan argument list for function pointers and replace them with custom types
|
|
arg_list.gsub!(/([\w\s\*]+)\(+([\w\s]*)\*[\*\s]*([\w\s]*)\s*\)+\s*\(((?:[\w\s\*]*,?)*)\s*\)*/) do |_m|
|
|
functype = "cmock_#{parse_project[:module_name]}_func_ptr#{parse_project[:typedefs].size + 1}"
|
|
funcret = Regexp.last_match(1).strip
|
|
funcdecl = Regexp.last_match(2).strip
|
|
funcname = Regexp.last_match(3).strip
|
|
funcargs = Regexp.last_match(4).strip
|
|
funconst = ''
|
|
if funcname.include? 'const'
|
|
funcname.gsub!('const', '').strip!
|
|
funconst = 'const '
|
|
end
|
|
if funcdecl != ''
|
|
funcdecl += ' '
|
|
end
|
|
parse_project[:typedefs] << "typedef #{funcret}(#{funcdecl}*#{functype})(#{funcargs});"
|
|
funcname = "cmock_arg#{c += 1}" if funcname.empty?
|
|
"#{functype} #{funconst}#{funcname}"
|
|
end
|
|
|
|
# scan argument list for function pointers with shorthand notation and replace them with custom types
|
|
arg_list.gsub!(/([\w\s\*]+)\s+(\w+)\s*\(((?:[\w\s\*]*,?)*)\s*\)*/) do |_m|
|
|
functype = "cmock_#{parse_project[:module_name]}_func_ptr#{parse_project[:typedefs].size + 1}"
|
|
funcret = Regexp.last_match(1).strip
|
|
funcname = Regexp.last_match(2).strip
|
|
funcargs = Regexp.last_match(3).strip
|
|
funconst = ''
|
|
if funcname.include? 'const'
|
|
funcname.gsub!('const', '').strip!
|
|
funconst = 'const '
|
|
end
|
|
parse_project[:typedefs] << "typedef #{funcret}(*#{functype})(#{funcargs});"
|
|
funcname = "cmock_arg#{c += 1}" if funcname.empty?
|
|
"#{functype} #{funconst}#{funcname}"
|
|
end
|
|
|
|
# automatically name unnamed arguments (those that only had a type)
|
|
arg_list.split(/\s*,\s*/).map do |arg|
|
|
parts = (arg.split - ['struct', 'union', 'enum', 'const', 'const*'])
|
|
if (parts.size < 2) || (parts[-1][-1].chr == '*') || @standards.include?(parts[-1])
|
|
"#{arg} cmock_arg#{c += 1}"
|
|
else
|
|
arg
|
|
end
|
|
end.join(', ')
|
|
end
|
|
end
|
|
|
|
def parse_declaration(parse_project, declaration, namespace = [], classname = nil)
|
|
decl = {}
|
|
decl[:namespace] = namespace
|
|
decl[:class] = classname
|
|
|
|
regex_match = @declaration_parse_matcher.match(declaration)
|
|
raise "Failed parsing function declaration: '#{declaration}'" if regex_match.nil?
|
|
|
|
# grab argument list
|
|
args = regex_match[2].strip
|
|
|
|
# process function attributes, return type, and name
|
|
parsed = parse_type_and_name(regex_match[1])
|
|
|
|
# Record original name without scope prefix
|
|
decl[:unscoped_name] = parsed[:name]
|
|
|
|
# Prefix name with namespace scope (if any) and then class
|
|
decl[:name] = namespace.join('_')
|
|
unless classname.nil?
|
|
decl[:name] << '_' unless decl[:name].empty?
|
|
decl[:name] << classname
|
|
end
|
|
# Add original name to complete fully scoped name
|
|
decl[:name] << '_' unless decl[:name].empty?
|
|
decl[:name] << decl[:unscoped_name]
|
|
|
|
decl[:modifier] = parsed[:modifier]
|
|
unless parsed[:c_calling_convention].nil?
|
|
decl[:c_calling_convention] = parsed[:c_calling_convention]
|
|
end
|
|
|
|
rettype = parsed[:type]
|
|
rettype = 'void' if @local_as_void.include?(rettype.strip)
|
|
decl[:return] = { :type => rettype,
|
|
:name => 'cmock_to_return',
|
|
:str => "#{rettype} cmock_to_return",
|
|
:void? => (rettype == 'void'),
|
|
:ptr? => parsed[:ptr?] || false,
|
|
:const? => parsed[:const?] || false,
|
|
:const_ptr? => parsed[:const_ptr?] || false }
|
|
|
|
# remove default argument statements from mock definitions
|
|
args.gsub!(/=\s*[a-zA-Z0-9_\.]+\s*/, ' ')
|
|
|
|
# check for var args
|
|
if args =~ /\.\.\./
|
|
decl[:var_arg] = args.match(/[\w\s]*\.\.\./).to_s.strip
|
|
args = if args =~ /\,[\w\s]*\.\.\./
|
|
args.gsub!(/\,[\w\s]*\.\.\./, '')
|
|
else
|
|
'void'
|
|
end
|
|
else
|
|
decl[:var_arg] = nil
|
|
end
|
|
args = clean_args(args, parse_project)
|
|
decl[:args_string] = args
|
|
decl[:args] = parse_args(args)
|
|
decl[:args_call] = decl[:args].map { |a| a[:name] }.join(', ')
|
|
decl[:contains_ptr?] = decl[:args].inject(false) { |ptr, arg| arg[:ptr?] ? true : ptr }
|
|
|
|
if decl[:return][:type].nil? || decl[:name].nil? || decl[:args].nil? ||
|
|
decl[:return][:type].empty? || decl[:name].empty?
|
|
raise "Failed Parsing Declaration Prototype!\n" \
|
|
" declaration: '#{declaration}'\n" \
|
|
" modifier: '#{decl[:modifier]}'\n" \
|
|
" return: #{prototype_inspect_hash(decl[:return])}\n" \
|
|
" function: '#{decl[:name]}'\n" \
|
|
" args: #{prototype_inspect_array_of_hashes(decl[:args])}\n"
|
|
end
|
|
|
|
decl
|
|
end
|
|
|
|
def prototype_inspect_hash(hash)
|
|
pairs = []
|
|
hash.each_pair { |name, value| pairs << ":#{name} => #{"'" if value.class == String}#{value}#{"'" if value.class == String}" }
|
|
"{#{pairs.join(', ')}}"
|
|
end
|
|
|
|
def prototype_inspect_array_of_hashes(array)
|
|
hashes = []
|
|
array.each { |hash| hashes << prototype_inspect_hash(hash) }
|
|
case array.size
|
|
when 0
|
|
return '[]'
|
|
when 1
|
|
return "[#{hashes[0]}]"
|
|
else
|
|
return "[\n #{hashes.join("\n ")}\n ]\n"
|
|
end
|
|
end
|
|
end
|