File size: 7,313 Bytes
520bbd9
 
 
 
 
 
e6d77be
520bbd9
 
 
 
 
 
e6d77be
520bbd9
e6d77be
520bbd9
e6d77be
520bbd9
e6d77be
 
520bbd9
 
 
 
e6d77be
520bbd9
e6d77be
 
520bbd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6d77be
520bbd9
 
e6d77be
520bbd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6d77be
 
520bbd9
e6d77be
520bbd9
 
 
e6d77be
520bbd9
 
 
 
 
 
 
e6d77be
520bbd9
 
 
e6d77be
520bbd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6d77be
520bbd9
 
 
 
 
e6d77be
520bbd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6d77be
520bbd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6d77be
520bbd9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e6d77be
520bbd9
e6d77be
520bbd9
 
e6d77be
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
import esprima
from typing import Any, Callable, Dict, Optional
import types
import ast
import inspect
from smolagents.tools import Tool

class JavaScriptMethodDispatcher(Tool):
    name = "javascript_as_python_caller"
    description = "turns java script code blocks into callable python functions"
    inputs = {'jsContent':{'type':'string', 'description':'content of a regular java script code block of a function definition'}}
    output_type = "callable"
    
    def __init__(self):
        self._method_cache: Dict[str, Callable] = {}
        
    def register_js_method(self, js_code: str, method_name: Optional[str] = None) -> Callable:
        """
        Convert JavaScript code to a Python callable and register it in the dispatcher.
        
        Args:
            js_code (str): JavaScript code containing the function
            method_name (str, optional): Name to register the method under. 
                                       If None, tries to extract from the JS code.
        
        Returns:
            Callable: Python function that can be called directly
        """
        try:
            # Parse JavaScript code
            parsed = esprima.parseScript(js_code)
            
            # Find the function declaration
            func_decl = None
            for node in parsed.body:
                if node.type == 'FunctionDeclaration':
                    func_decl = node
                    break
            
            if not func_decl:
                raise ValueError("No function declaration found in JavaScript code")
            
            # Get the method name
            if method_name is None:
                method_name = func_decl.id.name
            
            # Convert JavaScript parameters to Python
            params = [param.name for param in func_decl.params]
            
            # Convert the function body
            body_lines = self._convert_js_body(func_decl.body)
            
            # Create the Python function source
            py_source = f"def {method_name}({', '.join(params)}):\n"
            py_source += "\n".join(f"    {line}" for line in body_lines)
            
            # Create function namespace
            namespace = {
                'print': print,  # Common built-ins
                'str': str,
                'int': int,
                'float': float,
                'bool': bool,
                'list': list,
                'dict': dict,
                'None': None,
                'True': True,
                'False': False
            }
            
            # Compile and execute the Python code
            compiled_code = compile(py_source, '<string>', 'exec')
            exec(compiled_code, namespace)
            
            # Get the compiled function
            py_func = namespace[method_name]
            
            # Store in cache
            self._method_cache[method_name] = py_func
            
            return py_func
            
        except Exception as e:
            raise ValueError(f"Failed to convert JavaScript method: {str(e)}")
    
    def get_method(self, method_name: str) -> Optional[Callable]:
        """
        Get a registered method by name.
        
        Args:
            method_name (str): Name of the registered method
            
        Returns:
            Optional[Callable]: The registered Python function, or None if not found
        """
        return self._method_cache.get(method_name)
    
    def call_method(self, method_name: str, *args, **kwargs) -> Any:
        """
        Call a registered method by name with given arguments.
        
        Args:
            method_name (str): Name of the registered method
            *args: Positional arguments to pass to the method
            **kwargs: Keyword arguments to pass to the method
            
        Returns:
            Any: Result of the method call
            
        Raises:
            ValueError: If method is not found or call fails
        """
        method = self.get_method(method_name)
        if method is None:
            raise ValueError(f"Method '{method_name}' not found")
            
        try:
            return method(*args, **kwargs)
        except Exception as e:
            raise ValueError(f"Failed to call method '{method_name}': {str(e)}")
    
    def _convert_js_body(self, body_node: Any) -> list[str]:
        """Convert JavaScript function body to Python code lines."""
        # This is a simplified conversion - you'd want to expand this
        # based on your specific needs
        lines = []
        
        # Handle different types of statements
        if body_node.type == 'BlockStatement':
            for statement in body_node.body:
                if statement.type == 'ReturnStatement':
                    return_value = self._convert_js_expression(statement.argument)
                    lines.append(f"return {return_value}")
                elif statement.type == 'ExpressionStatement':
                    expr = self._convert_js_expression(statement.expression)
                    lines.append(expr)
                elif statement.type == 'IfStatement':
                    condition = self._convert_js_expression(statement.test)
                    lines.append(f"if {condition}:")
                    then_lines = self._convert_js_body(statement.consequent)
                    lines.extend(f"    {line}" for line in then_lines)
                    if statement.alternate:
                        lines.append("else:")
                        else_lines = self._convert_js_body(statement.alternate)
                        lines.extend(f"    {line}" for line in else_lines)
        
        return lines
    
    def _convert_js_expression(self, node: Any) -> str:
        """Convert JavaScript expression to Python code string."""
        if node.type == 'BinaryExpression':
            left = self._convert_js_expression(node.left)
            right = self._convert_js_expression(node.right)
            op = node.operator
            # Convert JavaScript operators to Python
            op_map = {
                '===': '==',
                '!==': '!=',
                '&&': 'and',
                '||': 'or'
            }
            op = op_map.get(op, op)
            return f"({left} {op} {right})"
        elif node.type == 'Literal':
            if isinstance(node.value, str):
                return f"'{node.value}'"
            return str(node.value)
        elif node.type == 'Identifier':
            # Handle JavaScript built-ins
            js_to_py = {
                'undefined': 'None',
                'null': 'None',
                'true': 'True',
                'false': 'False'
            }
            return js_to_py.get(node.name, node.name)
        elif node.type == 'CallExpression':
            func = self._convert_js_expression(node.callee)
            args = [self._convert_js_expression(arg) for arg in node.arguments]
            # Handle special cases
            if func == 'console.log':
                func = 'print'
            return f"{func}({', '.join(args)})"
        
        raise ValueError(f"Unsupported expression type: {node.type}")

    def forward(self, jsContent: str) -> callable:
        return self.register_js_method(jsContent)