Class: CExtractorPreprocessing

Inherits:
Object
  • Object
show all
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

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.

Parameters:

  • directive (String)

    raw directive text (as returned by try_extract_directive)

  • type (Symbol)

    the directive type to match; see MACRO_DEFINITION and other constants

Returns:

  • (String, nil)

    the directive text if it matches the requested type, nil otherwise



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.

Parameters:

  • call_str (String)

    a cleaned macro call string, e.g. "FOO(a, b, [c, d])"

Returns:

  • (Array(String, Array<String>))

    two-element array of [macro_name, params] where macro_name is nil and params is [] if the string is malformed



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.

Parameters:

  • scanner (StringScanner)

    positioned at the start of potential directive

Returns:

  • (Array(Boolean, String))

    [true, '#define FOO 42\n'] — directive text (any directive type) [false, nil ] — no # at current position; nothing consumed



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.

Parameters:

  • scanner (StringScanner)

    positioned anywhere in the source text

  • macro_names (Array<String>)

    macro names to search for

Returns:

  • (Array<String>)

    cleaned macro call strings



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().

Parameters:

  • scanner (StringScanner)

    positioned at potential static assert

Returns:

  • (Array(Boolean, String|nil))

    [true, '_Static_assert(sizeof(S) == 4, "msg");\n'] — full statement text [false, nil ] — not a static assert; scanner unchanged



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