Created
July 22, 2025 22:46
-
-
Save elbakramer/7c9dc0209c64a92acb5e9006ac2bd412 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from __future__ import annotations | |
import ast as pyast | |
import contextlib | |
import dataclasses | |
import typing | |
from collections import ChainMap | |
from typing import ClassVar | |
import click | |
import pynescript.ast as pyneast | |
from pynescript.ast import NodeVisitor | |
class PinescriptToPynecoreScriptTransformer(NodeVisitor): | |
_primitive_types: ClassVar[set[str]] = { | |
"int", | |
"float", | |
"bool", | |
"string", | |
} | |
_casting_types: ClassVar[set[str]] = { | |
"int", | |
"float", | |
"bool", | |
"color", | |
"string", | |
"line", | |
"linefill", | |
"label", | |
"box", | |
"table", | |
} | |
_python_type_to_pine_type_mapping: ClassVar[dict[type, str]] = { | |
int: "int", | |
float: "float", | |
bool: "bool", | |
str: "string", | |
} | |
_namespace_mapping: ClassVar[dict[str, str]] = { | |
"str": "string", | |
} | |
_pine_to_pyne_type_mapping: ClassVar[dict[str, str]] = { | |
"int": "int", | |
"float": "float", | |
"bool": "bool", | |
"color": "Color", | |
"string": "str", | |
"line": "Line", | |
"linefill": "LineFill", | |
"label": "Label", | |
"box": "Box", | |
"table": "Table", | |
"polyline": "Polyline", | |
"chart.point": "ChartPoint", | |
"array": "list", | |
"matrix": "Matrx", | |
"map": "dict", | |
} | |
def __init__(self): | |
self._inputs = [] | |
self._parsing_type = False | |
self._variable_types = ChainMap() | |
@contextlib.contextmanager | |
def type_parser(self): | |
self._parsing_type = True | |
try: | |
yield | |
finally: | |
self._parsing_type = False | |
def enter_block(self): | |
self._variable_types = self._variable_types.new_child() | |
def exit_block(self): | |
self._variable_types = self._variable_types.parents | |
@contextlib.contextmanager | |
def local_block(self): | |
self.enter_block() | |
try: | |
yield | |
finally: | |
self.exit_block() | |
@classmethod | |
def _init_block_result(cls, func_def: pyast.FunctionDef): | |
assign = pyast.Assign( | |
targets=[ | |
pyast.Name( | |
id="__block_result__", | |
cxt=pyast.Store(), | |
) | |
], | |
value=pyast.Name( | |
id="na", | |
ctx=pyast.Load(), | |
), | |
) | |
return_assigned = pyast.Return( | |
value=pyast.Name( | |
id="__block_result__", | |
ctx=pyast.Load(), | |
) | |
) | |
func_def.body.insert(-1, assign) | |
func_def.body.append(return_assigned) | |
@classmethod | |
def _set_block_result(cls, last_stmt: pyast.For | pyast.While | pyast.If): | |
inner_last_stmt = last_stmt.orelse[-1] if last_stmt.orelse else last_stmt.body[-1] | |
if isinstance(inner_last_stmt, pyast.Expr): | |
inner_last_stmt = pyast.Assign( | |
targets=[ | |
pyast.Name( | |
id="__block_result__", | |
cxt=pyast.Store(), | |
) | |
], | |
value=inner_last_stmt.value, | |
) | |
if last_stmt.orelse: | |
last_stmt.orelse[-1] = inner_last_stmt | |
else: | |
last_stmt.body[-1] = inner_last_stmt | |
elif isinstance(inner_last_stmt, pyast.For | pyast.While | pyast.If): | |
cls._set_block_result(inner_last_stmt) | |
@classmethod | |
def _add_return_stmt(cls, func_def: pyast.FunctionDef): | |
last_stmt = func_def.body[-1] | |
if isinstance(last_stmt, pyast.Expr): | |
value = last_stmt.value | |
last_stmt = pyast.Return(value=value) | |
func_def.body[-1] = last_stmt | |
elif isinstance(last_stmt, pyast.For | pyast.While | pyast.If): | |
cls._init_block_result(func_def) | |
cls._set_block_result(last_stmt) | |
def visit_Script(self, node: pyneast.Script) -> pyast.Module: | |
body = [] | |
for stmt in node.body: | |
result = self.visit(stmt) | |
if result is not None: | |
if isinstance(result, list): | |
body.extend(result) | |
else: | |
body.append(result) | |
decl_stmt = body[0] | |
if not isinstance(decl_stmt, pyast.Expr): | |
msg = "no declaration statement, first statement is not an expression" | |
raise ValueError(msg) | |
if not isinstance(decl_stmt.value, pyast.Call): | |
msg = "no declaration statement, first statement is not a function call" | |
raise ValueError(msg) | |
if not isinstance(decl_stmt.value.func, pyast.Name): | |
msg = "no declaration statement, function is not simple name" | |
raise ValueError(msg) | |
if decl_stmt.value.func.id not in {"indicator", "strategy", "library"}: | |
msg = f"no declaration statement, function name is not in [indicator, strategy, library] but {decl_stmt.value.func.id}" | |
raise ValueError(msg) | |
decl_call = decl_stmt.value | |
main_decorator = pyast.Call( | |
func=pyast.Attribute( | |
value=pyast.Name( | |
id="script", | |
ctx=pyast.Load(), | |
), | |
attr="indicator", | |
ctx=pyast.Load(), | |
), | |
args=decl_call.args, | |
keywords=decl_call.keywords, | |
) | |
body = body[1:] | |
docstring = pyast.Expr(value=pyast.Constant(value="\n@pyne\n")) | |
modules = [ | |
"pynecore", | |
"pynecore.core.pine_cast", | |
"pynecore.core.series", | |
"pynecore.lib", | |
"pynecore.types", | |
] | |
imports = [] | |
for module in modules: | |
import_stmt = pyast.ImportFrom( | |
module=module, | |
names=[pyast.alias(name="*")], | |
level=0, | |
) | |
imports.append(import_stmt) | |
args = [] | |
defaults = [] | |
for input in self._inputs: | |
if isinstance(input, pyast.Assign): | |
arg = input.targets[0].id | |
arg = pyast.arg(arg=arg) | |
args.append(arg) | |
default = input.value | |
defaults.append(default) | |
elif isinstance(input, pyast.AnnAssign): | |
arg = input.target.id | |
annotation = input.annotation | |
arg = pyast.arg(arg=arg, annotation=annotation) | |
args.append(arg) | |
default = input.value | |
defaults.append(default) | |
else: | |
msg = "unexpected input assignment" | |
raise RuntimeError(msg) | |
main_func = pyast.FunctionDef( | |
name="main", | |
args=pyast.arguments( | |
posonlyargs=[], | |
args=args, | |
kwonlyargs=[], | |
kw_defaults=[], | |
defaults=defaults, | |
), | |
body=body, | |
decorator_list=[main_decorator], | |
) | |
return pyast.Module( | |
body=[docstring, *imports, main_func], | |
type_ignores=[], | |
) | |
def visit_Expression(self, node: pyneast.Expression) -> pyast.Expression: | |
body = self.visit(node.body) | |
return pyast.Expression( | |
body=body, | |
) | |
def visit_FunctionDef(self, node: pyneast.FunctionDef) -> pyast.FunctionDef: | |
with self.local_block(): | |
name = node.name | |
args_and_defaults = [self.visit(arg) for arg in node.args] | |
args_and_defaults = typing.cast(list[tuple[pyast.arg, pyast.expr | None]], args_and_defaults) | |
args = [arg for arg, default in args_and_defaults] | |
defaults = [default for arg, default in args_and_defaults if default is not None] | |
args = pyast.arguments( | |
posonlyargs=[], | |
args=args, | |
kwonlyargs=[], | |
kw_defaults=[], | |
defaults=defaults, | |
) | |
body = [self.visit(stmt) for stmt in node.body] | |
decorator_list = [] | |
func_def = pyast.FunctionDef( | |
name=name, | |
args=args, | |
body=body, | |
decorator_list=decorator_list, | |
) | |
self._add_return_stmt(func_def) | |
return func_def | |
def visit_TypeDef(self, node: pyneast.TypeDef): | |
msg = f"unsupported node {node}" | |
raise ValueError(msg) | |
def visit_Assign(self, node: pyneast.Assign) -> pyast.Assign | pyast.AnnAssign | None: | |
if ( | |
isinstance(node.value, pyneast.Call) | |
and isinstance(node.value.func, pyneast.Attribute) | |
and isinstance(node.value.func.value, pyneast.Name) | |
and node.value.func.value.id == "input" | |
): | |
parsing_input = True | |
else: | |
parsing_input = False | |
var_type = node.type | |
var_mode = node.mode | |
if not var_type: | |
try: | |
value_eval = pyneast.literal_eval(node.value) | |
except ValueError: | |
if not isinstance(node.value, pyneast.Call): | |
var_type = node.type | |
elif ( | |
isinstance(node.value.func, pyneast.Specialize) | |
and isinstance(node.value.func.value, pyneast.Attribute) | |
and node.value.func.value.attr == "new" | |
): | |
var_type = pyneast.Specialize( | |
value=node.value.func.value.value, | |
args=node.value.func.args, | |
) | |
elif isinstance(node.value.func, pyneast.Attribute) and node.value.func.attr.startswith("new"): | |
var_type = node.value.func.value | |
if node.value.func.attr.startswith("new_"): | |
new, id = node.value.func.attr.split("_") | |
slice = pyneast.Name(id=id, ctx=pyneast.Load()) | |
var_type = pyneast.Specialize( | |
value=var_type, | |
args=slice, | |
) | |
elif isinstance(node.value.func, pyneast.Name) and node.value.func.id in self._casting_types: | |
var_type = node.value.func | |
else: | |
value_eval_type = type(value_eval) | |
if value_eval_type in self._python_type_to_pine_type_mapping: | |
var_type = pyneast.Name( | |
id=self._python_type_to_pine_type_mapping[value_eval_type], ctx=pyneast.Load() | |
) | |
var_key = str(dataclasses.replace(node.target, ctx=pyneast.Load())) | |
self._variable_types[var_key] = (var_type, var_mode) | |
target = self.visit(node.target) | |
value = self.visit(node.value) | |
if not node.type and not node.mode: | |
assign = pyast.Assign( | |
targets=[target], | |
value=value, | |
) | |
if parsing_input: | |
self._inputs.append(assign) | |
return | |
else: | |
return assign | |
else: | |
if not var_type: | |
msg = f"failed to infer peristent variables's type from {node}" | |
raise RuntimeError(msg) | |
with self.type_parser(): | |
annotation = self.visit(var_type) | |
if node.mode: | |
annotation = pyast.Subscript( | |
value=pyast.Name(id="Persistent", ctx=pyast.Load()), | |
slice=annotation, | |
ctx=pyast.Load(), | |
) | |
assign = pyast.AnnAssign( | |
target=target, | |
annotation=annotation, | |
value=value, | |
simple=1, | |
) | |
if parsing_input: | |
self._inputs.append(assign) | |
return | |
else: | |
return assign | |
def visit_ReAssign(self, node: pyneast.ReAssign) -> pyast.Assign: | |
target = self.visit(node.target) | |
value = self.visit(node.value) | |
return pyast.Assign( | |
targets=[target], | |
value=value, | |
) | |
def visit_AugAssign(self, node: pyneast.AugAssign) -> pyast.AugAssign: | |
target = self.visit(node.target) | |
op = self.visit(node.op) | |
value = self.visit(node.value) | |
return pyast.AugAssign( | |
target=target, | |
op=op, | |
value=value, | |
) | |
def visit_Import(self, node: pyneast.Import) -> pyast.Import: | |
name = f"lib.{node.namespace}.{node.name}.v{node.version}" | |
asname = node.alias | |
return pyast.Import( | |
names=[ | |
pyast.alias( | |
name=name, | |
asname=asname, | |
) | |
] | |
) | |
def visit_Expr(self, node: pyneast.Expr) -> pyast.Expr: | |
value = self.visit(node.value) | |
if isinstance(value, pyast.stmt): | |
return value | |
else: | |
return pyast.Expr(value=value) | |
def visit_Break(self, node: pyneast.Break) -> pyast.Break: | |
return pyast.Break() | |
def visit_Continue(self, node: pyneast.Continue) -> pyast.Continue: | |
return pyast.Continue() | |
def visit_BoolOp(self, node: pyneast.BoolOp) -> pyast.BoolOp: | |
op = self.visit(node.op) | |
values = [self.visit(value) for value in node.values] | |
return pyast.BoolOp( | |
op=op, | |
values=values, | |
) | |
def visit_BinOp(self, node: pyneast.BinOp) -> pyast.BinOp: | |
left = self.visit(node.left) | |
op = self.visit(node.op) | |
right = self.visit(node.right) | |
return pyast.BinOp( | |
left=left, | |
op=op, | |
right=right, | |
) | |
def visit_UnaryOp(self, node: pyneast.UnaryOp) -> pyast.UnaryOp: | |
op = self.visit(node.op) | |
operand = self.visit(node.operand) | |
return pyast.UnaryOp( | |
op=op, | |
operand=operand, | |
) | |
def visit_Conditional(self, node: pyneast.Conditional) -> pyast.IfExp: | |
test = self.visit(node.test) | |
body = self.visit(node.body) | |
orelse = self.visit(node.orelse) | |
return pyast.IfExp( | |
test=test, | |
body=body, | |
orelse=orelse, | |
) | |
def visit_Compare(self, node: pyneast.Compare) -> pyast.Compare: | |
left = self.visit(node.left) | |
ops = [self.visit(op) for op in node.ops] | |
comparators = [self.visit(comp) for comp in node.comparators] | |
return pyast.Compare( | |
left=left, | |
ops=ops, | |
comparators=comparators, | |
) | |
def visit_Call(self, node: pyneast.Call) -> pyast.Call: | |
if isinstance(node.func, pyneast.Name) and node.func.id in self._casting_types and len(node.args) == 1: | |
if isinstance(node.args[0].value, pyneast.Name) and node.args[0].value.id == "na": | |
typ_id = self._pine_to_pyne_type_mapping[node.func.id] | |
if typ_id in self._namespace_mapping: | |
typ_id = self._namespace_mapping[typ_id] | |
func = pyast.Name(id="NA", ctx=pyast.Load()) | |
args = [ | |
pyast.Name( | |
id=typ_id, | |
ctx=pyast.Load(), | |
) | |
] | |
call = pyast.Call( | |
func=func, | |
args=args, | |
keywords=[], | |
) | |
else: | |
func = pyast.Name(id=f"cast_{node.func.id}", ctx=pyast.Load()) | |
args_and_keywords = [self.visit(arg) for arg in node.args] | |
args = [arg for arg in args_and_keywords if isinstance(arg, pyast.expr)] | |
keywords = [keyword for keyword in args_and_keywords if isinstance(keyword, pyast.keyword)] | |
call = pyast.Call( | |
func=func, | |
args=args, | |
keywords=keywords, | |
) | |
else: | |
func = self.visit(node.func) | |
args_and_keywords = [self.visit(arg) for arg in node.args] | |
args = [arg for arg in args_and_keywords if isinstance(arg, pyast.expr)] | |
keywords = [keyword for keyword in args_and_keywords if isinstance(keyword, pyast.keyword)] | |
call = pyast.Call( | |
func=func, | |
args=args, | |
keywords=keywords, | |
) | |
if isinstance(node.func, pyneast.Attribute) and str(node.func.value) in self._variable_types: | |
typ, mod = self._variable_types[str(node.func.value)] | |
typ_name = typ | |
while not isinstance(typ_name, pyneast.Name): | |
if isinstance(typ_name, pyneast.Specialize): | |
typ_name = typ_name.value | |
elif isinstance(typ_name, pyneast.Attribute): | |
typ_name = typ_name.value | |
else: | |
msg = "unexpected type" | |
raise RuntimeError(msg) | |
call_func = typing.cast(pyast.Attribute, call.func) | |
instance = call_func.value | |
method = call_func.attr | |
namespace = pyast.Name(id=typ_name.id, ctx=pyast.Load()) | |
call.func = pyast.Attribute( | |
value=namespace, | |
attr=method, | |
) | |
call.args.insert(0, instance) | |
return call | |
def visit_Constant(self, node: pyneast.Constant) -> pyast.Constant: | |
value = node.value | |
kind = None | |
constant = pyast.Constant( | |
value=value, | |
kind=kind, | |
) | |
if node.kind == "#": | |
func = pyast.Attribute( | |
value=pyast.Name( | |
id="color", | |
ctx=pyast.Load(), | |
), | |
attr="new", | |
ctx=pyast.Load(), | |
) | |
args = [constant] | |
constant = pyast.Call( | |
func=func, | |
args=args, | |
keywords=[], | |
) | |
return constant | |
def visit_Attribute(self, node: pyneast.Attribute) -> pyast.Attribute | pyast.Name: | |
if isinstance(node.value, pyneast.Name) and node.value.id == "chart" and node.attr == "point": | |
id = "ChartPoint" | |
ctx = self.visit(node.ctx) | |
return pyast.Name( | |
id=id, | |
ctx=ctx, | |
) | |
else: | |
value = self.visit(node.value) | |
attr = node.attr | |
ctx = self.visit(node.ctx) | |
return pyast.Attribute( | |
value=value, | |
attr=attr, | |
ctx=ctx, | |
) | |
def visit_Subscript(self, node: pyneast.Subscript) -> pyast.Subscript: | |
if not node.slice: | |
value = pyast.Name( | |
id=self._pine_to_pyne_type_mapping["array"], | |
ctx=pyast.Load(), | |
) | |
slice = self.visit(node.value) | |
ctx = self.visit(node.ctx) | |
return pyast.Subscript( | |
value=value, | |
slice=slice, | |
ctx=ctx, | |
) | |
else: | |
value = self.visit(node.value) | |
slice = self.visit(node.slice) | |
ctx = self.visit(node.ctx) | |
if not hasattr(value, "ctx"): | |
return pyast.Call( | |
func=pyast.Name( | |
id="inline_series", | |
ctx=pyast.Load(), | |
), | |
args=[value, slice], | |
keywords=[], | |
) | |
else: | |
return pyast.Subscript( | |
value=value, | |
slice=slice, | |
ctx=ctx, | |
) | |
def visit_Name(self, node: pyneast.Name) -> pyast.Name: | |
if self._parsing_type and node.id in self._pine_to_pyne_type_mapping: | |
id = self._pine_to_pyne_type_mapping[node.id] | |
ctx = self.visit(node.ctx) | |
return pyast.Name( | |
id=id, | |
ctx=ctx, | |
) | |
else: | |
id = node.id | |
if id in self._namespace_mapping: | |
id = self._namespace_mapping[id] | |
ctx = self.visit(node.ctx) | |
return pyast.Name( | |
id=id, | |
ctx=ctx, | |
) | |
def visit_Tuple(self, node: pyneast.Tuple) -> pyast.Tuple: | |
elts = [self.visit(elt) for elt in node.elts] | |
ctx = self.visit(node.ctx) | |
return pyast.Tuple( | |
elts=elts, | |
ctx=ctx, | |
) | |
def visit_ForTo(self, node: pyneast.ForTo) -> pyast.For: | |
with self.local_block(): | |
target = self.visit(node.target) | |
body = [self.visit(stmt) for stmt in node.body] | |
start = self.visit(node.start) | |
end = self.visit(node.end) | |
step = self.visit(node.step) if node.step else None | |
args = [start, end] | |
if step: | |
args.append(step) | |
iter = pyast.Call( | |
func=pyast.Name(id="pine_range", ctx=pyast.Load()), | |
args=args, | |
keywords=[], | |
) | |
orelse = [] | |
return pyast.For( | |
target=target, | |
iter=iter, | |
body=body, | |
orelse=orelse, | |
) | |
def visit_ForIn(self, node: pyneast.ForIn) -> pyast.For: | |
with self.local_block(): | |
target = self.visit(node.target) | |
body = [self.visit(stmt) for stmt in node.body] | |
iter = self.visit(node.iter) | |
orelse = [] | |
return pyast.For( | |
target=target, | |
iter=iter, | |
body=body, | |
orelse=orelse, | |
) | |
def visit_While(self, node: pyneast.While) -> pyast.While: | |
with self.local_block(): | |
test = self.visit(node.test) | |
body = [self.visit(stmt) for stmt in node.body] | |
orelse = [] | |
return pyast.While( | |
test=test, | |
body=body, | |
orelse=orelse, | |
) | |
def visit_If(self, node: pyneast.If) -> pyast.If: | |
with self.local_block(): | |
test = self.visit(node.test) | |
body = [self.visit(stmt) for stmt in node.body] | |
orelse = [self.visit(stmt) for stmt in node.orelse] | |
return pyast.If( | |
test=test, | |
body=body, | |
orelse=orelse, | |
) | |
def visit_Switch(self, node: pyneast.Switch) -> pyast.Match | pyast.If: | |
with self.local_block(): | |
if node.subject: | |
subject = self.visit(node.subject) | |
cases = [self.visit(case) for case in node.cases] | |
return pyast.Match( | |
subject=subject, | |
cases=cases, | |
) | |
else: | |
case = node.cases[-1] | |
case = typing.cast(pyneast.Case, case) | |
if case.pattern: | |
test = self.visit(case.pattern) | |
body = self.visit(case.body) | |
if_chain = pyast.If( | |
test=test, | |
body=body, | |
orelse=[], | |
) | |
else: | |
if_chain = self.visit(case.body) | |
for case in reversed(node.cases[:-1]): | |
case = typing.cast(pyneast.Case, case) | |
test = self.visit(case.pattern) | |
body = self.visit(case.body) | |
if_chain = pyast.If( | |
test=test, | |
body=body, | |
orelse=[if_chain], | |
) | |
if not isinstance(if_chain, pyast.If): | |
if_chain = pyast.If( | |
test=pyast.Constant( | |
value=True, | |
kind=None, | |
), | |
body=if_chain, | |
orelse=[], | |
) | |
return if_chain | |
def visit_Qualify(self, node: pyneast.Qualify) -> pyast.Subscript: | |
return pyast.Subscript( | |
value=pyast.Name( | |
id=node.qualifier.__class__.__name__, | |
ctx=pyast.Load(), | |
), | |
slice=self.visit(node.value), | |
ctx=pyast.Load(), | |
) | |
def visit_Specialize(self, node: pyneast.Specialize) -> pyast.expr: | |
if not isinstance(node.value, pyneast.Attribute) or not ( | |
isinstance(node.value.value, pyneast.Name) | |
and node.value.value.id in {"array", "map"} | |
and node.value.attr == "new" | |
): | |
value = self.visit(node.value) | |
slice = self.visit(node.args) | |
return pyast.Subscript( | |
value=value, | |
slice=slice, | |
ctx=pyast.Load(), | |
) | |
if node.value.value.id == "map": | |
return self.visit(node.value) | |
if not isinstance(node.args, pyneast.Name): | |
value = self.visit(node.value) | |
slice = self.visit(node.args) | |
return pyast.Subscript( | |
value=value, | |
slice=slice, | |
ctx=pyast.Load(), | |
) | |
suffix = node.args.id | |
attr = self.visit(node.value) | |
attr = typing.cast(pyast.Attribute, attr) | |
attr.attr = f"{attr.attr}_{suffix}" | |
return attr | |
def visit_Var(self, node: pyneast.Var): | |
msg = f"unsupported node {node}" | |
raise ValueError(msg) | |
def visit_VarIp(self, node: pyneast.VarIp): | |
msg = f"unsupported node {node}" | |
raise ValueError(msg) | |
def visit_Const(self, node: pyneast.Const): | |
msg = f"unsupported node {node}" | |
raise ValueError(msg) | |
def visit_Input(self, node: pyneast.Input): | |
msg = f"unsupported node {node}" | |
raise ValueError(msg) | |
def visit_Simple(self, node: pyneast.Simple): | |
msg = f"unsupported node {node}" | |
raise ValueError(msg) | |
def visit_Series(self, node: pyneast.Series): | |
msg = f"unsupported node {node}" | |
raise ValueError(msg) | |
def visit_Load(self, node: pyneast.Load) -> pyast.Load: | |
return pyast.Load() | |
def visit_Store(self, node: pyneast.Store) -> pyast.Store: | |
return pyast.Store() | |
def visit_And(self, node: pyneast.And) -> pyast.And: | |
return pyast.And() | |
def visit_Or(self, node: pyneast.Or) -> pyast.Or: | |
return pyast.Or() | |
def visit_Add(self, node: pyneast.Add) -> pyast.Add: | |
return pyast.Add() | |
def visit_Sub(self, node: pyneast.Sub) -> pyast.Sub: | |
return pyast.Sub() | |
def visit_Mult(self, node: pyneast.Mult) -> pyast.Mult: | |
return pyast.Mult() | |
def visit_Div(self, node: pyneast.Div) -> pyast.Div: | |
return pyast.Div() | |
def visit_Mod(self, node: pyneast.Mod) -> pyast.Mod: | |
return pyast.Mod() | |
def visit_Not(self, node: pyneast.Not) -> pyast.Not: | |
return pyast.Not() | |
def visit_UAdd(self, node: pyneast.UAdd) -> pyast.UAdd: | |
return pyast.UAdd() | |
def visit_USub(self, node: pyneast.USub) -> pyast.USub: | |
return pyast.USub() | |
def visit_Eq(self, node: pyneast.Eq) -> pyast.Eq: | |
return pyast.Eq() | |
def visit_NotEq(self, node: pyneast.NotEq) -> pyast.NotEq: | |
return pyast.NotEq() | |
def visit_Lt(self, node: pyneast.Lt) -> pyast.Lt: | |
return pyast.Lt() | |
def visit_LtE(self, node: pyneast.LtE) -> pyast.LtE: | |
return pyast.LtE() | |
def visit_Gt(self, node: pyneast.Gt) -> pyast.Gt: | |
return pyast.Gt() | |
def visit_GtE(self, node: pyneast.GtE) -> pyast.GtE: | |
return pyast.GtE() | |
def visit_Param(self, node: pyneast.Param) -> tuple[pyast.arg, pyast.expr | None]: | |
arg = node.name | |
with self.type_parser(): | |
annotation = self.visit(node.type) if node.type else None | |
arg = pyast.arg(arg=arg, annotation=annotation) | |
default = self.visit(node.default) if node.default else None | |
return arg, default | |
def visit_Arg(self, node: pyneast.Arg) -> pyast.expr | pyast.keyword: | |
arg = node.name | |
value = self.visit(node.value) | |
return ( | |
pyast.keyword( | |
arg=arg, | |
value=value, | |
) | |
if arg | |
else value | |
) | |
def visit_Case(self, node: pyneast.Case) -> pyast.match_case: | |
if not node.pattern: | |
pattern = pyast.MatchAs() | |
elif isinstance(node.pattern, pyneast.Constant): | |
value = node.pattern.value | |
if isinstance(value, bool): | |
pattern = pyast.MatchSingleton(value=value) | |
else: | |
value = pyast.Constant(value=value) | |
pattern = pyast.MatchValue(value=value) | |
else: | |
value = self.visit(node.pattern) | |
pattern = pyast.MatchValue(value=value) | |
body = [self.visit(stmt) for stmt in node.body] | |
return pyast.match_case( | |
pattern=pattern, | |
body=body, | |
) | |
def visit_Comment(self, node: pyneast.Comment): | |
msg = f"unsupported node {node}" | |
raise ValueError(msg) | |
def compile(content: str) -> str: | |
transformer = PinescriptToPynecoreScriptTransformer() | |
pynescript_ast = pyneast.parse(content) | |
pynecore_ast = transformer.visit_Script(pynescript_ast) | |
pynecore_ast = pyast.fix_missing_locations(pynecore_ast) | |
code = pyast.unparse(pynecore_ast) | |
return code | |
@click.command | |
@click.argument("filename", type=click.Path()) | |
def main(filename): | |
with open(filename, encoding="utf-8") as f: | |
content = f.read() | |
click.echo(compile(content)) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment