Skip to content

Commit 01308bd

Browse files
committed
Rewind index to improve error recovery
1 parent eccbcce commit 01308bd

File tree

11 files changed

+639
-299
lines changed

11 files changed

+639
-299
lines changed

fluent/syntax/ftlstream.py

Lines changed: 52 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@ class FTLParserStream(ParserStream):
1212
last_comment_zero_four_syntax = False
1313

1414
def skip_blank_inline(self):
15-
while self.ch:
16-
if self.ch != INLINE_WS:
15+
while self.current_char:
16+
if self.current_char != INLINE_WS:
1717
break
1818
self.next()
1919

2020
def peek_blank_inline(self):
21-
ch = self.current_peek()
21+
ch = self.current_peek
2222
while ch:
2323
if ch != INLINE_WS:
2424
break
@@ -29,7 +29,7 @@ def skip_blank_block(self):
2929
while True:
3030
self.peek_blank_inline()
3131

32-
if self.current_peek_is('\n'):
32+
if self.current_peek == '\n':
3333
self.skip_to_peek()
3434
self.next()
3535
line_count += 1
@@ -39,26 +39,26 @@ def skip_blank_block(self):
3939

4040
def peek_blank_block(self):
4141
while True:
42-
line_start = self.get_peek_index()
42+
line_start = self.peek_offset
4343

4444
self.peek_blank_inline()
4545

46-
if self.current_peek_is('\n'):
46+
if self.current_peek == '\n':
4747
self.peek()
4848
else:
4949
self.reset_peek(line_start)
5050
break
5151

5252
def skip_blank(self):
53-
while self.ch in ANY_WS:
53+
while self.current_char in ANY_WS:
5454
self.next()
5555

5656
def peek_blank(self):
57-
while self.current_peek() in ANY_WS:
57+
while self.current_peek in ANY_WS:
5858
self.peek()
5959

6060
def expect_char(self, ch):
61-
if self.ch == ch:
61+
if self.current_char == ch:
6262
self.next()
6363
return True
6464

@@ -69,13 +69,13 @@ def expect_char(self, ch):
6969
raise ParseError('E0003', ch)
7070

7171
def expect_line_end(self):
72-
if self.ch is None:
72+
if self.current_char is None:
7373
# EOF is a valid line end in Fluent.
7474
return True
7575
return self.expect_char('\n')
7676

7777
def take_char(self, f):
78-
ch = self.ch
78+
ch = self.current_char
7979
if ch is not None and f(ch):
8080
self.next()
8181
return ch
@@ -90,13 +90,10 @@ def is_char_id_start(self, ch=None):
9090
(cc >= 65 and cc <= 90)
9191

9292
def is_identifier_start(self):
93-
ch = self.current_peek()
94-
is_id = self.is_char_id_start(ch)
95-
self.reset_peek()
96-
return is_id
93+
return self.is_char_id_start(self.current_peek)
9794

9895
def is_number_start(self):
99-
ch = self.peek() if self.current_is('-') else self.current()
96+
ch = self.peek() if self.current_char == '-' else self.current_char
10097
if ch is None:
10198
return False
10299

@@ -116,7 +113,7 @@ def is_value_start(self, skip):
116113
raise NotImplementedError()
117114

118115
self.peek_blank_inline()
119-
ch = self.current_peek()
116+
ch = self.current_peek
120117

121118
# Inline Patterns may start with any char.
122119
if ch is not None and ch != '\n':
@@ -129,19 +126,12 @@ def is_next_line_zero_four_comment(self, skip):
129126
if skip is True:
130127
raise NotImplementedError()
131128

132-
if not self.current_peek_is('\n'):
129+
if self.current_peek != '\n':
133130
return False
134131

135-
self.peek()
136-
137-
if self.current_peek_is('/'):
138-
self.peek()
139-
if self.current_peek_is('/'):
140-
self.reset_peek()
141-
return True
142-
132+
is_comment = (self.peek(), self.peek()) == ('/', '/')
143133
self.reset_peek()
144-
return False
134+
return is_comment
145135

