forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			376 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			376 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 '<EFBFBD>'
 | ||
| 
 | ||
|         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]
 | 
