diff options
Diffstat (limited to 'third_party/python/fluent.syntax/fluent/syntax/ast.py')
-rw-r--r-- | third_party/python/fluent.syntax/fluent/syntax/ast.py | 349 |
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 |