quantalogic 0.61.2__py3-none-any.whl → 0.80__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantalogic/agent.py +0 -1
- quantalogic/codeact/TODO.md +14 -0
- quantalogic/codeact/agent.py +400 -421
- quantalogic/codeact/cli.py +42 -224
- quantalogic/codeact/cli_commands/__init__.py +0 -0
- quantalogic/codeact/cli_commands/create_toolbox.py +45 -0
- quantalogic/codeact/cli_commands/install_toolbox.py +20 -0
- quantalogic/codeact/cli_commands/list_executor.py +15 -0
- quantalogic/codeact/cli_commands/list_reasoners.py +15 -0
- quantalogic/codeact/cli_commands/list_toolboxes.py +47 -0
- quantalogic/codeact/cli_commands/task.py +215 -0
- quantalogic/codeact/cli_commands/tool_info.py +24 -0
- quantalogic/codeact/cli_commands/uninstall_toolbox.py +43 -0
- quantalogic/codeact/config.yaml +21 -0
- quantalogic/codeact/constants.py +1 -1
- quantalogic/codeact/events.py +12 -5
- quantalogic/codeact/examples/README.md +342 -0
- quantalogic/codeact/examples/agent_sample.yaml +29 -0
- quantalogic/codeact/executor.py +186 -0
- quantalogic/codeact/history_manager.py +94 -0
- quantalogic/codeact/llm_util.py +3 -22
- quantalogic/codeact/plugin_manager.py +92 -0
- quantalogic/codeact/prompts/generate_action.j2 +65 -14
- quantalogic/codeact/prompts/generate_program.j2 +32 -19
- quantalogic/codeact/react_agent.py +318 -0
- quantalogic/codeact/reasoner.py +185 -0
- quantalogic/codeact/templates/toolbox/README.md.j2 +10 -0
- quantalogic/codeact/templates/toolbox/pyproject.toml.j2 +16 -0
- quantalogic/codeact/templates/toolbox/tools.py.j2 +6 -0
- quantalogic/codeact/templates.py +7 -0
- quantalogic/codeact/tools_manager.py +242 -119
- quantalogic/codeact/utils.py +16 -89
- quantalogic/codeact/xml_utils.py +126 -0
- quantalogic/flow/flow.py +151 -41
- quantalogic/flow/flow_extractor.py +61 -1
- quantalogic/flow/flow_generator.py +34 -6
- quantalogic/flow/flow_manager.py +64 -25
- quantalogic/flow/flow_manager_schema.py +32 -0
- quantalogic/tools/action_gen.py +1 -1
- quantalogic/tools/action_gen_safe.py +340 -0
- quantalogic/tools/tool.py +531 -109
- quantalogic/tools/write_file_tool.py +7 -8
- {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/METADATA +3 -2
- {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/RECORD +47 -42
- {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/WHEEL +1 -1
- quantalogic-0.80.dist-info/entry_points.txt +3 -0
- quantalogic/python_interpreter/__init__.py +0 -23
- quantalogic/python_interpreter/assignment_visitors.py +0 -63
- quantalogic/python_interpreter/base_visitors.py +0 -20
- quantalogic/python_interpreter/class_visitors.py +0 -22
- quantalogic/python_interpreter/comprehension_visitors.py +0 -172
- quantalogic/python_interpreter/context_visitors.py +0 -59
- quantalogic/python_interpreter/control_flow_visitors.py +0 -88
- quantalogic/python_interpreter/exception_visitors.py +0 -109
- quantalogic/python_interpreter/exceptions.py +0 -39
- quantalogic/python_interpreter/execution.py +0 -202
- quantalogic/python_interpreter/function_utils.py +0 -386
- quantalogic/python_interpreter/function_visitors.py +0 -209
- quantalogic/python_interpreter/import_visitors.py +0 -28
- quantalogic/python_interpreter/interpreter_core.py +0 -358
- quantalogic/python_interpreter/literal_visitors.py +0 -74
- quantalogic/python_interpreter/misc_visitors.py +0 -148
- quantalogic/python_interpreter/operator_visitors.py +0 -108
- quantalogic/python_interpreter/scope.py +0 -10
- quantalogic/python_interpreter/visit_handlers.py +0 -110
- quantalogic-0.61.2.dist-info/entry_points.txt +0 -6
- {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/LICENSE +0 -0
@@ -1,358 +0,0 @@
|
|
1
|
-
import ast
|
2
|
-
import asyncio
|
3
|
-
import builtins
|
4
|
-
import logging
|
5
|
-
import threading
|
6
|
-
from typing import Any, Callable, Dict, List, Optional, Tuple
|
7
|
-
|
8
|
-
import psutil # New dependency for resource monitoring
|
9
|
-
|
10
|
-
from .exceptions import BreakException, ContinueException, ReturnException, WrappedException
|
11
|
-
from .scope import Scope
|
12
|
-
|
13
|
-
|
14
|
-
class ASTInterpreter:
|
15
|
-
def __init__(
|
16
|
-
self,
|
17
|
-
allowed_modules: List[str],
|
18
|
-
env_stack: Optional[List[Dict[str, Any]]] = None,
|
19
|
-
source: Optional[str] = None,
|
20
|
-
restrict_os: bool = True,
|
21
|
-
namespace: Optional[Dict[str, Any]] = None,
|
22
|
-
max_recursion_depth: int = 1000, # Now configurable
|
23
|
-
max_operations: int = 10000000, # Added operation limit
|
24
|
-
max_memory_mb: int = 1024, # Added memory limit (1GB default)
|
25
|
-
sync_mode: bool = False, # Added for sync execution optimization
|
26
|
-
safe_builtins: Optional[Dict[str, Any]] = None # New customizable safe_builtins
|
27
|
-
) -> None:
|
28
|
-
self.allowed_modules: List[str] = allowed_modules
|
29
|
-
self.modules: Dict[str, Any] = {mod: __import__(mod) for mod in allowed_modules}
|
30
|
-
self.restrict_os: bool = restrict_os
|
31
|
-
self.sync_mode: bool = sync_mode
|
32
|
-
self.max_operations: int = max_operations
|
33
|
-
self.operations_count: int = 0
|
34
|
-
self.max_memory_mb: int = max_memory_mb
|
35
|
-
self.process = psutil.Process() # For memory monitoring
|
36
|
-
self.type_hints: Dict[str, Any] = {} # Added for type aliases
|
37
|
-
self.special_methods: Dict[str, Callable] = {} # Added for special method dispatch
|
38
|
-
|
39
|
-
# Default safe_builtins if none provided
|
40
|
-
default_safe_builtins: Dict[str, Any] = {
|
41
|
-
'None': None,
|
42
|
-
'False': False,
|
43
|
-
'True': True,
|
44
|
-
'int': int,
|
45
|
-
'float': float,
|
46
|
-
'str': str,
|
47
|
-
'list': list,
|
48
|
-
'dict': dict,
|
49
|
-
'set': set,
|
50
|
-
'tuple': tuple,
|
51
|
-
'bool': bool,
|
52
|
-
'object': object,
|
53
|
-
'range': range,
|
54
|
-
'iter': iter, # Added for test_iterators
|
55
|
-
'next': next, # Added to fix test_iterators
|
56
|
-
'sorted': sorted, # Added for test_dictionary_methods
|
57
|
-
'frozenset': frozenset, # Added for test_frozenset
|
58
|
-
'super': super, # Added for test_class_inheritance
|
59
|
-
'len': len, # Added for test_empty_structures
|
60
|
-
'property': property, # Added for test_property_decorator
|
61
|
-
'staticmethod': staticmethod, # Added for test_static_method
|
62
|
-
'classmethod': classmethod, # Added for test_class_method
|
63
|
-
'__name__': '__main__', # Simulate script execution context
|
64
|
-
}
|
65
|
-
|
66
|
-
# Use provided safe_builtins or default
|
67
|
-
self.safe_builtins = safe_builtins if safe_builtins is not None else default_safe_builtins
|
68
|
-
|
69
|
-
if env_stack is None:
|
70
|
-
self.env_stack: List[Dict[str, Any]] = [{}]
|
71
|
-
self.env_stack[0].update(self.modules)
|
72
|
-
|
73
|
-
# Define explicitly allowed built-in functions
|
74
|
-
allowed_builtins = {
|
75
|
-
"enumerate": enumerate,
|
76
|
-
"zip": zip,
|
77
|
-
"sum": sum,
|
78
|
-
"min": min,
|
79
|
-
"max": max,
|
80
|
-
"abs": abs,
|
81
|
-
"round": round,
|
82
|
-
"str": str,
|
83
|
-
"repr": repr,
|
84
|
-
"id": id,
|
85
|
-
"Exception": Exception,
|
86
|
-
"ZeroDivisionError": ZeroDivisionError,
|
87
|
-
"ValueError": ValueError,
|
88
|
-
"TypeError": TypeError,
|
89
|
-
"print": print,
|
90
|
-
"getattr": self.safe_getattr,
|
91
|
-
"vars": lambda obj=None: vars(obj) if obj else dict(self.env_stack[-1]),
|
92
|
-
}
|
93
|
-
|
94
|
-
# Update safe_builtins with allowed functions and restricted import
|
95
|
-
self.safe_builtins.update(allowed_builtins)
|
96
|
-
self.safe_builtins["__import__"] = self.safe_import
|
97
|
-
|
98
|
-
# Set the restricted builtins in the environment
|
99
|
-
self.env_stack[0]["__builtins__"] = self.safe_builtins
|
100
|
-
self.env_stack[0].update(self.safe_builtins)
|
101
|
-
self.env_stack[0]["logger"] = logging.getLogger(__name__)
|
102
|
-
|
103
|
-
# Add the provided namespace (e.g., tools) to the environment
|
104
|
-
if namespace is not None:
|
105
|
-
self.env_stack[0].update(namespace)
|
106
|
-
else:
|
107
|
-
self.env_stack = env_stack
|
108
|
-
if "__builtins__" not in self.env_stack[0]:
|
109
|
-
# Define explicitly allowed built-in functions
|
110
|
-
allowed_builtins = {
|
111
|
-
"enumerate": enumerate,
|
112
|
-
"zip": zip,
|
113
|
-
"sum": sum,
|
114
|
-
"min": min,
|
115
|
-
"max": max,
|
116
|
-
"abs": abs,
|
117
|
-
"round": round,
|
118
|
-
"str": str,
|
119
|
-
"repr": repr,
|
120
|
-
"id": id,
|
121
|
-
"Exception": Exception,
|
122
|
-
"ZeroDivisionError": ZeroDivisionError,
|
123
|
-
"ValueError": ValueError,
|
124
|
-
"TypeError": TypeError,
|
125
|
-
"print": print,
|
126
|
-
"getattr": self.safe_getattr,
|
127
|
-
"vars": lambda obj=None: vars(obj) if obj else dict(self.env_stack[-1]),
|
128
|
-
}
|
129
|
-
|
130
|
-
# Update safe_builtins with allowed functions and restricted import
|
131
|
-
self.safe_builtins.update(allowed_builtins)
|
132
|
-
self.safe_builtins["__import__"] = self.safe_import
|
133
|
-
|
134
|
-
# Set the restricted builtins in the environment
|
135
|
-
self.env_stack[0]["__builtins__"] = self.safe_builtins
|
136
|
-
self.env_stack[0].update(self.safe_builtins)
|
137
|
-
self.env_stack[0]["logger"] = logging.getLogger(__name__)
|
138
|
-
|
139
|
-
if namespace is not None:
|
140
|
-
self.env_stack[0].update(namespace)
|
141
|
-
|
142
|
-
if self.restrict_os:
|
143
|
-
os_related_modules = {"os", "sys", "subprocess", "shutil", "platform"}
|
144
|
-
for mod in os_related_modules:
|
145
|
-
if mod in self.modules:
|
146
|
-
del self.modules[mod]
|
147
|
-
for mod in list(self.allowed_modules):
|
148
|
-
if mod in os_related_modules:
|
149
|
-
self.allowed_modules.remove(mod)
|
150
|
-
|
151
|
-
self.source_lines: Optional[List[str]] = source.splitlines() if source else None
|
152
|
-
self.var_cache: Dict[str, Any] = {}
|
153
|
-
self.recursion_depth: int = 0
|
154
|
-
self.max_recursion_depth: int = max_recursion_depth
|
155
|
-
self.loop = None
|
156
|
-
self.current_class = None
|
157
|
-
self.current_instance = None
|
158
|
-
self.current_exception = None
|
159
|
-
self.last_exception = None
|
160
|
-
self.lock = threading.Lock() # Added for thread safety
|
161
|
-
|
162
|
-
if "decimal" in self.modules:
|
163
|
-
dec = self.modules["decimal"]
|
164
|
-
self.env_stack[0]["Decimal"] = dec.Decimal
|
165
|
-
self.env_stack[0]["getcontext"] = dec.getcontext
|
166
|
-
self.env_stack[0]["setcontext"] = dec.setcontext
|
167
|
-
self.env_stack[0]["localcontext"] = dec.localcontext
|
168
|
-
self.env_stack[0]["Context"] = dec.Context
|
169
|
-
|
170
|
-
from . import visit_handlers
|
171
|
-
for handler_name in visit_handlers.__all__:
|
172
|
-
handler = getattr(visit_handlers, handler_name)
|
173
|
-
setattr(self, handler_name, handler.__get__(self, ASTInterpreter))
|
174
|
-
|
175
|
-
def safe_import(self, name: str, globals=None, locals=None, fromlist=(), level=0) -> Any:
|
176
|
-
os_related_modules = {"os", "sys", "subprocess", "shutil", "platform"}
|
177
|
-
if self.restrict_os and name in os_related_modules:
|
178
|
-
raise ImportError(f"Import Error: Module '{name}' is blocked due to OS restriction.")
|
179
|
-
if name not in self.allowed_modules:
|
180
|
-
raise ImportError(f"Import Error: Module '{name}' is not allowed. Only {self.allowed_modules} are permitted.")
|
181
|
-
return self.modules[name]
|
182
|
-
|
183
|
-
def safe_getattr(self, obj: Any, name: str, default: Any = None) -> Any:
|
184
|
-
if name.startswith('__') and name.endswith('__') and name not in ['__init__', '__call__']:
|
185
|
-
raise AttributeError(f"Access to dunder attribute '{name}' is restricted.")
|
186
|
-
return getattr(obj, name, default)
|
187
|
-
|
188
|
-
def spawn_from_env(self, env_stack: List[Dict[str, Any]]) -> "ASTInterpreter":
|
189
|
-
new_interp = ASTInterpreter(
|
190
|
-
self.allowed_modules,
|
191
|
-
env_stack,
|
192
|
-
source="\n".join(self.source_lines) if self.source_lines else None,
|
193
|
-
restrict_os=self.restrict_os,
|
194
|
-
max_recursion_depth=self.max_recursion_depth,
|
195
|
-
max_operations=self.max_operations,
|
196
|
-
max_memory_mb=self.max_memory_mb,
|
197
|
-
sync_mode=self.sync_mode,
|
198
|
-
safe_builtins=self.safe_builtins # Pass along the customized safe_builtins
|
199
|
-
)
|
200
|
-
new_interp.loop = self.loop
|
201
|
-
new_interp.var_cache = self.var_cache.copy()
|
202
|
-
return new_interp
|
203
|
-
|
204
|
-
def get_variable(self, name: str) -> Any:
|
205
|
-
with self.lock:
|
206
|
-
if name in self.var_cache:
|
207
|
-
return self.var_cache[name]
|
208
|
-
for frame in reversed(self.env_stack):
|
209
|
-
if name in frame:
|
210
|
-
self.var_cache[name] = frame[name]
|
211
|
-
return frame[name]
|
212
|
-
raise NameError(f"Name '{name}' is not defined.")
|
213
|
-
|
214
|
-
def set_variable(self, name: str, value: Any) -> None:
|
215
|
-
with self.lock:
|
216
|
-
if "__global_names__" in self.env_stack[-1] and name in self.env_stack[-1]["__global_names__"]:
|
217
|
-
self.env_stack[0][name] = value
|
218
|
-
if name in self.var_cache:
|
219
|
-
del self.var_cache[name]
|
220
|
-
elif "__nonlocal_names__" in self.env_stack[-1] and name in self.env_stack[-1]["__nonlocal_names__"]:
|
221
|
-
for frame in reversed(self.env_stack[:-1]):
|
222
|
-
if name in frame:
|
223
|
-
frame[name] = value
|
224
|
-
if name in self.var_cache:
|
225
|
-
del self.var_cache[name]
|
226
|
-
return
|
227
|
-
raise NameError(f"Nonlocal name '{name}' not found in outer scope")
|
228
|
-
else:
|
229
|
-
self.env_stack[-1][name] = value
|
230
|
-
if name in self.var_cache:
|
231
|
-
del self.var_cache[name]
|
232
|
-
|
233
|
-
async def assign(self, target: ast.AST, value: Any) -> None:
|
234
|
-
if isinstance(target, ast.Name):
|
235
|
-
self.set_variable(target.id, value)
|
236
|
-
elif isinstance(target, (ast.Tuple, ast.List)):
|
237
|
-
star_index = None
|
238
|
-
for i, elt in enumerate(target.elts):
|
239
|
-
if isinstance(elt, ast.Starred):
|
240
|
-
if star_index is not None:
|
241
|
-
raise Exception("Multiple starred expressions not supported")
|
242
|
-
star_index = i
|
243
|
-
if star_index is None:
|
244
|
-
if len(target.elts) != len(value):
|
245
|
-
raise ValueError("Unpacking mismatch")
|
246
|
-
for t, v in zip(target.elts, value):
|
247
|
-
await self.assign(t, v)
|
248
|
-
else:
|
249
|
-
total = len(value)
|
250
|
-
before = target.elts[:star_index]
|
251
|
-
after = target.elts[star_index + 1:]
|
252
|
-
if len(before) + len(after) > total:
|
253
|
-
raise ValueError("Unpacking mismatch")
|
254
|
-
for i, elt2 in enumerate(before):
|
255
|
-
await self.assign(elt2, value[i])
|
256
|
-
starred_count = total - len(before) - len(after)
|
257
|
-
await self.assign(target.elts[star_index].value, value[len(before):len(before) + starred_count])
|
258
|
-
for j, elt2 in enumerate(after):
|
259
|
-
await self.assign(elt2, value[len(before) + starred_count + j])
|
260
|
-
elif isinstance(target, ast.Attribute):
|
261
|
-
obj = await self.visit(target.value, wrap_exceptions=True)
|
262
|
-
setattr(obj, target.attr, value)
|
263
|
-
elif isinstance(target, ast.Subscript):
|
264
|
-
obj = await self.visit(target.value, wrap_exceptions=True)
|
265
|
-
key = await self.visit(target.slice, wrap_exceptions=True)
|
266
|
-
obj[key] = value
|
267
|
-
else:
|
268
|
-
raise Exception("Unsupported assignment target type: " + str(type(target)))
|
269
|
-
|
270
|
-
async def visit(self, node: ast.AST, is_await_context: bool = False, wrap_exceptions: bool = True) -> Any:
|
271
|
-
self.operations_count += 1
|
272
|
-
if self.operations_count > self.max_operations:
|
273
|
-
raise RuntimeError(f"Exceeded maximum operations ({self.max_operations})")
|
274
|
-
memory_usage = self.process.memory_info().rss / 1024 / 1024 # MB
|
275
|
-
if memory_usage > self.max_memory_mb:
|
276
|
-
raise MemoryError(f"Memory usage exceeded limit ({self.max_memory_mb} MB)")
|
277
|
-
|
278
|
-
self.recursion_depth += 1
|
279
|
-
if self.recursion_depth > self.max_recursion_depth:
|
280
|
-
raise RecursionError(f"Maximum recursion depth exceeded ({self.max_recursion_depth})")
|
281
|
-
|
282
|
-
method_name: str = "visit_" + node.__class__.__name__
|
283
|
-
method = getattr(self, method_name, self.generic_visit)
|
284
|
-
self.env_stack[0]["logger"].debug(f"Visiting {method_name} at line {getattr(node, 'lineno', 'unknown')}")
|
285
|
-
|
286
|
-
try:
|
287
|
-
if self.sync_mode and not hasattr(node, 'await'): # Optimize for sync code
|
288
|
-
result = method(node, wrap_exceptions=wrap_exceptions)
|
289
|
-
elif method_name == "visit_Call":
|
290
|
-
result = await method(node, is_await_context, wrap_exceptions)
|
291
|
-
else:
|
292
|
-
result = await method(node, wrap_exceptions=wrap_exceptions)
|
293
|
-
self.recursion_depth -= 1
|
294
|
-
return result
|
295
|
-
except (ReturnException, BreakException, ContinueException):
|
296
|
-
self.recursion_depth -= 1
|
297
|
-
raise
|
298
|
-
except Exception as e:
|
299
|
-
self.recursion_depth -= 1
|
300
|
-
if not wrap_exceptions:
|
301
|
-
raise
|
302
|
-
lineno = getattr(node, "lineno", None) or 1
|
303
|
-
col = getattr(node, "col_offset", None) or 0
|
304
|
-
context_line = self.source_lines[lineno - 1] if self.source_lines and 1 <= lineno <= len(self.source_lines) else ""
|
305
|
-
raise WrappedException(
|
306
|
-
f"Error line {lineno}, col {col}:\n{context_line}\nDescription: {str(e)}", e, lineno, col, context_line
|
307
|
-
) from e
|
308
|
-
|
309
|
-
async def generic_visit(self, node: ast.AST, wrap_exceptions: bool = True) -> Any:
|
310
|
-
lineno = getattr(node, "lineno", None) or 1
|
311
|
-
context_line = self.source_lines[lineno - 1] if self.source_lines and 1 <= lineno <= len(self.source_lines) else ""
|
312
|
-
raise Exception(
|
313
|
-
f"Unsupported AST node type: {node.__class__.__name__} at line {lineno}.\nContext: {context_line}"
|
314
|
-
)
|
315
|
-
|
316
|
-
async def execute_async(self, node: ast.Module) -> Any:
|
317
|
-
return await self.visit(node)
|
318
|
-
|
319
|
-
def new_scope(self):
|
320
|
-
return Scope(self.env_stack)
|
321
|
-
|
322
|
-
async def _resolve_exception_type(self, node: Optional[ast.AST]) -> Any:
|
323
|
-
if node is None:
|
324
|
-
return Exception
|
325
|
-
if isinstance(node, ast.Name):
|
326
|
-
exc_type = self.get_variable(node.id)
|
327
|
-
if exc_type in (Exception, ZeroDivisionError, ValueError, TypeError):
|
328
|
-
return exc_type
|
329
|
-
return exc_type
|
330
|
-
if isinstance(node, ast.Call):
|
331
|
-
return await self.visit(node, wrap_exceptions=True)
|
332
|
-
return None
|
333
|
-
|
334
|
-
async def _create_class_instance(self, cls: type, *args, **kwargs):
|
335
|
-
if cls in (super, Exception, BaseException) or issubclass(cls, BaseException):
|
336
|
-
instance = cls.__new__(cls, *args, **kwargs)
|
337
|
-
if hasattr(instance, '__init__'):
|
338
|
-
init_method = instance.__init__.__func__ if hasattr(instance.__init__, '__func__') else instance.__init__
|
339
|
-
await self._execute_function(init_method, [instance] + list(args), kwargs)
|
340
|
-
return instance
|
341
|
-
instance = object.__new__(cls)
|
342
|
-
self.current_instance = instance
|
343
|
-
self.current_class = cls
|
344
|
-
init_method = cls.__init__.__func__ if hasattr(cls.__init__, '__func__') else cls.__init__
|
345
|
-
await self._execute_function(init_method, [instance] + list(args), kwargs)
|
346
|
-
self.current_instance = None
|
347
|
-
self.current_class = None
|
348
|
-
return instance
|
349
|
-
|
350
|
-
async def _execute_function(self, func: Any, args: List[Any], kwargs: Dict[str, Any]):
|
351
|
-
if asyncio.iscoroutinefunction(func):
|
352
|
-
return await func(*args, **kwargs)
|
353
|
-
elif callable(func):
|
354
|
-
result = func(*args, **kwargs)
|
355
|
-
if asyncio.iscoroutine(result):
|
356
|
-
return await result
|
357
|
-
return result
|
358
|
-
raise TypeError(f"Object {func} is not callable")
|
@@ -1,74 +0,0 @@
|
|
1
|
-
import ast
|
2
|
-
from typing import Any, Dict, List, Tuple
|
3
|
-
|
4
|
-
from .interpreter_core import ASTInterpreter
|
5
|
-
from .function_utils import Function
|
6
|
-
|
7
|
-
async def visit_Constant(self: ASTInterpreter, node: ast.Constant, wrap_exceptions: bool = True) -> Any:
|
8
|
-
return node.value
|
9
|
-
|
10
|
-
async def visit_Name(self: ASTInterpreter, node: ast.Name, wrap_exceptions: bool = True) -> Any:
|
11
|
-
if isinstance(node.ctx, ast.Load):
|
12
|
-
return self.get_variable(node.id)
|
13
|
-
elif isinstance(node.ctx, ast.Store):
|
14
|
-
return node.id
|
15
|
-
else:
|
16
|
-
raise Exception("Unsupported context for Name")
|
17
|
-
|
18
|
-
async def visit_List(self: ASTInterpreter, node: ast.List, wrap_exceptions: bool = True) -> List[Any]:
|
19
|
-
return [await self.visit(elt, wrap_exceptions=wrap_exceptions) for elt in node.elts]
|
20
|
-
|
21
|
-
async def visit_Tuple(self: ASTInterpreter, node: ast.Tuple, wrap_exceptions: bool = True) -> Tuple[Any, ...]:
|
22
|
-
elements = [await self.visit(elt, wrap_exceptions=wrap_exceptions) for elt in node.elts]
|
23
|
-
return tuple(elements)
|
24
|
-
|
25
|
-
async def visit_Dict(self: ASTInterpreter, node: ast.Dict, wrap_exceptions: bool = True) -> Dict[Any, Any]:
|
26
|
-
return {
|
27
|
-
await self.visit(k, wrap_exceptions=wrap_exceptions): await self.visit(v, wrap_exceptions=wrap_exceptions)
|
28
|
-
for k, v in zip(node.keys, node.values)
|
29
|
-
}
|
30
|
-
|
31
|
-
async def visit_Set(self: ASTInterpreter, node: ast.Set, wrap_exceptions: bool = True) -> set:
|
32
|
-
elements = [await self.visit(elt, wrap_exceptions=wrap_exceptions) for elt in node.elts]
|
33
|
-
return set(elements)
|
34
|
-
|
35
|
-
async def visit_Attribute(self: ASTInterpreter, node: ast.Attribute, wrap_exceptions: bool = True) -> Any:
|
36
|
-
value = await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
37
|
-
attr = node.attr
|
38
|
-
prop = getattr(type(value), attr, None)
|
39
|
-
if isinstance(prop, property) and isinstance(prop.fget, Function):
|
40
|
-
return await prop.fget(value)
|
41
|
-
return getattr(value, attr)
|
42
|
-
|
43
|
-
async def visit_Subscript(self: ASTInterpreter, node: ast.Subscript, wrap_exceptions: bool = True) -> Any:
|
44
|
-
value: Any = await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
45
|
-
slice_val: Any = await self.visit(node.slice, wrap_exceptions=wrap_exceptions)
|
46
|
-
return value[slice_val]
|
47
|
-
|
48
|
-
async def visit_Slice(self: ASTInterpreter, node: ast.Slice, wrap_exceptions: bool = True) -> slice:
|
49
|
-
lower: Any = await self.visit(node.lower, wrap_exceptions=wrap_exceptions) if node.lower else None
|
50
|
-
upper: Any = await self.visit(node.upper, wrap_exceptions=wrap_exceptions) if node.upper else None
|
51
|
-
step: Any = await self.visit(node.step, wrap_exceptions=wrap_exceptions) if node.step else None
|
52
|
-
return slice(lower, upper, step)
|
53
|
-
|
54
|
-
async def visit_Index(self: ASTInterpreter, node: ast.Index, wrap_exceptions: bool = True) -> Any:
|
55
|
-
return await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
56
|
-
|
57
|
-
async def visit_Starred(self: ASTInterpreter, node: ast.Starred, wrap_exceptions: bool = True) -> Any:
|
58
|
-
value = await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
59
|
-
if not isinstance(value, (list, tuple, set)):
|
60
|
-
raise TypeError(f"Cannot unpack non-iterable object of type {type(value).__name__}")
|
61
|
-
return value
|
62
|
-
|
63
|
-
async def visit_JoinedStr(self: ASTInterpreter, node: ast.JoinedStr, wrap_exceptions: bool = True) -> str:
|
64
|
-
parts = []
|
65
|
-
for value in node.values:
|
66
|
-
val = await self.visit(value, wrap_exceptions=wrap_exceptions)
|
67
|
-
if isinstance(value, ast.FormattedValue):
|
68
|
-
parts.append(str(val))
|
69
|
-
else:
|
70
|
-
parts.append(val)
|
71
|
-
return "".join(parts)
|
72
|
-
|
73
|
-
async def visit_FormattedValue(self: ASTInterpreter, node: ast.FormattedValue, wrap_exceptions: bool = True) -> Any:
|
74
|
-
return await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
@@ -1,148 +0,0 @@
|
|
1
|
-
import ast
|
2
|
-
from typing import Any
|
3
|
-
|
4
|
-
from .interpreter_core import ASTInterpreter
|
5
|
-
|
6
|
-
async def visit_Global(self: ASTInterpreter, node: ast.Global, wrap_exceptions: bool = True) -> None:
|
7
|
-
self.env_stack[-1].setdefault("__global_names__", set()).update(node.names)
|
8
|
-
|
9
|
-
async def visit_Nonlocal(self: ASTInterpreter, node: ast.Nonlocal, wrap_exceptions: bool = True) -> None:
|
10
|
-
self.env_stack[-1].setdefault("__nonlocal_names__", set()).update(node.names)
|
11
|
-
|
12
|
-
async def visit_Delete(self: ASTInterpreter, node: ast.Delete, wrap_exceptions: bool = True):
|
13
|
-
for target in node.targets:
|
14
|
-
if isinstance(target, ast.Name):
|
15
|
-
del self.env_stack[-1][target.id]
|
16
|
-
elif isinstance(target, ast.Subscript):
|
17
|
-
obj = await self.visit(target.value, wrap_exceptions=wrap_exceptions)
|
18
|
-
key = await self.visit(target.slice, wrap_exceptions=wrap_exceptions)
|
19
|
-
del obj[key]
|
20
|
-
else:
|
21
|
-
raise Exception(f"Unsupported del target: {type(target).__name__}")
|
22
|
-
|
23
|
-
async def visit_Assert(self: ASTInterpreter, node: ast.Assert, wrap_exceptions: bool = True) -> None:
|
24
|
-
test = await self.visit(node.test, wrap_exceptions=wrap_exceptions)
|
25
|
-
if not test:
|
26
|
-
msg = await self.visit(node.msg, wrap_exceptions=wrap_exceptions) if node.msg else "Assertion failed"
|
27
|
-
raise AssertionError(msg)
|
28
|
-
|
29
|
-
async def visit_Yield(self: ASTInterpreter, node: ast.Yield, wrap_exceptions: bool = True) -> Any:
|
30
|
-
value = await self.visit(node.value, wrap_exceptions=wrap_exceptions) if node.value else None
|
31
|
-
self.recursion_depth += 1 # Treat yields as recursion for loop detection
|
32
|
-
if self.recursion_depth > self.max_recursion_depth:
|
33
|
-
raise RecursionError(f"Maximum recursion depth exceeded in yield ({self.max_recursion_depth})")
|
34
|
-
self.recursion_depth -= 1
|
35
|
-
return value
|
36
|
-
|
37
|
-
async def visit_YieldFrom(self: ASTInterpreter, node: ast.YieldFrom, wrap_exceptions: bool = True) -> Any:
|
38
|
-
iterable = await self.visit(node.value, wrap_exceptions=wrap_exceptions)
|
39
|
-
if hasattr(iterable, '__aiter__'):
|
40
|
-
async def async_gen():
|
41
|
-
async for value in iterable:
|
42
|
-
yield value
|
43
|
-
return async_gen()
|
44
|
-
else:
|
45
|
-
def sync_gen():
|
46
|
-
for value in iterable:
|
47
|
-
yield value
|
48
|
-
return sync_gen()
|
49
|
-
|
50
|
-
async def visit_Match(self: ASTInterpreter, node: ast.Match, wrap_exceptions: bool = True) -> Any:
|
51
|
-
subject = await self.visit(node.subject, wrap_exceptions=wrap_exceptions)
|
52
|
-
result = None
|
53
|
-
base_frame = self.env_stack[-1].copy()
|
54
|
-
for case in node.cases:
|
55
|
-
self.env_stack.append(base_frame.copy())
|
56
|
-
try:
|
57
|
-
if await self._match_pattern(subject, case.pattern):
|
58
|
-
if case.guard and not await self.visit(case.guard, wrap_exceptions=True):
|
59
|
-
continue
|
60
|
-
for stmt in case.body[:-1]:
|
61
|
-
await self.visit(stmt, wrap_exceptions=wrap_exceptions)
|
62
|
-
result = await self.visit(case.body[-1], wrap_exceptions=wrap_exceptions)
|
63
|
-
break
|
64
|
-
finally:
|
65
|
-
self.env_stack.pop()
|
66
|
-
return result
|
67
|
-
|
68
|
-
async def _match_pattern(self: ASTInterpreter, subject: Any, pattern: ast.AST) -> bool:
|
69
|
-
if isinstance(pattern, ast.MatchValue):
|
70
|
-
value = await self.visit(pattern.value, wrap_exceptions=True)
|
71
|
-
return subject == value
|
72
|
-
elif isinstance(pattern, ast.MatchSingleton):
|
73
|
-
return subject is pattern.value
|
74
|
-
elif isinstance(pattern, ast.MatchSequence):
|
75
|
-
if not isinstance(subject, (list, tuple)):
|
76
|
-
return False
|
77
|
-
if len(pattern.patterns) != len(subject) and not any(isinstance(p, ast.MatchStar) for p in pattern.patterns):
|
78
|
-
return False
|
79
|
-
star_idx = None
|
80
|
-
for i, pat in enumerate(pattern.patterns):
|
81
|
-
if isinstance(pat, ast.MatchStar):
|
82
|
-
if star_idx is not None:
|
83
|
-
return False
|
84
|
-
star_idx = i
|
85
|
-
if star_idx is None:
|
86
|
-
for sub, pat in zip(subject, pattern.patterns):
|
87
|
-
if not await self._match_pattern(sub, pat):
|
88
|
-
return False
|
89
|
-
return True
|
90
|
-
else:
|
91
|
-
before = pattern.patterns[:star_idx]
|
92
|
-
after = pattern.patterns[star_idx + 1:]
|
93
|
-
if len(before) + len(after) > len(subject):
|
94
|
-
return False
|
95
|
-
for sub, pat in zip(subject[:len(before)], before):
|
96
|
-
if not await self._match_pattern(sub, pat):
|
97
|
-
return False
|
98
|
-
for sub, pat in zip(subject[len(subject) - len(after):], after):
|
99
|
-
if not await self._match_pattern(sub, pat):
|
100
|
-
return False
|
101
|
-
star_pat = pattern.patterns[star_idx]
|
102
|
-
star_count = len(subject) - len(before) - len(after)
|
103
|
-
star_sub = subject[len(before):len(before) + star_count]
|
104
|
-
if star_pat.name:
|
105
|
-
self.set_variable(star_pat.name, star_sub)
|
106
|
-
return True
|
107
|
-
elif isinstance(pattern, ast.MatchMapping):
|
108
|
-
if not isinstance(subject, dict):
|
109
|
-
return False
|
110
|
-
keys = [await self.visit(k, wrap_exceptions=True) for k in pattern.keys]
|
111
|
-
if len(keys) != len(subject) and pattern.rest is None:
|
112
|
-
return False
|
113
|
-
for k, p in zip(keys, pattern.patterns):
|
114
|
-
if k not in subject or not await self._match_pattern(subject[k], p):
|
115
|
-
return False
|
116
|
-
if pattern.rest:
|
117
|
-
remaining = {k: v for k, v in subject.items() if k not in keys}
|
118
|
-
self.set_variable(pattern.rest, remaining)
|
119
|
-
return True
|
120
|
-
elif isinstance(pattern, ast.MatchClass):
|
121
|
-
cls = await self.visit(pattern.cls, wrap_exceptions=True)
|
122
|
-
if not isinstance(subject, cls):
|
123
|
-
return False
|
124
|
-
attrs = [getattr(subject, attr) for attr in pattern.attribute_names]
|
125
|
-
if len(attrs) != len(pattern.patterns):
|
126
|
-
return False
|
127
|
-
for attr_val, pat in zip(attrs, pattern.patterns):
|
128
|
-
if not await self._match_pattern(attr_val, pat):
|
129
|
-
return False
|
130
|
-
return True
|
131
|
-
elif isinstance(pattern, ast.MatchStar):
|
132
|
-
if pattern.name:
|
133
|
-
self.set_variable(pattern.name, subject)
|
134
|
-
return True
|
135
|
-
elif isinstance(pattern, ast.MatchAs):
|
136
|
-
if pattern.pattern:
|
137
|
-
if not await self._match_pattern(subject, pattern.pattern):
|
138
|
-
return False
|
139
|
-
if pattern.name:
|
140
|
-
self.set_variable(pattern.name, subject)
|
141
|
-
return True
|
142
|
-
elif isinstance(pattern, ast.MatchOr):
|
143
|
-
for pat in pattern.patterns:
|
144
|
-
if await self._match_pattern(subject, pat):
|
145
|
-
return True
|
146
|
-
return False
|
147
|
-
else:
|
148
|
-
raise Exception(f"Unsupported match pattern: {pattern.__class__.__name__}")
|