KiBot/kibot/mcpyrate/markers.py

66 lines
2.3 KiB
Python

# -*- coding: utf-8; -*-
"""AST markers for internal communication.
*Internal* here means they are to be never passed to Python's `compile`;
macros may use them to work together.
"""
__all__ = ["ASTMarker", "get_markers", "delete_markers"]
import ast
from .walker import Walker
class ASTMarker(ast.AST):
"""Base class for AST markers.
Markers are AST-node-like objects meant for communication between
co-operating, related macros. They are also used by the macro expander
to talk with itself during expansion.
We inherit from `ast.AST`, so that during macro expansion, a marker
behaves like a single AST node.
It is a postcondition of a completed macro expansion that no markers
remain in the AST.
To help fail-fast, if you define your own marker types, use `get_markers`
to check (at an appropriate point) that the expanded AST has no instances
of your own markers remaining. (You'll want a base class for your own markers.)
A typical usage example is in the quasiquote system, where the unquote
operators (some of which expand to markers) may only appear inside a quoted
section. So just before the quote operator exits, it checks that all
quasiquote markers within that section have been compiled away.
"""
def __init__(self, body):
"""body: the actual AST that is annotated by this marker"""
self.body = body
self._fields = ["body"] # support ast.iter_fields
def get_markers(tree, cls=ASTMarker):
"""Return a `list` of any `cls` instances found in `tree`. For output validation."""
class ASTMarkerCollector(Walker):
def transform(self, tree):
if isinstance(tree, cls):
self.collect(tree)
return self.generic_visit(tree)
w = ASTMarkerCollector()
w.visit(tree)
return w.collected
def delete_markers(tree, cls=ASTMarker):
"""Delete any `cls` ASTMarker instances found in `tree`.
The deletion takes place by replacing each marker node with
the actual AST node stored in its `body` attribute.
"""
class ASTMarkerDeleter(Walker):
def transform(self, tree):
if isinstance(tree, cls):
tree = tree.body
return self.generic_visit(tree)
return ASTMarkerDeleter().visit(tree)