Source code for fluent.syntax.ast

# 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 BaseComment(Entry): def __init__(self, content=None, **kwargs): super(BaseComment, self).__init__(**kwargs) self.content = content
[docs]class Comment(BaseComment): def __init__(self, content=None, **kwargs): super(Comment, self).__init__(content, **kwargs)
[docs]class GroupComment(BaseComment): def __init__(self, content=None, **kwargs): super(GroupComment, self).__init__(content, **kwargs)
[docs]class ResourceComment(BaseComment): def __init__(self, content=None, **kwargs): super(ResourceComment, self).__init__(content, **kwargs)
[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