146136
# -1 - any
147137
# 0 - comment
@@ -151,23 +141,20 @@ def is_next_line_comment(self, skip, level=-1):
151141
if skip is True:
152142
raise NotImplementedError()
153143

154-
if not self.current_peek_is('\n'):
144+
if self.current_peek != '\n':
155145
return False
156146

157147
i = 0
158148

159149
while (i <= level or (level == -1 and i < 3)):
160-
self.peek()
161-
if not self.current_peek_is('#'):
150+
if self.peek() != '#':
162151
if i <= level and level != -1:
163152
self.reset_peek()
164153
return False
165154
break
166155
i += 1
167156

168-
self.peek()
169-
170-
if self.current_peek() in [' ', '\n']:
157+
if self.peek() in [' ', '\n']:
171158
self.reset_peek()
172159
return True
173160

@@ -178,15 +165,15 @@ def is_next_line_variant_start(self, skip):
178165
if skip is True:
179166
raise NotImplementedError()
180167

181-
if not self.current_peek_is('\n'):
168+
if self.current_peek != '\n':
182169
return False
183170

184171
self.peek_blank()
185172

186-
if self.current_peek_is('*'):
173+
if self.current_peek == '*':
187174
self.peek()
188175

189-
if self.current_peek_is('[') and not self.peek_char_is('['):
176+
if self.current_peek == '[' and self.peek() != '[':
190177
self.reset_peek()
191178
return True
192179

@@ -199,29 +186,29 @@ def is_next_line_attribute_start(self, skip):
199186

200187
self.peek_blank()
201188

202-
if self.current_peek_is('.'):
189+
if self.current_peek == '.':
203190
self.skip_to_peek()
204191
return True
205192

206193
self.reset_peek()
207194
return False
208195

209196
def is_next_line_value(self, skip):
210-
if not self.current_peek_is('\n'):
197+
if self.current_peek != '\n':
211198
return False
212199

213200
self.peek_blank_block()
214201

215-
ptr = self.get_peek_index()
202+
ptr = self.peek_offset
216203

217204
self.peek_blank_inline()
218205

219-
if not self.current_peek_is("{"):
220-
if (self.get_peek_index() - ptr == 0):
206+
if self.current_peek != "{":
207+
if (self.peek_offset - ptr == 0):
221208
self.reset_peek()
222209
return False
223210

224-
if not self.is_char_pattern_continuation(self.current_peek()):
211+
if not self.is_char_pattern_continuation(self.current_peek):
225212
self.reset_peek()
226213
return False
227214

@@ -232,23 +219,33 @@ def is_next_line_value(self, skip):
232219

233220
return True
234221

235-
def skip_to_next_entry_start(self):
236-
while self.ch:
237-
if self.current_is('\n') and not self.peek_char_is('\n'):
222+
def skip_to_next_entry_start(self, junk_start):
223+
last_newline = self.string.rfind('\n', 0, self.index)
224+
if junk_start < last_newline:
225+
# Last seen newline is _after_ the junk start. It's safe to rewind
226+
# without the risk of resuming at the same broken entry.
227+
self.index = last_newline
228+
229+
while self.current_char:
230+
# We're only interested in beginnings of line.
231+
if self.current_char != '\n':
238232
self.next()
233+
continue
239234

240-
if self.ch is None or \
241-
self.is_identifier_start() or \
242-
self.current_is('-') or \
243-
self.current_is('#') or \
244-
(self.current_is('/') and self.peek_char_is('/')) or \
245-
(self.current_is('[') and self.peek_char_is('[')):
246-
break
247-
self.next()
235+
# Break if the first char in this line looks like an entry start.
236+
first = self.next()
237+
if self.is_char_id_start(first) or first == '-' or first == '#':
238+
break
239+
240+
# Syntax 0.4 compatibility
241+
peek = self.peek()
242+
self.reset_peek()
243+
if (first, peek) == ('/', '/') or (first, peek) == ('[', '['):
244+
break
248245

249246
def take_id_start(self):
250-
if self.is_char_id_start(self.ch):
251-
ret = self.ch
247+
if self.is_char_id_start(self.current_char):
248+
ret = self.current_char
252249
self.next()
253250
return ret
254251

0 commit comments

Comments
 (0)