kweaver-dolphin 0.1.0__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 (199) hide show
  1. DolphinLanguageSDK/__init__.py +58 -0
  2. dolphin/__init__.py +62 -0
  3. dolphin/cli/__init__.py +20 -0
  4. dolphin/cli/args/__init__.py +9 -0
  5. dolphin/cli/args/parser.py +567 -0
  6. dolphin/cli/builtin_agents/__init__.py +22 -0
  7. dolphin/cli/commands/__init__.py +4 -0
  8. dolphin/cli/interrupt/__init__.py +8 -0
  9. dolphin/cli/interrupt/handler.py +205 -0
  10. dolphin/cli/interrupt/keyboard.py +82 -0
  11. dolphin/cli/main.py +49 -0
  12. dolphin/cli/multimodal/__init__.py +34 -0
  13. dolphin/cli/multimodal/clipboard.py +327 -0
  14. dolphin/cli/multimodal/handler.py +249 -0
  15. dolphin/cli/multimodal/image_processor.py +214 -0
  16. dolphin/cli/multimodal/input_parser.py +149 -0
  17. dolphin/cli/runner/__init__.py +8 -0
  18. dolphin/cli/runner/runner.py +989 -0
  19. dolphin/cli/ui/__init__.py +10 -0
  20. dolphin/cli/ui/console.py +2795 -0
  21. dolphin/cli/ui/input.py +340 -0
  22. dolphin/cli/ui/layout.py +425 -0
  23. dolphin/cli/ui/stream_renderer.py +302 -0
  24. dolphin/cli/utils/__init__.py +8 -0
  25. dolphin/cli/utils/helpers.py +135 -0
  26. dolphin/cli/utils/version.py +49 -0
  27. dolphin/core/__init__.py +107 -0
  28. dolphin/core/agent/__init__.py +10 -0
  29. dolphin/core/agent/agent_state.py +69 -0
  30. dolphin/core/agent/base_agent.py +970 -0
  31. dolphin/core/code_block/__init__.py +0 -0
  32. dolphin/core/code_block/agent_init_block.py +0 -0
  33. dolphin/core/code_block/assign_block.py +98 -0
  34. dolphin/core/code_block/basic_code_block.py +1865 -0
  35. dolphin/core/code_block/explore_block.py +1327 -0
  36. dolphin/core/code_block/explore_block_v2.py +712 -0
  37. dolphin/core/code_block/explore_strategy.py +672 -0
  38. dolphin/core/code_block/judge_block.py +220 -0
  39. dolphin/core/code_block/prompt_block.py +32 -0
  40. dolphin/core/code_block/skill_call_deduplicator.py +291 -0
  41. dolphin/core/code_block/tool_block.py +129 -0
  42. dolphin/core/common/__init__.py +17 -0
  43. dolphin/core/common/constants.py +176 -0
  44. dolphin/core/common/enums.py +1173 -0
  45. dolphin/core/common/exceptions.py +133 -0
  46. dolphin/core/common/multimodal.py +539 -0
  47. dolphin/core/common/object_type.py +165 -0
  48. dolphin/core/common/output_format.py +432 -0
  49. dolphin/core/common/types.py +36 -0
  50. dolphin/core/config/__init__.py +16 -0
  51. dolphin/core/config/global_config.py +1289 -0
  52. dolphin/core/config/ontology_config.py +133 -0
  53. dolphin/core/context/__init__.py +12 -0
  54. dolphin/core/context/context.py +1580 -0
  55. dolphin/core/context/context_manager.py +161 -0
  56. dolphin/core/context/var_output.py +82 -0
  57. dolphin/core/context/variable_pool.py +356 -0
  58. dolphin/core/context_engineer/__init__.py +41 -0
  59. dolphin/core/context_engineer/config/__init__.py +5 -0
  60. dolphin/core/context_engineer/config/settings.py +402 -0
  61. dolphin/core/context_engineer/core/__init__.py +7 -0
  62. dolphin/core/context_engineer/core/budget_manager.py +327 -0
  63. dolphin/core/context_engineer/core/context_assembler.py +583 -0
  64. dolphin/core/context_engineer/core/context_manager.py +637 -0
  65. dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
  66. dolphin/core/context_engineer/example/incremental_example.py +267 -0
  67. dolphin/core/context_engineer/example/traditional_example.py +334 -0
  68. dolphin/core/context_engineer/services/__init__.py +5 -0
  69. dolphin/core/context_engineer/services/compressor.py +399 -0
  70. dolphin/core/context_engineer/utils/__init__.py +6 -0
  71. dolphin/core/context_engineer/utils/context_utils.py +441 -0
  72. dolphin/core/context_engineer/utils/message_formatter.py +270 -0
  73. dolphin/core/context_engineer/utils/token_utils.py +139 -0
  74. dolphin/core/coroutine/__init__.py +15 -0
  75. dolphin/core/coroutine/context_snapshot.py +154 -0
  76. dolphin/core/coroutine/context_snapshot_profile.py +922 -0
  77. dolphin/core/coroutine/context_snapshot_store.py +268 -0
  78. dolphin/core/coroutine/execution_frame.py +145 -0
  79. dolphin/core/coroutine/execution_state_registry.py +161 -0
  80. dolphin/core/coroutine/resume_handle.py +101 -0
  81. dolphin/core/coroutine/step_result.py +101 -0
  82. dolphin/core/executor/__init__.py +18 -0
  83. dolphin/core/executor/debug_controller.py +630 -0
  84. dolphin/core/executor/dolphin_executor.py +1063 -0
  85. dolphin/core/executor/executor.py +624 -0
  86. dolphin/core/flags/__init__.py +27 -0
  87. dolphin/core/flags/definitions.py +49 -0
  88. dolphin/core/flags/manager.py +113 -0
  89. dolphin/core/hook/__init__.py +95 -0
  90. dolphin/core/hook/expression_evaluator.py +499 -0
  91. dolphin/core/hook/hook_dispatcher.py +380 -0
  92. dolphin/core/hook/hook_types.py +248 -0
  93. dolphin/core/hook/isolated_variable_pool.py +284 -0
  94. dolphin/core/interfaces.py +53 -0
  95. dolphin/core/llm/__init__.py +0 -0
  96. dolphin/core/llm/llm.py +495 -0
  97. dolphin/core/llm/llm_call.py +100 -0
  98. dolphin/core/llm/llm_client.py +1285 -0
  99. dolphin/core/llm/message_sanitizer.py +120 -0
  100. dolphin/core/logging/__init__.py +20 -0
  101. dolphin/core/logging/logger.py +526 -0
  102. dolphin/core/message/__init__.py +8 -0
  103. dolphin/core/message/compressor.py +749 -0
  104. dolphin/core/parser/__init__.py +8 -0
  105. dolphin/core/parser/parser.py +405 -0
  106. dolphin/core/runtime/__init__.py +10 -0
  107. dolphin/core/runtime/runtime_graph.py +926 -0
  108. dolphin/core/runtime/runtime_instance.py +446 -0
  109. dolphin/core/skill/__init__.py +14 -0
  110. dolphin/core/skill/context_retention.py +157 -0
  111. dolphin/core/skill/skill_function.py +686 -0
  112. dolphin/core/skill/skill_matcher.py +282 -0
  113. dolphin/core/skill/skillkit.py +700 -0
  114. dolphin/core/skill/skillset.py +72 -0
  115. dolphin/core/trajectory/__init__.py +10 -0
  116. dolphin/core/trajectory/recorder.py +189 -0
  117. dolphin/core/trajectory/trajectory.py +522 -0
  118. dolphin/core/utils/__init__.py +9 -0
  119. dolphin/core/utils/cache_kv.py +212 -0
  120. dolphin/core/utils/tools.py +340 -0
  121. dolphin/lib/__init__.py +93 -0
  122. dolphin/lib/debug/__init__.py +8 -0
  123. dolphin/lib/debug/visualizer.py +409 -0
  124. dolphin/lib/memory/__init__.py +28 -0
  125. dolphin/lib/memory/async_processor.py +220 -0
  126. dolphin/lib/memory/llm_calls.py +195 -0
  127. dolphin/lib/memory/manager.py +78 -0
  128. dolphin/lib/memory/sandbox.py +46 -0
  129. dolphin/lib/memory/storage.py +245 -0
  130. dolphin/lib/memory/utils.py +51 -0
  131. dolphin/lib/ontology/__init__.py +12 -0
  132. dolphin/lib/ontology/basic/__init__.py +0 -0
  133. dolphin/lib/ontology/basic/base.py +102 -0
  134. dolphin/lib/ontology/basic/concept.py +130 -0
  135. dolphin/lib/ontology/basic/object.py +11 -0
  136. dolphin/lib/ontology/basic/relation.py +63 -0
  137. dolphin/lib/ontology/datasource/__init__.py +27 -0
  138. dolphin/lib/ontology/datasource/datasource.py +66 -0
  139. dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
  140. dolphin/lib/ontology/datasource/sql.py +845 -0
  141. dolphin/lib/ontology/mapping.py +177 -0
  142. dolphin/lib/ontology/ontology.py +733 -0
  143. dolphin/lib/ontology/ontology_context.py +16 -0
  144. dolphin/lib/ontology/ontology_manager.py +107 -0
  145. dolphin/lib/skill_results/__init__.py +31 -0
  146. dolphin/lib/skill_results/cache_backend.py +559 -0
  147. dolphin/lib/skill_results/result_processor.py +181 -0
  148. dolphin/lib/skill_results/result_reference.py +179 -0
  149. dolphin/lib/skill_results/skillkit_hook.py +324 -0
  150. dolphin/lib/skill_results/strategies.py +328 -0
  151. dolphin/lib/skill_results/strategy_registry.py +150 -0
  152. dolphin/lib/skillkits/__init__.py +44 -0
  153. dolphin/lib/skillkits/agent_skillkit.py +155 -0
  154. dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
  155. dolphin/lib/skillkits/env_skillkit.py +250 -0
  156. dolphin/lib/skillkits/mcp_adapter.py +616 -0
  157. dolphin/lib/skillkits/mcp_skillkit.py +771 -0
  158. dolphin/lib/skillkits/memory_skillkit.py +650 -0
  159. dolphin/lib/skillkits/noop_skillkit.py +31 -0
  160. dolphin/lib/skillkits/ontology_skillkit.py +89 -0
  161. dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
  162. dolphin/lib/skillkits/resource/__init__.py +52 -0
  163. dolphin/lib/skillkits/resource/models/__init__.py +6 -0
  164. dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
  165. dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
  166. dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
  167. dolphin/lib/skillkits/resource/skill_cache.py +215 -0
  168. dolphin/lib/skillkits/resource/skill_loader.py +395 -0
  169. dolphin/lib/skillkits/resource/skill_validator.py +406 -0
  170. dolphin/lib/skillkits/resource_skillkit.py +11 -0
  171. dolphin/lib/skillkits/search_skillkit.py +163 -0
  172. dolphin/lib/skillkits/sql_skillkit.py +274 -0
  173. dolphin/lib/skillkits/system_skillkit.py +509 -0
  174. dolphin/lib/skillkits/vm_skillkit.py +65 -0
  175. dolphin/lib/utils/__init__.py +9 -0
  176. dolphin/lib/utils/data_process.py +207 -0
  177. dolphin/lib/utils/handle_progress.py +178 -0
  178. dolphin/lib/utils/security.py +139 -0
  179. dolphin/lib/utils/text_retrieval.py +462 -0
  180. dolphin/lib/vm/__init__.py +11 -0
  181. dolphin/lib/vm/env_executor.py +895 -0
  182. dolphin/lib/vm/python_session_manager.py +453 -0
  183. dolphin/lib/vm/vm.py +610 -0
  184. dolphin/sdk/__init__.py +60 -0
  185. dolphin/sdk/agent/__init__.py +12 -0
  186. dolphin/sdk/agent/agent_factory.py +236 -0
  187. dolphin/sdk/agent/dolphin_agent.py +1106 -0
  188. dolphin/sdk/api/__init__.py +4 -0
  189. dolphin/sdk/runtime/__init__.py +8 -0
  190. dolphin/sdk/runtime/env.py +363 -0
  191. dolphin/sdk/skill/__init__.py +10 -0
  192. dolphin/sdk/skill/global_skills.py +706 -0
  193. dolphin/sdk/skill/traditional_toolkit.py +260 -0
  194. kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
  195. kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
  196. kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
  197. kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
  198. kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
  199. kweaver_dolphin-0.1.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,499 @@
