1# coding=utf-8
2from __future__ import unicode_literals
3import re
4import sys
5import json
6import six
7
8
9def to_json(value, fn=None):
10    if isinstance(value, BaseNode):
11        return value.to_json(fn)
12    if isinstance(value, list):
13        return list(to_json(item, fn) for item in value)
14    if isinstance(value, tuple):
15        return list(to_json(item, fn) for item in value)
16    else:
17        return value
18
19
20def from_json(value):
21    if isinstance(value, dict):
22        cls = getattr(sys.modules[__name__], value['type'])
23        args = {
24            k: from_json(v)
25            for k, v in value.items()
26            if k != 'type'
27        }
28        return cls(**args)
29    if isinstance(value, list):
30        return list(map(from_json, value))
31    else:
32        return value
33
34
35def scalars_equal(node1, node2, ignored_fields):
36    """Compare two nodes which are not lists."""
37
38    if type(node1) != type(node2):
39        return False
40
41    if isinstance(node1, BaseNode):
42        return node1.equals(node2, ignored_fields)
43
44    return node1 == node2
45
46
47class BaseNode(object):
48    """Base class for all Fluent AST nodes.
49
50    All productions described in the ASDL subclass BaseNode, including Span and
51    Annotation.  Implements __str__, to_json and traverse.
52    """
53
54    def clone(self):
55        """Create a deep clone of the current node."""
56        def visit(value):
57            """Clone node and its descendants."""
58            if isinstance(value, BaseNode):
59                return value.clone()
60            if isinstance(value, list):
61                return [visit(child) for child in value]
62            if isinstance(value, tuple):
63                return tuple(visit(child) for child in value)
64            return value
65
66        # Use all attributes found on the node as kwargs to the constructor.
67        return self.__class__(
68            **{name: visit(value) for name, value in vars(self).items()}
69        )
70
71    def equals(self, other, ignored_fields=['span']):
72        """Compare two nodes.
73
74        Nodes are deeply compared on a field by field basis. If possible, False
75        is returned early. When comparing attributes and variants in
76        SelectExpressions, the order doesn't matter. By default, spans are not
77        taken into account.
78        """
79
80        self_keys = set(vars(self).keys())
81        other_keys = set(vars(other).keys())
82
83        if ignored_fields:
84            for key in ignored_fields:
85                self_keys.discard(key)
86                other_keys.discard(key)
87
88        if self_keys != other_keys:
89            return False
90
91        for key in self_keys:
92            field1 = getattr(self, key)
93            field2 = getattr(other, key)
94
95            # List-typed nodes are compared item-by-item.  When comparing
96            # attributes and variants, the order of items doesn't matter.
97            if isinstance(field1, list) and isinstance(field2, list):
98                if len(field1) != len(field2):
99                    return False
100
101                for elem1, elem2 in zip(field1, field2):
102                    if not scalars_equal(elem1, elem2, ignored_fields):
103                        return False
104
105            elif not scalars_equal(field1, field2, ignored_fields):
106                return False
107
108        return True
109
110    def to_json(self, fn=None):
111        obj = {
112            name: to_json(value, fn)
113            for name, value in vars(self).items()
114        }
115        obj.update(
116            {'type': self.__class__.__name__}
117        )
118        return fn(obj) if fn else obj
119
120    def __str__(self):
121        return json.dumps(self.to_json())
122
123
124class SyntaxNode(BaseNode):
125    """Base class for AST nodes which can have Spans."""
126
127    def __init__(self, span=None, **kwargs):
128        super(SyntaxNode, self).__init__(**kwargs)
129        self.span = span
130
131    def add_span(self, start, end):
132        self.span = Span(start, end)
133
134
135class Resource(SyntaxNode):
136    def __init__(self, body=None, **kwargs):
137        super(Resource, self).__init__(**kwargs)
138        self.body = body or []
139
140
141class Entry(SyntaxNode):
142    """An abstract base class for useful elements of Resource.body."""
143
144
145class Message(Entry):
146    def __init__(self, id, value=None, attributes=None,
147                 comment=None, **kwargs):
148        super(Message, self).__init__(**kwargs)
149        self.id = id
150        self.value = value
151        self.attributes = attributes or []
152        self.comment = comment
153
154
155class Term(Entry):
156    def __init__(self, id, value, attributes=None,
157                 comment=None, **kwargs):
158        super(Term, self).__init__(**kwargs)
159        self.id = id
160        self.value = value
161        self.attributes = attributes or []
162        self.comment = comment
163
164
165class Pattern(SyntaxNode):
166    def __init__(self, elements, **kwargs):
167        super(Pattern, self).__init__(**kwargs)
168        self.elements = elements
169
170
171class PatternElement(SyntaxNode):
172    """An abstract base class for elements of Patterns."""
173
174
175class TextElement(PatternElement):
176    def __init__(self, value, **kwargs):
177        super(TextElement, self).__init__(**kwargs)
178        self.value = value
179
180
181class Placeable(PatternElement):
182    def __init__(self, expression, **kwargs):
183        super(Placeable, self).__init__(**kwargs)
184        self.expression = expression
185
186
187class Expression(SyntaxNode):
188    """An abstract base class for expressions."""
189
190
191class Literal(Expression):
192    """An abstract base class for literals."""
193    def __init__(self, value, **kwargs):
194        super(Literal, self).__init__(**kwargs)
195        self.value = value
196
197    def parse(self):
198        return {'value': self.value}
199
200
201class StringLiteral(Literal):
202    def parse(self):
203        def from_escape_sequence(matchobj):
204            c, codepoint4, codepoint6 = matchobj.groups()
205            if c:
206                return c
207            codepoint = int(codepoint4 or codepoint6, 16)
208            if codepoint <= 0xD7FF or 0xE000 <= codepoint:
209                return six.unichr(codepoint)
210            # Escape sequences reresenting surrogate code points are
211            # well-formed but invalid in Fluent. Replace them with U+FFFD
212            # REPLACEMENT CHARACTER.
213            return '�'
214
215        value = re.sub(
216            r'\\(?:(\\|")|u([0-9a-fA-F]{4})|U([0-9a-fA-F]{6}))',
217            from_escape_sequence,
218            self.value
219        )
220        return {'value': value}
221
222
223class NumberLiteral(Literal):
224    def parse(self):
225        value = float(self.value)
226        decimal_position = self.value.find('.')
227        precision = 0
228        if decimal_position >= 0:
229            precision = len(self.value) - decimal_position - 1
230        return {
231            'value': value,
232            'precision': precision
233        }
234
235
236class MessageReference(Expression):
237    def __init__(self, id, attribute=None, **kwargs):
238        super(MessageReference, self).__init__(**kwargs)
239        self.id = id
240        self.attribute = attribute
241
242
243class TermReference(Expression):
244    def __init__(self, id, attribute=None, arguments=None, **kwargs):
245        super(TermReference, self).__init__(**kwargs)
246        self.id = id
247        self.attribute = attribute
248        self.arguments = arguments
249
250
251class VariableReference(Expression):
252    def __init__(self, id, **kwargs):
253        super(VariableReference, self).__init__(**kwargs)
254        self.id = id
255
256
257class FunctionReference(Expression):
258    def __init__(self, id, arguments, **kwargs):
259        super(FunctionReference, self).__init__(**kwargs)
260        self.id = id
261        self.arguments = arguments
262
263
264class SelectExpression(Expression):
265    def __init__(self, selector, variants, **kwargs):
266        super(SelectExpression, self).__init__(**kwargs)
267        self.selector = selector
268        self.variants = variants
269
270
271class CallArguments(SyntaxNode):
272    def __init__(self, positional=None, named=None, **kwargs):
273        super(CallArguments, self).__init__(**kwargs)
274        self.positional = [] if positional is None else positional
275        self.named = [] if named is None else named
276
277
278class Attribute(SyntaxNode):
279    def __init__(self, id, value, **kwargs):
280        super(Attribute, self).__init__(**kwargs)
281        self.id = id
282        self.value = value
283
284
285class Variant(SyntaxNode):
286    def __init__(self, key, value, default=False, **kwargs):
287        super(Variant, self).__init__(**kwargs)
288        self.key = key
289        self.value = value
290        self.default = default
291
292
293class NamedArgument(SyntaxNode):
294    def __init__(self, name, value, **kwargs):
295        super(NamedArgument, self).__init__(**kwargs)
296        self.name = name
297        self.value = value
298
299
300class Identifier(SyntaxNode):
301    def __init__(self, name, **kwargs):
302        super(Identifier, self).__init__(**kwargs)
303        self.name = name
304
305
306class BaseComment(Entry):
307    def __init__(self, content=None, **kwargs):
308        super(BaseComment, self).__init__(**kwargs)
309        self.content = content
310
311
312class Comment(BaseComment):
313    def __init__(self, content=None, **kwargs):
314        super(Comment, self).__init__(content, **kwargs)
315
316
317class GroupComment(BaseComment):
318    def __init__(self, content=None, **kwargs):
319        super(GroupComment, self).__init__(content, **kwargs)
320
321
322class ResourceComment(BaseComment):
323    def __init__(self, content=None, **kwargs):
324        super(ResourceComment, self).__init__(content, **kwargs)
325
326
327class Junk(SyntaxNode):
328    def __init__(self, content=None, annotations=None, **kwargs):
329        super(Junk, self).__init__(**kwargs)
330        self.content = content
331        self.annotations = annotations or []
332
333    def add_annotation(self, annot):
334        self.annotations.append(annot)
335
336
337class Span(BaseNode):
338    def __init__(self, start, end, **kwargs):
339        super(Span, self).__init__(**kwargs)
340        self.start = start
341        self.end = end
342
343
344class Annotation(SyntaxNode):
345    def __init__(self, code, arguments=None, message=None, **kwargs):
346        super(Annotation, self).__init__(**kwargs)
347        self.code = code
348        self.arguments = arguments or []
349        self.message = message
350