diff options
Diffstat (limited to 'FreeRTOS-Plus/Test/CMock/lib/cmock_header_parser.rb')
-rw-r--r-- | FreeRTOS-Plus/Test/CMock/lib/cmock_header_parser.rb | 595 |
1 files changed, 595 insertions, 0 deletions
diff --git a/FreeRTOS-Plus/Test/CMock/lib/cmock_header_parser.rb b/FreeRTOS-Plus/Test/CMock/lib/cmock_header_parser.rb new file mode 100644 index 000000000..8638a04f9 --- /dev/null +++ b/FreeRTOS-Plus/Test/CMock/lib/cmock_header_parser.rb @@ -0,0 +1,595 @@ +# ========================================== +# 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) + @funcs = [] + @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\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) + @module_name = name.gsub(/\W/, '') + @typedefs = [] + @funcs = [] + @normalized_source = nil + function_names = [] + + all_funcs = parse_functions(import_source(source)).map { |item| [item] } + all_funcs += parse_cpp_functions(import_source(source, true)) + all_funcs.map do |decl| + func = parse_declaration(*decl) + unless function_names.include? func[:name] + @funcs << func + function_names << func[:name] + end + end + + @normalized_source = if @treat_inlines == :include + transform_inline_functions(source) + else + '' + end + + { :includes => nil, + :functions => @funcs, + :typedefs => @typedefs, + :normalized_source => @normalized_source } + end + + private if $ThisIsOnlyATest.nil? ################ + + 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) + + # 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*\\\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| + loop do + inline_function_match = source.match(/#{format}/) # Search for inline function declaration + + break if inline_function_match.nil? # No inline functions so nothing to do + + # 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]?)/, '') + source = stripped_pre_match + 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 + source = inline_function_match.pre_match + 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. + 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 + + source = inline_function_match.pre_match + inline_function_stripped # Make new source with the inline function removed and move on to the next + end + end + + source + end + + def import_source(source, 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 (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) + + # 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!(/^\s*#.*/, '') + source.gsub!(/extern\s+\"C\"\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_#{@module_name}_func_ptr#{@typedefs.size + 1}" + unless cpp # only collect once + @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) + 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\*]+)\(+\s*\*[\*\s]*([\w\s]*)\s*\)+\s*\(((?:[\w\s\*]*,?)*)\s*\)*/) do |_m| + functype = "cmock_#{@module_name}_func_ptr#{@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 + @typedefs << "typedef #{funcret}(*#{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_#{@module_name}_func_ptr#{@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 + @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(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) + 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 |