forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			237 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
	
		
			8 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| from typing import List, Union
 | |
| from . import ast
 | |
| 
 | |
| 
 | |
| def indent_except_first_line(content: str) -> str:
 | |
|     return "    ".join(
 | |
|         content.splitlines(True)
 | |
|     )
 | |
| 
 | |
| 
 | |
| def includes_new_line(elem: Union[ast.TextElement, ast.Placeable]) -> bool:
 | |
|     return isinstance(elem, ast.TextElement) and "\n" in elem.value
 | |
| 
 | |
| 
 | |
| def is_select_expr(elem: Union[ast.TextElement, ast.Placeable]) -> bool:
 | |
|     return (
 | |
|         isinstance(elem, ast.Placeable) and
 | |
|         isinstance(elem.expression, ast.SelectExpression))
 | |
| 
 | |
| 
 | |
| def should_start_on_new_line(pattern: ast.Pattern) -> bool:
 | |
|     is_multiline = any(is_select_expr(elem) for elem in pattern.elements) \
 | |
|         or any(includes_new_line(elem) for elem in pattern.elements)
 | |
| 
 | |
|     if is_multiline:
 | |
|         first_element = pattern.elements[0]
 | |
|         if isinstance(first_element, ast.TextElement):
 | |
|             first_char = first_element.value[0]
 | |
|             if first_char in ("[", ".", "*"):
 | |
|                 return False
 | |
|         return True
 | |
|     return False
 | |
| 
 | |
| 
 | |
| class FluentSerializer:
 | |
|     """FluentSerializer converts :class:`.ast.SyntaxNode` objects to unicode strings.
 | |
| 
 | |
|     `with_junk` controls if parse errors are written back or not.
 | |
|     """
 | |
|     HAS_ENTRIES = 1
 | |
| 
 | |
|     def __init__(self, with_junk: bool = False):
 | |
|         self.with_junk = with_junk
 | |
| 
 | |
|     def serialize(self, resource: ast.Resource) -> str:
 | |
|         "Serialize a :class:`.ast.Resource` to a string."
 | |
|         if not isinstance(resource, ast.Resource):
 | |
|             raise Exception('Unknown resource type: {}'.format(type(resource)))
 | |
| 
 | |
|         state = 0
 | |
| 
 | |
|         parts: List[str] = []
 | |
|         for entry in resource.body:
 | |
|             if not isinstance(entry, ast.Junk) or self.with_junk:
 | |
|                 parts.append(self.serialize_entry(entry, state))
 | |
|                 if not state & self.HAS_ENTRIES:
 | |
|                     state |= self.HAS_ENTRIES
 | |
| 
 | |
|         return "".join(parts)
 | |
| 
 | |
|     def serialize_entry(self, entry: ast.EntryType, state: int = 0) -> str:
 | |
|         "Serialize an :class:`.ast.Entry` to a string."
 | |
|         if isinstance(entry, ast.Message):
 | |
|             return serialize_message(entry)
 | |
|         if isinstance(entry, ast.Term):
 | |
|             return serialize_term(entry)
 | |
|         if isinstance(entry, ast.Comment):
 | |
|             if state & self.HAS_ENTRIES:
 | |
|                 return "\n{}\n".format(serialize_comment(entry, "#"))
 | |
|             return "{}\n".format(serialize_comment(entry, "#"))
 | |
|         if isinstance(entry, ast.GroupComment):
 | |
|             if state & self.HAS_ENTRIES:
 | |
|                 return "\n{}\n".format(serialize_comment(entry, "##"))
 | |
|             return "{}\n".format(serialize_comment(entry, "##"))
 | |
|         if isinstance(entry, ast.ResourceComment):
 | |
|             if state & self.HAS_ENTRIES:
 | |
|                 return "\n{}\n".format(serialize_comment(entry, "###"))
 | |
|             return "{}\n".format(serialize_comment(entry, "###"))
 | |
|         if isinstance(entry, ast.Junk):
 | |
|             return serialize_junk(entry)
 | |
|         raise Exception('Unknown entry type: {}'.format(type(entry)))
 | |
| 
 | |
| 
 | |
| def serialize_comment(comment: Union[ast.Comment, ast.GroupComment, ast.ResourceComment], prefix: str = "#") -> str:
 | |
|     if not comment.content:
 | |
|         return f'{prefix}\n'
 | |
| 
 | |
|     prefixed = "\n".join([
 | |
|         prefix if len(line) == 0 else f"{prefix} {line}"
 | |
|         for line in comment.content.split("\n")
 | |
|     ])
 | |
|     # Add the trailing line break.
 | |
|     return f'{prefixed}\n'
 | |
| 
 | |
| 
 | |
| def serialize_junk(junk: ast.Junk) -> str:
 | |
|     return junk.content or ''
 | |
| 
 | |
| 
 | |
| def serialize_message(message: ast.Message) -> str:
 | |
|     parts: List[str] = []
 | |
| 
 | |
|     if message.comment:
 | |
|         parts.append(serialize_comment(message.comment))
 | |
| 
 | |
|     parts.append(f"{message.id.name} =")
 | |
| 
 | |
|     if message.value:
 | |
|         parts.append(serialize_pattern(message.value))
 | |
| 
 | |
|     if message.attributes:
 | |
|         for attribute in message.attributes:
 | |
|             parts.append(serialize_attribute(attribute))
 | |
| 
 | |
|     parts.append("\n")
 | |
|     return ''.join(parts)
 | |
| 
 | |
| 
 | |
| def serialize_term(term: ast.Term) -> str:
 | |
