mirror of
https://github.com/HeyPuter/firefox.git
synced 2026-11-13 20:14:57 -10:00
377 lines
11 KiB
Python
377 lines
11 KiB
Python
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]
|