Skip to content

Commit

Permalink
Merge branch 'fluent_1_syntax' into refine_error_handling
Browse files Browse the repository at this point in the history
  • Loading branch information
spookylukey committed May 22, 2019
2 parents abd53ef + 3475416 commit 20f3e25
Show file tree
Hide file tree
Showing 189 changed files with 5,790 additions and 3,120 deletions.
27 changes: 23 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,29 @@ python:
- "pypy3.5-6.0"
- "nightly"
env:
- PACKAGE=fluent.syntax
- PACKAGE=fluent.runtime
install: pip install tox-travis
script: cd $PACKAGE; tox
global:
- FLUENT_RUNTIME_DEFAULT_DEPS="fluent.syntax==0.15 attrs==19.1.0 babel==2.6.0 pytz==2018.9 six==1.12.0"
- FLUENT_SYNTAX_DEFAULT_DEPS="six"
matrix:
- PACKAGE=fluent.syntax
- PACKAGE=fluent.runtime
matrix:
include:
- name: "fluent.runtime w/ fluent.syntax==0.14"
python: "3.6"
env: PACKAGE=fluent.runtime TEST_DEPS="fluent.syntax==0.14 attrs==19.1.0 babel==2.6.0 pytz==2018.9 six==1.12.0"
- name: "fluent.runtime with latest everything"
python: "3.6"
# These are copy-pasted from setup.py
env: PACKAGE=fluent.runtime TEST_DEPS="fluent.syntax>=0.14,<=0.16 attrs babel pytz six"
allow_failures:
- python: "3.6"
env: PACKAGE=fluent.runtime TEST_DEPS="fluent.syntax>=0.14,<=0.16 attrs babel pytz six"

install:
- if [ "$PACKAGE" = "fluent.runtime" ]; then TEST_DEPS=${TEST_DEPS:-$FLUENT_RUNTIME_DEFAULT_DEPS}; else TEST_DEPS=${TEST_DEPS:-$FLUENT_SYNTAX_DEFAULT_DEPS}; fi
- pip install $TEST_DEPS
script: cd $PACKAGE; ./runtests.py
notifications:
irc:
channels:
Expand Down
15 changes: 15 additions & 0 deletions CODE_OF_CONDUCT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Community Participation Guidelines

This repository is governed by Mozilla's code of conduct and etiquette
guidelines. For more details, please read the [Mozilla Community Participation
Guidelines][].


## How to Report

For more information on how to report violations of the Community Participation
Guidelines, please read our [How to Report][] page.


[Mozilla Community Participation Guidelines]: https://www.mozilla.org/about/governance/policies/participation/
[How to Report]: https://www.mozilla.org/about/governance/policies/participation/reporting/
4 changes: 2 additions & 2 deletions fluent.runtime/fluent/runtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ def __init__(self, locales, functions=None, use_isolating=True):
if functions:
_functions.update(functions)
self._functions = _functions
self._use_isolating = use_isolating
self.use_isolating = use_isolating
self._messages_and_terms = {}
self._compiled = {}
self._compiler = Compiler(use_isolating=use_isolating)
self._compiler = Compiler()
self._babel_locale = self._get_babel_locale()
self._plural_form = babel.plural.to_python(self._babel_locale.plural_form)

Expand Down
9 changes: 2 additions & 7 deletions fluent.runtime/fluent/runtime/prepare.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@


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)
Expand All @@ -28,19 +25,17 @@ 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)
isinstance(elements[0], resolver.Placeable)
):
# Don't isolate isolated placeables
return elements[0].expression
return resolver.NeverIsolatingPlaceable(elements[0].expression)
if any(
not isinstance(child, resolver.Literal)
for child in elements
Expand Down
96 changes: 29 additions & 67 deletions fluent.runtime/fluent/runtime/resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ class CurrentEnvironment(object):
class ResolverEnvironment(object):
context = attr.ib()
errors = attr.ib()
part_count = attr.ib(default=0)
part_count = attr.ib(default=0, init=False)
active_patterns = attr.ib(factory=set, init=False)
current = attr.ib(factory=CurrentEnvironment)

@contextlib.contextmanager
Expand Down Expand Up @@ -103,15 +104,14 @@ class Pattern(FTL.Pattern, BaseResolver):

def __init__(self, *args, **kwargs):
super(Pattern, self).__init__(*args, **kwargs)
self.dirty = False

def __call__(self, env):
if self.dirty:
if self in env.active_patterns:
env.errors.append(FluentCyclicReferenceError("Cyclic reference"))
return FluentNone()
if env.part_count > self.MAX_PARTS:
return ""
self.dirty = True
env.active_patterns.add(self)
elements = self.elements
remaining_parts = self.MAX_PARTS - env.part_count
if len(self.elements) > remaining_parts:
Expand All @@ -122,7 +122,7 @@ def __call__(self, env):
resolve(element(env), env) for element in elements
)
env.part_count += len(elements)
self.dirty = False
env.active_patterns.remove(self)
return retval


