Class: CExtractorDeclarations

Inherits:
Object
  • Object
show all
Includes:
CExtractorConstants, CExtractorTypes
Defined in:
lib/ceedling/c_extractor/c_extractor_declarations.rb

Constant Summary

Constants included from CExtractorConstants

CExtractorConstants::C11_SPECIFIER_KEYWORDS, CExtractorConstants::DECORATOR_KEYWORDS, CExtractorConstants::DEFAULT_CHUNK_SIZE, CExtractorConstants::DEFAULT_MAX_FUNCTION_LENGTH, CExtractorConstants::MODIFIER_KEYWORDS, CExtractorConstants::MSVC_CALLING_CONVENTIONS, CExtractorConstants::PRIVATE_KEYWORDS, CExtractorConstants::TYPE_KEYWORDS, CExtractorConstants::TYPE_QUALIFIER_KEYWORDS

Instance Method Summary collapse

Instance Method Details

#setupObject



19
20
21
22
# File 'lib/ceedling/c_extractor/c_extractor_declarations.rb', line 19

def setup()
  # Aliases
  @code_text = @c_extractor_code_text
end

#try_extract_variable(scanner) ⇒ Object

Attempts to extract a complete variable declaration from the scanner

Scans forward from the current scanner position looking for a complete C variable declaration terminated by a semicolon. Handles complex declaration syntax including:

- Simple variables: `int x;`
- Pointers: `char* ptr;`, `int** buffer;`
- Arrays: `int arr[10];`, `char matrix[3][4];`
- Initializers: `int x = 5;`, `int arr[] = {1, 2, 3};`
- String literals: `char* str = "hello";`
- Qualifiers: `const int MAX;`, `static volatile int flag;`
- Function pointers: `void (*callback)(int);`
- Complex nested structures with balanced parentheses, brackets, and braces
- Compound declarations: `int x, y;` expanded to one struct per variable

The extraction process:

1. Tracks nesting depth of (), [], and {} to handle complex declarations
2. Properly handles string literals (both " and ') including escape sequences
3. Skips comments (both // line comments and /* block comments */)
4. Stops at the first semicolon found at depth 0 (outside all nesting)
5. Validates the extracted text looks like a valid declaration
6. Expands compound declarations and parses each into a CVariableDeclaration struct

Parameters:

scanner: StringScanner positioned at potential start of variable declaration

Returns: Array of [success, declarations]

  • success: Boolean indicating if a valid declaration was found
  • declarations: Array of CVariableDeclaration structs (nil if not found)

Side effects:

On success: Advances scanner position past the semicolon
On failure: Resets scanner position to starting position

Safety:

A declaration with no terminating semicolon runs the scan to the end of
whatever buffer the caller (CExtractor#extract_next_feature) has grown so
far; that caller is the one place enforcing an overall length ceiling,
since only it knows how much more of the file remains to try growing into.


62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# File 'lib/ceedling/c_extractor/c_extractor_declarations.rb', line 62

def try_extract_variable(scanner)
  start_pos = scanner.pos

  # Tracks paren, bracket, and brace depth simultaneously with an inline string state machine — not suitable for collect_balanced()
  paren_depth = 0
  bracket_depth = 0
  brace_depth = 0
  in_string = false
  string_char = nil

  # Scan until we find a semicolon at depth 0.
  # If we reach the end of string scanner, we failed to find something.
  until scanner.eos?
    char = scanner.peek(1)

    # Handle string literals
    if in_string
      if char == '\\'
        scanner.getch
        scanner.getch unless scanner.eos?
        next
      elsif char == string_char
        scanner.getch
        in_string = false
        string_char = nil
        next
      else
        scanner.getch
        next
      end
    end

    case char
    when '"', "'"
      in_string = true
      string_char = char
      scanner.getch
    when '/'
      # Handle comments
      if scanner.peek(2) =~ %r{^(/[/*])}
        if scanner.peek(2) == '//'
          # Line comment -- skip to end of line
          scanner.scan_until(/\n/) || scanner.terminate
        elsif scanner.peek(2) == '/*'
          # Block comment -- skip to closing */
          scanner.pos += 2
          scanner.scan_until(%r{\*/})
        else
          scanner.getch
        end
      else
        scanner.getch
      end
    when '='
      # Track assignment for initializer detection
      scanner.getch
    when '('
      paren_depth += 1
      scanner.getch
    when ')'
      paren_depth -= 1
      scanner.getch
      # Unbalanced parentheses -- not a valid declaration
      if paren_depth < 0
        scanner.pos = start_pos
        return [false, nil]
      end
    when '['
      bracket_depth += 1
      scanner.getch
    when ']'
      bracket_depth -= 1
      scanner.getch
      # Unbalanced brackets -- not a valid declaration
      if bracket_depth < 0
        scanner.pos = start_pos
        return [false, nil]
      end
    when '{'
      # Braces after '=' are initializers, not code blocks
      brace_depth += 1
      scanner.getch
    when '}'
      brace_depth -= 1
      scanner.getch
      # Unbalanced braces -- not a valid declaration
      if brace_depth < 0
        scanner.pos = start_pos
        return [false, nil]
      end
    when ';'
      # Found semicolon - check if it's at depth 0
      if paren_depth == 0 && bracket_depth == 0 && brace_depth == 0
        # This is the end of a declaration
        scanner.getch  # Consume the semicolon

        # Extract the declaration
        declaration = scanner.string[start_pos...scanner.pos]

        # Verify this looks like a valid declaration
        # Must have at least a type and identifier
        # Can end with: word character, ], ), }, or " (for string initializers)
        # `/m` flag: declaration may span multiple lines (e.g. a multiline brace
        # initializer), so '.' must match embedded newlines too
        if declaration =~ /\w+.*[\w\]\)\}"']\s*;$/m
          return [true, expand_and_parse(declaration)]
        else
          scanner.pos = start_pos
          return [false, nil]
        end
      else
        # Semicolon inside parens, brackets, or braces -- keep scanning
        scanner.getch
      end
    else
      scanner.getch
    end
  end

  # Reached end without finding a complete declaration
  scanner.pos = start_pos
  [false, nil]
end