diff --git a/lib/cmock_header_parser.rb b/lib/cmock_header_parser.rb index 53377b9..4ade8cc 100644 --- a/lib/cmock_header_parser.rb +++ b/lib/cmock_header_parser.rb @@ -146,44 +146,35 @@ class CMockHeaderParser 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(' ')), + args << { :type => arg_elements[0..-2].join(' '), :name => arg_elements[-1] }.merge(divine_ptr_and_const(arg)) end return args end - def divine_ptr(arg_type) - return false unless arg_type.include? '*' - return false if arg_type.gsub(/(const|char|\*|\s)+/,'').empty? + 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 return true end def divine_const(arg) - return false if !(/(?:^|\s|\*)const(?:\*|\s|$)/ =~ arg) # check for const as part of a larger word - return true if (/const(?:\w|\s)*\*/ =~ arg) # check const comes before * indicating const data - return false if (/\*\s*const/ =~ arg) # check const comes after * indicating const ptr - return true + # a non-pointer arg containing "const" is a constant + # an arg containing "const" before the last * is a pointer to a constant + return ( arg.include?('*') ? (/(^|\s|\*)const(\s(\w|\s)*)?\*(?!.*\*)/ =~ arg) + : (/(^|\s)const(\s|$)/ =~ arg) ) ? true : false end def divine_ptr_and_const(arg) - divination = { :ptr? => false, :const? => false, :const_ptr? => false } + divination = {} - #first check if there is a pointer present and that it's not part of a C string or function definition - #divination[:ptr?] = (arg.split[0..-2].join.include?('*') && !arg.gsub(/(const|char|\*|\s)+/,'').empty?) - divination[:ptr?] = (arg.include?('*') && !arg.gsub(/(const|char|\*|\s)+/,'').empty?) + divination[:ptr?] = divine_ptr(arg) + divination[:const?] = divine_const(arg) - #if there isn't a const that isn't part of a larger word, we're done - return divination if !(/(?:^|\s|\*)const(?:\*|\s|$)/ =~ arg) - divination[:const?] = true - - # check const comes after * indicating const ptr - if (/\*\s*const/ =~ arg) - divination[:const_ptr?] = true - - #check const comes before * indicating also const data - divination[:const?] = (/const(?:\w|\s)*\*/ =~ arg) ? true : false - end + # an arg containing "const" after the last * is a constant pointer + divination[:const_ptr?] = (/\*(?!.*\*)\s*const(\s|$)/ =~ arg) ? true : false return divination end @@ -237,8 +228,8 @@ class CMockHeaderParser #process function attributes, return type, and name descriptors = regex_match[1] - descriptors.gsub!(/\s+\*/,'*') #remove space to place asterisks with return type (where they belong) - descriptors.gsub!(/\*(\w)/,'* \1') #pull asterisks away from function name to place asterisks with return type (where they belong) + descriptors.gsub!(/(\w)\*/,'\1 *') #pull asterisks away from preceding word + descriptors.gsub!(/\*(\w)/,'* \1') #pull asterisks away from following word descriptors = descriptors.split #array of all descriptor strings #grab name @@ -258,7 +249,7 @@ class CMockHeaderParser end end decl[:modifier] = decl[:modifier].join(' ') - rettype = rettype.join(' ') + rettype = rettype.join(' ').gsub(/\s+\*/,'*') #remove space before asterisks rettype = 'void' if (@local_as_void.include?(rettype.strip)) decl[:return] = { :type => rettype, :name => 'cmock_to_return', diff --git a/test/unit/cmock_header_parser_test.rb b/test/unit/cmock_header_parser_test.rb index a3d4a45..7375c50 100644 --- a/test/unit/cmock_header_parser_test.rb +++ b/test/unit/cmock_header_parser_test.rb @@ -805,6 +805,39 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(expected, @parser.parse("module", source)[:functions]) end + it "should properly handle const before or after return type" do + + sources = [ + "const int * PorkRoast(void);\n", + "int const * PorkRoast(void);\n", + "const int* PorkRoast(void);\n", + "int const* PorkRoast(void);\n", + "const int *PorkRoast(void);\n", + "int const *PorkRoast(void);\n" + ] + + expected = [{ :var_arg=>nil, + :name=>"PorkRoast", + :return=> { :type => "int*", + :name => 'cmock_to_return', + :ptr? => true, + :const? => true, + :const_ptr? => false, + :str => "int* cmock_to_return", + :void? => false + }, + :modifier=>"const", + :contains_ptr? => false, + :args=>[], + :args_string=>"void", + :args_call=>"" + }] + + sources.each do |source| + assert_equal(expected, @parser.parse("module", source)[:functions]) + end + end + it "should properly handle const applied after asterisk in return type (not legal C, but sometimes used)" do source = "int * const PorkRoast(void);\n" @@ -1052,7 +1085,7 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(expected, result[:functions]) end - it "handle arrays and treat them as pointers" do + it "handle arrays and treat them as pointers or strings" do source = "void KeyOperated(CUSTOM_TYPE thing1[], int thing2 [ ], char thing3 [][2 ][ 3], int* thing4[4])" expected = [{:var_arg=>nil, :return=>{ :type => "void", @@ -1068,7 +1101,7 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do :contains_ptr? => true, :args=>[ {:type=>"CUSTOM_TYPE*", :name=>"thing1", :ptr? => true, :const? => false, :const_ptr? => false}, {:type=>"int*", :name=>"thing2", :ptr? => true, :const? => false, :const_ptr? => false}, - {:type=>"char*", :name=>"thing3", :ptr? => true, :const? => false, :const_ptr? => false}, #THIS one will likely change in the future when we improve multidimensional array support + {:type=>"char*", :name=>"thing3", :ptr? => false, :const? => false, :const_ptr? => false}, #THIS one will likely change in the future when we improve multidimensional array support {:type=>"int**", :name=>"thing4", :ptr? => true, :const? => false, :const_ptr? => false} #THIS one will likely change in the future when we improve multidimensional array support ], :args_string=>"CUSTOM_TYPE* thing1, int* thing2, char* thing3, int** thing4", @@ -1407,7 +1440,7 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do :contains_ptr? => true, :args=>[ {:type=>"sqlite3_stmt*", :name=>"cmock_arg2", :ptr? => true, :const? => false, :const_ptr? => false}, {:type=>"int", :name=>"cmock_arg3", :ptr? => false, :const? => false, :const_ptr? => false}, - {:type=>"char*", :name=>"cmock_arg4", :ptr? => true, :const? => true, :const_ptr? => false}, + {:type=>"char*", :name=>"cmock_arg4", :ptr? => false, :const? => true, :const_ptr? => false}, {:type=>"int", :name=>"n", :ptr? => false, :const? => false, :const_ptr? => false}, {:type=>"cmock_module_func_ptr1", :name=>"cmock_arg1", :ptr? => false, :const? => false, :const_ptr? => false} ], @@ -1510,4 +1543,69 @@ describe CMockHeaderParser, "Verify CMockHeaderParser Module" do assert_equal(expected, @parser.parse("module", source)[:functions]) end + it "divines all permutations of ptr, const, and const_ptr correctly" do + truth_table = [ + # argument ptr const const_ptr + [ "constNOTconst constNOTconst", false, false, false ], + [ "const constNOTconst constNOTconst", false, true, false ], + [ "constNOTconst const constNOTconst", false, true, false ], + [ "constNOTconst *constNOTconst", true, false, false ], + [ "const constNOTconst *constNOTconst", true, true, false ], + [ "constNOTconst const *constNOTconst", true, true, false ], + [ "constNOTconst *const constNOTconst", true, false, true ], + [ "const constNOTconst *const constNOTconst", true, true, true ], + [ "constNOTconst const *const constNOTconst", true, true, true ], + [ "constNOTconst **constNOTconst", true, false, false ], + [ "const constNOTconst **constNOTconst", true, false, false ], + [ "constNOTconst const **constNOTconst", true, false, false ], + [ "constNOTconst *const *constNOTconst", true, true, false ], + [ "const constNOTconst *const *constNOTconst", true, true, false ], + [ "constNOTconst const *const *constNOTconst", true, true, false ], + [ "constNOTconst **const constNOTconst", true, false, true ], + [ "const constNOTconst **const constNOTconst", true, false, true ], + [ "constNOTconst const **const constNOTconst", true, false, true ], + [ "constNOTconst *const *const constNOTconst", true, true, true ], + [ "const constNOTconst *const *const constNOTconst", true, true, true ], + [ "constNOTconst const *const *const constNOTconst", true, true, true ] + ] + + truth_table.each do |entry| + assert_equal(@parser.divine_ptr(entry[0]), entry[1]) + assert_equal(@parser.divine_const(entry[0]), entry[2]) + assert_equal(@parser.divine_ptr_and_const(entry[0]), + { ptr?: entry[1], const?: entry[2], const_ptr?: entry[3] }) + end + end + + it "divines ptr correctly for string types" do + truth_table = [ + # argument ptr + [ "char s", false ], + [ "const char s", false ], + [ "char const s", false ], + [ "char *s", false ], + [ "const char *s", false ], + [ "char const *s", false ], + [ "char *const s", false ], + [ "const char *const s", false ], + [ "char const *const s", false ], + [ "char **s", true ], + [ "const char **s", true ], + [ "char const **s", true ], + [ "char *const *s", true ], + [ "const char *const *s", true ], + [ "char const *const *s", true ], + [ "char **const s", true ], + [ "const char **const s", true ], + [ "char const **const s", true ], + [ "char *const *const s", true ], + [ "const char *const *const s", true ], + [ "char const *const *const s", true ] + ] + + truth_table.each do |entry| + assert_equal(@parser.divine_ptr(entry[0]), entry[1]) + end + end + end