Skip to content

Commit

Permalink
Refactor resolver into a tree of callable objects, or partially evalu…
Browse files Browse the repository at this point in the history
…ated (#95)
  • Loading branch information
Pike authored Feb 19, 2019
1 parent a13ef99 commit 0509340
Show file tree
Hide file tree
Showing 8 changed files with 301 additions and 301 deletions.
40 changes: 31 additions & 9 deletions fluent.runtime/fluent/runtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
from fluent.syntax.ast import Message, Term

from .builtins import BUILTINS
from .resolver import resolve
from .utils import ATTRIBUTE_SEPARATOR, TERM_SIGIL, add_message_and_attrs_to_store, ast_to_id
from .prepare import Compiler
from .resolver import ResolverEnvironment, CurrentEnvironment
from .utils import ATTRIBUTE_SEPARATOR, TERM_SIGIL, ast_to_id, native_to_fluent


class FluentBundle(object):
Expand All @@ -33,6 +34,8 @@ def __init__(self, locales, functions=None, use_isolating=True):
self._functions = _functions
self._use_isolating = use_isolating
self._messages_and_terms = {}
self._compiled = {}
self._compiler = Compiler(use_isolating=use_isolating)
self._babel_locale = self._get_babel_locale()
self._plural_form = babel.plural.to_python(self._babel_locale.plural_form)

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

def has_message(self, message_id):
if message_id.startswith(TERM_SIGIL) or ATTRIBUTE_SEPARATOR in message_id:
return False
return message_id in self._messages_and_terms

def lookup(self, full_id):
if full_id not in self._compiled:
entry_id = full_id.split(ATTRIBUTE_SEPARATOR, 1)[0]
entry = self._messages_and_terms[entry_id]
compiled = self._compiler(entry)
if compiled.value is not None:
self._compiled[entry_id] = compiled.value
for attr in compiled.attributes:
self._compiled[ATTRIBUTE_SEPARATOR.join([entry_id, attr.id.name])] = attr.value
return self._compiled[full_id]

def format(self, message_id, args=None):
if message_id.startswith(TERM_SIGIL):
raise LookupError(message_id)
message = self._messages_and_terms[message_id]
if args is None:
args = {}
return resolve(self, message, args)
if args is not None:
fluent_args = {
argname: native_to_fluent(argvalue)
for argname, argvalue in args.items()
}
else:
fluent_args = {}

errors = []
resolve = self.lookup(message_id)
env = ResolverEnvironment(context=self,
current=CurrentEnvironment(args=fluent_args),
errors=errors)
return [resolve(env), errors]

def _get_babel_locale(self):
for l in self.locales:
Expand Down
53 changes: 53 additions & 0 deletions fluent.runtime/fluent/runtime/prepare.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
from __future__ import absolute_import, unicode_literals
from fluent.syntax import ast as FTL
from . import resolver


class Compiler(object):
def __init__(self, use_isolating=False):
self.use_isolating = use_isolating

def __call__(self, item):
if isinstance(item, FTL.BaseNode):
return self.compile(item)
if isinstance(item, (tuple, list)):
return [self(elem) for elem in item]
return item

def compile(self, node):
nodename = type(node).__name__
if not hasattr(resolver, nodename):
return node
kwargs = vars(node).copy()
for propname, propvalue in kwargs.items():
kwargs[propname] = self(propvalue)
handler = getattr(self, 'compile_' + nodename, self.compile_generic)
return handler(nodename, **kwargs)

def compile_generic(self, nodename, **kwargs):
return getattr(resolver, nodename)(**kwargs)

def compile_Placeable(self, _, expression, **kwargs):
if self.use_isolating:
return resolver.IsolatingPlaceable(expression=expression, **kwargs)
if isinstance(expression, resolver.Literal):
return expression
return resolver.Placeable(expression=expression, **kwargs)

def compile_Pattern(self, _, elements, **kwargs):
if (
len(elements) == 1 and
isinstance(elements[0], resolver.IsolatingPlaceable)
):
# Don't isolate isolated placeables
return elements[0].expression
if any(
not isinstance(child, resolver.Literal)
for child in elements
):
return resolver.Pattern(elements=elements, **kwargs)
if len(elements) == 1:
return elements[0]
return resolver.TextElement(
''.join(child(None) for child in elements)
)
Loading

0 comments on commit 0509340

Please sign in to comment.