Skip to content

Commit 82f985f

Browse files
committed
Updated fluent.runtime to fluent.syntax 0.10 and implemented new features.
1 parent 16af8e9 commit 82f985f

File tree

8 files changed

+318
-65
lines changed

8 files changed

+318
-65
lines changed

fluent.runtime/fluent/runtime/__init__.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ def add_messages(self, source):
4141
# TODO - warn/error about duplicates
4242
for item in resource.body:
4343
if isinstance(item, (Message, Term)):
44-
if item.id.name not in self._messages_and_terms:
45-
self._messages_and_terms[item.id.name] = item
44+
prefix = "-" if isinstance(item, Term) else ""
45+
full_id = prefix + item.id.name
46+
if full_id not in self._messages_and_terms:
47+
self._messages_and_terms[full_id] = item
4648

4749
def has_message(self, message_id):
4850
if message_id.startswith('-'):

fluent.runtime/fluent/runtime/resolver.py

+83-61
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
from __future__ import absolute_import, unicode_literals
22

3+
import contextlib
34
from datetime import date, datetime
45
from decimal import Decimal
56

67
import attr
78
import 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
1615
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
1817

1918
try:
2019
from functools import singledispatch
@@ -37,13 +36,38 @@
3736
PDI = "\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
4148
class 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

4973
def 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)
158182
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)
161184

162185

163186
@handle.register(TermReference)
164187
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)
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):
200225
def 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)
320333
def 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)
336349
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-
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)

fluent.runtime/fluent/runtime/utils.py

+31
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
from fluent.syntax.ast import AttributeExpression, TermReference
2+
3+
from .errors import FluentReferenceError
4+
5+
16
def numeric_to_native(val):
27
"""
38
Given a numeric string (as defined by fluent spec),
@@ -9,3 +14,29 @@ def numeric_to_native(val):
914
return float(val)
1015
else:
1116
return int(val)
17+
18+
19+
def reference_to_id(ref):
20+
"""
21+
Returns a string reference for a MessageReference, TermReference or AttributeExpression
22+
AST node.
23+
24+
e.g.
25+
message
26+
message.attr
27+
-term
28+
-term.attr
29+
"""
30+
if isinstance(ref, AttributeExpression):
31+
return "{0}.{1}".format(reference_to_id(ref.ref),
32+
ref.name.name)
33+
return ('-' if isinstance(ref, TermReference) else '') + ref.id.name
34+
35+
36+
def unknown_reference_error_obj(ref_id):
37+
if '.' in ref_id:
38+
return FluentReferenceError("Unknown attribute: {0}".format(ref_id))
39+
elif ref_id.startswith('-'):
40+
return FluentReferenceError("Unknown term: {0}".format(ref_id))
41+
else:
42+
return FluentReferenceError("Unknown message: {0}".format(ref_id))

fluent.runtime/setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
],
2727
packages=['fluent', 'fluent.runtime'],
2828
install_requires=[
29-
'fluent>=0.9,<0.10',
29+
'fluent.syntax>=0.10',
3030
'attrs',
3131
'babel',
3232
'pytz',

0 commit comments

Comments
 (0)