diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 9a1d447..bdf83a3 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -1,35 +1,41 @@ class CMockHeaderParser - attr_accessor :match_type, :attribute_match, :decl_modifier, :decl_return, :decl_function, :decl_args - + attr_accessor :match_type, :attribute_match, :src_lines, :funcs, :c_attributes, :declaration_parse_matcher, :included + def initialize(source, match_type=/\w+\**/, attributes=['static', '__monitor', '__ramfunc', '__irq', '__fiq']) - source = source.gsub(/\/\/.*$/, '') #remove line comments - source = source.gsub(/\/\*.*?\*\//m, '') #remove block comments - @lines = source.split(/(^\s*\#.*$) # Treat preprocessor directives as a logical line - | (;|\{|\}) /x) # Match ;, {, and } as end of lines - @lines.delete_if {|line| line =~ /\\\n/} #ignore lines that contain continuation lines - @lines.delete_if {|line| line =~ /typedef/} #ignore lines that contain typedef statements - @lines.delete_if {|line| line =~ /\#define/} #remove defines - - @functions = nil + import_source(source) + @funcs = nil @match_type = match_type @c_attributes = attributes @declaration_parse_matcher = /(\w*\s+)*([^\s]+)\s+(\w+)\s*\(([^\)]*)\)/ @included = nil - @var_args_ellipsis = '...' end def parse mod = {:includes => nil, :externs => nil, :functions => []} mod[:includes] = included_files mod[:externs] = externs - functions.each do |decl| - mod[:functions] << parse_declaration(decl) + parse_functions + if !@funcs.nil? and @funcs.length > 0 + @funcs.each do |decl| + mod[:functions] << parse_declaration(decl) + end end return mod end private + + def import_source(source) + source = source.gsub(/\/\/.*$/, '') #remove line comments + source = source.gsub(/\/\*.*?\*\//m, '') #remove block comments + @src_lines = source.split(/(^\s*\#.*$) # Treat preprocessor directives as a logical line + | (;|\{|\}) /x) # Match ;, {, and } as end of lines + @src_lines.delete_if {|line| line.length < 1} + @src_lines.delete_if {|line| line =~ /\\\n/} #ignore lines that contain continuation lines + @src_lines.delete_if {|line| line =~ /typedef/i} #ignore lines that contain typedef statements + @src_lines.delete_if {|line| line =~ /\#define/i} #remove defines + end def c_attributes=(value) @c_attributes = value @@ -39,9 +45,11 @@ class CMockHeaderParser def included_files if @included.nil? @included = [] - @lines.each do |line| - if line =~ /#include\s+"(.*)"/ - @included << $1 + if !@src_lines.nil? and @src_lines.length > 0 + @src_lines.each do |line| + if line =~ /#include\s+"(.*)"/ + @included << $1 + end end end end @@ -52,28 +60,30 @@ class CMockHeaderParser if !@externs @externs = [] depth = 0 - @lines.each do |line| - if depth.zero? && line =~ /^\s*extern.*/m - @externs << $&.strip.gsub(/\s+/, ' ') - end - if line =~ /\{/ - depth += 1 - end - if line =~ /\}/ - depth -= 1 + if !@src_lines.nil? and @src_lines.length > 0 + @src_lines.each do |line| + if depth.zero? && line =~ /^\s*extern.*/m + @externs << $&.strip.gsub(/\s+/, ' ') + end + if line =~ /\{/ + depth += 1 + end + if line =~ /\}/ + depth -= 1 + end end end end return @externs end - def functions - if @functions.nil? - @functions = [] + def parse_functions + if @funcs.nil? + @funcs = [] depth = 0 - @lines.each do |line| + @src_lines.each do |line| if depth.zero? && line =~ /#{@attribute_match}\s*#{@match_type}\s+\w+\s*\(.*\)/m - @functions << line.strip.gsub(/\s+/, ' ') + @funcs << line.strip.gsub(/\s+/, ' ') end if line =~ /\{/ depth += 1 @@ -84,21 +94,21 @@ class CMockHeaderParser end end #eliminate define functions - @functions ||= functions.reject do |func| + @funcs ||= @funcs.reject do |func| func =~ /\bdefine\b/ end - return @functions + return @funcs end def parse_args(arg_list) args = [] arg_list.split(',').each do |arg| arg = arg.strip - return args if ((arg == @var_args_ellipsis) || (arg == 'void')) + return args if ((arg == '...') || (arg == 'void')) arg_match = arg.match /^(.+)\s+(\*?\w+)$/ raise "Failed parsing argument list at argument: '#{arg}'" if arg_match.nil? - #put the asterix with the type (where it belongs) + #put the asterisk with the type (where it belongs) if (arg_match[-1][0] == '*') arg_match[1] << '*' arg_match[-1].slice!(0) @@ -119,15 +129,21 @@ class CMockHeaderParser decl[:modifier] = modifier.strip decl[:rettype] = $2 decl[:name] = $3 - args = $4 + + #put the asterisk with the type (where it belongs) + if (decl[:name][0] == '*') + decl[:rettype] << '*' + decl[:name].slice!(0) + end + #remove default parameter statements from mock definitions args.gsub!(/=\s*[a-zA-Z0-9_\.]+\s*\,/, ',') args.gsub!(/=\s*[a-zA-Z0-9_\.]+\s*/, ' ') #check for var args if (args =~ /\.\.\./) - decl[:var_arg] = args.match( /[\w\s]*\.\.\./ ).to_s + decl[:var_arg] = args.match( /[\w\s]*\.\.\./ ).to_s.strip if (args =~ /\,[\w\s]*\.\.\./) args = args.gsub!(/\,[\w\s]*\.\.\./,'') else diff --git a/rakefile_helper.rb b/rakefile_helper.rb index 93d878e..504b98c 100644 --- a/rakefile_helper.rb +++ b/rakefile_helper.rb @@ -88,9 +88,8 @@ module RakefileHelpers def execute(command_string, verbose=true) report command_string - output = `#{command_string}` - report(output) if verbose - report '' + output = `#{command_string}`.chomp + report(output) if (verbose && !output.nil? && (output.length > 0)) if $?.exitstatus != 0 raise "Command failed. (Returned #{$?.exitstatus})" end diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb new file mode 100644 index 0000000..2e7f1d0 --- /dev/null +++ b/test/unit/cmock_header_parser_test.rb @@ -0,0 +1,356 @@ +require File.expand_path(File.dirname(__FILE__)) + "/../test_helper" +require File.expand_path(File.dirname(__FILE__)) + "/../../lib/cmock_header_parser" + +class CMockHeaderParserTest < Test::Unit::TestCase + + def setup + @parser = CMockHeaderParser.new("//some contents") + end + + def teardown + end + + should "create and initialize variables to defaults appropriately" do + @parser = CMockHeaderParser.new("//some contents") + assert_nil(@parser.funcs) + assert_equal(/\w+\**/, @parser.match_type) + assert_equal(['static', '__monitor', '__ramfunc', '__irq', '__fiq'], @parser.c_attributes) + assert_equal(/(\w*\s+)*([^\s]+)\s+(\w+)\s*\(([^\)]*)\)/, @parser.declaration_parse_matcher) + assert_nil(@parser.included) + end + + should "strip out line comments" do + source = + " abcd;\n" + + "// hello;\n" + + "who // is you\n" + @parser = CMockHeaderParser.new(source) + + expected = + [ + " abcd", + ";", + "\n\nwho \n" + ] + + assert_equal(expected, @parser.src_lines) + end + + should "remove block comments" do + source = + " abcd;\n" + + "/* hello;*/\n" + + "who /* is you\n" + + "whatdya say? */\n" + + "/* shizzzle*/" + @parser = CMockHeaderParser.new(source) + + expected = + [ + " abcd", + ";", + "\n\nwho \n" + ] + + assert_equal(expected, @parser.src_lines) + end + + should "treat preprocessor directives as single line" do + #flunk "this substitution generates an empty first line element?" + source = + "#when stuff_happens\n" + + "#ifdef _TEST\n" + + "#pragma stack_switch" + @parser = CMockHeaderParser.new(source) + + expected = + [ + "#when stuff_happens", + "\n", + "#ifdef _TEST", + "\n", + "#pragma stack_switch" + ] + + assert_equal(expected, @parser.src_lines) + end + + should "Match ; { and } as end of line characters" do + source = + " i like ice cream; and i can eat { vanilla, chocolate } when I want to; so there!" + @parser = CMockHeaderParser.new(source) + + expected = + [ + " i like ice cream", + ";", + " and i can eat ", + "{", + " vanilla, chocolate ", + "}", + " when I want to", + ";", + " so there!" + ] + + assert_equal(expected, @parser.src_lines) + end + + should "ignore lines that contain continuation characters" do + source = + "hoo hah \\\n" + + "when \\ \n" + @parser = CMockHeaderParser.new(source) + + expected = + [ + ] + + assert_equal(expected, @parser.src_lines) + end + + should "ignore lines that contain typedef statements" do + source = + "#typedef uint32 (unsigned int)\n" + + "whack me? #typedef int INT\n" + + "#typedef who cares what really comes here\n" + + "this should remain!" + @parser = CMockHeaderParser.new(source) + + expected = + [ + "\nthis should remain!" + ] + + assert_equal(expected, @parser.src_lines) + end + + should "remove defines" do + source = + "hello;\n" + + "#define whatever you feel like defining\n" + + "#DEFINE I JUST DON'T CARE\n" + + "#deFINE\n" + + @parser = CMockHeaderParser.new(source) + + expected = + [ + "hello", + ";", + "\n", + "\n", + "\n", + "\n" + ] + + assert_equal(expected, @parser.src_lines) + end + + should "extract and return included files" do + source = + "int Foo(int a, unsigned int b);" + + "hello;\n" + + "#define whatever you feel like defining\n" + + "#include \"myheader.h\"\n" + + "#include \t \"os.h\"\n" + + @parser = CMockHeaderParser.new(source) + parsed_stuff = @parser.parse + + expected = + [ + "myheader.h", + "os.h" + ] + + assert_equal(expected, parsed_stuff[:includes]) + end + + should "extract and return externs" do + source = + "extern int Foo(int a, unsigned int b);\n" + + "extern unsigned int NumSamples;\n" + + "extern FOO_TYPE fooFun;" + + @parser = CMockHeaderParser.new(source) + + expected = + [ + "extern int Foo(int a, unsigned int b)", + ";", + "\nextern unsigned int NumSamples", + ";", + "\nextern FOO_TYPE fooFun", + ";" + ] + + assert_equal(expected, @parser.src_lines) + end + + should "extract and return function declarations" do + + source = + "int Foo(int a, unsigned int b);\n" + + "void bar \n(uint la, int de, bool da);\n" + + "void\n shiz(void);\n" + + "void tat();\n" + + @parser = CMockHeaderParser.new(source) + parsed_stuff = @parser.parse + + expected = + [ + { + :modifier => "", + :args_string => "int a, unsigned int b", + :rettype => "int", + :var_arg => nil, + :args => [{:type => "int", :name => "a"}, {:type => "unsigned int", :name => "b"}], + :name => "Foo" + }, + + { + :modifier => "", + :args_string => "uint la, int de, bool da", + :rettype => "void", + :var_arg => nil, + :args => + [ + {:type => "uint", :name => "la"}, + {:type => "int", :name => "de"}, + {:type => "bool", :name => "da"} + ], + :name => "bar" + }, + + { + :modifier => "", + :args_string => "void", + :rettype => "void", + :var_arg => nil, + :args => [], + :name => "shiz" + }, + + { + :modifier => "", + :args_string => "", + :rettype => "void", + :var_arg => nil, + :args => [], + :name => "tat" + } + ] + + assert_equal(expected, parsed_stuff[:functions]) + end + + should "extract and return function declarations with attributes" do + + source = + "static \tint \n Foo(int a, unsigned int b);\n" + + "goodness \t bool bar \n(uint la, int de, bool da);\n" + + @parser = CMockHeaderParser.new(source, /\w+\**/, ['static', 'goodness']) + parsed_stuff = @parser.parse + + expected = + [ + { + :modifier => "static", + :args_string => "int a, unsigned int b", + :rettype => "int", + :var_arg => nil, + :args => [{:type => "int", :name => "a"}, {:type => "unsigned int", :name => "b"}], + :name => "Foo" + }, + + { + :modifier => "goodness", + :args_string => "uint la, int de, bool da", + :rettype => "bool", + :var_arg => nil, + :args => + [ + {:type => "uint", :name => "la"}, + {:type => "int", :name => "de"}, + {:type => "bool", :name => "da"} + ], + :name => "bar" + } + ] + + assert_equal(expected, parsed_stuff[:functions]) + end + + should "extract and return function declarations with variable argument lists" do + + source = + "\tint \n printf(char * const format, ...);\n" + + "bool bar \n(...);\n" + + @parser = CMockHeaderParser.new(source) + parsed_stuff = @parser.parse + + expected = + [ + { + :modifier => "", + :args_string => "char * const format", + :rettype => "int", + :var_arg => "...", + :args => [{:type => "char * const", :name => "format"}], + :name => "printf" + }, + { + :modifier => "", + :args_string => "void", + :rettype => "bool", + :var_arg => "...", + :args => [], + :name => "bar" + } + ] + + assert_equal(expected, parsed_stuff[:functions]) + end + + should "attach * to type" do + + source = + "MY_STRUCT* HooWah(char * format);\n" + + "bool* HotShot(HIS_STRUCT *p, unsigned int * pint);\n" + + @parser = CMockHeaderParser.new(source) + parsed_stuff = @parser.parse + + expected = + [ + { + :modifier => "", + :args_string => "char * format", + :rettype => "MY_STRUCT*", + :var_arg => nil, + :args => [{:type => "char *", :name => "format"}], + :name => "HooWah" + }, + + { + :modifier => "", + :args_string => "HIS_STRUCT *p, unsigned int * pint", + :rettype => "bool*", + :var_arg => nil, + :args => + [ + {:type => "HIS_STRUCT", :name => "*p"}, + {:type => "unsigned int *", :name => "pint"} + ], + :name => "HotShot" + } + ] + + assert_equal(expected, parsed_stuff[:functions]) + end + +end \ No newline at end of file