Skip to content

Commit eaee17a

Browse files
committed
Track source position information in Streams
Prior to this change, the only positional variables which were tracked when moving through the stream was the current index in the stream. We would like to start reporting richer positional information from the parser, specifically the position (in terms of row/column) in the source object. This change allows this by updating the Stream class to additionally track its current (zero-indexed) row and column while moving through the stream. We will persist this information into the syntax tree in the following commits.
1 parent 308e0fc commit eaee17a

File tree

2 files changed

+133
-0
lines changed

2 files changed

+133
-0
lines changed

fluent.syntax/fluent/syntax/stream.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ class ParserStream:
99
def __init__(self, string: str):
1010
self.string = string
1111
self.index = 0
12+
self.row_index = 0
13+
self.column_index = 0
1214
self.peek_offset = 0
15+
self._peek_row_offset = 0
16+
self._peek_column_offset = 0
1317

1418
def get(self, offset: int) -> Union[str, None]:
1519
try:
@@ -37,9 +41,19 @@ def current_peek(self) -> Union[str, None]:
3741

3842
def next(self) -> Union[str, None]:
3943
self.peek_offset = 0
44+
self._peek_row_offset = 0
45+
self._peek_column_offset = 0
46+
4047
# Skip over CRLF as if it was a single character.
4148
if self.get(self.index) == "\r" and self.get(self.index + 1) == "\n":
4249
self.index += 1
50+
# If we have reached a newline reset the position
51+
if self.get(self.index) == "\n":
52+
self.row_index += 1
53+
self.column_index = 0
54+
else:
55+
self.column_index += 1
56+
4357
self.index += 1
4458
return self.get(self.index)
4559

@@ -50,6 +64,13 @@ def peek(self) -> Union[str, None]:
5064
and self.get(self.index + self.peek_offset + 1) == "\n"
5165
):
5266
self.peek_offset += 1
67+
68+
if self.get(self.index + self.peek_offset) == "\n":
69+
self._peek_row_offset += 1
70+
self._peek_column_offset = 0
71+
else:
72+
self._peek_column_offset += 1
73+
5374
self.peek_offset += 1
5475
return self.get(self.index + self.peek_offset)
5576

@@ -58,7 +79,17 @@ def reset_peek(self, offset: int = 0) -> None:
5879

5980
def skip_to_peek(self) -> None:
6081
self.index += self.peek_offset
82+
self.row_index += self._peek_row_offset
83+
if self._peek_row_offset:
84+
# There have been newlines during the peek, so the column offset is the column index
85+
# since the last newline
86+
self.column_index = self._peek_column_offset
87+
else:
88+
self.column_index += self._peek_column_offset
89+
6190
self.peek_offset = 0
91+
self._peek_row_offset = 0
92+
self._peek_column_offset = 0
6293

6394

6495
EOL = "\n"

fluent.syntax/tests/syntax/test_stream.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,3 +159,105 @@ def test_reset_peek(self):
159159

160160
self.assertEqual("d", ps.peek())
161161
self.assertEqual(None, ps.peek())
162+
163+
def test_source_position_over_newline(self):
164+
ps = ParserStream("ab\nc")
165+
166+
# Initially we should start at 0,0
167+
self.assertEqual("a", ps.current_char)
168+
self.assertEqual(0, ps.row_index)
169+
self.assertEqual(0, ps.column_index)
170+
171+
# Advancing within a line should increase the columm
172+
self.assertEqual("b", ps.next())
173+
self.assertEqual(0, ps.row_index)
174+
self.assertEqual(1, ps.column_index)
175+
176+
# Advancing to the newline should increase the column
177+
self.assertEqual("\n", ps.next())
178+
self.assertEqual(0, ps.row_index)
179+
self.assertEqual(2, ps.column_index)
180+
181+
# Advancing past the newline should increase the row and reset the column
182+
self.assertEqual("c", ps.next())
183+
self.assertEqual(1, ps.row_index)
184+
self.assertEqual(0, ps.column_index)
185+
186+
def test_source_position_over_crlf(self):
187+
ps = ParserStream("a\r\nb")
188+
189+
# Initially we should start at 0,0
190+
self.assertEqual("a", ps.current_char)
191+
self.assertEqual(0, ps.row_index)
192+
self.assertEqual(0, ps.column_index)
193+
194+
# Advancing to the CRLF should increase the column
195+
self.assertEqual("\r", ps.next())
196+
self.assertEqual(0, ps.row_index)
197+
self.assertEqual(1, ps.column_index)
198+
199+
# Advancing past the CRLF should increase the row and reset the column
200+
self.assertEqual("b", ps.next())
201+
self.assertEqual(1, ps.row_index)
202+
self.assertEqual(0, ps.column_index)
203+
204+
def test_skip_to_peek_over_newline(self):
205+
ps = ParserStream("ab\nc")
206+
207+
# Initially we should start at 0,0
208+
self.assertEqual("a", ps.current_char)
209+
self.assertEqual(0, ps.row_index)
210+
self.assertEqual(0, ps.column_index)
211+
212+
# Peeking then advancing within a line should increase the columm
213+
self.assertEqual("b", ps.peek())
214+
self.assertEqual(0, ps.row_index)
215+
self.assertEqual(0, ps.column_index)
216+
217+
ps.skip_to_peek()
218+
self.assertEqual(0, ps.row_index)
219+
self.assertEqual(1, ps.column_index)
220+
221+
# Peeking then advancing to the newline should increase the column
222+
self.assertEqual("\n", ps.peek())
223+
self.assertEqual(0, ps.row_index)
224+
self.assertEqual(1, ps.column_index)
225+
226+
ps.skip_to_peek()
227+
self.assertEqual(0, ps.row_index)
228+
self.assertEqual(2, ps.column_index)
229+
230+
# Peeking then advancing past the newline should increase the row and reset the column
231+
self.assertEqual("c", ps.peek())
232+
self.assertEqual(0, ps.row_index)
233+
self.assertEqual(2, ps.column_index)
234+
235+
ps.skip_to_peek()
236+
self.assertEqual(1, ps.row_index)
237+
self.assertEqual(0, ps.column_index)
238+
239+
def test_skip_to_peek_over_crlf(self):
240+
ps = ParserStream("a\r\nb")
241+
242+
# Initially we should start at 0,0
243+
self.assertEqual("a", ps.current_char)
244+
self.assertEqual(0, ps.row_index)
245+
self.assertEqual(0, ps.column_index)
246+
247+
# Peeking then advancing to the CRLF should increase the column
248+
self.assertEqual("\r", ps.peek())
249+
self.assertEqual(0, ps.row_index)
250+
self.assertEqual(0, ps.column_index)
251+
252+
ps.skip_to_peek()
253+
self.assertEqual(0, ps.row_index)
254+
self.assertEqual(1, ps.column_index)
255+
256+
# Peeking then advancing past the CRLF should increase the row and reset the column
257+
self.assertEqual("b", ps.peek())
258+
self.assertEqual(0, ps.row_index)
259+
self.assertEqual(1, ps.column_index)
260+
261+
ps.skip_to_peek()
262+
self.assertEqual(1, ps.row_index)
263+
self.assertEqual(0, ps.column_index)

0 commit comments

Comments
 (0)