@@ -48,11 +48,12 @@ class Location:
4848class Span:
4949 """Source span from start to stop location."""
5050
51- __slots__ = ("start", "stop")
51+ __slots__ = ("start", "stop", "type_name" )
5252
53- def __init__(self, start: Location, stop: Location):
53+ def __init__(self, start: Location, stop: Location, type_name: str = "" ):
5454 self.start = start
5555 self.stop = stop
56+ self.type_name = type_name
5657
5758 def __repr__(self) -> str:
5859 return f"Span({{self.start}}, {{self.stop}})"
@@ -217,7 +218,7 @@ class Parser:
217218 self.id_to_debuginfo = {{}}
218219 self._current_fragment_id: bytes | None = None
219220 self._relation_id_to_name = {{}}
220- self.provenance: dict[tuple[ int, ...] , Span] = {{}}
221+ self.provenance: dict[int, Span] = {{}}
221222 self._line_starts = _compute_line_starts(input_str)
222223
223224 def _make_location(self, offset: int) -> Location:
@@ -230,14 +231,20 @@ class Parser:
230231 """Return the start offset of the current token."""
231232 return self.lookahead(0).start_pos
232233
233- def record_span(self, start_offset: int) -> None:
234- """Record a span from start_offset to the previous token's end."""
234+ def record_span(self, start_offset: int, type_name: str = "") -> None:
235+ """Record a span from start_offset to the previous token's end.
236+
237+ Uses first-wins semantics: the innermost parse function records first,
238+ and outer wrappers that share the same offset do not overwrite.
239+ """
240+ if start_offset in self.provenance:
241+ return
235242 if self.pos > 0:
236243 end_offset = self.tokens[self.pos - 1].end_pos
237244 else:
238245 end_offset = start_offset
239- span = Span(self._make_location(start_offset), self._make_location(end_offset))
240- self.provenance[tuple() ] = span
246+ span = Span(self._make_location(start_offset), self._make_location(end_offset), type_name )
247+ self.provenance[start_offset ] = span
241248
242249 def lookahead(self, k: int = 0) -> Token:
243250 """Get lookahead token at offset k."""
@@ -349,7 +356,7 @@ class Parser:
349356 # --- Parse methods ---
350357{parse_nonterminal_defns}
351358
352- def parse(input_str: str) -> tuple[Any, dict[tuple[ int, ...] , Span]]:
359+ def parse(input_str: str) -> tuple[Any, dict[int, Span]]:
353360 """Parse input string and return (result, provenance) tuple."""
354361 lexer = Lexer(input_str)
355362 parser = Parser(lexer.tokens, input_str)
0 commit comments