Skip to content

Commit 8179447

Browse files
committed
add .span() method and test for it
1 parent c5ba3d5 commit 8179447

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

src/parsy/__init__.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ def make_stream(data: str | bytes, source: Any):
5050
"location metadata.",
5151
)
5252

53+
54+
@dataclass
55+
class SourceSpan:
56+
"""Identifies a span of material from the data to parse.
57+
58+
Attributes:
59+
source (str | None): the source of the data, e.g. a file path.
60+
start ([int, int]): the start row and column of the span.
61+
end ([int, int]): the end row and column of the span.
62+
"""
63+
64+
source: str | None
65+
start: [int, int]
66+
end: [int, int]
67+
68+
5369
def line_info_at(stream, index):
5470
if index > len(stream):
5571
raise ValueError("invalid index")
@@ -391,6 +407,9 @@ def mark(self) -> Parser:
391407
((start_row, start_column),
392408
original_value,
393409
(end_row, end_column))
410+
411+
``.span()'' is a more powerful version of this combinator, returning a
412+
SourceSpan.
394413
"""
395414

396415
@generate
@@ -407,6 +426,28 @@ def marked():
407426

408427
return marked
409428

429+
def span(self) -> Parser:
430+
"""
431+
Returns a parser that augments the initial parser's result with a
432+
SourceSpan capturing where that parser started and stopped.
433+
The new value is a tuple:
434+
435+
(source_span, original_value)
436+
"""
437+
438+
@generate
439+
def marked():
440+
start = yield line_info
441+
body = yield self
442+
end = yield line_info
443+
try:
444+
source = start[2]
445+
except IndexError:
446+
source = None
447+
return (SourceSpan(source, start[:2], end[:2]), body)
448+
449+
return marked
450+
410451
def tag(self, name: str) -> Parser:
411452
"""
412453
Returns a parser that wraps the produced value of the initial parser in a

tests/test_parsy.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from parsy import (
99
ParseError,
10+
SourceSpan,
1011
alt,
1112
any_char,
1213
char_from,
@@ -208,6 +209,35 @@ def test_mark(self):
208209
self.assertEqual(letters, ["q", "w", "e", "r"])
209210
self.assertEqual(end, (1, 4))
210211

212+
def test_span(self):
213+
parser = (letter.many().span() << string("\n")).many()
214+
source = "sample"
215+
216+
lines = parser.parse("asdf\nqwer\n", source=source)
217+
218+
self.assertEqual(len(lines), 2)
219+
220+
(span, letters) = lines[0]
221+
self.assertEqual(span, SourceSpan(source, (0, 0), (0, 4)))
222+
self.assertEqual(letters, ["a", "s", "d", "f"])
223+
224+
(span, letters) = lines[1]
225+
self.assertEqual(span, SourceSpan(source, (1, 0), (1, 4)))
226+
227+
def test_span_no_source(self):
228+
parser = (letter.many().span() << string("\n")).many()
229+
230+
lines = parser.parse("asdf\nqwer\n")
231+
232+
self.assertEqual(len(lines), 2)
233+
234+
(span, letters) = lines[0]
235+
self.assertEqual(span, SourceSpan(None, (0, 0), (0, 4)))
236+
self.assertEqual(letters, ["a", "s", "d", "f"])
237+
238+
(span, letters) = lines[1]
239+
self.assertEqual(span, SourceSpan(None, (1, 0), (1, 4)))
240+
211241
def test_tag(self):
212242
parser = letter.many().concat().tag("word")
213243
self.assertEqual(
@@ -692,6 +722,7 @@ def test_line_info_at(self):
692722
self.assertRaises(ValueError, lambda: line_info_at(text, 8))
693723

694724

725+
695726
class TestForwardDeclaration(unittest.TestCase):
696727
def test_forward_declaration_1(self):
697728
# This is the example from the docs

0 commit comments

Comments
 (0)