Skip to content

How does Declo work?

The goal of declo is to manipulate Python objects with JavaScript syntax. We can do using three strategies, each of them have their own trade-offs:

1. Source to Source

Convert js src => py src. This can be kinda hard because of regex parsing etc, it will involve creating a custom grammar parser. This can be good at the start, but can quickly get complex, as and when we add more features. A simple way is:

def arrow_func(js_code = "x => x + 1"):
    parts = js_code.replace(" ", "").split("=>")

    if len(parts) != 2:
        raise ValueError("Invalid arrow_func expression format")

    param, body = parts
    py_code = f"lambda {param}: {body}"
    code_obj = compile(py_code, "<string>", "eval")

    return eval(code_obj)

2. AST to AST

Convert js ast => py ast. It is easier to work with structured data. Coding the rules for the equivalence is relatively easier. We provide this api in declo.ast.ast_js2py. This can get a little verbose too:

def ast_js2py(js_node: Union[Program, ExpressionStatement, ArrowFunctionExpression, BinaryExpression, Identifier, Literal]) -> ast.AST:
    if isinstance(js_node, Program):
        return ast.Module(body=[ast_js2py(stmt) for stmt in js_node.body], type_ignores=[])
    elif isinstance(js_node, ExpressionStatement):
        return ast.Expr(value=ast_js2py(js_node.expression))
    elif isinstance(js_node, ArrowFunctionExpression):
        return ast.Lambda(
            args=ast.arguments(
                posonlyargs=[],
                args=[ast.arg(arg=param.name, annotation=None, type_comment=None) for param in js_node.params],
                vararg=None,
                kwonlyargs=[], kw_defaults=[], kwarg=None, defaults=[]
            ),
            body=ast_js2py(js_node.body)
        )
    elif isinstance(js_node, BinaryExpression):
        return ast.BinOp(
            left=ast_js2py(js_node.left),
            op=ast.Add() if js_node.operator == "+" else None,  # Extend this for other operators
            right=ast_js2py(js_node.right)
        )
    elif isinstance(js_node, Identifier):
        return ast.Name(id=js_node.name, ctx=ast.Load())
    elif isinstance(js_node, Literal):
        return ast.Constant(value=js_node.value, kind=None)
    else:
        raise ValueError(f"Unsupported JS AST node type: {js_node.__class__.__name__}")

3. SRC to AST

Convert js src => py ast. End to End convertion would be the best, but is the most complex which requires a mix of S1 and S2. We could do this by patching the python grammar Python.asdl to support the new syntax. See: ... We don't have to recompile python to run this code, we just reuse the python pgen parser with our new JsPython.asdl to directly create the python objects.