shrinkray 25.12.27.2__tar.gz → 25.12.28.0__tar.gz
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.
- {shrinkray-25.12.27.2/src/shrinkray.egg-info → shrinkray-25.12.28.0}/PKG-INFO +1 -1
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/pyproject.toml +1 -1
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/__main__.py +25 -11
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/bytes.py +0 -125
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/clangdelta.py +0 -24
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/genericlanguages.py +0 -20
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/json.py +0 -8
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/patching.py +0 -63
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/sequences.py +0 -25
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/reducer.py +0 -50
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/state.py +192 -56
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/__init__.py +0 -4
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/client.py +2 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/protocol.py +8 -11
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/subprocess/worker.py +67 -25
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/tui.py +114 -92
- shrinkray-25.12.28.0/src/shrinkray/validation.py +403 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0/src/shrinkray.egg-info}/PKG-INFO +1 -1
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/SOURCES.txt +2 -2
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_byte_reduction_passes.py +1 -97
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_clang_delta.py +2 -47
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_generic_language.py +0 -31
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_generic_shrinking_properties.py +0 -15
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_json_passes.py +0 -43
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_main.py +40 -35
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_patching.py +0 -140
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_python_reducers.py +0 -11
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_reducer.py +1 -13
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_reduction_passes.py +8 -169
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_state.py +209 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_subprocess_protocol.py +0 -32
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_subprocess_worker.py +99 -14
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_tui.py +158 -155
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_tui_snapshots.py +108 -0
- shrinkray-25.12.28.0/tests/test_validation.py +1131 -0
- shrinkray-25.12.27.2/src/shrinkray/display.py +0 -75
- shrinkray-25.12.27.2/tests/test_display.py +0 -135
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/LICENSE +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/README.md +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/setup.cfg +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/__init__.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/cli.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/formatting.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/__init__.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/definitions.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/python.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/passes/sat.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/problem.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/process.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/py.typed +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/ui.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray/work.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/dependency_links.txt +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/entry_points.txt +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/requires.txt +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/src/shrinkray.egg-info/top_level.txt +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_cli.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_definitions.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_dimacs_cnf.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_formatting.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_misc_reduction_performance.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_natural_sort_orders.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_problem.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_process.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_sat.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_subprocess_client.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_subprocess_integration.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_ui.py +0 -0
- {shrinkray-25.12.27.2 → shrinkray-25.12.28.0}/tests/test_work.py +0 -0
|
@@ -17,18 +17,19 @@ from shrinkray.cli import (
|
|
|
17
17
|
validate_command,
|
|
18
18
|
validate_ui,
|
|
19
19
|
)
|
|
20
|
+
from shrinkray.formatting import determine_formatter_command
|
|
20
21
|
from shrinkray.passes.clangdelta import (
|
|
21
22
|
C_FILE_EXTENSIONS,
|
|
22
23
|
ClangDelta,
|
|
23
24
|
find_clang_delta,
|
|
24
25
|
)
|
|
25
|
-
from shrinkray.problem import InvalidInitialExample
|
|
26
26
|
from shrinkray.state import (
|
|
27
27
|
ShrinkRayDirectoryState,
|
|
28
28
|
ShrinkRayState,
|
|
29
29
|
ShrinkRayStateSingleFile,
|
|
30
30
|
)
|
|
31
31
|
from shrinkray.ui import BasicUI, ShrinkRayUI
|
|
32
|
+
from shrinkray.validation import run_validation
|
|
32
33
|
from shrinkray.work import Volume
|
|
33
34
|
|
|
34
35
|
|
|
@@ -39,12 +40,9 @@ async def run_shrink_ray(
|
|
|
39
40
|
"""Run the shrink ray reduction process."""
|
|
40
41
|
async with trio.open_nursery() as nursery:
|
|
41
42
|
problem = state.problem
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
assert len(excs.exceptions) == 1
|
|
46
|
-
(e,) = excs.exceptions
|
|
47
|
-
await state.report_error(e)
|
|
43
|
+
# Validation runs before run_shrink_ray is called, so setup() should
|
|
44
|
+
# always succeed. If it doesn't, there's a bug and we want it to propagate.
|
|
45
|
+
await problem.setup()
|
|
48
46
|
|
|
49
47
|
reducer = state.reducer
|
|
50
48
|
|
|
@@ -273,6 +271,26 @@ def main(
|
|
|
273
271
|
if not backup:
|
|
274
272
|
backup = filename + os.extsep + "bak"
|
|
275
273
|
|
|
274
|
+
# Run initial validation before any state setup
|
|
275
|
+
# This validates the interestingness test and formatter with proper output streaming
|
|
276
|
+
formatter_command = None
|
|
277
|
+
if not os.path.isdir(filename) and formatter.lower() != "none":
|
|
278
|
+
formatter_command = determine_formatter_command(formatter, filename)
|
|
279
|
+
|
|
280
|
+
validation_result = run_validation(
|
|
281
|
+
file_path=filename,
|
|
282
|
+
test=test,
|
|
283
|
+
input_type=input_type,
|
|
284
|
+
in_place=in_place,
|
|
285
|
+
formatter_command=formatter_command,
|
|
286
|
+
)
|
|
287
|
+
|
|
288
|
+
if not validation_result.success:
|
|
289
|
+
print(f"\nError: {validation_result.error_message}", file=sys.stderr)
|
|
290
|
+
sys.exit(1)
|
|
291
|
+
|
|
292
|
+
print("\nStarting reduction...", file=sys.stderr, flush=True)
|
|
293
|
+
|
|
276
294
|
state_kwargs: dict[str, Any] = {
|
|
277
295
|
"input_type": input_type,
|
|
278
296
|
"in_place": in_place,
|
|
@@ -307,8 +325,6 @@ def main(
|
|
|
307
325
|
|
|
308
326
|
state = ShrinkRayDirectoryState(initial=initial, **state_kwargs)
|
|
309
327
|
|
|
310
|
-
trio.run(state.check_formatter)
|
|
311
|
-
|
|
312
328
|
else:
|
|
313
329
|
try:
|
|
314
330
|
os.remove(backup)
|
|
@@ -323,8 +339,6 @@ def main(
|
|
|
323
339
|
|
|
324
340
|
state = ShrinkRayStateSingleFile(initial=initial, **state_kwargs)
|
|
325
341
|
|
|
326
|
-
trio.run(state.check_formatter)
|
|
327
|
-
|
|
328
342
|
if ui_type == UIType.textual:
|
|
329
343
|
from shrinkray.tui import run_textual_ui
|
|
330
344
|
|
|
@@ -28,26 +28,6 @@ from shrinkray.passes.patching import Cuts, Patches, apply_patches
|
|
|
28
28
|
from shrinkray.problem import Format, ReductionProblem
|
|
29
29
|
|
|
30
30
|
|
|
31
|
-
@define(frozen=True)
|
|
32
|
-
class Encoding(Format[bytes, str]):
|
|
33
|
-
"""Format that decodes/encodes bytes using a character encoding."""
|
|
34
|
-
|
|
35
|
-
encoding: str
|
|
36
|
-
|
|
37
|
-
def __repr__(self) -> str:
|
|
38
|
-
return f"Encoding({repr(self.encoding)})"
|
|
39
|
-
|
|
40
|
-
@property
|
|
41
|
-
def name(self) -> str:
|
|
42
|
-
return self.encoding
|
|
43
|
-
|
|
44
|
-
def parse(self, input: bytes) -> str:
|
|
45
|
-
return input.decode(self.encoding)
|
|
46
|
-
|
|
47
|
-
def dumps(self, input: str) -> bytes:
|
|
48
|
-
return input.encode(self.encoding)
|
|
49
|
-
|
|
50
|
-
|
|
51
31
|
@define(frozen=True)
|
|
52
32
|
class Split(Format[bytes, list[bytes]]):
|
|
53
33
|
"""Format that splits bytes by a delimiter.
|
|
@@ -165,9 +145,6 @@ def tokenize(text: bytes) -> list[bytes]:
|
|
|
165
145
|
return result
|
|
166
146
|
|
|
167
147
|
|
|
168
|
-
MAX_DELETE_INTERVAL = 8
|
|
169
|
-
|
|
170
|
-
|
|
171
148
|
async def lexeme_based_deletions(
|
|
172
149
|
problem: ReductionProblem[bytes], min_size: int = 8
|
|
173
150
|
) -> None:
|
|
@@ -601,108 +578,6 @@ async def lower_individual_bytes(problem: ReductionProblem[bytes]) -> None:
|
|
|
601
578
|
await apply_patches(problem, IndividualByteReplacement(), patches)
|
|
602
579
|
|
|
603
580
|
|
|
604
|
-
RegionReplacementPatch = list[tuple[int, int, int]]
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
class RegionReplacement(Patches[RegionReplacementPatch, bytes]):
|
|
608
|
-
@property
|
|
609
|
-
def empty(self) -> RegionReplacementPatch:
|
|
610
|
-
return []
|
|
611
|
-
|
|
612
|
-
def combine(self, *patches: RegionReplacementPatch) -> RegionReplacementPatch:
|
|
613
|
-
result: RegionReplacementPatch = []
|
|
614
|
-
for p in patches:
|
|
615
|
-
result.extend(p)
|
|
616
|
-
return result
|
|
617
|
-
|
|
618
|
-
def apply(self, patch: RegionReplacementPatch, target: bytes) -> bytes:
|
|
619
|
-
result = bytearray(target)
|
|
620
|
-
for i, j, d in patch:
|
|
621
|
-
if d < result[i]:
|
|
622
|
-
for k in range(i, j):
|
|
623
|
-
result[k] = d
|
|
624
|
-
return bytes(result)
|
|
625
|
-
|
|
626
|
-
def size(self, patch: RegionReplacementPatch) -> int:
|
|
627
|
-
return 0
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
async def short_replacements(problem: ReductionProblem[bytes]) -> None:
|
|
631
|
-
"""Replace short regions with uniform byte values.
|
|
632
|
-
|
|
633
|
-
Tries replacing 1-4 byte regions with uniform values like 0, 1,
|
|
634
|
-
space, newline, or period. Useful for simplifying small sequences.
|
|
635
|
-
"""
|
|
636
|
-
target = problem.current_test_case
|
|
637
|
-
patches = [
|
|
638
|
-
[(i, j, c)]
|
|
639
|
-
for c in [0, 1] + list(b"01 \t\n\r.")
|
|
640
|
-
for i in range(len(target))
|
|
641
|
-
if target[i] > c
|
|
642
|
-
for j in range(i + 1, min(i + 5, len(target) + 1))
|
|
643
|
-
]
|
|
644
|
-
|
|
645
|
-
await apply_patches(problem, RegionReplacement(), patches)
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
WHITESPACE = b" \t\r\n"
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
async def sort_whitespace(problem: ReductionProblem[bytes]) -> None:
|
|
652
|
-
"""NB: This is a stupid pass that we only really need for artificial
|
|
653
|
-
test cases, but it's helpful for allowing those artificial test cases
|
|
654
|
-
to expose other issues."""
|
|
655
|
-
|
|
656
|
-
whitespace_up_to = 0
|
|
657
|
-
while (
|
|
658
|
-
whitespace_up_to < len(problem.current_test_case)
|
|
659
|
-
and problem.current_test_case[whitespace_up_to] not in WHITESPACE
|
|
660
|
-
):
|
|
661
|
-
whitespace_up_to += 1
|
|
662
|
-
while (
|
|
663
|
-
whitespace_up_to < len(problem.current_test_case)
|
|
664
|
-
and problem.current_test_case[whitespace_up_to] in WHITESPACE
|
|
665
|
-
):
|
|
666
|
-
whitespace_up_to += 1
|
|
667
|
-
|
|
668
|
-
# If the initial whitespace ends with a newline we want to keep it doing
|
|
669
|
-
# that. This is mostly for Python purposes.
|
|
670
|
-
if (
|
|
671
|
-
whitespace_up_to > 0
|
|
672
|
-
and problem.current_test_case[whitespace_up_to - 1] == b"\n"[0]
|
|
673
|
-
):
|
|
674
|
-
whitespace_up_to -= 1
|
|
675
|
-
|
|
676
|
-
i = whitespace_up_to + 1
|
|
677
|
-
|
|
678
|
-
while i < len(problem.current_test_case):
|
|
679
|
-
if problem.current_test_case[i] not in WHITESPACE:
|
|
680
|
-
i += 1
|
|
681
|
-
continue
|
|
682
|
-
|
|
683
|
-
async def can_move_to_whitespace(k: int) -> bool:
|
|
684
|
-
if i + k > len(problem.current_test_case):
|
|
685
|
-
return False
|
|
686
|
-
|
|
687
|
-
base = problem.current_test_case
|
|
688
|
-
target = base[i : i + k]
|
|
689
|
-
|
|
690
|
-
if any(c not in WHITESPACE for c in target):
|
|
691
|
-
return False
|
|
692
|
-
|
|
693
|
-
prefix = base[:whitespace_up_to]
|
|
694
|
-
attempt = prefix + target + base[whitespace_up_to:i] + base[i + k :]
|
|
695
|
-
return await problem.is_interesting(attempt)
|
|
696
|
-
|
|
697
|
-
k = await problem.work.find_large_integer(can_move_to_whitespace)
|
|
698
|
-
whitespace_up_to += k
|
|
699
|
-
i += k + 1
|
|
700
|
-
test_case = problem.current_test_case
|
|
701
|
-
await problem.is_interesting(
|
|
702
|
-
bytes(sorted(test_case[:whitespace_up_to])) + test_case[whitespace_up_to:]
|
|
703
|
-
)
|
|
704
|
-
|
|
705
|
-
|
|
706
581
|
# These are some cheat substitutions that are sometimes helpful, but mostly
|
|
707
582
|
# for passing stupid tests.
|
|
708
583
|
STANDARD_SUBSTITUTIONS = [(b"\0\0", b"\1"), (b"\0\0", b"\xff")]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import subprocess
|
|
3
|
-
from functools import lru_cache
|
|
4
3
|
from glob import glob
|
|
5
4
|
from shutil import which
|
|
6
5
|
from tempfile import NamedTemporaryFile
|
|
@@ -26,29 +25,6 @@ def find_clang_delta():
|
|
|
26
25
|
return clang_delta
|
|
27
26
|
|
|
28
27
|
|
|
29
|
-
@lru_cache(maxsize=1)
|
|
30
|
-
def clang_delta_works() -> bool:
|
|
31
|
-
"""Check if clang_delta can actually execute.
|
|
32
|
-
|
|
33
|
-
This verifies not just that the binary exists, but that it can run.
|
|
34
|
-
On some systems (e.g., Ubuntu 24.04), creduce is installed but
|
|
35
|
-
clang_delta fails at runtime due to shared library issues.
|
|
36
|
-
"""
|
|
37
|
-
clang_delta = find_clang_delta()
|
|
38
|
-
if not clang_delta:
|
|
39
|
-
return False
|
|
40
|
-
try:
|
|
41
|
-
# Run a simple test to verify clang_delta works
|
|
42
|
-
result = subprocess.run(
|
|
43
|
-
[clang_delta, "--help"],
|
|
44
|
-
capture_output=True,
|
|
45
|
-
timeout=5,
|
|
46
|
-
)
|
|
47
|
-
return result.returncode == 0
|
|
48
|
-
except (OSError, subprocess.TimeoutExpired):
|
|
49
|
-
return False
|
|
50
|
-
|
|
51
|
-
|
|
52
28
|
TRANSFORMATIONS: list[str] = [
|
|
53
29
|
"aggregate-to-scalar",
|
|
54
30
|
"binop-simplification",
|
|
@@ -9,7 +9,6 @@ from string import ascii_lowercase, ascii_uppercase
|
|
|
9
9
|
from typing import AnyStr
|
|
10
10
|
|
|
11
11
|
import trio
|
|
12
|
-
from attr import define
|
|
13
12
|
|
|
14
13
|
from shrinkray.passes.bytes import ByteReplacement, delete_intervals
|
|
15
14
|
from shrinkray.passes.definitions import ReductionPass
|
|
@@ -24,25 +23,6 @@ from shrinkray.problem import (
|
|
|
24
23
|
from shrinkray.work import NotFound
|
|
25
24
|
|
|
26
25
|
|
|
27
|
-
@define(frozen=True)
|
|
28
|
-
class Substring(Format[AnyStr, AnyStr]):
|
|
29
|
-
prefix: AnyStr
|
|
30
|
-
suffix: AnyStr
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def name(self) -> str:
|
|
34
|
-
return f"Substring({len(self.prefix)}, {len(self.suffix)})"
|
|
35
|
-
|
|
36
|
-
def parse(self, input: AnyStr) -> AnyStr:
|
|
37
|
-
if input.startswith(self.prefix) and input.endswith(self.suffix):
|
|
38
|
-
return input[len(self.prefix) : len(input) - len(self.suffix)]
|
|
39
|
-
else:
|
|
40
|
-
raise ParseError()
|
|
41
|
-
|
|
42
|
-
def dumps(self, input: AnyStr) -> AnyStr:
|
|
43
|
-
return self.prefix + input + self.suffix
|
|
44
|
-
|
|
45
|
-
|
|
46
26
|
class RegionReplacingPatches(Patches[dict[int, AnyStr], AnyStr]):
|
|
47
27
|
def __init__(self, regions: list[tuple[int, int]]):
|
|
48
28
|
assert regions
|
|
@@ -9,14 +9,6 @@ from shrinkray.passes.patching import Patches, apply_patches
|
|
|
9
9
|
from shrinkray.problem import Format, ParseError, ReductionProblem
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
def is_json(s: bytes) -> bool:
|
|
13
|
-
try:
|
|
14
|
-
json.loads(s)
|
|
15
|
-
return True
|
|
16
|
-
except ValueError:
|
|
17
|
-
return False
|
|
18
|
-
|
|
19
|
-
|
|
20
12
|
@define(frozen=True)
|
|
21
13
|
class _JSON(Format[bytes, Any]):
|
|
22
14
|
def __repr__(self) -> str:
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from collections.abc import Callable, Iterable, Sequence
|
|
3
|
-
from enum import Enum
|
|
4
|
-
from random import Random
|
|
5
3
|
from typing import Any, TypeVar, cast
|
|
6
4
|
|
|
7
5
|
import trio
|
|
@@ -52,27 +50,6 @@ class SetPatches[T, TargetType](Patches[frozenset[T], TargetType]):
|
|
|
52
50
|
return len(patch)
|
|
53
51
|
|
|
54
52
|
|
|
55
|
-
class ListPatches[T, TargetType](Patches[list[T], TargetType]):
|
|
56
|
-
def __init__(self, apply: Callable[[list[T], TargetType], TargetType]):
|
|
57
|
-
self.__apply = apply
|
|
58
|
-
|
|
59
|
-
@property
|
|
60
|
-
def empty(self):
|
|
61
|
-
return []
|
|
62
|
-
|
|
63
|
-
def combine(self, *patches: list[T]) -> list[T]:
|
|
64
|
-
result = []
|
|
65
|
-
for p in patches:
|
|
66
|
-
result.extend(p)
|
|
67
|
-
return result
|
|
68
|
-
|
|
69
|
-
def apply(self, patch: list[T], target: TargetType) -> TargetType:
|
|
70
|
-
return self.__apply(patch, target)
|
|
71
|
-
|
|
72
|
-
def size(self, patch: list[T]) -> int:
|
|
73
|
-
return len(patch)
|
|
74
|
-
|
|
75
|
-
|
|
76
53
|
class PatchApplier[PatchType, TargetType]:
|
|
77
54
|
def __init__(
|
|
78
55
|
self,
|
|
@@ -174,15 +151,6 @@ class PatchApplier[PatchType, TargetType]:
|
|
|
174
151
|
return await receive_merge_result.receive()
|
|
175
152
|
|
|
176
153
|
|
|
177
|
-
class Direction(Enum):
|
|
178
|
-
LEFT = 0
|
|
179
|
-
RIGHT = 1
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
class Completed(Exception):
|
|
183
|
-
pass
|
|
184
|
-
|
|
185
|
-
|
|
186
154
|
async def apply_patches[PatchType, TargetType](
|
|
187
155
|
problem: ReductionProblem[TargetType],
|
|
188
156
|
patch_info: Patches[PatchType, TargetType],
|
|
@@ -220,37 +188,6 @@ async def apply_patches[PatchType, TargetType](
|
|
|
220
188
|
await applier.try_apply_patch(patch)
|
|
221
189
|
|
|
222
190
|
|
|
223
|
-
class LazyMutableRange:
|
|
224
|
-
def __init__(self, n: int):
|
|
225
|
-
self.__size = n
|
|
226
|
-
self.__mask: dict[int, int] = {}
|
|
227
|
-
|
|
228
|
-
def __getitem__(self, i: int) -> int:
|
|
229
|
-
return self.__mask.get(i, i)
|
|
230
|
-
|
|
231
|
-
def __setitem__(self, i: int, v: int) -> None:
|
|
232
|
-
self.__mask[i] = v
|
|
233
|
-
|
|
234
|
-
def __len__(self) -> int:
|
|
235
|
-
return self.__size
|
|
236
|
-
|
|
237
|
-
def pop(self) -> int:
|
|
238
|
-
i = len(self) - 1
|
|
239
|
-
result = self[i]
|
|
240
|
-
self.__size = i
|
|
241
|
-
self.__mask.pop(i, None)
|
|
242
|
-
return result
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
def lazy_shuffle[T](seq: Sequence[T], rnd: Random) -> Iterable[T]:
|
|
246
|
-
indices = LazyMutableRange(len(seq))
|
|
247
|
-
while indices:
|
|
248
|
-
j = len(indices) - 1
|
|
249
|
-
i = rnd.randrange(0, len(indices))
|
|
250
|
-
indices[i], indices[j] = indices[j], indices[i]
|
|
251
|
-
yield seq[indices.pop()]
|
|
252
|
-
|
|
253
|
-
|
|
254
191
|
CutPatch = list[tuple[int, int]]
|
|
255
192
|
|
|
256
193
|
|
|
@@ -18,31 +18,6 @@ async def delete_elements[Seq: Sequence[Any]](problem: ReductionProblem[Seq]) ->
|
|
|
18
18
|
)
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
def merged_intervals(intervals: list[tuple[int, int]]) -> list[tuple[int, int]]:
|
|
22
|
-
normalized: list[list[int]] = []
|
|
23
|
-
for start, end in sorted(map(tuple, intervals)):
|
|
24
|
-
if normalized and normalized[-1][-1] >= start:
|
|
25
|
-
normalized[-1][-1] = max(normalized[-1][-1], end)
|
|
26
|
-
else:
|
|
27
|
-
normalized.append([start, end])
|
|
28
|
-
return list(map(tuple, normalized)) # type: ignore
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def with_deletions[Seq: Sequence[Any]](
|
|
32
|
-
target: Seq, deletions: list[tuple[int, int]]
|
|
33
|
-
) -> Seq:
|
|
34
|
-
result: list[Any] = []
|
|
35
|
-
prev = 0
|
|
36
|
-
total_deleted = 0
|
|
37
|
-
for start, end in deletions:
|
|
38
|
-
total_deleted += end - start
|
|
39
|
-
result.extend(target[prev:start])
|
|
40
|
-
prev = end
|
|
41
|
-
result.extend(target[prev:])
|
|
42
|
-
assert len(result) + total_deleted == len(target)
|
|
43
|
-
return type(target)(result) # type: ignore
|
|
44
|
-
|
|
45
|
-
|
|
46
21
|
def block_deletion[Seq: Sequence[Any]](
|
|
47
22
|
min_block: int, max_block: int
|
|
48
23
|
) -> ReductionPass[Seq]:
|
|
@@ -99,50 +99,6 @@ class Reducer[T](ABC):
|
|
|
99
99
|
"""Skip the currently running pass. Override in subclasses for pass control."""
|
|
100
100
|
|
|
101
101
|
|
|
102
|
-
@define
|
|
103
|
-
class BasicReducer[T](Reducer[T]):
|
|
104
|
-
reduction_passes: Iterable[ReductionPass[T]]
|
|
105
|
-
pumps: Iterable[ReductionPump[T]] = ()
|
|
106
|
-
_status: str = "Starting up"
|
|
107
|
-
|
|
108
|
-
def __attrs_post_init__(self) -> None:
|
|
109
|
-
self.reduction_passes = list(self.reduction_passes)
|
|
110
|
-
|
|
111
|
-
@property
|
|
112
|
-
def status(self) -> str:
|
|
113
|
-
return self._status
|
|
114
|
-
|
|
115
|
-
@status.setter
|
|
116
|
-
def status(self, value: str) -> None:
|
|
117
|
-
self._status = value
|
|
118
|
-
|
|
119
|
-
async def run_pass(self, rp: ReductionPass[T]) -> None:
|
|
120
|
-
await rp(self.target)
|
|
121
|
-
|
|
122
|
-
async def run(self) -> None:
|
|
123
|
-
await self.target.setup()
|
|
124
|
-
|
|
125
|
-
while True:
|
|
126
|
-
prev = self.target.current_test_case
|
|
127
|
-
for rp in self.reduction_passes:
|
|
128
|
-
self.status = f"Running reduction pass {rp.__name__}"
|
|
129
|
-
await self.run_pass(rp)
|
|
130
|
-
for pump in self.pumps:
|
|
131
|
-
self.status = f"Pumping with {pump.__name__}"
|
|
132
|
-
pumped = await pump(self.target)
|
|
133
|
-
if pumped != self.target.current_test_case:
|
|
134
|
-
with self.backtrack(pumped):
|
|
135
|
-
for rp in self.reduction_passes:
|
|
136
|
-
self.status = f"Running reduction pass {rp.__name__} under pump {pump.__name__}"
|
|
137
|
-
await self.run_pass(rp)
|
|
138
|
-
if prev == self.target.current_test_case:
|
|
139
|
-
return
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
class RestartPass(Exception):
|
|
143
|
-
pass
|
|
144
|
-
|
|
145
|
-
|
|
146
102
|
@define
|
|
147
103
|
class PassStats:
|
|
148
104
|
"""Statistics for a single reduction pass."""
|
|
@@ -181,12 +137,6 @@ class PassStatsTracker:
|
|
|
181
137
|
return list(self._stats.values())
|
|
182
138
|
|
|
183
139
|
|
|
184
|
-
class SkipPass(Exception):
|
|
185
|
-
"""Raised to skip the current pass."""
|
|
186
|
-
|
|
187
|
-
pass
|
|
188
|
-
|
|
189
|
-
|
|
190
140
|
@define
|
|
191
141
|
class ShrinkRay(Reducer[bytes]):
|
|
192
142
|
clang_delta: ClangDelta | None = None
|