summaryrefslogtreecommitdiffstats
path: root/third_party/python/fluent.syntax/fluent/syntax/ast.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/python/fluent.syntax/fluent/syntax/ast.py
parentInitial commit. (diff)
downloadfirefox-esr-upstream.tar.xz
firefox-esr-upstream.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/python/fluent.syntax/fluent/syntax/ast.py')
-rw-r--r--third_party/python/fluent.syntax/fluent/syntax/ast.py376
1 files changed, 376 insertions, 0 deletions
diff --git a/third_party/python/fluent.syntax/fluent/syntax/ast.py b/third_party/python/fluent.syntax/fluent/syntax/ast.py
new file mode 100644
index 0000000000..d2e4849079
--- /dev/null
+++ b/third_party/python/fluent.syntax/fluent/syntax/ast.py
@@ -0,0 +1,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]