Class: PreprocessinatorExtractor

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

Instance Method Summary collapse

Instance Method Details

#extract_file_as_array_from_expansion(input, filepath) ⇒ Object

‘input` must have the interface of IO – StringIO for testing or File in typical use



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
# File 'lib/ceedling/preprocessinator_extractor.rb', line 78

def extract_file_as_array_from_expansion(input, filepath)

  # Iterate through all lines and alternate between extract and ignore modes.
  # All lines between a '#' line containing the file name of our filepath and the
  # next '#' line should be extracted.
  #
  # Notes:
  #  1. Successive blocks can all be from the same source text file without a different, intervening '#' line.
  #     Multiple back-to-back blocks could all begin with '# 99 "path/file.c"'.
  #  2. The first line of the file could start a text block we care about.
  #  3. End of file could end a text block.

  base_name  = File.basename( filepath )
  # Preprocessor output blocks take the form of '# <digits> <text> [optional digits]'
  directive  = /^# \d+ \"/
  # Preprocessor output blocks for the file we care about take the form of '# <digits> "path/filename.ext" [optional digits]'
  marker     = /^# \d+ \".*#{Regexp.escape(base_name)}\"/
  # Boolean to ping pong between line-by-line extract/ignore
  extract    = false

  lines = []

  # Use `each_line()` instead of `readlines()` (chomp removes newlines).
  # `each_line()` processes the IO buffer one line at a time instead of ingesting lines in an array.
  # At large buffer sizes needed for potentially lengthy preprocessor output this is far more memory efficient and faster.
  input.each_line( chomp:true ) do |line|
    
    # Clean up any oddball characters in an otherwise ASCII document
    encoding_options = {
      :invalid           => :replace,  # Replace invalid byte sequences
      :undef             => :replace,  # Replace anything not defined in ASCII
      :replace           => '',        # Use a blank for those replacements
      :universal_newline => true       # Always break lines with \n
    }
    line = line.encode("ASCII", **encoding_options).encode('UTF-8')

    # Handle extraction if the line is not a preprocessor directive
    if extract and not line =~ directive
      # Strip a line so we can omit useless blank lines
      _line = line.strip()
      # Restore text with left-side whitespace if previous stripping left some text
      _line = line.rstrip() if !_line.empty?
      lines << _line

    # Otherwise the line contained a preprocessor directive; drop out of extract mode
    else
      extract = false
    end

    # Enter extract mode if the line is a preprocessor directive with filename of interest
    extract = true if line =~ marker
  end

  return lines
end

#extract_file_as_string_from_expansion(input, filepath) ⇒ Object

Simple variation of preceding that returns file contents as single string



136
137
138
# File 'lib/ceedling/preprocessinator_extractor.rb', line 136

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



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/ceedling/preprocessinator_extractor.rb', line 161

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



181
182
183
184
185
186
187
188
# File 'lib/ceedling/preprocessinator_extractor.rb', line 181

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



155
156
157
# File 'lib/ceedling/preprocessinator_extractor.rb', line 155

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

#extract_test_directive_macro_calls(file_contents) ⇒ Object

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



142
143
144
145
146
147
148
149
150
151
# File 'lib/ceedling/preprocessinator_extractor.rb', line 142

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 = [
    /#{UNITY_TEST_SOURCE_FILE}.+?"\)/,
    /#{UNITY_TEST_INCLUDE_PATH}.+?"\)/
  ]

  return extract_tokens_by_regex_list( file_contents, *regexes )
end