11from __future__ import absolute_import , unicode_literals
22
3+ import contextlib
34from datetime import date , datetime
45from decimal import Decimal
56
67import attr
78import six
89
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 )
1413
15- from .errors import FluentCyclicReferenceError , FluentReferenceError
14+ from .errors import FluentCyclicReferenceError , FluentFormatError , FluentReferenceError
1615from .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
1817
1918try :
2019 from functools import singledispatch
3736PDI = "\u2069 "
3837
3938
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+
4047@attr .s
4148class ResolverEnvironment (object ):
4249 context = attr .ib ()
43- args = attr .ib ()
4450 errors = attr .ib ()
4551 dirty = attr .ib (factory = set )
4652 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 )
4771
4872
4973def resolve (context , message , args ):
@@ -55,7 +79,7 @@ def resolve(context, message, args):
5579 """
5680 errors = []
5781 env = ResolverEnvironment (context = context ,
58- args = args ,
82+ current = CurrentEnvironment ( args = args ) ,
5983 errors = errors )
6084 return fully_resolve (message , env ), errors
6185
@@ -156,33 +180,34 @@ def handle_number_expression(number_expression, env):
156180
157181@handle .register (MessageReference )
158182def 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 )
161184
162185
163186@handle .register (TermReference )
164187def 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 )
167190
168191
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 )
178199 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 ]
186211
187212
188213@handle .register (FluentNone )
@@ -200,10 +225,11 @@ def handle_none(none, env):
200225def handle_variable_reference (argument , env ):
201226 name = argument .id .name
202227 try :
203- arg_val = env .args [name ]
228+ arg_val = env .current . args [name ]
204229 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 )))
207233 return FluentNone (name )
208234
209235 if isinstance (arg_val ,
@@ -217,21 +243,8 @@ def handle_variable_reference(argument, env):
217243
218244
219245@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 )
235248
236249
237250@handle .register (VariantList )
@@ -318,7 +331,7 @@ def handle_indentifier(identifier, env):
318331
319332@handle .register (VariantExpression )
320333def handle_variant_expression (expression , env ):
321- message = lookup_reference (expression .ref . id . name , env )
334+ message = lookup_reference (expression .ref , env )
322335 if isinstance (message , FluentNone ):
323336 return message
324337
@@ -334,21 +347,30 @@ def handle_variant_expression(expression, env):
334347
335348@handle .register (CallExpression )
336349def 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-
345350 args = [handle (arg , env ) for arg in expression .positional ]
346351 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 + "()" )
352374
353375
354376@handle .register (FluentNumber )
0 commit comments