Class: PreprocessinatorReconstructor

Inherits:
Object
  • Object
show all
Defined in:
lib/ceedling/preprocess/preprocessinator_reconstructor.rb

Instance Method Summary collapse

Instance Method Details

#compact_file_from_expansion(input_filepath:, source_filepath:, output_filepath:) ⇒ Object

File-based convenience wrapper around compact_from_expansion. Opens input_filepath for reading and output_filepath for writing, then delegates to compact_from_expansion with the resulting IO objects.



106
107
108
109
110
111
112
113
114
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 106

def compact_file_from_expansion(input_filepath:, source_filepath:, output_filepath:)
  # Open input in binary mode: GCC output under non-C locale contains non-ASCII bytes
  # (localized markers). Per-line clean_encoding in _scan_expansion_for_file handles content.
  File.open( input_filepath, 'rb' ) do |input|
    File.open( output_filepath, 'w' ) do |output|
      compact_from_expansion( input: input, filepath: source_filepath, output: output )
    end
  end
end

#compact_from_expansion(input:, filepath:, output:) ⇒ Object

Writes only C code from input preprocessor expansion belonging to filepath to output IO object incrementally (one logical line at a time) without building an intermediate array. output must respond to puts (e.g. File or StringIO).



98
99
100
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 98

def compact_from_expansion(input:, filepath:, output:)
  _scan_expansion_for_file(input, filepath) { |line| output.puts(line) }
end

#extract_file_as_array_from_expansion(input, filepath) ⇒ Object

input must have the interface of IO -- StringIO for testing or File in typical use



82
83
84
85
86
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 82

def extract_file_as_array_from_expansion(input, filepath)
  lines = []
  _scan_expansion_for_file(input, filepath) { |line| lines << line }
  return lines
end

#extract_file_as_string_from_expansion(input, filepath) ⇒ Object

Simple variation of preceding that returns file contents as single string



90
91
92
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 90

def extract_file_as_string_from_expansion(input, filepath)
  return extract_file_as_array_from_expansion(input, filepath).join( "\n" )
end

#extract_include_guard(file_contents) ⇒ Object

Find include guard in file contents as string



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 213

def extract_include_guard(file_contents)
  # Look for first occurrence of #ifndef <sring> followed by #define <string>
  regex = /#\s*ifndef\s+(\w+)(?:\s*\n)+\s*#\s*define\s+(\w+)/
  matches = file_contents.match( regex )

  # Return if no match results
  return nil if matches.nil?

  # Return if match results are not expected size
  return nil if matches.size != 3

  # Return if #ifndef <string> does not match #define <string>
  return nil if matches[1] != matches[2]

  # Return string in common
  return matches[1]
end

#extract_macro_defs(file_contents, include_guard) ⇒ Object

Extract all macro definitions as a list from a file as string



233
234
235
236
237
238
239
240
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 233

def extract_macro_defs(file_contents, include_guard)
  macro_definitions = extract_multiline_directives( file_contents, 'define' )

  # Remove an include guard if provided
  macro_definitions.reject! {|macro| macro.include?( include_guard ) } if !include_guard.nil?

  return macro_definitions
end

#extract_pragmas(file_contents) ⇒ Object

Extract all pragmas as a list from a file as string



207
208
209
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 207

def extract_pragmas(file_contents)
  return extract_multiline_directives( file_contents, 'pragma' )
end

#extract_test_case_directives(file_contents) ⇒ Object

Extract TEST_CASE()/TEST_RANGE()/TEST_MATRIX() calls paired with the name of the test function each stack of calls immediately precedes.

Unlike extract_test_directive_macro_calls above, this cannot be built on top of extract_tokens_by_regex_list(). That helper (via @parsing_parcels.code_lines) hands the regex one physical/continuation-joined line at a time, so a pattern spanning "TEST_CASE(...)\nvoid test_Foo(" could never match -- and that split-across-two-lines layout is the overwhelmingly common real-world case (each macro call is a line of its own, the function signature is the next line down). So we scan the whole string in one pass instead, letting PATTERNS::TEST_CASE_DIRECTIVE's /m flag see across line breaks.

file_contents is expected to already have had comments stripped and #if/#ifdef/#include directives (and their line markers) resolved away -- true of both callers in PreprocessinatorFileAssembler#collect_test_file_contents: the non-fallback path reads the directives-only GCC output (already comment-stripped in place by PreprocessinatorCommentStripper before this ever runs, and already stripped of line markers by _scan_expansion_for_file above), and the fallback path reads text already run through @parsing_parcels.code_lines (which strips comments) plus conditional filtering. So there's nothing here to confuse the adjacency test between a macro call and the function signature that follows it.



150
151
152
153
154
155
156
157
158
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 150

def extract_test_case_directives(file_contents)
  pairs = []

  file_contents.scan( PATTERNS::TEST_CASE_DIRECTIVE ) do |directive, function|
    pairs << { function: function, directive: directive.strip.split( "\n" ).map( &:rstrip ) }
  end

  return pairs
end

#extract_test_directive_macro_calls(file_contents) ⇒ Object

Extract all test directive macros as a list from a file as string



118
119
120
121
122
123
124
125
126
127
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 118

def extract_test_directive_macro_calls(file_contents)
  # Look for TEST_SOURCE_FILE("...") and TEST_INCLUDE_PATH("...") in a string (i.e. a file's contents as a string)

  regexes = [
    /(#{PATTERNS::TEST_SOURCE_FILE})/,
    /(#{PATTERNS::TEST_INCLUDE_PATH})/
  ]

  return extract_tokens_by_regex_list( file_contents, *regexes ).map(&:first)
end

#splice_test_case_directives(contents:, directives:) ⇒ Object

Reinsert TEST_CASE()/TEST_RANGE()/TEST_MATRIX() calls (extracted above from a macro-preserving preprocessor pass) into contents (built from a fully expanded preprocessor pass, where these macros -- real Unity macros that #define to nothing -- have already vanished without a trace) immediately ahead of the matching test function's line. contents is a scanning artifact only (never compiled), so reinserting this raw, unexpanded macro text back into it is safe: Unity's own runner generator is the only consumer, and it only cares that the text is there, immediately before the function.

A single cursor walks contents forward as each directive is placed, rather than independently searching the whole array per directive. Two reasons: it mirrors the existing precedent for this exact original-order correlation problem elsewhere in Ceedling (GeneratorTestRunner#parse_test_file's line-number remap, and Unity's own find_tests() line-number lookup), and it avoids ever misattributing a directive to an earlier stray match of the same function name (e.g. a forward declaration) instead of the real definition that follows it in file order.



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
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 176

def splice_test_case_directives(contents:, directives:)
  cursor = 0

  directives.each do |pair|
    function_regex = /^\s*void\s+#{Regexp.escape(pair[:function])}\s*\(/

    # Search forward from the cursor only. If the function isn't found -- e.g. it was
    # compiled out by an inactive #ifdef branch, resolved identically in both the source
    # this directive came from and in `contents` itself -- there's nothing to attach the
    # directive to, and dropping it silently is correct: an orphaned directive is inert.
    index = nil
    contents[cursor..-1].each_with_index do |line, offset|
      if line =~ function_regex
        index = cursor + offset
        break
      end
    end
    next if index.nil?

    contents.insert( index, *pair[:directive] )

    # Advance past the directive lines just inserted and the function line itself so the
    # next directive's search starts after this one, preserving file order.
    cursor = index + pair[:directive].size + 1
  end

  return contents
end