Class: CExtractorPreprocessing
- Defined in:
- lib/ceedling/c_extractor/c_extractor_preprocessing.rb
Overview
=========================================================================
Ceedling - Test-Centered Build System for C ThrowTheSwitch.org Copyright (c) 2010-26 Mike Karlesky, Mark VanderVoord, & Greg Williams SPDX-License-Identifier: MIT
Constant Summary collapse
- MACRO_DEFINITION =
Directive type symbols for use with filter_directive()
:macro_definition
Instance Method Summary collapse
-
#filter_directive(directive, type) ⇒ String?
Filter a directive string by type, returning the text only if it matches the requested type.
-
#parse_macro_call(call_str) ⇒ Array(String, Array<String>)
Parse a single macro call string (as returned by
try_extract_macro_calls) into its macro name and an array of individual parameter strings. -
#try_extract_directive(scanner) ⇒ Array(Boolean, String)
Try to extract a C preprocessing directive from the scanner.
-
#try_extract_macro_calls(scanner, macro_names) ⇒ Array<String>
Scan
scannerfor calls to any macro inmacro_namesand return them as a flat Array of cleaned strings in order of appearance. -
#try_extract_static_assert(scanner) ⇒ Array(Boolean, String|nil)
Try to extract a C11 _Static_assert or C23 static_assert statement from the scanner.
Instance Method Details
#filter_directive(directive, type) ⇒ String?
Filter a directive string by type, returning the text only if it matches the requested type. This allows callers to selectively collect specific directive types while still ensuring all directives are consumed from the input.
104 105 106 107 108 109 |
# File 'lib/ceedling/c_extractor/c_extractor_preprocessing.rb', line 104 def filter_directive(directive, type) case type when MACRO_DEFINITION directive.match?(/\A#\s*define\b/) ? directive : nil end end |
#parse_macro_call(call_str) ⇒ Array(String, Array<String>)
Parse a single macro call string (as returned by try_extract_macro_calls) into its
macro name and an array of individual parameter strings.
Top-level commas (not nested inside (), [], {}, or string literals) are
treated as argument separators. Each returned parameter is trimmed of leading
and trailing whitespace.
72 73 74 75 76 77 78 79 80 |
# File 'lib/ceedling/c_extractor/c_extractor_preprocessing.rb', line 72 def parse_macro_call(call_str) scanner = StringScanner.new( call_str ) # Extract macro name — everything before the opening '(' macro_name = scanner.scan( /[^(]+/ )&.strip return [nil, []] if macro_name.nil? || !scanner.scan( /\(/ ) return [macro_name, _split_params(scanner)] end |
#try_extract_directive(scanner) ⇒ Array(Boolean, String)
Try to extract a C preprocessing directive from the scanner. Called as a feature extractor by CExtractor#extract_next_feature. Returns every directive found as raw text — callers filter by type as needed.
90 91 92 93 94 95 |
# File 'lib/ceedling/c_extractor/c_extractor_preprocessing.rb', line 90 def try_extract_directive(scanner) text = _collect_directive(scanner) return [false, nil] if text.nil? [true, text] end |
#try_extract_macro_calls(scanner, macro_names) ⇒ Array<String>
Scan scanner for calls to any macro in macro_names and return them as a
flat Array of cleaned strings in order of appearance. Each string is the full
macro call with its argument list on a single line, runs of whitespace
(including embedded newlines) collapsed to a single space.
- Macro calls inside C line or block comments are skipped.
- String literals (including those containing commas, parens, or a macro name) are collected verbatim and do not cause false positives or broken extraction.
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 |
# File 'lib/ceedling/c_extractor/c_extractor_preprocessing.rb', line 27 def try_extract_macro_calls(scanner, macro_names) results = [] pattern = _build_pattern(macro_names) until scanner.eos? # Skip comments — macro names inside are not extracted if scanner.check( %r{/[/*]} ) @c_extractor_code_text.skip_comment(scanner) # Skip string/char literals — macro names inside are not extracted elsif (ch = scanner.peek(1)) == '"' || ch == "'" @c_extractor_code_text.skip_c_string(scanner, ch) # Found a macro name followed by '(' — collect its argument list. # Guard against matching a suffix of a longer identifier (e.g., FOO inside NOTFOO). # Note: `\b` and lookbehinds are unreliable in StringScanner because the engine # only sees the remaining suffix; we inspect the original string manually instead. elsif scanner.scan(pattern) macro_name = scanner[1] match_start = scanner.pos - scanner.matched.length if match_start > 0 && scanner.string[match_start - 1] =~ /\w/ _collect_balanced_args(scanner) # consume and discard — part of a longer identifier else args = _collect_balanced_args(scanner) results << _clean_whitespace("#{macro_name}(#{args})") if args end else scanner.getch end end return results end |
#try_extract_static_assert(scanner) ⇒ Array(Boolean, String|nil)
Try to extract a C11 _Static_assert or C23 static_assert statement from the scanner. Called as a feature extractor by CExtractor#extract_next_feature. The statement is consumed and the full text returned, but callers discard it — static asserts are not collected into CModule.
Handles all three forms:
_Static_assert(expr, "message"); # C11 — message required
static_assert(expr); # C23 — message optional
static_assert(expr, "message"); # C23 — with message
The expression argument may contain arbitrarily nested parentheses (e.g. sizeof(struct S) == 8) which are handled by collect_balanced().
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 |
# File 'lib/ceedling/c_extractor/c_extractor_preprocessing.rb', line 128 def try_extract_static_assert(scanner) return [false, nil] unless scanner.check(/(?:_Static_assert|static_assert)\b/) text = +'' # Consume keyword # Pattern (?:_Static_assert|static_assert) ensures that a longer identifier (e.g. `not_static_assert`) does not match text << scanner.scan(/(?:_Static_assert|static_assert)/) # Consume optional whitespace between keyword and '(' text << (scanner.scan(/[ \t]*/) || '') # Consume the balanced argument list — handles all nested parens, strings, comments success, args = @c_extractor_code_text.collect_balanced(scanner, '(', ')') return [false, nil] unless success text << args # Consume optional whitespace before ';' text << (scanner.scan(/[ \t]*/) || '') # Consume the required terminating ';' return [false, nil] unless scanner.scan(/;/) text << ';' # Absorb optional trailing newline (mirrors try_extract_typedef convention) text << (scanner.scan(/[ \t]*\n/) || '') [true, text] end |