Skip to content

Commit 468fe28

Browse files
authored
Implement Syntax 0.6 in the tooling parser (#69)
Spec changelog: https://github.com/projectfluent/fluent/releases/tag/v0.6.0. Based on projectfluent/fluent.js#253.
1 parent b17dde4 commit 468fe28

File tree

81 files changed

+1849
-531
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+1849
-531
lines changed

fluent/syntax/ast.py

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -151,12 +151,7 @@ def __init__(self, body=None, **kwargs):
151151

152152

153153
class Entry(SyntaxNode):
154-
def __init__(self, annotations=None, **kwargs):
155-
super(Entry, self).__init__(**kwargs)
156-
self.annotations = annotations or []
157-
158-
def add_annotation(self, annot):
159-
self.annotations.append(annot)
154+
"""An abstract base class for useful elements of Resource.body."""
160155

161156

162157
class Message(Entry):
@@ -179,14 +174,20 @@ def __init__(self, id, value, attributes=None,
179174
self.comment = comment
180175

181176

177+
class VariantList(SyntaxNode):
178+
def __init__(self, variants, **kwargs):
179+
super(VariantList, self).__init__(**kwargs)
180+
self.variants = variants
181+
182+
182183
class Pattern(SyntaxNode):
183184
def __init__(self, elements, **kwargs):
184185
super(Pattern, self).__init__(**kwargs)
185186
self.elements = elements
186187

187188

188189
class PatternElement(SyntaxNode):
189-
pass
190+
"""An abstract base class for elements of Patterns."""
190191

191192

192193
class TextElement(PatternElement):
@@ -202,19 +203,18 @@ def __init__(self, expression, **kwargs):
202203

203204

204205
class Expression(SyntaxNode):
205-
def __init__(self, **kwargs):
206-
super(Expression, self).__init__(**kwargs)
206+
"""An abstract base class for expressions."""
207207

208208

209-
class StringExpression(Expression):
209+
class StringLiteral(Expression):
210210
def __init__(self, value, **kwargs):
211-
super(StringExpression, self).__init__(**kwargs)
211+
super(StringLiteral, self).__init__(**kwargs)
212212
self.value = value
213213

214214

215-
class NumberExpression(Expression):
215+
class NumberLiteral(Expression):
216216
def __init__(self, value, **kwargs):
217-
super(NumberExpression, self).__init__(**kwargs)
217+
super(NumberLiteral, self).__init__(**kwargs)
218218
self.value = value
219219

220220

@@ -224,23 +224,29 @@ def __init__(self, id, **kwargs):
224224
self.id = id
225225

226226

227-
class ExternalArgument(Expression):
227+
class TermReference(Expression):
228+
def __init__(self, id, **kwargs):
229+
super(TermReference, self).__init__(**kwargs)
230+
self.id = id
231+
232+
233+
class VariableReference(Expression):
228234
def __init__(self, id, **kwargs):
229-
super(ExternalArgument, self).__init__(**kwargs)
235+
super(VariableReference, self).__init__(**kwargs)
230236
self.id = id
231237

232238

233239
class SelectExpression(Expression):
234-
def __init__(self, expression, variants, **kwargs):
240+
def __init__(self, selector, variants, **kwargs):
235241
super(SelectExpression, self).__init__(**kwargs)
236-
self.expression = expression
242+
self.selector = selector
237243
self.variants = variants
238244

239245

240246
class AttributeExpression(Expression):
241-
def __init__(self, id, name, **kwargs):
247+
def __init__(self, ref, name, **kwargs):
242248
super(AttributeExpression, self).__init__(**kwargs)
243-
self.id = id
249+
self.ref = ref
244250
self.name = name
245251

246252

@@ -252,10 +258,11 @@ def __init__(self, ref, key, **kwargs):
252258

253259

254260
class CallExpression(Expression):
255-
def __init__(self, callee, args=None, **kwargs):
261+
def __init__(self, callee, positional=None, named=None, **kwargs):
256262
super(CallExpression, self).__init__(**kwargs)
257263
self.callee = callee
258-
self.args = args or []
264+
self.positional = positional or []
265+
self.named = named or []
259266

260267

261268
class Attribute(SyntaxNode):
@@ -278,16 +285,16 @@ def __init__(self, key, value, default=False, **kwargs):
278285

279286
@property
280287
def sorting_key(self):
281-
if isinstance(self.key, NumberExpression):
288+
if isinstance(self.key, NumberLiteral):
282289
return self.key.value
283290
return self.key.name
284291

285292

286293
class NamedArgument(SyntaxNode):
287-
def __init__(self, name, val, **kwargs):
294+
def __init__(self, name, value, **kwargs):
288295
super(NamedArgument, self).__init__(**kwargs)
289296
self.name = name
290-
self.val = val
297+
self.value = value
291298

292299

293300
class Identifier(SyntaxNode):
@@ -327,10 +334,14 @@ def __init__(self, name, **kwargs):
327334
super(Function, self).__init__(name, **kwargs)
328335

329336

330-
class Junk(Entry):
331-
def __init__(self, content=None, **kwargs):
337+
class Junk(SyntaxNode):
338+
def __init__(self, content=None, annotations=None, **kwargs):
332339
super(Junk, self).__init__(**kwargs)
333340
self.content = content
341+
self.annotations = annotations or []
342+
343+
def add_annotation(self, annot):
344+
self.annotations.append(annot)
334345

335346

336347
class Span(BaseNode):

fluent/syntax/errors.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,16 @@ def get_error_message(code, args):
5151
return 'Attributes of terms cannot be used as placeables'
5252
if code == 'E0020':
5353
return 'Unterminated string expression'
54+
if code == 'E0021':
55+
return 'Positional arguments must not follow named arguments'
56+
if code == 'E0022':
57+
return 'Named arguments must be unique'
58+
if code == 'E0023':
59+
return 'VariantLists are only allowed inside of other VariantLists.'
60+
if code == 'E0024':
61+
return 'Cannot access variants of a message.'
62+
if code == 'E0025':
63+
return 'Unknown escape sequence: {}'.format(args[0])
64+
if code == 'E0026':
65+
return 'Invalid Unicode escape sequence: {}'.format(args[0])
5466
return code

fluent/syntax/ftlstream.py

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,17 @@ def peek_inline_ws(self):
2424
ch = self.peek()
2525

2626
def skip_blank_lines(self):
27+
line_count = 0
2728
while True:
2829
self.peek_inline_ws()
2930

3031
if self.current_peek_is('\n'):
3132
self.skip_to_peek()
3233
self.next()
34+
line_count += 1
3335
else:
3436
self.reset_peek()
35-
break
37+
return line_count
3638

3739
def peek_blank_lines(self):
3840
while True:
@@ -67,11 +69,11 @@ def expect_indent(self):
6769
self.expect_char(' ')
6870
self.skip_inline_ws()
6971

70-
def take_char_if(self, ch):
71-
if self.ch == ch:
72-
self.next()
72+
def expect_line_end(self):
73+
if self.ch is None:
74+
# EOF is a valid line end in Fluent.
7375
return True
74-
return False
76+
return self.expect_char('\n')
7577

7678
def take_char(self, f):
7779
ch = self.ch
@@ -88,10 +90,7 @@ def is_char_id_start(self, ch=None):
8890
return (cc >= 97 and cc <= 122) or \
8991
(cc >= 65 and cc <= 90)
9092

91-
def is_entry_id_start(self):
92-
if self.current_is('-'):
93-
self.peek()
94-
93+
def is_identifier_start(self):
9594
ch = self.current_peek()
9695
is_id = self.is_char_id_start(ch)
9796
self.reset_peek()
@@ -112,15 +111,15 @@ def is_char_pattern_continuation(self, ch):
112111

113112
return ch not in SPECIAL_LINE_START_CHARS
114113

115-
def is_peek_pattern_start(self):
114+
def is_peek_value_start(self):
116115
self.peek_inline_ws()
117116
ch = self.current_peek()
118117

119118
# Inline Patterns may start with any char.
120119
if ch is not None and ch != '\n':
121120
return True
122121

123-
return self.is_peek_next_line_pattern_start()
122+
return self.is_peek_next_line_value()
124123

125124
def is_peek_next_line_zero_four_style_comment(self):
126125
if not self.current_peek_is('\n'):
@@ -150,7 +149,7 @@ def is_peek_next_line_comment(self, level=-1):
150149
while (i <= level or (level == -1 and i < 3)):
151150
self.peek()
152151
if not self.current_peek_is('#'):
153-
if i != level and level != -1:
152+
if i <= level and level != -1:
154153
self.reset_peek()
155154
return False
156155
break
@@ -214,7 +213,7 @@ def is_peek_next_line_attribute_start(self):
214213
self.reset_peek()
215214
return False
216215

217-
def is_peek_next_line_pattern_start(self):
216+
def is_peek_next_line_value(self):
218217
if not self.current_peek_is('\n'):
219218
return False
220219

@@ -243,25 +242,21 @@ def skip_to_next_entry_start(self):
243242
self.next()
244243

245244
if self.ch is None or \
246-
self.is_entry_id_start() or \
245+
self.is_identifier_start() or \
246+
self.current_is('-') or \
247247
self.current_is('#') or \
248248
(self.current_is('/') and self.peek_char_is('/')) or \
249249
(self.current_is('[') and self.peek_char_is('[')):
250250
break
251251
self.next()
252252

253-
def take_id_start(self, allow_term):
254-
if allow_term and self.current_is('-'):
255-
self.next()
256-
return '-'
257-
253+
def take_id_start(self):
258254
if self.is_char_id_start(self.ch):
259255
ret = self.ch
260256
self.next()
261257
return ret
262258

263-
allowed_range = 'a-zA-Z-' if allow_term else 'a-zA-Z'
264-
raise ParseError('E0004', allowed_range)
259+
raise ParseError('E0004', 'a-zA-Z')
265260

266261
def take_id_char(self):
267262
def closure(ch):
@@ -288,3 +283,12 @@ def closure(ch):
288283
cc = ord(ch)
289284
return (cc >= 48 and cc <= 57)
290285
return self.take_char(closure)
286+
287+
def take_hex_digit(self):
288+
def closure(ch):
289+
cc = ord(ch)
290+
return (
291+
(cc >= 48 and cc <= 57) # 0-9
292+
or (cc >= 65 and cc <= 70) # A-F
293+
or (cc >= 97 and cc <= 102)) # a-f
294+
return self.take_char(closure)

0 commit comments

Comments
 (0)