Now I have all the information needed. Let me generate the section content.

# Section 02: State Machine Core

## Overview

This section implements the core state machine that drives GDScript parsing: the `_parse_gd_content()` internal function and the `parse_gd()` public wrapper. The state machine processes lines one at a time, tracking context via states (TOP_LEVEL, IN_FUNCTION, IN_ENUM, IN_MULTILINE_VALUE, IN_INNER_CLASS), and populates a `GdScript` dataclass with all extracted structural information.

This section covers TOP_LEVEL state handling (class declarations, signals, functions, variables, enums, inner classes, annotations, multiline values, property getters/setters), indentation tracking, function end_line detection, comment stripping before matching, and the public `parse_gd(path, known_autoloads)` file-reading wrapper.

**File:** `/Users/salvatorefarruggio/Desktop/godotiq/src/godotiq/parsers/gd_parser.py` (adds state machine to section-01's dataclasses and helpers)

## Dependencies

- **section-01-dataclasses-helpers** must be completed first. This section assumes all 9 dataclasses (GdSignal, GdFunction, GdVariable, GdEnum, GdSignalConnection, GdSignalEmission, GdAutoloadRef, GdPreloadRef, GdScript) and all helper functions (_strip_comment, _split_args_depth_aware, _find_matching_paren, _parse_signal_args, _parse_func_args, _count_emit_args, _parse_enum_body) plus compiled regex patterns are already in place.

- **section-03-body-patterns** handles IN_FUNCTION body scanning (connect, emit, autoload refs, etc.) and can be developed in parallel. This section should leave a clear hook point where IN_FUNCTION body pattern detection will be inserted.

## Compatibility Note

The existing stub defines `parse_gd(content: str)` which raises `NotImplementedError`. This section changes the signature to `parse_gd(path: str, known_autoloads: list[str] | None = None)` to match `parse_tscn(path: str)`. Existing Sprint 0 tests will need updating:

- `tests/test_server.py` line 47-49 tests that `parse_gd("")` raises `NotImplementedError`. After this change, calling `parse_gd("")` will instead raise `FileNotFoundError` (empty string is not a valid file path). This test must be updated to expect `FileNotFoundError` or to pass a nonexistent path.
- `tests/test_scaffolding_int.py` only checks that `parse_gd` is callable and that `GdSignal`, `GdFunction`, `GdScript` can be imported -- those will continue to work.

## Tests (Write First)

These tests go in `/Users/salvatorefarruggio/Desktop/godotiq/tests/test_parsers/test_gd_parser.py`. They use `_parse_gd_content()` directly with inline strings so no temp files are needed.

### TestClassDeclarations

```python
class TestClassDeclarations:
    """Test class_name and extends parsing at TOP_LEVEL."""

    def test_parse_class_name(self):
        """'class_name EconomyManagerClass' sets class_name."""
        result = _parse_gd_content("class_name EconomyManagerClass\n")
        assert result.class_name == "EconomyManagerClass"

    def test_parse_extends(self):
        """'extends Node' sets extends."""
        result = _parse_gd_content("extends Node\n")
        assert result.extends == "Node"

    def test_parse_combined_class_name_extends(self):
        """'class_name Item extends Node' sets both."""
        result = _parse_gd_content("class_name Item extends Node\n")
        assert result.class_name == "Item"
        assert result.extends == "Node"

    def test_parse_extends_file_path(self):
        """'extends \"res://path.gd\"' stores the quoted path as-is."""
        result = _parse_gd_content('extends "res://path.gd"\n')
        assert result.extends == '"res://path.gd"'

    def test_minimal_file(self):
        """A file with only 'extends Node3D' sets extends, everything else empty."""
        result = _parse_gd_content("extends Node3D\n")
        assert result.extends == "Node3D"
        assert result.class_name == ""
        assert result.signals == []
        assert result.functions == []
        assert result.variables == []

    def test_empty_file(self):
        """Empty file returns default GdScript, no exceptions."""
        result = _parse_gd_content("")
        assert result.class_name == ""
        assert result.extends == ""

    def test_missing_file_raises(self):
        """parse_gd with nonexistent path raises FileNotFoundError."""
        with pytest.raises(FileNotFoundError):
            parse_gd("/nonexistent/path/script.gd")
```

### TestSignalParsing

```python
class TestSignalParsing:
    """Test signal declaration parsing."""

    def test_signal_no_args(self):
        """'signal health_depleted' has empty args."""
        result = _parse_gd_content("signal health_depleted\n")
        assert len(result.signals) == 1
        assert result.signals[0].name == "health_depleted"
        assert result.signals[0].args == []

    def test_signal_untyped_args(self):
        """'signal foo(a, b)' produces [('a', ''), ('b', '')]."""
        result = _parse_gd_content("signal foo(a, b)\n")
        assert result.signals[0].args == [("a", ""), ("b", "")]

    def test_signal_typed_args(self):
        """'signal cash_changed(amount: float, delta: float)' captures types."""
        result = _parse_gd_content("signal cash_changed(amount: float, delta: float)\n")
        sig = result.signals[0]
        assert sig.args == [("amount", "float"), ("delta", "float")]

    def test_signal_custom_type(self):
        """Signal args with custom types like 'Printer.State' stored as-is."""
        content = "signal any_state_changed(id: String, old: Printer.State, new: Printer.State)\n"
        result = _parse_gd_content(content)
        sig = result.signals[0]
        assert sig.args[1] == ("old", "Printer.State")
        assert sig.args[2] == ("new", "Printer.State")

    def test_multiple_signals(self):
        """Multiple signal declarations produce correct count."""
        content = "signal a\nsignal b\nsignal c\n"
        result = _parse_gd_content(content)
        assert len(result.signals) == 3

    def test_signal_line_number(self):
        """Signal line numbers match source (1-indexed)."""
        content = "\n\nsignal foo\n"
        result = _parse_gd_content(content)
        assert result.signals[0].line == 3
```

### TestFunctionParsing

```python
class TestFunctionParsing:
    """Test function declaration and end_line tracking."""

    def test_basic_func(self):
        """'func _ready() -> void:' parsed correctly."""
        content = "func _ready() -> void:\n\tpass\n"
        result = _parse_gd_content(content)
        f = result.functions[0]
        assert f.name == "_ready"
        assert f.return_type == "void"
        assert f.args == []

    def test_func_with_typed_params_and_default(self):
        """Function with typed params and defaults produces correct arg triples."""
        content = 'func add_cash(amount: float, description: String = "") -> void:\n\tpass\n'
        result = _parse_gd_content(content)
        f = result.functions[0]
        assert f.args == [("amount", "float", ""), ("description", "String", '""')]
        assert f.return_type == "void"

    def test_func_return_bool(self):
        """'func can_afford(amount: float) -> bool:' captures return type."""
        content = "func can_afford(amount: float) -> bool:\n\treturn true\n"
        result = _parse_gd_content(content)
        assert result.functions[0].return_type == "bool"

    def test_static_func(self):
        """'static func sum2(a, b):' sets is_static=True."""
        content = "static func sum2(a, b):\n\treturn a + b\n"
        result = _parse_gd_content(content)
        assert result.functions[0].is_static is True

    def test_virtual_func(self):
        """'_ready' is both virtual and private."""
        content = "func _ready():\n\tpass\n"
        result = _parse_gd_content(content)
        f = result.functions[0]
        assert f.is_virtual is True
        assert f.is_private is True

    def test_non_virtual_func(self):
        """'add_cash' is neither virtual nor private."""
        content = "func add_cash():\n\tpass\n"
        result = _parse_gd_content(content)
        f = result.functions[0]
        assert f.is_virtual is False
        assert f.is_private is False

    def test_func_no_return_type(self):
        """'func foo():' has empty return_type."""
        content = "func foo():\n\tpass\n"
        result = _parse_gd_content(content)
        assert result.functions[0].return_type == ""

    def test_func_complex_return_type(self):
        """'func get_idle_printers() -> Array[Printer]:' captures generic return."""
        content = "func get_idle_printers() -> Array[Printer]:\n\tpass\n"
        result = _parse_gd_content(content)
        assert result.functions[0].return_type == "Array[Printer]"

    def test_func_arg_with_parens_in_default(self):
        """Depth-aware splitting handles 'Vector2(1, 2)' as a single default."""
        content = "func f(v: Vector2 = Vector2(1, 2)):\n\tpass\n"
        result = _parse_gd_content(content)
        f = result.functions[0]
        assert len(f.args) == 1
        assert f.args[0] == ("v", "Vector2", "Vector2(1, 2)")

    def test_end_line_for_function(self):
        """end_line is last non-blank, non-comment line in function body."""
        content = "func foo():\n\tvar x = 1\n\treturn x\n\n"
        result = _parse_gd_content(content)
        assert result.functions[0].end_line == 3

    def test_end_line_last_function_in_file(self):
        """end_line for last function is last non-blank line of file."""
        content = "func foo():\n\tpass\n"
        result = _parse_gd_content(content)
        assert result.functions[0].end_line == 2

    def test_one_liner_func(self):
        """'func square(a): return a * a' has end_line == line."""
        # One-liner: the entire function is on one line
        content = "func square(a): return a * a\n"
        result = _parse_gd_content(content)
        f = result.functions[0]
        assert f.end_line == f.line
```

### TestVariableParsing

```python
class TestVariableParsing:
    """Test variable and constant declaration parsing."""

    def test_typed_var(self):
        """'var cash: float = 100.0' extracts type and default."""
        content = "var cash: float = 100.0\n"
        result = _parse_gd_content(content)
        v = result.variables[0]
        assert v.name == "cash"
        assert v.type_hint == "float"
        assert v.default_value == "100.0"

    def test_const(self):
        """'const ANSWER = 42' sets is_const=True."""
        content = "const ANSWER = 42\n"
        result = _parse_gd_content(content)
        v = result.variables[0]
        assert v.is_const is True
        assert v.default_value == "42"

    def test_typed_const(self):
        """'const LEVEL_XP: Array[int] = [...]' captures type."""
        content = "const LEVEL_XP: Array[int] = [0, 40, 100]\n"
        result = _parse_gd_content(content)
        v = result.variables[0]
        assert v.is_const is True
        assert v.type_hint == "Array[int]"

    def test_export_same_line(self):
        """'@export var speed: float = 5.0' captures annotation."""
        content = "@export var speed: float = 5.0\n"
        result = _parse_gd_content(content)
        v = result.variables[0]
        assert v.annotation == "@export"
        assert v.name == "speed"

    def test_export_range_same_line(self):
        """'@export_range(0, 100) var hp: int' captures full annotation."""
        content = "@export_range(0, 100) var hp: int\n"
        result = _parse_gd_content(content)
        v = result.variables[0]
        assert v.annotation == "@export_range(0, 100)"

    def test_onready_same_line(self):
        """'@onready var sprite := $Sprite2D' captures annotation."""
        content = "@onready var sprite := $Sprite2D\n"
        result = _parse_gd_content(content)
        v = result.variables[0]
        assert v.annotation == "@onready"

    def test_static_var(self):
        """'static var balance: int = 0' sets is_static=True."""
        content = "static var balance: int = 0\n"
        result = _parse_gd_content(content)
        v = result.variables[0]
        assert v.is_static is True

    def test_class_scope(self):
        """Class-level var has scope='class'."""
        content = "var x: int = 0\n"
        result = _parse_gd_content(content)
        assert result.variables[0].scope == "class"

    def test_local_scope_not_captured(self):
        """Variables inside function bodies are NOT captured."""
        content = "func foo():\n\tvar local = 1\n"
        result = _parse_gd_content(content)
        assert len(result.variables) == 0

    def test_multiline_const_array(self):
        """Multiline const with brackets accumulates complete default_value."""
        content = "const XP: Array[int] = [\n\t0,\n\t40,\n\t100,\n]\n"
        result = _parse_gd_content(content)
        v = result.variables[0]
        assert v.is_const is True
        assert "0" in v.default_value
        assert "100" in v.default_value

    def test_multiline_var_dict(self):
        """Multiline var dict triggers IN_MULTILINE_VALUE too."""
        content = 'var stats: Dictionary = {\n\t"wins": 0,\n\t"losses": 0,\n}\n'
        result = _parse_gd_content(content)
        v = result.variables[0]
        assert "wins" in v.default_value

    def test_property_getter_setter(self):
        """'var health: int:' is captured as variable, getter/setter body skipped."""
        content = "var health: int:\n\tget:\n\t\treturn _health\n\tset(value):\n\t\t_health = value\n"
        result = _parse_gd_content(content)
        # The variable itself is captured
        assert len(result.variables) == 1
        assert result.variables[0].name == "health"
        # The getter/setter body should NOT create extra functions
        assert len(result.functions) == 0

    def test_inline_comment_stripped(self):
        """Inline comment stripped before parsing."""
        content = "var speed: float = 5.0  # movement speed\n"
        result = _parse_gd_content(content)
        assert result.variables[0].default_value == "5.0"

    def test_export_group_discarded(self):
        """@export_group standalone is recognized and discarded."""
        content = '@export_group("Movement")\n@export var speed: float = 5.0\n'
        result = _parse_gd_content(content)
        # The @export_group should not corrupt the annotation buffer
        assert len(result.variables) == 1
        assert result.variables[0].annotation == "@export"
```

### TestEnumParsing

```python
class TestEnumParsing:
    """Test enum declaration parsing."""

    def test_named_enum(self):
        """Named enum with simple values."""
        content = "enum GameState { PLAYING, PAUSED, DAILY_SUMMARY }\n"
        result = _parse_gd_content(content)
        e = result.enums[0]
        assert e.name == "GameState"
        assert e.values == {"PLAYING": None, "PAUSED": None, "DAILY_SUMMARY": None}

    def test_enum_explicit_values(self):
        """Enum with explicit integer values."""
        content = "enum { A, B = 5, C }\n"
        result = _parse_gd_content(content)
        assert result.enums[0].values["B"] == 5
        assert result.enums[0].values["C"] is None

    def test_enum_negative_values(self):
        """Enum with negative integer values."""
        content = "enum { X = -1 }\n"
        result = _parse_gd_content(content)
        assert result.enums[0].values["X"] == -1

    def test_anonymous_enum(self):
        """Anonymous enum has empty name."""
        content = "enum { A, B }\n"
        result = _parse_gd_content(content)
        assert result.enums[0].name == ""

    def test_multiline_enum(self):
        """Multiline enum accumulated via bracket depth."""
        content = "enum State {\n\tIDLE,\n\tRUNNING,\n\tSTOPPED,\n}\n"
        result = _parse_gd_content(content)
        e = result.enums[0]
        assert e.name == "State"
        assert "IDLE" in e.values
        assert "STOPPED" in e.values

    def test_enum_line_number(self):
        """Enum line number matches source."""
        content = "\nenum Foo { A }\n"
        result = _parse_gd_content(content)
        assert result.enums[0].line == 2
```

### TestInnerClasses

```python
class TestInnerClasses:
    """Test inner class detection and body skipping."""

    def test_inner_class_detected(self):
        """'class Circle extends Shape:' adds 'Circle' to inner_classes."""
        content = "class Circle extends Shape:\n\tvar radius: float = 1.0\n\tfunc area():\n\t\treturn PI * radius * radius\n"
        result = _parse_gd_content(content)
        assert "Circle" in result.inner_classes

    def test_inner_class_body_skipped(self):
        """Vars and funcs inside inner class are NOT added to outer class."""
        content = "class Circle extends Shape:\n\tvar radius: float = 1.0\n\tfunc area():\n\t\tpass\n"
        result = _parse_gd_content(content)
        # Inner class vars/funcs should not appear in outer class
        assert len(result.variables) == 0
        assert len(result.functions) == 0

    def test_inner_class_without_extends(self):
        """'class Something:' without extends is detected."""
        content = "class Something:\n\tpass\n"
        result = _parse_gd_content(content)
        assert "Something" in result.inner_classes
```

### TestEdgeCases

```python
class TestEdgeCases:
    """Test edge cases and error handling."""

    def test_empty_string(self):
        """Empty content returns default GdScript."""
        result = _parse_gd_content("")
        assert result.class_name == ""
        assert result.extends == ""
        assert result.signals == []

    def test_only_comments(self):
        """File with only comments returns empty GdScript."""
        result = _parse_gd_content("# This is a comment\n## Doc comment\n")
        assert result.class_name == ""

    def test_malformed_line_no_raise(self):
        """Malformed lines are silently skipped."""
        content = "extends Node\n!@#$%^&*()\nvar x: int = 1\n"
        result = _parse_gd_content(content)
        assert result.extends == "Node"
        assert len(result.variables) == 1

    def test_unclosed_multiline_at_eof(self):
        """Unclosed multiline construct at EOF closes with accumulated content."""
        content = "const XP: Array[int] = [\n\t0,\n\t40,\n"
        result = _parse_gd_content(content)
        assert len(result.variables) == 1

    def test_annotation_buffer_not_corrupted_by_rpc(self):
        """@rpc before func does not corrupt annotation buffer."""
        content = "@rpc\nfunc remote_action():\n\tpass\n@export var x: int = 1\n"
        result = _parse_gd_content(content)
        assert result.variables[0].annotation == "@export"

    def test_export_group_no_buffer_corruption(self):
        """@export_group does not leave an annotation in the buffer."""
        content = '@export_group("Group")\nvar plain: int = 1\n'
        result = _parse_gd_content(content)
        # plain var should NOT have @export_group as annotation
        assert result.variables[0].annotation == ""
```

## Implementation Details

### State Enum

Define a simple string-based state tracking (or use an enum/constants):

```python
# States for the line-by-line state machine
_STATE_TOP_LEVEL = "TOP_LEVEL"
_STATE_IN_FUNCTION = "IN_FUNCTION"
_STATE_IN_ENUM = "IN_ENUM"
_STATE_IN_MULTILINE_VALUE = "IN_MULTILINE_VALUE"
_STATE_IN_INNER_CLASS = "IN_INNER_CLASS"
```

### The `_parse_gd_content()` Function

This is the core of the parser. It takes a raw string content and optional `known_autoloads` list, and returns a `GdScript` dataclass.

```python
def _parse_gd_content(content: str, known_autoloads: list[str] | None = None) -> GdScript:
    """Parse GDScript content string into a GdScript dataclass.

    This is the internal parsing function. Use parse_gd() for file-based parsing.

    Args:
        content: Raw GDScript source text.
        known_autoloads: Optional list of known autoload names for detection.

    Returns:
        Populated GdScript dataclass. Never raises on malformed content.
    """
```

**Algorithm outline:**

1. Initialize `result = GdScript()` and `state = _STATE_TOP_LEVEL`
2. Initialize tracking variables: `func_indent`, `current_func`, `enum_buffer`, `enum_depth`, `multiline_buffer`, `multiline_depth`, `multiline_var`, `inner_class_indent`, `annotation_buffer`, `last_nonblank_line`
3. Split content into lines, iterate with 1-indexed line numbers
4. For each line:
   a. **All-state scanning:** Check for `preload(...)` / `load(...)` patterns and `is_instance_valid(...)` regardless of state. Append to `result.preload_refs` or `result.is_instance_valid_lines`.
   b. Skip blank lines and full-comment lines (but record them for end_line tracking)
   c. Strip inline comments via `_strip_comment()` before regex matching
   d. **Dispatch by state:**

### TOP_LEVEL State Processing

The TOP_LEVEL state handles all class-level declarations. Processing order matters to avoid ambiguity:

1. **Check for `class_name ... extends ...` combined pattern first** -- if matched, set both `result.class_name` and `result.extends`, continue
2. **Check for `class_name`** -- set `result.class_name`
3. **Check for `extends`** -- set `result.extends`
4. **Check for `signal`** -- parse signal name and optional args using `_parse_signal_args()`, create `GdSignal`, append to `result.signals`
5. **Check for `enum`** -- if single-line (contains `{...}`), parse with `_parse_enum_body()` and append `GdEnum`. If multiline start (has `{` but no `}`), enter IN_ENUM state, begin accumulating
6. **Check for annotation + var on same line** -- regex `^(@\w+(?:\([^)]*\))?)\s+(var|const)\s+` matches lines like `@export var speed: float = 5.0`. Extract the annotation string and parse the var portion
7. **Check for standalone annotation** -- if `@export*` or `@onready`, buffer it. If `@export_group`, `@export_subgroup`, `@export_category`, discard (grouping annotations). If `@rpc`, `@warning_ignore`, `@tool`, `@icon`, `@abstract`, `@static_unload`, discard (non-var annotations)
8. **Check for inner class** -- `^class\s+(\w+)\s*(?:extends\s+[\w.]+)?\s*:` -- append name to `result.inner_classes`, enter IN_INNER_CLASS, record indent
9. **Check for `static func` or `func`** -- detect function start, use depth-aware paren matching to extract full arg list, parse return type after `)`, create `GdFunction`, set `is_static`, `is_virtual`, `is_private`, enter IN_FUNCTION state
10. **Check for `static var`** -- strip `static ` prefix, parse as variable with `is_static=True`
11. **Check for `var` or `const`** -- parse variable/constant declaration. Apply buffered annotation if present, clear buffer. Check for:
    - Trailing colon without `=` (property getter/setter) -- enter IN_FUNCTION to skip body
    - Unclosed brackets in default value -- enter IN_MULTILINE_VALUE
    - Normal single-line -- append to `result.variables`

### IN_FUNCTION State Processing

Track the function's starting indent level (always 0 for class-level functions). On each line:

1. Skip blank and comment-only lines (do not trigger function end)
2. Measure indent of current non-blank line
3. If indent <= func_indent AND line is not blank/comment: exit IN_FUNCTION, set `current_func.end_line` to the last recorded non-blank, non-comment line number, return to TOP_LEVEL, and **re-process the current line** in TOP_LEVEL
4. Otherwise: scan for body patterns (connect, emit, autoload refs -- implemented in section-03). Record `last_nonblank_line` for end_line tracking.

**One-liner detection:** If the function declaration line contains a `:` followed by non-whitespace code (e.g., `func square(a): return a * a`), it is a one-liner. Set `end_line = line` immediately and stay in/return to TOP_LEVEL.

### IN_ENUM State Processing

1. Append each line to `enum_buffer`
2. Count `{` and `}` to track depth
3. When depth returns to 0, join buffer, parse with `_parse_enum_body()`, create `GdEnum`, append to `result.enums`, return to TOP_LEVEL

### IN_MULTILINE_VALUE State Processing

1. Append each line to `multiline_buffer`
2. Count `[`/`]` and `{`/`}` to track bracket depth
3. When depth returns to 0, join buffer into `multiline_var.default_value`, append `multiline_var` to `result.variables`, return to TOP_LEVEL

### IN_INNER_CLASS State Processing

1. Skip blank and comment-only lines
2. Measure indent of current non-blank line
3. If indent <= `inner_class_indent`: return to TOP_LEVEL, re-process current line
4. Otherwise: skip line (inner class body is not parsed)

### End-of-File Cleanup

After all lines are processed:

1. If still in IN_FUNCTION: set `current_func.end_line` to last non-blank line, close the function
2. If still in IN_MULTILINE_VALUE: set default_value from accumulated buffer, append variable
3. If still in IN_ENUM: parse accumulated buffer, append enum

### Annotation Handling Detail

The annotation buffer (`annotation_buffer: str`) holds a pending annotation to apply to the next variable:

- **Set** when a standalone `@export*` or `@onready` line is encountered
- **Consumed** when the next `var` or `const` line is parsed -- assigned to `variable.annotation`, then cleared
- **NOT set** by `@export_group`, `@export_subgroup`, `@export_category`, `@rpc`, `@warning_ignore`, `@tool`, `@icon`, `@abstract`, `@static_unload` -- these are silently discarded
- **Cleared** if a non-variable line consumes the buffer position (e.g., `func` after `@export` on its own line -- this is malformed but should not corrupt state)

### Indentation Measurement

```python
def _get_indent(line: str) -> int:
    """Return count of leading whitespace characters."""
    return len(line) - len(line.lstrip())
```

This handles both tabs and spaces. Blank lines (all whitespace) are handled by checking `line.strip()` before measuring indent.

### Comment Stripping

The `_strip_comment()` helper (from section-01) is called on each line before regex matching. It removes the portion from the first unquoted `#` to end of line. Lines that are entirely comments (`line.strip().startswith("#")`) are detected separately and skipped without stripping.

### The `parse_gd()` Public Wrapper

```python
def parse_gd(path: str, known_autoloads: list[str] | None = None) -> GdScript:
    """Parse a .gd file into a structured GdScript representation.

    Args:
        path: Absolute or relative path to a .gd file.
        known_autoloads: Optional list of known autoload singleton names
                         for accurate autoload reference detection.

    Returns:
        Parsed GdScript dataclass with all extracted structural information.
        Never raises on malformed content -- uses best-effort parsing.

    Raises:
        FileNotFoundError: If the file does not exist.
    """
```

This function:
1. Opens the file at `path` and reads its content (using `Path(path).read_text(encoding="utf-8")`)
2. Calls `_parse_gd_content(content, known_autoloads)`
3. Returns the result
4. If the file does not exist, the `FileNotFoundError` from `Path.read_text()` propagates naturally

### Updating the Existing Sprint 0 Test

The existing test in `/Users/salvatorefarruggio/Desktop/godotiq/tests/test_server.py` (line 47-49):

```python
def test_parse_gd_stub_raises(self) -> None:
    with pytest.raises(NotImplementedError):
        parse_gd("")
```

Must be updated to:

```python
def test_parse_gd_stub_raises(self) -> None:
    with pytest.raises((NotImplementedError, FileNotFoundError)):
        parse_gd("/nonexistent/path.gd")
```

Or, since the parser is now implemented, change it to test that a missing file raises `FileNotFoundError`:

```python
def test_parse_gd_missing_file_raises(self) -> None:
    with pytest.raises(FileNotFoundError):
        parse_gd("/nonexistent/path.gd")
```

### Hook Point for Section 03

The IN_FUNCTION state body should include a clearly marked location where section-03 will add body pattern detection (connect, emit, autoload refs). A minimal approach is to have the IN_FUNCTION processing call a helper function like `_scan_body_line(line, lineno, result, known_autoloads, autoload_regex)` that section-03 will implement. Initially this can be a no-op or a pass-through that only tracks `last_nonblank_line`.

Similarly, the all-state scanning for `preload`/`load` and `is_instance_valid` can be implemented as part of this section (they are simple regex matches) or deferred to section-03. Given that `preload`/`load` and `is_instance_valid` are documented as "all lines regardless of state" patterns, implementing them here makes the state machine complete for TOP_LEVEL concerns. The plan places them in section-03, so coordinate accordingly -- either implement here or leave clearly marked hooks.

### Regex Patterns Used by This Section

These are the module-level compiled regex patterns that the state machine dispatches on. They should already be defined in section-01, but are listed here for reference:

- `_RE_CLASS_NAME_EXTENDS`: `^class_name\s+(\w+)\s+extends\s+(.+)$`
- `_RE_CLASS_NAME`: `^class_name\s+(\w+)`
- `_RE_EXTENDS`: `^extends\s+(.+)$`
- `_RE_SIGNAL`: `^signal\s+(\w+)(?:\(([^)]*)\))?`
- `_RE_FUNC`: `^(?:static\s+)?func\s+(\w+)\s*\(`
- `_RE_VAR`: `^(var|const)\s+(\w+)\s*(?::\s*([^=:]+?))?\s*(?:(?::=|=)\s*(.+))?$`
- `_RE_ENUM_SINGLE`: `^enum\s+(\w+)?\s*\{([^}]*)\}`
- `_RE_ENUM_START`: `^enum\s+(\w+)?\s*\{`
- `_RE_INNER_CLASS`: `^class\s+(\w+)\s*(?:extends\s+[\w.]+)?\s*:`
- `_RE_ANNOTATION_VAR`: `^(@\w+(?:\([^)]*\))?)\s+(var|const)\s+`
- `_RE_ANNOTATION`: `^@(\w+)(?:\(([^)]*)\))?`
- `_RE_PRELOAD_LOAD`: `(?:preload|load)\(\s*"([^"]+)"\s*\)`
- `_RE_IS_INSTANCE_VALID`: `is_instance_valid\(`

### Module-Level Exports

After this section, the module's `__all__` or importable symbols should include:
- All 9 dataclasses: `GdSignal`, `GdFunction`, `GdVariable`, `GdEnum`, `GdSignalConnection`, `GdSignalEmission`, `GdAutoloadRef`, `GdPreloadRef`, `GdScript`
- `parse_gd` (public API)
- `_parse_gd_content` (internal, but used by tests)

## Summary Checklist

1. Write the test classes listed above in `/Users/salvatorefarruggio/Desktop/godotiq/tests/test_parsers/test_gd_parser.py`
2. Implement `_parse_gd_content()` with the 5-state machine in `/Users/salvatorefarruggio/Desktop/godotiq/src/godotiq/parsers/gd_parser.py`
3. Implement `parse_gd()` as the public file-reading wrapper
4. Update the Sprint 0 test in `/Users/salvatorefarruggio/Desktop/godotiq/tests/test_server.py` to expect `FileNotFoundError` instead of `NotImplementedError`
5. Leave hook points for section-03 body pattern detection in IN_FUNCTION state
6. Verify all existing 127 tests still pass with `pytest -v`