@@ -29,6 +29,20 @@ def __getitem__(self, i):
2929 else :
3030 return self .data [i ]
3131
32+ @dataclass
33+ class SourceSpan :
34+ """Identifies a span of material from the data to parse.
35+
36+ Attributes:
37+ source (str | None): the source of the data, e.g. a file path.
38+ start ([int, int]): the start row and column of the span.
39+ end ([int, int]): the end row and column of the span.
40+ """
41+
42+ source : str | None
43+ start : [int , int ]
44+ end : [int , int ]
45+
3246def line_info_at (stream : Stream , index ):
3347 if index > len (stream ):
3448 raise ValueError ("invalid index" )
@@ -365,6 +379,9 @@ def mark(self) -> Parser:
365379 ((start_row, start_column),
366380 original_value,
367381 (end_row, end_column))
382+
383+ ``.span()'' is a more powerful version of this combinator, returning a
384+ SourceSpan.
368385 """
369386
370387 @generate
@@ -376,6 +393,24 @@ def marked():
376393
377394 return marked
378395
396+ def span (self ) -> Parser :
397+ """
398+ Returns a parser that augments the initial parser's result with a
399+ SourceSpan capturing where that parser started and stopped.
400+ The new value is a tuple:
401+
402+ (source_span, original_value)
403+ """
404+
405+ @generate
406+ def marked ():
407+ source , * start = yield line_info
408+ body = yield self
409+ _ , * end = yield line_info
410+ return (SourceSpan (source , tuple (start ), tuple (end )), body )
411+
412+ return marked
413+
379414 def tag (self , name : str ) -> Parser :
380415 """
381416 Returns a parser that wraps the produced value of the initial parser in a
0 commit comments