Closed
Description
The simple read-only visitor we landed in #96 is great for performance, but doesn't cut it for actual transformations. I'm having a local branch, that I hope is generic enough to replace traverse.
Not happy with the name of it, I'm using ContextVisitor
, because it uses enter
and exit
, but I can't really use python context managers, because you can't pass arguments to __exit__
.
I'm implementing .traverse()
as part of the patch to fluent.syntax
, but to make things more tangible, here's how transforms_from
could look like. Compare with https://hg.mozilla.org/l10n/fluent-migration/file/797c19359d4b/fluent/migrate/helpers.py#l43 through line 123.
class IntoTranforms(FTL.ContextVisitor):
IMPLICIT_TRANSFORMS = ("CONCAT",)
FORBIDDEN_TRANSFORMS = ("PLURALS", "REPLACE", "REPLACE_IN_TEXT")
def __init__(self, substitutions):
self.substitutions = substitutions
def generic_exit(self, node, props):
return node.__class__(**props)
def enter_Junk(self, node):
anno = node.annotations[0]
raise InvalidTransformError(
"Transform contains parse error: {}, at {}".format(
anno.message, anno.span.start))
def enter_CallExpression(self, node):
name = node.callee.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))
return True
def exit_CallExpression(self, node, props):
if node.callee.id.name != 'COPY':
return self.generic_exit(node, props)
args = (self.into_argument(arg) for arg in node.positional)
kwargs = {
arg.name.name: self.into_argument(arg.value)
for arg in node.named}
return COPY(*args, **kwargs)
def exit_Placeable(self, node, props):
if isinstance(props['expression'], Transform):
return props['expression']
return self.generic_exit(node, props)
def exit_Pattern(self, node, props):
# 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).
return CONCAT(*props['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__))
Metadata
Metadata
Assignees
Labels
No labels