-
Notifications
You must be signed in to change notification settings - Fork 6
Description
I've been wishing I could use tag strings lately and so to satisfy that craving I thought it would be cool to create an import-time transpiler that would rewrite:
my_tag @ f"my {custom} string"
# ^ or any other operator not typically used with strings
To be:
my_tag("my ", (lambda: custom, "custom", None, None), " string")
The syntax seems clever in a few different ways:
- it's valid Python
- syntax highlighters will highlight expressions in the string
- the transpiler will be straightforward to implement since the AST's JoinedStr already splits out the expressions
Something like this seems like a rather natural extension of @pauleveritt's work in viewdom.
Implementation Details
Some potential issues I've thought of and ways to solve them.
Static Typing
To deal with static type analyzers complaining about the unsupported @
operator, tag functions can be decorated with:
def tag(func: TagFunc[T]) -> Tag[T]:
# convince MyPy that our new syntax is valid
return cast(Tag, func)
class TagFunc(Protocol[T]):
def __call__(self, *args: Thunk) -> T: ...
class Tag(Generic[T]):
def __call__(self, *args: Thunk) -> T: ...
def __matmul__(self, other: str) -> T: ...
An alternate syntax of my_tag(f"...")
would not require this typing hack since tag functions must already accept *args: str | Thunk
. The main problem here is that there are probably many times where some_function(f"...")
would show up in a normal code-base. Thus it would be hard to determine whether any given instance of that syntax ought to be transpiled. Solving this would require users to mark which identifiers should be treated as tags - perhaps with a line like set_tags("my_tag")
. This seems more inconvenient than having the tag author add the aforementioned decorator though.
Performance
To avoid transpiling every module, users would need to indicate which ones should be rewritten at import-time. This could be done by having the user import this library somewhere at the top of their module. At import time, the transpiler, before parsing the file, would then scan it for a line like import <this_library>
or from <this_library> import ...
.