Module

incremental

Incremental re-parsing for Patitas ASTs.

When a user edits one paragraph, only that paragraph's region needs re-parsing. This module accepts a previous Document AST plus the new source text and an edit range, then:

  1. Identifies which top-level blocks overlap the edit range.
  2. Determines the minimal region of source that must be re-parsed.
  3. Re-parses only that region.
  4. Splices the new blocks into the existing AST.

The result is a new Document that is semantically identical to a full re-parse but computed in O(change) rather than O(document).

Fallback:

If the edit cannot be handled incrementally (e.g., the region
detection fails), falls back to a full re-parse.  This guarantees
correctness at all times.

Thread Safety:

``parse_incremental`` is a pure function — safe to call from any thread.

Functions

parse_incremental 6 Document
Parse only the edited region and splice into the existing AST.
def parse_incremental(new_source: str, previous: Document, edit_start: int, edit_end: int, new_length: int, *, source_file: str | None = None) -> Document
Parameters
Name Type Description
new_source str

The complete new source text (after the edit).

previous Document

The Document AST from before the edit.

edit_start int

Offset in the OLD source where the edit begins.

edit_end int

Offset in the OLD source where the edit ends (i.e., the old text from edit_start..edit_end was replaced).

new_length int

Length of the replacement text in the new source. The replaced region in new_source isnew_source[edit_start : edit_start + new_length].

source_file str | None

Optional source file path for location tracking.

Default:None
Returns
Document
_find_affected_range 3 tuple[int | None, int | …
Find the range of block indices affected by an edit. A block is affected if it…
def _find_affected_range(blocks: Sequence[Block], edit_start: int, edit_end: int) -> tuple[int | None, int | None]

Find the range of block indices affected by an edit.

A block is affected if its source range overlaps [edit_start, edit_end). If no blocks overlap, expands to the surrounding blocks to handle boundary changes (e.g., merging two paragraphs).

Returns (first_affected, last_affected) or (None, None).

Parameters
Name Type Description
blocks Sequence[Block]
edit_start int
edit_end int
Returns
tuple[int | None, int | None]
_parse_region 3 tuple[Block, ...] | None
Parse a source region and adjust locations to absolute offsets. Returns None i…
def _parse_region(source: str, offset: int, source_file: str | None) -> tuple[Block, ...] | None

Parse a source region and adjust locations to absolute offsets.

Returns None if parsing fails.

Parameters
Name Type Description
source str
offset int
source_file str | None
Returns
tuple[Block, ...] | None
_shift_block_offset 2 Block
Create a new block with its top-level location offset shifted. Uses ``dataclas…
def _shift_block_offset(block: Block, delta: int) -> Block

Create a new block with its top-level location offset shifted.

Usesdataclasses.replacefor clean frozen-dataclass copying. Only adjusts the top-level location; deep child adjustment is deferred to a future optimization pass.

Parameters
Name Type Description
block Block
delta int
Returns
Block
_adjust_offsets 2 tuple[Block, ...]
Shift location offsets of all blocks by delta.
def _adjust_offsets(blocks: Sequence[Block], delta: int) -> tuple[Block, ...]
Parameters
Name Type Description
blocks Sequence[Block]
delta int
Returns
tuple[Block, ...]
_full_parse 2 Document
Fall back to a full re-parse.
def _full_parse(source: str, source_file: str | None) -> Document
Parameters
Name Type Description
source str
source_file str | None
Returns
Document