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.
Files changed (67) hide show
  1. quantalogic/agent.py +0 -1
  2. quantalogic/codeact/TODO.md +14 -0
  3. quantalogic/codeact/agent.py +400 -421
  4. quantalogic/codeact/cli.py +42 -224
  5. quantalogic/codeact/cli_commands/__init__.py +0 -0
  6. quantalogic/codeact/cli_commands/create_toolbox.py +45 -0
  7. quantalogic/codeact/cli_commands/install_toolbox.py +20 -0
  8. quantalogic/codeact/cli_commands/list_executor.py +15 -0
  9. quantalogic/codeact/cli_commands/list_reasoners.py +15 -0
  10. quantalogic/codeact/cli_commands/list_toolboxes.py +47 -0
  11. quantalogic/codeact/cli_commands/task.py +215 -0
  12. quantalogic/codeact/cli_commands/tool_info.py +24 -0
  13. quantalogic/codeact/cli_commands/uninstall_toolbox.py +43 -0
  14. quantalogic/codeact/config.yaml +21 -0
  15. quantalogic/codeact/constants.py +1 -1
  16. quantalogic/codeact/events.py +12 -5
  17. quantalogic/codeact/examples/README.md +342 -0
  18. quantalogic/codeact/examples/agent_sample.yaml +29 -0
  19. quantalogic/codeact/executor.py +186 -0
  20. quantalogic/codeact/history_manager.py +94 -0
  21. quantalogic/codeact/llm_util.py +3 -22
  22. quantalogic/codeact/plugin_manager.py +92 -0
  23. quantalogic/codeact/prompts/generate_action.j2 +65 -14
  24. quantalogic/codeact/prompts/generate_program.j2 +32 -19
  25. quantalogic/codeact/react_agent.py +318 -0
  26. quantalogic/codeact/reasoner.py +185 -0
  27. quantalogic/codeact/templates/toolbox/README.md.j2 +10 -0
  28. quantalogic/codeact/templates/toolbox/pyproject.toml.j2 +16 -0
  29. quantalogic/codeact/templates/toolbox/tools.py.j2 +6 -0
  30. quantalogic/codeact/templates.py +7 -0
  31. quantalogic/codeact/tools_manager.py +242 -119
  32. quantalogic/codeact/utils.py +16 -89
  33. quantalogic/codeact/xml_utils.py +126 -0
  34. quantalogic/flow/flow.py +151 -41
  35. quantalogic/flow/flow_extractor.py +61 -1
  36. quantalogic/flow/flow_generator.py +34 -6
  37. quantalogic/flow/flow_manager.py +64 -25
  38. quantalogic/flow/flow_manager_schema.py +32 -0
  39. quantalogic/tools/action_gen.py +1 -1
  40. quantalogic/tools/action_gen_safe.py +340 -0
  41. quantalogic/tools/tool.py +531 -109
  42. quantalogic/tools/write_file_tool.py +7 -8
  43. {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/METADATA +3 -2
  44. {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/RECORD +47 -42
  45. {quantalogic-0.61.2.dist-info → quantalogic-0.80.dist-info}/WHEEL +1 -1
  46. quantalogic-0.80.dist-info/entry_points.txt +3 -0
  47. quantalogic/python_interpreter/__init__.py +0 -23
  48. quantalogic/python_interpreter/assignment_visitors.py +0 -63
  49. quantalogic/python_interpreter/base_visitors.py +0 -20
  50. quantalogic/python_interpreter/class_visitors.py +0 -22
  51. quantalogic/python_interpreter/comprehension_visitors.py +0 -172
  52. quantalogic/python_interpreter/context_visitors.py +0 -59
  53. quantalogic/python_interpreter/control_flow_visitors.py +0 -88
  54. quantalogic/python_interpreter/exception_visitors.py +0 -109
  55. quantalogic/python_interpreter/exceptions.py +0 -39
  56. quantalogic/python_interpreter/execution.py +0 -202
  57. quantalogic/python_interpreter/function_utils.py +0 -386
  58. quantalogic/python_interpreter/function_visitors.py +0 -209
  59. quantalogic/python_interpreter/import_visitors.py +0 -28
  60. quantalogic/python_interpreter/interpreter_core.py +0 -358
  61. quantalogic/python_interpreter/literal_visitors.py +0 -74
  62. quantalogic/python_interpreter/misc_visitors.py +0 -148
  63. quantalogic/python_interpreter/operator_visitors.py +0 -108
  64. quantalogic/python_interpreter/scope.py +0 -10
  65. quantalogic/python_interpreter/visit_handlers.py +0 -110
  66. quantalogic-0.61.2.dist-info/entry_points.txt +0 -6
  67. {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__}")