forked from mirrors/gecko-dev
148 lines
5 KiB
Python
148 lines
5 KiB
Python
"""Fluent AST helpers.
|
|
|
|
The functions defined in this module offer a shorthand for defining common AST
|
|
nodes.
|
|
|
|
They take a string argument and immediately return a corresponding AST node.
|
|
(As opposed to Transforms which are AST nodes on their own and only return the
|
|
migrated AST nodes when they are evaluated by a MigrationContext.) """
|
|
|
|
from __future__ import annotations
|
|
from typing import List
|
|
|
|
from fluent.syntax import FluentParser, ast as FTL
|
|
from fluent.syntax.visitor import Transformer
|
|
from .transforms import Transform, CONCAT, COPY, COPY_PATTERN
|
|
from .errors import NotSupportedError, InvalidTransformError
|
|
|
|
|
|
def VARIABLE_REFERENCE(name):
|
|
"""Create an ExternalArgument expression."""
|
|
|
|
return FTL.VariableReference(id=FTL.Identifier(name))
|
|
|
|
|
|
def MESSAGE_REFERENCE(name):
|
|
"""Create a MessageReference expression.
|
|
|
|
If the passed name contains a `.`, we're generating
|
|
a message reference with an attribute.
|
|
"""
|
|
if "." in name:
|
|
name, attribute = name.split(".")
|
|
attribute = FTL.Identifier(attribute)
|
|
else:
|
|
attribute = None
|
|
|
|
return FTL.MessageReference(
|
|
id=FTL.Identifier(name),
|
|
attribute=attribute,
|
|
)
|
|
|
|
|
|
def TERM_REFERENCE(name):
|
|
"""Create a TermReference expression."""
|
|
|
|
return FTL.TermReference(id=FTL.Identifier(name))
|
|
|
|
|
|
class IntoTranforms(Transformer):
|
|
IMPLICIT_TRANSFORMS = ("CONCAT",)
|
|
FORBIDDEN_TRANSFORMS = ("PLURALS", "REPLACE", "REPLACE_IN_TEXT")
|
|
|
|
def __init__(self, substitutions):
|
|
self.substitutions = substitutions
|
|
|
|
def visit_Junk(self, node):
|
|
anno = node.annotations[0]
|
|
raise InvalidTransformError(
|
|
"Transform contains parse error: {}, at {}".format(
|
|
anno.message, anno.span.start
|
|
)
|
|
)
|
|
|
|
def visit_FunctionReference(self, node):
|
|
name = node.id.name
|
|
if name in self.IMPLICIT_TRANSFORMS:
|
|
raise NotSupportedError(
|
|
"{} may not be used with transforms_from(). It runs "
|
|
"implicitly on all Patterns anyways.".format(name)
|
|
)
|
|
if name in self.FORBIDDEN_TRANSFORMS:
|
|
raise NotSupportedError(
|
|
"{} may not be used with transforms_from(). It requires "
|
|
"additional logic in Python code.".format(name)
|
|
)
|
|
if name in ("COPY", "COPY_PATTERN"):
|
|
args = (self.into_argument(arg) for arg in node.arguments.positional)
|
|
kwargs = {
|
|
arg.name.name: self.into_argument(arg.value)
|
|
for arg in node.arguments.named
|
|
}
|
|
if name == "COPY":
|
|
return COPY(*args, **kwargs)
|
|
return COPY_PATTERN(*args, **kwargs)
|
|
return self.generic_visit(node)
|
|
|
|
def visit_Placeable(self, node):
|
|
"""If the expression is a Transform, replace this Placeable
|
|
with the Transform it's holding.
|
|
Transforms evaluate to Patterns, which are flattened as
|
|
elements of Patterns in Transform.pattern_of, but only
|
|
one level deep.
|
|
"""
|
|
node = self.generic_visit(node)
|
|
if isinstance(node.expression, Transform):
|
|
return node.expression
|
|
return node
|
|
|
|
def visit_Pattern(self, node):
|
|
"""Replace the Pattern with CONCAT which is more accepting of its
|
|
elements. CONCAT takes PatternElements, Expressions and other
|
|
Patterns (e.g. returned from evaluating transforms).
|
|
"""
|
|
node = self.generic_visit(node)
|
|
return CONCAT(*node.elements)
|
|
|
|
def into_argument(self, node):
|
|
"""Convert AST node into an argument to migration transforms."""
|
|
if isinstance(node, FTL.StringLiteral):
|
|
# Special cases for booleans which don't exist in Fluent.
|
|
if node.value == "True":
|
|
return True
|
|
if node.value == "False":
|
|
return False
|
|
return node.value
|
|
if isinstance(node, FTL.MessageReference):
|
|
try:
|
|
return self.substitutions[node.id.name]
|
|
except KeyError:
|
|
raise InvalidTransformError(
|
|
"Unknown substitution in COPY: {}".format(node.id.name)
|
|
)
|
|
else:
|
|
raise InvalidTransformError(
|
|
"Invalid argument passed to COPY: {}".format(type(node).__name__)
|
|
)
|
|
|
|
|
|
def transforms_from(ftl, **substitutions) -> List[FTL.Message | FTL.Term]:
|
|
"""Parse FTL code into a list of Message nodes with Transforms.
|
|
|
|
The FTL may use a fabricated COPY function inside of placeables which
|
|
will be converted into actual COPY migration transform.
|
|
|
|
new-key = Hardcoded text { COPY("filepath.dtd", "string.key") }
|
|
|
|
For convenience, COPY may also refer to transforms_from's keyword
|
|
arguments via the MessageReference syntax:
|
|
|
|
transforms_from(\"""
|
|
new-key = Hardcoded text { COPY(file_dtd, "string.key") }
|
|
\""", file_dtd="very/long/path/to/a/file.dtd")
|
|
|
|
"""
|
|
|
|
parser = FluentParser(with_spans=False)
|
|
resource = parser.parse(ftl)
|
|
return IntoTranforms(substitutions).visit(resource).body
|