summaryrefslogtreecommitdiff
path: root/FreeRTOS-Plus/Test/CMock/lib/cmock_header_parser.rb
blob: 8638a04f918a706eb7d7211435f724fd8e020cca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
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