Expand All @@ -142,18 +142,21 @@ def __call__(self, env):

class Placeable(FTL.Placeable, BaseResolver):
def __call__(self, env):
return self.expression(env)
inner = resolve(self.expression(env), env)
if not env.context.use_isolating:
return inner
return "\u2068" + inner + "\u2069"


class IsolatingPlaceable(FTL.Placeable, BaseResolver):
class NeverIsolatingPlaceable(FTL.Placeable, BaseResolver):
def __call__(self, env):
inner = self.expression(env)
return "\u2068" + resolve(inner, env) + "\u2069"
inner = resolve(self.expression(env), env)
return inner


class StringLiteral(FTL.StringLiteral, Literal):
def __call__(self, env):
return self.value
return self.parse()['value']


class NumberLiteral(FTL.NumberLiteral, BaseResolver):
Expand All @@ -175,7 +178,14 @@ def __call__(self, env):

class TermReference(FTL.TermReference, BaseResolver):
def __call__(self, env):
with env.modified_for_term_reference():
if self.arguments:
if self.arguments.positional:
env.errors.append(FluentFormatError("Ignored positional arguments passed to term '{0}'"
.format(reference_to_id(self))))
kwargs = {kwarg.name.name: kwarg.value(env) for kwarg in self.arguments.named}
else:
kwargs = None
with env.modified_for_term_reference(args=kwargs):
return lookup_reference(self, env)(env)


Expand All @@ -195,9 +205,9 @@ def lookup_reference(ref, env):
except LookupError:
env.errors.append(unknown_reference_error_obj(ref_id))

if isinstance(ref, AttributeExpression):
if ref.attribute:
# Fallback
parent_id = reference_to_id(ref.ref)
parent_id = reference_to_id(ref, ignore_attributes=True)
try:
return env.context.lookup(parent_id)
except LookupError:
Expand Down Expand Up @@ -226,40 +236,10 @@ def __call__(self, env):
return FluentNone(name)


class AttributeExpression(FTL.AttributeExpression, BaseResolver):
def __call__(self, env):
return lookup_reference(self, env)(env)


class Attribute(FTL.Attribute, BaseResolver):
pass


class VariantList(FTL.VariantList, BaseResolver):
def __call__(self, env, key=None):
found = None
for variant in self.variants:
if variant.default:
default = variant
if key is None:
# We only want the default
break

compare_value = variant.key(env)
if match(key, compare_value, env):
found = variant
break

if found is None:
if (key is not None and not isinstance(key, FluentNone)):
env.errors.append(FluentReferenceError("Unknown variant: {0}"
.format(key)))
found = default
assert found, "Not having a default variant is a parse error"

return found.value(env)


class SelectExpression(FTL.SelectExpression, BaseResolver):
def __call__(self, env):
key = self.selector(env)
Expand Down Expand Up @@ -309,33 +289,15 @@ def __call__(self, env):
return self.name


class VariantExpression(FTL.VariantExpression, BaseResolver):
def __call__(self, env):
message = lookup_reference(self.ref, env)

# TODO What to do if message is not a VariantList?
# Need test at least.
assert isinstance(message, VariantList)

variant_name = self.key.name
return message(env, variant_name)
class CallArguments(FTL.CallArguments, BaseResolver):
pass


class CallExpression(FTL.CallExpression, BaseResolver):
class FunctionReference(FTL.FunctionReference, BaseResolver):
def __call__(self, env):
args = [arg(env) for arg in self.positional]
kwargs = {kwarg.name.name: kwarg.value(env) for kwarg in self.named}

if isinstance(self.callee, (TermReference, AttributeExpression)):
term = lookup_reference(self.callee, env)
if args:
env.errors.append(FluentFormatError("Ignored positional arguments passed to term '{0}'"
.format(reference_to_id(self.callee))))
with env.modified_for_term_reference(args=kwargs):
return term(env)

