Class: CExtractorDefinitions

Inherits:
Object
  • Object
show all
Defined in:
lib/ceedling/c_extractor/c_extractor_definitions.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

Instance Method Summary collapse

Instance Method Details

#try_extract_aggregate_definition(scanner) ⇒ Array(Boolean, String|nil)

Tracks brace depth and uses post-body lookahead to distinguish type definitions from variable declarations — not suitable for collect_balanced()

Try to extract a file-scope struct, enum, or union type definition (non-typedef form). Called as a feature extractor by CExtractor#extract_next_feature. Collects standalone aggregate type definitions (body required, ';' follows '}' directly with only optional whitespace/comments between) as raw text. Comments are replaced with a single space; string literals are verbatim.

Handles:

struct [tag] { member-list };
enum   [tag] { enumerator-list };
union  [tag] { member-list };

Does NOT handle (returns [false, nil], scanner unchanged):

struct/enum/union { ... } declarator;  — variable declaration; falls to variable extractor
struct/enum/union tag;                 — forward declaration without body; not collected

Parameters:

  • scanner (StringScanner)

    positioned at potential aggregate definition

Returns:

  • (Array(Boolean, String|nil))

    [true, "struct Foo { int x; };\n"] — full verbatim aggregate definition text [false, nil ] — not a standalone aggregate definition; scanner unchanged



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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ceedling/c_extractor/c_extractor_definitions.rb', line 87

def try_extract_aggregate_definition(scanner)
  return [false, nil] unless scanner.check(/(?:struct|enum|union)\b/)

  start_pos = scanner.pos
  text      = +''
  depth     = 0

  until scanner.eos?
    ch = scanner.peek(1)

    if ch == '"' || ch == "'"
      # Capture string/char literals verbatim — ';' or '{'/'}' inside must not affect state
      before = scanner.pos
      @c_extractor_code_text.skip_c_string(scanner, ch)
      text << scanner.string[before...scanner.pos]

    elsif scanner.check(%r{/[/*]})
      # Replace comment with a single space — ';' inside must not terminate
      @c_extractor_code_text.skip_comment(scanner)
      text << ' '

    elsif scanner.scan(/\{/)
      depth += 1
      text  << '{'

    elsif scanner.scan(/\}/)
      depth -= 1
      text  << '}'

      if depth == 0
        # Body closed. Speculatively advance past whitespace/comments to peek at next char.
        # Do NOT commit whitespace to text yet — we may need to rollback entirely.
        ws_start = scanner.pos
        loop do
          init = scanner.pos
          scanner.skip(/\s+/)
          @c_extractor_code_text.skip_comment(scanner) if scanner.check(%r{/[/*]})
          break if scanner.pos == init
        end

        if scanner.peek(1) == ';'
          # Standalone type definition — commit
          text << scanner.string[ws_start...scanner.pos]  # include whitespace before ';'
          text << scanner.scan(/;/)
          scanner.scan(/[ \t]*\n/)                         # absorb optional trailing newline
          return [true, text.rstrip]
        else
          # Declarator present (variable name, '*', '[', etc.) — not a standalone type definition.
          # Rollback entirely so the variable extractor sees the full text.
          scanner.pos = start_pos
          return [false, nil]
        end
      end

    elsif depth == 0 && scanner.scan(/;/)
      # Hit ';' at depth 0 before any '{' — forward declaration or variable declaration.
      # Neither is a standalone aggregate type definition; rollback.
      scanner.pos = start_pos
      return [false, nil]

    else
      text << scanner.getch
    end
  end

  # EOF without completing extraction — rollback
  scanner.pos = start_pos
  [false, nil]
end

#try_extract_typedef(scanner) ⇒ Array(Boolean, String)

Tracks brace depth but terminates on ';' rather than '}' — not suitable for collect_balanced() Try to extract a C typedef declaration from the scanner. Called as a feature extractor by CExtractor#extract_next_feature. Collects everything from the typedef keyword through the terminating ; (handling nested braces for struct/union/enum bodies, string literals, and comments) and returns it as a raw string including any trailing newline. Comments are replaced with a single space; string literals are verbatim.

Parameters:

  • scanner (StringScanner)

    positioned at the start of potential typedef

Returns:

  • (Array(Boolean, String))

    [true, "typedef struct { int x; } Point;\n"] — full typedef text [false, nil ] — no typedef keyword here; nothing consumed



24
25
26
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
61
62
63
# File 'lib/ceedling/c_extractor/c_extractor_definitions.rb', line 24

def try_extract_typedef(scanner)
  return [false, nil] unless scanner.check(/typedef\b/)

  text  = +''
  depth = 0   # brace nesting — typedef body terminates only at depth == 0

  until scanner.eos?
    ch = scanner.peek(1)

    if ch == '"' || ch == "'"
      # Capture string/char literals verbatim — a ';' inside must not terminate
      before = scanner.pos
      @c_extractor_code_text.skip_c_string(scanner, ch)
      text << scanner.string[before...scanner.pos]

    elsif scanner.check(%r{/[/*]})
      # Replace comment with a single space — a ';' inside must not terminate
      @c_extractor_code_text.skip_comment(scanner)
      text << ' '

    elsif scanner.scan(/\{/)
      depth += 1
      text  << '{'

    elsif scanner.scan(/\}/)
      depth -= 1
      text  << '}'

    elsif depth == 0 && scanner.scan(/;/)
      text << ';'
      scanner.scan(/[ \t]*\n/)                  # absorb optional trailing newline
      return [true, text.rstrip]

    else
      text << scanner.getch
    end
  end

  [false, nil]   # EOF without finding ';'
end