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