1
+ """Expression Evaluator Module
2
+
3
+ This module provides safe expression evaluation using Python's AST module.
4
+
5
+ Key Features:
6
+ - Safe parsing and evaluation (no eval/exec)
7
+ - Support for basic arithmetic and comparison operators
8
+ - Support for built-in functions (len, min, max, abs)
9
+ - Variable substitution with $ prefix
10
+ - Result normalization to 0-1 range
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import ast
16
+ import operator
17
+ import re
18
+ from typing import Any, Dict, Callable, Optional
19
+
20
+
21
+ # Built-in functions available in expressions
22
+ BUILTIN_FUNCTIONS: Dict[str, Callable] = {
23
+ 'len': len,
24
+ 'min': min,
25
+ 'max': max,
26
+ 'abs': abs,
27
+ 'int': int,
28
+ 'float': float,
29
+ 'str': str,
30
+ 'bool': bool,
31
+ }
32
+
33
+ # Safe division wrappers to handle ZeroDivisionError
34
+ def _safe_truediv(a, b):
35
+ """Safe true division that raises error on division by zero."""
36
+ if b == 0:
37
+ raise ExpressionError(f"Division by zero: {a} / 0")
38
+ return operator.truediv(a, b)
39
+
40
+
41
+ def _safe_floordiv(a, b):
42
+ """Safe floor division that raises error on division by zero."""
43
+ if b == 0:
44
+ raise ExpressionError(f"Division by zero: {a} // 0")
45
+ return operator.floordiv(a, b)
46
+
47
+
48
+ def _safe_mod(a, b):
49
+ """Safe modulo that raises error on division by zero."""
50
+ if b == 0:
51
+ raise ExpressionError(f"Modulo by zero: {a} % 0")
52
+ return operator.mod(a, b)
53
+
54
+
55
+ # Supported binary operators
56
+ BINARY_OPS = {
57
+ ast.Add: operator.add,
58
+ ast.Sub: operator.sub,
59
+ ast.Mult: operator.mul,
60
+ ast.Div: _safe_truediv,
61
+ ast.FloorDiv: _safe_floordiv,
62
+ ast.Mod: _safe_mod,
63
+ ast.Pow: operator.pow,
64
+ }
65
+
66
+ # Supported comparison operators
67
+ COMPARE_OPS = {
68
+ ast.Eq: operator.eq,
69
+ ast.NotEq: operator.ne,
70
+ ast.Lt: operator.lt,
71
+ ast.LtE: operator.le,
72
+ ast.Gt: operator.gt,
73
+ ast.GtE: operator.ge,
74
+ ast.In: lambda a, b: a in b,
75
+ ast.NotIn: lambda a, b: a not in b,
76
+ }
77
+
78
+ # Supported unary operators
79
+ UNARY_OPS = {
80
+ ast.UAdd: operator.pos,
81
+ ast.USub: operator.neg,
82
+ ast.Not: operator.not_,
83
+ }
84
+
85
+ # Maximum AST nesting depth for safety
86
+ MAX_AST_DEPTH = 10
87
+
88
+
89
+ class ExpressionError(Exception):
90
+ """Exception raised for expression parsing or evaluation errors."""
91
+ pass
92
+
93
+
94
+ class ExpressionEvaluator:
95
+ """Safe expression evaluator using Python AST.
96
+
97
+ Supports:
98
+ - Arithmetic: +, -, *, /, //, %, **
99
+ - Comparison: ==, !=, <, <=, >, >=, in, not in
100
+ - Logical: and, or, not
101
+ - Built-in functions: len(), min(), max(), abs(), int(), float(), str(), bool()
102
+ - Variable access: $answer, $think, $steps, $tool_calls
103
+ - Boolean literals: True, False
104
+ - Numeric literals: 1, 2.5, 0.8
105
+
106
+ Example:
107
+ ```python
108
+ evaluator = ExpressionEvaluator(
109
+ expr="len($answer) > 100",
110
+ context={'answer': 'Hello World', 'think': '...'}
111
+ )
112
+ score = await evaluator.evaluate() # Returns 0.0 or 1.0
113
+ ```
114
+ """
115
+
116
+ def __init__(
117
+ self,
118
+ expr: str,
119
+ context: Dict[str, Any],
120
+ model: Optional[str] = None,
121
+ ):
122
+ """Initialize expression evaluator.
123
+
124
+ Args:
125
+ expr: Expression string to evaluate
126
+ context: Dictionary of variable values
127
+ model: Optional model name (reserved for future LLM-based evaluation)
128
+ """
129
+ self.original_expr = expr
130
+ self.context = context
131
+ self.model = model
132
+
133
+ # Preprocess and parse expression
134
+ self.processed_expr = self._preprocess_variables(expr)
135
+ self.ast_tree = self._parse_expr(self.processed_expr)
136
+
137
+ def _preprocess_variables(self, expr: str) -> str:
138
+ """Convert $variable syntax to Python-compatible format.
139
+
140
+ Transforms:
141
+ - $answer -> __var_answer
142
+ - $tool_calls -> __var_tool_calls
143
+
144
+ Args:
145
+ expr: Original expression with $ variables
146
+
147
+ Returns:
148
+ Processed expression with renamed variables
149
+ """
150
+ # Pattern to match $variable_name (with optional path like $obj.attr)
151
+ pattern = r'\$([a-zA-Z_][a-zA-Z0-9_]*)'
152
+
153
+ def replace_var(match):
154
+ var_name = match.group(1)
155
+ return f'__var_{var_name}'
156
+
157
+ return re.sub(pattern, replace_var, expr)
158
+
159
+ def _parse_expr(self, expr: str) -> ast.Expression:
160
+ """Parse expression string into AST.
161
+
162
+ Args:
163
+ expr: Preprocessed expression string
164
+
165
+ Returns:
166
+ Parsed AST tree
167
+
168
+ Raises:
169
+ ExpressionError: If expression is syntactically invalid
170
+ """
171
+ try:
172
+ tree = ast.parse(expr, mode='eval')
173
+ self._validate_ast(tree, depth=0)
174
+ return tree
175
+ except SyntaxError as e:
176
+ raise ExpressionError(f"Invalid expression syntax: {self.original_expr}") from e
177
+
178
+ def _validate_ast(self, node: ast.AST, depth: int) -> None:
179
+ """Validate AST for safety.
180
+
181
+ Checks:
182
+ - Maximum nesting depth
183
+ - Only allowed node types
184
+
185
+ Args:
186
+ node: AST node to validate
187
+ depth: Current nesting depth
188
+
189
+ Raises:
190
+ ExpressionError: If AST contains unsafe constructs
191
+ """
192
+ if depth > MAX_AST_DEPTH:
193
+ raise ExpressionError(f"Expression too deeply nested (max {MAX_AST_DEPTH} levels)")
194
+
195
+ # Allowed node types
196
+ allowed_types = (
197
+ ast.Expression,
198
+ ast.BinOp,
199
+ ast.UnaryOp,
200
+ ast.Compare,
201
+ ast.BoolOp,
202
+ ast.Call,
203
+ ast.Name,
204
+ ast.Constant,
205
+ ast.Num, # Python 3.7 compatibility
206
+ ast.Str, # Python 3.7 compatibility
207
+ ast.NameConstant, # Python 3.7 compatibility
208
+ ast.List,
209
+ ast.Tuple,
210
+ ast.Subscript,
211
+ ast.Index, # Python 3.8 compatibility
212
+ ast.Slice,
213
+ ast.Attribute,
214
+ # Operators
215
+ ast.Add, ast.Sub, ast.Mult, ast.Div, ast.FloorDiv, ast.Mod, ast.Pow,
216
+ ast.Eq, ast.NotEq, ast.Lt, ast.LtE, ast.Gt, ast.GtE, ast.In, ast.NotIn,
217
+ ast.And, ast.Or, ast.Not,
218
+ ast.UAdd, ast.USub,
219
+ ast.Load,
220
+ ast.IfExp, # Ternary expression: x if cond else y
221
+ )
222
+
223
+ if not isinstance(node, allowed_types):
224
+ raise ExpressionError(f"Unsupported expression construct: {type(node).__name__}")
225
+
226
+ # Recursively validate children
227
+ for child in ast.iter_child_nodes(node):
228
+ self._validate_ast(child, depth + 1)
229
+
230
+ async def evaluate(self) -> float:
231
+ """Evaluate expression and return normalized score.
232
+
233
+ Returns:
234
+ Score between 0.0 and 1.0
235
+
236
+ Raises:
237
+ ExpressionError: If evaluation fails
238
+ """
239
+ try:
240
+ result = self._eval_node(self.ast_tree.body)
241
+ return self._normalize(result)
242
+ except ExpressionError:
243
+ raise
244
+ except Exception as e:
245
+ raise ExpressionError(f"Expression evaluation failed: {e}") from e
246
+
247
+ def evaluate_sync(self) -> float:
248
+ """Synchronous version of evaluate().
249
+
250
+ Returns:
251
+ Score between 0.0 and 1.0
252
+ """
253
+ try:
254
+ result = self._eval_node(self.ast_tree.body)
255
+ return self._normalize(result)
256
+ except ExpressionError:
257
+ raise
258
+ except Exception as e:
259
+ raise ExpressionError(f"Expression evaluation failed: {e}") from e
260
+
261
+ def _eval_node(self, node: ast.AST) -> Any:
262
+ """Recursively evaluate AST node.
263
+
264
+ Args:
265
+ node: AST node to evaluate
266
+
267
+ Returns:
268
+ Evaluated value
269
+ """
270
+ # Constant values (Python 3.8+)
271
+ if isinstance(node, ast.Constant):
272
+ return node.value
273
+
274
+ # Numeric constants (Python 3.7)
275
+ if isinstance(node, ast.Num):
276
+ return node.n
277
+
278
+ # String constants (Python 3.7)
279
+ if isinstance(node, ast.Str):
280
+ return node.s
281
+
282
+ # Boolean/None constants (Python 3.7)
283
+ if isinstance(node, ast.NameConstant):
284
+ return node.value
285
+
286
+ # Variable access
287
+ if isinstance(node, ast.Name):
288
+ return self._get_variable(node.id)
289
+
290
+ # Binary operations
291
+ if isinstance(node, ast.BinOp):
292
+ left = self._eval_node(node.left)
293
+ right = self._eval_node(node.right)
294
+ op_func = BINARY_OPS.get(type(node.op))
295
+ if op_func is None:
296
+ raise ExpressionError(f"Unsupported binary operator: {type(node.op).__name__}")
297
+ return op_func(left, right)
298
+
299
+ # Unary operations
300
+ if isinstance(node, ast.UnaryOp):
301
+ operand = self._eval_node(node.operand)
302
+ op_func = UNARY_OPS.get(type(node.op))
303
+ if op_func is None:
304
+ raise ExpressionError(f"Unsupported unary operator: {type(node.op).__name__}")
305
+ return op_func(operand)
306
+
307
+ # Comparison operations
308
+ if isinstance(node, ast.Compare):
309
+ return self._eval_compare(node)
310
+
311
+ # Boolean operations (and, or)
312
+ if isinstance(node, ast.BoolOp):
313
+ return self._eval_bool_op(node)
314
+
315
+ # Function calls
316
+ if isinstance(node, ast.Call):
317
+ return self._eval_call(node)
318
+
319
+ # List literals
320
+ if isinstance(node, ast.List):
321
+ return [self._eval_node(elem) for elem in node.elts]
322
+
323
+ # Tuple literals
324
+ if isinstance(node, ast.Tuple):
325
+ return tuple(self._eval_node(elem) for elem in node.elts)
326
+
327
+ # Subscript (indexing)
328
+ if isinstance(node, ast.Subscript):
329
+ value = self._eval_node(node.value)
330
+ # Handle different Python versions
331
+ if isinstance(node.slice, ast.Index):
332
+ index = self._eval_node(node.slice.value)
333
+ else:
334
+ index = self._eval_node(node.slice)
335
+ return value[index]
336
+
337
+ # Attribute access
338
+ if isinstance(node, ast.Attribute):
339
+ value = self._eval_node(node.value)
340
+ if isinstance(value, dict):
341
+ return value.get(node.attr)
342
+ return getattr(value, node.attr, None)
343
+
344
+ # Ternary expression
345
+ if isinstance(node, ast.IfExp):
346
+ condition = self._eval_node(node.test)
347
+ if condition:
348
+ return self._eval_node(node.body)
349
+ else:
350
+ return self._eval_node(node.orelse)
351
+
352
+ raise ExpressionError(f"Cannot evaluate node type: {type(node).__name__}")
353
+
354
+ def _get_variable(self, name: str) -> Any:
355
+ """Get variable value from context.
356
+
357
+ Args:
358
+ name: Variable name (possibly prefixed with __var_)
359
+
360
+ Returns:
361
+ Variable value
362
+ """
363
+ # Check for our renamed variables
364
+ if name.startswith('__var_'):
365
+ var_name = name[6:] # Remove __var_ prefix
366
+ if var_name in self.context:
367
+ return self.context[var_name]
368
+ raise ExpressionError(f"Unknown variable: ${var_name}")
369
+
370
+ # Check built-in functions (used as names)
371
+ if name in BUILTIN_FUNCTIONS:
372
+ return BUILTIN_FUNCTIONS[name]
373
+
374
+ # Check boolean constants
375
+ if name == 'True':
376
+ return True
377
+ if name == 'False':
378
+ return False
379
+ if name == 'None':
380
+ return None
381
+
382
+ # Check context directly
383
+ if name in self.context:
384
+ return self.context[name]
385
+
386
+ raise ExpressionError(f"Unknown variable: {name}")
387
+
388
+ def _eval_compare(self, node: ast.Compare) -> bool:
389
+ """Evaluate comparison expression.
390
+
391
+ Handles chained comparisons like: a < b < c
392
+
393
+ Args:
394
+ node: Compare AST node
395
+
396
+ Returns:
397
+ Boolean result
398
+ """
399
+ left = self._eval_node(node.left)
400
+
401
+ for op, comparator in zip(node.ops, node.comparators):
402
+ right = self._eval_node(comparator)
403
+ op_func = COMPARE_OPS.get(type(op))
404
+ if op_func is None:
405
+ raise ExpressionError(f"Unsupported comparison operator: {type(op).__name__}")
406
+
407
+ if not op_func(left, right):
408
+ return False
409
+ left = right
410
+
411
+ return True
412
+
413
+ def _eval_bool_op(self, node: ast.BoolOp) -> bool:
414
+ """Evaluate boolean operation (and/or).
415
+
416
+ Implements short-circuit evaluation.
417
+
418
+ Args:
419
+ node: BoolOp AST node
420
+
421
+ Returns:
422
+ Boolean result
423
+ """
424
+ if isinstance(node.op, ast.And):
425
+ for value in node.values:
426
+ if not self._eval_node(value):
427
+ return False
428
+ return True
429
+ elif isinstance(node.op, ast.Or):
430
+ for value in node.values:
431
+ if self._eval_node(value):
432
+ return True
433
+ return False
434
+ else:
435
+ raise ExpressionError(f"Unsupported boolean operator: {type(node.op).__name__}")
436
+
437
+ def _eval_call(self, node: ast.Call) -> Any:
438
+ """Evaluate function call.
439
+
440
+ Args:
441
+ node: Call AST node
442
+
443
+ Returns:
444
+ Function result
445
+ """
446
+ # Get function name
447
+ if isinstance(node.func, ast.Name):
448
+ func_name = node.func.id
449
+ elif isinstance(node.func, ast.Attribute):
450
+ # Method calls like str.upper() - not supported yet
451
+ raise ExpressionError("Method calls are not supported in expressions")
452
+ else:
453
+ raise ExpressionError(f"Unsupported function call type: {type(node.func).__name__}")
454
+
455
+ # Check if function is allowed
456
+ if func_name not in BUILTIN_FUNCTIONS:
457
+ raise ExpressionError(f"Unknown function: {func_name}")
458
+
459
+ # Evaluate arguments
460
+ args = [self._eval_node(arg) for arg in node.args]
461
+
462
+ # Call function
463
+ func = BUILTIN_FUNCTIONS[func_name]
464
+ return func(*args)
465
+
466
+ def _normalize(self, value: Any) -> float:
467
+ """Normalize value to 0-1 range.
468
+
469
+ Conversion rules:
470
+ - bool: True -> 1.0, False -> 0.0
471
+ - int/float: Clamp to [0, 1]
472
+ - str: non-empty -> 1.0, empty -> 0.0
473
+ - list/tuple: non-empty -> 1.0, empty -> 0.0
474
+ - None: 0.0
475
+ - Other: 0.0
476
+
477
+ Args:
478
+ value: Value to normalize
479
+
480
+ Returns:
481
+ Float between 0.0 and 1.0
482
+ """
483
+ if isinstance(value, bool):
484
+ return 1.0 if value else 0.0
485
+
486
+ if isinstance(value, (int, float)):
487
+ return max(0.0, min(1.0, float(value)))
488
+
489
+ if isinstance(value, str):
490
+ return 1.0 if len(value) > 0 else 0.0
491
+
492
+ if isinstance(value, (list, tuple)):
493
+ return 1.0 if len(value) > 0 else 0.0
494
+
495
+ if value is None:
496
+ return 0.0
497
+
498
+ # Default: try to convert to bool
499
+ return 1.0 if bool(value) else 0.0