algomath-extract 1.0.0
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.
- package/README.md +260 -0
- package/bin/algo-extract.js +143 -0
- package/bin/algo-generate.js +102 -0
- package/bin/algo-help.js +136 -0
- package/bin/algo-list.js +56 -0
- package/bin/algo-run.js +141 -0
- package/bin/algo-status.js +88 -0
- package/bin/algo-verify.js +189 -0
- package/bin/install.js +349 -0
- package/package.json +57 -0
- package/requirements.txt +20 -0
- package/src/__pycache__/intent.cpython-313.pyc +0 -0
- package/src/cli/__pycache__/commands.cpython-313.pyc +0 -0
- package/src/cli/cli_entry.py +106 -0
- package/src/cli/commands.py +339 -0
- package/src/execution/__init__.py +74 -0
- package/src/execution/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/display.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/errors.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/executor.cpython-313.pyc +0 -0
- package/src/execution/__pycache__/sandbox.cpython-313.pyc +0 -0
- package/src/execution/display.py +261 -0
- package/src/execution/errors.py +158 -0
- package/src/execution/executor.py +253 -0
- package/src/execution/sandbox.py +333 -0
- package/src/extraction/__init__.py +102 -0
- package/src/extraction/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/boundaries.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/errors.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/llm_extraction.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/notation.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/parser.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/pdf_processor.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/prompts.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/review.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/schema.cpython-313.pyc +0 -0
- package/src/extraction/__pycache__/validation.cpython-313.pyc +0 -0
- package/src/extraction/boundaries.py +281 -0
- package/src/extraction/errors.py +156 -0
- package/src/extraction/llm_extraction.py +225 -0
- package/src/extraction/notation.py +240 -0
- package/src/extraction/parser.py +402 -0
- package/src/extraction/pdf_processor.py +281 -0
- package/src/extraction/prompts.py +90 -0
- package/src/extraction/review.py +298 -0
- package/src/extraction/schema.py +173 -0
- package/src/extraction/validation.py +202 -0
- package/src/generation/__init__.py +79 -0
- package/src/generation/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/code_generator.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/errors.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/hybrid.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/llm_generator.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/persistence.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/prompts.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/review.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/templates.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/types.cpython-313.pyc +0 -0
- package/src/generation/__pycache__/validation.cpython-313.pyc +0 -0
- package/src/generation/code_generator.py +375 -0
- package/src/generation/errors.py +84 -0
- package/src/generation/hybrid.py +210 -0
- package/src/generation/llm_generator.py +223 -0
- package/src/generation/persistence.py +221 -0
- package/src/generation/prompts.py +202 -0
- package/src/generation/review.py +254 -0
- package/src/generation/templates.py +208 -0
- package/src/generation/types.py +196 -0
- package/src/generation/validation.py +278 -0
- package/src/intent.py +323 -0
- package/src/verification/__init__.py +63 -0
- package/src/verification/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/checker.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/comparison.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/explainer.cpython-313.pyc +0 -0
- package/src/verification/__pycache__/static_analysis.cpython-313.pyc +0 -0
- package/src/verification/checker.py +220 -0
- package/src/verification/comparison.py +492 -0
- package/src/verification/explainer.py +414 -0
- package/src/verification/static_analysis.py +540 -0
- package/src/workflows/__init__.py +21 -0
- package/src/workflows/__pycache__/__init__.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/extract.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/generate.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/run.cpython-313.pyc +0 -0
- package/src/workflows/__pycache__/verify.cpython-313.pyc +0 -0
- package/src/workflows/extract.py +181 -0
- package/src/workflows/generate.py +155 -0
- package/src/workflows/run.py +187 -0
- package/src/workflows/verify.py +334 -0
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"""Static analysis and edge case detection module for AlgoMath.
|
|
2
|
+
|
|
3
|
+
Provides detection of potential edge cases through static code analysis
|
|
4
|
+
and execution-based testing.
|
|
5
|
+
|
|
6
|
+
Per D-13 through D-16: Pattern detection, execution testing, severity levels,
|
|
7
|
+
and comprehensive edge case coverage.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Dict, List, Optional, Set
|
|
13
|
+
import re
|
|
14
|
+
import sys
|
|
15
|
+
|
|
16
|
+
from src.extraction.schema import Algorithm, Step, StepType
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EdgeCaseSeverity(Enum):
|
|
20
|
+
"""Severity levels for edge cases."""
|
|
21
|
+
INFO = "info"
|
|
22
|
+
WARNING = "warning"
|
|
23
|
+
CRITICAL = "critical"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class EdgeCase:
|
|
28
|
+
"""Represents a detected edge case.
|
|
29
|
+
|
|
30
|
+
Per D-15: Includes severity, description, location, suggestion.
|
|
31
|
+
Per D-16: Covers empty, single, max, negative, zero values.
|
|
32
|
+
Per D-141: Suggests specific test values.
|
|
33
|
+
"""
|
|
34
|
+
type: str
|
|
35
|
+
severity: EdgeCaseSeverity
|
|
36
|
+
description: str
|
|
37
|
+
location: Optional[str] = None
|
|
38
|
+
suggestion: str = ""
|
|
39
|
+
test_input: Optional[Dict[str, Any]] = None
|
|
40
|
+
|
|
41
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
42
|
+
"""Convert to dictionary representation."""
|
|
43
|
+
return {
|
|
44
|
+
"type": self.type,
|
|
45
|
+
"severity": self.severity.value,
|
|
46
|
+
"description": self.description,
|
|
47
|
+
"location": self.location,
|
|
48
|
+
"suggestion": self.suggestion,
|
|
49
|
+
"test_input": self.test_input,
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class EdgeCaseDetector:
|
|
54
|
+
"""Detects edge cases through static and execution analysis.
|
|
55
|
+
|
|
56
|
+
Per D-13: Static analysis for patterns.
|
|
57
|
+
Per D-14: Execution-based testing.
|
|
58
|
+
Per D-15: Report detected and potential edge cases.
|
|
59
|
+
Per D-16: Test empty, single, max, negative, zero.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, code: str, algorithm: Optional[Algorithm] = None):
|
|
63
|
+
"""Initialize detector with code and optional algorithm.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
code: Python code to analyze
|
|
67
|
+
algorithm: Optional algorithm structure for context
|
|
68
|
+
"""
|
|
69
|
+
self.code = code
|
|
70
|
+
self.algorithm = algorithm
|
|
71
|
+
self._detected: List[EdgeCase] = []
|
|
72
|
+
|
|
73
|
+
def analyze_static(self) -> List[EdgeCase]:
|
|
74
|
+
"""Perform static analysis to detect edge cases.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
List of detected edge cases
|
|
78
|
+
"""
|
|
79
|
+
edge_cases = []
|
|
80
|
+
|
|
81
|
+
# Run all static analysis checks
|
|
82
|
+
edge_cases.extend(self._check_empty_loops())
|
|
83
|
+
edge_cases.extend(self._check_division_by_zero())
|
|
84
|
+
edge_cases.extend(self._check_recursion_depth())
|
|
85
|
+
edge_cases.extend(self._check_uninitialized_variables())
|
|
86
|
+
edge_cases.extend(self._check_off_by_one())
|
|
87
|
+
edge_cases.extend(self._check_infinite_loops())
|
|
88
|
+
edge_cases.extend(self._check_empty_collections())
|
|
89
|
+
|
|
90
|
+
self._detected.extend(edge_cases)
|
|
91
|
+
return edge_cases
|
|
92
|
+
|
|
93
|
+
def analyze_execution(self, sandbox) -> List[EdgeCase]:
|
|
94
|
+
"""Perform execution-based edge case detection.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
sandbox: SandboxExecutor instance for running code
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
List of detected edge cases from execution
|
|
101
|
+
"""
|
|
102
|
+
edge_cases = []
|
|
103
|
+
|
|
104
|
+
# Run execution-based tests
|
|
105
|
+
edge_cases.extend(self._test_empty_input(sandbox))
|
|
106
|
+
edge_cases.extend(self._test_single_element(sandbox))
|
|
107
|
+
edge_cases.extend(self._test_boundary_values(sandbox))
|
|
108
|
+
edge_cases.extend(self._test_negative_values(sandbox))
|
|
109
|
+
edge_cases.extend(self._test_zero_values(sandbox))
|
|
110
|
+
|
|
111
|
+
self._detected.extend(edge_cases)
|
|
112
|
+
return edge_cases
|
|
113
|
+
|
|
114
|
+
def detect_edge_cases(self) -> List[EdgeCase]:
|
|
115
|
+
"""Run all detection methods and return combined results.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
List of all detected edge cases
|
|
119
|
+
"""
|
|
120
|
+
# Run static analysis
|
|
121
|
+
static_cases = self.analyze_static()
|
|
122
|
+
|
|
123
|
+
# Note: Execution analysis requires a sandbox instance
|
|
124
|
+
# Return static cases for now
|
|
125
|
+
return static_cases
|
|
126
|
+
|
|
127
|
+
def _check_empty_loops(self) -> List[EdgeCase]:
|
|
128
|
+
"""Detect loops with no body or trivial body."""
|
|
129
|
+
edge_cases = []
|
|
130
|
+
|
|
131
|
+
# Pattern: for/while loop with only pass/continue/break
|
|
132
|
+
patterns = [
|
|
133
|
+
(r'for\s+\w+\s+in\s+[^:]+:\s*\n\s*(?:pass|continue|break|\.\.\.)\s*\n',
|
|
134
|
+
"Loop with empty body"),
|
|
135
|
+
(r'while\s+[^:]+:\s*\n\s*(?:pass|continue|break|\.\.\.)\s*\n',
|
|
136
|
+
"While loop with empty body"),
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
for pattern, desc in patterns:
|
|
140
|
+
matches = list(re.finditer(pattern, self.code, re.MULTILINE))
|
|
141
|
+
for i, match in enumerate(matches):
|
|
142
|
+
line_num = self._get_line_number(match.start())
|
|
143
|
+
edge_cases.append(EdgeCase(
|
|
144
|
+
type="empty_loop",
|
|
145
|
+
severity=EdgeCaseSeverity.WARNING,
|
|
146
|
+
description=desc,
|
|
147
|
+
location=f"line {line_num}",
|
|
148
|
+
suggestion="Ensure loop has meaningful work or remove it"
|
|
149
|
+
))
|
|
150
|
+
|
|
151
|
+
return edge_cases
|
|
152
|
+
|
|
153
|
+
def _check_division_by_zero(self) -> List[EdgeCase]:
|
|
154
|
+
"""Detect potential division by zero."""
|
|
155
|
+
edge_cases = []
|
|
156
|
+
|
|
157
|
+
# Pattern: division without zero check
|
|
158
|
+
div_pattern = r'(?:/|//|%|\bdiv\b)\s*\(?\s*\w+'
|
|
159
|
+
matches = list(re.finditer(div_pattern, self.code))
|
|
160
|
+
|
|
161
|
+
for match in matches:
|
|
162
|
+
line_num = self._get_line_number(match.start())
|
|
163
|
+
context = self._get_line_context(line_num)
|
|
164
|
+
|
|
165
|
+
# Check if there's a zero guard
|
|
166
|
+
if not self._has_zero_guard(context):
|
|
167
|
+
edge_cases.append(EdgeCase(
|
|
168
|
+
type="division_by_zero",
|
|
169
|
+
severity=EdgeCaseSeverity.CRITICAL,
|
|
170
|
+
description="Potential division by zero",
|
|
171
|
+
location=f"line {line_num}",
|
|
172
|
+
suggestion="Add check: if divisor != 0: before dividing",
|
|
173
|
+
test_input={"divisor": 0}
|
|
174
|
+
))
|
|
175
|
+
|
|
176
|
+
# Check for modulo operations
|
|
177
|
+
mod_pattern = r'\w+\s*%\s*\w+'
|
|
178
|
+
mod_matches = list(re.finditer(mod_pattern, self.code))
|
|
179
|
+
|
|
180
|
+
for match in mod_matches:
|
|
181
|
+
line_num = self._get_line_number(match.start())
|
|
182
|
+
context = self._get_line_context(line_num)
|
|
183
|
+
|
|
184
|
+
if not self._has_zero_guard(context):
|
|
185
|
+
edge_cases.append(EdgeCase(
|
|
186
|
+
type="modulo_by_zero",
|
|
187
|
+
severity=EdgeCaseSeverity.CRITICAL,
|
|
188
|
+
description="Potential modulo by zero",
|
|
189
|
+
location=f"line {line_num}",
|
|
190
|
+
suggestion="Add check: if divisor != 0: before modulo",
|
|
191
|
+
test_input={"divisor": 0}
|
|
192
|
+
))
|
|
193
|
+
|
|
194
|
+
return edge_cases
|
|
195
|
+
|
|
196
|
+
def _check_recursion_depth(self) -> List[EdgeCase]:
|
|
197
|
+
"""Detect recursive functions and depth concerns."""
|
|
198
|
+
edge_cases = []
|
|
199
|
+
|
|
200
|
+
# Pattern: function that calls itself
|
|
201
|
+
func_pattern = r'def\s+(\w+)\s*\([^)]*\):'
|
|
202
|
+
func_matches = list(re.finditer(func_pattern, self.code))
|
|
203
|
+
|
|
204
|
+
for match in func_matches:
|
|
205
|
+
func_name = match.group(1)
|
|
206
|
+
func_start = match.end()
|
|
207
|
+
|
|
208
|
+
# Look for recursive call within function scope
|
|
209
|
+
func_body = self._get_function_body(func_start)
|
|
210
|
+
if func_name in func_body:
|
|
211
|
+
line_num = self._get_line_number(match.start())
|
|
212
|
+
edge_cases.append(EdgeCase(
|
|
213
|
+
type="recursion_depth",
|
|
214
|
+
severity=EdgeCaseSeverity.WARNING,
|
|
215
|
+
description=f"Recursive function '{func_name}' may hit recursion limit",
|
|
216
|
+
location=f"line {line_num}",
|
|
217
|
+
suggestion="Consider iterative version or add recursion depth limit"
|
|
218
|
+
))
|
|
219
|
+
|
|
220
|
+
return edge_cases
|
|
221
|
+
|
|
222
|
+
def _check_uninitialized_variables(self) -> List[EdgeCase]:
|
|
223
|
+
"""Detect use of potentially uninitialized variables."""
|
|
224
|
+
edge_cases = []
|
|
225
|
+
|
|
226
|
+
# Simple check: variable used before assignment in a branch
|
|
227
|
+
# This is a heuristic - full analysis requires AST
|
|
228
|
+
var_pattern = r'(?:return|if|while|for)\s+\w+'
|
|
229
|
+
matches = list(re.finditer(var_pattern, self.code))
|
|
230
|
+
|
|
231
|
+
# Note: Full uninitialized variable detection requires AST parsing
|
|
232
|
+
# This is a simplified version
|
|
233
|
+
|
|
234
|
+
return edge_cases
|
|
235
|
+
|
|
236
|
+
def _check_off_by_one(self) -> List[EdgeCase]:
|
|
237
|
+
"""Detect potential off-by-one errors."""
|
|
238
|
+
edge_cases = []
|
|
239
|
+
|
|
240
|
+
# Pattern: indexing with len() without -1
|
|
241
|
+
patterns = [
|
|
242
|
+
(r'\[\s*len\s*\(\s*\w+\s*\)\s*\]', "Array index at len() may be out of bounds"),
|
|
243
|
+
(r'range\s*\(\s*len\s*\(\s*\w+\s*\)\s*\+\s*1\s*\)', "Extra iteration in range"),
|
|
244
|
+
]
|
|
245
|
+
|
|
246
|
+
for pattern, desc in patterns:
|
|
247
|
+
matches = list(re.finditer(pattern, self.code))
|
|
248
|
+
for match in matches:
|
|
249
|
+
line_num = self._get_line_number(match.start())
|
|
250
|
+
edge_cases.append(EdgeCase(
|
|
251
|
+
type="off_by_one",
|
|
252
|
+
severity=EdgeCaseSeverity.WARNING,
|
|
253
|
+
description=desc,
|
|
254
|
+
location=f"line {line_num}",
|
|
255
|
+
suggestion="Verify indices and ranges are correct"
|
|
256
|
+
))
|
|
257
|
+
|
|
258
|
+
return edge_cases
|
|
259
|
+
|
|
260
|
+
def _check_infinite_loops(self) -> List[EdgeCase]:
|
|
261
|
+
"""Detect potential infinite loops."""
|
|
262
|
+
edge_cases = []
|
|
263
|
+
|
|
264
|
+
# Pattern: while True without break
|
|
265
|
+
loop_pattern = r'while\s+True\s*:'
|
|
266
|
+
matches = list(re.finditer(loop_pattern, self.code))
|
|
267
|
+
|
|
268
|
+
for match in matches:
|
|
269
|
+
# Check if there's a break in the loop body
|
|
270
|
+
body = self._get_loop_body(match.end())
|
|
271
|
+
if 'break' not in body:
|
|
272
|
+
line_num = self._get_line_number(match.start())
|
|
273
|
+
edge_cases.append(EdgeCase(
|
|
274
|
+
type="infinite_loop",
|
|
275
|
+
severity=EdgeCaseSeverity.CRITICAL,
|
|
276
|
+
description="Potential infinite loop (while True without break)",
|
|
277
|
+
location=f"line {line_num}",
|
|
278
|
+
suggestion="Add a break condition or use a for loop"
|
|
279
|
+
))
|
|
280
|
+
|
|
281
|
+
return edge_cases
|
|
282
|
+
|
|
283
|
+
def _check_empty_collections(self) -> List[EdgeCase]:
|
|
284
|
+
"""Detect operations on empty collections."""
|
|
285
|
+
edge_cases = []
|
|
286
|
+
|
|
287
|
+
# Pattern: direct access to list elements without check
|
|
288
|
+
patterns = [
|
|
289
|
+
(r'\[\s*0\s*\]', "First element access without empty check"),
|
|
290
|
+
(r'\.pop\s*\(\s*\)', "Pop operation without empty check"),
|
|
291
|
+
]
|
|
292
|
+
|
|
293
|
+
for pattern, desc in patterns:
|
|
294
|
+
if re.search(pattern, self.code):
|
|
295
|
+
edge_cases.append(EdgeCase(
|
|
296
|
+
type="empty_collection",
|
|
297
|
+
severity=EdgeCaseSeverity.WARNING,
|
|
298
|
+
description=desc,
|
|
299
|
+
suggestion="Add check: if len(collection) > 0: before accessing"
|
|
300
|
+
))
|
|
301
|
+
|
|
302
|
+
return edge_cases
|
|
303
|
+
|
|
304
|
+
def _test_empty_input(self, sandbox) -> List[EdgeCase]:
|
|
305
|
+
"""Test with empty inputs per D-16."""
|
|
306
|
+
edge_cases = []
|
|
307
|
+
|
|
308
|
+
if self.algorithm:
|
|
309
|
+
test_inputs = self._generate_test_inputs("empty_input", self.algorithm.inputs)
|
|
310
|
+
# Execution would happen here with sandbox
|
|
311
|
+
# For now, add potential edge case
|
|
312
|
+
if test_inputs:
|
|
313
|
+
edge_cases.append(EdgeCase(
|
|
314
|
+
type="empty_input",
|
|
315
|
+
severity=EdgeCaseSeverity.INFO,
|
|
316
|
+
description="Algorithm behavior with empty input",
|
|
317
|
+
suggestion="Try n=0, empty list, or empty string"
|
|
318
|
+
))
|
|
319
|
+
|
|
320
|
+
return edge_cases
|
|
321
|
+
|
|
322
|
+
def _test_single_element(self, sandbox) -> List[EdgeCase]:
|
|
323
|
+
"""Test with single element inputs per D-16."""
|
|
324
|
+
edge_cases = []
|
|
325
|
+
|
|
326
|
+
if self.algorithm:
|
|
327
|
+
test_inputs = self._generate_test_inputs("single_element", self.algorithm.inputs)
|
|
328
|
+
if test_inputs:
|
|
329
|
+
edge_cases.append(EdgeCase(
|
|
330
|
+
type="single_element",
|
|
331
|
+
severity=EdgeCaseSeverity.INFO,
|
|
332
|
+
description="Algorithm behavior with single element",
|
|
333
|
+
suggestion="Try n=1, single item list"
|
|
334
|
+
))
|
|
335
|
+
|
|
336
|
+
return edge_cases
|
|
337
|
+
|
|
338
|
+
def _test_boundary_values(self, sandbox) -> List[EdgeCase]:
|
|
339
|
+
"""Test with boundary values per D-16."""
|
|
340
|
+
edge_cases = []
|
|
341
|
+
|
|
342
|
+
edge_cases.append(EdgeCase(
|
|
343
|
+
type="max_value",
|
|
344
|
+
severity=EdgeCaseSeverity.WARNING,
|
|
345
|
+
description="Algorithm behavior with maximum values",
|
|
346
|
+
suggestion="Try sys.maxsize, float('inf'), very large numbers"
|
|
347
|
+
))
|
|
348
|
+
|
|
349
|
+
edge_cases.append(EdgeCase(
|
|
350
|
+
type="min_value",
|
|
351
|
+
severity=EdgeCaseSeverity.WARNING,
|
|
352
|
+
description="Algorithm behavior with minimum values",
|
|
353
|
+
suggestion="Try -sys.maxsize, float('-inf'), very small numbers"
|
|
354
|
+
))
|
|
355
|
+
|
|
356
|
+
return edge_cases
|
|
357
|
+
|
|
358
|
+
def _test_negative_values(self, sandbox) -> List[EdgeCase]:
|
|
359
|
+
"""Test with negative values per D-16."""
|
|
360
|
+
edge_cases = []
|
|
361
|
+
|
|
362
|
+
edge_cases.append(EdgeCase(
|
|
363
|
+
type="negative",
|
|
364
|
+
severity=EdgeCaseSeverity.INFO,
|
|
365
|
+
description="Algorithm behavior with negative inputs",
|
|
366
|
+
suggestion="Try -1, negative integers, negative floats"
|
|
367
|
+
))
|
|
368
|
+
|
|
369
|
+
return edge_cases
|
|
370
|
+
|
|
371
|
+
def _test_zero_values(self, sandbox) -> List[EdgeCase]:
|
|
372
|
+
"""Test with zero values per D-16."""
|
|
373
|
+
edge_cases = []
|
|
374
|
+
|
|
375
|
+
edge_cases.append(EdgeCase(
|
|
376
|
+
type="zero",
|
|
377
|
+
severity=EdgeCaseSeverity.INFO,
|
|
378
|
+
description="Algorithm behavior with zero input",
|
|
379
|
+
suggestion="Try n=0, empty values, zero floats"
|
|
380
|
+
))
|
|
381
|
+
|
|
382
|
+
return edge_cases
|
|
383
|
+
|
|
384
|
+
def _generate_test_inputs(
|
|
385
|
+
self,
|
|
386
|
+
edge_case_type: str,
|
|
387
|
+
algorithm_inputs: List[Dict]
|
|
388
|
+
) -> List[Dict]:
|
|
389
|
+
"""Generate test inputs for edge case type.
|
|
390
|
+
|
|
391
|
+
Per D-14, D-16: Generate varied test inputs.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
edge_case_type: Type of edge case
|
|
395
|
+
algorithm_inputs: Algorithm input definitions
|
|
396
|
+
|
|
397
|
+
Returns:
|
|
398
|
+
List of test input dictionaries
|
|
399
|
+
"""
|
|
400
|
+
test_inputs = []
|
|
401
|
+
|
|
402
|
+
if edge_case_type == "empty_input":
|
|
403
|
+
for input_def in algorithm_inputs:
|
|
404
|
+
name = input_def.get("name", "x")
|
|
405
|
+
input_type = input_def.get("type", "any")
|
|
406
|
+
|
|
407
|
+
if input_type == "list" or input_type == "array":
|
|
408
|
+
test_inputs.append({name: []})
|
|
409
|
+
elif input_type == "str":
|
|
410
|
+
test_inputs.append({name: ""})
|
|
411
|
+
elif input_type == "dict":
|
|
412
|
+
test_inputs.append({name: {}})
|
|
413
|
+
else:
|
|
414
|
+
test_inputs.append({name: 0})
|
|
415
|
+
|
|
416
|
+
elif edge_case_type == "single_element":
|
|
417
|
+
for input_def in algorithm_inputs:
|
|
418
|
+
name = input_def.get("name", "x")
|
|
419
|
+
input_type = input_def.get("type", "any")
|
|
420
|
+
|
|
421
|
+
if input_type == "list" or input_type == "array":
|
|
422
|
+
test_inputs.append({name: [1]})
|
|
423
|
+
elif input_type == "str":
|
|
424
|
+
test_inputs.append({name: "a"})
|
|
425
|
+
else:
|
|
426
|
+
test_inputs.append({name: 1})
|
|
427
|
+
|
|
428
|
+
elif edge_case_type == "max_value":
|
|
429
|
+
for input_def in algorithm_inputs:
|
|
430
|
+
name = input_def.get("name", "x")
|
|
431
|
+
input_type = input_def.get("type", "any")
|
|
432
|
+
|
|
433
|
+
if input_type == "int":
|
|
434
|
+
test_inputs.append({name: sys.maxsize})
|
|
435
|
+
elif input_type == "float":
|
|
436
|
+
test_inputs.append({name: float('inf')})
|
|
437
|
+
else:
|
|
438
|
+
test_inputs.append({name: 10**9})
|
|
439
|
+
|
|
440
|
+
elif edge_case_type == "negative":
|
|
441
|
+
for input_def in algorithm_inputs:
|
|
442
|
+
name = input_def.get("name", "x")
|
|
443
|
+
input_type = input_def.get("type", "any")
|
|
444
|
+
|
|
445
|
+
if input_type in ("int", "float"):
|
|
446
|
+
test_inputs.append({name: -1})
|
|
447
|
+
test_inputs.append({name: -0.001})
|
|
448
|
+
|
|
449
|
+
elif edge_case_type == "zero":
|
|
450
|
+
for input_def in algorithm_inputs:
|
|
451
|
+
name = input_def.get("name", "x")
|
|
452
|
+
input_type = input_def.get("type", "any")
|
|
453
|
+
|
|
454
|
+
if input_type in ("int", "float"):
|
|
455
|
+
test_inputs.append({name: 0})
|
|
456
|
+
test_inputs.append({name: 0.0})
|
|
457
|
+
|
|
458
|
+
return test_inputs
|
|
459
|
+
|
|
460
|
+
def _get_line_number(self, pos: int) -> int:
|
|
461
|
+
"""Get line number for a position in code."""
|
|
462
|
+
return self.code[:pos].count('\n') + 1
|
|
463
|
+
|
|
464
|
+
def _get_line_context(self, line_num: int) -> str:
|
|
465
|
+
"""Get context around a line number."""
|
|
466
|
+
lines = self.code.split('\n')
|
|
467
|
+
start = max(0, line_num - 3)
|
|
468
|
+
end = min(len(lines), line_num + 2)
|
|
469
|
+
return '\n'.join(lines[start:end])
|
|
470
|
+
|
|
471
|
+
def _has_zero_guard(self, context: str) -> bool:
|
|
472
|
+
"""Check if context has a zero guard."""
|
|
473
|
+
guard_patterns = [
|
|
474
|
+
r'if\s+\w+\s*!=\s*0',
|
|
475
|
+
r'if\s+\w+\s*>\s*0',
|
|
476
|
+
r'if\s+\w+',
|
|
477
|
+
r'try\s*:',
|
|
478
|
+
r'except\s+ZeroDivisionError',
|
|
479
|
+
]
|
|
480
|
+
return any(re.search(p, context) for p in guard_patterns)
|
|
481
|
+
|
|
482
|
+
def _get_function_body(self, start_pos: int) -> str:
|
|
483
|
+
"""Extract function body starting from position."""
|
|
484
|
+
# Simple extraction - find next def at same indentation or EOF
|
|
485
|
+
remaining = self.code[start_pos:]
|
|
486
|
+
lines = remaining.split('\n')
|
|
487
|
+
body_lines = []
|
|
488
|
+
|
|
489
|
+
for line in lines[1:]: # Skip first line (def line)
|
|
490
|
+
if line.strip() and not line.startswith(' ') and not line.startswith('\t'):
|
|
491
|
+
break
|
|
492
|
+
body_lines.append(line)
|
|
493
|
+
|
|
494
|
+
return '\n'.join(body_lines)
|
|
495
|
+
|
|
496
|
+
def _get_loop_body(self, start_pos: int) -> str:
|
|
497
|
+
"""Extract loop body starting from position."""
|
|
498
|
+
remaining = self.code[start_pos:]
|
|
499
|
+
lines = remaining.split('\n')
|
|
500
|
+
body_lines = []
|
|
501
|
+
indent = None
|
|
502
|
+
|
|
503
|
+
for line in lines:
|
|
504
|
+
if not line.strip():
|
|
505
|
+
continue
|
|
506
|
+
stripped = line.lstrip()
|
|
507
|
+
current_indent = len(line) - len(stripped)
|
|
508
|
+
|
|
509
|
+
if indent is None and stripped:
|
|
510
|
+
indent = current_indent
|
|
511
|
+
body_lines.append(line)
|
|
512
|
+
elif stripped and current_indent > indent:
|
|
513
|
+
body_lines.append(line)
|
|
514
|
+
elif stripped and current_indent <= indent:
|
|
515
|
+
break
|
|
516
|
+
|
|
517
|
+
return '\n'.join(body_lines)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def detect_edge_cases(
|
|
521
|
+
code: str,
|
|
522
|
+
algorithm: Optional[Algorithm] = None,
|
|
523
|
+
sandbox=None
|
|
524
|
+
) -> List[EdgeCase]:
|
|
525
|
+
"""Convenience function to detect edge cases.
|
|
526
|
+
|
|
527
|
+
Args:
|
|
528
|
+
code: Python code to analyze
|
|
529
|
+
algorithm: Optional algorithm structure
|
|
530
|
+
sandbox: Optional SandboxExecutor for execution testing
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
List of detected edge cases
|
|
534
|
+
"""
|
|
535
|
+
detector = EdgeCaseDetector(code, algorithm)
|
|
536
|
+
|
|
537
|
+
if sandbox:
|
|
538
|
+
return detector.analyze_execution(sandbox)
|
|
539
|
+
else:
|
|
540
|
+
return detector.analyze_static()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Workflow modules for AlgoMath.
|
|
3
|
+
|
|
4
|
+
This package contains workflow implementations for the four core phases:
|
|
5
|
+
- extract: Extract algorithm steps from text
|
|
6
|
+
- generate: Generate executable code from steps
|
|
7
|
+
- run: Execute generated code
|
|
8
|
+
- verify: Verify execution results
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from .extract import run_extraction
|
|
12
|
+
from .generate import run_generation
|
|
13
|
+
from .run import run_execution
|
|
14
|
+
from .verify import run_verification
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
'run_extraction',
|
|
18
|
+
'run_generation',
|
|
19
|
+
'run_execution',
|
|
20
|
+
'run_verification',
|
|
21
|
+
]
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|