Class: PreprocessinatorReconstructor
- Defined in:
- lib/ceedling/preprocess/preprocessinator_reconstructor.rb
Instance Method Summary collapse
-
#compact_file_from_expansion(input_filepath:, source_filepath:, output_filepath:) ⇒ Object
File-based convenience wrapper around
compact_from_expansion. -
#compact_from_expansion(input:, filepath:, output:) ⇒ Object
Writes only C code from
inputpreprocessor expansion belonging tofilepathtooutputIO object incrementally (one logical line at a time) without building an intermediate array. -
#extract_file_as_array_from_expansion(input, filepath) ⇒ Object
inputmust have the interface of IO -- StringIO for testing or File in typical use. -
#extract_file_as_string_from_expansion(input, filepath) ⇒ Object
Simple variation of preceding that returns file contents as single string.
-
#extract_include_guard(file_contents) ⇒ Object
Find include guard in file contents as string.
-
#extract_macro_defs(file_contents, include_guard) ⇒ Object
Extract all macro definitions as a list from a file as string.
-
#extract_pragmas(file_contents) ⇒ Object
Extract all pragmas as a list from a file as string.
-
#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.
-
#extract_test_directive_macro_calls(file_contents) ⇒ Object
Extract all test directive macros as a list from a file as string.
-
#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#defineto nothing -- have already vanished without a trace) immediately ahead of the matching test function's line.
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 115 116 117 |
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 106 def compact_file_from_expansion(input_filepath:, source_filepath:, output_filepath:) # Binary mode on both ends: GCC output under non-C locale contains non-ASCII # bytes (localized markers; per-line clean_encoding in _scan_expansion_for_file # handles content), and the compacted output is read again downstream by code # that depends on its line count being exact. Windows text mode would rewrite # "\n" to "\r\n" on write, which this content must not be subject to. @file_wrapper.open( input_filepath, 'rb' ) do |input| @file_wrapper.open( output_filepath, 'wb' ) 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
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 |
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 216 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
236 237 238 239 240 241 242 243 |
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 236 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
210 211 212 |
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 210 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.
153 154 155 156 157 158 159 160 161 |
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 153 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
121 122 123 124 125 126 127 128 129 130 |
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 121 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.
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 |
# File 'lib/ceedling/preprocess/preprocessinator_reconstructor.rb', line 179 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 |