# builtin or custom function call
function_name = self.callee.id.name
args = [arg(env) for arg in self.arguments.positional]
kwargs = {kwarg.name.name: kwarg.value(env) for kwarg in self.arguments.named}
function_name = self.id.name
try:
function = env.context._functions[function_name]
except LookupError:
Expand Down
27 changes: 10 additions & 17 deletions fluent.runtime/fluent/runtime/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
from datetime import date, datetime
from decimal import Decimal

import six

from fluent.syntax.ast import AttributeExpression, Term, TermReference
from fluent.syntax.ast import Term, TermReference

from .errors import FluentFormatError, FluentReferenceError
from .types import FluentDate, FluentDateTime, FluentDecimal, FluentFloat, FluentInt
Expand Down Expand Up @@ -209,9 +207,9 @@ def args_match(function_name, args, kwargs, arg_spec):
return (match, sanitized_args, sanitized_kwargs, errors)


def reference_to_id(ref):
def reference_to_id(ref, ignore_attributes=False):
"""
Returns a string reference for a MessageReference, TermReference or AttributeExpression
Returns a string reference for a MessageReference or TermReference
AST node.
e.g.
Expand All @@ -220,12 +218,14 @@ def reference_to_id(ref):
-term
-term.attr
"""
if isinstance(ref, AttributeExpression):
return _make_attr_id(reference_to_id(ref.ref),
ref.name.name)
if isinstance(ref, TermReference):
return TERM_SIGIL + ref.id.name
return ref.id.name
start = TERM_SIGIL + ref.id.name
else:
start = ref.id.name

if not ignore_attributes and ref.attribute:
return ''.join([start, ATTRIBUTE_SEPARATOR, ref.attribute.name])
return start


def sanitize_function_args(arg_spec, name, errors):
Expand All @@ -252,10 +252,3 @@ def unknown_reference_error_obj(ref_id):
if ref_id.startswith(TERM_SIGIL):
return FluentReferenceError("Unknown term: {0}".format(ref_id))
return FluentReferenceError("Unknown message: {0}".format(ref_id))


def _make_attr_id(parent_ref_id, attr_name):
"""
Given a parent id and the attribute name, return the attribute id
"""
return ''.join([parent_ref_id, ATTRIBUTE_SEPARATOR, attr_name])
9 changes: 5 additions & 4 deletions fluent.runtime/setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/usr/bin/env python
from setuptools import setup
import sys


setup(name='fluent.runtime',
version='0.1',
Expand All @@ -19,12 +19,13 @@
'Programming Language :: Python :: 3.5',
],
packages=['fluent', 'fluent.runtime'],
# These should also be duplicated in tox.ini and ../.travis.yml
install_requires=[
'fluent.syntax>=0.12,<=0.13',
'fluent.syntax>=0.14,<=0.16',
'attrs',
'babel',
'pytz',
'six',
],
tests_require=['six'],
test_suite='tests'
test_suite='tests',
)
10 changes: 9 additions & 1 deletion fluent.runtime/tests/format/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ class Unsupported(object):
use-any-args = { ANY_ARGS(1, 2, 3, x:1) }
use-restricted-ok = { RESTRICTED(allowed: 1) }
use-restricted-bad = { RESTRICTED(notAllowed: 1) }
bad-output = { BAD_OUTPUT() }
bad-output = { BAD_OUTPUT() } stuff
bad-output-2 = { BAD_OUTPUT() }
non-identfier-arg = { ANY_ARGS(1, foo: 2, non-identifier: 3) }
"""))

Expand Down Expand Up @@ -157,6 +158,13 @@ def test_bad_output(self):
self.ctx.format('bad-output')
self.assertIn("Unsupported", cm.exception.args[0])

@unittest.expectedFailure
def test_bad_output_2(self):
# This is a developer error, so should raise an exception
with self.assertRaises(TypeError) as cm:
self.ctx.format('bad-output-2')
self.assertIn("Unsupported", cm.exception.args[0])

def test_non_identifier_python_keyword_args(self):
val, errs = self.ctx.format('non-identfier-arg')
self.assertEqual(val, '1 foo=2 non-identifier=3')
Expand Down
Loading

0 comments on commit 20f3e25

Please sign in to comment.