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.
- DolphinLanguageSDK/__init__.py +58 -0
- dolphin/__init__.py +62 -0
- dolphin/cli/__init__.py +20 -0
- dolphin/cli/args/__init__.py +9 -0
- dolphin/cli/args/parser.py +567 -0
- dolphin/cli/builtin_agents/__init__.py +22 -0
- dolphin/cli/commands/__init__.py +4 -0
- dolphin/cli/interrupt/__init__.py +8 -0
- dolphin/cli/interrupt/handler.py +205 -0
- dolphin/cli/interrupt/keyboard.py +82 -0
- dolphin/cli/main.py +49 -0
- dolphin/cli/multimodal/__init__.py +34 -0
- dolphin/cli/multimodal/clipboard.py +327 -0
- dolphin/cli/multimodal/handler.py +249 -0
- dolphin/cli/multimodal/image_processor.py +214 -0
- dolphin/cli/multimodal/input_parser.py +149 -0
- dolphin/cli/runner/__init__.py +8 -0
- dolphin/cli/runner/runner.py +989 -0
- dolphin/cli/ui/__init__.py +10 -0
- dolphin/cli/ui/console.py +2795 -0
- dolphin/cli/ui/input.py +340 -0
- dolphin/cli/ui/layout.py +425 -0
- dolphin/cli/ui/stream_renderer.py +302 -0
- dolphin/cli/utils/__init__.py +8 -0
- dolphin/cli/utils/helpers.py +135 -0
- dolphin/cli/utils/version.py +49 -0
- dolphin/core/__init__.py +107 -0
- dolphin/core/agent/__init__.py +10 -0
- dolphin/core/agent/agent_state.py +69 -0
- dolphin/core/agent/base_agent.py +970 -0
- dolphin/core/code_block/__init__.py +0 -0
- dolphin/core/code_block/agent_init_block.py +0 -0
- dolphin/core/code_block/assign_block.py +98 -0
- dolphin/core/code_block/basic_code_block.py +1865 -0
- dolphin/core/code_block/explore_block.py +1327 -0
- dolphin/core/code_block/explore_block_v2.py +712 -0
- dolphin/core/code_block/explore_strategy.py +672 -0
- dolphin/core/code_block/judge_block.py +220 -0
- dolphin/core/code_block/prompt_block.py +32 -0
- dolphin/core/code_block/skill_call_deduplicator.py +291 -0
- dolphin/core/code_block/tool_block.py +129 -0
- dolphin/core/common/__init__.py +17 -0
- dolphin/core/common/constants.py +176 -0
- dolphin/core/common/enums.py +1173 -0
- dolphin/core/common/exceptions.py +133 -0
- dolphin/core/common/multimodal.py +539 -0
- dolphin/core/common/object_type.py +165 -0
- dolphin/core/common/output_format.py +432 -0
- dolphin/core/common/types.py +36 -0
- dolphin/core/config/__init__.py +16 -0
- dolphin/core/config/global_config.py +1289 -0
- dolphin/core/config/ontology_config.py +133 -0
- dolphin/core/context/__init__.py +12 -0
- dolphin/core/context/context.py +1580 -0
- dolphin/core/context/context_manager.py +161 -0
- dolphin/core/context/var_output.py +82 -0
- dolphin/core/context/variable_pool.py +356 -0
- dolphin/core/context_engineer/__init__.py +41 -0
- dolphin/core/context_engineer/config/__init__.py +5 -0
- dolphin/core/context_engineer/config/settings.py +402 -0
- dolphin/core/context_engineer/core/__init__.py +7 -0
- dolphin/core/context_engineer/core/budget_manager.py +327 -0
- dolphin/core/context_engineer/core/context_assembler.py +583 -0
- dolphin/core/context_engineer/core/context_manager.py +637 -0
- dolphin/core/context_engineer/core/tokenizer_service.py +260 -0
- dolphin/core/context_engineer/example/incremental_example.py +267 -0
- dolphin/core/context_engineer/example/traditional_example.py +334 -0
- dolphin/core/context_engineer/services/__init__.py +5 -0
- dolphin/core/context_engineer/services/compressor.py +399 -0
- dolphin/core/context_engineer/utils/__init__.py +6 -0
- dolphin/core/context_engineer/utils/context_utils.py +441 -0
- dolphin/core/context_engineer/utils/message_formatter.py +270 -0
- dolphin/core/context_engineer/utils/token_utils.py +139 -0
- dolphin/core/coroutine/__init__.py +15 -0
- dolphin/core/coroutine/context_snapshot.py +154 -0
- dolphin/core/coroutine/context_snapshot_profile.py +922 -0
- dolphin/core/coroutine/context_snapshot_store.py +268 -0
- dolphin/core/coroutine/execution_frame.py +145 -0
- dolphin/core/coroutine/execution_state_registry.py +161 -0
- dolphin/core/coroutine/resume_handle.py +101 -0
- dolphin/core/coroutine/step_result.py +101 -0
- dolphin/core/executor/__init__.py +18 -0
- dolphin/core/executor/debug_controller.py +630 -0
- dolphin/core/executor/dolphin_executor.py +1063 -0
- dolphin/core/executor/executor.py +624 -0
- dolphin/core/flags/__init__.py +27 -0
- dolphin/core/flags/definitions.py +49 -0
- dolphin/core/flags/manager.py +113 -0
- dolphin/core/hook/__init__.py +95 -0
- dolphin/core/hook/expression_evaluator.py +499 -0
- dolphin/core/hook/hook_dispatcher.py +380 -0
- dolphin/core/hook/hook_types.py +248 -0
- dolphin/core/hook/isolated_variable_pool.py +284 -0
- dolphin/core/interfaces.py +53 -0
- dolphin/core/llm/__init__.py +0 -0
- dolphin/core/llm/llm.py +495 -0
- dolphin/core/llm/llm_call.py +100 -0
- dolphin/core/llm/llm_client.py +1285 -0
- dolphin/core/llm/message_sanitizer.py +120 -0
- dolphin/core/logging/__init__.py +20 -0
- dolphin/core/logging/logger.py +526 -0
- dolphin/core/message/__init__.py +8 -0
- dolphin/core/message/compressor.py +749 -0
- dolphin/core/parser/__init__.py +8 -0
- dolphin/core/parser/parser.py +405 -0
- dolphin/core/runtime/__init__.py +10 -0
- dolphin/core/runtime/runtime_graph.py +926 -0
- dolphin/core/runtime/runtime_instance.py +446 -0
- dolphin/core/skill/__init__.py +14 -0
- dolphin/core/skill/context_retention.py +157 -0
- dolphin/core/skill/skill_function.py +686 -0
- dolphin/core/skill/skill_matcher.py +282 -0
- dolphin/core/skill/skillkit.py +700 -0
- dolphin/core/skill/skillset.py +72 -0
- dolphin/core/trajectory/__init__.py +10 -0
- dolphin/core/trajectory/recorder.py +189 -0
- dolphin/core/trajectory/trajectory.py +522 -0
- dolphin/core/utils/__init__.py +9 -0
- dolphin/core/utils/cache_kv.py +212 -0
- dolphin/core/utils/tools.py +340 -0
- dolphin/lib/__init__.py +93 -0
- dolphin/lib/debug/__init__.py +8 -0
- dolphin/lib/debug/visualizer.py +409 -0
- dolphin/lib/memory/__init__.py +28 -0
- dolphin/lib/memory/async_processor.py +220 -0
- dolphin/lib/memory/llm_calls.py +195 -0
- dolphin/lib/memory/manager.py +78 -0
- dolphin/lib/memory/sandbox.py +46 -0
- dolphin/lib/memory/storage.py +245 -0
- dolphin/lib/memory/utils.py +51 -0
- dolphin/lib/ontology/__init__.py +12 -0
- dolphin/lib/ontology/basic/__init__.py +0 -0
- dolphin/lib/ontology/basic/base.py +102 -0
- dolphin/lib/ontology/basic/concept.py +130 -0
- dolphin/lib/ontology/basic/object.py +11 -0
- dolphin/lib/ontology/basic/relation.py +63 -0
- dolphin/lib/ontology/datasource/__init__.py +27 -0
- dolphin/lib/ontology/datasource/datasource.py +66 -0
- dolphin/lib/ontology/datasource/oracle_datasource.py +338 -0
- dolphin/lib/ontology/datasource/sql.py +845 -0
- dolphin/lib/ontology/mapping.py +177 -0
- dolphin/lib/ontology/ontology.py +733 -0
- dolphin/lib/ontology/ontology_context.py +16 -0
- dolphin/lib/ontology/ontology_manager.py +107 -0
- dolphin/lib/skill_results/__init__.py +31 -0
- dolphin/lib/skill_results/cache_backend.py +559 -0
- dolphin/lib/skill_results/result_processor.py +181 -0
- dolphin/lib/skill_results/result_reference.py +179 -0
- dolphin/lib/skill_results/skillkit_hook.py +324 -0
- dolphin/lib/skill_results/strategies.py +328 -0
- dolphin/lib/skill_results/strategy_registry.py +150 -0
- dolphin/lib/skillkits/__init__.py +44 -0
- dolphin/lib/skillkits/agent_skillkit.py +155 -0
- dolphin/lib/skillkits/cognitive_skillkit.py +82 -0
- dolphin/lib/skillkits/env_skillkit.py +250 -0
- dolphin/lib/skillkits/mcp_adapter.py +616 -0
- dolphin/lib/skillkits/mcp_skillkit.py +771 -0
- dolphin/lib/skillkits/memory_skillkit.py +650 -0
- dolphin/lib/skillkits/noop_skillkit.py +31 -0
- dolphin/lib/skillkits/ontology_skillkit.py +89 -0
- dolphin/lib/skillkits/plan_act_skillkit.py +452 -0
- dolphin/lib/skillkits/resource/__init__.py +52 -0
- dolphin/lib/skillkits/resource/models/__init__.py +6 -0
- dolphin/lib/skillkits/resource/models/skill_config.py +109 -0
- dolphin/lib/skillkits/resource/models/skill_meta.py +127 -0
- dolphin/lib/skillkits/resource/resource_skillkit.py +393 -0
- dolphin/lib/skillkits/resource/skill_cache.py +215 -0
- dolphin/lib/skillkits/resource/skill_loader.py +395 -0
- dolphin/lib/skillkits/resource/skill_validator.py +406 -0
- dolphin/lib/skillkits/resource_skillkit.py +11 -0
- dolphin/lib/skillkits/search_skillkit.py +163 -0
- dolphin/lib/skillkits/sql_skillkit.py +274 -0
- dolphin/lib/skillkits/system_skillkit.py +509 -0
- dolphin/lib/skillkits/vm_skillkit.py +65 -0
- dolphin/lib/utils/__init__.py +9 -0
- dolphin/lib/utils/data_process.py +207 -0
- dolphin/lib/utils/handle_progress.py +178 -0
- dolphin/lib/utils/security.py +139 -0
- dolphin/lib/utils/text_retrieval.py +462 -0
- dolphin/lib/vm/__init__.py +11 -0
- dolphin/lib/vm/env_executor.py +895 -0
- dolphin/lib/vm/python_session_manager.py +453 -0
- dolphin/lib/vm/vm.py +610 -0
- dolphin/sdk/__init__.py +60 -0
- dolphin/sdk/agent/__init__.py +12 -0
- dolphin/sdk/agent/agent_factory.py +236 -0
- dolphin/sdk/agent/dolphin_agent.py +1106 -0
- dolphin/sdk/api/__init__.py +4 -0
- dolphin/sdk/runtime/__init__.py +8 -0
- dolphin/sdk/runtime/env.py +363 -0
- dolphin/sdk/skill/__init__.py +10 -0
- dolphin/sdk/skill/global_skills.py +706 -0
- dolphin/sdk/skill/traditional_toolkit.py +260 -0
- kweaver_dolphin-0.1.0.dist-info/METADATA +521 -0
- kweaver_dolphin-0.1.0.dist-info/RECORD +199 -0
- kweaver_dolphin-0.1.0.dist-info/WHEEL +5 -0
- kweaver_dolphin-0.1.0.dist-info/entry_points.txt +27 -0
- kweaver_dolphin-0.1.0.dist-info/licenses/LICENSE.txt +201 -0
- 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
|