summaryrefslogtreecommitdiffstats
path: root/third_party/python/fluent.syntax/fluent/syntax/ast.py
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/python/fluent.syntax/fluent/syntax/ast.py')
-rw-r--r--third_party/python/fluent.syntax/fluent/syntax/ast.py349
1 files changed, 349 insertions, 0 deletions
diff --git a/third_party/python/fluent.syntax/fluent/syntax/ast.py b/third_party/python/fluent.syntax/fluent/syntax/ast.py
new file mode 100644
index 0000000000..7ad5d611d6
--- /dev/null
+++ b/third_party/python/fluent.syntax/fluent/syntax/ast.py
@@ -0,0 +1,349 @@
+# 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
+
+
+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.
+ """
+
+ 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()}
+ )
+
+ 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())
+
+
+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)
+
+
+class Resource(SyntaxNode):
+ def __init__(self, body=None, **kwargs):
+ super(Resource, self).__init__(**kwargs)
+ self.body = body or []
+
+
+class Entry(SyntaxNode):
+ """An abstract base class for useful elements of Resource.body."""
+
+
+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
+
+
+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
+
+
+class Pattern(SyntaxNode):
+ def __init__(self, elements, **kwargs):
+ super(Pattern, self).__init__(**kwargs)
+ self.elements = elements
+
+
+class PatternElement(SyntaxNode):
+ """An abstract base class for elements of Patterns."""
+
+
+class TextElement(PatternElement):
+ def __init__(self, value, **kwargs):
+ super(TextElement, self).__init__(**kwargs)
+ self.value = value
+
+
+class Placeable(PatternElement):
+ def __init__(self, expression, **kwargs):
+ super(Placeable, self).__init__(**kwargs)
+ self.expression = expression
+
+
+class Expression(SyntaxNode):
+ """An abstract base class for expressions."""
+
+
+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}
+
+
+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}
+
+
+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
+ }
+
+
+class MessageReference(Expression):
+ def __init__(self, id, attribute=None, **kwargs):
+ super(MessageReference, self).__init__(**kwargs)
+ self.id = id
+ self.attribute = attribute
+
+
+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
+
+
+class VariableReference(Expression):
+ def __init__(self, id, **kwargs):
+ super(VariableReference, self).__init__(**kwargs)
+ self.id = id
+
+
+class FunctionReference(Expression):
+ def __init__(self, id, arguments, **kwargs):
+ super(FunctionReference, self).__init__(**kwargs)
+ self.id = id
+ self.arguments = arguments
+
+
+class SelectExpression(Expression):
+ def __init__(self, selector, variants, **kwargs):
+ super(SelectExpression, self).__init__(**kwargs)
+ self.selector = selector
+ self.variants = variants
+
+
+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
+
+
+class Attribute(SyntaxNode):
+ def __init__(self, id, value, **kwargs):
+ super(Attribute, self).__init__(**kwargs)
+ self.id = id
+ self.value = value
+
+
+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
+
+
+class NamedArgument(SyntaxNode):
+ def __init__(self, name, value, **kwargs):
+ super(NamedArgument, self).__init__(**kwargs)
+ self.name = name
+ self.value = value
+
+
+class Identifier(SyntaxNode):
+ def __init__(self, name, **kwargs):
+ super(Identifier, self).__init__(**kwargs)
+ self.name = name
+
+
+class BaseComment(Entry):
+ def __init__(self, content=None, **kwargs):
+ super(BaseComment, self).__init__(**kwargs)
+ self.content = content
+
+
+class Comment(BaseComment):
+ def __init__(self, content=None, **kwargs):
+ super(Comment, self).__init__(content, **kwargs)
+
+
+class GroupComment(BaseComment):
+ def __init__(self, content=None, **kwargs):
+ super(GroupComment, self).__init__(content, **kwargs)
+
+
+class ResourceComment(BaseComment):
+ def __init__(self, content=None, **kwargs):
+ super(ResourceComment, self).__init__(content, **kwargs)
+
+
+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)
+
+
+class Span(BaseNode):
+ def __init__(self, start, end, **kwargs):
+ super(Span, self).__init__(**kwargs)
+ self.start = start
+ self.end = end
+
+
+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