Skip to content

Commit 0509340

Browse files
authored
Refactor resolver into a tree of callable objects, or partially evaluated (#95)
1 parent a13ef99 commit 0509340

File tree

8 files changed

+301
-301
lines changed

8 files changed

+301
-301
lines changed

fluent.runtime/fluent/runtime/__init__.py

+31-9
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@
88
from fluent.syntax.ast import Message, Term
99

1010
from .builtins import BUILTINS
11-
from .resolver import resolve
12-
from .utils import ATTRIBUTE_SEPARATOR, TERM_SIGIL, add_message_and_attrs_to_store, ast_to_id
11+
from .prepare import Compiler
12+
from .resolver import ResolverEnvironment, CurrentEnvironment
13+
from .utils import ATTRIBUTE_SEPARATOR, TERM_SIGIL, ast_to_id, native_to_fluent
1314

1415

1516
class FluentBundle(object):
@@ -33,6 +34,8 @@ def __init__(self, locales, functions=None, use_isolating=True):
3334
self._functions = _functions
3435
self._use_isolating = use_isolating
3536
self._messages_and_terms = {}
37+
self._compiled = {}
38+
self._compiler = Compiler(use_isolating=use_isolating)
3639
self._babel_locale = self._get_babel_locale()
3740
self._plural_form = babel.plural.to_python(self._babel_locale.plural_form)
3841

@@ -44,22 +47,41 @@ def add_messages(self, source):
4447
if isinstance(item, (Message, Term)):
4548
full_id = ast_to_id(item)
4649
if full_id not in self._messages_and_terms:
47-
# We add attributes to the store to enable faster looker
48-
# later, and more direct code in some instances.
49-
add_message_and_attrs_to_store(self._messages_and_terms, full_id, item)
50+
self._messages_and_terms[full_id] = item
5051

5152
def has_message(self, message_id):
5253
if message_id.startswith(TERM_SIGIL) or ATTRIBUTE_SEPARATOR in message_id:
5354
return False
5455
return message_id in self._messages_and_terms
5556

57+
def lookup(self, full_id):
58+
if full_id not in self._compiled:
59+
entry_id = full_id.split(ATTRIBUTE_SEPARATOR, 1)[0]
60+
entry = self._messages_and_terms[entry_id]
61+
compiled = self._compiler(entry)
62+
if compiled.value is not None:
63+
self._compiled[entry_id] = compiled.value
64+
for attr in compiled.attributes:
65+
self._compiled[ATTRIBUTE_SEPARATOR.join([entry_id, attr.id.name])] = attr.value
66+
return self._compiled[full_id]
67+
5668
def format(self, message_id, args=None):
5769
if message_id.startswith(TERM_SIGIL):
5870
raise LookupError(message_id)
59-
message = self._messages_and_terms[message_id]
60-
if args is None:
61-
args = {}
62-
return resolve(self, message, args)
71+
if args is not None:
72+
fluent_args = {
73+
argname: native_to_fluent(argvalue)
74+
for argname, argvalue in args.items()
75+
}
76+
else:
77+
fluent_args = {}
78+
79+
errors = []
80+
resolve = self.lookup(message_id)
81+
env = ResolverEnvironment(context=self,
82+
current=CurrentEnvironment(args=fluent_args),
83+
errors=errors)
84+
return [resolve(env), errors]
6385

6486
def _get_babel_locale(self):
6587
for l in self.locales:
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
from __future__ import absolute_import, unicode_literals
2+
from fluent.syntax import ast as FTL
3+
from . import resolver
4+
5+
6+
class Compiler(object):
7+
def __init__(self, use_isolating=False):
8+
self.use_isolating = use_isolating
9+
10+
def __call__(self, item):
11+
if isinstance(item, FTL.BaseNode):
12+
return self.compile(item)
13+
if isinstance(item, (tuple, list)):
14+
return [self(elem) for elem in item]
15+
return item
16+
17+
def compile(self, node):
18+
nodename = type(node).__name__
19+
if not hasattr(resolver, nodename):
20+
return node
21+
kwargs = vars(node).copy()
22+
for propname, propvalue in kwargs.items():
23+
kwargs[propname] = self(propvalue)
24+
handler = getattr(self, 'compile_' + nodename, self.compile_generic)
25+
return handler(nodename, **kwargs)
26+
27+
def compile_generic(self, nodename, **kwargs):
28+
return getattr(resolver, nodename)(**kwargs)
29+
30+
def compile_Placeable(self, _, expression, **kwargs):
31+
if self.use_isolating:
32+
return resolver.IsolatingPlaceable(expression=expression, **kwargs)
33+
if isinstance(expression, resolver.Literal):
34+
return expression
35+
return resolver.Placeable(expression=expression, **kwargs)
36+
37+
def compile_Pattern(self, _, elements, **kwargs):
38+
if (
39+
len(elements) == 1 and
40+
isinstance(elements[0], resolver.IsolatingPlaceable)
41+
):
42+
# Don't isolate isolated placeables
43+
return elements[0].expression
44+
if any(
45+
not isinstance(child, resolver.Literal)
46+
for child in elements
47+
):
48+
return resolver.Pattern(elements=elements, **kwargs)
49+
if len(elements) == 1:
50+
return elements[0]
51+
return resolver.TextElement(
52+
''.join(child(None) for child in elements)
53+
)

0 commit comments

Comments
 (0)