|     parts: List[str] = []
 | |
| 
 | |
|     if term.comment:
 | |
|         parts.append(serialize_comment(term.comment))
 | |
| 
 | |
|     parts.append(f"-{term.id.name} =")
 | |
|     parts.append(serialize_pattern(term.value))
 | |
| 
 | |
|     if term.attributes:
 | |
|         for attribute in term.attributes:
 | |
|             parts.append(serialize_attribute(attribute))
 | |
| 
 | |
|     parts.append("\n")
 | |
|     return ''.join(parts)
 | |
| 
 | |
| 
 | |
| def serialize_attribute(attribute: ast.Attribute) -> str:
 | |
|     return "\n    .{} ={}".format(
 | |
|         attribute.id.name,
 | |
|         indent_except_first_line(serialize_pattern(attribute.value))
 | |
|     )
 | |
| 
 | |
| 
 | |
| def serialize_pattern(pattern: ast.Pattern) -> str:
 | |
|     content = "".join(serialize_element(elem) for elem in pattern.elements)
 | |
|     content = indent_except_first_line(content)
 | |
| 
 | |
|     if should_start_on_new_line(pattern):
 | |
|         return f'\n    {content}'
 | |
| 
 | |
|     return f' {content}'
 | |
| 
 | |
| 
 | |
| def serialize_element(element: ast.PatternElement) -> str:
 | |
|     if isinstance(element, ast.TextElement):
 | |
|         return element.value
 | |
|     if isinstance(element, ast.Placeable):
 | |
|         return serialize_placeable(element)
 | |
|     raise Exception('Unknown element type: {}'.format(type(element)))
 | |
| 
 | |
| 
 | |
| def serialize_placeable(placeable: ast.Placeable) -> str:
 | |
|     expr = placeable.expression
 | |
|     if isinstance(expr, ast.Placeable):
 | |
|         return "{{{}}}".format(serialize_placeable(expr))
 | |
|     if isinstance(expr, ast.SelectExpression):
 | |
|         # Special-case select expressions to control the withespace around the
 | |
|         # opening and the closing brace.
 | |
|         return "{{ {}}}".format(serialize_expression(expr))
 | |
|     if isinstance(expr, ast.Expression):
 | |
|         return "{{ {} }}".format(serialize_expression(expr))
 | |
|     raise Exception('Unknown expression type: {}'.format(type(expr)))
 | |
| 
 | |
| 
 | |
| def serialize_expression(expression: Union[ast.Expression, ast.Placeable]) -> str:
 | |
|     if isinstance(expression, ast.StringLiteral):
 | |
|         return f'"{expression.value}"'
 | |
|     if isinstance(expression, ast.NumberLiteral):
 | |
|         return expression.value
 | |
|     if isinstance(expression, ast.VariableReference):
 | |
|         return f"${expression.id.name}"
 | |
|     if isinstance(expression, ast.TermReference):
 | |
|         out = f"-{expression.id.name}"
 | |
|         if expression.attribute is not None:
 | |
|             out += f".{expression.attribute.name}"
 | |
|         if expression.arguments is not None:
 | |
|             out += serialize_call_arguments(expression.arguments)
 | |
|         return out
 | |
|     if isinstance(expression, ast.MessageReference):
 | |
|         out = expression.id.name
 | |
|         if expression.attribute is not None:
 | |
|             out += f".{expression.attribute.name}"
 | |
|         return out
 | |
|     if isinstance(expression, ast.FunctionReference):
 | |
|         args = serialize_call_arguments(expression.arguments)
 | |
|         return f"{expression.id.name}{args}"
 | |
|     if isinstance(expression, ast.SelectExpression):
 | |
|         out = "{} ->".format(
 | |
|             serialize_expression(expression.selector))
 | |
|         for variant in expression.variants:
 | |
|             out += serialize_variant(variant)
 | |
|         return f"{out}\n"
 | |
|     if isinstance(expression, ast.Placeable):
 | |
|         return serialize_placeable(expression)
 | |
|     raise Exception('Unknown expression type: {}'.format(type(expression)))
 | |
| 
 | |
| 
 | |
| def serialize_variant(variant: ast.Variant) -> str:
 | |
|     return "\n{}[{}]{}".format(
 | |
|         "   *" if variant.default else "    ",
 | |
|         serialize_variant_key(variant.key),
 | |
|         indent_except_first_line(serialize_pattern(variant.value))
 | |
|     )
 | |
| 
 | |
| 
 | |
| def serialize_call_arguments(expr: ast.CallArguments) -> str:
 | |
|     positional = ", ".join(
 | |
|         serialize_expression(arg) for arg in expr.positional)
 | |
|     named = ", ".join(
 | |
|         serialize_named_argument(arg) for arg in expr.named)
 | |
|     if len(expr.positional) > 0 and len(expr.named) > 0:
 | |
|         return f'({positional}, {named})'
 | |
|     return '({})'.format(positional or named)
 | |
| 
 | |
| 
 | |
| def serialize_named_argument(arg: ast.NamedArgument) -> str:
 | |
|     return "{}: {}".format(
 | |
|         arg.name.name,
 | |
|         serialize_expression(arg.value)
 | |
|     )
 | |
| 
 | |
| 
 | |
| def serialize_variant_key(key: Union[ast.Identifier, ast.NumberLiteral]) -> str:
 | |
|     if isinstance(key, ast.Identifier):
 | |
|         return key.name
 | |
|     if isinstance(key, ast.NumberLiteral):
 | |
|         return key.value
 | |
|     raise Exception('Unknown variant key type: {}'.format(type(key)))
 | 
