1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
|
import re
import sys
import json
from typing import Any, Callable, Dict, List, TypeVar, Union, cast
Node = TypeVar('Node', bound='BaseNode')
ToJsonFn = Callable[[Dict[str, Any]], Any]
def to_json(value: Any, fn: Union[ToJsonFn, None] = None) -> Any:
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: Any) -> Any:
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: Any, node2: Any, ignored_fields: List[str]) -> bool:
"""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 cast(bool, node1 == node2)
class BaseNode:
"""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: Node) -> Node:
"""Create a deep clone of the current node."""
def visit(value: Any) -> Any:
"""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: 'BaseNode', ignored_fields: List[str] = ['span']) -> bool:
"""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: Union[ToJsonFn, None] = None) -> Any:
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) -> str:
return json.dumps(self.to_json())
class SyntaxNode(BaseNode):
"""Base class for AST nodes which can have Spans."""
def __init__(self, span: Union['Span', None] = None, **kwargs: Any):
super().__init__(**kwargs)
self.span = span
def add_span(self, start: int, end: int) -> None:
self.span = Span(start, end)
class Resource(SyntaxNode):
def __init__(self, body: Union[List['EntryType'], None] = None, **kwargs: Any):
super().__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: 'Identifier',
value: Union['Pattern', None] = None,
attributes: Union[List['Attribute'], None] = None,
comment: Union['Comment', None] = None,
**kwargs: Any):
super().__init__(**kwargs)
self.id = id
self.value = value
self.attributes = attributes or []
self.comment = comment
class Term(Entry):
def __init__(self, id: 'Identifier', value: 'Pattern', attributes: Union[List['Attribute'], None] = None,
comment: Union['Comment', None] = None, **kwargs: Any):
super().__init__(**kwargs)
self.id = id
self.value = value
self.attributes = attributes or []
self.comment = comment
class Pattern(SyntaxNode):
def __init__(self, elements: List[Union['TextElement', 'Placeable']], **kwargs: Any):
super().__init__(**kwargs)
self.elements = elements
class PatternElement(SyntaxNode):
"""An abstract base class for elements of Patterns."""
class TextElement(PatternElement):
def __init__(self, value: str, **kwargs: Any):
super().__init__(**kwargs)
self.value = value
class Placeable(PatternElement):
def __init__(self,
expression: Union['InlineExpression', 'Placeable', 'SelectExpression'],
**kwargs: Any):
super().__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: str, **kwargs: Any):
super().__init__(**kwargs)
self.value = value
def parse(self) -> Dict[str, Any]:
return {'value': self.value}
class StringLiteral(Literal):
def parse(self) -> Dict[str, str]:
def from_escape_sequence(matchobj: Any) -> str:
c, codepoint4, codepoint6 = matchobj.groups()
if c:
return cast(str, c)
codepoint = int(codepoint4 or codepoint6, 16)
if codepoint <= 0xD7FF or 0xE000 <= codepoint:
return chr(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) -> Dict[str, Union[float, int]]:
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: 'Identifier', attribute: Union['Identifier', None] = None, **kwargs: Any):
super().__init__(**kwargs)
self.id = id
self.attribute = attribute
class TermReference(Expression):
def __init__(self,
id: 'Identifier',
attribute: Union['Identifier', None] = None,
arguments: Union['CallArguments', None] = None,
**kwargs: Any):
super().__init__(**kwargs)
self.id = id
self.attribute = attribute
self.arguments = arguments
class VariableReference(Expression):
def __init__(self, id: 'Identifier', **kwargs: Any):
super().__init__(**kwargs)
self.id = id
class FunctionReference(Expression):
def __init__(self, id: 'Identifier', arguments: 'CallArguments', **kwargs: Any):
super().__init__(**kwargs)
self.id = id
self.arguments = arguments
class SelectExpression(Expression):
def __init__(self, selector: 'InlineExpression', variants: List['Variant'], **kwargs: Any):
super().__init__(**kwargs)
self.selector = selector
self.variants = variants
class CallArguments(SyntaxNode):
def __init__(self,
positional: Union[List[Union['InlineExpression', Placeable]], None] = None,
named: Union[List['NamedArgument'], None] = None,
**kwargs: Any):
super().__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: 'Identifier', value: Pattern, **kwargs: Any):
super().__init__(**kwargs)
self.id = id
self.value = value
class Variant(SyntaxNode):
def __init__(self, key: Union['Identifier', NumberLiteral], value: Pattern, default: bool = False, **kwargs: Any):
super().__init__(**kwargs)
self.key = key
self.value = value
self.default = default
class NamedArgument(SyntaxNode):
def __init__(self, name: 'Identifier', value: Union[NumberLiteral, StringLiteral], **kwargs: Any):
super().__init__(**kwargs)
self.name = name
self.value = value
class Identifier(SyntaxNode):
def __init__(self, name: str, **kwargs: Any):
super().__init__(**kwargs)
self.name = name
class BaseComment(Entry):
def __init__(self, content: Union[str, None] = None, **kwargs: Any):
super().__init__(**kwargs)
self.content = content
class Comment(BaseComment):
def __init__(self, content: Union[str, None] = None, **kwargs: Any):
super().__init__(content, **kwargs)
class GroupComment(BaseComment):
def __init__(self, content: Union[str, None] = None, **kwargs: Any):
super().__init__(content, **kwargs)
class ResourceComment(BaseComment):
def __init__(self, content: Union[str, None] = None, **kwargs: Any):
super().__init__(content, **kwargs)
class Junk(SyntaxNode):
def __init__(self,
content: Union[str, None] = None,
annotations: Union[List['Annotation'], None] = None,
**kwargs: Any):
super().__init__(**kwargs)
self.content = content
self.annotations = annotations or []
def add_annotation(self, annot: 'Annotation') -> None:
self.annotations.append(annot)
class Span(BaseNode):
def __init__(self, start: int, end: int, **kwargs: Any):
super().__init__(**kwargs)
self.start = start
self.end = end
class Annotation(SyntaxNode):
def __init__(self,
code: str,
arguments: Union[List[Any], None] = None,
message: Union[str, None] = None,
**kwargs: Any):
super().__init__(**kwargs)
self.code = code
self.arguments = arguments or []
self.message = message
EntryType = Union[Message, Term, Comment, GroupComment, ResourceComment, Junk]
InlineExpression = Union[NumberLiteral, StringLiteral, MessageReference,
TermReference, VariableReference, FunctionReference]
|