1
1
from __future__ import absolute_import , unicode_literals
2
2
3
+ import contextlib
3
4
from datetime import date , datetime
4
5
from decimal import Decimal
5
6
6
7
import attr
7
8
import six
8
9
9
- from fluent .syntax .ast import (AttributeExpression , CallExpression , Message ,
10
- MessageReference , NumberLiteral , Pattern ,
11
- Placeable , SelectExpression , StringLiteral , Term ,
12
- TermReference , TextElement , VariableReference ,
13
- VariantExpression , VariantList , Identifier )
10
+ from fluent .syntax .ast import (AttributeExpression , CallExpression , Identifier , Message , MessageReference ,
11
+ NumberLiteral , Pattern , Placeable , SelectExpression , StringLiteral , Term , TermReference ,
12
+ TextElement , VariableReference , VariantExpression , VariantList )
14
13
15
- from .errors import FluentCyclicReferenceError , FluentReferenceError
14
+ from .errors import FluentCyclicReferenceError , FluentFormatError , FluentReferenceError
16
15
from .types import FluentDateType , FluentNone , FluentNumber , fluent_date , fluent_number
17
- from .utils import numeric_to_native
16
+ from .utils import numeric_to_native , reference_to_id , unknown_reference_error_obj
18
17
19
18
try :
20
19
from functools import singledispatch
37
36
PDI = "\u2069 "
38
37
39
38
39
+ @attr .s
40
+ class CurrentEnvironment (object ):
41
+ # The parts of ResolverEnvironment that we want to mutate (and restore)
42
+ # temporarily for some parts of a call chain.
43
+ args = attr .ib ()
44
+ error_for_missing_arg = attr .ib (default = True )
45
+
46
+
40
47
@attr .s
41
48
class ResolverEnvironment (object ):
42
49
context = attr .ib ()
43
- args = attr .ib ()
44
50
errors = attr .ib ()
45
51
dirty = attr .ib (factory = set )
46
52
part_count = attr .ib (default = 0 )
53
+ current = attr .ib (factory = CurrentEnvironment )
54
+
55
+ @contextlib .contextmanager
56
+ def modified (self , ** replacements ):
57
+ """
58
+ Context manager that modifies the 'current' attribute of the
59
+ environment, restoring the old data at the end.
60
+ """
61
+ # CurrentEnvironment only has immutable args at the moment, so the
62
+ # shallow copy returned by attr.evolve is fine.
63
+ old_current = self .current
64
+ self .current = attr .evolve (old_current , ** replacements )
65
+ yield self
66
+ self .current = old_current
67
+
68
+ def modified_for_term_reference (self , args = None ):
69
+ return self .modified (args = args if args is not None else {},
70
+ error_for_missing_arg = False )
47
71
48
72
49
73
def resolve (context , message , args ):
@@ -55,7 +79,7 @@ def resolve(context, message, args):
55
79
"""
56
80
errors = []
57
81
env = ResolverEnvironment (context = context ,
58
- args = args ,
82
+ current = CurrentEnvironment ( args = args ) ,
59
83
errors = errors )
60
84
return fully_resolve (message , env ), errors
61
85
@@ -156,33 +180,34 @@ def handle_number_expression(number_expression, env):
156
180
157
181
@handle .register (MessageReference )
158
182
def handle_message_reference (message_reference , env ):
159
- name = message_reference .id .name
160
- return handle (lookup_reference (name , env ), env )
183
+ return handle (lookup_reference (message_reference , env ), env )
161
184
162
185
163
186
@handle .register (TermReference )
164
187
def handle_term_reference (term_reference , env ):
165
- name = term_reference . id . name
166
- return handle (lookup_reference (name , env ), env )
188
+ with env . modified_for_term_reference ():
189
+ return handle (lookup_reference (term_reference , env ), env )
167
190
168
191
169
- def lookup_reference (name , env ):
170
- message = None
171
- try :
172
- message = env .context ._messages_and_terms [name ]
173
- except LookupError :
174
- if name .startswith ("-" ):
175
- env .errors .append (
176
- FluentReferenceError ("Unknown term: {0}"
177
- .format (name )))
192
+ def lookup_reference (ref , env ):
193
+ ref_id = reference_to_id (ref )
194
+ if "." in ref_id :
195
+ parent_id , attr_name = ref_id .split ('.' )
196
+ if parent_id not in env .context ._messages_and_terms :
197
+ env .errors .append (unknown_reference_error_obj (ref_id ))
198
+ return FluentNone (ref_id )
178
199
else :
179
- env .errors .append (
180
- FluentReferenceError ("Unknown message: {0}"
181
- .format (name )))
182
- if message is None :
183
- message = FluentNone (name )
184
-
185
- return message
200
+ parent = env .context ._messages_and_terms [parent_id ]
201
+ for attribute in parent .attributes :
202
+ if attribute .id .name == attr_name :
203
+ return attribute .value
204
+ env .errors .append (unknown_reference_error_obj (ref_id ))
205
+ return parent
206
+ else :
207
+ if ref_id not in env .context ._messages_and_terms :
208
+ env .errors .append (unknown_reference_error_obj (ref_id ))
209
+ return FluentNone (ref_id )
210
+ return env .context ._messages_and_terms [ref_id ]
186
211
187
212
188
213
@handle .register (FluentNone )
@@ -200,10 +225,11 @@ def handle_none(none, env):
200
225
def handle_variable_reference (argument , env ):
201
226
name = argument .id .name
202
227
try :
203
- arg_val = env .args [name ]
228
+ arg_val = env .current . args [name ]
204
229
except LookupError :
205
- env .errors .append (
206
- FluentReferenceError ("Unknown external: {0}" .format (name )))
230
+ if env .current .error_for_missing_arg :
231
+ env .errors .append (
232
+ FluentReferenceError ("Unknown external: {0}" .format (name )))
207
233
return FluentNone (name )
208
234
209
235
if isinstance (arg_val ,
@@ -217,21 +243,8 @@ def handle_variable_reference(argument, env):
217
243
218
244
219
245
@handle .register (AttributeExpression )
220
- def handle_attribute_expression (attribute , env ):
221
- parent_id = attribute .ref .id .name
222
- attr_name = attribute .name .name
223
- message = lookup_reference (parent_id , env )
224
- if isinstance (message , FluentNone ):
225
- return message
226
-
227
- for message_attr in message .attributes :
228
- if message_attr .id .name == attr_name :
229
- return handle (message_attr .value , env )
230
-
231
- env .errors .append (
232
- FluentReferenceError ("Unknown attribute: {0}.{1}"
233
- .format (parent_id , attr_name )))
234
- return handle (message , env )
246
+ def handle_attribute_expression (attribute_ref , env ):
247
+ return handle (lookup_reference (attribute_ref , env ), env )
235
248
236
249
237
250
@handle .register (VariantList )
@@ -318,7 +331,7 @@ def handle_indentifier(identifier, env):
318
331
319
332
@handle .register (VariantExpression )
320
333
def handle_variant_expression (expression , env ):
321
- message = lookup_reference (expression .ref . id . name , env )
334
+ message = lookup_reference (expression .ref , env )
322
335
if isinstance (message , FluentNone ):
323
336
return message
324
337
@@ -334,21 +347,30 @@ def handle_variant_expression(expression, env):
334
347
335
348
@handle .register (CallExpression )
336
349
def handle_call_expression (expression , env ):
337
- function_name = expression .callee .name
338
- try :
339
- function = env .context ._functions [function_name ]
340
- except LookupError :
341
- env .errors .append (FluentReferenceError ("Unknown function: {0}"
342
- .format (function_name )))
343
- return FluentNone (function_name + "()" )
344
-
345
350
args = [handle (arg , env ) for arg in expression .positional ]
346
351
kwargs = {kwarg .name .name : handle (kwarg .value , env ) for kwarg in expression .named }
347
- try :
348
- return function (* args , ** kwargs )
349
- except Exception as e :
350
- env .errors .append (e )
351
- return FluentNone (function_name + "()" )
352
+
353
+ if isinstance (expression .callee , (TermReference , AttributeExpression )):
354
+ term = lookup_reference (expression .callee , env )
355
+ if args :
356
+ env .errors .append (FluentFormatError ("Ignored positional arguments passed to term '{0}'"
357
+ .format (reference_to_id (expression .callee ))))
358
+ with env .modified_for_term_reference (args = kwargs ):
359
+ return handle (term , env )
360
+ else :
361
+ function_name = expression .callee .id .name
362
+ try :
363
+ function = env .context ._functions [function_name ]
364
+ except LookupError :
365
+ env .errors .append (FluentReferenceError ("Unknown function: {0}"
366
+ .format (function_name )))
367
+ return FluentNone (function_name + "()" )
368
+
369
+ try :
370
+ return function (* args , ** kwargs )
371
+ except Exception as e :
372
+ env .errors .append (e )
373
+ return FluentNone (function_name + "()" )
352
374
353
375
354
376
@handle .register (FluentNumber )
0 commit comments