From 85ce6b2513fa12a5f66c532d664e4ba9f2443a12 Mon Sep 17 00:00:00 2001 From: Mark VanderVoord Date: Fri, 28 Feb 2014 10:58:57 -0500 Subject: [PATCH] Fix encoding issues for anyone using a somewhat modern Ruby (1.9 or greater) --- lib/cmock.rb | 3 ++ lib/cmock_config.rb | 72 ++++++++++++------------- lib/cmock_header_parser.rb | 75 ++++++++++++++------------- test/unit/cmock_header_parser_test.rb | 1 + 4 files changed, 79 insertions(+), 72 deletions(-) diff --git a/lib/cmock.rb b/lib/cmock.rb index 4208dd2..627aa27 100644 --- a/lib/cmock.rb +++ b/lib/cmock.rb @@ -13,6 +13,9 @@ "cmock_generator_utils", "cmock_unityhelper_parser"].each {|req| require "#{File.expand_path(File.dirname(__FILE__))}/#{req}"} + +$QUICK_RUBY_VERSION = RUBY_VERSION.split('.').inject(0){|vv,v| vv * 100 + v.to_i } + class CMock def initialize(options=nil) diff --git a/lib/cmock_config.rb b/lib/cmock_config.rb index 34b27a6..a50cb32 100644 --- a/lib/cmock_config.rb +++ b/lib/cmock_config.rb @@ -2,46 +2,46 @@ # 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 CMockConfig - - CMockDefaultOptions = + + CMockDefaultOptions = { - :framework => :unity, - :mock_path => 'mocks', - :mock_prefix => 'Mock', - :plugins => [], - :strippables => ['(?:__attribute__\s*\(+.*?\)+)'], - :attributes => ['__ramfunc', '__irq', '__fiq', 'register', 'extern'], - :c_calling_conventions => ['__stdcall', '__cdecl', '__fastcall'], - :enforce_strict_ordering => false, - :unity_helper_path => false, - :treat_as => {}, - :treat_as_void => [], - :memcmp_if_unknown => true, - :when_no_prototypes => :warn, #the options being :ignore, :warn, or :error - :when_ptr => :compare_data, #the options being :compare_ptr, :compare_data, or :smart - :verbosity => 2, #the options being 0 errors only, 1 warnings and errors, 2 normal info, 3 verbose - :treat_externs => :exclude, #the options being :include or :exclude - :ignore => :args_and_calls, #the options being :args_and_calls or :args_only - :callback_include_count => true, - :callback_after_arg_check => false, - :includes => nil, - :includes_h_pre_orig_header => nil, - :includes_h_post_orig_header => nil, - :includes_c_pre_header => nil, - :includes_c_post_header => nil + :framework => :unity, + :mock_path => 'mocks', + :mock_prefix => 'Mock', + :plugins => [], + :strippables => ['(?:__attribute__\s*\(+.*?\)+)'], + :attributes => ['__ramfunc', '__irq', '__fiq', 'register', 'extern'], + :c_calling_conventions => ['__stdcall', '__cdecl', '__fastcall'], + :enforce_strict_ordering => false, + :unity_helper_path => false, + :treat_as => {}, + :treat_as_void => [], + :memcmp_if_unknown => true, + :when_no_prototypes => :warn, #the options being :ignore, :warn, or :error + :when_ptr => :compare_data, #the options being :compare_ptr, :compare_data, or :smart + :verbosity => 2, #the options being 0 errors only, 1 warnings and errors, 2 normal info, 3 verbose + :treat_externs => :exclude, #the options being :include or :exclude + :ignore => :args_and_calls, #the options being :args_and_calls or :args_only + :callback_include_count => true, + :callback_after_arg_check => false, + :includes => nil, + :includes_h_pre_orig_header => nil, + :includes_h_post_orig_header => nil, + :includes_c_pre_header => nil, + :includes_c_post_header => nil, } - + def initialize(options=nil) case(options) - when NilClass then options = CMockDefaultOptions.clone + when NilClass then options = CMockDefaultOptions.clone when String then options = CMockDefaultOptions.clone.merge(load_config_file_from_yaml(options)) when Hash then options = CMockDefaultOptions.clone.merge(options) else raise "If you specify arguments, it should be a filename or a hash of options" end - + #do some quick type verification [:plugins, :attributes, :treat_as_void].each do |opt| unless (options[opt].class == Array) @@ -59,30 +59,30 @@ class CMockConfig options[:plugins].compact! options[:plugins].map! {|p| p.to_sym} @options = options - + treat_as_map = standard_treat_as_map()#.clone treat_as_map.merge!(@options[:treat_as]) @options[:treat_as] = treat_as_map - + @options.each_key { |key| eval("def #{key.to_s}() return @options[:#{key.to_s}] end") } end - + def load_config_file_from_yaml yaml_filename require 'yaml' require 'fileutils' YAML.load_file(yaml_filename)[:cmock] end - + def set_path(path) @src_path = path end - + def load_unity_helper return File.new(@options[:unity_helper_path]).read if (@options[:unity_helper_path]) return nil end - def standard_treat_as_map + def standard_treat_as_map { 'int' => 'INT', 'char' => 'INT8', diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 4ae8492..edfaaae 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -2,12 +2,12 @@ # 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_attributes, :treat_as_void, :treat_externs - + def initialize(cfg) @funcs = [] @c_strippables = cfg.strippables @@ -22,31 +22,34 @@ class CMockHeaderParser @treat_externs = cfg.treat_externs @c_strippables += ['extern'] if (@treat_externs == :include) #we'll need to remove the attribute if we're allowing externs end - + def parse(name, source) @module_name = name.gsub(/\W/,'') @typedefs = [] @funcs = [] function_names = [] - - parse_functions( import_source(source) ).map do |decl| + + parse_functions( import_source(source) ).map do |decl| func = parse_declaration(decl) unless (function_names.include? func[:name]) @funcs << func function_names << func[:name] end end - + { :includes => nil, :functions => @funcs, :typedefs => @typedefs } end - + private if $ThisIsOnlyATest.nil? ################ - + def import_source(source) + # 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) if ($QUICK_RUBY_VERSION > 10900) + # 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 @@ -54,25 +57,25 @@ class CMockHeaderParser if void_types @local_as_void += void_types.flatten.uniq.compact 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!(/\/\*.*?\*\//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(/__attrbute__\s*\(\(\.*\)\)/, '') - - # remove preprocessor statements and extern "C" + + # 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 @@ -81,21 +84,21 @@ class CMockHeaderParser 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+)(?:#{@c_strippables.join('|')})(?=$|\W+)/,'\1') unless @c_strippables.empty? # remove known attributes slated to be stripped - + #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}" @typedefs << "typedef #{$1.strip}(*#{functype})(#{$4});" "#{functype} #{$2.strip}(#{$3});" end - + #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.delete_if {|line| line.strip.length == 0} # remove blank lines @@ -113,23 +116,23 @@ class CMockHeaderParser if funcs.empty? case @when_no_prototypes when :error - raise "ERROR: No function prototypes found!" + raise "ERROR: No function prototypes found!" when :warn puts "WARNING: No function prototypes found!" unless (@verbosity < 1) end end return funcs end - + def parse_args(arg_list) args = [] arg_list.split(',').each do |arg| - arg.strip! + arg.strip! return args if (arg =~ /^\s*((\.\.\.)|(void))\s*$/) # we're done if we reach void by itself or ... arg_array = arg.split arg_elements = arg_array - @c_attributes # split up words and remove known attributes - args << { :type => (arg_type =arg_elements[0..-2].join(' ')), - :name => arg_elements[-1], + args << { :type => (arg_type =arg_elements[0..-2].join(' ')), + :name => arg_elements[-1], :ptr? => divine_ptr(arg_type), :const? => arg_array.include?('const') } @@ -151,7 +154,7 @@ class CMockHeaderParser arg_list.gsub!(/(\w+)(?:\s*\[[\s\d\w+-]*\])+/,'*\1') # magically turn brackets into asterisks arg_list.gsub!(/\s+\*/,'*') # remove space to place asterisks with type (where they belong) arg_list.gsub!(/\*(\w)/,'* \1') # pull asterisks away from arg to place asterisks with type (where they belong) - + #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| @@ -168,25 +171,25 @@ class CMockHeaderParser 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 { |arg| + arg_list.split(/\s*,\s*/).map { |arg| parts = (arg.split - ['struct', 'union', 'enum', 'const', 'const*']) if ((parts.size < 2) or (parts[-1][-1].chr == '*') or (@standards.include?(parts[-1]))) - "#{arg} cmock_arg#{c+=1}" + "#{arg} cmock_arg#{c+=1}" else arg end }.join(', ') end end - + def parse_declaration(declaration) decl = {} - + regex_match = @declaration_parse_matcher.match(declaration) - raise "Failed parsing function declaration: '#{declaration}'" if regex_match.nil? - + raise "Failed parsing function declaration: '#{declaration}'" if regex_match.nil? + #grab argument list args = regex_match[2].strip @@ -214,17 +217,17 @@ class CMockHeaderParser decl[:modifier] = decl[:modifier].join(' ') rettype = rettype.join(' ') rettype = 'void' if (@local_as_void.include?(rettype.strip)) - decl[:return] = { :type => rettype, - :name => 'cmock_to_return', + decl[:return] = { :type => rettype, + :name => 'cmock_to_return', :ptr? => divine_ptr(rettype), :const? => rettype.split(/\s/).include?('const'), :str => "#{rettype} cmock_to_return", :void? => (rettype == 'void') } - + #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 @@ -241,7 +244,7 @@ class CMockHeaderParser 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? or decl[:name].nil? or decl[:args].nil? or decl[:return][:type].empty? or decl[:name].empty?) raise "Failed Parsing Declaration Prototype!\n" + diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index b82288e..ba8d646 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -5,6 +5,7 @@ # ========================================== $ThisIsOnlyATest = true +$QUICK_RUBY_VERSION = RUBY_VERSION.split('.').inject(0){|vv,v| vv * 100 + v.to_i } require File.expand_path(File.dirname(__FILE__)) + "/../test_helper" require 'cmock_header_parser'