# coding=utf-8
from __future__ import unicode_literals
import re
import sys
import json
import six
def to_json(value, fn=None):
if isinstance(value, BaseNode):
return value.to_json(fn)
if isinstance(value, list):
return list(to_json(item, fn) for item in value)
if isinstance(value, tuple):
return list(to_json(item, fn) for item in value)
else:
return value
def from_json(value):
if isinstance(value, dict):
cls = getattr(sys.modules[__name__], value['type'])
args = {
k: from_json(v)
for k, v in value.items()
if k != 'type'
}
return cls(**args)
if isinstance(value, list):
return list(map(from_json, value))
else:
return value
def scalars_equal(node1, node2, ignored_fields):
"""Compare two nodes which are not lists."""
if type(node1) != type(node2):
return False
if isinstance(node1, BaseNode):
return node1.equals(node2, ignored_fields)
return node1 == node2
[docs]class BaseNode(object):
"""Base class for all Fluent AST nodes.
All productions described in the ASDL subclass BaseNode, including Span and
Annotation. Implements __str__, to_json and traverse.
"""
[docs] def clone(self):
"""Create a deep clone of the current node."""
def visit(value):
"""Clone node and its descendants."""
if isinstance(value, BaseNode):
return value.clone()
if isinstance(value, list):
return [visit(child) for child in value]
if isinstance(value, tuple):
return tuple(visit(child) for child in value)
return value
# Use all attributes found on the node as kwargs to the constructor.
return self.__class__(
**{name: visit(value) for name, value in vars(self).items()}
)
[docs] def equals(self, other, ignored_fields=['span']):
"""Compare two nodes.
Nodes are deeply compared on a field by field basis. If possible, False
is returned early. When comparing attributes and variants in
SelectExpressions, the order doesn't matter. By default, spans are not
taken into account.
"""
self_keys = set(vars(self).keys())
other_keys = set(vars(other).keys())
if ignored_fields:
for key in ignored_fields:
self_keys.discard(key)
other_keys.discard(key)
if self_keys != other_keys:
return False
for key in self_keys:
field1 = getattr(self, key)
field2 = getattr(other, key)
# List-typed nodes are compared item-by-item. When comparing
# attributes and variants, the order of items doesn't matter.
if isinstance(field1, list) and isinstance(field2, list):
if len(field1) != len(field2):
return False
for elem1, elem2 in zip(field1, field2):
if not scalars_equal(elem1, elem2, ignored_fields):
return False
elif not scalars_equal(field1, field2, ignored_fields):
return False
return True
def to_json(self, fn=None):
obj = {
name: to_json(value, fn)
for name, value in vars(self).items()
}
obj.update(
{'type': self.__class__.__name__}
)
return fn(obj) if fn else obj
def __str__(self):
return json.dumps(self.to_json())
[docs]class SyntaxNode(BaseNode):
"""Base class for AST nodes which can have Spans."""
def __init__(self, span=None, **kwargs):
super(SyntaxNode, self).__init__(**kwargs)
self.span = span
def add_span(self, start, end):
self.span = Span(start, end)
[docs]class Resource(SyntaxNode):
def __init__(self, body=None, **kwargs):
super(Resource, self).__init__(**kwargs)
self.body = body or []
[docs]class Entry(SyntaxNode):
"""An abstract base class for useful elements of Resource.body."""
[docs]class Message(Entry):
def __init__(self, id, value=None, attributes=None,
comment=None, **kwargs):
super(Message, self).__init__(**kwargs)
self.id = id
self.value = value
self.attributes = attributes or []
self.comment = comment
[docs]class Term(Entry):
def __init__(self, id, value, attributes=None,
comment=None, **kwargs):
super(Term, self).__init__(**kwargs)
self.id = id
self.value = value
self.attributes = attributes or []
self.comment = comment
[docs]class Pattern(SyntaxNode):
def __init__(self, elements, **kwargs):
super(Pattern, self).__init__(**kwargs)
self.elements = elements
[docs]class PatternElement(SyntaxNode):
"""An abstract base class for elements of Patterns."""
[docs]class TextElement(PatternElement):
def __init__(self, value, **kwargs):
super(TextElement, self).__init__(**kwargs)
self.value = value
[docs]class Placeable(PatternElement):
def __init__(self, expression, **kwargs):
super(Placeable, self).__init__(**kwargs)
self.expression = expression
[docs]class Expression(SyntaxNode):
"""An abstract base class for expressions."""
[docs]class Literal(Expression):
"""An abstract base class for literals."""
def __init__(self, value, **kwargs):
super(Literal, self).__init__(**kwargs)
self.value = value
def parse(self):
return {'value': self.value}
[docs]class StringLiteral(Literal):
def parse(self):
def from_escape_sequence(matchobj):
c, codepoint4, codepoint6 = matchobj.groups()
if c:
return c
codepoint = int(codepoint4 or codepoint6, 16)
if codepoint <= 0xD7FF or 0xE000 <= codepoint:
return six.unichr(codepoint)
# Escape sequences reresenting surrogate code points are
# well-formed but invalid in Fluent. Replace them with U+FFFD
# REPLACEMENT CHARACTER.
return '�'
value = re.sub(
r'\\(?:(\\|")|u([0-9a-fA-F]{4})|U([0-9a-fA-F]{6}))',
from_escape_sequence,
self.value
)
return {'value': value}
[docs]class NumberLiteral(Literal):
def parse(self):
value = float(self.value)
decimal_position = self.value.find('.')
precision = 0
if decimal_position >= 0:
precision = len(self.value) - decimal_position - 1
return {
'value': value,
'precision': precision
}
[docs]class MessageReference(Expression):
def __init__(self, id, attribute=None, **kwargs):
super(MessageReference, self).__init__(**kwargs)
self.id = id
self.attribute = attribute
[docs]class TermReference(Expression):
def __init__(self, id, attribute=None, arguments=None, **kwargs):
super(TermReference, self).__init__(**kwargs)
self.id = id
self.attribute = attribute
self.arguments = arguments
[docs]class VariableReference(Expression):
def __init__(self, id, **kwargs):
super(VariableReference, self).__init__(**kwargs)
self.id = id
[docs]class FunctionReference(Expression):
def __init__(self, id, arguments, **kwargs):
super(FunctionReference, self).__init__(**kwargs)
self.id = id
self.arguments = arguments
[docs]class SelectExpression(Expression):
def __init__(self, selector, variants, **kwargs):
super(SelectExpression, self).__init__(**kwargs)
self.selector = selector
self.variants = variants
[docs]class CallArguments(SyntaxNode):
def __init__(self, positional=None, named=None, **kwargs):
super(CallArguments, self).__init__(**kwargs)
self.positional = [] if positional is None else positional
self.named = [] if named is None else named
[docs]class Attribute(SyntaxNode):
def __init__(self, id, value, **kwargs):
super(Attribute, self).__init__(**kwargs)
self.id = id
self.value = value
[docs]class Variant(SyntaxNode):
def __init__(self, key, value, default=False, **kwargs):
super(Variant, self).__init__(**kwargs)
self.key = key
self.value = value
self.default = default
[docs]class NamedArgument(SyntaxNode):
def __init__(self, name, value, **kwargs):
super(NamedArgument, self).__init__(**kwargs)
self.name = name
self.value = value
[docs]class Identifier(SyntaxNode):
def __init__(self, name, **kwargs):
super(Identifier, self).__init__(**kwargs)
self.name = name
[docs]class Junk(SyntaxNode):
def __init__(self, content=None, annotations=None, **kwargs):
super(Junk, self).__init__(**kwargs)
self.content = content
self.annotations = annotations or []
def add_annotation(self, annot):
self.annotations.append(annot)
[docs]class Span(BaseNode):
def __init__(self, start, end, **kwargs):
super(Span, self).__init__(**kwargs)
self.start = start
self.end = end
[docs]class Annotation(SyntaxNode):
def __init__(self, code, arguments=None, message=None, **kwargs):
super(Annotation, self).__init__(**kwargs)
self.code = code
self.arguments = arguments or []
self.message = message