From 8c0c242dc8a4a41b670f03e70c0b252cf14459db Mon Sep 17 00:00:00 2001 From: youknowdot Date: Sat, 24 Oct 2026 23:09:49 -0700 Subject: [PATCH 01/18] Align profiler bindings and capability checks with Python 3.15 (#9340) * Align offline profiler argument binding with Python 3.15 Accept the canonical keyword names for binary sample writes and native context exits. Resolve the argument layout before numeric callbacks, then preserve float-subclass payloads while accepting numeric protocols for required and optional profile statistics. AI-assisted source extraction, implementation, and source review with OpenAI Codex. Runtime validation remains pending. Assisted-by: OpenAI Codex:model version unavailable * Restore the offline profiler capability flag Preserve the original mega branch's zero-valued process_vm_readv capability export. The native live unwinder remains unsupported; this restores the public feature query without claiming live inspection support or changing canonical test helpers. AI-assisted source preservation and review with OpenAI Codex. Assisted-by: OpenAI Codex:model version unavailable * Restore finite-EOF table bounds for offline profiles Match CPython's EOF-wide table count and decoding windows while retaining checked offsets, allocation limits, cursor bounds, and replay checks. This restores the original mega's finite-file contract without changing canonical tests or the already validated argument-binding implementation. Assisted-by: OpenAI Codex:model version unavailable * Preserve symtable warning exceptions and original filenames (#9282) Keep warning-hook exceptions untouched instead of rewriting SyntaxError subclasses. Thread the original Python filename through parser warning emission and fresh warning-as-error SyntaxError construction, preserving fsdecoded bytes paths and lone surrogates. Assisted-by: OpenAI Codex:model-version-unavailable * build(deps): align the Cranelift 0.136 dependency family (#9341) * build(deps): bump the wasmtime group across 1 directory with 2 updates Bumps the wasmtime group with 2 updates in the / directory: [cranelift](https://github.com/bytecodealliance/wasmtime) and [cranelift-module](https://github.com/bytecodealliance/wasmtime). Updates `cranelift` from 0.135.2 to 0.136.3 - [Release notes](https://github.com/bytecodealliance/wasmtime/releases) - [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md) - [Commits](https://github.com/bytecodealliance/wasmtime/commits) Updates `cranelift-module` from 0.135.3 to 0.136.3 - [Release notes](https://github.com/bytecodealliance/wasmtime/releases) - [Changelog](https://github.com/bytecodealliance/wasmtime/blob/main/RELEASES.md) - [Commits](https://github.com/bytecodealliance/wasmtime/commits) --- updated-dependencies: - dependency-name: cranelift dependency-version: 0.136.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: wasmtime - dependency-name: cranelift-module dependency-version: 0.136.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: wasmtime ... Signed-off-by: dependabot[bot] * Align cranelift-jit with the 0.136 dependency family Update cranelift-jit alongside cranelift and cranelift-module so the JIT shares their LibCall, FuncId, and Module trait identities. Regenerate only the affected dependency lock entries, preserving the original dependency PR changes. AI assistance covered the dependency diagnosis, alignment, and static review. Assisted-by: OpenAI Codex:model-version-unavailable --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Support selective canonical function migration Add hash-pinned function selection to the official Lib migration tool, preserving canonical source bytes and rejecting ambiguous dependencies. Assisted-by: OpenAI Codex:model version unavailable * Detect CPython runtimes for canonical inspection feature checks Add native runtime-cookie detection and import the exact Python 3.15 inspection helpers and tests through the official Lib migration tool. Preserve the offline profiler argument and finite-EOF corrections. Assisted-by: OpenAI Codex:model version unavailable --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 2 + Cargo.toml | 2 + Lib/test/support/__init__.py | 87 + Lib/test/test_external_inspection.py | 4377 +++++++++++++---- crates/host_env/Cargo.toml | 5 + crates/host_env/src/lib.rs | 1 + .../host_env/src/remote_debugging/common.rs | 62 + crates/host_env/src/remote_debugging/linux.rs | 190 + crates/host_env/src/remote_debugging/macos.rs | 281 ++ crates/host_env/src/remote_debugging/mod.rs | 56 + .../src/remote_debugging/object_file.rs | 94 + .../host_env/src/remote_debugging/windows.rs | 178 + crates/stdlib/src/_remote_debugging.rs | 68 +- crates/stdlib/src/_remote_debugging/binary.rs | 11 +- scripts/update_lib/cmd_migrate.py | 458 +- .../tests/test_selected_functions.py | 320 ++ 16 files changed, 5275 insertions(+), 917 deletions(-) create mode 100644 crates/host_env/src/remote_debugging/common.rs create mode 100644 crates/host_env/src/remote_debugging/linux.rs create mode 100644 crates/host_env/src/remote_debugging/macos.rs create mode 100644 crates/host_env/src/remote_debugging/mod.rs create mode 100644 crates/host_env/src/remote_debugging/object_file.rs create mode 100644 crates/host_env/src/remote_debugging/windows.rs create mode 100644 scripts/update_lib/tests/test_selected_functions.py diff --git a/Cargo.lock b/Cargo.lock index 51b77ecb5be..4cbb8189bf0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3859,11 +3859,13 @@ dependencies = [ "libffi", "libloading 0.9.0", "mac_address", + "mach2", "memchr", "memmap2", "nix", "num-traits", "num_cpus", + "object", "parking_lot", "paste", "pkcs8 0.11.0", diff --git a/Cargo.toml b/Cargo.toml index 95b57c139c1..638a86a72de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -259,6 +259,7 @@ log = "0.4.30" lz4_flex = "0.14" nix = { version = "0.31", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } mac_address = "1.1.3" +mach2 = "0.4.3" malachite-bigint = "0.14.0" malachite-q = "0.14.0" malachite-base = "0.14.0" @@ -271,6 +272,7 @@ num-integer = "0.1.46" num-traits = "0.2" num_cpus = "1.17.0" num_enum = { version = "0.7", default-features = false } +object = { version = "0.37.3", default-features = false, features = ["read_core", "elf", "macho", "pe", "std", "unaligned"] } openssl = "0.10.80" openssl-sys = "0.9.110" optional = "0.5" diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 9a7d416797b..e7761cdde40 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -22,6 +22,7 @@ __all__ = [ + "has_remote_subprocess_debugging", "requires_remote_subprocess_debugging", "requires_root_user", "requires_non_root_user", # globals "PIPE_MAX_SIZE", "verbose", "max_memuse", "use_resources", "failfast", @@ -657,6 +658,92 @@ def requires_working_socket(*, module=False): else: return unittest.skipUnless(has_socket_support, msg) +@functools.cache +def has_remote_subprocess_debugging(): + """Check if we have permissions to debug subprocesses remotely. + + Returns True if we have permissions, False if we don't. + Checks for: + - Platform support (Linux, macOS, Windows only) + - On Linux: process_vm_readv support + - _remote_debugging module availability + - Actual subprocess debugging permissions (e.g., macOS entitlements) + Result is cached. + """ + # Check platform support + if sys.platform not in ("linux", "darwin", "win32"): + return False + + try: + import _remote_debugging + except ImportError: + return False + + # On Linux, check for process_vm_readv support + if sys.platform == "linux": + if not getattr(_remote_debugging, "PROCESS_VM_READV_SUPPORTED", False): + return False + + # First check if we can read our own process + if not _remote_debugging.is_python_process(os.getpid()): + return False + + # Check subprocess access - debugging child processes may require + # additional permissions depending on platform security settings + import socket + import subprocess + + # Create a socket for child to signal readiness + server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server.bind(("127.0.0.1", 0)) + server.listen(1) + port = server.getsockname()[1] + + # Child connects to signal it's ready, then waits for parent to close + child_code = f""" +import socket +s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +s.connect(("127.0.0.1", {port})) +s.recv(1) # Wait for parent to signal done +""" + proc = subprocess.Popen( + [sys.executable, "-c", child_code], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + try: + server.settimeout(5.0) + conn, _ = server.accept() + # Child is ready, test if we can probe it + result = _remote_debugging.is_python_process(proc.pid) + # Check if subprocess is still alive after probing + if proc.poll() is not None: + return False + conn.close() # Signal child to exit + return result + except (socket.timeout, OSError): + return False + finally: + server.close() + proc.kill() + proc.wait() + + +def requires_remote_subprocess_debugging(): + """Skip tests that require remote subprocess debugging permissions. + + This also implies subprocess support, so no need to use both + @requires_subprocess() and @requires_remote_subprocess_debugging(). + """ + if not has_subprocess_support: + return unittest.skip("requires subprocess support") + return unittest.skipUnless( + has_remote_subprocess_debugging(), + "requires remote subprocess debugging permissions" + ) + + # Does strftime() support glibc extension like '%4Y'? has_strftime_extensions = False if sys.platform != "win32": diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index 08779bdb008..797b13af59f 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -1,11 +1,14 @@ +import asyncio import unittest import os import textwrap +import contextlib import importlib import sys import socket import threading import time +from contextlib import contextmanager from asyncio import staggered, taskgroups, base_events, tasks from unittest.mock import ANY from test.support import ( @@ -13,6 +16,7 @@ SHORT_TIMEOUT, busy_retry, requires_gil_enabled, + requires_remote_subprocess_debugging, ) from test.support.import_helper import import_module from test.support.script_helper import make_script @@ -20,6 +24,31 @@ import subprocess +# Profiling mode constants +PROFILING_MODE_WALL = 0 +PROFILING_MODE_CPU = 1 +PROFILING_MODE_GIL = 2 +PROFILING_MODE_ALL = 3 +PROFILING_MODE_EXCEPTION = 4 + +# Thread status flags +THREAD_STATUS_HAS_GIL = 1 << 0 +THREAD_STATUS_ON_CPU = 1 << 1 +THREAD_STATUS_UNKNOWN = 1 << 2 +THREAD_STATUS_HAS_EXCEPTION = 1 << 4 + +# Maximum number of retry attempts for operations that may fail transiently +MAX_TRIES = 10 +RETRY_DELAY = 0.1 + +# Exceptions that can occur transiently when reading from a live process +TRANSIENT_ERRORS = (OSError, RuntimeError, UnicodeDecodeError) + +try: + from concurrent import interpreters +except ImportError: + interpreters = None + PROCESS_VM_READV_SUPPORTED = False try: @@ -32,12 +61,149 @@ ) +# ============================================================================ +# Module-level helper functions +# ============================================================================ + + def _make_test_script(script_dir, script_basename, source): to_return = make_script(script_dir, script_basename, source) importlib.invalidate_caches() return to_return +def _create_server_socket(port, backlog=1): + """Create and configure a server socket for test communication.""" + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.bind(("localhost", port)) + server_socket.settimeout(SHORT_TIMEOUT) + server_socket.listen(backlog) + return server_socket + + +def _wait_for_signal(sock, expected_signals, timeout=SHORT_TIMEOUT): + """ + Wait for expected signal(s) from a socket with proper timeout and EOF handling. + + Args: + sock: Connected socket to read from + expected_signals: Single bytes object or list of bytes objects to wait for + timeout: Socket timeout in seconds + + Returns: + bytes: Complete accumulated response buffer + + Raises: + RuntimeError: If connection closed before signal received or timeout + """ + if isinstance(expected_signals, bytes): + expected_signals = [expected_signals] + + sock.settimeout(timeout) + buffer = b"" + + while True: + # Check if all expected signals are in buffer + if all(sig in buffer for sig in expected_signals): + return buffer + + try: + chunk = sock.recv(4096) + if not chunk: + # EOF - connection closed + raise RuntimeError( + f"Connection closed before receiving expected signals. " + f"Expected: {expected_signals}, Got: {buffer[-200:]!r}" + ) + buffer += chunk + except socket.timeout: + raise RuntimeError( + f"Timeout waiting for signals. " + f"Expected: {expected_signals}, Got: {buffer[-200:]!r}" + ) + + +def _wait_for_n_signals(sock, signal_pattern, count, timeout=SHORT_TIMEOUT): + """ + Wait for N occurrences of a signal pattern. + + Args: + sock: Connected socket to read from + signal_pattern: bytes pattern to count (e.g., b"ready") + count: Number of occurrences expected + timeout: Socket timeout in seconds + + Returns: + bytes: Complete accumulated response buffer + + Raises: + RuntimeError: If connection closed or timeout before receiving all signals + """ + sock.settimeout(timeout) + buffer = b"" + found_count = 0 + + while found_count < count: + try: + chunk = sock.recv(4096) + if not chunk: + raise RuntimeError( + f"Connection closed after {found_count}/{count} signals. " + f"Last 200 bytes: {buffer[-200:]!r}" + ) + buffer += chunk + # Count occurrences in entire buffer + found_count = buffer.count(signal_pattern) + except socket.timeout: + raise RuntimeError( + f"Timeout waiting for {count} signals (found {found_count}). " + f"Last 200 bytes: {buffer[-200:]!r}" + ) + + return buffer + + +@contextmanager +def _managed_subprocess(args, timeout=SHORT_TIMEOUT): + """ + Context manager for subprocess lifecycle management. + + Ensures process is properly terminated and cleaned up even on exceptions. + Uses graceful termination first, then forceful kill if needed. + """ + p = subprocess.Popen(args) + try: + yield p + finally: + try: + p.terminate() + try: + p.wait(timeout=timeout) + except subprocess.TimeoutExpired: + p.kill() + try: + p.wait(timeout=timeout) + except subprocess.TimeoutExpired: + pass # Process refuses to die, nothing more we can do + except OSError: + pass # Process already dead + + +def _cleanup_sockets(*sockets): + """Safely close multiple sockets, ignoring errors.""" + for sock in sockets: + if sock is not None: + try: + sock.close() + except OSError: + pass + + +# ============================================================================ +# Decorators and skip conditions +# ============================================================================ + skip_if_not_supported = unittest.skipIf( ( sys.platform != "darwin" @@ -48,36 +214,311 @@ def _make_test_script(script_dir, script_basename, source): ) +def requires_subinterpreters(meth): + """Decorator to skip a test if subinterpreters are not supported.""" + return unittest.skipIf(interpreters is None, "subinterpreters required")( + meth + ) + + +# ============================================================================ +# Simple wrapper functions for RemoteUnwinder +# ============================================================================ + def get_stack_trace(pid): - unwinder = RemoteUnwinder(pid, all_threads=True, debug=True) - return unwinder.get_stack_trace() + for _ in busy_retry(SHORT_TIMEOUT): + try: + unwinder = RemoteUnwinder(pid, all_threads=True, debug=True) + return unwinder.get_stack_trace() + except RuntimeError as e: + continue + raise RuntimeError("Failed to get stack trace after retries") def get_async_stack_trace(pid): - unwinder = RemoteUnwinder(pid, debug=True) - return unwinder.get_async_stack_trace() + for _ in busy_retry(SHORT_TIMEOUT): + try: + unwinder = RemoteUnwinder(pid, debug=True) + return unwinder.get_async_stack_trace() + except RuntimeError as e: + continue + raise RuntimeError("Failed to get async stack trace after retries") def get_all_awaited_by(pid): - unwinder = RemoteUnwinder(pid, debug=True) - return unwinder.get_all_awaited_by() + for _ in busy_retry(SHORT_TIMEOUT): + try: + unwinder = RemoteUnwinder(pid, debug=True) + return unwinder.get_all_awaited_by() + except RuntimeError as e: + continue + raise RuntimeError("Failed to get all awaited_by after retries") + + +def _get_stack_trace_with_retry(unwinder, timeout=SHORT_TIMEOUT, condition=None): + """Get stack trace from an existing unwinder with retry for transient errors. + + This handles the case where we want to reuse an existing RemoteUnwinder + instance but still handle transient failures like "Failed to parse initial + frame in chain" that can occur when sampling at an inopportune moment. + If condition is provided, keeps retrying until condition(traces) is True. + """ + last_error = None + for _ in busy_retry(timeout): + try: + traces = unwinder.get_stack_trace() + if condition is None or condition(traces): + return traces + # Condition not met yet, keep retrying + except TRANSIENT_ERRORS as e: + last_error = e + continue + if last_error: + raise RuntimeError( + f"Failed to get stack trace after retries: {last_error}" + ) + raise RuntimeError("Condition never satisfied within timeout") + + +# ============================================================================ +# Base test class with shared infrastructure +# ============================================================================ + +class RemoteInspectionTestBase(unittest.TestCase): + """Base class for remote inspection tests with common helpers.""" -class TestGetStackTrace(unittest.TestCase): maxDiff = None + def _run_script_and_get_trace( + self, + script, + trace_func, + wait_for_signals=None, + port=None, + backlog=1, + ): + """ + Common pattern: run a script, wait for signals, get trace. + + Args: + script: Script content (will be formatted with port if {port} present) + trace_func: Function to call with pid to get trace (e.g., get_stack_trace) + wait_for_signals: Signal(s) to wait for before getting trace + port: Port to use (auto-selected if None) + backlog: Socket listen backlog + + Returns: + tuple: (trace_result, script_name) + """ + if port is None: + port = find_unused_port() + + # Format script with port if needed + if "{port}" in script or "{{port}}" in script: + script = script.replace("{{port}}", "{port}").format(port=port) + + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + + server_socket = _create_server_socket(port, backlog) + script_name = _make_test_script(script_dir, "script", script) + client_socket = None + + try: + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None + + if wait_for_signals: + _wait_for_signal(client_socket, wait_for_signals) + + trace = trace_func(p.pid) + return trace, script_name + finally: + _cleanup_sockets(client_socket, server_socket) + + def _find_frame_in_trace(self, stack_trace, predicate): + """ + Find a frame matching predicate in stack trace. + + Args: + stack_trace: List of InterpreterInfo objects + predicate: Function(frame) -> bool + + Returns: + FrameInfo or None + """ + for interpreter_info in stack_trace: + for thread_info in interpreter_info.threads: + for frame in thread_info.frame_info: + if predicate(frame): + return frame + return None + + def _find_thread_by_id(self, stack_trace, thread_id): + """Find a thread by its native thread ID.""" + for interpreter_info in stack_trace: + for thread_info in interpreter_info.threads: + if thread_info.thread_id == thread_id: + return thread_info + return None + + def _find_thread_with_frame(self, stack_trace, frame_predicate): + """Find a thread containing a frame matching predicate.""" + for interpreter_info in stack_trace: + for thread_info in interpreter_info.threads: + for frame in thread_info.frame_info: + if frame_predicate(frame): + return thread_info + return None + + def _get_thread_statuses(self, stack_trace): + """Extract thread_id -> status mapping from stack trace.""" + statuses = {} + for interpreter_info in stack_trace: + for thread_info in interpreter_info.threads: + statuses[thread_info.thread_id] = thread_info.status + return statuses + + def _get_task_id_map(self, stack_trace): + """Create task_id -> task mapping from async stack trace.""" + return {task.task_id: task for task in stack_trace[0].awaited_by} + + def _get_awaited_by_relationships(self, stack_trace): + """Extract task name to awaited_by set mapping.""" + id_to_task = self._get_task_id_map(stack_trace) + return { + task.task_name: set( + id_to_task[awaited.task_name].task_name + for awaited in task.awaited_by + ) + for task in stack_trace[0].awaited_by + } + + def _extract_coroutine_stacks(self, stack_trace): + """Extract and format coroutine stacks from tasks.""" + return { + task.task_name: sorted( + tuple(tuple(frame) for frame in coro.call_stack) + for coro in task.coroutine_stack + ) + for task in stack_trace[0].awaited_by + } + + @staticmethod + def _frame_to_lineno_tuple(frame): + """Convert frame to (filename, lineno, funcname, opcode) tuple. + + This extracts just the line number from the location, ignoring column + offsets which can vary due to sampling timing (e.g., when two statements + are on the same line, the sample might catch either one). + """ + filename, location, funcname, opcode = frame + return (filename, location.lineno, funcname, opcode) + + def _extract_coroutine_stacks_lineno_only(self, stack_trace): + """Extract coroutine stacks with line numbers only (no column offsets). + + Use this for tests where sampling timing can cause column offset + variations (e.g., 'expr1; expr2' on the same line). + """ + return { + task.task_name: sorted( + tuple(self._frame_to_lineno_tuple(frame) for frame in coro.call_stack) + for coro in task.coroutine_stack + ) + for task in stack_trace[0].awaited_by + } + + +# ============================================================================ +# Test classes +# ============================================================================ + + +@requires_remote_subprocess_debugging() +class TestSelfStackTrace(RemoteInspectionTestBase): + @skip_if_not_supported + def test_long_task_name_is_truncated(self): + # gh-157788 + async def main(): + asyncio.create_task(asyncio.sleep(10_000), name="x" * 300) + await asyncio.sleep(0) + names = [ + task.task_name + for info in RemoteUnwinder(os.getpid()).get_all_awaited_by() + for task in info.awaited_by + ] + return asyncio.current_task().get_name(), names + + main_name, names = asyncio.run(main()) + self.assertIn(main_name, names) + self.assertEqual([len(n) for n in names if n.startswith("x")], [255]) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_self_trace_with_large_linetable(self): + script = textwrap.dedent("""\ + import os + import _remote_debugging + + assignments = "\\n".join( + f"value_{i} = {i}" for i in range(1000) + ) + expected_lineno = len(assignments.splitlines()) + 1 + source = ( + f"{assignments}\\n" + "stack_trace = " + "_remote_debugging.RemoteUnwinder(os.getpid()).get_stack_trace()\\n" + ) + code = compile(source, "large_linetable.py", "exec") + assert len(code.co_linetable) > 4096, len(code.co_linetable) + namespace = {"os": os, "_remote_debugging": _remote_debugging} + exec(code, namespace) + large_linetable_frames = [ + frame + for interpreter in namespace["stack_trace"] + for thread in interpreter.threads + for frame in thread.frame_info + if frame.filename == "large_linetable.py" + ] + assert len(large_linetable_frames) == 1, large_linetable_frames + assert large_linetable_frames[0].location.lineno == expected_lineno, ( + large_linetable_frames[0] + ) + """) + + result = subprocess.run( + [sys.executable, "-c", script], + capture_output=True, + text=True, + timeout=SHORT_TIMEOUT, + ) + self.assertEqual( + result.returncode, 0, + f"stdout: {result.stdout}\nstderr: {result.stderr}" + ) + + +@requires_remote_subprocess_debugging() +class TestGetStackTrace(RemoteInspectionTestBase): @skip_if_not_supported @unittest.skipIf( sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support", ) def test_remote_stack_trace(self): - # Spawn a process with some realistic Python code port = find_unused_port() script = textwrap.dedent( f"""\ import time, sys, socket, threading - # Connect to the test process + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', {port})) @@ -90,66 +531,59 @@ def baz(): foo() def foo(): - sock.sendall(b"ready:thread\\n"); time.sleep(10_000) # same line number + sock.sendall(b"ready:thread\\n"); time.sleep(10_000) t = threading.Thread(target=bar) t.start() - sock.sendall(b"ready:main\\n"); t.join() # same line number + sock.sendall(b"ready:main\\n"); t.join() """ ) - stack_trace = None + with os_helper.temp_dir() as work_dir: script_dir = os.path.join(work_dir, "script_pkg") os.mkdir(script_dir) - # Create a socket server to communicate with the target process - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server_socket.bind(("localhost", port)) - server_socket.settimeout(SHORT_TIMEOUT) - server_socket.listen(1) - + server_socket = _create_server_socket(port) script_name = _make_test_script(script_dir, "script", script) client_socket = None + try: - p = subprocess.Popen([sys.executable, script_name]) - client_socket, _ = server_socket.accept() - server_socket.close() - response = b"" - while ( - b"ready:main" not in response - or b"ready:thread" not in response - ): - response += client_socket.recv(1024) - stack_trace = get_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) - finally: - if client_socket is not None: - client_socket.close() - p.kill() - p.terminate() - p.wait(timeout=SHORT_TIMEOUT) + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None - thread_expected_stack_trace = [ - FrameInfo([script_name, 15, "foo"]), - FrameInfo([script_name, 12, "baz"]), - FrameInfo([script_name, 9, "bar"]), - FrameInfo([threading.__file__, ANY, "Thread.run"]), - ] - # Is possible that there are more threads, so we check that the - # expected stack traces are in the result (looking at you Windows!) - self.assertIn((ANY, thread_expected_stack_trace), stack_trace) - - # Check that the main thread stack trace is in the result - frame = FrameInfo([script_name, 19, ""]) - for _, stack in stack_trace: - if frame in stack: - break - else: - self.fail("Main thread stack trace not found in result") + _wait_for_signal( + client_socket, [b"ready:main", b"ready:thread"] + ) + + stack_trace = get_stack_trace(p.pid) + + # Find expected thread stack by funcname + found_thread = self._find_thread_with_frame( + stack_trace, + lambda f: f.funcname == "foo" and f.location.lineno == 15, + ) + self.assertIsNotNone( + found_thread, "Expected thread stack trace not found" + ) + # Check the funcnames in order + funcnames = [f.funcname for f in found_thread.frame_info] + self.assertEqual( + funcnames[:6], + ["foo", "baz", "bar", "Thread.run", "Thread._bootstrap_inner", "Thread._bootstrap"] + ) + + # Check main thread + found_main = self._find_frame_in_trace( + stack_trace, + lambda f: f.funcname == "" and f.location.lineno == 19, + ) + self.assertIsNotNone( + found_main, "Main thread stack trace not found" + ) + finally: + _cleanup_sockets(client_socket, server_socket) @skip_if_not_supported @unittest.skipIf( @@ -193,13 +627,81 @@ def test_self_trace_after_ctypes_import(self): f"stdout: {result.stdout}\nstderr: {result.stderr}" ) + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_remote_stack_trace_non_ascii_names(self): + # Exercise each PyUnicode kind (1-byte non-ASCII, 2-byte BMP, + # 4-byte non-BMP) for both the filename and the function name + # reported in the stack trace. + latin1 = "zażółć" # 1-byte non-ASCII (forces non-ASCII path) + bmp = "λάμβδα" # 2-byte BMP + astral = "𐌀𐌁𐌂𐌃" # 4-byte non-BMP (Old Italic; XID, no NFKC fold) + func_name = f"{latin1}_{bmp}_{astral}" + script_basename = f"mod_{latin1}_{bmp}_{astral}" + + port = find_unused_port() + script = textwrap.dedent( + f"""\ + import socket + import time + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def {func_name}(): + sock.sendall(b"ready") + time.sleep(10_000) + + {func_name}() + """ + ) + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + script_name = _make_test_script(script_dir, script_basename, script) + + server_socket = _create_server_socket(port) + client_socket = None + try: + p = subprocess.Popen([sys.executable, script_name]) + client_socket, _ = server_socket.accept() + server_socket.close() + _wait_for_signal(client_socket, b"ready") + + stack_trace = get_stack_trace(p.pid) + except PermissionError: + self.skipTest("Insufficient permissions to read the stack trace") + finally: + if client_socket is not None: + client_socket.close() + p.kill() + p.wait(timeout=SHORT_TIMEOUT) + + frames = [ + frame + for interp in stack_trace + for thread in interp.threads + for frame in thread.frame_info + ] + target = next( + (f for f in frames if f.funcname == func_name), None + ) + self.assertIsNotNone( + target, + f"Frame for {func_name!r} missing; got " + f"{[(f.filename, f.funcname) for f in frames]}", + ) + self.assertEqual(target.filename, script_name) + @skip_if_not_supported @unittest.skipIf( sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support", ) def test_async_remote_stack_trace(self): - # Spawn a process with some realistic Python code port = find_unused_port() script = textwrap.dedent( f"""\ @@ -207,12 +709,12 @@ def test_async_remote_stack_trace(self): import time import sys import socket - # Connect to the test process + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', {port})) def c5(): - sock.sendall(b"ready"); time.sleep(10_000) # same line number + sock.sendall(b"ready"); time.sleep(10_000) async def c4(): await asyncio.sleep(0) @@ -243,7 +745,7 @@ def new_eager_loop(): asyncio.run(main(), loop_factory={{TASK_FACTORY}}) """ ) - stack_trace = None + for task_factory_variant in "asyncio.new_event_loop", "new_eager_loop": with ( self.subTest(task_factory_variant=task_factory_variant), @@ -251,203 +753,159 @@ def new_eager_loop(): ): script_dir = os.path.join(work_dir, "script_pkg") os.mkdir(script_dir) - server_socket = socket.socket( - socket.AF_INET, socket.SOCK_STREAM - ) - server_socket.setsockopt( - socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 - ) - server_socket.bind(("localhost", port)) - server_socket.settimeout(SHORT_TIMEOUT) - server_socket.listen(1) + + server_socket = _create_server_socket(port) script_name = _make_test_script( script_dir, "script", script.format(TASK_FACTORY=task_factory_variant), ) client_socket = None - try: - p = subprocess.Popen([sys.executable, script_name]) - client_socket, _ = server_socket.accept() - server_socket.close() - response = client_socket.recv(1024) - self.assertEqual(response, b"ready") - stack_trace = get_async_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) - finally: - if client_socket is not None: - client_socket.close() - p.kill() - p.terminate() - p.wait(timeout=SHORT_TIMEOUT) - - # First check all the tasks are present - tasks_names = [ - task.task_name for task in stack_trace[0].awaited_by - ] - for task_name in ["c2_root", "sub_main_1", "sub_main_2"]: - self.assertIn(task_name, tasks_names) - - # Now ensure that the awaited_by_relationships are correct - id_to_task = { - task.task_id: task for task in stack_trace[0].awaited_by - } - task_name_to_awaited_by = { - task.task_name: set( - id_to_task[awaited.task_name].task_name - for awaited in task.awaited_by - ) - for task in stack_trace[0].awaited_by - } - self.assertEqual( - task_name_to_awaited_by, - { - "c2_root": {"Task-1", "sub_main_1", "sub_main_2"}, - "Task-1": set(), - "sub_main_1": {"Task-1"}, - "sub_main_2": {"Task-1"}, - }, - ) - # Now ensure that the coroutine stacks are correct - coroutine_stacks = { - task.task_name: sorted( - tuple(tuple(frame) for frame in coro.call_stack) - for coro in task.coroutine_stack - ) - for task in stack_trace[0].awaited_by - } - self.assertEqual( - coroutine_stacks, - { - "Task-1": [ - ( - tuple( - [ - taskgroups.__file__, - ANY, - "TaskGroup._aexit", - ] - ), - tuple( - [ - taskgroups.__file__, - ANY, - "TaskGroup.__aexit__", - ] - ), - tuple([script_name, 26, "main"]), - ) - ], - "c2_root": [ - ( - tuple([script_name, 10, "c5"]), - tuple([script_name, 14, "c4"]), - tuple([script_name, 17, "c3"]), - tuple([script_name, 20, "c2"]), - ) - ], - "sub_main_1": [(tuple([script_name, 23, "c1"]),)], - "sub_main_2": [(tuple([script_name, 23, "c1"]),)], - }, - ) + try: + with _managed_subprocess( + [sys.executable, script_name] + ) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None + + response = _wait_for_signal(client_socket, b"ready") + self.assertIn(b"ready", response) + + stack_trace = get_async_stack_trace(p.pid) + + # Check all tasks are present + tasks_names = [ + task.task_name + for task in stack_trace[0].awaited_by + ] + for task_name in [ + "c2_root", + "sub_main_1", + "sub_main_2", + ]: + self.assertIn(task_name, tasks_names) + + # Check awaited_by relationships + relationships = self._get_awaited_by_relationships( + stack_trace + ) + self.assertEqual( + relationships, + { + "c2_root": { + "Task-1", + "sub_main_1", + "sub_main_2", + }, + "Task-1": set(), + "sub_main_1": {"Task-1"}, + "sub_main_2": {"Task-1"}, + }, + ) - # Now ensure the coroutine stacks for the awaited_by relationships are correct. - awaited_by_coroutine_stacks = { - task.task_name: sorted( - ( - id_to_task[coro.task_name].task_name, - tuple(tuple(frame) for frame in coro.call_stack), + # Check coroutine stacks (using line numbers only to avoid + # flakiness from column offset variations when sampling + # catches different statements on the same line) + coroutine_stacks = self._extract_coroutine_stacks_lineno_only( + stack_trace ) - for coro in task.awaited_by - ) - for task in stack_trace[0].awaited_by - } - self.assertEqual( - awaited_by_coroutine_stacks, - { - "Task-1": [], - "c2_root": [ - ( - "Task-1", - ( - tuple( - [ - taskgroups.__file__, - ANY, - "TaskGroup._aexit", - ] - ), - tuple( - [ - taskgroups.__file__, - ANY, - "TaskGroup.__aexit__", - ] - ), - tuple([script_name, 26, "main"]), - ), - ), - ("sub_main_1", (tuple([script_name, 23, "c1"]),)), - ("sub_main_2", (tuple([script_name, 23, "c1"]),)), - ], - "sub_main_1": [ - ( - "Task-1", + self.assertEqual( + coroutine_stacks, + { + "Task-1": [ + ( + (taskgroups.__file__, ANY, "TaskGroup._aexit", None), + (taskgroups.__file__, ANY, "TaskGroup.__aexit__", None), + (script_name, 26, "main", None), + ) + ], + "c2_root": [ + ( + (script_name, 10, "c5", None), + (script_name, 14, "c4", None), + (script_name, 17, "c3", None), + (script_name, 20, "c2", None), + ) + ], + "sub_main_1": [ + ((script_name, 23, "c1", None),) + ], + "sub_main_2": [ + ((script_name, 23, "c1", None),) + ], + }, + ) + + # Check awaited_by coroutine stacks (line numbers only) + id_to_task = self._get_task_id_map(stack_trace) + awaited_by_coroutine_stacks = { + task.task_name: sorted( ( + id_to_task[coro.task_name].task_name, tuple( - [ - taskgroups.__file__, - ANY, - "TaskGroup._aexit", - ] - ), - tuple( - [ - taskgroups.__file__, - ANY, - "TaskGroup.__aexit__", - ] + self._frame_to_lineno_tuple(frame) + for frame in coro.call_stack ), - tuple([script_name, 26, "main"]), - ), + ) + for coro in task.awaited_by ) - ], - "sub_main_2": [ - ( - "Task-1", - ( - tuple( - [ - taskgroups.__file__, - ANY, - "TaskGroup._aexit", - ] + for task in stack_trace[0].awaited_by + } + self.assertEqual( + awaited_by_coroutine_stacks, + { + "Task-1": [], + "c2_root": [ + ( + "Task-1", + ( + (taskgroups.__file__, ANY, "TaskGroup._aexit", None), + (taskgroups.__file__, ANY, "TaskGroup.__aexit__", None), + (script_name, 26, "main", None), + ), ), - tuple( - [ - taskgroups.__file__, - ANY, - "TaskGroup.__aexit__", - ] + ( + "sub_main_1", + ((script_name, 23, "c1", None),), ), - tuple([script_name, 26, "main"]), - ), - ) - ], - }, - ) - - @skip_if_not_supported + ( + "sub_main_2", + ((script_name, 23, "c1", None),), + ), + ], + "sub_main_1": [ + ( + "Task-1", + ( + (taskgroups.__file__, ANY, "TaskGroup._aexit", None), + (taskgroups.__file__, ANY, "TaskGroup.__aexit__", None), + (script_name, 26, "main", None), + ), + ) + ], + "sub_main_2": [ + ( + "Task-1", + ( + (taskgroups.__file__, ANY, "TaskGroup._aexit", None), + (taskgroups.__file__, ANY, "TaskGroup.__aexit__", None), + (script_name, 26, "main", None), + ), + ) + ], + }, + ) + finally: + _cleanup_sockets(client_socket, server_socket) + + @skip_if_not_supported @unittest.skipIf( sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support", ) def test_asyncgen_remote_stack_trace(self): - # Spawn a process with some realistic Python code port = find_unused_port() script = textwrap.dedent( f"""\ @@ -455,12 +913,12 @@ def test_asyncgen_remote_stack_trace(self): import time import sys import socket - # Connect to the test process + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', {port})) async def gen_nested_call(): - sock.sendall(b"ready"); time.sleep(10_000) # same line number + sock.sendall(b"ready"); time.sleep(10_000) async def gen(): for num in range(2): @@ -475,59 +933,53 @@ async def main(): asyncio.run(main()) """ ) - stack_trace = None + with os_helper.temp_dir() as work_dir: script_dir = os.path.join(work_dir, "script_pkg") os.mkdir(script_dir) - # Create a socket server to communicate with the target process - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server_socket.bind(("localhost", port)) - server_socket.settimeout(SHORT_TIMEOUT) - server_socket.listen(1) + + server_socket = _create_server_socket(port) script_name = _make_test_script(script_dir, "script", script) client_socket = None + try: - p = subprocess.Popen([sys.executable, script_name]) - client_socket, _ = server_socket.accept() - server_socket.close() - response = client_socket.recv(1024) - self.assertEqual(response, b"ready") - stack_trace = get_async_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) - finally: - if client_socket is not None: - client_socket.close() - p.kill() - p.terminate() - p.wait(timeout=SHORT_TIMEOUT) + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None - # For this simple asyncgen test, we only expect one task with the full coroutine stack - self.assertEqual(len(stack_trace[0].awaited_by), 1) - task = stack_trace[0].awaited_by[0] - self.assertEqual(task.task_name, "Task-1") + response = _wait_for_signal(client_socket, b"ready") + self.assertIn(b"ready", response) - # Check the coroutine stack - based on actual output, only shows main - coroutine_stack = sorted( - tuple(tuple(frame) for frame in coro.call_stack) - for coro in task.coroutine_stack - ) - self.assertEqual( - coroutine_stack, - [ - ( - tuple([script_name, 10, "gen_nested_call"]), - tuple([script_name, 16, "gen"]), - tuple([script_name, 19, "main"]), + stack_trace = get_async_stack_trace(p.pid) + + # For this simple asyncgen test, we only expect one task + self.assertEqual(len(stack_trace[0].awaited_by), 1) + task = stack_trace[0].awaited_by[0] + self.assertEqual(task.task_name, "Task-1") + + # Check the coroutine stack (using line numbers only to avoid + # flakiness from column offset variations when sampling + # catches different statements on the same line) + coroutine_stack = sorted( + tuple(self._frame_to_lineno_tuple(frame) for frame in coro.call_stack) + for coro in task.coroutine_stack + ) + self.assertEqual( + coroutine_stack, + [ + ( + (script_name, 10, "gen_nested_call", None), + (script_name, 16, "gen", None), + (script_name, 19, "main", None), + ) + ], ) - ], - ) - # No awaited_by relationships expected for this simple case - self.assertEqual(task.awaited_by, []) + # No awaited_by relationships expected + self.assertEqual(task.awaited_by, []) + finally: + _cleanup_sockets(client_socket, server_socket) @skip_if_not_supported @unittest.skipIf( @@ -535,7 +987,6 @@ async def main(): "Test only runs on Linux with process_vm_readv support", ) def test_async_gather_remote_stack_trace(self): - # Spawn a process with some realistic Python code port = find_unused_port() script = textwrap.dedent( f"""\ @@ -543,13 +994,13 @@ def test_async_gather_remote_stack_trace(self): import time import sys import socket - # Connect to the test process + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', {port})) async def deep(): await asyncio.sleep(0) - sock.sendall(b"ready"); time.sleep(10_000) # same line number + sock.sendall(b"ready"); time.sleep(10_000) async def c1(): await asyncio.sleep(0) @@ -564,103 +1015,89 @@ async def main(): asyncio.run(main()) """ ) - stack_trace = None + with os_helper.temp_dir() as work_dir: script_dir = os.path.join(work_dir, "script_pkg") os.mkdir(script_dir) - # Create a socket server to communicate with the target process - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server_socket.bind(("localhost", port)) - server_socket.settimeout(SHORT_TIMEOUT) - server_socket.listen(1) + + server_socket = _create_server_socket(port) script_name = _make_test_script(script_dir, "script", script) client_socket = None + try: - p = subprocess.Popen([sys.executable, script_name]) - client_socket, _ = server_socket.accept() - server_socket.close() - response = client_socket.recv(1024) - self.assertEqual(response, b"ready") - stack_trace = get_async_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) - finally: - if client_socket is not None: - client_socket.close() - p.kill() - p.terminate() - p.wait(timeout=SHORT_TIMEOUT) + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None - # First check all the tasks are present - tasks_names = [ - task.task_name for task in stack_trace[0].awaited_by - ] - for task_name in ["Task-1", "Task-2"]: - self.assertIn(task_name, tasks_names) + response = _wait_for_signal(client_socket, b"ready") + self.assertIn(b"ready", response) - # Now ensure that the awaited_by_relationships are correct - id_to_task = { - task.task_id: task for task in stack_trace[0].awaited_by - } - task_name_to_awaited_by = { - task.task_name: set( - id_to_task[awaited.task_name].task_name - for awaited in task.awaited_by - ) - for task in stack_trace[0].awaited_by - } - self.assertEqual( - task_name_to_awaited_by, - { - "Task-1": set(), - "Task-2": {"Task-1"}, - }, - ) + stack_trace = get_async_stack_trace(p.pid) - # Now ensure that the coroutine stacks are correct - coroutine_stacks = { - task.task_name: sorted( - tuple(tuple(frame) for frame in coro.call_stack) - for coro in task.coroutine_stack - ) - for task in stack_trace[0].awaited_by - } - self.assertEqual( - coroutine_stacks, - { - "Task-1": [(tuple([script_name, 21, "main"]),)], - "Task-2": [ - ( - tuple([script_name, 11, "deep"]), - tuple([script_name, 15, "c1"]), - ) - ], - }, - ) + # Check all tasks are present + tasks_names = [ + task.task_name for task in stack_trace[0].awaited_by + ] + for task_name in ["Task-1", "Task-2"]: + self.assertIn(task_name, tasks_names) - # Now ensure the coroutine stacks for the awaited_by relationships are correct. - awaited_by_coroutine_stacks = { - task.task_name: sorted( - ( - id_to_task[coro.task_name].task_name, - tuple(tuple(frame) for frame in coro.call_stack), + # Check awaited_by relationships + relationships = self._get_awaited_by_relationships( + stack_trace ) - for coro in task.awaited_by - ) - for task in stack_trace[0].awaited_by - } - self.assertEqual( - awaited_by_coroutine_stacks, - { - "Task-1": [], - "Task-2": [ - ("Task-1", (tuple([script_name, 21, "main"]),)) - ], - }, - ) + self.assertEqual( + relationships, + { + "Task-1": set(), + "Task-2": {"Task-1"}, + }, + ) + + # Check coroutine stacks (using line numbers only to avoid + # flakiness from column offset variations when sampling + # catches different statements on the same line) + coroutine_stacks = self._extract_coroutine_stacks_lineno_only( + stack_trace + ) + self.assertEqual( + coroutine_stacks, + { + "Task-1": [((script_name, 21, "main", None),)], + "Task-2": [ + ( + (script_name, 11, "deep", None), + (script_name, 15, "c1", None), + ) + ], + }, + ) + + # Check awaited_by coroutine stacks (line numbers only) + id_to_task = self._get_task_id_map(stack_trace) + awaited_by_coroutine_stacks = { + task.task_name: sorted( + ( + id_to_task[coro.task_name].task_name, + tuple( + self._frame_to_lineno_tuple(frame) for frame in coro.call_stack + ), + ) + for coro in task.awaited_by + ) + for task in stack_trace[0].awaited_by + } + self.assertEqual( + awaited_by_coroutine_stacks, + { + "Task-1": [], + "Task-2": [ + ("Task-1", ((script_name, 21, "main", None),)) + ], + }, + ) + finally: + _cleanup_sockets(client_socket, server_socket) @skip_if_not_supported @unittest.skipIf( @@ -668,7 +1105,6 @@ async def main(): "Test only runs on Linux with process_vm_readv support", ) def test_async_staggered_race_remote_stack_trace(self): - # Spawn a process with some realistic Python code port = find_unused_port() script = textwrap.dedent( f"""\ @@ -676,13 +1112,13 @@ def test_async_staggered_race_remote_stack_trace(self): import time import sys import socket - # Connect to the test process + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', {port})) async def deep(): await asyncio.sleep(0) - sock.sendall(b"ready"); time.sleep(10_000) # same line number + sock.sendall(b"ready"); time.sleep(10_000) async def c1(): await asyncio.sleep(0) @@ -700,123 +1136,101 @@ async def main(): asyncio.run(main()) """ ) - stack_trace = None + with os_helper.temp_dir() as work_dir: script_dir = os.path.join(work_dir, "script_pkg") os.mkdir(script_dir) - # Create a socket server to communicate with the target process - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server_socket.bind(("localhost", port)) - server_socket.settimeout(SHORT_TIMEOUT) - server_socket.listen(1) + + server_socket = _create_server_socket(port) script_name = _make_test_script(script_dir, "script", script) client_socket = None + try: - p = subprocess.Popen([sys.executable, script_name]) - client_socket, _ = server_socket.accept() - server_socket.close() - response = client_socket.recv(1024) - self.assertEqual(response, b"ready") - stack_trace = get_async_stack_trace(p.pid) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) - finally: - if client_socket is not None: - client_socket.close() - p.kill() - p.terminate() - p.wait(timeout=SHORT_TIMEOUT) + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None - # First check all the tasks are present - tasks_names = [ - task.task_name for task in stack_trace[0].awaited_by - ] - for task_name in ["Task-1", "Task-2"]: - self.assertIn(task_name, tasks_names) + response = _wait_for_signal(client_socket, b"ready") + self.assertIn(b"ready", response) - # Now ensure that the awaited_by_relationships are correct - id_to_task = { - task.task_id: task for task in stack_trace[0].awaited_by - } - task_name_to_awaited_by = { - task.task_name: set( - id_to_task[awaited.task_name].task_name - for awaited in task.awaited_by - ) - for task in stack_trace[0].awaited_by - } - self.assertEqual( - task_name_to_awaited_by, - { - "Task-1": set(), - "Task-2": {"Task-1"}, - }, - ) + stack_trace = get_async_stack_trace(p.pid) - # Now ensure that the coroutine stacks are correct - coroutine_stacks = { - task.task_name: sorted( - tuple(tuple(frame) for frame in coro.call_stack) - for coro in task.coroutine_stack - ) - for task in stack_trace[0].awaited_by - } - self.assertEqual( - coroutine_stacks, - { - "Task-1": [ - ( - tuple([staggered.__file__, ANY, "staggered_race"]), - tuple([script_name, 21, "main"]), - ) - ], - "Task-2": [ - ( - tuple([script_name, 11, "deep"]), - tuple([script_name, 15, "c1"]), - tuple( - [ - staggered.__file__, - ANY, - "staggered_race..run_one_coro", - ] - ), - ) - ], - }, - ) + # Check all tasks are present + tasks_names = [ + task.task_name for task in stack_trace[0].awaited_by + ] + for task_name in ["Task-1", "Task-2"]: + self.assertIn(task_name, tasks_names) - # Now ensure the coroutine stacks for the awaited_by relationships are correct. - awaited_by_coroutine_stacks = { - task.task_name: sorted( - ( - id_to_task[coro.task_name].task_name, - tuple(tuple(frame) for frame in coro.call_stack), + # Check awaited_by relationships + relationships = self._get_awaited_by_relationships( + stack_trace ) - for coro in task.awaited_by - ) - for task in stack_trace[0].awaited_by - } - self.assertEqual( - awaited_by_coroutine_stacks, - { - "Task-1": [], - "Task-2": [ - ( - "Task-1", + self.assertEqual( + relationships, + { + "Task-1": set(), + "Task-2": {"Task-1"}, + }, + ) + + # Check coroutine stacks (using line numbers only to avoid + # flakiness from column offset variations when sampling + # catches different statements on the same line) + coroutine_stacks = self._extract_coroutine_stacks_lineno_only( + stack_trace + ) + self.assertEqual( + coroutine_stacks, + { + "Task-1": [ + ( + (staggered.__file__, ANY, "staggered_race", None), + (script_name, 21, "main", None), + ) + ], + "Task-2": [ + ( + (script_name, 11, "deep", None), + (script_name, 15, "c1", None), + (staggered.__file__, ANY, "staggered_race..run_one_coro", None), + ) + ], + }, + ) + + # Check awaited_by coroutine stacks (line numbers only) + id_to_task = self._get_task_id_map(stack_trace) + awaited_by_coroutine_stacks = { + task.task_name: sorted( ( + id_to_task[coro.task_name].task_name, tuple( - [staggered.__file__, ANY, "staggered_race"] + self._frame_to_lineno_tuple(frame) for frame in coro.call_stack ), - tuple([script_name, 21, "main"]), - ), + ) + for coro in task.awaited_by ) - ], - }, - ) + for task in stack_trace[0].awaited_by + } + self.assertEqual( + awaited_by_coroutine_stacks, + { + "Task-1": [], + "Task-2": [ + ( + "Task-1", + ( + (staggered.__file__, ANY, "staggered_race", None), + (script_name, 21, "main", None), + ), + ) + ], + }, + ) + finally: + _cleanup_sockets(client_socket, server_socket) @skip_if_not_supported @unittest.skipIf( @@ -824,6 +1238,10 @@ async def main(): "Test only runs on Linux with process_vm_readv support", ) def test_async_global_awaited_by(self): + # Reduced from 1000 to 100 to avoid file descriptor exhaustion + # when running tests in parallel (e.g., -j 20) + NUM_TASKS = 100 + port = find_unused_port() script = textwrap.dedent( f"""\ @@ -839,7 +1257,6 @@ def test_async_global_awaited_by(self): PORT = socket_helper.find_unused_port() connections = 0 - # Connect to the test process sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', {port})) @@ -862,23 +1279,16 @@ async def echo_client(message): assert message == data.decode() writer.close() await writer.wait_closed() - # Signal we are ready to sleep sock.sendall(b"ready") await asyncio.sleep(SHORT_TIMEOUT) async def echo_client_spam(server): async with asyncio.TaskGroup() as tg: - while connections < 1000: + while connections < {NUM_TASKS}: msg = list(ascii_lowercase + digits) random.shuffle(msg) tg.create_task(echo_client("".join(msg))) await asyncio.sleep(0) - # at least a 1000 tasks created. Each task will signal - # when is ready to avoid the race caused by the fact that - # tasks are waited on tg.__exit__ and we cannot signal when - # that happens otherwise - # at this point all client tasks completed without assertion errors - # let's wrap up the test server.close() await server.wait_closed() @@ -893,323 +1303,243 @@ async def main(): asyncio.run(main()) """ ) - stack_trace = None + with os_helper.temp_dir() as work_dir: script_dir = os.path.join(work_dir, "script_pkg") os.mkdir(script_dir) - # Create a socket server to communicate with the target process - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server_socket.bind(("localhost", port)) - server_socket.settimeout(SHORT_TIMEOUT) - server_socket.listen(1) + + server_socket = _create_server_socket(port) script_name = _make_test_script(script_dir, "script", script) client_socket = None + try: - p = subprocess.Popen([sys.executable, script_name]) - client_socket, _ = server_socket.accept() - server_socket.close() - for _ in range(1000): - expected_response = b"ready" - response = client_socket.recv(len(expected_response)) - self.assertEqual(response, expected_response) - for _ in busy_retry(SHORT_TIMEOUT): + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None + + # Wait for NUM_TASKS "ready" signals try: - all_awaited_by = get_all_awaited_by(p.pid) - except RuntimeError as re: - # This call reads a linked list in another process with - # no synchronization. That occasionally leads to invalid - # reads. Here we avoid making the test flaky. - msg = str(re) - if msg.startswith("Task list appears corrupted"): - continue - elif msg.startswith( - "Invalid linked list structure reading remote memory" - ): - continue - elif msg.startswith("Unknown error reading memory"): - continue - elif msg.startswith("Unhandled frame owner"): - continue - raise # Unrecognized exception, safest not to ignore it - else: - break - # expected: a list of two elements: 1 thread, 1 interp - self.assertEqual(len(all_awaited_by), 2) - # expected: a tuple with the thread ID and the awaited_by list - self.assertEqual(len(all_awaited_by[0]), 2) - # expected: no tasks in the fallback per-interp task list - self.assertEqual(all_awaited_by[1], (0, [])) - entries = all_awaited_by[0][1] - # expected: at least 1000 pending tasks - self.assertGreaterEqual(len(entries), 1000) - # the first three tasks stem from the code structure - main_stack = [ - FrameInfo([taskgroups.__file__, ANY, "TaskGroup._aexit"]), - FrameInfo( - [taskgroups.__file__, ANY, "TaskGroup.__aexit__"] - ), - FrameInfo([script_name, 60, "main"]), - ] - self.assertIn( - TaskInfo( - [ANY, "Task-1", [CoroInfo([main_stack, ANY])], []] - ), - entries, - ) - self.assertIn( - TaskInfo( - [ - ANY, - "server task", + _wait_for_n_signals(client_socket, b"ready", NUM_TASKS) + except RuntimeError as e: + self.fail(str(e)) + + all_awaited_by = get_all_awaited_by(p.pid) + + # Expected: a list of two elements: 1 thread, 1 interp + self.assertEqual(len(all_awaited_by), 2) + # Expected: a tuple with the thread ID and the awaited_by list + self.assertEqual(len(all_awaited_by[0]), 2) + # Expected: no tasks in the fallback per-interp task list + self.assertEqual(all_awaited_by[1], (0, [])) + + entries = all_awaited_by[0][1] + # Expected: at least NUM_TASKS pending tasks + self.assertGreaterEqual(len(entries), NUM_TASKS) + + # Check the main task structure + main_stack = [ + FrameInfo( + [taskgroups.__file__, ANY, "TaskGroup._aexit", ANY] + ), + FrameInfo( + [taskgroups.__file__, ANY, "TaskGroup.__aexit__", ANY] + ), + FrameInfo([script_name, ANY, "main", ANY]), + ] + self.assertIn( + TaskInfo( + [ANY, "Task-1", [CoroInfo([main_stack, ANY])], []] + ), + entries, + ) + self.assertIn( + TaskInfo( [ - CoroInfo( - [ + ANY, + "server task", + [ + CoroInfo( [ - FrameInfo( - [ - base_events.__file__, - ANY, - "Server.serve_forever", - ] - ) - ], - ANY, - ] - ) - ], - [ - CoroInfo( - [ + [ + FrameInfo( + [ + base_events.__file__, + ANY, + "Server.serve_forever", + ANY, + ] + ) + ], + ANY, + ] + ) + ], + [ + CoroInfo( [ - FrameInfo( - [ - taskgroups.__file__, - ANY, - "TaskGroup._aexit", - ] - ), - FrameInfo( - [ - taskgroups.__file__, - ANY, - "TaskGroup.__aexit__", - ] - ), - FrameInfo( - [script_name, ANY, "main"] - ), - ], - ANY, - ] - ) - ], - ] - ), - entries, - ) - self.assertIn( - TaskInfo( - [ - ANY, - "Task-4", + [ + FrameInfo( + [ + taskgroups.__file__, + ANY, + "TaskGroup._aexit", + ANY, + ] + ), + FrameInfo( + [ + taskgroups.__file__, + ANY, + "TaskGroup.__aexit__", + ANY, + ] + ), + FrameInfo( + [script_name, ANY, "main", ANY] + ), + ], + ANY, + ] + ) + ], + ] + ), + entries, + ) + self.assertIn( + TaskInfo( [ - CoroInfo( - [ + ANY, + "Task-4", + [ + CoroInfo( [ - FrameInfo( - [tasks.__file__, ANY, "sleep"] - ), - FrameInfo( - [ - script_name, - 38, - "echo_client", - ] - ), - ], - ANY, - ] - ) - ], - [ - CoroInfo( - [ + [ + FrameInfo( + [ + tasks.__file__, + ANY, + "sleep", + ANY, + ] + ), + FrameInfo( + [ + script_name, + ANY, + "echo_client", + ANY, + ] + ), + ], + ANY, + ] + ) + ], + [ + CoroInfo( [ - FrameInfo( - [ - taskgroups.__file__, - ANY, - "TaskGroup._aexit", - ] - ), - FrameInfo( - [ - taskgroups.__file__, - ANY, - "TaskGroup.__aexit__", - ] - ), - FrameInfo( - [ - script_name, - 41, - "echo_client_spam", - ] - ), - ], - ANY, - ] - ) - ], - ] - ), - entries, - ) + [ + FrameInfo( + [ + taskgroups.__file__, + ANY, + "TaskGroup._aexit", + ANY, + ] + ), + FrameInfo( + [ + taskgroups.__file__, + ANY, + "TaskGroup.__aexit__", + ANY, + ] + ), + FrameInfo( + [ + script_name, + ANY, + "echo_client_spam", + ANY, + ] + ), + ], + ANY, + ] + ) + ], + ] + ), + entries, + ) - expected_awaited_by = [ - CoroInfo( - [ - [ - FrameInfo( - [ - taskgroups.__file__, - ANY, - "TaskGroup._aexit", - ] - ), - FrameInfo( - [ - taskgroups.__file__, - ANY, - "TaskGroup.__aexit__", - ] - ), - FrameInfo( - [script_name, 41, "echo_client_spam"] - ), - ], - ANY, + # Find tasks awaited by echo_client_spam via TaskGroup + def matches_awaited_by_pattern(task): + if len(task.awaited_by) != 1: + return False + coro = task.awaited_by[0] + if len(coro.call_stack) != 3: + return False + funcnames = [f.funcname for f in coro.call_stack] + return funcnames == [ + "TaskGroup._aexit", + "TaskGroup.__aexit__", + "echo_client_spam", ] - ) - ] - tasks_with_awaited = [ - task - for task in entries - if task.awaited_by == expected_awaited_by - ] - self.assertGreaterEqual(len(tasks_with_awaited), 1000) - - # the final task will have some random number, but it should for - # sure be one of the echo client spam horde (In windows this is not true - # for some reason) - if sys.platform != "win32": - self.assertEqual( - tasks_with_awaited[-1].awaited_by, - entries[-1].awaited_by, - ) - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" - ) - finally: - if client_socket is not None: - client_socket.close() - p.kill() - p.terminate() - p.wait(timeout=SHORT_TIMEOUT) - @skip_if_not_supported - @unittest.skipIf( - sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, - "Test only runs on Linux with process_vm_readv support", - ) - def test_self_trace(self): - stack_trace = get_stack_trace(os.getpid()) - # Is possible that there are more threads, so we check that the - # expected stack traces are in the result (looking at you Windows!) - this_tread_stack = None - for thread_id, stack in stack_trace: - if thread_id == threading.get_native_id(): - this_tread_stack = stack - break - self.assertIsNotNone(this_tread_stack) - self.assertEqual( - stack[:2], - [ - FrameInfo( - [ - __file__, - get_stack_trace.__code__.co_firstlineno + 2, - "get_stack_trace", - ] - ), - FrameInfo( - [ - __file__, - self.test_self_trace.__code__.co_firstlineno + 6, - "TestGetStackTrace.test_self_trace", + tasks_with_awaited = [ + task + for task in entries + if matches_awaited_by_pattern(task) ] - ), - ], - ) + self.assertGreaterEqual(len(tasks_with_awaited), NUM_TASKS) + + # Final task should be from echo client spam (not on Windows) + if sys.platform != "win32": + self.assertEqual( + tasks_with_awaited[-1].awaited_by, + entries[-1].awaited_by, + ) + finally: + _cleanup_sockets(client_socket, server_socket) @skip_if_not_supported @unittest.skipIf( sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, "Test only runs on Linux with process_vm_readv support", ) - @requires_gil_enabled("Free threaded builds don't have an 'active thread'") - def test_only_active_thread(self): - # Test that only_active_thread parameter works correctly + def test_async_global_awaited_by_from_non_main_thread(self): port = find_unused_port() script = textwrap.dedent( f"""\ - import time, sys, socket, threading + import asyncio + import socket + import threading + import time - # Connect to the test process sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect(('localhost', {port})) - def worker_thread(name, barrier, ready_event): - barrier.wait() # Synchronize thread start - ready_event.wait() # Wait for main thread signal - # Sleep to keep thread alive - time.sleep(10_000) - - def main_work(): - # Do busy work to hold the GIL - sock.sendall(b"working\\n") - count = 0 - while count < 100000000: - count += 1 - if count % 10000000 == 0: - pass # Keep main thread busy - sock.sendall(b"done\\n") - - # Create synchronization primitives - num_threads = 3 - barrier = threading.Barrier(num_threads + 1) # +1 for main thread - ready_event = threading.Event() - - # Start worker threads - threads = [] - for i in range(num_threads): - t = threading.Thread(target=worker_thread, args=(f"Worker-{{i}}", barrier, ready_event)) - t.start() - threads.append(t) - - # Wait for all threads to be ready - barrier.wait() - - # Signal ready to parent process - sock.sendall(b"ready\\n") + async def worker_main(): + task = asyncio.create_task( + asyncio.sleep(10_000), + name="worker task", + ) + await asyncio.sleep(0) + sock.sendall(f"ready:{{threading.get_native_id()}}\\n".encode()) + await task - # Signal threads to start waiting - ready_event.set() + def run_worker_loop(): + asyncio.run(worker_main()) - # Now do busy work to hold the GIL - main_work() + threading.Thread( + target=run_worker_loop, + name="async-worker", + daemon=True, + ).start() + time.sleep(10_000) """ ) @@ -1217,88 +1547,2463 @@ def main_work(): script_dir = os.path.join(work_dir, "script_pkg") os.mkdir(script_dir) - # Create a socket server to communicate with the target process - server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server_socket.bind(("localhost", port)) - server_socket.settimeout(SHORT_TIMEOUT) - server_socket.listen(1) - + server_socket = _create_server_socket(port) script_name = _make_test_script(script_dir, "script", script) client_socket = None + try: - p = subprocess.Popen([sys.executable, script_name]) - client_socket, _ = server_socket.accept() - server_socket.close() + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None + + response = _wait_for_signal(client_socket, b"ready:") + worker_thread_id = int( + response.split(b"ready:", 1)[1].splitlines()[0] + ) + + for _ in busy_retry(SHORT_TIMEOUT): + all_awaited_by = get_all_awaited_by(p.pid) + if any( + task.task_name == "worker task" + for info in all_awaited_by + if info.thread_id == worker_thread_id + for task in info.awaited_by + ): + break + else: + self.fail( + "get_all_awaited_by() did not report " + "the asyncio task from the non-main thread" + ) + finally: + _cleanup_sockets(client_socket, server_socket) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_async_remote_stack_trace_from_non_main_thread(self): + port = find_unused_port() + script = textwrap.dedent( + f"""\ + import asyncio + import socket + import threading + import time + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def blocking_call(): + sock.sendall(f"ready:{{threading.get_native_id()}}\\n".encode()) + time.sleep(10_000) + + async def worker_task(): + await asyncio.sleep(0) + blocking_call() + + async def worker_main(): + task = asyncio.create_task( + worker_task(), + name="worker task", + ) + await task + + def run_worker_loop(): + asyncio.run(worker_main()) + + threading.Thread( + target=run_worker_loop, + name="async-worker", + daemon=True, + ).start() + time.sleep(10_000) + """ + ) + + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + + server_socket = _create_server_socket(port) + script_name = _make_test_script(script_dir, "script", script) + client_socket = None + + try: + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None + + response = _wait_for_signal(client_socket, b"ready:") + worker_thread_id = int( + response.split(b"ready:", 1)[1].splitlines()[0] + ) + + for _ in busy_retry(SHORT_TIMEOUT): + stack_trace = get_async_stack_trace(p.pid) + if any( + task.task_name == "worker task" + for info in stack_trace + if info.thread_id == worker_thread_id + for task in info.awaited_by + ): + break + else: + self.fail( + "get_async_stack_trace() did not report " + "the running asyncio task from the non-main thread" + ) + finally: + _cleanup_sockets(client_socket, server_socket) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_self_trace(self): + stack_trace = get_stack_trace(os.getpid()) + + this_thread_stack = None + for interpreter_info in stack_trace: + for thread_info in interpreter_info.threads: + if thread_info.thread_id == threading.get_native_id(): + this_thread_stack = thread_info.frame_info + break + if this_thread_stack: + break + + self.assertIsNotNone(this_thread_stack) + # Check the top two frames + self.assertGreaterEqual(len(this_thread_stack), 2) + self.assertEqual(this_thread_stack[0].funcname, "get_stack_trace") + self.assertTrue(this_thread_stack[0].filename.endswith("test_external_inspection.py")) + self.assertEqual(this_thread_stack[1].funcname, "TestGetStackTrace.test_self_trace") + self.assertTrue(this_thread_stack[1].filename.endswith("test_external_inspection.py")) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + @requires_subinterpreters + def test_subinterpreter_stack_trace(self): + port = find_unused_port() + + import pickle + + subinterp_code = textwrap.dedent(f""" + import socket + import time + + def sub_worker(): + def nested_func(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + sock.sendall(b"ready:sub\\n") + time.sleep(10_000) + nested_func() + + sub_worker() + """).strip() + + pickled_code = pickle.dumps(subinterp_code) + + script = textwrap.dedent( + f""" + from concurrent import interpreters + import time + import sys + import socket + import threading + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def main_worker(): + sock.sendall(b"ready:main\\n") + time.sleep(10_000) + + def run_subinterp(): + subinterp = interpreters.create() + import pickle + pickled_code = {pickled_code!r} + subinterp_code = pickle.loads(pickled_code) + subinterp.exec(subinterp_code) + + sub_thread = threading.Thread(target=run_subinterp) + sub_thread.start() + + main_thread = threading.Thread(target=main_worker) + main_thread.start() + + main_thread.join() + sub_thread.join() + """ + ) + + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + + server_socket = _create_server_socket(port) + script_name = _make_test_script(script_dir, "script", script) + client_sockets = [] + + try: + with _managed_subprocess([sys.executable, script_name]) as p: + # Accept connections from both main and subinterpreter + responses = set() + while len(responses) < 2: + try: + client_socket, _ = server_socket.accept() + client_sockets.append(client_socket) + response = client_socket.recv(1024) + if b"ready:main" in response: + responses.add("main") + if b"ready:sub" in response: + responses.add("sub") + except socket.timeout: + break + + server_socket.close() + server_socket = None + + stack_trace = get_stack_trace(p.pid) + + # Verify we have at least one interpreter + self.assertGreaterEqual(len(stack_trace), 1) + + # Look for main interpreter (ID 0) and subinterpreter (ID > 0) + main_interp = None + sub_interp = None + for interpreter_info in stack_trace: + if interpreter_info.interpreter_id == 0: + main_interp = interpreter_info + elif interpreter_info.interpreter_id > 0: + sub_interp = interpreter_info + + self.assertIsNotNone( + main_interp, "Main interpreter should be present" + ) + + # Check main interpreter has expected stack trace + main_found = self._find_frame_in_trace( + [main_interp], lambda f: f.funcname == "main_worker" + ) + self.assertIsNotNone( + main_found, + "Main interpreter should have main_worker in stack", + ) + + # If subinterpreter is present, check its stack trace + if sub_interp: + sub_found = self._find_frame_in_trace( + [sub_interp], + lambda f: f.funcname + in ("sub_worker", "nested_func"), + ) + self.assertIsNotNone( + sub_found, + "Subinterpreter should have sub_worker or nested_func in stack", + ) + finally: + _cleanup_sockets(*client_sockets, server_socket) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + @requires_subinterpreters + def test_multiple_subinterpreters_with_threads(self): + port = find_unused_port() + + import pickle + + subinterp1_code = textwrap.dedent(f""" + import socket + import time + import threading + + def worker1(): + def nested_func(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + sock.sendall(b"ready:sub1-t1\\n") + time.sleep(10_000) + nested_func() + + def worker2(): + def nested_func(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + sock.sendall(b"ready:sub1-t2\\n") + time.sleep(10_000) + nested_func() + + t1 = threading.Thread(target=worker1) + t2 = threading.Thread(target=worker2) + t1.start() + t2.start() + t1.join() + t2.join() + """).strip() + + subinterp2_code = textwrap.dedent(f""" + import socket + import time + import threading + + def worker1(): + def nested_func(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + sock.sendall(b"ready:sub2-t1\\n") + time.sleep(10_000) + nested_func() + + def worker2(): + def nested_func(): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + sock.sendall(b"ready:sub2-t2\\n") + time.sleep(10_000) + nested_func() + + t1 = threading.Thread(target=worker1) + t2 = threading.Thread(target=worker2) + t1.start() + t2.start() + t1.join() + t2.join() + """).strip() + + pickled_code1 = pickle.dumps(subinterp1_code) + pickled_code2 = pickle.dumps(subinterp2_code) + + script = textwrap.dedent( + f""" + from concurrent import interpreters + import time + import sys + import socket + import threading + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def main_worker(): + sock.sendall(b"ready:main\\n") + time.sleep(10_000) + + def run_subinterp1(): + subinterp = interpreters.create() + import pickle + pickled_code = {pickled_code1!r} + subinterp_code = pickle.loads(pickled_code) + subinterp.exec(subinterp_code) + + def run_subinterp2(): + subinterp = interpreters.create() + import pickle + pickled_code = {pickled_code2!r} + subinterp_code = pickle.loads(pickled_code) + subinterp.exec(subinterp_code) + + sub1_thread = threading.Thread(target=run_subinterp1) + sub2_thread = threading.Thread(target=run_subinterp2) + sub1_thread.start() + sub2_thread.start() + + main_thread = threading.Thread(target=main_worker) + main_thread.start() + + main_thread.join() + sub1_thread.join() + sub2_thread.join() + """ + ) + + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + + server_socket = _create_server_socket(port, backlog=5) + script_name = _make_test_script(script_dir, "script", script) + client_sockets = [] + + try: + with _managed_subprocess([sys.executable, script_name]) as p: + # Accept connections from main and all subinterpreter threads + expected_responses = { + "ready:main", + "ready:sub1-t1", + "ready:sub1-t2", + "ready:sub2-t1", + "ready:sub2-t2", + } + responses = set() + + while len(responses) < 5: + try: + client_socket, _ = server_socket.accept() + client_sockets.append(client_socket) + response = client_socket.recv(1024) + response_str = response.decode().strip() + if response_str in expected_responses: + responses.add(response_str) + except socket.timeout: + break + + server_socket.close() + server_socket = None + + stack_trace = get_stack_trace(p.pid) + + # Verify we have multiple interpreters + self.assertGreaterEqual(len(stack_trace), 2) + + # Count interpreters by ID + interpreter_ids = { + interp.interpreter_id for interp in stack_trace + } + self.assertIn( + 0, + interpreter_ids, + "Main interpreter should be present", + ) + self.assertGreaterEqual(len(interpreter_ids), 3) + + # Count total threads + total_threads = sum( + len(interp.threads) for interp in stack_trace + ) + self.assertGreaterEqual(total_threads, 5) + + # Look for expected function names + all_funcnames = set() + for interpreter_info in stack_trace: + for thread_info in interpreter_info.threads: + for frame in thread_info.frame_info: + all_funcnames.add(frame.funcname) + + expected_funcs = { + "main_worker", + "worker1", + "worker2", + "nested_func", + } + found_funcs = expected_funcs.intersection(all_funcnames) + self.assertGreater(len(found_funcs), 0) + finally: + _cleanup_sockets(*client_sockets, server_socket) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + @requires_gil_enabled("Free threaded builds don't have an 'active thread'") + def test_only_active_thread(self): + port = find_unused_port() + script = textwrap.dedent( + f"""\ + import time, sys, socket, threading + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def worker_thread(name, barrier, ready_event): + barrier.wait() + ready_event.wait() + time.sleep(10_000) + + def main_work(): + sock.sendall(b"working\\n") + count = 0 + while count < 100000000: + count += 1 + if count % 10000000 == 0: + pass + sock.sendall(b"done\\n") + + num_threads = 3 + barrier = threading.Barrier(num_threads + 1) + ready_event = threading.Event() + + threads = [] + for i in range(num_threads): + t = threading.Thread(target=worker_thread, args=(f"Worker-{{i}}", barrier, ready_event)) + t.start() + threads.append(t) + + barrier.wait() + sock.sendall(b"ready\\n") + ready_event.set() + main_work() + """ + ) + + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + + server_socket = _create_server_socket(port) + script_name = _make_test_script(script_dir, "script", script) + client_socket = None + + try: + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None + + # Wait for ready and working signals + _wait_for_signal(client_socket, [b"ready", b"working"]) + + # Get stack trace with all threads + unwinder_all = RemoteUnwinder(p.pid, all_threads=True) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + all_traces = unwinder_all.get_stack_trace() + found = self._find_frame_in_trace( + all_traces, + lambda f: f.funcname == "main_work" + and f.location.lineno > 12, + ) + if found: + break + else: + self.fail( + "Main thread did not start its busy work on time" + ) + + # Get stack trace with only GIL holder + unwinder_gil = RemoteUnwinder( + p.pid, only_active_thread=True + ) + # Use condition to retry until we capture a thread holding the GIL + # (sampling may catch moments with no GIL holder on slow CI) + gil_traces = _get_stack_trace_with_retry( + unwinder_gil, + condition=lambda t: sum(len(i.threads) for i in t) >= 1, + ) + + # Count threads + total_threads = sum( + len(interp.threads) for interp in all_traces + ) + self.assertGreater(total_threads, 1) + + total_gil_threads = sum( + len(interp.threads) for interp in gil_traces + ) + self.assertEqual(total_gil_threads, 1) + + # Get the GIL holder thread ID + gil_thread_id = None + for interpreter_info in gil_traces: + if interpreter_info.threads: + gil_thread_id = interpreter_info.threads[ + 0 + ].thread_id + break + + # Get all thread IDs + all_thread_ids = [] + for interpreter_info in all_traces: + for thread_info in interpreter_info.threads: + all_thread_ids.append(thread_info.thread_id) + + self.assertIn(gil_thread_id, all_thread_ids) + finally: + _cleanup_sockets(client_socket, server_socket) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_opcodes_collection(self): + """Test that opcodes are collected when the opcodes flag is set.""" + script = textwrap.dedent( + """\ + import time, sys, socket + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def foo(): + sock.sendall(b"ready") + time.sleep(10_000) + + foo() + """ + ) + + def get_trace_with_opcodes(pid): + return RemoteUnwinder(pid, opcodes=True).get_stack_trace() + + stack_trace, _ = self._run_script_and_get_trace( + script, get_trace_with_opcodes, wait_for_signals=b"ready" + ) + + # Find our foo frame and verify it has an opcode + foo_frame = self._find_frame_in_trace( + stack_trace, lambda f: f.funcname == "foo" + ) + self.assertIsNotNone(foo_frame, "Could not find foo frame") + self.assertIsInstance(foo_frame.opcode, int) + self.assertGreaterEqual(foo_frame.opcode, 0) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_location_tuple_format(self): + """Test that location is a 4-tuple (lineno, end_lineno, col_offset, end_col_offset).""" + script = textwrap.dedent( + """\ + import time, sys, socket + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def foo(): + sock.sendall(b"ready") + time.sleep(10_000) + + foo() + """ + ) + + def get_trace_with_opcodes(pid): + return RemoteUnwinder(pid, opcodes=True).get_stack_trace() + + stack_trace, _ = self._run_script_and_get_trace( + script, get_trace_with_opcodes, wait_for_signals=b"ready" + ) + + # Find our foo frame + foo_frame = self._find_frame_in_trace( + stack_trace, lambda f: f.funcname == "foo" + ) + self.assertIsNotNone(foo_frame, "Could not find foo frame") + + # Check location is a 4-tuple with valid values + location = foo_frame.location + self.assertIsInstance(location, tuple) + self.assertEqual(len(location), 4) + lineno, end_lineno, col_offset, end_col_offset = location + self.assertIsInstance(lineno, int) + self.assertGreater(lineno, 0) + self.assertIsInstance(end_lineno, int) + self.assertGreaterEqual(end_lineno, lineno) + self.assertIsInstance(col_offset, int) + self.assertGreaterEqual(col_offset, 0) + self.assertIsInstance(end_col_offset, int) + self.assertGreaterEqual(end_col_offset, col_offset) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_location_tuple_exact_values(self): + """Test exact values of location tuple including column offsets.""" + script = textwrap.dedent( + """\ + import time, sys, socket + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def foo(): + sock.sendall(b"ready") + time.sleep(10_000) + + foo() + """ + ) + + def get_trace_with_opcodes(pid): + return RemoteUnwinder(pid, opcodes=True).get_stack_trace() + + stack_trace, _ = self._run_script_and_get_trace( + script, get_trace_with_opcodes, wait_for_signals=b"ready" + ) + + foo_frame = self._find_frame_in_trace( + stack_trace, lambda f: f.funcname == "foo" + ) + self.assertIsNotNone(foo_frame, "Could not find foo frame") + + # Can catch either sock.sendall (line 7) or time.sleep (line 8) + location = foo_frame.location + valid_locations = [ + (7, 7, 4, 26), # sock.sendall(b"ready") + (8, 8, 4, 22), # time.sleep(10_000) + ] + actual = (location.lineno, location.end_lineno, + location.col_offset, location.end_col_offset) + self.assertIn(actual, valid_locations) + + @skip_if_not_supported + @unittest.skipIf(sys._is_gil_enabled(), "Requires free-threading") + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Requires process_vm_readv", + ) + def test_tlbc_cache_refresh_after_growth(self): + # Reproducer from gh-157660. + script = textwrap.dedent("""\ + import os, threading + from _remote_debugging import RemoteUnwinder + from _queue import SimpleQueue + from test import support + + go = threading.Lock() + stop = threading.Lock() + go.acquire() + stop.acquire() + ready = SimpleQueue() + + def leaf(): + ready.put(None) + stop.acquire() + + def start_leaf(): + ready.put(None) + go.acquire() + leaf() + + def park(): + ready.put(None) + stop.acquire() + + def leaf_count(u): + return sum( + f.funcname == "leaf" + for i in u.get_stack_trace() + for t in i.threads for f in t.frame_info + ) + + # SimpleQueue.put() and Lock.acquire() do not push Python frames. + # Once notified, the worker's stack stays stable until go is released. + threading.Thread(target=leaf, daemon=True).start() + ready.get(timeout=support.SHORT_TIMEOUT) + for _ in range(16): + threading.Thread(target=park, daemon=True).start() + ready.get(timeout=support.SHORT_TIMEOUT) + threading.Thread(target=start_leaf, daemon=True).start() + ready.get(timeout=support.SHORT_TIMEOUT) + + u = RemoteUnwinder(os.getpid(), all_threads=True, cache_frames=False) + assert leaf_count(u) == 1 + go.release() + ready.get(timeout=support.SHORT_TIMEOUT) + assert leaf_count(u) == 2 + """) + result = subprocess.run( + [sys.executable, "-X", "gil=0", "-X", "tlbc=1", "-c", script], + capture_output=True, + text=True, + timeout=SHORT_TIMEOUT, + ) + self.assertEqual( + result.returncode, 0, + f"stdout: {result.stdout}\nstderr: {result.stderr}", + ) + + @skip_if_not_supported + @unittest.skipIf(sys._is_gil_enabled(), "Requires free-threading") + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Requires process_vm_readv", + ) + def test_tlbc_cache_refresh_after_slot_fill(self): + # Reproducer from gh-157660. + script = textwrap.dedent("""\ + import os, threading + from _remote_debugging import RemoteUnwinder + from _queue import SimpleQueue + + go = threading.Lock() + stop = threading.Lock() + go.acquire() + stop.acquire() + ready = SimpleQueue() + + def leaf(): + ready.put(None) + stop.acquire() + + def start_leaf(): + ready.put(None) + go.acquire() + leaf() + + from test import support + + def lines(u): + return sorted( + f.location.lineno + for i in u.get_stack_trace() + for t in i.threads for f in t.frame_info + if f.funcname == "leaf" + ) + + # SimpleQueue.put() and Lock.acquire() do not push Python frames. + # Once notified, the worker's stack stays stable until go is released. + threading.Thread(target=leaf, daemon=True).start() + ready.get(timeout=support.SHORT_TIMEOUT) + threading.Thread(target=start_leaf, daemon=True).start() + ready.get(timeout=support.SHORT_TIMEOUT) + u = RemoteUnwinder(os.getpid(), all_threads=True, cache_frames=False) + before = lines(u) + # The notification can be observed before put() returns, so either + # line in leaf() is a valid sample. + assert before in ([12], [13]), before + go.release() + ready.get(timeout=support.SHORT_TIMEOUT) + cached = lines(u) + assert len(cached) == 2, cached + assert all(line in (12, 13) for line in cached), cached + """) + result = subprocess.run( + [sys.executable, "-X", "gil=0", "-X", "tlbc=1", "-c", script], + capture_output=True, + text=True, + timeout=SHORT_TIMEOUT, + ) + self.assertEqual( + result.returncode, 0, + f"stdout: {result.stdout}\nstderr: {result.stderr}", + ) + + +class TestUnsupportedPlatformHandling(unittest.TestCase): + @unittest.skipIf( + sys.platform in ("linux", "darwin", "win32"), + "Test only runs on unsupported platforms (not Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_unsupported_platform_error(self): + with self.assertRaises(RuntimeError) as cm: + RemoteUnwinder(os.getpid()) + + self.assertIn( + "Reading the PyRuntime section is not supported on this platform", + str(cm.exception), + ) + + +@requires_remote_subprocess_debugging() +class TestDetectionOfThreadStatus(RemoteInspectionTestBase): + def _run_thread_status_test(self, mode, check_condition): + """ + Common pattern for thread status detection tests. + + Args: + mode: Profiling mode (PROFILING_MODE_CPU, PROFILING_MODE_GIL, etc.) + check_condition: Function(statuses, sleeper_tid, busy_tid) -> bool + """ + port = find_unused_port() + script = textwrap.dedent( + f"""\ + import time, sys, socket, threading + import os + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.connect(('localhost', {port})) + + def sleeper(): + tid = threading.get_native_id() + sock.sendall(f'ready:sleeper:{{tid}}\\n'.encode()) + time.sleep(10000) + + def busy(): + tid = threading.get_native_id() + sock.sendall(f'ready:busy:{{tid}}\\n'.encode()) + x = 0 + while True: + x = x + 1 + time.sleep(0.5) + + t1 = threading.Thread(target=sleeper) + t2 = threading.Thread(target=busy) + t1.start() + t2.start() + sock.sendall(b'ready:main\\n') + t1.join() + t2.join() + sock.close() + """ + ) + + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + + server_socket = _create_server_socket(port) + script_name = _make_test_script( + script_dir, "thread_status_script", script + ) + client_socket = None + + try: + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None + + # Wait for all ready signals and parse TIDs + response = _wait_for_signal( + client_socket, + [b"ready:main", b"ready:sleeper", b"ready:busy"], + ) + + sleeper_tid = None + busy_tid = None + for line in response.split(b"\n"): + if line.startswith(b"ready:sleeper:"): + try: + sleeper_tid = int(line.split(b":")[-1]) + except (ValueError, IndexError): + pass + elif line.startswith(b"ready:busy:"): + try: + busy_tid = int(line.split(b":")[-1]) + except (ValueError, IndexError): + pass + + self.assertIsNotNone( + sleeper_tid, "Sleeper thread id not received" + ) + self.assertIsNotNone( + busy_tid, "Busy thread id not received" + ) + + # Sample until we see expected thread states + statuses = {} + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=mode, + skip_non_matching_threads=False, + ) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + if check_condition( + statuses, sleeper_tid, busy_tid + ): + break + + return statuses, sleeper_tid, busy_tid + finally: + _cleanup_sockets(client_socket, server_socket) + + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on supported platforms (Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_thread_status_detection(self): + def check_cpu_status(statuses, sleeper_tid, busy_tid): + return ( + sleeper_tid in statuses + and busy_tid in statuses + and not (statuses[sleeper_tid] & THREAD_STATUS_ON_CPU) + and (statuses[busy_tid] & THREAD_STATUS_ON_CPU) + ) + + statuses, sleeper_tid, busy_tid = self._run_thread_status_test( + PROFILING_MODE_CPU, check_cpu_status + ) + + self.assertIn(sleeper_tid, statuses) + self.assertIn(busy_tid, statuses) + self.assertFalse( + statuses[sleeper_tid] & THREAD_STATUS_ON_CPU, + "Sleeper thread should be off CPU", + ) + self.assertTrue( + statuses[busy_tid] & THREAD_STATUS_ON_CPU, + "Busy thread should be on CPU", + ) + + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on supported platforms (Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_thread_status_gil_detection(self): + def check_gil_status(statuses, sleeper_tid, busy_tid): + return ( + sleeper_tid in statuses + and busy_tid in statuses + and not (statuses[sleeper_tid] & THREAD_STATUS_HAS_GIL) + and (statuses[busy_tid] & THREAD_STATUS_HAS_GIL) + ) + + statuses, sleeper_tid, busy_tid = self._run_thread_status_test( + PROFILING_MODE_GIL, check_gil_status + ) + + self.assertIn(sleeper_tid, statuses) + self.assertIn(busy_tid, statuses) + self.assertFalse( + statuses[sleeper_tid] & THREAD_STATUS_HAS_GIL, + "Sleeper thread should not have GIL", + ) + self.assertTrue( + statuses[busy_tid] & THREAD_STATUS_HAS_GIL, + "Busy thread should have GIL", + ) + + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on supported platforms (Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_thread_status_all_mode_detection(self): + port = find_unused_port() + script = textwrap.dedent( + f"""\ + import socket + import threading + import time + import sys + + def sleeper_thread(): + conn = socket.create_connection(("localhost", {port})) + conn.sendall(b"sleeper:" + str(threading.get_native_id()).encode()) + while True: + time.sleep(1) + + def busy_thread(): + conn = socket.create_connection(("localhost", {port})) + conn.sendall(b"busy:" + str(threading.get_native_id()).encode()) + while True: + sum(range(100000)) + + t1 = threading.Thread(target=sleeper_thread) + t2 = threading.Thread(target=busy_thread) + t1.start() + t2.start() + t1.join() + t2.join() + """ + ) + + with os_helper.temp_dir() as tmp_dir: + script_file = make_script(tmp_dir, "script", script) + server_socket = _create_server_socket(port, backlog=2) + client_sockets = [] + + try: + with _managed_subprocess( + [sys.executable, script_file], + ) as p: + sleeper_tid = None + busy_tid = None + + # Receive thread IDs from the child process + for _ in range(2): + client_socket, _ = server_socket.accept() + client_sockets.append(client_socket) + line = client_socket.recv(1024) + if line: + if line.startswith(b"sleeper:"): + try: + sleeper_tid = int(line.split(b":")[-1]) + except (ValueError, IndexError): + pass + elif line.startswith(b"busy:"): + try: + busy_tid = int(line.split(b":")[-1]) + except (ValueError, IndexError): + pass + + server_socket.close() + server_socket = None + + statuses = {} + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=PROFILING_MODE_ALL, + skip_non_matching_threads=False, + ) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + # Check ALL mode provides both GIL and CPU info + if ( + sleeper_tid in statuses + and busy_tid in statuses + and not ( + statuses[sleeper_tid] + & THREAD_STATUS_ON_CPU + ) + and not ( + statuses[sleeper_tid] + & THREAD_STATUS_HAS_GIL + ) + and (statuses[busy_tid] & THREAD_STATUS_ON_CPU) + and ( + statuses[busy_tid] & THREAD_STATUS_HAS_GIL + ) + ): + break + + self.assertIsNotNone( + sleeper_tid, "Sleeper thread id not received" + ) + self.assertIsNotNone( + busy_tid, "Busy thread id not received" + ) + self.assertIn(sleeper_tid, statuses) + self.assertIn(busy_tid, statuses) + + # Sleeper: off CPU, no GIL + self.assertFalse( + statuses[sleeper_tid] & THREAD_STATUS_ON_CPU, + "Sleeper should be off CPU", + ) + self.assertFalse( + statuses[sleeper_tid] & THREAD_STATUS_HAS_GIL, + "Sleeper should not have GIL", + ) + + # Busy: on CPU, has GIL + self.assertTrue( + statuses[busy_tid] & THREAD_STATUS_ON_CPU, + "Busy should be on CPU", + ) + self.assertTrue( + statuses[busy_tid] & THREAD_STATUS_HAS_GIL, + "Busy should have GIL", + ) + finally: + _cleanup_sockets(*client_sockets, server_socket) + + def _make_exception_test_script(self, port): + """Create script with exception and normal threads for testing.""" + return textwrap.dedent( + f"""\ + import socket + import threading + import time + + def exception_thread(): + conn = socket.create_connection(("localhost", {port})) + conn.sendall(b"exception:" + str(threading.get_native_id()).encode()) + try: + raise ValueError("test exception") + except ValueError: + while True: + time.sleep(0.01) + + def normal_thread(): + conn = socket.create_connection(("localhost", {port})) + conn.sendall(b"normal:" + str(threading.get_native_id()).encode()) + while True: + sum(range(1000)) + + t1 = threading.Thread(target=exception_thread) + t2 = threading.Thread(target=normal_thread) + t1.start() + t2.start() + t1.join() + t2.join() + """ + ) + + @contextmanager + def _run_exception_test_process(self): + """Context manager to run exception test script and yield thread IDs and process.""" + port = find_unused_port() + script = self._make_exception_test_script(port) + + with os_helper.temp_dir() as tmp_dir: + script_file = make_script(tmp_dir, "script", script) + server_socket = _create_server_socket(port, backlog=2) + client_sockets = [] + + try: + with _managed_subprocess([sys.executable, script_file]) as p: + exception_tid = None + normal_tid = None + + for _ in range(2): + client_socket, _ = server_socket.accept() + client_sockets.append(client_socket) + line = client_socket.recv(1024) + if line: + if line.startswith(b"exception:"): + try: + exception_tid = int(line.split(b":")[-1]) + except (ValueError, IndexError): + pass + elif line.startswith(b"normal:"): + try: + normal_tid = int(line.split(b":")[-1]) + except (ValueError, IndexError): + pass + + server_socket.close() + server_socket = None + + yield p, exception_tid, normal_tid + finally: + _cleanup_sockets(*client_sockets, server_socket) + + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on supported platforms (Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_thread_status_exception_detection(self): + """Test that THREAD_STATUS_HAS_EXCEPTION is set when thread has an active exception.""" + with self._run_exception_test_process() as (p, exception_tid, normal_tid): + self.assertIsNotNone(exception_tid, "Exception thread id not received") + self.assertIsNotNone(normal_tid, "Normal thread id not received") + + statuses = {} + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=PROFILING_MODE_ALL, + skip_non_matching_threads=False, + ) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + if ( + exception_tid in statuses + and normal_tid in statuses + and (statuses[exception_tid] & THREAD_STATUS_HAS_EXCEPTION) + and not (statuses[normal_tid] & THREAD_STATUS_HAS_EXCEPTION) + ): + break + + self.assertIn(exception_tid, statuses) + self.assertIn(normal_tid, statuses) + self.assertTrue( + statuses[exception_tid] & THREAD_STATUS_HAS_EXCEPTION, + "Exception thread should have HAS_EXCEPTION flag", + ) + self.assertFalse( + statuses[normal_tid] & THREAD_STATUS_HAS_EXCEPTION, + "Normal thread should not have HAS_EXCEPTION flag", + ) + + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on supported platforms (Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_thread_status_exception_mode_filtering(self): + """Test that PROFILING_MODE_EXCEPTION correctly filters threads.""" + with self._run_exception_test_process() as (p, exception_tid, normal_tid): + self.assertIsNotNone(exception_tid, "Exception thread id not received") + self.assertIsNotNone(normal_tid, "Normal thread id not received") + + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=PROFILING_MODE_EXCEPTION, + skip_non_matching_threads=True, + ) + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + if exception_tid in statuses: + self.assertNotIn( + normal_tid, + statuses, + "Normal thread should be filtered out in exception mode", + ) + return + + self.fail("Never found exception thread in exception mode") + +@requires_remote_subprocess_debugging() +class TestExceptionDetectionScenarios(RemoteInspectionTestBase): + """Test exception detection across all scenarios. + + This class verifies the exact conditions under which THREAD_STATUS_HAS_EXCEPTION + is set. Each test covers a specific scenario: + + 1. except_block: Thread inside except block + -> SHOULD have HAS_EXCEPTION (exc_info->exc_value is set) + + 2. finally_propagating: Exception propagating through finally block + -> SHOULD have HAS_EXCEPTION (current_exception is set) + + 3. finally_after_except: Finally block after except handled exception + -> Should NOT have HAS_EXCEPTION (exc_info cleared after except) + + 4. finally_no_exception: Finally block with no exception raised + -> Should NOT have HAS_EXCEPTION (no exception state) + """ + + def _make_single_scenario_script(self, port, scenario): + """Create script for a single exception scenario.""" + scenarios = { + "except_block": f"""\ +import socket +import threading +import time + +def target_thread(): + '''Inside except block - exception info is present''' + conn = socket.create_connection(("localhost", {port})) + conn.sendall(b"ready:" + str(threading.get_native_id()).encode()) + try: + raise ValueError("test") + except ValueError: + while True: + time.sleep(0.01) + +t = threading.Thread(target=target_thread) +t.start() +t.join() +""", + "finally_propagating": f"""\ +import socket +import threading +import time + +def target_thread(): + '''Exception propagating through finally - current_exception is set''' + conn = socket.create_connection(("localhost", {port})) + conn.sendall(b"ready:" + str(threading.get_native_id()).encode()) + try: + try: + raise ValueError("propagating") + finally: + # Exception is propagating through here + while True: + time.sleep(0.01) + except: + pass # Never reached due to infinite loop + +t = threading.Thread(target=target_thread) +t.start() +t.join() +""", + "finally_after_except": f"""\ +import socket +import threading +import time + +def target_thread(): + '''Finally runs after except handled - exc_info is cleared''' + conn = socket.create_connection(("localhost", {port})) + conn.sendall(b"ready:" + str(threading.get_native_id()).encode()) + try: + raise ValueError("test") + except ValueError: + pass # Exception caught and handled + finally: + while True: + time.sleep(0.01) + +t = threading.Thread(target=target_thread) +t.start() +t.join() +""", + "finally_no_exception": f"""\ +import socket +import threading +import time + +def target_thread(): + '''Finally with no exception at all''' + conn = socket.create_connection(("localhost", {port})) + conn.sendall(b"ready:" + str(threading.get_native_id()).encode()) + try: + pass # No exception + finally: + while True: + time.sleep(0.01) + +t = threading.Thread(target=target_thread) +t.start() +t.join() +""", + } + + return scenarios[scenario] + + @contextmanager + def _run_scenario_process(self, scenario): + """Context manager to run a single scenario and yield thread ID and process.""" + port = find_unused_port() + script = self._make_single_scenario_script(port, scenario) + + with os_helper.temp_dir() as tmp_dir: + script_file = make_script(tmp_dir, "script", script) + server_socket = _create_server_socket(port, backlog=1) + client_socket = None + + try: + with _managed_subprocess([sys.executable, script_file]) as p: + thread_tid = None + + client_socket, _ = server_socket.accept() + line = client_socket.recv(1024) + if line and line.startswith(b"ready:"): + try: + thread_tid = int(line.split(b":")[-1]) + except (ValueError, IndexError): + pass + + server_socket.close() + server_socket = None + + yield p, thread_tid + finally: + _cleanup_sockets(client_socket, server_socket) + + def _check_thread_status( + self, p, thread_tid, condition, condition_name="condition" + ): + """Helper to check thread status with a custom condition. + + This waits until we see 3 consecutive samples where the condition + returns True, which confirms the thread has reached and is stable + in the expected state. Samples that don't match are ignored (the + thread may not have reached the expected state yet). + + Args: + p: Process object with pid attribute + thread_tid: Thread ID to check + condition: Callable(statuses, thread_tid) -> bool that returns + True when the thread is in the expected state + condition_name: Description of condition for error messages + """ + unwinder = RemoteUnwinder( + p.pid, + all_threads=True, + mode=PROFILING_MODE_ALL, + skip_non_matching_threads=False, + ) + + # Wait for 3 consecutive samples matching expected state + matching_samples = 0 + for _ in busy_retry(SHORT_TIMEOUT): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + statuses = self._get_thread_statuses(traces) + + if thread_tid in statuses: + if condition(statuses, thread_tid): + matching_samples += 1 + if matching_samples >= 3: + return # Success - confirmed stable in expected state + else: + # Thread not yet in expected state, reset counter + matching_samples = 0 + + self.fail( + f"Thread did not stabilize in expected state " + f"({condition_name}) within timeout" + ) + + def _check_exception_status(self, p, thread_tid, expect_exception): + """Helper to check if thread has expected exception status.""" + def condition(statuses, tid): + has_exc = bool(statuses[tid] & THREAD_STATUS_HAS_EXCEPTION) + return has_exc == expect_exception + + self._check_thread_status( + p, thread_tid, condition, + condition_name=f"expect_exception={expect_exception}" + ) + + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on supported platforms (Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_except_block_has_exception(self): + """Test that thread inside except block has HAS_EXCEPTION flag. - # Wait for ready signal - response = b"" - while b"ready" not in response: - response += client_socket.recv(1024) - - # Wait for the main thread to start its busy work - while b"working" not in response: - response += client_socket.recv(1024) - - # Get stack trace with all threads - unwinder_all = RemoteUnwinder(p.pid, all_threads=True) - for _ in range(10): - # Wait for the main thread to start its busy work - all_traces = unwinder_all.get_stack_trace() - found = False - for thread_id, stack in all_traces: - if not stack: - continue - current_frame = stack[0] - if ( - current_frame.funcname == "main_work" - and current_frame.lineno > 15 - ): - found = True + When a thread is executing inside an except block, exc_info->exc_value + is set, so THREAD_STATUS_HAS_EXCEPTION should be True. + """ + with self._run_scenario_process("except_block") as (p, thread_tid): + self.assertIsNotNone(thread_tid, "Thread ID not received") + self._check_exception_status(p, thread_tid, expect_exception=True) - if found: - break - # Give a bit of time to take the next sample - time.sleep(0.1) + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on supported platforms (Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_finally_propagating_has_exception(self): + """Test that finally block with propagating exception has HAS_EXCEPTION flag. + + When an exception is propagating through a finally block (not yet caught), + current_exception is set, so THREAD_STATUS_HAS_EXCEPTION should be True. + """ + with self._run_scenario_process("finally_propagating") as (p, thread_tid): + self.assertIsNotNone(thread_tid, "Thread ID not received") + self._check_exception_status(p, thread_tid, expect_exception=True) + + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on supported platforms (Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_finally_after_except_no_exception(self): + """Test that finally block after except has NO HAS_EXCEPTION flag. + + When a finally block runs after an except block has handled the exception, + Python clears exc_info before entering finally, so THREAD_STATUS_HAS_EXCEPTION + should be False. + """ + with self._run_scenario_process("finally_after_except") as (p, thread_tid): + self.assertIsNotNone(thread_tid, "Thread ID not received") + self._check_exception_status(p, thread_tid, expect_exception=False) + + @unittest.skipIf( + sys.platform not in ("linux", "darwin", "win32"), + "Test only runs on supported platforms (Linux, macOS, or Windows)", + ) + @unittest.skipIf( + sys.platform == "android", "Android raises Linux-specific exception" + ) + def test_finally_no_exception_no_flag(self): + """Test that finally block with no exception has NO HAS_EXCEPTION flag. + + When a finally block runs during normal execution (no exception raised), + there is no exception state, so THREAD_STATUS_HAS_EXCEPTION should be False. + """ + with self._run_scenario_process("finally_no_exception") as (p, thread_tid): + self.assertIsNotNone(thread_tid, "Thread ID not received") + self._check_exception_status(p, thread_tid, expect_exception=False) + + +@requires_remote_subprocess_debugging() +class TestFrameCaching(RemoteInspectionTestBase): + """Test that frame caching produces correct results. + + Uses socket-based synchronization for deterministic testing. + All tests verify cache reuse via object identity checks (assertIs). + """ + + @contextmanager + def _target_process(self, script_body): + """Context manager for running a target process with socket sync.""" + port = find_unused_port() + script = f"""\ +import socket +sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +sock.connect(('localhost', {port})) +{textwrap.dedent(script_body)} +""" + + with os_helper.temp_dir() as work_dir: + script_dir = os.path.join(work_dir, "script_pkg") + os.mkdir(script_dir) + + server_socket = _create_server_socket(port) + script_name = _make_test_script(script_dir, "script", script) + client_socket = None + + try: + with _managed_subprocess([sys.executable, script_name]) as p: + client_socket, _ = server_socket.accept() + server_socket.close() + server_socket = None + + def make_unwinder(cache_frames=True): + return RemoteUnwinder( + p.pid, all_threads=True, cache_frames=cache_frames + ) + + yield p, client_socket, make_unwinder + finally: + _cleanup_sockets(client_socket, server_socket) + + def _get_frames_with_retry(self, unwinder, required_funcs): + """Get frames containing required_funcs, with retry for transient errors.""" + for _ in range(MAX_TRIES): + with contextlib.suppress(*TRANSIENT_ERRORS): + traces = unwinder.get_stack_trace() + for interp in traces: + for thread in interp.threads: + funcs = {f.funcname for f in thread.frame_info} + if required_funcs.issubset(funcs): + return thread.frame_info + time.sleep(RETRY_DELAY) + return None + + def _sample_frames( + self, + client_socket, + unwinder, + wait_signal, + send_ack, + required_funcs, + expected_frames=1, + ): + """Wait for signal, sample frames with retry until required funcs present, send ack.""" + _wait_for_signal(client_socket, wait_signal) + frames = None + for _ in range(MAX_TRIES): + frames = self._get_frames_with_retry(unwinder, required_funcs) + if frames and len(frames) >= expected_frames: + break + time.sleep(RETRY_DELAY) + client_socket.sendall(send_ack) + return frames + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_cache_hit_same_stack(self): + """Test that consecutive samples reuse cached parent frame objects. + + The current frame (index 0) is always re-read from memory to get + updated line numbers, so it may be a different object. Parent frames + (index 1+) should be identical objects from cache. + """ + script_body = """\ + def level3(): + sock.sendall(b"sync1") + sock.recv(16) + sock.sendall(b"sync2") + sock.recv(16) + sock.sendall(b"sync3") + sock.recv(16) + + def level2(): + level3() + + def level1(): + level2() + + level1() + """ + + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + unwinder = make_unwinder(cache_frames=True) + expected = {"level1", "level2", "level3"} + + frames1 = self._sample_frames( + client_socket, unwinder, b"sync1", b"ack", expected + ) + frames2 = self._sample_frames( + client_socket, unwinder, b"sync2", b"ack", expected + ) + frames3 = self._sample_frames( + client_socket, unwinder, b"sync3", b"done", expected + ) + + self.assertIsNotNone(frames1) + self.assertIsNotNone(frames2) + self.assertIsNotNone(frames3) + self.assertEqual(len(frames1), len(frames2)) + self.assertEqual(len(frames2), len(frames3)) + + # Current frame (index 0) is always re-read, so check value equality + self.assertEqual(frames1[0].funcname, frames2[0].funcname) + self.assertEqual(frames2[0].funcname, frames3[0].funcname) + + # Parent frames (index 1+) must be identical objects (cache reuse) + for i in range(1, len(frames1)): + f1, f2, f3 = frames1[i], frames2[i], frames3[i] + self.assertIs( + f1, f2, f"Frame {i}: samples 1-2 must be same object" + ) + self.assertIs( + f2, f3, f"Frame {i}: samples 2-3 must be same object" + ) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_line_number_updates_in_same_frame(self): + """Test that line numbers are correctly updated when execution moves within a function. + + When the profiler samples at different points within the same function, + it must report the correct line number for each sample, not stale cached values. + """ + script_body = """\ + def outer(): + inner() + + def inner(): + sock.sendall(b"line_a"); sock.recv(16) + sock.sendall(b"line_b"); sock.recv(16) + sock.sendall(b"line_c"); sock.recv(16) + sock.sendall(b"line_d"); sock.recv(16) + + outer() + """ + + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + unwinder = make_unwinder(cache_frames=True) + + frames_a = self._sample_frames( + client_socket, unwinder, b"line_a", b"ack", {"inner"} + ) + frames_b = self._sample_frames( + client_socket, unwinder, b"line_b", b"ack", {"inner"} + ) + frames_c = self._sample_frames( + client_socket, unwinder, b"line_c", b"ack", {"inner"} + ) + frames_d = self._sample_frames( + client_socket, unwinder, b"line_d", b"done", {"inner"} + ) + + self.assertIsNotNone(frames_a) + self.assertIsNotNone(frames_b) + self.assertIsNotNone(frames_c) + self.assertIsNotNone(frames_d) + + # Get the 'inner' frame from each sample (should be index 0) + inner_a = frames_a[0] + inner_b = frames_b[0] + inner_c = frames_c[0] + inner_d = frames_d[0] + + self.assertEqual(inner_a.funcname, "inner") + self.assertEqual(inner_b.funcname, "inner") + self.assertEqual(inner_c.funcname, "inner") + self.assertEqual(inner_d.funcname, "inner") + + # Line numbers must be different and increasing (execution moves forward) + self.assertLess( + inner_a.location.lineno, inner_b.location.lineno, "Line B should be after line A" + ) + self.assertLess( + inner_b.location.lineno, inner_c.location.lineno, "Line C should be after line B" + ) + self.assertLess( + inner_c.location.lineno, inner_d.location.lineno, "Line D should be after line C" + ) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_cache_invalidation_on_return(self): + """Test cache invalidation when stack shrinks (function returns).""" + script_body = """\ + def inner(): + sock.sendall(b"at_inner") + sock.recv(16) + + def outer(): + inner() + sock.sendall(b"at_outer") + sock.recv(16) + + outer() + """ + + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + unwinder = make_unwinder(cache_frames=True) + + frames_deep = self._sample_frames( + client_socket, + unwinder, + b"at_inner", + b"ack", + {"inner", "outer"}, + ) + frames_shallow = self._sample_frames( + client_socket, unwinder, b"at_outer", b"done", {"outer"} + ) + + self.assertIsNotNone(frames_deep) + self.assertIsNotNone(frames_shallow) + + funcs_deep = [f.funcname for f in frames_deep] + funcs_shallow = [f.funcname for f in frames_shallow] + + self.assertIn("inner", funcs_deep) + self.assertIn("outer", funcs_deep) + self.assertNotIn("inner", funcs_shallow) + self.assertIn("outer", funcs_shallow) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_cache_invalidation_on_call(self): + """Test cache invalidation when stack grows (new function called).""" + script_body = """\ + def deeper(): + sock.sendall(b"at_deeper") + sock.recv(16) + + def middle(): + sock.sendall(b"at_middle") + sock.recv(16) + deeper() + + def top(): + middle() + + top() + """ + + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + unwinder = make_unwinder(cache_frames=True) + + frames_before = self._sample_frames( + client_socket, + unwinder, + b"at_middle", + b"ack", + {"middle", "top"}, + ) + frames_after = self._sample_frames( + client_socket, + unwinder, + b"at_deeper", + b"done", + {"deeper", "middle", "top"}, + ) + + self.assertIsNotNone(frames_before) + self.assertIsNotNone(frames_after) + + funcs_before = [f.funcname for f in frames_before] + funcs_after = [f.funcname for f in frames_after] + + self.assertIn("middle", funcs_before) + self.assertIn("top", funcs_before) + self.assertNotIn("deeper", funcs_before) + + self.assertIn("deeper", funcs_after) + self.assertIn("middle", funcs_after) + self.assertIn("top", funcs_after) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_partial_stack_reuse(self): + """Test that unchanged parent frames are reused from cache when top frame moves.""" + script_body = """\ + def level4(): + sock.sendall(b"sync1") + sock.recv(16) + sock.sendall(b"sync2") + sock.recv(16) + + def level3(): + level4() + + def level2(): + level3() + + def level1(): + level2() + + level1() + """ + + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + unwinder = make_unwinder(cache_frames=True) + + # Sample 1: level4 at first sendall + frames1 = self._sample_frames( + client_socket, + unwinder, + b"sync1", + b"ack", + {"level1", "level2", "level3", "level4"}, + ) + # Sample 2: level4 at second sendall (same stack, different line) + frames2 = self._sample_frames( + client_socket, + unwinder, + b"sync2", + b"done", + {"level1", "level2", "level3", "level4"}, + ) + + self.assertIsNotNone(frames1) + self.assertIsNotNone(frames2) + + def find_frame(frames, funcname): + for f in frames: + if f.funcname == funcname: + return f + return None + + # level4 should have different line numbers (it moved) + l4_1 = find_frame(frames1, "level4") + l4_2 = find_frame(frames2, "level4") + self.assertIsNotNone(l4_1) + self.assertIsNotNone(l4_2) + self.assertNotEqual( + l4_1.location.lineno, + l4_2.location.lineno, + "level4 should be at different lines", + ) + + # Parent frames (level1, level2, level3) should be reused from cache + for name in ["level1", "level2", "level3"]: + f1 = find_frame(frames1, name) + f2 = find_frame(frames2, name) + self.assertIsNotNone(f1, f"{name} missing from sample 1") + self.assertIsNotNone(f2, f"{name} missing from sample 2") + self.assertIs(f1, f2, f"{name} should be reused from cache") + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_recursive_frames(self): + """Test caching with same function appearing multiple times (recursion).""" + script_body = """\ + def recurse(n): + if n <= 0: + sock.sendall(b"sync1") + sock.recv(16) + sock.sendall(b"sync2") + sock.recv(16) else: - self.fail( - "Main thread did not start its busy work on time" - ) + recurse(n - 1) - # Get stack trace with only GIL holder - unwinder_gil = RemoteUnwinder(p.pid, only_active_thread=True) - gil_traces = unwinder_gil.get_stack_trace() + recurse(5) + """ - except PermissionError: - self.skipTest( - "Insufficient permissions to read the stack trace" + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + unwinder = make_unwinder(cache_frames=True) + + frames1 = self._sample_frames( + client_socket, unwinder, b"sync1", b"ack", {"recurse"} + ) + frames2 = self._sample_frames( + client_socket, unwinder, b"sync2", b"done", {"recurse"} + ) + + self.assertIsNotNone(frames1) + self.assertIsNotNone(frames2) + + # Should have multiple "recurse" frames (6 total: recurse(5) down to recurse(0)) + recurse_count = sum(1 for f in frames1 if f.funcname == "recurse") + self.assertEqual(recurse_count, 6, "Should have 6 recursive frames") + + self.assertEqual(len(frames1), len(frames2)) + + # Current frame (index 0) is re-read, check value equality + self.assertEqual(frames1[0].funcname, frames2[0].funcname) + + # Parent frames (index 1+) should be identical objects (cache reuse) + for i in range(1, len(frames1)): + self.assertIs( + frames1[i], + frames2[i], + f"Frame {i}: recursive frames must be same object", + ) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_cache_vs_no_cache_equivalence(self): + """Test that cache_frames=True and cache_frames=False produce equivalent results.""" + script_body = """\ + def level3(): + sock.sendall(b"ready"); sock.recv(16) + + def level2(): + level3() + + def level1(): + level2() + + level1() + """ + + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + _wait_for_signal(client_socket, b"ready") + + # Sample with cache + unwinder_cache = make_unwinder(cache_frames=True) + frames_cached = self._get_frames_with_retry( + unwinder_cache, {"level1", "level2", "level3"} + ) + + # Sample without cache + unwinder_no_cache = make_unwinder(cache_frames=False) + frames_no_cache = self._get_frames_with_retry( + unwinder_no_cache, {"level1", "level2", "level3"} + ) + + client_socket.sendall(b"done") + + self.assertIsNotNone(frames_cached) + self.assertIsNotNone(frames_no_cache) + + # Same number of frames + self.assertEqual(len(frames_cached), len(frames_no_cache)) + + # Same function names in same order + funcs_cached = [f.funcname for f in frames_cached] + funcs_no_cache = [f.funcname for f in frames_no_cache] + self.assertEqual(funcs_cached, funcs_no_cache) + + # For level3 (leaf frame), due to timing races we can be at either + # sock.sendall() or sock.recv() - both are valid. For parent frames, + # the locations should match exactly. + # Valid locations for level3: line 3 has two statements + # sock.sendall(b"ready") -> col 4-26 + # sock.recv(16) -> col 28-41 + level3_valid_cols = {(4, 26), (28, 41)} + + for i in range(len(frames_cached)): + loc_cached = frames_cached[i].location + loc_no_cache = frames_no_cache[i].location + + if frames_cached[i].funcname == "level3": + # Leaf frame: can be at either statement + self.assertIn( + (loc_cached.col_offset, loc_cached.end_col_offset), + level3_valid_cols, ) - finally: - if client_socket is not None: - client_socket.close() - p.kill() - p.terminate() - p.wait(timeout=SHORT_TIMEOUT) + self.assertIn( + (loc_no_cache.col_offset, loc_no_cache.end_col_offset), + level3_valid_cols, + ) + else: + # Parent frames: must match exactly + self.assertEqual(loc_cached, loc_no_cache) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_cache_per_thread_isolation(self): + """Test that frame cache is per-thread and cache invalidation works independently.""" + script_body = """\ + import threading + + lock = threading.Lock() + + def sync(msg): + with lock: + sock.sendall(msg + b"\\n") + sock.recv(1) + + # Thread 1 functions + def baz1(): + sync(b"t1:baz1") + + def bar1(): + baz1() + + def blech1(): + sync(b"t1:blech1") + + def foo1(): + bar1() # Goes down to baz1, syncs + blech1() # Returns up, goes down to blech1, syncs + + # Thread 2 functions + def baz2(): + sync(b"t2:baz2") - # Verify we got multiple threads in all_traces - self.assertGreater( - len(all_traces), 1, "Should have multiple threads" + def bar2(): + baz2() + + def blech2(): + sync(b"t2:blech2") + + def foo2(): + bar2() # Goes down to baz2, syncs + blech2() # Returns up, goes down to blech2, syncs + + t1 = threading.Thread(target=foo1) + t2 = threading.Thread(target=foo2) + t1.start() + t2.start() + t1.join() + t2.join() + """ + + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + unwinder = make_unwinder(cache_frames=True) + + # Message dispatch table: signal -> required functions for that thread + dispatch = { + b"t1:baz1": {"baz1", "bar1", "foo1"}, + b"t2:baz2": {"baz2", "bar2", "foo2"}, + b"t1:blech1": {"blech1", "foo1"}, + b"t2:blech2": {"blech2", "foo2"}, + } + + # Track results for each sync point + results = {} + + # Process 4 sync points (order depends on thread scheduling) + buffer = _wait_for_signal(client_socket, b"\n") + for i in range(4): + # Extract first message from buffer + msg, sep, buffer = buffer.partition(b"\n") + self.assertIn(msg, dispatch, f"Unexpected message: {msg!r}") + + # Sample frames for the thread at this sync point + required_funcs = dispatch[msg] + frames = self._get_frames_with_retry(unwinder, required_funcs) + self.assertIsNotNone(frames, f"Thread not found for {msg!r}") + results[msg] = [f.funcname for f in frames] + + # Release thread and wait for next message (if not last) + client_socket.sendall(b"k") + if i < 3: + buffer += _wait_for_signal(client_socket, b"\n") + + # Validate Phase 1: baz snapshots + t1_baz = results.get(b"t1:baz1") + t2_baz = results.get(b"t2:baz2") + self.assertIsNotNone(t1_baz, "Missing t1:baz1 snapshot") + self.assertIsNotNone(t2_baz, "Missing t2:baz2 snapshot") + + # Thread 1 at baz1: should have foo1->bar1->baz1 + self.assertIn("baz1", t1_baz) + self.assertIn("bar1", t1_baz) + self.assertIn("foo1", t1_baz) + self.assertNotIn("blech1", t1_baz) + # No cross-contamination + self.assertNotIn("baz2", t1_baz) + self.assertNotIn("bar2", t1_baz) + self.assertNotIn("foo2", t1_baz) + + # Thread 2 at baz2: should have foo2->bar2->baz2 + self.assertIn("baz2", t2_baz) + self.assertIn("bar2", t2_baz) + self.assertIn("foo2", t2_baz) + self.assertNotIn("blech2", t2_baz) + # No cross-contamination + self.assertNotIn("baz1", t2_baz) + self.assertNotIn("bar1", t2_baz) + self.assertNotIn("foo1", t2_baz) + + # Validate Phase 2: blech snapshots (cache invalidation test) + t1_blech = results.get(b"t1:blech1") + t2_blech = results.get(b"t2:blech2") + self.assertIsNotNone(t1_blech, "Missing t1:blech1 snapshot") + self.assertIsNotNone(t2_blech, "Missing t2:blech2 snapshot") + + # Thread 1 at blech1: bar1/baz1 should be GONE (cache invalidated) + self.assertIn("blech1", t1_blech) + self.assertIn("foo1", t1_blech) + self.assertNotIn( + "bar1", t1_blech, "Cache not invalidated: bar1 still present" + ) + self.assertNotIn( + "baz1", t1_blech, "Cache not invalidated: baz1 still present" + ) + # No cross-contamination + self.assertNotIn("blech2", t1_blech) + + # Thread 2 at blech2: bar2/baz2 should be GONE (cache invalidated) + self.assertIn("blech2", t2_blech) + self.assertIn("foo2", t2_blech) + self.assertNotIn( + "bar2", t2_blech, "Cache not invalidated: bar2 still present" + ) + self.assertNotIn( + "baz2", t2_blech, "Cache not invalidated: baz2 still present" + ) + # No cross-contamination + self.assertNotIn("blech1", t2_blech) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_new_unwinder_with_stale_last_profiled_frame(self): + """Test that a new unwinder returns complete stack when cache lookup misses.""" + script_body = """\ + def level4(): + sock.sendall(b"sync1") + sock.recv(16) + sock.sendall(b"sync2") + sock.recv(16) + + def level3(): + level4() + + def level2(): + level3() + + def level1(): + level2() + + level1() + """ + + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + expected = {"level1", "level2", "level3", "level4"} + + # First unwinder samples - this sets last_profiled_frame in target + unwinder1 = make_unwinder(cache_frames=True) + frames1 = self._sample_frames( + client_socket, unwinder1, b"sync1", b"ack", expected ) - # Verify we got exactly one thread in gil_traces - self.assertEqual( - len(gil_traces), 1, "Should have exactly one GIL holder" + # Create NEW unwinder (empty cache) and sample + # The target still has last_profiled_frame set from unwinder1 + unwinder2 = make_unwinder(cache_frames=True) + frames2 = self._sample_frames( + client_socket, unwinder2, b"sync2", b"done", expected ) - # The GIL holder should be in the all_traces list - gil_thread_id = gil_traces[0][0] - all_thread_ids = [trace[0] for trace in all_traces] - self.assertIn( - gil_thread_id, - all_thread_ids, - "GIL holder should be among all threads", + self.assertIsNotNone(frames1) + self.assertIsNotNone(frames2) + + funcs1 = [f.funcname for f in frames1] + funcs2 = [f.funcname for f in frames2] + + # Both should have all levels + for level in ["level1", "level2", "level3", "level4"]: + self.assertIn(level, funcs1, f"{level} missing from first sample") + self.assertIn(level, funcs2, f"{level} missing from second sample") + + # Should have same stack depth + self.assertEqual( + len(frames1), + len(frames2), + "New unwinder should return complete stack despite stale last_profiled_frame", + ) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_cache_exhaustion(self): + """Test cache works when frame limit (1024) is exceeded. + + FRAME_CACHE_MAX_FRAMES=1024. With 1100 recursive frames, + the cache can't store all of them but should still work. + """ + # Use 1100 to exceed FRAME_CACHE_MAX_FRAMES=1024 + depth = 1100 + script_body = f"""\ +import sys +sys.setrecursionlimit(2000) + +def recurse(n): + if n <= 0: + sock.sendall(b"ready") + sock.recv(16) # wait for ack + sock.sendall(b"ready2") + sock.recv(16) # wait for done + return + recurse(n - 1) + +recurse({depth}) +""" + + with self._target_process(script_body) as ( + p, + client_socket, + make_unwinder, + ): + unwinder_cache = make_unwinder(cache_frames=True) + unwinder_no_cache = make_unwinder(cache_frames=False) + + frames_cached = self._sample_frames( + client_socket, + unwinder_cache, + b"ready", + b"ack", + {"recurse"}, + expected_frames=1102, + ) + # Sample again with no cache for comparison + frames_no_cache = self._sample_frames( + client_socket, + unwinder_no_cache, + b"ready2", + b"done", + {"recurse"}, + expected_frames=1102, ) + self.assertIsNotNone(frames_cached) + self.assertIsNotNone(frames_no_cache) + + # Both should have many recurse frames (> 1024 limit) + cached_count = [f.funcname for f in frames_cached].count("recurse") + no_cache_count = [f.funcname for f in frames_no_cache].count("recurse") + + self.assertGreater( + cached_count, 1000, "Should have >1000 recurse frames" + ) + self.assertGreater( + no_cache_count, 1000, "Should have >1000 recurse frames" + ) + + # Both modes should produce same frame count + self.assertEqual( + len(frames_cached), + len(frames_no_cache), + "Cache exhaustion should not affect stack completeness", + ) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_get_stats(self): + """Test that get_stats() returns statistics when stats=True.""" + script_body = """\ + sock.sendall(b"ready") + sock.recv(16) + """ + + with self._target_process(script_body) as (p, client_socket, _): + unwinder = RemoteUnwinder(p.pid, all_threads=True, stats=True) + _wait_for_signal(client_socket, b"ready") + + # Take a sample + _get_stack_trace_with_retry(unwinder) + + stats = unwinder.get_stats() + client_socket.sendall(b"done") + + # Verify expected keys exist + expected_keys = [ + "total_samples", + "frame_cache_hits", + "frame_cache_misses", + "frame_cache_partial_hits", + "frames_read_from_cache", + "frames_read_from_memory", + "frame_cache_hit_rate", + "batched_read_attempts", + "batched_read_successes", + "batched_read_misses", + "batched_read_segments_requested", + "batched_read_segments_completed", + "batched_read_success_rate", + "batched_read_segment_completion_rate", + ] + for key in expected_keys: + self.assertIn(key, stats) + + self.assertEqual(stats["total_samples"], 1) + + @skip_if_not_supported + @unittest.skipIf( + sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, + "Test only runs on Linux with process_vm_readv support", + ) + def test_get_stats_disabled_raises(self): + """Test that get_stats() raises RuntimeError when stats=False.""" + script_body = """\ + sock.sendall(b"ready") + sock.recv(16) + """ + + with self._target_process(script_body) as (p, client_socket, _): + unwinder = RemoteUnwinder( + p.pid, all_threads=True + ) # stats=False by default + _wait_for_signal(client_socket, b"ready") + + with self.assertRaises(RuntimeError): + unwinder.get_stats() + + client_socket.sendall(b"done") + if __name__ == "__main__": unittest.main() diff --git a/crates/host_env/Cargo.toml b/crates/host_env/Cargo.toml index 7f9634213fc..b7ed30e233b 100644 --- a/crates/host_env/Cargo.toml +++ b/crates/host_env/Cargo.toml @@ -77,8 +77,12 @@ libloading = { workspace = true, optional = true } libffi = { workspace = true, features = ["system"], optional = true } [target.'cfg(target_os = "macos")'.dependencies] +mach2 = { workspace = true } system-configuration = { workspace = true } +[target.'cfg(any(all(target_os = "linux", not(target_env = "uclibc")), target_os = "macos", windows))'.dependencies] +object = { workspace = true } + [target.'cfg(windows)'.dependencies] memchr.workspace = true junction = { workspace = true } @@ -97,6 +101,7 @@ windows-sys = { workspace = true, features = [ "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", + "Win32_System_Diagnostics_ToolHelp", "Win32_System_Environment", "Win32_System_EventLog", "Win32_System_Ole", diff --git a/crates/host_env/src/lib.rs b/crates/host_env/src/lib.rs index 33b5ad60b99..912fd6274f5 100644 --- a/crates/host_env/src/lib.rs +++ b/crates/host_env/src/lib.rs @@ -36,6 +36,7 @@ pub mod locale; #[cfg(feature = "native-certs")] pub mod native_certs; pub mod readline; +pub mod remote_debugging; #[cfg(feature = "ssl")] pub mod ssl; diff --git a/crates/host_env/src/remote_debugging/common.rs b/crates/host_env/src/remote_debugging/common.rs new file mode 100644 index 00000000000..a3f9be6982d --- /dev/null +++ b/crates/host_env/src/remote_debugging/common.rs @@ -0,0 +1,62 @@ +use std::io; + +pub(super) const COOKIE: &[u8; 8] = b"xdebugpy"; + +pub(super) trait RemoteRead { + fn read_exact(&mut self, address: u64, destination: &mut [u8]) -> io::Result<()>; +} + +pub(super) fn has_cookie(reader: &mut impl RemoteRead, address: u64) -> io::Result { + if address == 0 { + return Ok(false); + } + let mut bytes = [0; COOKIE.len()]; + match reader.read_exact(address, &mut bytes) { + Ok(()) => Ok(&bytes == COOKIE), + Err(error) if stops_probe(&error) => Err(error), + Err(_) => Ok(false), + } +} + +pub(super) fn stops_probe(error: &io::Error) -> bool { + matches!( + error.kind(), + io::ErrorKind::PermissionDenied | io::ErrorKind::OutOfMemory + ) +} + +pub(super) fn invalid_data(message: &'static str) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, message) +} + +// These are resource limits, not claims about an unwinder's capabilities. +// Exhaustion is a failed probe, just like inaccessible or missing metadata. +pub(super) struct ReadBudget { + bytes: usize, + calls: usize, +} + +impl ReadBudget { + pub(super) fn new() -> Self { + Self { + bytes: 256 * 1024 * 1024, + calls: 65_536, + } + } + + pub(super) fn consume(&mut self, bytes: usize) -> io::Result<()> { + self.bytes = self.bytes.checked_sub(bytes).ok_or_else(|| { + io::Error::new( + io::ErrorKind::OutOfMemory, + "runtime probe byte budget exhausted", + ) + })?; + self.calls = self.calls.checked_sub(1).ok_or_else(|| { + io::Error::new( + io::ErrorKind::OutOfMemory, + "runtime probe read budget exhausted", + ) + })?; + Ok(()) + } +} diff --git a/crates/host_env/src/remote_debugging/linux.rs b/crates/host_env/src/remote_debugging/linux.rs new file mode 100644 index 00000000000..a237ac47c3a --- /dev/null +++ b/crates/host_env/src/remote_debugging/linux.rs @@ -0,0 +1,190 @@ +use super::{COOKIE, ReadBudget, RemoteRead, has_cookie, invalid_data, object_file, stops_probe}; +use std::{ + ffi::OsStr, + fs::{File, OpenOptions}, + io::{self, Read}, + os::unix::{ffi::OsStrExt, fs::FileExt}, + path::Path, +}; + +const MAX_MAPS_BYTES: u64 = 8 * 1024 * 1024; +const SCAN_CHUNK: usize = 1024 * 1024; + +struct Process { + pid: i32, + memory: Option, + budget: ReadBudget, +} + +impl RemoteRead for Process { + fn read_exact(&mut self, address: u64, destination: &mut [u8]) -> io::Result<()> { + let address = usize::try_from(address) + .map_err(|_| invalid_data("remote address exceeds native pointer width"))?; + address + .checked_add(destination.len()) + .ok_or_else(|| invalid_data("remote read address overflow"))?; + let mut completed = 0; + while completed < destination.len() { + let remaining = &mut destination[completed..]; + self.budget.consume(remaining.len())?; + let current = address + completed; + let count = if let Some(memory) = &self.memory { + memory.read_at(remaining, current as u64)? + } else { + let local = libc::iovec { + iov_base: remaining.as_mut_ptr().cast(), + iov_len: remaining.len(), + }; + let remote = libc::iovec { + iov_base: current as *mut libc::c_void, + iov_len: remaining.len(), + }; + // SAFETY: local names the writable destination slice. The + // remote pointer is only an address for the kernel, never a + // pointer dereferenced by this process. + let result = unsafe { libc::process_vm_readv(self.pid, &local, 1, &remote, 1, 0) }; + if result < 0 { + let error = io::Error::last_os_error(); + if error.raw_os_error() == Some(libc::ENOSYS) { + // Match CPython's fallback only for a missing syscall. + // Permission denials must not select another route. + self.memory = Some( + OpenOptions::new() + .read(true) + .write(true) + .open(format!("/proc/{}/mem", self.pid))?, + ); + continue; + } + return Err(error); + } + result as usize + }; + if count == 0 || count > remaining.len() { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + completed += count; + } + Ok(()) + } +} + +struct Mapping<'a> { + start: u64, + end: u64, + writable: bool, + path: &'a [u8], +} + +fn token<'a>(remaining: &mut &'a [u8]) -> Option<&'a [u8]> { + let source = (*remaining).trim_ascii_start(); + if source.is_empty() { + return None; + } + let end = source + .iter() + .position(u8::is_ascii_whitespace) + .unwrap_or(source.len()); + let (value, rest) = source.split_at(end); + *remaining = rest; + Some(value) +} + +fn mapping(line: &[u8]) -> Option> { + let mut fields = line; + let range = token(&mut fields)?; + let permissions = token(&mut fields)?; + for _ in 0..3 { + token(&mut fields)?; + } + let path = fields.trim_ascii_start(); + if path.is_empty() || (path.starts_with(b"[") && path.ends_with(b"]")) { + return None; + } + let dash = range.iter().position(|byte| *byte == b'-')?; + let parse = |bytes| u64::from_str_radix(core::str::from_utf8(bytes).ok()?, 16).ok(); + let start = parse(&range[..dash])?; + let end = parse(&range[dash + 1..])?; + if start == 0 || end <= start { + return None; + } + Some(Mapping { + start, + end, + writable: permissions.starts_with(b"rw"), + path, + }) +} + +fn scan_deleted(process: &mut Process, mapping: &Mapping<'_>) -> io::Result> { + let mut buffer = vec![0; SCAN_CHUNK]; + let mut address = mapping.start; + while address < mapping.end { + let wanted = usize::try_from((mapping.end - address).min(SCAN_CHUNK as u64)) + .map_err(|_| invalid_data("mapping scan size overflow"))?; + match process.read_exact(address, &mut buffer[..wanted]) { + Ok(()) => { + if let Some(offset) = buffer[..wanted] + .windows(COOKIE.len()) + .position(|bytes| bytes == COOKIE) + { + return Ok(Some(address + offset as u64)); + } + } + Err(error) if stops_probe(&error) => return Err(error), + Err(_) => {} + } + let overlap = COOKIE.len() - 1; + if wanted <= overlap { + break; + } + address = address + .checked_add((wanted - overlap) as u64) + .ok_or_else(|| invalid_data("mapping scan address overflow"))?; + } + Ok(None) +} + +pub(super) fn probe(pid: i32) -> io::Result { + let mut maps = Vec::new(); + File::open(format!("/proc/{pid}/maps"))? + .take(MAX_MAPS_BYTES + 1) + .read_to_end(&mut maps)?; + if maps.len() as u64 > MAX_MAPS_BYTES { + return Err(invalid_data("process map exceeds its size bound")); + } + let mut process = Process { + pid, + memory: None, + budget: ReadBudget::new(), + }; + for line in maps.split(|byte| *byte == b'\n') { + process.budget.consume(0)?; + let Some(mapping) = mapping(line) else { + continue; + }; + let path = Path::new(OsStr::from_bytes(mapping.path)); + let Some(name) = path.file_name() else { + continue; + }; + if !name.as_bytes().windows(6).any(|bytes| bytes == b"python") { + continue; + } + let candidate = if mapping.path.ends_with(b" (deleted)") { + if !mapping.writable { + continue; + } + scan_deleted(&mut process, &mapping) + } else { + object_file::read(path, &mut process.budget).and_then(|bytes| { + object_file::section_address(&bytes, mapping.start, object::BinaryFormat::Elf) + }) + }; + match candidate { + Ok(Some(address)) if has_cookie(&mut process, address)? => return Ok(true), + Err(error) if stops_probe(&error) => return Err(error), + _ => {} + } + } + Ok(false) +} diff --git a/crates/host_env/src/remote_debugging/macos.rs b/crates/host_env/src/remote_debugging/macos.rs new file mode 100644 index 00000000000..c43916628c1 --- /dev/null +++ b/crates/host_env/src/remote_debugging/macos.rs @@ -0,0 +1,281 @@ +use super::{ReadBudget, RemoteRead, has_cookie, invalid_data, object_file, stops_probe}; +use core::mem::size_of; +use mach2::{ + kern_return::{ + KERN_INVALID_ADDRESS, KERN_NO_ACCESS, KERN_PROTECTION_FAILURE, KERN_RESOURCE_SHORTAGE, + KERN_SUCCESS, kern_return_t, + }, + mach_port::mach_port_deallocate, + port::{MACH_PORT_DEAD, MACH_PORT_NULL, mach_port_t}, + traps::{mach_task_self, task_for_pid}, + vm::{mach_vm_read_overwrite, mach_vm_region}, + vm_prot::{VM_PROT_EXECUTE, VM_PROT_READ}, + vm_region::{VM_REGION_BASIC_INFO_64, vm_region_basic_info_64}, +}; +use object::BinaryFormat; +use std::{ffi::OsStr, io, os::unix::ffi::OsStrExt, path::Path}; + +const MAX_FAT_ARCHES: usize = 64; + +struct OwnedPort(mach_port_t); + +impl Drop for OwnedPort { + fn drop(&mut self) { + if self.0 != MACH_PORT_NULL && self.0 != MACH_PORT_DEAD { + // task_for_pid and mach_vm_region each return an owned send right. + // mach_task_self is borrowed and must not itself be deallocated. + unsafe { mach_port_deallocate(mach_task_self(), self.0) }; + } + } +} + +struct Process { + task: OwnedPort, + budget: ReadBudget, +} + +fn mach_error(code: kern_return_t) -> io::Error { + // Mach error codes carry the detailed code in their low fourteen bits. + let kind = match code & 0x3fff { + KERN_PROTECTION_FAILURE | KERN_NO_ACCESS => io::ErrorKind::PermissionDenied, + KERN_RESOURCE_SHORTAGE => io::ErrorKind::OutOfMemory, + KERN_INVALID_ADDRESS => io::ErrorKind::UnexpectedEof, + _ => io::ErrorKind::Other, + }; + io::Error::new(kind, format!("Mach runtime probe failed: {code}")) +} + +impl RemoteRead for Process { + fn read_exact(&mut self, address: u64, destination: &mut [u8]) -> io::Result<()> { + if destination.is_empty() { + return Ok(()); + } + address + .checked_add(destination.len() as u64 - 1) + .ok_or_else(|| invalid_data("remote read address overflow"))?; + self.budget.consume(destination.len())?; + let mut count = 0; + // Only the local destination is a dereferenceable pointer. The remote + // address remains an integer interpreted by the kernel. + let result = unsafe { + mach_vm_read_overwrite( + self.task.0, + address, + destination.len() as u64, + destination.as_mut_ptr() as usize as u64, + &mut count, + ) + }; + if result != KERN_SUCCESS { + return Err(mach_error(result)); + } + if count != destination.len() as u64 { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + Ok(()) + } +} + +fn word(bytes: &[u8], offset: usize) -> io::Result<[u8; N]> { + let end = offset + .checked_add(N) + .ok_or_else(|| invalid_data("Mach-O field offset overflow"))?; + bytes + .get(offset..end) + .and_then(|value| value.try_into().ok()) + .ok_or_else(|| invalid_data("truncated Mach-O field")) +} + +fn read_u32(bytes: &[u8], offset: usize, little: bool) -> io::Result { + let value = word(bytes, offset)?; + Ok(if little { + u32::from_le_bytes(value) + } else { + u32::from_be_bytes(value) + }) +} + +fn sysctl_int(name: &core::ffi::CStr) -> io::Result { + let mut value = 0i32; + let mut length = size_of::(); + let result = unsafe { + libc::sysctlbyname( + name.as_ptr(), + (&mut value as *mut i32).cast(), + &mut length, + core::ptr::null_mut(), + 0, + ) + }; + if result != 0 { + return Err(io::Error::last_os_error()); + } + if length != size_of::() { + return Err(invalid_data("invalid CPU sysctl size")); + } + Ok(value) +} + +fn mach_slice(bytes: &[u8]) -> io::Result> { + let magic = u32::from_be_bytes(word(bytes, 0)?); + let little = match magic { + object::macho::FAT_MAGIC => false, + object::macho::FAT_CIGAM => true, + _ => return Ok(Some(bytes)), + }; + let count = usize::try_from(read_u32(bytes, 4, little)?) + .map_err(|_| invalid_data("Mach-O architecture count overflow"))?; + if count > MAX_FAT_ARCHES { + return Err(invalid_data("Mach-O architecture count exceeds its bound")); + } + let entry_size = 20; + let table_end = count + .checked_mul(entry_size) + .and_then(|size| size.checked_add(8)) + .ok_or_else(|| invalid_data("Mach-O architecture table overflow"))?; + if table_end > bytes.len() { + return Err(invalid_data("truncated Mach-O architecture table")); + } + // Match the pinned CPython locator's host-CPU selection, rather than guessing + // from the inspector's build target (which may itself run under translation). + let cpu = sysctl_int(c"hw.cputype")? as u32; + let capable = sysctl_int(c"hw.cpu64bit_capable")?; + let cpu = cpu + | if capable != 0 { + object::macho::CPU_ARCH_ABI64 + } else { + 0 + }; + for index in 0..count { + // The bounded, validated architecture table makes these offsets safe. + let entry = 8 + index * entry_size; + if read_u32(bytes, entry, little)? != cpu { + continue; + } + let offset = u64::from(read_u32(bytes, entry + 8, little)?); + let length = u64::from(read_u32(bytes, entry + 12, little)?); + let end = offset + .checked_add(length) + .ok_or_else(|| invalid_data("Mach-O architecture range overflow"))?; + let offset = usize::try_from(offset) + .map_err(|_| invalid_data("Mach-O architecture offset overflow"))?; + let end = + usize::try_from(end).map_err(|_| invalid_data("Mach-O architecture end overflow"))?; + if offset < table_end || length == 0 { + return Err(invalid_data("invalid Mach-O architecture range")); + } + return bytes + .get(offset..end) + .map(Some) + .ok_or_else(|| invalid_data("truncated Mach-O architecture")); + } + Ok(None) +} + +fn candidate(process: &mut Process, path: &Path, base: u64) -> io::Result { + let bytes = object_file::read(path, &mut process.budget)?; + let Some(bytes) = mach_slice(&bytes)? else { + return Ok(false); + }; + let Some(address) = object_file::section_address(bytes, base, BinaryFormat::MachO)? else { + return Ok(false); + }; + has_cookie(process, address) +} + +fn search(process: &mut Process, pid: i32, prefix: &[u8]) -> io::Result { + let mut address = 0; + loop { + process.budget.consume(0)?; + let requested = address; + let mut size = 0; + let mut info = vm_region_basic_info_64::default(); + let mut count = vm_region_basic_info_64::count(); + let mut object = MACH_PORT_NULL; + let result = unsafe { + mach_vm_region( + process.task.0, + &mut address, + &mut size, + VM_REGION_BASIC_INFO_64, + (&mut info as *mut vm_region_basic_info_64).cast(), + &mut count, + &mut object, + ) + }; + // Even irrelevant regions may return an owned memory-object port. + let _object = OwnedPort(object); + if result == KERN_INVALID_ADDRESS { + return Ok(false); + } + if result != KERN_SUCCESS { + return Err(mach_error(result)); + } + if count < vm_region_basic_info_64::count() || address < requested || size == 0 { + return Err(invalid_data("invalid Mach memory region")); + } + let next = address + .checked_add(size) + .ok_or_else(|| invalid_data("Mach memory region address overflow"))?; + if info.protection & (VM_PROT_READ | VM_PROT_EXECUTE) == (VM_PROT_READ | VM_PROT_EXECUTE) { + let mut path = [0u8; libc::MAXPATHLEN as usize + 1]; + process.budget.consume(0)?; + let length = unsafe { + // A zero return can describe an anonymous mapping; clear errno + // first so an older failure cannot be mistaken for a denial. + *libc::__error() = 0; + libc::proc_regionfilename( + pid, + address, + path.as_mut_ptr().cast(), + libc::MAXPATHLEN as u32, + ) + }; + if length <= 0 { + let error = io::Error::last_os_error(); + if stops_probe(&error) { + return Err(error); + } + } else if (length as usize) < path.len() + && let Some(end) = path.iter().position(|&byte| byte == 0) + && end < libc::MAXPATHLEN as usize + { + let path = &path[..end]; + let name = path.rsplit(|&byte| byte == b'/').next().unwrap_or(path); + if name.starts_with(prefix) { + match candidate(process, Path::new(OsStr::from_bytes(path)), address) { + Ok(true) => return Ok(true), + Err(error) if stops_probe(&error) => return Err(error), + Ok(false) | Err(_) => {} + } + } + } + } + address = next; + } +} + +pub(super) fn probe(pid: i32) -> io::Result { + let mut task = MACH_PORT_NULL; + let result = unsafe { task_for_pid(mach_task_self(), pid, &mut task) }; + let task = OwnedPort(task); + if result != KERN_SUCCESS || task.0 == MACH_PORT_NULL || task.0 == MACH_PORT_DEAD { + // CPython classifies every task_for_pid failure as a permission failure. + return Err(io::Error::new( + io::ErrorKind::PermissionDenied, + format!("cannot acquire task port for PID {pid}: {result}"), + )); + } + let mut process = Process { + task, + budget: ReadBudget::new(), + }; + for prefix in [b"libpython".as_slice(), b"python", b"Python"] { + match search(&mut process, pid, prefix) { + Ok(true) => return Ok(true), + Err(error) if stops_probe(&error) => return Err(error), + Ok(false) | Err(_) => {} + } + } + Ok(false) +} diff --git a/crates/host_env/src/remote_debugging/mod.rs b/crates/host_env/src/remote_debugging/mod.rs new file mode 100644 index 00000000000..c65101a046e --- /dev/null +++ b/crates/host_env/src/remote_debugging/mod.rs @@ -0,0 +1,56 @@ +//! Read-only discovery of CPython's exported runtime metadata. +//! +//! A successful cookie probe identifies a runtime; it does not validate its +//! debug offsets or promise that stack unwinding is supported. + +#[cfg(all(target_os = "linux", not(target_env = "uclibc")))] +mod linux; +#[cfg(target_os = "macos")] +mod macos; +#[cfg(windows)] +mod windows; + +#[cfg(any( + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "macos", + windows +))] +mod object_file; + +#[cfg(any( + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "macos", + windows +))] +mod common; + +#[cfg(all(target_os = "linux", not(target_env = "uclibc")))] +use common::COOKIE; +#[cfg(any( + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "macos", + windows +))] +use common::{ReadBudget, RemoteRead, has_cookie, invalid_data, stops_probe}; + +pub const PROCESS_VM_READV_SUPPORTED: u8 = + cfg!(all(target_os = "linux", not(target_env = "uclibc"))) as u8; + +pub fn is_python_process(pid: i32) -> bool { + #[cfg(all(target_os = "linux", not(target_env = "uclibc")))] + let result = linux::probe(pid); + #[cfg(target_os = "macos")] + let result = macos::probe(pid); + #[cfg(windows)] + let result = windows::probe(pid); + #[cfg(not(any( + all(target_os = "linux", not(target_env = "uclibc")), + target_os = "macos", + windows + )))] + let result: Result = { + let _ = pid; + Err(std::io::ErrorKind::Unsupported.into()) + }; + result.unwrap_or(false) +} diff --git a/crates/host_env/src/remote_debugging/object_file.rs b/crates/host_env/src/remote_debugging/object_file.rs new file mode 100644 index 00000000000..37678b93a9c --- /dev/null +++ b/crates/host_env/src/remote_debugging/object_file.rs @@ -0,0 +1,94 @@ +use super::{ReadBudget, invalid_data}; +use object::{BinaryFormat, Object, ObjectSection, ObjectSegment}; +use std::{fs::OpenOptions, io, io::Read, path::Path}; + +const MAX_OBJECT_BYTES: u64 = 256 * 1024 * 1024; + +pub(super) fn read(path: &Path, budget: &mut ReadBudget) -> io::Result> { + budget.consume(0)?; + let mut options = OpenOptions::new(); + options.read(true); + #[cfg(unix)] + { + use std::os::unix::fs::OpenOptionsExt; + // A mapped pathname can be replaced with a FIFO while being inspected. + options.custom_flags(libc::O_NONBLOCK); + } + let file = options.open(path)?; + let metadata = file.metadata()?; + if !metadata.is_file() || metadata.len() > MAX_OBJECT_BYTES { + return Err(invalid_data( + "runtime candidate is not a bounded regular file", + )); + } + let mut bytes = Vec::new(); + bytes + .try_reserve_exact(metadata.len() as usize) + .map_err(|_| io::Error::from(io::ErrorKind::OutOfMemory))?; + let result = file.take(MAX_OBJECT_BYTES + 1).read_to_end(&mut bytes); + // A failed read may already have appended bytes. Account for them before + // propagating the error, as well as before rejecting a grown file. + budget.consume(bytes.len())?; + result?; + if bytes.len() as u64 > MAX_OBJECT_BYTES { + return Err(invalid_data("runtime candidate grew beyond its size bound")); + } + Ok(bytes) +} + +pub(super) fn section_address( + bytes: &[u8], + mapping_base: u64, + format: BinaryFormat, +) -> io::Result> { + let file = object::File::parse(bytes).map_err(|_| invalid_data("invalid object file"))?; + if file.format() != format { + return Ok(None); + } + let name = match format { + BinaryFormat::Elf => b".PyRuntime".as_slice(), + BinaryFormat::MachO => b"PyRuntime".as_slice(), + BinaryFormat::Pe => b"PyRuntim".as_slice(), + _ => return Ok(None), + }; + let section = if format == BinaryFormat::MachO { + file.sections().find(|section| { + section.name_bytes().ok() == Some(name) + && section.segment_name_bytes().ok() == Some(Some(b"__DATA".as_slice())) + }) + } else { + file.section_by_name_bytes(name) + }; + let Some(section) = section else { + return Ok(None); + }; + let image_base = match format { + BinaryFormat::Elf => { + // The ELF reader's segment iterator contains only PT_LOAD entries. + let first = file + .segments() + .next() + .ok_or_else(|| invalid_data("ELF image has no load segment"))?; + let alignment = first.align(); + if alignment == 0 || !alignment.is_power_of_two() { + return Err(invalid_data("invalid ELF load alignment")); + } + first.address() - first.address() % alignment + } + BinaryFormat::MachO => file + .segments() + .find(|segment| segment.name_bytes().ok() == Some(Some(b"__TEXT".as_slice()))) + .ok_or_else(|| invalid_data("Mach-O image has no text segment"))? + .address(), + BinaryFormat::Pe => file.relative_address_base(), + _ => unreachable!(), + }; + let relative = section + .address() + .checked_sub(image_base) + .ok_or_else(|| invalid_data("runtime section precedes its image base"))?; + let address = mapping_base + .checked_add(relative) + .ok_or_else(|| invalid_data("runtime section address overflow"))?; + Ok((address != 0).then_some(address)) +} diff --git a/crates/host_env/src/remote_debugging/windows.rs b/crates/host_env/src/remote_debugging/windows.rs new file mode 100644 index 00000000000..77a55fc0a5c --- /dev/null +++ b/crates/host_env/src/remote_debugging/windows.rs @@ -0,0 +1,178 @@ +use super::{ReadBudget, RemoteRead, has_cookie, invalid_data, object_file, stops_probe}; +use core::mem::size_of; +use object::BinaryFormat; +use std::{ffi::OsString, io, os::windows::ffi::OsStringExt, path::PathBuf}; +use windows_sys::Win32::{ + Foundation::{ + CloseHandle, ERROR_BAD_LENGTH, ERROR_NO_MORE_FILES, GetLastError, HANDLE, + INVALID_HANDLE_VALUE, + }, + System::{ + Diagnostics::{ + Debug::ReadProcessMemory, + ToolHelp::{ + CreateToolhelp32Snapshot, MODULEENTRY32W, Module32FirstW, Module32NextW, + TH32CS_SNAPMODULE, + }, + }, + Threading::{ + OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_SUSPEND_RESUME, PROCESS_VM_OPERATION, + PROCESS_VM_READ, PROCESS_VM_WRITE, + }, + }, +}; + +const MAX_SNAPSHOT_ATTEMPTS: usize = 16; +const MAX_MODULES: usize = 16_384; + +struct OwnedHandle(HANDLE); + +impl Drop for OwnedHandle { + fn drop(&mut self) { + // Only successfully acquired process and snapshot handles are owned here. + unsafe { CloseHandle(self.0) }; + } +} + +struct Process { + handle: OwnedHandle, + budget: ReadBudget, +} + +impl RemoteRead for Process { + fn read_exact(&mut self, address: u64, mut destination: &mut [u8]) -> io::Result<()> { + if destination.is_empty() { + return Ok(()); + } + let end = address + .checked_add(destination.len() as u64 - 1) + .ok_or_else(|| invalid_data("remote read address overflow"))?; + usize::try_from(end).map_err(|_| invalid_data("remote address exceeds pointer width"))?; + let mut address = usize::try_from(address) + .map_err(|_| invalid_data("remote address exceeds pointer width"))?; + while !destination.is_empty() { + self.budget.consume(destination.len())?; + let mut count = 0; + // The address is passed to the OS, never dereferenced in this process. + let success = unsafe { + ReadProcessMemory( + self.handle.0, + address as *const _, + destination.as_mut_ptr().cast(), + destination.len(), + &mut count, + ) + }; + if success == 0 { + // In particular, an ERROR_PARTIAL_COPY failure is not a successful + // short read. CPython only continues after successful reads. + return Err(io::Error::last_os_error()); + } + if count == 0 { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + if count > destination.len() { + return Err(invalid_data("remote read exceeded its destination")); + } + destination = &mut destination[count..]; + if !destination.is_empty() { + address = address + .checked_add(count) + .ok_or_else(|| invalid_data("remote read address overflow"))?; + } + } + Ok(()) + } +} + +fn snapshot(pid: u32, budget: &mut ReadBudget) -> io::Result { + for _ in 0..MAX_SNAPSHOT_ATTEMPTS { + budget.consume(0)?; + // Keep CPython's module selection flags, including its cross-bitness limits. + let handle = unsafe { CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pid) }; + if handle != INVALID_HANDLE_VALUE { + return Ok(OwnedHandle(handle)); + } + let error = unsafe { GetLastError() }; + if error != ERROR_BAD_LENGTH { + return Err(io::Error::from_raw_os_error(error as i32)); + } + } + Err(io::Error::from_raw_os_error(ERROR_BAD_LENGTH as i32)) +} + +fn terminated(value: &[u16]) -> io::Result<&[u16]> { + let end = value + .iter() + .position(|&unit| unit == 0) + .ok_or_else(|| invalid_data("unterminated module name or path"))?; + Ok(&value[..end]) +} + +fn candidate(process: &mut Process, module: &MODULEENTRY32W) -> io::Result { + const PYTHON: &[u16] = &[112, 121, 116, 104, 111, 110]; + let name = terminated(&module.szModule)?; + if !name.windows(PYTHON.len()).any(|part| part == PYTHON) { + return Ok(false); + } + let path = PathBuf::from(OsString::from_wide(terminated(&module.szExePath)?)); + let bytes = object_file::read(&path, &mut process.budget)?; + let base = module.modBaseAddr as usize as u64; + let Some(address) = object_file::section_address(&bytes, base, BinaryFormat::Pe)? else { + return Ok(false); + }; + has_cookie(process, address) +} + +pub(super) fn probe(pid: i32) -> io::Result { + // CPython requests these rights even for this read-only operation. Reducing + // them could make detection succeed where CPython's handle initialization fails. + // The DWORD conversion also preserves the Windows API's signed-PID behavior. + let handle = unsafe { + OpenProcess( + PROCESS_VM_READ + | PROCESS_VM_WRITE + | PROCESS_VM_OPERATION + | PROCESS_QUERY_INFORMATION + | PROCESS_SUSPEND_RESUME, + 0, + pid as u32, + ) + }; + if handle.is_null() { + return Err(io::Error::last_os_error()); + } + let mut process = Process { + handle: OwnedHandle(handle), + budget: ReadBudget::new(), + }; + let snapshot = snapshot(pid as u32, &mut process.budget)?; + let mut module = MODULEENTRY32W { + dwSize: size_of::() as u32, + ..Default::default() + }; + process.budget.consume(0)?; + let mut success = unsafe { Module32FirstW(snapshot.0, &mut module) }; + for _ in 0..MAX_MODULES { + if success == 0 { + let error = unsafe { GetLastError() }; + return if error == ERROR_NO_MORE_FILES { + Ok(false) + } else { + Err(io::Error::from_raw_os_error(error as i32)) + }; + } + match candidate(&mut process, &module) { + Ok(true) => return Ok(true), + Err(error) if stops_probe(&error) => return Err(error), + Ok(false) | Err(_) => {} + } + process.budget.consume(0)?; + module.dwSize = size_of::() as u32; + success = unsafe { Module32NextW(snapshot.0, &mut module) }; + } + Err(io::Error::new( + io::ErrorKind::OutOfMemory, + "runtime probe module budget exhausted", + )) +} diff --git a/crates/stdlib/src/_remote_debugging.rs b/crates/stdlib/src/_remote_debugging.rs index 898bb9efc65..ba98b3b3b55 100644 --- a/crates/stdlib/src/_remote_debugging.rs +++ b/crates/stdlib/src/_remote_debugging.rs @@ -7,7 +7,7 @@ mod _remote_debugging { use super::binary; use crate::vm::{ AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult, VirtualMachine, - builtins::{PyBaseExceptionRef, PyDictRef, PyList, PyTuple, PyType}, + builtins::{PyBaseExceptionRef, PyDictRef, PyFloat, PyList, PyTuple, PyType}, common::{lock::PyMutex, rc::PyRc}, convert::{IntoPyException, ToPyObject}, function::{FsPath, FuncArgs}, @@ -117,6 +117,10 @@ mod _remote_debugging { #[pyclass(with(PyStructSequence))] impl InterpreterInfo {} + #[pyattr] + const PROCESS_VM_READV_SUPPORTED: u8 = + rustpython_host_env::remote_debugging::PROCESS_VM_READV_SUPPORTED; + #[pyattr] const THREAD_STATUS_HAS_GIL: u8 = 1; #[pyattr] @@ -130,6 +134,22 @@ mod _remote_debugging { #[pyattr] const THREAD_STATUS_MAIN_THREAD: u8 = 32; + #[derive(FromArgs)] + struct IsPythonProcessArgs { + #[pyarg(any)] + pid: PyObjectRef, + } + + /// Check if a process is a Python process. + #[pyfunction] + fn is_python_process(args: IsPythonProcessArgs, vm: &VirtualMachine) -> PyResult { + // Finish argument binding before invoking a user-defined __index__. + let pid: i32 = args.pid.try_into_value(vm)?; + Ok(rustpython_host_env::remote_debugging::is_python_process( + pid, + )) + } + #[pyfunction] fn zstd_available() -> bool { false @@ -293,20 +313,36 @@ mod _remote_debugging { #[derive(FromArgs)] struct BinaryExitArgs { - #[pyarg(positional, optional)] + #[pyarg(any, optional)] exc_type: Option, - #[pyarg(positional, optional)] + #[pyarg(any, optional, name = "exc_val")] _exc_value: Option, - #[pyarg(positional, optional)] + #[pyarg(any, optional, name = "exc_tb")] _exc_tb: Option, } + #[derive(FromArgs)] + struct SampleArgs { + #[pyarg(any)] + stack_frames: PyObjectRef, + #[pyarg(any)] + timestamp_us: PyObjectRef, + } + + fn profile_float(obj: &PyObject, vm: &VirtualMachine) -> PyResult { + // PyFloat_AsDouble reads float subclasses without calling __float__. + Ok(match obj.downcast_ref::() { + Some(value) => value.to_f64(), + None => obj.try_float(vm)?.to_f64(), + }) + } + #[derive(FromArgs)] struct SetStatsArgs { #[pyarg(any)] - duration_sec: f64, + duration_sec: PyObjectRef, #[pyarg(any)] - sample_rate: f64, + sample_rate: PyObjectRef, #[pyarg(any, optional)] error_rate: Option, #[pyarg(any, optional)] @@ -316,19 +352,19 @@ mod _remote_debugging { fn optional_float(value: Option, vm: &VirtualMachine) -> PyResult> { value .filter(|value| !vm.is_none(value)) - .map(|value| value.try_float(vm).map(|value| value.to_f64())) + .map(|value| profile_float(&value, vm)) .transpose() } #[pyclass(with(Constructor, Initializer))] impl BinaryWriter { #[pymethod] - fn write_sample( - zelf: &Py, - stack_frames: PyObjectRef, - timestamp_us: u64, - vm: &VirtualMachine, - ) -> PyResult<()> { + fn write_sample(zelf: &Py, args: SampleArgs, vm: &VirtualMachine) -> PyResult<()> { + let SampleArgs { + stack_frames, + timestamp_us, + } = args; + let timestamp_us = timestamp_us.try_into_value::(vm)?; if zelf.state.lock().writer.is_none() { return Err(vm.new_value_error("Writer is closed")); } @@ -378,13 +414,15 @@ mod _remote_debugging { #[pymethod] fn set_stats(zelf: &Py, args: SetStatsArgs, vm: &VirtualMachine) -> PyResult<()> { + let duration_sec = profile_float(&args.duration_sec, vm)?; + let sample_rate = profile_float(&args.sample_rate, vm)?; let is_open = zelf.state.lock().writer.is_some(); if !is_open { return Err(vm.new_value_error("Writer is closed")); } let profile = binary::ProfileStats { - duration_sec: args.duration_sec, - sample_rate: args.sample_rate, + duration_sec, + sample_rate, error_rate: optional_float(args.error_rate, vm)?, missed_samples: optional_float(args.missed_samples, vm)?, }; diff --git a/crates/stdlib/src/_remote_debugging/binary.rs b/crates/stdlib/src/_remote_debugging/binary.rs index d77b66226d9..1d6158ee250 100644 --- a/crates/stdlib/src/_remote_debugging/binary.rs +++ b/crates/stdlib/src/_remote_debugging/binary.rs @@ -792,7 +792,7 @@ impl Reader { let mut footer = Cursor::new(&data[footer_start..]); let string_count = endian.u32(footer.fixed()?); let frame_count = endian.u32(footer.fixed()?); - let (profile_stats, tables_end) = parse_profile_stats(&data, endian, footer_start)?; + let (profile_stats, _) = parse_profile_stats(&data, endian, footer_start)?; for (name, offset) in [("string", string_offset), ("frame", frame_offset)] { if offset > data.len() as u64 { return Err(invalid(format!( @@ -815,15 +815,14 @@ impl Reader { .map_err(|_| Error::Overflow("File is too large to read"))?; let frame_offset = usize::try_from(frame_offset) .map_err(|_| Error::Overflow("File is too large to read"))?; - if frame_offset > tables_end { - return Err(invalid("Frame table overlaps file trailer")); - } if compression_type == 1 { return Err(Error::Runtime( "File uses zstd compression but zstd support not compiled in", )); } - let mut string_data = Cursor::new(&data[string_offset..frame_offset]); + // CPython bounds table entries by the remaining file bytes, including + // subsequent sections. Keep the count and cursor checks anchored to EOF. + let mut string_data = Cursor::new(&data[string_offset..]); validate_count("string", string_count, string_data.remaining(), 1)?; let mut strings = Vec::new(); reserve(&mut strings, string_count as usize)?; @@ -836,7 +835,7 @@ impl Reader { .map_err(|_| invalid("String table overflow"))?; strings.push(decode_utf8_replacing(bytes)?); } - let mut frame_data = Cursor::new(&data[frame_offset..tables_end]); + let mut frame_data = Cursor::new(&data[frame_offset..]); validate_count("frame", frame_count, frame_data.remaining(), 7)?; let mut frames = Vec::new(); reserve(&mut frames, frame_count as usize)?; diff --git a/scripts/update_lib/cmd_migrate.py b/scripts/update_lib/cmd_migrate.py index 949fe831580..efbce2d0398 100644 --- a/scripts/update_lib/cmd_migrate.py +++ b/scripts/update_lib/cmd_migrate.py @@ -19,6 +19,12 @@ Use a host Python that parses the source version (Python 3.15 for 3.15 syntax), as required by the existing marker extraction and application steps. +For additive updates, --assignments or --functions selects contiguous canonical +top-level definitions before an unchanged anchor. Both input SHA256 pins are +required. --exports copies only complete selected __all__ lines. Function +dependencies are checked without importing or executing either module; unchanged +imports and simple assignments are supported, not arbitrary existing helpers. + Canonical removals are a separate, explicit operation. JSON source metadata pins path (Lib/test/test_*), commit and tree; removals maps explicit relative filenames to sha256 and upstream_commit. The caller must first verify and @@ -43,6 +49,7 @@ import re import shutil import subprocess +import symtable import sys sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) @@ -57,6 +64,68 @@ def _preserve_block(old, new, canonical, local): return new.replace(canonical, local, 1) +def _single_assignment(tree, name): + matches = [ + node + for node in ast.walk(tree) + if isinstance(node, ast.Name) + and isinstance(node.ctx, ast.Store) + and node.id == name + ] + nodes = [ + node + for node in tree.body + if isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + and node.targets[0].id == name + ] + rebound = any( + isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) + and node.name == name + or isinstance(node, ast.alias) + and (node.asname or node.name.split(".")[0]) == name + for node in ast.walk(tree) + ) + if len(nodes) != 1 or matches != [nodes[0].targets[0]] or rebound: + raise ValueError(f"Expected one top-level assignment to {name}") + return nodes[0] + + +def _selected_export_addition(src, dst, source_lines, lines, exports): + src_all, dst_all = ( + _single_assignment(src, "__all__"), + _single_assignment(dst, "__all__"), + ) + if not isinstance(src_all.value, ast.List) or not isinstance( + dst_all.value, ast.List + ): + raise ValueError("__all__ must be a literal list") + if any( + not isinstance(node, ast.Constant) or not isinstance(node.value, str) + for node in src_all.value.elts + dst_all.value.elts + ): + raise ValueError("__all__ entries must be literal strings") + src_values, dst_values = ( + ast.literal_eval(src_all.value), + ast.literal_eval(dst_all.value), + ) + if any(src_values.count(name) != 1 or name in dst_values for name in exports): + raise ValueError( + "Exports must occur once in canonical __all__ and be absent locally" + ) + export_nodes = [node for node in src_all.value.elts if node.value in exports] + export_lines = sorted({node.lineno - 1 for node in export_nodes}) + fragment = "".join(source_lines[line] for line in export_lines) + if ast.literal_eval("[\n" + fragment + "]") != exports: + raise ValueError( + "Canonical export lines include unselected entries or differ in order" + ) + if lines[dst_all.lineno - 1].strip() != "__all__ = [": + raise ValueError("Expected a multiline destination __all__ list") + return dst_all.lineno, [fragment] + + def copy_selected_assignments(source, destination, names, exports): """Copy contiguous canonical assignments and export lines, failing closed. @@ -74,34 +143,7 @@ def copy_selected_assignments(source, destination, names, exports): raise ValueError("Exports must be unique selected assignment names") src, dst = ast.parse(source), ast.parse(destination) - def assignment(tree, name): - matches = [ - node - for node in ast.walk(tree) - if isinstance(node, ast.Name) - and isinstance(node.ctx, ast.Store) - and node.id == name - ] - nodes = [ - node - for node in tree.body - if isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - and node.targets[0].id == name - ] - rebound = any( - isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) - and node.name == name - or isinstance(node, ast.alias) - and (node.asname or node.name.split(".")[0]) == name - for node in ast.walk(tree) - ) - if len(nodes) != 1 or matches != [nodes[0].targets[0]] or rebound: - raise ValueError(f"Expected one top-level assignment to {name}") - return nodes[0] - - selected = [assignment(src, name) for name in names] + selected = [_single_assignment(src, name) for name in names] start = src.body.index(selected[0]) if src.body[start : start + len(names)] != selected or start + len(names) == len( src.body @@ -223,34 +265,288 @@ def assignment(tree, name): ) ] if exports: - src_all, dst_all = assignment(src, "__all__"), assignment(dst, "__all__") - if not isinstance(src_all.value, ast.List) or not isinstance( - dst_all.value, ast.List + additions.append( + _selected_export_addition(src, dst, source_lines, lines, exports) + ) + for offset, fragment in sorted(additions, reverse=True): + lines[offset:offset] = fragment + result = "".join(lines) + ast.parse(result) + return result + + +def _module_bindings(tree): + """Collect possible module bindings without mistaking function locals for them.""" + bindings = collections.defaultdict(list) + + def visit(node): + children = ast.iter_child_nodes(node) + names = [] + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + names = [node.name] + # A declaration in a nested scope can rebind a module dependency. + for child in ast.walk(node): + if isinstance(child, ast.Global): + for name in child.names: + bindings[name].append(child) + children = list(node.decorator_list) + if isinstance(node, ast.ClassDef): + children += node.bases + node.keywords + else: + children += node.args.defaults + [ + value for value in node.args.kw_defaults if value is not None + ] + elif isinstance(node, ast.Lambda): + children = node.args.defaults + [ + value for value in node.args.kw_defaults if value is not None + ] + elif isinstance(node, ast.Name) and isinstance(node.ctx, (ast.Store, ast.Del)): + names = [node.id] + elif isinstance(node, ast.alias): + names = [node.asname or node.name.split(".")[0]] + elif isinstance(node, (ast.ExceptHandler, ast.MatchAs, ast.MatchStar)): + names = [node.name] if node.name is not None else [] + elif isinstance(node, ast.MatchMapping) and node.rest is not None: + names = [node.rest] + for name in names: + bindings[name].append(node) + for child in children: + visit(child) + + visit(tree) + return bindings + + +def _function_dependencies(fragment): + """Find definition-time and deferred global reads without executing source.""" + table = symtable.symtable(fragment, "", "exec") + if any( + symbol.is_assigned() and not symbol.is_namespace() + for symbol in table.get_symbols() + ): + raise ValueError("Function headers cannot bind additional module names") + immediate = { + symbol.get_name() for symbol in table.get_symbols() if symbol.is_referenced() + } + deferred = set() + + def visit(scope): + for symbol in scope.get_symbols(): + if symbol.is_global(): + if symbol.is_assigned() or symbol.is_imported(): + raise ValueError("Selected functions cannot rebind module globals") + if symbol.is_referenced(): + deferred.add(symbol.get_name()) + for child in scope.get_children(): + visit(child) + + for child in table.get_children(): + visit(child) + if (immediate | deferred) & {"eval", "exec", "globals", "locals", "vars"}: + raise ValueError("Dynamic namespace dependencies require a full migration") + return immediate, deferred + + +def copy_selected_functions(source, destination, names, exports): + """Add contiguous complete functions, preserving unrelated destination bytes. + + Global dependencies must be builtins, selected functions, or unambiguously + bound identical imports/simple assignments (checked transitively). Existing + helper functions and dynamic namespace dependencies require a full migration. + Scoped expressions in definition-time headers and classes anywhere inside + selected functions also require a full migration. Symbol-table analysis + distinguishes body locals, closures and comprehensions from module + dependencies; it never imports or calls the selected functions. + """ + if ( + not names + or len(set(names)) != len(names) + or not all(name.isidentifier() for name in names) + ): + raise ValueError("Function names must be unique identifiers") + if len(set(exports)) != len(exports) or not set(exports) <= set(names): + raise ValueError("Exports must be unique selected function names") + src, dst = ast.parse(source), ast.parse(destination) + src_bindings, dst_bindings = _module_bindings(src), _module_bindings(dst) + selected = [] + for name in names: + matches = src_bindings.get(name, []) + if ( + len(matches) != 1 + or not isinstance(matches[0], (ast.FunctionDef, ast.AsyncFunctionDef)) + or matches[0] not in src.body + or getattr(matches[0], "type_params", ()) ): - raise ValueError("__all__ must be a literal list") + raise ValueError(f"Expected one top-level non-generic function: {name}") + if dst_bindings.get(name): + raise ValueError( + f"A selected function already exists in the destination: {name}" + ) + node = matches[0] + headers = ( + node.decorator_list + + node.args.defaults + + [value for value in node.args.kw_defaults if value is not None] + ) + arguments = node.args.posonlyargs + node.args.args + node.args.kwonlyargs + arguments += [arg for arg in (node.args.vararg, node.args.kwarg) if arg] + headers += [arg.annotation for arg in arguments if arg.annotation is not None] + if node.returns is not None: + headers.append(node.returns) if any( - not isinstance(node, ast.Constant) or not isinstance(node.value, str) - for node in src_all.value.elts + dst_all.value.elts + isinstance( + child, + ( + ast.Lambda, + ast.ListComp, + ast.SetComp, + ast.DictComp, + ast.GeneratorExp, + ), + ) + for header in headers + for child in ast.walk(header) ): - raise ValueError("__all__ entries must be literal strings") - src_values, dst_values = ( - ast.literal_eval(src_all.value), - ast.literal_eval(dst_all.value), - ) - if any(src_values.count(name) != 1 or name in dst_values for name in exports): raise ValueError( - "Exports must occur once in canonical __all__ and be absent locally" + "Scoped expressions in function headers require a full migration" ) - export_nodes = [node for node in src_all.value.elts if node.value in exports] - export_lines = sorted({node.lineno - 1 for node in export_nodes}) - fragment = "".join(source_lines[line] for line in export_lines) - if ast.literal_eval("[\n" + fragment + "]") != exports: + if any(isinstance(child, ast.ClassDef) for child in ast.walk(node)): + raise ValueError("Classes in selected functions require a full migration") + selected.append(node) + start = src.body.index(selected[0]) + if src.body[start : start + len(names)] != selected or start + len(names) == len( + src.body + ): + raise ValueError("Functions must be contiguous and followed by an anchor") + first_line = min( + [selected[0].lineno, *(node.lineno for node in selected[0].decorator_list)] + ) + anchor = src.body[start + len(names)] + matches = [node for node in dst.body if ast.dump(node) == ast.dump(anchor)] + if len(matches) != 1: + raise ValueError("Expected one unchanged destination anchor") + for tree, node in ((src, anchor), (dst, matches[0])): + index = tree.body.index(node) + if ( + getattr(node, "decorator_list", ()) + or node.col_offset + or index + and tree.body[index - 1].end_lineno >= node.lineno + or index + 1 < len(tree.body) + and tree.body[index + 1].lineno <= node.end_lineno + ): raise ValueError( - "Canonical export lines include unselected entries or differ in order" + "Anchor must be undecorated and occupy complete source lines" ) - if lines[dst_all.lineno - 1].strip() != "__all__ = [": - raise ValueError("Expected a multiline destination __all__ list") - additions.append((dst_all.lineno, [fragment])) + if start and src.body[start - 1].end_lineno >= first_line: + raise ValueError("Selected functions must occupy complete source lines") + + source_lines, lines = source.splitlines(True), destination.splitlines(True) + futures = [ + [ + node + for node in tree.body + if isinstance(node, ast.ImportFrom) and node.module == "__future__" + ] + for tree in (src, dst) + ] + if [ast.dump(node) for node in futures[0]] != [ + ast.dump(node) for node in futures[1] + ]: + raise ValueError("Source and destination future imports differ") + prefix = "".join( + "".join(source_lines[node.lineno - 1 : node.end_lineno]) for node in futures[0] + ) + if "*" in src_bindings or "*" in dst_bindings: + raise ValueError("Wildcard import makes dependency bindings ambiguous") + + def dependency(name, source_boundary, destination_boundary): + if name in {"eval", "exec", "globals", "locals", "vars"}: + raise ValueError("Dynamic namespace dependencies require a full migration") + declarations = [] + for tree, bindings, boundary in ( + (src, src_bindings, source_boundary), + (dst, dst_bindings, destination_boundary), + ): + bound = bindings.get(name, []) + if not bound and name in vars(builtins): + declarations.append(None) + continue + if len(bound) != 1: + raise ValueError(f"Missing or ambiguous dependency binding: {name}") + candidates = [ + node + for node in tree.body + if isinstance(node, (ast.Import, ast.ImportFrom)) + and bound[0] in node.names + or isinstance(node, ast.Assign) + and len(node.targets) == 1 + and node.targets[0] is bound[0] + ] + if len(candidates) != 1 or candidates[0].lineno >= boundary: + raise ValueError( + f"Expected an earlier import or simple assignment: {name}" + ) + declarations.append(candidates[0]) + left, right = declarations + if left is None and right is None: + return + if left is None or right is None or ast.dump(left) != ast.dump(right): + raise ValueError(f"Changed dependency binding: {name}") + if isinstance(left, ast.Assign): + for child in ast.walk(left.value): + if isinstance( + child, + ( + ast.Lambda, + ast.NamedExpr, + ast.ListComp, + ast.SetComp, + ast.DictComp, + ast.GeneratorExp, + ), + ): + raise ValueError(f"Unsupported dependency expression: {name}") + if isinstance(child, ast.Name) and isinstance(child.ctx, ast.Load): + dependency(child.id, left.lineno, right.lineno) + + earlier = set() + for node in selected: + line = min([node.lineno, *(item.lineno for item in node.decorator_list)]) + immediate, deferred = _function_dependencies( + prefix + "".join(source_lines[line - 1 : node.end_lineno]) + ) + if (immediate & set(names)) - earlier: + raise ValueError("Definition-time dependency is not yet bound") + for name in (immediate | deferred) - set(names): + dependency(name, first_line, matches[0].lineno) + earlier.add(node.name) + + # Keep a following anchor comment attached to its original statement. Only + # canonical function bytes and their trailing whitespace/comments are added. + end = anchor.lineno - 1 + comment = next( + ( + i + for i in range(selected[-1].end_lineno, end) + if source_lines[i].startswith("#") + ), + end, + ) + anchor_comments = source_lines[comment:end] + offset = matches[0].lineno - 1 - len(anchor_comments) + if offset < 0 or lines[offset : matches[0].lineno - 1] != anchor_comments: + raise ValueError("Anchor's leading comments differ") + additions = [(offset, source_lines[first_line - 1 : comment])] + if exports: + for tree, bindings in ((src, src_bindings), (dst, dst_bindings)): + if bindings.get("__all__") != [ + _single_assignment(tree, "__all__").targets[0] + ]: + raise ValueError("Ambiguous __all__ binding") + additions.append( + _selected_export_addition(src, dst, source_lines, lines, exports) + ) for offset, fragment in sorted(additions, reverse=True): lines[offset:offset] = fragment result = "".join(lines) @@ -258,10 +554,12 @@ def assignment(tree, name): return result -def migrate_selected_assignments(path, names, exports, source_hash, destination_hash): +def _migrate_selected( + path, names, exports, source_hash, destination_hash, kind, copier +): target = parse_lib_path(path) if target.parts[:1] != ("Lib",) or ".." in target.parts or target.suffix != ".py": - raise ValueError("Selective assignments require an explicit Lib Python file") + raise ValueError("Selective imports require an explicit Lib Python file") source, destination = path.read_bytes(), target.read_bytes() for label, contents, expected in ( ("source", source, source_hash), @@ -269,16 +567,16 @@ def migrate_selected_assignments(path, names, exports, source_hash, destination_ ): if hashlib.sha256(contents).hexdigest() != expected: raise ValueError(f"{label} SHA256 does not match the pinned input") - result = copy_selected_assignments( + result = copier( source.decode("utf-8"), destination.decode("utf-8"), names, exports ).encode("utf-8") if target.read_bytes() != destination: - raise ValueError("Destination changed during assignment preparation") + raise ValueError("Destination changed during selective import preparation") target.write_bytes(result) print( json.dumps( { - "assignments": names, + kind: names, "exports": exports, "source_sha256": source_hash, "destination_before_sha256": destination_hash, @@ -289,6 +587,30 @@ def migrate_selected_assignments(path, names, exports, source_hash, destination_ ) +def migrate_selected_assignments(path, names, exports, source_hash, destination_hash): + return _migrate_selected( + path, + names, + exports, + source_hash, + destination_hash, + "assignments", + copy_selected_assignments, + ) + + +def migrate_selected_functions(path, names, exports, source_hash, destination_hash): + return _migrate_selected( + path, + names, + exports, + source_hash, + destination_hash, + "functions", + copy_selected_functions, + ) + + def patch_single_content( src_path: pathlib.Path, lib_path: pathlib.Path, @@ -781,6 +1103,11 @@ def main(argv: list[str] | None = None) -> int: nargs="+", help="Copy selected canonical assignments", ) + parser.add_argument( + "--functions", + nargs="+", + help="Copy complete selected canonical functions without executing them", + ) parser.add_argument("--exports", nargs="+", default=[]) parser.add_argument("--source-sha256") parser.add_argument("--destination-sha256") @@ -792,6 +1119,7 @@ def main(argv: list[str] | None = None) -> int: args.legacy_map is not None or args.patches is not None or args.assignments + or args.functions or args.exports or args.source_sha256 or args.destination_sha256 @@ -808,15 +1136,25 @@ def main(argv: list[str] | None = None) -> int: return 0 if args.dry_run: raise ValueError("--dry-run requires --canonical-removals") - if args.assignments: - if args.legacy_map is not None or args.patches is not None: + if args.assignments or args.functions: + if ( + args.legacy_map is not None + or args.patches is not None + or args.assignments + and args.functions + ): raise ValueError( - "Selective imports cannot combine with --legacy-map or --patches" + "Selective imports cannot combine with other migration modes" ) try: - migrate_selected_assignments( + migrate = ( + migrate_selected_functions + if args.functions + else migrate_selected_assignments + ) + migrate( args.path, - args.assignments, + args.functions or args.assignments, args.exports, args.source_sha256, args.destination_sha256, @@ -825,7 +1163,7 @@ def main(argv: list[str] | None = None) -> int: raise ValueError(str(exc)) from exc return 0 if args.exports or args.source_sha256 or args.destination_sha256: - raise ValueError("Selective options require --assignments") + raise ValueError("Selective options require --assignments or --functions") if args.patches is not None and ( args.path.is_dir() or args.legacy_map is not None ): diff --git a/scripts/update_lib/tests/test_selected_functions.py b/scripts/update_lib/tests/test_selected_functions.py new file mode 100644 index 00000000000..0de48a63f0a --- /dev/null +++ b/scripts/update_lib/tests/test_selected_functions.py @@ -0,0 +1,320 @@ +"""Source-only selective function migration, without importing copied helpers.""" + +import contextlib +import hashlib +import io +import json +import pathlib +import tempfile +import unittest +from unittest.mock import patch + +from update_lib.cmd_migrate import copy_selected_functions, main + + +class TestSelectedFunctions(unittest.TestCase): + names = ["available", "requires_available"] + exports = names + imports = "import functools\nimport os\nimport sys\nimport unittest\n\n" + export_line = ' "available", "requires_available",\n' + dependencies = 'IS_MOBILE = sys.platform == "mobile"\nAVAILABLE = not IS_MOBILE\n\n' + block = ( + "@functools.cache\n" + "def available(limit=1):\n" + " import errno\n" + " try:\n" + ' return AVAILABLE and bool(errno.EINVAL) and os.name != "" and limit > 0\n' + " except ValueError as error:\n" + " return bool(str(error))\n\n\n" + "def requires_available():\n" + ' return unittest.skipUnless(available(), "needs availability")\n\n\n' + ) + anchor = "# Keep this comment with STATUS.\nSTATUS = 1\n" + source = ( + imports + + '__all__ = [\n "existing",\n' + + export_line + + "]\n\n" + + dependencies + + block + + anchor + ) + destination = ( + imports + + '__all__ = [\n "existing",\n]\n\n' + + dependencies + + 'def existing(sys):\n return "keep π"\n\n' + + anchor + ) + + def copy(self, source=None, destination=None, names=None, exports=None): + return copy_selected_functions( + self.source if source is None else source, + self.destination if destination is None else destination, + self.names if names is None else names, + self.exports if exports is None else exports, + ) + + def test_exact_functions_exports_and_unrelated_bytes(self): + self.assertEqual( + self.copy(), + self.destination.replace( + "__all__ = [\n", "__all__ = [\n" + self.export_line + ).replace(self.anchor, self.block + self.anchor), + ) + self.assertEqual(self.copy().count(self.anchor), 1) + + def test_destination_line_endings_and_local_adaptations_survive(self): + source = self.source.replace(self.anchor, "STATUS = 1\n") + destination = self.destination.replace(self.anchor, "STATUS = 1\n").replace( + "\n", "\r\n" + ) + self.assertEqual( + self.copy(source, destination), + destination.replace( + "__all__ = [\r\n", "__all__ = [\r\n" + self.export_line + ).replace("STATUS = 1\r\n", self.block + "STATUS = 1\r\n"), + ) + + def test_local_imports_closures_and_comprehensions_are_not_global_dependencies( + self, + ): + block = ( + "@functools.cache\n" + "def available(items=()):\n" + " def inner(value):\n" + " return value in items\n" + " return [inner(value) for value in items]\n\n\n" + "async def requires_available():\n" + " import errno as local\n" + " return local.EINVAL\n\n\n" + ) + self.assertIn(block, self.copy(self.source.replace(self.block, block))) + with self.assertRaises(ValueError): + self.copy( + self.source.replace( + self.block, block.replace("value in items", "MISSING", 1) + ) + ) + + def test_multiple_decorators_defaults_and_body_are_copied_without_execution(self): + block = self.block.replace( + "@functools.cache\ndef available(limit=1):", + "@functools.cache\n@functools.lru_cache(\n maxsize=None\n)\n" + "def available(limit=os.name):", + ).replace( + " import errno\n", + " raise RuntimeError('must not run')\n import errno\n", + ) + self.assertIn(block, self.copy(self.source.replace(self.block, block))) + + def test_selection_and_existing_bindings_fail_closed(self): + for source, destination, names in ( + (self.source, self.destination, []), + (self.source, self.destination, self.names * 2), + (self.source, self.destination, list(reversed(self.names))), + (self.source, self.destination, ["missing"]), + (self.source + "available = None\n", self.destination, self.names), + (self.source, self.destination + "del available\n", self.names), + (self.source, self.destination + "import other as available\n", self.names), + ( + self.source, + self.destination + "if condition:\n available = None\n", + self.names, + ), + ( + self.source.replace( + "def requires_available", "UNSELECTED = 1\ndef requires_available" + ), + self.destination, + self.names, + ), + (self.source.replace(self.anchor, ""), self.destination, self.names), + ): + with self.subTest(names=names), self.assertRaises(ValueError): + self.copy(source, destination, names) + + def test_missing_changed_late_and_rebound_dependencies_fail_closed(self): + for destination in ( + self.destination.replace("import functools\n", ""), + self.destination.replace( + "import functools\n", "import other as functools\n" + ), + self.destination.replace("import os\n", "") + "import os\n", + self.destination.replace( + 'sys.platform == "mobile"', 'sys.platform == "other"' + ), + self.destination + "AVAILABLE = True\n", + self.destination + "del sys\n", + self.destination + "bool = None\n", + self.destination + "def rebind():\n global sys\n sys = None\n", + self.destination + "from unknown import *\n", + ): + with self.subTest(destination=destination), self.assertRaises(ValueError): + self.copy(destination=destination) + for source in ( + self.source + "del sys\n", + self.source + "from unknown import *\n", + self.source.replace("AVAILABLE = not IS_MOBILE", "AVAILABLE = MISSING"), + self.source.replace("limit=1", "limit=requires_available()"), + ): + with self.subTest(source=source), self.assertRaises(ValueError): + self.copy(source=source) + + def test_unsupported_namespace_changes_and_future_mismatch_fail_closed(self): + for statement in ( + "global AVAILABLE\n AVAILABLE = True", + "return globals()", + "return eval('AVAILABLE')", + ): + with self.subTest(statement=statement), self.assertRaises(ValueError): + self.copy( + self.source.replace( + " import errno", " " + statement + "\n import errno" + ) + ) + with self.assertRaises(ValueError): + self.copy("from __future__ import annotations\n" + self.source) + with self.assertRaises(ValueError): + self.copy(self.source.replace("limit=1", "limit=(EXTRA := 1)")) + + def test_anchor_ambiguity_partial_lines_and_changed_comments_fail_closed(self): + for destination in ( + self.destination.replace("STATUS = 1", "STATUS = 2"), + self.destination + "STATUS = 1\n", + self.destination.replace("STATUS = 1", "STATUS = 1; other = 2"), + self.destination.replace("STATUS = 1", "other = 2; STATUS = 1"), + self.destination.replace( + "# Keep this comment with STATUS.", "# Changed comment." + ), + ): + with self.subTest(destination=destination), self.assertRaises(ValueError): + self.copy(destination=destination) + replacement = "@functools.cache\ndef anchor(): pass\n" + with self.assertRaises(ValueError): + self.copy( + self.source.replace("STATUS = 1\n", replacement), + self.destination.replace("STATUS = 1\n", replacement), + ) + + def test_exports_require_complete_canonical_lines(self): + for exports in ( + self.exports[:1], + list(reversed(self.exports)), + self.exports * 2, + ["other"], + ): + with self.subTest(exports=exports), self.assertRaises(ValueError): + self.copy(exports=exports) + with self.assertRaises(ValueError): + self.copy(destination=self.destination + "del __all__\n") + self.assertNotIn(self.export_line, self.copy(exports=[])) + + def test_cli_hash_pins_receipt_and_concurrent_destination_guard(self): + with tempfile.TemporaryDirectory() as temp, contextlib.chdir(temp): + source = pathlib.Path("canonical/Lib/example.py") + destination = pathlib.Path("Lib/example.py") + source.parent.mkdir(parents=True) + destination.parent.mkdir(parents=True) + source.write_text(self.source) + destination.write_text(self.destination) + args = [ + str(source), + "--functions", + *self.names, + "--exports", + *self.exports, + "--source-sha256", + hashlib.sha256(source.read_bytes()).hexdigest(), + "--destination-sha256", + "0" * 64, + ] + with contextlib.redirect_stderr(io.StringIO()): + self.assertEqual(main(args), 1) + self.assertEqual(destination.read_text(), self.destination) + args[-1] = hashlib.sha256(destination.read_bytes()).hexdigest() + with contextlib.redirect_stdout(io.StringIO()) as output: + self.assertEqual(main(args), 0) + receipt = json.loads(output.getvalue()) + self.assertEqual(receipt["functions"], self.names) + self.assertEqual(destination.read_text(), self.copy()) + destination.write_text(self.destination) + + def concurrent_change(*unused): + destination.write_text("# changed meanwhile\n") + return self.copy() + + with patch( + "update_lib.cmd_migrate.copy_selected_functions", concurrent_change + ): + with contextlib.redirect_stderr(io.StringIO()) as errors: + self.assertEqual(main(args), 1) + self.assertIn("Destination changed", errors.getvalue()) + self.assertEqual(destination.read_text(), "# changed meanwhile\n") + + def test_cli_modes_are_exclusive_before_reading_inputs(self): + for options in ( + ["--assignments", "value"], + ["--patches", "missing.json"], + ["--legacy-map", "missing.json"], + ["--canonical-removals", "missing.json"], + ): + with ( + self.subTest(options=options), + contextlib.redirect_stderr(io.StringIO()) as errors, + ): + self.assertEqual( + main( + ["missing/Lib/example.py", "--functions", *self.names, *options] + ), + 1, + ) + self.assertIn("cannot combine", errors.getvalue()) + + def test_scoped_header_expressions_fail_closed(self): + for header in ( + "def available(limit=(lambda: requires_available())()):", + "def available(limit=[requires_available() for _ in (0,)]):", + "def available(limit={requires_available() for _ in (0,)}):", + "def available(limit={0: requires_available() for _ in (0,)}):", + "def available(limit=(requires_available() for _ in (0,))):", + "def available(*, limit=(lambda: requires_available())()):", + "def available(limit: (lambda: requires_available())() = 1):", + ): + with ( + self.subTest(header=header), + self.assertRaisesRegex( + ValueError, "Scoped expressions in function headers" + ), + ): + self.copy(self.source.replace("def available(limit=1):", header)) + for decorator in ( + "@(lambda: requires_available())()", + "@[requires_available for _ in (0,)][0]", + ): + with ( + self.subTest(decorator=decorator), + self.assertRaisesRegex( + ValueError, "Scoped expressions in function headers" + ), + ): + self.copy(self.source.replace("@functools.cache", decorator)) + + def test_nested_classes_fail_closed(self): + for body in ( + " class Inner:\n os = os\n", + " def nested():\n class Inner:\n os = os\n", + ): + with ( + self.subTest(body=body), + self.assertRaisesRegex(ValueError, "Classes in selected functions"), + ): + self.copy( + self.source.replace( + " import errno\n", body + " import errno\n" + ) + ) + + +if __name__ == "__main__": + unittest.main() From db4878080a7a9cb75ee7889cf632248dd72098af Mon Sep 17 00:00:00 2001 From: 1ndahous3 <9205230+1ndahous3@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:10:43 +0300 Subject: [PATCH 02/18] Collect cycles through sets, frozensets and set iterators (#9343) Assisted-by: Codex:GPT-6 --- Lib/test/test_set.py | 1 - crates/vm/src/builtins/set.rs | 193 ++++++++++++++++++++++++++++++-- crates/vm/src/dict_inner.rs | 10 ++ crates/vm/src/object/core.rs | 21 +++- crates/vm/src/object/payload.rs | 15 +++ 5 files changed, 226 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_set.py b/Lib/test/test_set.py index b615af5082d..7079eb1b9f6 100644 --- a/Lib/test/test_set.py +++ b/Lib/test/test_set.py @@ -352,7 +352,6 @@ def test_do_not_rehash_dict_keys(self): self.assertEqual(sum(elem.hash_count for elem in d), n) self.assertEqual(d3, dict.fromkeys(d, 123)) - @unittest.expectedFailure # TODO: RUSTPYTHON def test_container_iterator(self): # Bug #3680: tp_traverse was not implemented for set iterator object class C(object): diff --git a/crates/vm/src/builtins/set.rs b/crates/vm/src/builtins/set.rs index bd204fea155..ce251efca31 100644 --- a/crates/vm/src/builtins/set.rs +++ b/crates/vm/src/builtins/set.rs @@ -8,7 +8,7 @@ use super::{ use crate::{ AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, atomic_func, - class::{PyClassDef, PyClassImpl}, + class::{PyClassDef, PyClassImpl, StaticType}, common::{ ascii, hash::PyHash, @@ -22,6 +22,7 @@ use crate::{ ArgIterable, FuncArgs, NameOthers, OptionalArg, PosArgs, PyArithmeticValue, PyComparisonValue, }, + object::{Traverse, TraverseFn}, protocol::{PyIterReturn, PyNumberMethods, PySequenceMethods}, recursion::ReprGuard, types::AsNumber, @@ -40,12 +41,23 @@ use rustpython_common::{ pub(crate) type SetContentType = dict_inner::Dict<()>; -#[pyclass(module = false, name = "set", unhashable = true, traverse)] +#[pyclass(module = false, name = "set", unhashable = true, traverse = "manual")] #[derive(Default)] pub struct PySet { pub(super) inner: PySetInner, } +// SAFETY: the inner table visits each exclusively owned reference once. +unsafe impl Traverse for PySet { + fn traverse(&self, tracer: &mut TraverseFn<'_>) { + self.inner.traverse(tracer); + } + + fn clear(&mut self, out: &mut Vec) { + Traverse::clear(&mut self.inner, out); + } +} + impl PySet { #[deprecated(note = "Use `PySet::default().into_ref(ctx)` instead")] pub fn new_ref(ctx: &Context) -> PyRef { @@ -82,12 +94,28 @@ impl PySet { } } -#[pyclass(module = false, name = "frozenset", unhashable = true)] +#[pyclass( + module = false, + name = "frozenset", + unhashable = true, + traverse = "manual" +)] pub struct PyFrozenSet { inner: PySetInner, hash: PyAtomic, } +// SAFETY: the inner table visits each exclusively owned reference once. +unsafe impl Traverse for PyFrozenSet { + fn traverse(&self, tracer: &mut TraverseFn<'_>) { + self.inner.traverse(tracer); + } + + fn clear(&mut self, out: &mut Vec) { + Traverse::clear(&mut self.inner, out); + } +} + impl Default for PyFrozenSet { fn default() -> Self { Self { @@ -161,6 +189,9 @@ impl fmt::Debug for PyFrozenSet { } impl PyPayload for PySet { + // Ordinary destruction can release the table directly after untracking. + const CLEAR_ON_DEALLOC: bool = false; + #[inline] fn class(ctx: &Context) -> &'static Py { ctx.types.set_type @@ -168,6 +199,22 @@ impl PyPayload for PySet { } impl PyPayload for PyFrozenSet { + const CLEAR_ON_DEALLOC: bool = false; + + fn needs_gc(&self, class: &Py) -> bool { + // Subclasses can acquire instance state. Shared working tables may + // still be mutated by another owner, so keep those tracked as well. + if !class.is(Self::static_type()) || PyRc::strong_count(&self.inner.content) != 1 { + return true; + } + self.inner.content.any_key(|key| { + // Exact untracked frozensets are immutable and were checked at + // allocation. Other untracked objects may later acquire GC edges. + key.gc_may_have_edges() + && !(key.class().is(Self::static_type()) && !key.is_gc_tracked()) + }) + } + #[inline] fn class(ctx: &Context) -> &'static Py { ctx.types.frozenset_type @@ -179,10 +226,22 @@ pub(super) struct PySetInner { content: PyRc, } -unsafe impl crate::object::Traverse for PySetInner { - fn traverse(&self, tracer_fn: &mut crate::object::TraverseFn<'_>) { - // FIXME(discord9): Rc means shared ref, so should it be traced? - self.content.traverse(tracer_fn) +// SAFETY: a shared table owns only one reference per entry, regardless of its +// number of owners. Only attribute those edges to an exclusive owner; otherwise +// leave them as external roots rather than subtracting the same edges twice. +unsafe impl Traverse for PySetInner { + fn traverse(&self, tracer: &mut TraverseFn<'_>) { + if PyRc::strong_count(&self.content) == 1 { + self.content.traverse(tracer); + } + } + + fn clear(&mut self, out: &mut Vec) { + if let Some(content) = PyRc::get_mut(&mut self.content) { + // Detach before releasing keys: dropping them may re-enter Python. + out.reserve(content.len()); + out.extend(content.drain_entries().map(|(key, ())| key)); + } } } @@ -1652,6 +1711,13 @@ pub(crate) struct PyAnySet { object: PyObject, } +// SAFETY: this owner contributes exactly one strong reference to the object. +unsafe impl Traverse for PyRef { + fn traverse(&self, tracer: &mut TraverseFn<'_>) { + tracer(self.as_object()); + } +} + impl Borrow for PyAnySet { #[inline(always)] fn borrow(&self) -> &PyObject { @@ -1747,12 +1813,14 @@ impl TryFromObject for PyRef { } } -#[pyclass(module = false, name = "set_iterator")] +#[pyclass(module = false, name = "set_iterator", traverse, clear = false)] pub(crate) struct PySetIterator { + #[pytraverse(skip)] size: DictSize, /// Whether the set was found to have changed, which `setiter_iternext()` /// records by writing a size no set can have. Sticky: what it makes the /// iterator answer, it answers from then on. + #[pytraverse(skip)] changed: PyAtomic, internal: PyMutex>>, } @@ -1907,6 +1975,115 @@ pub(crate) fn init(context: &'static Context) { mod reference_tests { use super::*; + #[test] + fn set_gc_detaches_keys_for_deferred_release() { + use crate::object::MaybeTraverse; + + fn check(mut set: impl MaybeTraverse, key: &PyObjectRef) { + let mut visits = 0; + set.try_traverse(&mut |child| { + assert!(core::ptr::eq(child, &**key)); + visits += 1; + }); + assert_eq!(visits, 1); + let mut edges = Vec::new(); + set.try_clear(&mut edges); + set.try_traverse(&mut |_| panic!("cleared set retained a key")); + set.try_clear(&mut edges); + assert_eq!(edges.len(), 1); + assert!(core::ptr::eq(&*edges[0], &**key)); + assert_eq!(key.strong_count(), 2); + drop(edges); + assert_eq!(key.strong_count(), 1); + } + + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + for frozen in [false, true] { + let key: PyObjectRef = vm.ctx.new_int(1000).into(); + let inner = PySetInner::default(); + inner.add(&key, vm).unwrap(); + if frozen { + check( + PyFrozenSet { + inner, + ..Default::default() + }, + &key, + ); + } else { + check(PySet { inner }, &key); + } + } + }); + } + + #[test] + fn set_gc_preserves_shared_table_edges() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let key: PyObjectRef = vm.ctx.new_int(1000).into(); + let mut inner = PySetInner::default(); + inner.add(&key, vm).unwrap(); + let alias = inner.clone(); + inner.traverse(&mut |_| panic!("shared table edge counted")); + alias.traverse(&mut |_| panic!("shared table edge counted")); + let mut edges = Vec::new(); + Traverse::clear(&mut inner, &mut edges); + assert!(edges.is_empty()); + assert!(alias.contains(&key, vm).unwrap()); + drop(alias); + Traverse::clear(&mut inner, &mut edges); + assert_eq!(inner.len(), 0); + assert_eq!(edges.len(), 1); + }); + } + + #[test] + fn frozenset_gc_tracking_covers_native_construction() { + crate::Interpreter::without_stdlib(Default::default()).enter(|vm| { + let key: PyObjectRef = vm.ctx.new_int(1000).into(); + let atomic = PyRef::new_ref( + PyFrozenSet::from_iter(vm, [key.clone()]).unwrap(), + vm.ctx.types.frozenset_type.to_owned(), + None, + ); + assert!(!atomic.as_object().is_gc_tracked()); + let nested = PyFrozenSet::from_iter(vm, [atomic.into()]) + .unwrap() + .into_ref(&vm.ctx); + assert!(!nested.as_object().is_gc_tracked()); + + // A traversable key must keep the frozen owner visible to GC. + let tuple: PyObjectRef = vm.ctx.new_tuple(vec![key.clone()]).into(); + let owner = PyFrozenSet::from_iter(vm, [tuple]) + .unwrap() + .into_ref(&vm.ctx); + assert!(owner.as_object().is_gc_tracked()); + + let subclass = vm.ctx.new_class( + None, + "FrozenSubclass", + vm.ctx.types.frozenset_type.to_owned(), + Default::default(), + ); + let subclass = PyFrozenSet::default() + .into_ref_with_type(vm, subclass) + .unwrap(); + assert!(subclass.as_object().is_gc_tracked()); + + // A temporary native alias can still add traversable keys. + let inner = PySetInner::default(); + inner.add(&key, vm).unwrap(); + let alias = inner.clone(); + let owner = PyFrozenSet { + inner, + ..Default::default() + } + .into_ref(&vm.ctx); + assert!(owner.as_object().is_gc_tracked()); + drop(alias); + }); + } + #[test] fn anyset_reference_layout_and_ownership() { static_assertions::assert_not_impl_any!(PyAnySet: PyPayload); diff --git a/crates/vm/src/dict_inner.rs b/crates/vm/src/dict_inner.rs index 8b4a8eb6def..6388f3ed383 100644 --- a/crates/vm/src/dict_inner.rs +++ b/crates/vm/src/dict_inner.rs @@ -1165,6 +1165,16 @@ impl Dict { current != *old } + /// Inspect keys without allocating or cloning their references. + /// The predicate must not run Python or mutate this table. + pub(crate) fn any_key(&self, mut predicate: impl FnMut(&PyObject) -> bool) -> bool { + self.read() + .entries + .iter() + .flatten() + .any(|entry| predicate(&entry.key)) + } + pub(crate) fn keys(&self) -> Vec { self.read() .entries diff --git a/crates/vm/src/object/core.rs b/crates/vm/src/object/core.rs index 72c15855c2c..fa0e6be1af1 100644 --- a/crates/vm/src/object/core.rs +++ b/crates/vm/src/object/core.rs @@ -198,7 +198,9 @@ pub(super) unsafe fn default_dealloc(obj: *mut PyObject) { // them. Some payloads (e.g. FrameObject) drop children in place inside clear_fn // instead of extracting them, so user code (`__del__`) may run here. let mut edges = Vec::new(); - if let Some(clear_fn) = vtable.clear { + if (T::CLEAR_ON_DEALLOC || T::HAS_FREELIST) + && let Some(clear_fn) = vtable.clear + { unsafe { clear_fn(obj, &mut edges) }; } // Drop extracted child references - may trigger recursive destruction. @@ -2290,6 +2292,15 @@ impl PyObject { GcBits::from_bits_retain(self.0.gc_bits.load(Ordering::Relaxed)).contains(GcBits::TRACKED) } + /// Whether this object's payload or instance storage can own GC edges. + /// Unlike is_gc_tracked, this also covers temporarily untracked objects. + #[inline] + pub(crate) fn gc_may_have_edges(&self) -> bool { + self.0.vtable.trace.is_some() + || self.class().heaptype_ext.is_some() + || self.0.ext_ref().is_some() + } + /// Get the referents (objects directly referenced) of this object. /// Uses the full traverse including dict and slots. pub fn gc_get_referents(&self) -> Vec { @@ -2937,6 +2948,8 @@ impl PyRef { pub fn new_ref(payload: T, typ: crate::builtins::PyTypeRef, dict: Option) -> Self { let has_dict = dict.is_some(); let is_heaptype = typ.heaptype_ext.is_some(); + let track = !T::NEW_REF_UNTRACKED + && (has_dict || is_heaptype || (T::HAS_TRAVERSE && payload.needs_gc(&typ))); // Try to reuse from freelist (no dict, no heaptype) let cached = if !has_dict && !is_heaptype { @@ -2969,14 +2982,12 @@ impl PyRef { }; // Track object if: - // - HAS_TRAVERSE is true (Rust payload implements Traverse), OR + // - HAS_TRAVERSE is true and the payload needs cyclic GC, OR // - has instance dict (user-defined class instances), OR // - heap type (all heap type instances are GC-tracked, like Py_TPFLAGS_HAVE_GC) // unless the payload opts out via NEW_REF_UNTRACKED (e.g. call frames, // which are tracked lazily only on escape). - if (::HAS_TRAVERSE || has_dict || is_heaptype) - && !T::NEW_REF_UNTRACKED - { + if track { // Tracks under the interpreter running now and collects if this // allocation pushed gen0 past its threshold. unsafe { diff --git a/crates/vm/src/object/payload.rs b/crates/vm/src/object/payload.rs index a5a4bf35b7f..36de55fc314 100644 --- a/crates/vm/src/object/payload.rs +++ b/crates/vm/src/object/payload.rs @@ -91,6 +91,21 @@ pub trait PyPayload: MaybeTraverse + PyThreadingConstraint + Sized + 'static { /// which is created untracked and tracked lazily only on escape. const NEW_REF_UNTRACKED: bool = false; + /// Whether this payload needs cyclic GC when first wrapped in an object. + /// Returning false promises that this instance cannot participate in a + /// reference cycle for its entire lifetime, not just at allocation time. + /// The check must not run Python. Instance dictionaries and heap types are + /// still tracked, unless NEW_REF_UNTRACKED opts out explicitly. + #[inline] + fn needs_gc(&self, _class: &Py) -> bool { + true + } + + /// Run tp_clear before ordinary reference-counted destruction. Payloads + /// whose Rust destructor releases their children directly may opt out. + /// Cyclic GC still calls tp_clear; freelists also require a cleared payload. + const CLEAR_ON_DEALLOC: bool = true; + /// Whether this type has a freelist. Types with freelists require /// immediate (non-deferred) GC untracking during dealloc to prevent /// race conditions when the object is reused. From 65ee7bae8bff7e0c6a169965eadedc9f421ec558 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Sat, 24 Oct 2026 23:11:10 -0700 Subject: [PATCH 03/18] Align Python 3.15 bytecode tooling (#9344) Extract independent bytecode tooling from the Python 3.15 integration. Decode packed import-name arguments without changing older-interpreter fallback behavior, validate canonical opcode numbering, bound deoptimization families to the enum, and reuse parsed CPython analysis. Validated generators reproduce tracked metadata exactly; normal, lazy and eager import fallback matches canonical disassembly. No interpreter, generated metadata or test changes. Assisted-by: OpenAI Codex: model version unavailable --- scripts/dis_dump.py | 12 +++++++++++- tools/opcode_metadata/cpython.py | 2 ++ tools/opcode_metadata/opcodes.py | 16 ++++++++++++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/scripts/dis_dump.py b/scripts/dis_dump.py index 813de22e658..a149f724af9 100755 --- a/scripts/dis_dump.py +++ b/scripts/dis_dump.py @@ -170,12 +170,22 @@ def _resolve_arg_fallback(code, opname, arg): "LOAD_ATTR", "STORE_ATTR", "DELETE_ATTR", - "IMPORT_NAME", "IMPORT_FROM", "LOAD_FROM_DICT_OR_GLOBALS", ): if 0 <= arg < len(code.co_names): return code.co_names[arg] + elif opname == "IMPORT_NAME": + name_idx, policy = arg, 0 + if sys.version_info >= (3, 15): + name_idx, policy = arg >> 2, arg & 3 + if 0 <= name_idx < len(code.co_names): + name = code.co_names[name_idx] + if policy & 1 and name: + return f"{name} + lazy" + if policy & 2 and name: + return f"{name} + eager" + return name elif opname == "LOAD_SUPER_ATTR": name_idx = arg >> 2 if 0 <= name_idx < len(code.co_names): diff --git a/tools/opcode_metadata/cpython.py b/tools/opcode_metadata/cpython.py index 56743a88842..7e32430a2c5 100644 --- a/tools/opcode_metadata/cpython.py +++ b/tools/opcode_metadata/cpython.py @@ -1,3 +1,4 @@ +import functools import os import pathlib import sys @@ -18,6 +19,7 @@ from stack import get_stack_effect +@functools.cache def get_analysis() -> Analysis: from generators_common import DEFAULT_INPUT diff --git a/tools/opcode_metadata/opcodes.py b/tools/opcode_metadata/opcodes.py index 9178921b2c0..7550af1e258 100644 --- a/tools/opcode_metadata/opcodes.py +++ b/tools/opcode_metadata/opcodes.py @@ -33,10 +33,11 @@ def deopts(self) -> dict[str, list[str]]: for member in family.members: member_name = to_pascal_case(member.name) - if member.name == family_name: + if member_name == family_name or member_name not in names: continue - res[family_name].append(member_name) + if member_name not in res[family_name]: + res[family_name].append(member_name) return dict(res) @@ -76,6 +77,17 @@ def iter_opcodes(text: str, override_confs: OverrideConfs) -> Iterable[Opcode]: opcode = Opcode.from_str(entry) rust_name = opcode.rust_name + # Shared opcodes use CPython's wire-format IDs; RustPython-only + # instructions must occupy an otherwise unused slot. + expected_id = analysis.opmap.get(opcode.cpython_name) + if expected_id is not None and opcode.id != expected_id: + raise ValueError( + f"{rust_name} has opcode ID {opcode.id}; CPython requires {expected_id}" + ) + if expected_id is None and opcode.id in analysis.opmap.values(): + raise ValueError( + f"RustPython-only opcode {rust_name} collides with CPython ID {opcode.id}" + ) override = override_confs.get(rust_name, SKIP_OVERRIDE) cpython_name = opcode.cpython_name From a8ad41e2b131249d4795bd8db00d6cfdde49a747 Mon Sep 17 00:00:00 2001 From: fanninpm <27117322+fanninpm@users.noreply.github.com> Date: Sun, 25 Oct 2026 02:11:44 -0400 Subject: [PATCH 04/18] Specify exactly where package.json files are located (#9345) --- .github/dependabot.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e9e2041d482..e8517d5c49f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -161,7 +161,8 @@ updates: cooldown: default-days: 7 - package-ecosystem: npm - directory: / + directories: + - "wasm/*" schedule: interval: weekly cooldown: From 2efa217efedbdf7d7f682b5386604708c0a3b779 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Sat, 24 Oct 2026 23:12:19 -0700 Subject: [PATCH 05/18] Restore canonical traceback Levenshtein coverage (#9346) Import the unchanged Python 3.15.0 fixture with the official library updater and retire the missing-fixture expectedFailure after validating the existing distance and threshold tests. Preserve all assertions and unrelated markers. Assisted-by: OpenAI Codex: model version unavailable --- Lib/test/levenshtein_examples.json | 50002 +++++++++++++++++++++++++++ Lib/test/test_traceback.py | 1 - 2 files changed, 50002 insertions(+), 1 deletion(-) create mode 100644 Lib/test/levenshtein_examples.json diff --git a/Lib/test/levenshtein_examples.json b/Lib/test/levenshtein_examples.json new file mode 100644 index 00000000000..a32672cfdba --- /dev/null +++ b/Lib/test/levenshtein_examples.json @@ -0,0 +1,50002 @@ +[ + [ + "", + "", + 0 + ], + [ + "", + "AabBbb", + 12 + ], + [ + "", + "AbaC", + 8 + ], + [ + "", + "B", + 2 + ], + [ + "", + "Ba", + 4 + ], + [ + "", + "CBaACCCa", + 16 + ], + [ + "", + "CCbBC", + 10 + ], + [ + "", + "CcbCbaaAB", + 18 + ], + [ + "", + "bAa", + 6 + ], + [ + "", + "bAaCABb", + 14 + ], + [ + "A", + "A", + 0 + ], + [ + "A", + "AA", + 2 + ], + [ + "A", + "AABCAabcC", + 16 + ], + [ + "A", + "AACB", + 6 + ], + [ + "A", + "AACC", + 6 + ], + [ + "A", + "ABAaBbc", + 12 + ], + [ + "A", + "ABCcC", + 8 + ], + [ + "A", + "ABa", + 4 + ], + [ + "A", + "ABbBabcaa", + 16 + ], + [ + "A", + "ABbbCBcA", + 14 + ], + [ + "A", + "ACCBcBbBb", + 16 + ], + [ + "A", + "ACa", + 4 + ], + [ + "A", + "ACb", + 4 + ], + [ + "A", + "ACcbbaAB", + 14 + ], + [ + "A", + "AaAaccAb", + 14 + ], + [ + "A", + "AaabbCC", + 12 + ], + [ + "A", + "AabB", + 6 + ], + [ + "A", + "AbBa", + 6 + ], + [ + "A", + "AbBaA", + 8 + ], + [ + "A", + "AbCBBCaC", + 14 + ], + [ + "A", + "AbCb", + 6 + ], + [ + "A", + "AbcB", + 6 + ], + [ + "A", + "B", + 2 + ], + [ + "A", + "BA", + 2 + ], + [ + "A", + "BABbCc", + 10 + ], + [ + "A", + "BABca", + 8 + ], + [ + "A", + "BB", + 4 + ], + [ + "A", + "BBaBBaa", + 13 + ], + [ + "A", + "BBbbaAA", + 12 + ], + [ + "A", + "BCCAAc", + 10 + ], + [ + "A", + "BCa", + 5 + ], + [ + "A", + "BCcaC", + 9 + ], + [ + "A", + "BCccaa", + 11 + ], + [ + "A", + "BaacCbC", + 13 + ], + [ + "A", + "BabCC", + 9 + ], + [ + "A", + "Bac", + 5 + ], + [ + "A", + "BbCbb", + 10 + ], + [ + "A", + "BbaBAC", + 10 + ], + [ + "A", + "Bbc", + 6 + ], + [ + "A", + "BcB", + 6 + ], + [ + "A", + "BcCAbCBA", + 14 + ], + [ + "A", + "BcaaCbCB", + 15 + ], + [ + "A", + "C", + 2 + ], + [ + "A", + "CA", + 2 + ], + [ + "A", + "CABAacB", + 12 + ], + [ + "A", + "CABBABBc", + 14 + ], + [ + "A", + "CAaaBc", + 10 + ], + [ + "A", + "CBAA", + 6 + ], + [ + "A", + "CBCcBaBAB", + 16 + ], + [ + "A", + "CBcbc", + 10 + ], + [ + "A", + "CC", + 4 + ], + [ + "A", + "CCA", + 4 + ], + [ + "A", + "CCB", + 6 + ], + [ + "A", + "CCBaACcC", + 14 + ], + [ + "A", + "CCBcAAacb", + 16 + ], + [ + "A", + "CaCCaA", + 10 + ], + [ + "A", + "CbAB", + 6 + ], + [ + "A", + "CbBaAACC", + 14 + ], + [ + "A", + "CbCCbB", + 12 + ], + [ + "A", + "CbacBb", + 11 + ], + [ + "A", + "CbbABc", + 10 + ], + [ + "A", + "CcC", + 6 + ], + [ + "A", + "CcCacAAAC", + 16 + ], + [ + "A", + "CccCbaCcb", + 17 + ], + [ + "A", + "a", + 1 + ], + [ + "A", + "aABABBa", + 12 + ], + [ + "A", + "aABBA", + 8 + ], + [ + "A", + "aABCaBCa", + 14 + ], + [ + "A", + "aACBabAC", + 14 + ], + [ + "A", + "aBCb", + 7 + ], + [ + "A", + "aBaB", + 7 + ], + [ + "A", + "aBbBb", + 9 + ], + [ + "A", + "aC", + 3 + ], + [ + "A", + "aCA", + 4 + ], + [ + "A", + "aaBBb", + 9 + ], + [ + "A", + "aaC", + 5 + ], + [ + "A", + "aaCb", + 7 + ], + [ + "A", + "aaCbABbb", + 14 + ], + [ + "A", + "aaCcbb", + 11 + ], + [ + "A", + "aaaaCcAB", + 14 + ], + [ + "A", + "aaabb", + 9 + ], + [ + "A", + "aaacCabCC", + 17 + ], + [ + "A", + "aacBccCAC", + 16 + ], + [ + "A", + "aacC", + 7 + ], + [ + "A", + "abCAb", + 8 + ], + [ + "A", + "abbBcaccc", + 17 + ], + [ + "A", + "acCAB", + 8 + ], + [ + "A", + "acacBcAA", + 14 + ], + [ + "A", + "b", + 2 + ], + [ + "A", + "bABACaB", + 12 + ], + [ + "A", + "bAbA", + 6 + ], + [ + "A", + "bAbaBc", + 10 + ], + [ + "A", + "bBAa", + 6 + ], + [ + "A", + "bBa", + 5 + ], + [ + "A", + "bBaCAab", + 12 + ], + [ + "A", + "bBaCaCBbB", + 17 + ], + [ + "A", + "bBbAbaBa", + 14 + ], + [ + "A", + "bBbacabAb", + 16 + ], + [ + "A", + "bCCbA", + 8 + ], + [ + "A", + "bCCbB", + 10 + ], + [ + "A", + "bCbbACc", + 12 + ], + [ + "A", + "bCbbc", + 10 + ], + [ + "A", + "baACBCB", + 12 + ], + [ + "A", + "babABb", + 10 + ], + [ + "A", + "bacB", + 7 + ], + [ + "A", + "bacacA", + 10 + ], + [ + "A", + "bb", + 4 + ], + [ + "A", + "bbCa", + 7 + ], + [ + "A", + "bbCbaa", + 11 + ], + [ + "A", + "bbbAcb", + 10 + ], + [ + "A", + "bc", + 4 + ], + [ + "A", + "bcAAcCAb", + 14 + ], + [ + "A", + "bcC", + 6 + ], + [ + "A", + "bcbbaab", + 13 + ], + [ + "A", + "c", + 2 + ], + [ + "A", + "cAB", + 4 + ], + [ + "A", + "cACAc", + 8 + ], + [ + "A", + "cAaBccaC", + 14 + ], + [ + "A", + "cAcAaCAc", + 14 + ], + [ + "A", + "cBAaC", + 8 + ], + [ + "A", + "cBAac", + 8 + ], + [ + "A", + "cBCAABbc", + 14 + ], + [ + "A", + "cBaCbab", + 13 + ], + [ + "A", + "cBacA", + 8 + ], + [ + "A", + "cBb", + 6 + ], + [ + "A", + "cC", + 4 + ], + [ + "A", + "cCBABaC", + 12 + ], + [ + "A", + "cCCCa", + 9 + ], + [ + "A", + "cCa", + 5 + ], + [ + "A", + "cCaAaCaCB", + 16 + ], + [ + "A", + "cCabbbCa", + 15 + ], + [ + "A", + "caacCacAb", + 16 + ], + [ + "A", + "cabbC", + 9 + ], + [ + "A", + "cb", + 4 + ], + [ + "A", + "cbC", + 6 + ], + [ + "A", + "cbCAaAaAB", + 16 + ], + [ + "A", + "cbbaaAbbB", + 16 + ], + [ + "A", + "cc", + 4 + ], + [ + "A", + "ccCcCBaB", + 15 + ], + [ + "A", + "ccabbC", + 11 + ], + [ + "AA", + "AA", + 0 + ], + [ + "AA", + "ACAA", + 4 + ], + [ + "AA", + "BAc", + 4 + ], + [ + "AA", + "BC", + 4 + ], + [ + "AA", + "BaccaAA", + 10 + ], + [ + "AA", + "Bba", + 5 + ], + [ + "AA", + "BbcbcccAB", + 16 + ], + [ + "AA", + "CAb", + 4 + ], + [ + "AA", + "Cc", + 4 + ], + [ + "AA", + "CcBACCa", + 11 + ], + [ + "AA", + "aCBa", + 6 + ], + [ + "AA", + "aaBA", + 5 + ], + [ + "AA", + "aaBac", + 8 + ], + [ + "AA", + "bAAabBA", + 10 + ], + [ + "AA", + "bABbacbb", + 13 + ], + [ + "AA", + "bBbBbc", + 12 + ], + [ + "AA", + "bC", + 4 + ], + [ + "AA", + "bCabb", + 9 + ], + [ + "AA", + "baAB", + 5 + ], + [ + "AA", + "bbBaBcCBB", + 17 + ], + [ + "AA", + "bbBbAaaB", + 13 + ], + [ + "AA", + "c", + 4 + ], + [ + "AA", + "cACaCbab", + 13 + ], + [ + "AA", + "cBbCbBcA", + 14 + ], + [ + "AA", + "cBba", + 7 + ], + [ + "AA", + "cCbBBCca", + 15 + ], + [ + "AA", + "cacaca", + 10 + ], + [ + "AA", + "ccbccAa", + 11 + ], + [ + "AAA", + "B", + 6 + ], + [ + "AAA", + "C", + 6 + ], + [ + "AAA", + "aBBbcAaBa", + 14 + ], + [ + "AAA", + "aacbbBbbc", + 16 + ], + [ + "AAA", + "abAb", + 5 + ], + [ + "AAA", + "cccaAbBcB", + 15 + ], + [ + "AAAAABA", + "CaBcAaa", + 10 + ], + [ + "AAABACbC", + "BBBC", + 11 + ], + [ + "AAABAbaaA", + "CBbbacb", + 12 + ], + [ + "AAABa", + "AA", + 6 + ], + [ + "AAABc", + "cbBabcaBA", + 14 + ], + [ + "AAAC", + "acBcCCcC", + 13 + ], + [ + "AAACAbAaa", + "BCc", + 16 + ], + [ + "AAACBACc", + "CaCabAc", + 10 + ], + [ + "AAACCbbcA", + "bc", + 14 + ], + [ + "AAACa", + "A", + 8 + ], + [ + "AAACbCaa", + "acABAAa", + 9 + ], + [ + "AAACbaaa", + "BBa", + 13 + ], + [ + "AAAa", + "bBaaCc", + 9 + ], + [ + "AAAaAB", + "BCB", + 10 + ], + [ + "AAAaAcC", + "CAbBcBBC", + 12 + ], + [ + "AAAaCAAb", + "aBbAbaaA", + 12 + ], + [ + "AAAaCaA", + "cBcABaAb", + 11 + ], + [ + "AAAaaaaA", + "ABBBcaA", + 10 + ], + [ + "AAAb", + "AcC", + 6 + ], + [ + "AAAb", + "B", + 7 + ], + [ + "AAAbAB", + "cCBaAbcc", + 11 + ], + [ + "AAAbBcbc", + "C", + 15 + ], + [ + "AAAbaAABb", + "cCcAaca", + 15 + ], + [ + "AAAbbaA", + "Cbcc", + 12 + ], + [ + "AAAbbc", + "ABcaCCbca", + 11 + ], + [ + "AAAbcAcBA", + "bBaACAC", + 13 + ], + [ + "AAAbcbCa", + "cabCCbCba", + 10 + ], + [ + "AAAcAcCC", + "cbBCAAB", + 13 + ], + [ + "AAAcBAbCB", + "cbbABC", + 12 + ], + [ + "AAB", + "CB", + 4 + ], + [ + "AAB", + "accAC", + 7 + ], + [ + "AAB", + "bCCbaBbAc", + 15 + ], + [ + "AAB", + "bbb", + 5 + ], + [ + "AAB", + "bcCBcC", + 10 + ], + [ + "AABA", + "AAc", + 4 + ], + [ + "AABA", + "cBCAbA", + 7 + ], + [ + "AABABCcCc", + "abAC", + 12 + ], + [ + "AABAaB", + "BAB", + 6 + ], + [ + "AABAbAa", + "BcaB", + 11 + ], + [ + "AABAcb", + "cca", + 10 + ], + [ + "AABBA", + "AbAaCBb", + 8 + ], + [ + "AABBAACcC", + "A", + 16 + ], + [ + "AABBBcb", + "B", + 12 + ], + [ + "AABBCaB", + "bAbBA", + 8 + ], + [ + "AABBCabC", + "BCC", + 10 + ], + [ + "AABBCcB", + "cABBBAACc", + 10 + ], + [ + "AABBabAA", + "A", + 14 + ], + [ + "AABBac", + "BACCCB", + 10 + ], + [ + "AABBc", + "CAaBaA", + 7 + ], + [ + "AABCb", + "CcaCab", + 8 + ], + [ + "AABCbccb", + "B", + 14 + ], + [ + "AABCcA", + "BabC", + 8 + ], + [ + "AABCcB", + "BAB", + 8 + ], + [ + "AABCca", + "AbBBC", + 7 + ], + [ + "AABCcbBB", + "Bab", + 12 + ], + [ + "AABa", + "ACCcBA", + 7 + ], + [ + "AABa", + "BCcbaAa", + 11 + ], + [ + "AABa", + "C", + 8 + ], + [ + "AABaAc", + "Ccaa", + 9 + ], + [ + "AABaC", + "aA", + 7 + ], + [ + "AABaCccb", + "AABaAAbc", + 8 + ], + [ + "AABabcc", + "ccaABC", + 11 + ], + [ + "AABacB", + "cCbaa", + 9 + ], + [ + "AABacBac", + "AcAa", + 10 + ], + [ + "AABaccb", + "CcbBbbc", + 12 + ], + [ + "AABbBa", + "bBAcbaaB", + 10 + ], + [ + "AABbCc", + "AccbaB", + 8 + ], + [ + "AABba", + "cBbAc", + 7 + ], + [ + "AABbaBa", + "BBaAbb", + 10 + ], + [ + "AABc", + "BccAAA", + 10 + ], + [ + "AABcABaaA", + "cbB", + 14 + ], + [ + "AABcAC", + "acabAC", + 7 + ], + [ + "AABcBbcb", + "baAACAbAB", + 11 + ], + [ + "AABcCB", + "ca", + 10 + ], + [ + "AABcaBC", + "AaAaab", + 8 + ], + [ + "AAC", + "CBabAaB", + 11 + ], + [ + "AAC", + "aBA", + 5 + ], + [ + "AAC", + "aCBCBbac", + 13 + ], + [ + "AAC", + "aCa", + 5 + ], + [ + "AAC", + "baaBAba", + 11 + ], + [ + "AAC", + "bbAbc", + 7 + ], + [ + "AACA", + "aaCB", + 4 + ], + [ + "AACA", + "ab", + 7 + ], + [ + "AACA", + "cc", + 7 + ], + [ + "AACAba", + "CBAAaAB", + 9 + ], + [ + "AACAba", + "b", + 10 + ], + [ + "AACAbbc", + "AbaCABCcB", + 8 + ], + [ + "AACBACbcB", + "b", + 16 + ], + [ + "AACBC", + "Abbc", + 6 + ], + [ + "AACBC", + "aaCcAb", + 8 + ], + [ + "AACBCA", + "A", + 10 + ], + [ + "AACBCcbaa", + "cbBca", + 12 + ], + [ + "AACBaB", + "AAcABBA", + 7 + ], + [ + "AACBc", + "aABCaACA", + 10 + ], + [ + "AACC", + "bB", + 8 + ], + [ + "AACCA", + "b", + 10 + ], + [ + "AACCAC", + "Ccab", + 8 + ], + [ + "AACCBBAac", + "abbabb", + 14 + ], + [ + "AACCCCbc", + "BBCCaB", + 11 + ], + [ + "AACCCc", + "bcCbaBACA", + 14 + ], + [ + "AACCaBcA", + "BaC", + 13 + ], + [ + "AACCaca", + "ccbaBBaB", + 13 + ], + [ + "AACCc", + "b", + 10 + ], + [ + "AACa", + "BAC", + 4 + ], + [ + "AACaAaBA", + "AbcbB", + 11 + ], + [ + "AACaac", + "C", + 10 + ], + [ + "AACacB", + "CcbCBaBb", + 11 + ], + [ + "AACb", + "AC", + 4 + ], + [ + "AACb", + "ac", + 6 + ], + [ + "AACbAC", + "BBCCBCa", + 10 + ], + [ + "AACbC", + "bb", + 8 + ], + [ + "AACbbcaa", + "bbaaBCBb", + 14 + ], + [ + "AACbc", + "AbcbbBcCB", + 11 + ], + [ + "AACbc", + "aCCB", + 6 + ], + [ + "AACbcAaAb", + "BABcaBBAA", + 12 + ], + [ + "AACbcb", + "cabBc", + 8 + ], + [ + "AACc", + "BbCC", + 5 + ], + [ + "AACc", + "bbb", + 8 + ], + [ + "AACcAABAC", + "CBAcBaABa", + 11 + ], + [ + "AACcAb", + "CBCbCACcb", + 11 + ], + [ + "AACcCAAc", + "AbAACbCC", + 11 + ], + [ + "AACcabbC", + "cAAccbAB", + 9 + ], + [ + "AACcbA", + "BCCCcaCc", + 12 + ], + [ + "AAa", + "ACCC", + 6 + ], + [ + "AAa", + "BcAC", + 6 + ], + [ + "AAa", + "CACcaAAAb", + 13 + ], + [ + "AAa", + "CAaCA", + 6 + ], + [ + "AAa", + "aCcBACb", + 11 + ], + [ + "AAa", + "abACAA", + 7 + ], + [ + "AAa", + "b", + 6 + ], + [ + "AAa", + "cAAaBA", + 6 + ], + [ + "AAa", + "cBaB", + 6 + ], + [ + "AAaA", + "ca", + 6 + ], + [ + "AAaACcb", + "ABCcb", + 6 + ], + [ + "AAaB", + "aCAC", + 6 + ], + [ + "AAaB", + "cbAb", + 6 + ], + [ + "AAaBA", + "bBAc", + 8 + ], + [ + "AAaBB", + "ABCCaCBb", + 9 + ], + [ + "AAaBa", + "AcB", + 6 + ], + [ + "AAaBaB", + "aCcb", + 9 + ], + [ + "AAaBacBaC", + "CacABc", + 12 + ], + [ + "AAaBbAb", + "cC", + 14 + ], + [ + "AAaBbcaa", + "caB", + 12 + ], + [ + "AAaC", + "aCbBcBCB", + 13 + ], + [ + "AAaCACAB", + "cBa", + 14 + ], + [ + "AAaCCaCaA", + "baACABCCA", + 10 + ], + [ + "AAaCCcbb", + "aa", + 13 + ], + [ + "AAaCaccb", + "AAA", + 11 + ], + [ + "AAaCbAAAc", + "babcbBccb", + 14 + ], + [ + "AAaCcB", + "aA", + 9 + ], + [ + "AAaa", + "Abcbb", + 8 + ], + [ + "AAaaA", + "ACaCAcaa", + 9 + ], + [ + "AAaaB", + "cAaaAB", + 4 + ], + [ + "AAaaBbaC", + "bbCaCBc", + 12 + ], + [ + "AAaaCBabC", + "bBb", + 14 + ], + [ + "AAaacacca", + "C", + 17 + ], + [ + "AAaaccbA", + "CB", + 14 + ], + [ + "AAabAbac", + "cbABacbbc", + 11 + ], + [ + "AAabBBaA", + "caCcAAB", + 13 + ], + [ + "AAabCA", + "CbACAA", + 9 + ], + [ + "AAabCbCA", + "ACb", + 10 + ], + [ + "AAabb", + "B", + 9 + ], + [ + "AAabc", + "cBc", + 7 + ], + [ + "AAacA", + "CBBcB", + 8 + ], + [ + "AAacA", + "Cc", + 8 + ], + [ + "AAacCBCBc", + "bAaacCc", + 9 + ], + [ + "AAacCaBa", + "a", + 14 + ], + [ + "AAacCbCCC", + "BaB", + 15 + ], + [ + "AAaccABAA", + "bBCb", + 16 + ], + [ + "AAb", + "AbB", + 3 + ], + [ + "AAb", + "B", + 5 + ], + [ + "AAb", + "aCBbBBa", + 11 + ], + [ + "AAb", + "bAAc", + 4 + ], + [ + "AAb", + "bABA", + 5 + ], + [ + "AAb", + "bBCbBbaCA", + 16 + ], + [ + "AAb", + "bbBBaB", + 10 + ], + [ + "AAb", + "bbCcAb", + 8 + ], + [ + "AAbA", + "Bbb", + 6 + ], + [ + "AAbAAaBCC", + "b", + 16 + ], + [ + "AAbABB", + "Ca", + 11 + ], + [ + "AAbAaBBcB", + "BABaCB", + 10 + ], + [ + "AAbBBb", + "a", + 11 + ], + [ + "AAbBCCb", + "bA", + 12 + ], + [ + "AAbBbBcC", + "C", + 14 + ], + [ + "AAbBbbBcc", + "aCA", + 16 + ], + [ + "AAbC", + "cBbBCBBb", + 12 + ], + [ + "AAbCbBcC", + "BaABcCaCc", + 11 + ], + [ + "AAbCcCbC", + "a", + 15 + ], + [ + "AAba", + "cBCabCC", + 11 + ], + [ + "AAba", + "cBcBCcaBc", + 15 + ], + [ + "AAbaCabC", + "Aa", + 12 + ], + [ + "AAbabb", + "cbBAca", + 10 + ], + [ + "AAbb", + "AcbaaAB", + 9 + ], + [ + "AAbb", + "bccb", + 6 + ], + [ + "AAbbAcAB", + "cbacaBBBA", + 14 + ], + [ + "AAbbBAaAC", + "bcbaabc", + 11 + ], + [ + "AAbbbB", + "bCa", + 10 + ], + [ + "AAbcBbA", + "aaccCbB", + 8 + ], + [ + "AAbcaBBBA", + "ABb", + 13 + ], + [ + "AAbcacaB", + "cAAAbBb", + 12 + ], + [ + "AAbcbacC", + "abbbAaCCa", + 10 + ], + [ + "AAc", + "ACAc", + 2 + ], + [ + "AAc", + "AbB", + 4 + ], + [ + "AAc", + "BAabA", + 7 + ], + [ + "AAc", + "aAbc", + 3 + ], + [ + "AAc", + "aaaaacAA", + 12 + ], + [ + "AAc", + "aab", + 4 + ], + [ + "AAc", + "c", + 4 + ], + [ + "AAcA", + "AAbAAA", + 6 + ], + [ + "AAcABCA", + "bBBAbab", + 11 + ], + [ + "AAcAC", + "cCAAa", + 8 + ], + [ + "AAcAac", + "cAcA", + 6 + ], + [ + "AAcAbBcB", + "AAAAB", + 8 + ], + [ + "AAcAccAb", + "Ccac", + 11 + ], + [ + "AAcBBBcCB", + "A", + 16 + ], + [ + "AAcBbA", + "Aa", + 9 + ], + [ + "AAcC", + "bCbcaCCa", + 12 + ], + [ + "AAcCabaB", + "cACAC", + 11 + ], + [ + "AAcaACCBA", + "abcBCca", + 11 + ], + [ + "AAcaCBAcB", + "cCaBCcC", + 11 + ], + [ + "AAcab", + "A", + 8 + ], + [ + "AAcab", + "AcaabCccc", + 12 + ], + [ + "AAcb", + "aCBCcCBcC", + 14 + ], + [ + "AAcbAccc", + "CcccbA", + 12 + ], + [ + "AAcbCCAcB", + "BbbCaACc", + 11 + ], + [ + "AAcbCca", + "a", + 12 + ], + [ + "AAcbc", + "caCcb", + 7 + ], + [ + "AAcc", + "CcaBbcC", + 10 + ], + [ + "AAccB", + "cCca", + 7 + ], + [ + "AAccb", + "aA", + 7 + ], + [ + "AAccccbAA", + "caAaa", + 14 + ], + [ + "AB", + "A", + 2 + ], + [ + "AB", + "AACca", + 8 + ], + [ + "AB", + "ABcAc", + 6 + ], + [ + "AB", + "ACCACCbC", + 13 + ], + [ + "AB", + "BAA", + 4 + ], + [ + "AB", + "BAbac", + 7 + ], + [ + "AB", + "BC", + 4 + ], + [ + "AB", + "BbaCAc", + 10 + ], + [ + "AB", + "Bbac", + 7 + ], + [ + "AB", + "BcA", + 6 + ], + [ + "AB", + "CAAacB", + 8 + ], + [ + "AB", + "CC", + 4 + ], + [ + "AB", + "CCaabBcb", + 13 + ], + [ + "AB", + "CCcB", + 6 + ], + [ + "AB", + "CbA", + 5 + ], + [ + "AB", + "aCAC", + 6 + ], + [ + "AB", + "bBA", + 4 + ], + [ + "AB", + "bCbB", + 6 + ], + [ + "AB", + "bCccbbb", + 13 + ], + [ + "AB", + "bbb", + 5 + ], + [ + "AB", + "bcAbAAA", + 11 + ], + [ + "AB", + "bcbC", + 7 + ], + [ + "AB", + "c", + 4 + ], + [ + "AB", + "cCabccbCB", + 15 + ], + [ + "AB", + "caCBB", + 7 + ], + [ + "AB", + "caaac", + 9 + ], + [ + "AB", + "cababCa", + 12 + ], + [ + "ABA", + "A", + 4 + ], + [ + "ABA", + "ACBcBa", + 7 + ], + [ + "ABA", + "AcAb", + 4 + ], + [ + "ABA", + "abbBaaC", + 10 + ], + [ + "ABA", + "c", + 6 + ], + [ + "ABA", + "caAaCCbc", + 13 + ], + [ + "ABAAB", + "cbB", + 7 + ], + [ + "ABAABacbC", + "Ba", + 14 + ], + [ + "ABAAaCAa", + "cABb", + 14 + ], + [ + "ABAAaaBb", + "BCACaB", + 8 + ], + [ + "ABAAabb", + "bAcABc", + 9 + ], + [ + "ABAAabbCC", + "cbcBc", + 15 + ], + [ + "ABAAbBCb", + "aCACb", + 9 + ], + [ + "ABAAc", + "AbCc", + 5 + ], + [ + "ABAAcC", + "A", + 10 + ], + [ + "ABABBCaBb", + "aAcBcc", + 12 + ], + [ + "ABABCAB", + "cbBBCaA", + 8 + ], + [ + "ABABaaCc", + "BBBBBAB", + 11 + ], + [ + "ABABbCCab", + "aAC", + 13 + ], + [ + "ABAC", + "abaAC", + 4 + ], + [ + "ABACAccCC", + "BBCBcbCAc", + 11 + ], + [ + "ABACCAACA", + "aAacA", + 11 + ], + [ + "ABACaa", + "BA", + 8 + ], + [ + "ABACb", + "b", + 8 + ], + [ + "ABACbaCcC", + "cCBBbCbC", + 12 + ], + [ + "ABAa", + "A", + 6 + ], + [ + "ABAaAbbc", + "ccaCBA", + 13 + ], + [ + "ABAaB", + "bb", + 8 + ], + [ + "ABAaCCAB", + "acba", + 12 + ], + [ + "ABAaaBCBB", + "BaCC", + 12 + ], + [ + "ABAacbAbA", + "ABAb", + 10 + ], + [ + "ABAb", + "AcA", + 4 + ], + [ + "ABAb", + "cCab", + 5 + ], + [ + "ABAbACCB", + "cBCB", + 10 + ], + [ + "ABAbBC", + "BBAacabC", + 9 + ], + [ + "ABAbCCb", + "BAA", + 10 + ], + [ + "ABAbaCBB", + "aaCbbbBB", + 9 + ], + [ + "ABAbaCcC", + "bac", + 10 + ], + [ + "ABAc", + "CBACCbBA", + 11 + ], + [ + "ABAcAcB", + "Ac", + 10 + ], + [ + "ABAca", + "aCcCaAbC", + 12 + ], + [ + "ABAcaBc", + "bCaabABAb", + 14 + ], + [ + "ABAcaac", + "BcA", + 9 + ], + [ + "ABAcabA", + "aa", + 11 + ], + [ + "ABAcb", + "cCa", + 9 + ], + [ + "ABB", + "acaccCcA", + 15 + ], + [ + "ABB", + "bAaAAC", + 10 + ], + [ + "ABB", + "bBbAA", + 7 + ], + [ + "ABB", + "bac", + 6 + ], + [ + "ABBAAaCc", + "CaCaCAaCc", + 9 + ], + [ + "ABBAAbb", + "acBbCba", + 9 + ], + [ + "ABBAAc", + "bBccabAcB", + 11 + ], + [ + "ABBAbbAA", + "abAaaa", + 10 + ], + [ + "ABBAcBaa", + "Ab", + 13 + ], + [ + "ABBAcbAc", + "abBccbA", + 6 + ], + [ + "ABBBAa", + "ccBbcBa", + 9 + ], + [ + "ABBBAbAcc", + "abCaCAbcc", + 10 + ], + [ + "ABBBB", + "cAaCBCbCb", + 12 + ], + [ + "ABBBCaB", + "BaAbBabBa", + 12 + ], + [ + "ABBCC", + "aBBbaBAab", + 13 + ], + [ + "ABBCCc", + "aAcAbB", + 11 + ], + [ + "ABBCCcab", + "AcBcbcbAc", + 10 + ], + [ + "ABBa", + "bA", + 6 + ], + [ + "ABBaACA", + "cbBcAB", + 9 + ], + [ + "ABBaACCcb", + "bbCCcCb", + 10 + ], + [ + "ABBaa", + "bcabCCC", + 12 + ], + [ + "ABBabAbAC", + "AbcbbAcCc", + 10 + ], + [ + "ABBaba", + "BCcbAaBCA", + 13 + ], + [ + "ABBacb", + "CAbcBAbBa", + 11 + ], + [ + "ABBb", + "Aabc", + 5 + ], + [ + "ABBb", + "aAcAb", + 6 + ], + [ + "ABBbBAbCA", + "CCBACAAb", + 14 + ], + [ + "ABBbCbb", + "bccbcAc", + 11 + ], + [ + "ABBbb", + "aAabbCbCC", + 11 + ], + [ + "ABBcBBC", + "bCaB", + 10 + ], + [ + "ABBcCCC", + "bABbaCCC", + 5 + ], + [ + "ABBcb", + "aCc", + 7 + ], + [ + "ABBcbcC", + "AACBbAC", + 8 + ], + [ + "ABBccBcAa", + "ABa", + 12 + ], + [ + "ABBccCBA", + "CCCACa", + 12 + ], + [ + "ABBccc", + "B", + 10 + ], + [ + "ABC", + "BAaACA", + 8 + ], + [ + "ABC", + "BCc", + 4 + ], + [ + "ABC", + "CAa", + 6 + ], + [ + "ABC", + "CBca", + 5 + ], + [ + "ABC", + "CbCbbCB", + 11 + ], + [ + "ABC", + "bacacB", + 10 + ], + [ + "ABC", + "c", + 5 + ], + [ + "ABCAB", + "bAaAc", + 8 + ], + [ + "ABCAC", + "BaCBaC", + 7 + ], + [ + "ABCAa", + "aABbb", + 8 + ], + [ + "ABCAbAacA", + "cbCAa", + 11 + ], + [ + "ABCBC", + "AB", + 6 + ], + [ + "ABCBa", + "CcAACBC", + 8 + ], + [ + "ABCBa", + "caBbccC", + 10 + ], + [ + "ABCCAa", + "A", + 10 + ], + [ + "ABCCC", + "CC", + 6 + ], + [ + "ABCCbcBc", + "AbCBcBB", + 6 + ], + [ + "ABCCcCBB", + "bbccAbBBc", + 11 + ], + [ + "ABCCccaB", + "aCcACCCa", + 10 + ], + [ + "ABCaBa", + "BBb", + 8 + ], + [ + "ABCaBa", + "bbAcAA", + 10 + ], + [ + "ABCaC", + "bcCcab", + 8 + ], + [ + "ABCaa", + "AbCcaAb", + 6 + ], + [ + "ABCab", + "b", + 8 + ], + [ + "ABCabB", + "AAc", + 9 + ], + [ + "ABCb", + "CBBBC", + 7 + ], + [ + "ABCb", + "aABCcBC", + 7 + ], + [ + "ABCbAA", + "Ba", + 9 + ], + [ + "ABCbBBbc", + "cabbbCbcB", + 11 + ], + [ + "ABCbC", + "cc", + 8 + ], + [ + "ABCbCaCCc", + "aABB", + 15 + ], + [ + "ABCbaaCbb", + "CBbBBABBB", + 12 + ], + [ + "ABCbaaac", + "aaCaCCCca", + 13 + ], + [ + "ABCbbA", + "BCaAa", + 7 + ], + [ + "ABCbcC", + "ccbBBa", + 11 + ], + [ + "ABCc", + "Aa", + 6 + ], + [ + "ABCcAabbB", + "cCCaBBc", + 11 + ], + [ + "ABCcBcbC", + "Bcbcc", + 8 + ], + [ + "ABCcb", + "CacBAb", + 9 + ], + [ + "ABCccaAcC", + "BaAcBbC", + 12 + ], + [ + "ABa", + "AaabCa", + 7 + ], + [ + "ABa", + "CaA", + 5 + ], + [ + "ABaABc", + "caC", + 9 + ], + [ + "ABaAC", + "Caca", + 8 + ], + [ + "ABaAb", + "bAaC", + 7 + ], + [ + "ABaB", + "c", + 8 + ], + [ + "ABaBa", + "aacCAc", + 10 + ], + [ + "ABaBb", + "acbaBCcb", + 8 + ], + [ + "ABaBccB", + "BacbBBBcb", + 11 + ], + [ + "ABaCAb", + "aba", + 8 + ], + [ + "ABaCCC", + "aCAaacbc", + 10 + ], + [ + "ABaCc", + "bB", + 8 + ], + [ + "ABaa", + "CCcCC", + 10 + ], + [ + "ABabAbb", + "BCAcB", + 9 + ], + [ + "ABabBC", + "acc", + 9 + ], + [ + "ABabBaAc", + "AbBCbaBBB", + 12 + ], + [ + "ABababAC", + "aabaaBAA", + 8 + ], + [ + "ABabb", + "bBb", + 6 + ], + [ + "ABabcBacC", + "aC", + 14 + ], + [ + "ABabcBcc", + "BAcB", + 9 + ], + [ + "ABacCac", + "bbaCBcbcB", + 11 + ], + [ + "ABacbAAab", + "bC", + 16 + ], + [ + "ABacbbaCC", + "CbCbcbBAA", + 13 + ], + [ + "ABb", + "AaAcB", + 7 + ], + [ + "ABb", + "B", + 4 + ], + [ + "ABb", + "BB", + 3 + ], + [ + "ABb", + "BBaCCAAA", + 14 + ], + [ + "ABb", + "bBBaA", + 7 + ], + [ + "ABb", + "baCaaBaa", + 13 + ], + [ + "ABbA", + "CaACAABa", + 12 + ], + [ + "ABbACAC", + "BAcBBaB", + 12 + ], + [ + "ABbACAaAa", + "Cca", + 14 + ], + [ + "ABbAaa", + "cbacaa", + 7 + ], + [ + "ABbAaaB", + "BBc", + 11 + ], + [ + "ABbAacbAC", + "bAbAaacaa", + 11 + ], + [ + "ABbAbA", + "aaAaB", + 9 + ], + [ + "ABbBAb", + "a", + 11 + ], + [ + "ABbBcCbcA", + "acaAacBAB", + 15 + ], + [ + "ABbCC", + "caACcc", + 9 + ], + [ + "ABbCaAbb", + "b", + 14 + ], + [ + "ABbCb", + "aABCBBca", + 10 + ], + [ + "ABbCbA", + "A", + 10 + ], + [ + "ABbCbA", + "AbCbB", + 4 + ], + [ + "ABbCbbBa", + "B", + 14 + ], + [ + "ABbCcB", + "AaCccab", + 8 + ], + [ + "ABbaACBCc", + "baBCbCbcB", + 11 + ], + [ + "ABbaabcCC", + "aaaacAAAA", + 15 + ], + [ + "ABbacAAA", + "CAAccb", + 13 + ], + [ + "ABbb", + "cAaC", + 8 + ], + [ + "ABbbA", + "BCcbb", + 8 + ], + [ + "ABbbC", + "ABBc", + 4 + ], + [ + "ABbbbB", + "BCabaCc", + 12 + ], + [ + "ABbc", + "bBcAAac", + 10 + ], + [ + "ABbcc", + "AbBABaaa", + 11 + ], + [ + "ABbccBA", + "cACaCcCC", + 11 + ], + [ + "ABc", + "a", + 5 + ], + [ + "ABc", + "aA", + 5 + ], + [ + "ABc", + "b", + 5 + ], + [ + "ABcA", + "CB", + 6 + ], + [ + "ABcAa", + "BcbAC", + 6 + ], + [ + "ABcAaB", + "ab", + 9 + ], + [ + "ABcAbAa", + "AcAABbb", + 9 + ], + [ + "ABcAc", + "c", + 8 + ], + [ + "ABcAcCAc", + "cAcBcCaC", + 8 + ], + [ + "ABcBBcCCB", + "BaAcBbC", + 12 + ], + [ + "ABcBbAb", + "aAbACcAca", + 13 + ], + [ + "ABcBcCCbB", + "cbAC", + 13 + ], + [ + "ABcC", + "Cabc", + 6 + ], + [ + "ABcC", + "c", + 6 + ], + [ + "ABcCAaCc", + "C", + 14 + ], + [ + "ABcCAbCba", + "CcBa", + 12 + ], + [ + "ABcCBb", + "CAb", + 8 + ], + [ + "ABcCCa", + "bCaca", + 7 + ], + [ + "ABcCCcBa", + "Cbc", + 12 + ], + [ + "ABcCbbBbC", + "b", + 16 + ], + [ + "ABcaBC", + "Bcc", + 7 + ], + [ + "ABcaBb", + "CB", + 9 + ], + [ + "ABcb", + "a", + 7 + ], + [ + "ABcbAcc", + "AbBa", + 9 + ], + [ + "ABcbBC", + "cCcAa", + 10 + ], + [ + "ABcbCacCb", + "ba", + 14 + ], + [ + "ABcc", + "bAAcbCa", + 9 + ], + [ + "ABccA", + "CA", + 7 + ], + [ + "ABcccb", + "CB", + 10 + ], + [ + "AC", + "A", + 2 + ], + [ + "AC", + "AAb", + 4 + ], + [ + "AC", + "AB", + 2 + ], + [ + "AC", + "ABBc", + 5 + ], + [ + "AC", + "ABaaaBc", + 11 + ], + [ + "AC", + "ACBaAbb", + 10 + ], + [ + "AC", + "AaBcBC", + 8 + ], + [ + "AC", + "AaCCCB", + 8 + ], + [ + "AC", + "AabB", + 6 + ], + [ + "AC", + "AacAaBc", + 11 + ], + [ + "AC", + "AbBCacbA", + 12 + ], + [ + "AC", + "AbbbcbACb", + 14 + ], + [ + "AC", + "B", + 4 + ], + [ + "AC", + "BBbA", + 8 + ], + [ + "AC", + "BCaa", + 6 + ], + [ + "AC", + "BbCaaA", + 10 + ], + [ + "AC", + "BcAab", + 8 + ], + [ + "AC", + "C", + 2 + ], + [ + "AC", + "CAcb", + 5 + ], + [ + "AC", + "Ca", + 4 + ], + [ + "AC", + "CabAB", + 8 + ], + [ + "AC", + "aABaABAB", + 14 + ], + [ + "AC", + "aAacbbcAC", + 14 + ], + [ + "AC", + "aBCaCBba", + 13 + ], + [ + "AC", + "aa", + 3 + ], + [ + "AC", + "aaaBBabAb", + 16 + ], + [ + "AC", + "baccAbcB", + 13 + ], + [ + "AC", + "c", + 3 + ], + [ + "AC", + "cACCaBA", + 10 + ], + [ + "AC", + "cCaaAacaA", + 15 + ], + [ + "AC", + "cbcbbcC", + 12 + ], + [ + "ACA", + "Aa", + 3 + ], + [ + "ACA", + "C", + 4 + ], + [ + "ACA", + "CbaaBc", + 11 + ], + [ + "ACA", + "aAABAAcb", + 12 + ], + [ + "ACA", + "cACCbaAaC", + 12 + ], + [ + "ACAA", + "AcaAAb", + 5 + ], + [ + "ACAABBaC", + "A", + 14 + ], + [ + "ACAABcA", + "bCBACacC", + 10 + ], + [ + "ACAAC", + "bB", + 10 + ], + [ + "ACAAC", + "cBaaCabaC", + 11 + ], + [ + "ACAAa", + "CAa", + 4 + ], + [ + "ACAAaCaA", + "AAacb", + 9 + ], + [ + "ACAAbcaAa", + "bba", + 14 + ], + [ + "ACABA", + "bAB", + 6 + ], + [ + "ACABA", + "c", + 9 + ], + [ + "ACABABA", + "CcC", + 12 + ], + [ + "ACABACaB", + "bcBAaAB", + 8 + ], + [ + "ACABbCaBc", + "cA", + 15 + ], + [ + "ACAC", + "aaBAA", + 7 + ], + [ + "ACACB", + "aaCcbaBC", + 11 + ], + [ + "ACACBB", + "cCb", + 8 + ], + [ + "ACACBbccA", + "BABbAc", + 10 + ], + [ + "ACACC", + "BaAA", + 8 + ], + [ + "ACACa", + "Bccc", + 8 + ], + [ + "ACACbB", + "aCaacAaC", + 11 + ], + [ + "ACACbab", + "cC", + 11 + ], + [ + "ACAaA", + "Aaabbc", + 9 + ], + [ + "ACAaB", + "caBBcB", + 9 + ], + [ + "ACAaBA", + "CAbCC", + 8 + ], + [ + "ACAaCBccb", + "c", + 16 + ], + [ + "ACAabbCAc", + "BB", + 16 + ], + [ + "ACAb", + "AaaCC", + 7 + ], + [ + "ACAbBCaCA", + "A", + 16 + ], + [ + "ACAbbaaBA", + "aacacABbc", + 15 + ], + [ + "ACAbcB", + "bcBCB", + 7 + ], + [ + "ACAc", + "CCbcaabA", + 12 + ], + [ + "ACAcBCc", + "AcaAbcb", + 8 + ], + [ + "ACAcaabA", + "CACbBC", + 10 + ], + [ + "ACB", + "ACbbaBb", + 8 + ], + [ + "ACB", + "aCbbc", + 6 + ], + [ + "ACB", + "baBacBAB", + 12 + ], + [ + "ACB", + "cabbCcBA", + 11 + ], + [ + "ACBA", + "CbCCCaCAc", + 14 + ], + [ + "ACBAC", + "CaABbbcBA", + 13 + ], + [ + "ACBACBB", + "cAABCbBC", + 9 + ], + [ + "ACBACCba", + "ACBCcCb", + 5 + ], + [ + "ACBAbCBc", + "bC", + 12 + ], + [ + "ACBAba", + "aAaCaC", + 10 + ], + [ + "ACBAcb", + "aCACc", + 6 + ], + [ + "ACBB", + "bAbb", + 6 + ], + [ + "ACBBAB", + "BbBCb", + 8 + ], + [ + "ACBBABBa", + "bbabA", + 11 + ], + [ + "ACBBBaA", + "bccbA", + 10 + ], + [ + "ACBBBaCA", + "bCCCB", + 12 + ], + [ + "ACBBcAccc", + "ab", + 16 + ], + [ + "ACBCCC", + "a", + 11 + ], + [ + "ACBCbaa", + "ACCAbCCb", + 10 + ], + [ + "ACBaA", + "BcBcBcA", + 9 + ], + [ + "ACBaBa", + "AaAa", + 6 + ], + [ + "ACBabaa", + "abcAbbb", + 10 + ], + [ + "ACBabbab", + "CAa", + 11 + ], + [ + "ACBac", + "BcbcCb", + 9 + ], + [ + "ACBb", + "bcCAca", + 10 + ], + [ + "ACBbCaaBB", + "Cc", + 15 + ], + [ + "ACBba", + "CcaAbbc", + 10 + ], + [ + "ACBbaAbAC", + "aCabbAC", + 7 + ], + [ + "ACBbcAcB", + "cb", + 13 + ], + [ + "ACBcAB", + "cb", + 9 + ], + [ + "ACBcaaAAb", + "ACBA", + 10 + ], + [ + "ACC", + "BcccCaAba", + 15 + ], + [ + "ACC", + "Ccb", + 5 + ], + [ + "ACC", + "aB", + 5 + ], + [ + "ACC", + "bBcABBbAC", + 14 + ], + [ + "ACC", + "cABA", + 6 + ], + [ + "ACC", + "caACC", + 4 + ], + [ + "ACCA", + "BaaBc", + 9 + ], + [ + "ACCA", + "BacAabAAa", + 14 + ], + [ + "ACCACABB", + "ABAAbBaBa", + 11 + ], + [ + "ACCAbB", + "CcbAacC", + 11 + ], + [ + "ACCAccAB", + "ACbCC", + 10 + ], + [ + "ACCBAAC", + "Ac", + 11 + ], + [ + "ACCBAaBC", + "a", + 14 + ], + [ + "ACCBB", + "BcCccabbA", + 13 + ], + [ + "ACCBBcac", + "CCB", + 10 + ], + [ + "ACCBCBC", + "AcaaCacba", + 11 + ], + [ + "ACCBac", + "aCACaCc", + 7 + ], + [ + "ACCBb", + "BaCac", + 8 + ], + [ + "ACCBbcbAC", + "AAaCaCBCC", + 12 + ], + [ + "ACCBcCBB", + "aBBABb", + 10 + ], + [ + "ACCBcaaCa", + "CBb", + 14 + ], + [ + "ACCBcc", + "bbAa", + 11 + ], + [ + "ACCCAaCcC", + "CBAc", + 12 + ], + [ + "ACCCBac", + "CcCBACBB", + 9 + ], + [ + "ACCCC", + "CACCACc", + 5 + ], + [ + "ACCCb", + "CbBaCcb", + 9 + ], + [ + "ACCCbaCAc", + "abACCC", + 11 + ], + [ + "ACCaAACa", + "C", + 14 + ], + [ + "ACCaACAa", + "a", + 14 + ], + [ + "ACCaB", + "A", + 8 + ], + [ + "ACCaCA", + "a", + 10 + ], + [ + "ACCaaAc", + "A", + 12 + ], + [ + "ACCac", + "a", + 8 + ], + [ + "ACCbA", + "aCCBa", + 3 + ], + [ + "ACCbB", + "BaAa", + 10 + ], + [ + "ACCbC", + "CcbaCC", + 7 + ], + [ + "ACCbCba", + "a", + 12 + ], + [ + "ACCbaBbb", + "aA", + 14 + ], + [ + "ACCbba", + "aaCBCbccB", + 11 + ], + [ + "ACCbbbC", + "BCbABAbaA", + 13 + ], + [ + "ACCbcBa", + "aCBaBcbC", + 9 + ], + [ + "ACCbcbccB", + "caAAbaCcb", + 13 + ], + [ + "ACCcAccCc", + "AccAb", + 11 + ], + [ + "ACCcBcB", + "AcBcAAca", + 9 + ], + [ + "ACCcC", + "a", + 9 + ], + [ + "ACCcaC", + "cCA", + 8 + ], + [ + "ACCcacC", + "BBaaAcAab", + 14 + ], + [ + "ACa", + "AcbCAbaB", + 10 + ], + [ + "ACa", + "Bca", + 3 + ], + [ + "ACa", + "ba", + 4 + ], + [ + "ACa", + "baAAaba", + 10 + ], + [ + "ACaA", + "A", + 6 + ], + [ + "ACaA", + "ABCAba", + 6 + ], + [ + "ACaA", + "acbCc", + 8 + ], + [ + "ACaA", + "cc", + 7 + ], + [ + "ACaAC", + "cABCBAc", + 7 + ], + [ + "ACaAcABa", + "cCAACC", + 10 + ], + [ + "ACaB", + "ABAbccB", + 9 + ], + [ + "ACaB", + "bAAAaAbAA", + 13 + ], + [ + "ACaBBBCB", + "AaAC", + 10 + ], + [ + "ACaBBaAa", + "AcaBcBbcB", + 9 + ], + [ + "ACaBCB", + "cc", + 10 + ], + [ + "ACaBCBb", + "b", + 12 + ], + [ + "ACaBacCc", + "bb", + 15 + ], + [ + "ACaBcb", + "BcCc", + 9 + ], + [ + "ACaCACc", + "c", + 12 + ], + [ + "ACaCBccBc", + "CcABCcBcb", + 9 + ], + [ + "ACaCCBA", + "AAcacC", + 8 + ], + [ + "ACaa", + "BAAc", + 7 + ], + [ + "ACaaBAbc", + "ACAccba", + 9 + ], + [ + "ACaabb", + "BCAb", + 7 + ], + [ + "ACaacBa", + "cBCBAc", + 11 + ], + [ + "ACabABBA", + "CAcCbB", + 10 + ], + [ + "ACabC", + "B", + 9 + ], + [ + "ACabac", + "BACcbCcbB", + 10 + ], + [ + "ACabbcA", + "aCacc", + 7 + ], + [ + "ACaccaa", + "AA", + 11 + ], + [ + "ACb", + "A", + 4 + ], + [ + "ACb", + "cBAab", + 6 + ], + [ + "ACb", + "ccbccbB", + 11 + ], + [ + "ACbA", + "AaBacA", + 7 + ], + [ + "ACbA", + "BaCCbA", + 5 + ], + [ + "ACbA", + "abACa", + 7 + ], + [ + "ACbACcbaB", + "ccc", + 14 + ], + [ + "ACbAbaCBC", + "acbaba", + 9 + ], + [ + "ACbB", + "aCACBAbb", + 9 + ], + [ + "ACbBA", + "CACB", + 6 + ], + [ + "ACbBCC", + "bcbCaAB", + 11 + ], + [ + "ACbBa", + "B", + 8 + ], + [ + "ACbBaCab", + "cbB", + 11 + ], + [ + "ACbCCbBb", + "aaca", + 14 + ], + [ + "ACbCaA", + "bAbC", + 8 + ], + [ + "ACbCb", + "c", + 9 + ], + [ + "ACbCc", + "ABc", + 5 + ], + [ + "ACbCcbA", + "accAAAaCc", + 15 + ], + [ + "ACba", + "acABAcbC", + 11 + ], + [ + "ACba", + "bCabba", + 6 + ], + [ + "ACbaAaA", + "BaaAbcC", + 12 + ], + [ + "ACbaAbB", + "AaBbbbb", + 8 + ], + [ + "ACbaAc", + "ABaBbCBcB", + 12 + ], + [ + "ACbaCAC", + "bBcaC", + 8 + ], + [ + "ACbaCaB", + "cbaA", + 8 + ], + [ + "ACbaaaB", + "CAcA", + 10 + ], + [ + "ACbabABB", + "BCBABBAc", + 11 + ], + [ + "ACbabcac", + "ABBA", + 11 + ], + [ + "ACbbBB", + "cAbbaC", + 8 + ], + [ + "ACbbBb", + "abAAbCab", + 10 + ], + [ + "ACbbCc", + "AccCbBAAB", + 11 + ], + [ + "ACbbca", + "cbABC", + 9 + ], + [ + "ACbbccB", + "b", + 12 + ], + [ + "ACbcBBc", + "CBacBbCbA", + 11 + ], + [ + "ACbcCba", + "abBBc", + 10 + ], + [ + "ACbcCccA", + "BBaA", + 13 + ], + [ + "ACc", + "AABbabcbb", + 14 + ], + [ + "ACc", + "ABbCcC", + 6 + ], + [ + "ACc", + "ABcBaB", + 8 + ], + [ + "ACc", + "a", + 5 + ], + [ + "ACc", + "b", + 6 + ], + [ + "ACc", + "baCBbbCbc", + 13 + ], + [ + "ACc", + "bcAcC", + 6 + ], + [ + "ACc", + "c", + 4 + ], + [ + "ACcA", + "CbcBBcbb", + 13 + ], + [ + "ACcA", + "aBBA", + 5 + ], + [ + "ACcAAb", + "ccAa", + 6 + ], + [ + "ACcABac", + "cA", + 10 + ], + [ + "ACcAc", + "baC", + 8 + ], + [ + "ACcBCBbbB", + "aA", + 17 + ], + [ + "ACcBa", + "ABBbBAbca", + 12 + ], + [ + "ACcBa", + "abbaca", + 9 + ], + [ + "ACcBba", + "baaAcAcA", + 12 + ], + [ + "ACcBbaa", + "CbCcCCBAC", + 12 + ], + [ + "ACcC", + "AcCABAC", + 8 + ], + [ + "ACcCACb", + "CABB", + 9 + ], + [ + "ACcCC", + "ccabBBC", + 11 + ], + [ + "ACcCa", + "Bcb", + 8 + ], + [ + "ACcCacAa", + "CbCba", + 10 + ], + [ + "ACcCc", + "bbBCC", + 7 + ], + [ + "ACca", + "cBBCBcbC", + 12 + ], + [ + "ACcaCAb", + "aaA", + 9 + ], + [ + "ACcaaAb", + "CaCAc", + 8 + ], + [ + "ACcabc", + "AAACb", + 8 + ], + [ + "ACcac", + "abBCBCc", + 9 + ], + [ + "ACcbB", + "caBaa", + 10 + ], + [ + "ACcbC", + "aCA", + 7 + ], + [ + "ACcbaBb", + "aCACCCAaA", + 12 + ], + [ + "ACcbbC", + "cacb", + 8 + ], + [ + "ACcbcCBB", + "A", + 14 + ], + [ + "ACcc", + "Cca", + 4 + ], + [ + "ACcca", + "accBbB", + 8 + ], + [ + "ACccabBC", + "Bb", + 14 + ], + [ + "ACccb", + "C", + 8 + ], + [ + "ACccbCccb", + "BbCacaA", + 14 + ], + [ + "ACccca", + "A", + 10 + ], + [ + "Aa", + "A", + 2 + ], + [ + "Aa", + "AAcac", + 6 + ], + [ + "Aa", + "ABBa", + 4 + ], + [ + "Aa", + "AbAabCcB", + 12 + ], + [ + "Aa", + "AbCCCCbcC", + 16 + ], + [ + "Aa", + "AbccAA", + 9 + ], + [ + "Aa", + "B", + 4 + ], + [ + "Aa", + "BA", + 3 + ], + [ + "Aa", + "BB", + 4 + ], + [ + "Aa", + "BBaccBAc", + 14 + ], + [ + "Aa", + "BCAaBb", + 8 + ], + [ + "Aa", + "BaBBAc", + 10 + ], + [ + "Aa", + "BaBcCaBc", + 13 + ], + [ + "Aa", + "BaCc", + 6 + ], + [ + "Aa", + "BabcCcAb", + 14 + ], + [ + "Aa", + "BbBB", + 8 + ], + [ + "Aa", + "C", + 4 + ], + [ + "Aa", + "CAabcaaAB", + 14 + ], + [ + "Aa", + "CAbbCbCB", + 14 + ], + [ + "Aa", + "CBBaBccb", + 14 + ], + [ + "Aa", + "CBCAccba", + 12 + ], + [ + "Aa", + "a", + 2 + ], + [ + "Aa", + "aAc", + 4 + ], + [ + "Aa", + "aacb", + 5 + ], + [ + "Aa", + "acAAacaB", + 12 + ], + [ + "Aa", + "acaA", + 5 + ], + [ + "Aa", + "acbc", + 7 + ], + [ + "Aa", + "b", + 4 + ], + [ + "Aa", + "bBA", + 5 + ], + [ + "Aa", + "bBABbc", + 10 + ], + [ + "Aa", + "baABcc", + 10 + ], + [ + "Aa", + "babbc", + 8 + ], + [ + "Aa", + "bbabBCac", + 13 + ], + [ + "Aa", + "cAAB", + 5 + ], + [ + "Aa", + "cC", + 4 + ], + [ + "Aa", + "caCAB", + 8 + ], + [ + "Aa", + "ccabB", + 8 + ], + [ + "AaA", + "Acbc", + 6 + ], + [ + "AaA", + "BBAcB", + 8 + ], + [ + "AaA", + "BaAbb", + 6 + ], + [ + "AaA", + "CAcBB", + 8 + ], + [ + "AaA", + "Cc", + 6 + ], + [ + "AaA", + "a", + 4 + ], + [ + "AaA", + "bAcaab", + 7 + ], + [ + "AaA", + "bCbCac", + 10 + ], + [ + "AaA", + "bCcaBAa", + 10 + ], + [ + "AaA", + "cAbaCAbB", + 10 + ], + [ + "AaAA", + "bcacBb", + 10 + ], + [ + "AaAABca", + "aACbAbaa", + 9 + ], + [ + "AaAAbA", + "CAbA", + 6 + ], + [ + "AaAAbBCcc", + "bbcAb", + 14 + ], + [ + "AaAAcbcbB", + "aAcCCBba", + 11 + ], + [ + "AaABbA", + "BBaACcACA", + 12 + ], + [ + "AaABc", + "cCcaBB", + 9 + ], + [ + "AaABcBAbb", + "BABAbaB", + 11 + ], + [ + "AaACCAb", + "C", + 12 + ], + [ + "AaACCaac", + "aabBbAbCA", + 13 + ], + [ + "AaACa", + "BBb", + 10 + ], + [ + "AaAaABB", + "CABcCCcCA", + 16 + ], + [ + "AaAaBbCc", + "B", + 14 + ], + [ + "AaAaa", + "cba", + 8 + ], + [ + "AaAabb", + "CACAA", + 9 + ], + [ + "AaAaccCB", + "bAaacBbaA", + 12 + ], + [ + "AaAb", + "cABAcb", + 6 + ], + [ + "AaAbAaAb", + "a", + 14 + ], + [ + "AaAbAbbB", + "aA", + 12 + ], + [ + "AaAbBba", + "CCBac", + 12 + ], + [ + "AaAbb", + "BbaB", + 8 + ], + [ + "AaAbbc", + "cAAcCC", + 8 + ], + [ + "AaAbc", + "a", + 8 + ], + [ + "AaAc", + "aAaCA", + 6 + ], + [ + "AaAcA", + "BCAAAba", + 8 + ], + [ + "AaAccaBbc", + "baAcAACaa", + 11 + ], + [ + "AaB", + "AbbAbb", + 8 + ], + [ + "AaB", + "Bca", + 6 + ], + [ + "AaB", + "bbaAb", + 7 + ], + [ + "AaB", + "bcABBBCc", + 12 + ], + [ + "AaB", + "caAb", + 5 + ], + [ + "AaBA", + "cBAB", + 6 + ], + [ + "AaBACC", + "BCAAbB", + 10 + ], + [ + "AaBACaBA", + "cAc", + 13 + ], + [ + "AaBAa", + "c", + 10 + ], + [ + "AaBAc", + "CcACba", + 10 + ], + [ + "AaBAcBcaa", + "cb", + 15 + ], + [ + "AaBBAC", + "ACAca", + 9 + ], + [ + "AaBBAcbcc", + "CbCcBb", + 14 + ], + [ + "AaBBbcA", + "A", + 12 + ], + [ + "AaBCcbCB", + "CCcAb", + 11 + ], + [ + "AaBa", + "abA", + 4 + ], + [ + "AaBa", + "bbbCabA", + 10 + ], + [ + "AaBaaaCBa", + "A", + 16 + ], + [ + "AaBabBCb", + "aBac", + 9 + ], + [ + "AaBbB", + "bBaa", + 8 + ], + [ + "AaBba", + "ab", + 6 + ], + [ + "AaBbba", + "caAbBAa", + 7 + ], + [ + "AaBbbaac", + "Cbcca", + 12 + ], + [ + "AaBbbbA", + "AabcBAa", + 7 + ], + [ + "AaBc", + "bAaaBaCCB", + 11 + ], + [ + "AaBcA", + "aCbCbB", + 9 + ], + [ + "AaBcAbBC", + "CBCACaBBA", + 12 + ], + [ + "AaBccbA", + "BBcaaccb", + 10 + ], + [ + "AaC", + "AACcabcAa", + 13 + ], + [ + "AaC", + "ABAaabA", + 10 + ], + [ + "AaC", + "Acc", + 3 + ], + [ + "AaC", + "aCCB", + 5 + ], + [ + "AaC", + "bbbbbbAA", + 15 + ], + [ + "AaCA", + "CCc", + 6 + ], + [ + "AaCAAAcac", + "ABB", + 16 + ], + [ + "AaCAAbaa", + "CCbABc", + 11 + ], + [ + "AaCACCa", + "BCcBACc", + 10 + ], + [ + "AaCACCbcc", + "CAacc", + 10 + ], + [ + "AaCAcAcB", + "bbA", + 14 + ], + [ + "AaCB", + "BbBC", + 8 + ], + [ + "AaCB", + "bCCbBAA", + 10 + ], + [ + "AaCBa", + "ACAbC", + 7 + ], + [ + "AaCBaC", + "Ba", + 8 + ], + [ + "AaCBaaAAC", + "CaBc", + 13 + ], + [ + "AaCBbBBa", + "CBbAcCBb", + 12 + ], + [ + "AaCCAAC", + "ACaBb", + 9 + ], + [ + "AaCCAbcBB", + "BCaab", + 13 + ], + [ + "AaCCCB", + "AaaAA", + 8 + ], + [ + "AaCCCBB", + "bB", + 11 + ], + [ + "AaCCcbB", + "BCcaCcB", + 9 + ], + [ + "AaCCcc", + "cbc", + 9 + ], + [ + "AaCaAAa", + "ccccBbcaC", + 15 + ], + [ + "AaCaCccb", + "aCB", + 11 + ], + [ + "AaCaaaA", + "a", + 12 + ], + [ + "AaCaac", + "CBabAA", + 10 + ], + [ + "AaCac", + "AbaBC", + 7 + ], + [ + "AaCbbbBaB", + "ccC", + 16 + ], + [ + "AaCbcCb", + "ccAAaB", + 12 + ], + [ + "AaCc", + "a", + 6 + ], + [ + "AaCcABAa", + "B", + 14 + ], + [ + "AaCcAbC", + "Bb", + 12 + ], + [ + "AaCcBBcc", + "cBb", + 11 + ], + [ + "AaCcaCBaA", + "ABca", + 12 + ], + [ + "AaCcab", + "bBcB", + 9 + ], + [ + "AaCcacA", + "BBBAcbC", + 13 + ], + [ + "AaCcbBbCa", + "BBBAaabCb", + 14 + ], + [ + "AaCcc", + "abcBAb", + 10 + ], + [ + "AaCccBBCb", + "cB", + 14 + ], + [ + "AaCccbC", + "baaaaAb", + 11 + ], + [ + "Aaa", + "BaAA", + 5 + ], + [ + "Aaa", + "CbabcBBB", + 14 + ], + [ + "Aaa", + "aAa", + 2 + ], + [ + "Aaa", + "aBB", + 5 + ], + [ + "Aaa", + "bAAbBaBcc", + 13 + ], + [ + "Aaa", + "ca", + 4 + ], + [ + "AaaA", + "aBabbB", + 9 + ], + [ + "AaaA", + "baCb", + 6 + ], + [ + "AaaAAC", + "a", + 10 + ], + [ + "AaaAAaC", + "b", + 14 + ], + [ + "AaaACAcbA", + "CBAcAC", + 12 + ], + [ + "AaaACa", + "BABcccC", + 11 + ], + [ + "AaaAbBAab", + "bacccCc", + 16 + ], + [ + "AaaAcBCB", + "Bbbb", + 14 + ], + [ + "AaaAcb", + "AcabAA", + 8 + ], + [ + "AaaB", + "aaCAC", + 6 + ], + [ + "AaaBABa", + "bBaC", + 11 + ], + [ + "AaaBCaCC", + "c", + 15 + ], + [ + "AaaBa", + "CBCBc", + 8 + ], + [ + "AaaC", + "aaAc", + 3 + ], + [ + "AaaC", + "b", + 8 + ], + [ + "AaaC", + "caaBCAABc", + 12 + ], + [ + "AaaCB", + "ABcbBCCA", + 12 + ], + [ + "AaaCcbcc", + "B", + 15 + ], + [ + "Aaaa", + "abCBCBa", + 11 + ], + [ + "AaaaA", + "CccAB", + 9 + ], + [ + "AaaaBcb", + "BCabBBBC", + 11 + ], + [ + "AaaaCCC", + "BBcaacaCB", + 11 + ], + [ + "AaaacbC", + "CcAcBACcb", + 13 + ], + [ + "AaabA", + "cB", + 9 + ], + [ + "AaabACBca", + "bAc", + 12 + ], + [ + "AaabCAC", + "bCaacCbB", + 10 + ], + [ + "Aaaba", + "BbccCaa", + 12 + ], + [ + "AaabaBba", + "aBaB", + 9 + ], + [ + "Aaababb", + "BcbBCcc", + 13 + ], + [ + "AaabcBb", + "abab", + 8 + ], + [ + "Aaac", + "BAbaab", + 6 + ], + [ + "AaacACBc", + "ACbbCaABA", + 12 + ], + [ + "AaaccCBcb", + "CBabaCACb", + 11 + ], + [ + "AaacccCc", + "c", + 14 + ], + [ + "Aab", + "BcBaaCBC", + 12 + ], + [ + "Aab", + "CBBaACcBA", + 15 + ], + [ + "Aab", + "a", + 4 + ], + [ + "AabA", + "acCbacA", + 9 + ], + [ + "AabAAcBBB", + "aa", + 15 + ], + [ + "AabAbBbcB", + "AcCcA", + 14 + ], + [ + "AabAccB", + "AcabBCb", + 8 + ], + [ + "AabBABc", + "AAabbCcCc", + 9 + ], + [ + "AabBCAaC", + "aBcCbc", + 10 + ], + [ + "AabBCC", + "A", + 10 + ], + [ + "AabBaBcBB", + "cbcc", + 14 + ], + [ + "AabBacAaa", + "aAc", + 13 + ], + [ + "AabBba", + "bcabb", + 8 + ], + [ + "AabBbc", + "CcAaA", + 12 + ], + [ + "AabBcBca", + "Ca", + 13 + ], + [ + "AabBcbc", + "ac", + 10 + ], + [ + "AabCbA", + "B", + 11 + ], + [ + "AabCbC", + "ccBbc", + 8 + ], + [ + "Aaba", + "BBaa", + 6 + ], + [ + "AabaACCC", + "aaBBcccCa", + 10 + ], + [ + "AabaCCccc", + "BaB", + 15 + ], + [ + "AabbA", + "AB", + 7 + ], + [ + "AabbABCBb", + "aBB", + 12 + ], + [ + "AabbB", + "AA", + 7 + ], + [ + "AabbB", + "bCCC", + 10 + ], + [ + "AabbbbbC", + "bBbBaB", + 10 + ], + [ + "AabbcCACc", + "AAa", + 14 + ], + [ + "Aabc", + "bBCbc", + 6 + ], + [ + "Aabc", + "bbA", + 6 + ], + [ + "AabcAAbCc", + "Aa", + 14 + ], + [ + "AabcBBb", + "bAbAcCBB", + 8 + ], + [ + "AabcBb", + "bb", + 8 + ], + [ + "AabcC", + "BbbBcbbC", + 10 + ], + [ + "AabcbCa", + "CB", + 12 + ], + [ + "Aac", + "Bccc", + 6 + ], + [ + "Aac", + "CBABCbaA", + 12 + ], + [ + "Aac", + "Cb", + 6 + ], + [ + "Aac", + "a", + 4 + ], + [ + "Aac", + "aBbCCaaa", + 13 + ], + [ + "Aac", + "baBAcA", + 8 + ], + [ + "Aac", + "c", + 4 + ], + [ + "Aac", + "caAABac", + 8 + ], + [ + "AacA", + "ACcBA", + 4 + ], + [ + "AacA", + "BaaAccBcA", + 11 + ], + [ + "AacAABbaa", + "a", + 16 + ], + [ + "AacABAB", + "CCc", + 12 + ], + [ + "AacAcbbC", + "cbCa", + 12 + ], + [ + "AacBBCc", + "BcBAA", + 10 + ], + [ + "AacBaA", + "AAa", + 7 + ], + [ + "AacBbaCBB", + "CcbC", + 12 + ], + [ + "AacBbcccc", + "CbACCaBab", + 18 + ], + [ + "AacC", + "BcBcAbb", + 12 + ], + [ + "AacCaBcaB", + "CAAb", + 13 + ], + [ + "AacCacC", + "CaABC", + 9 + ], + [ + "AacCcCcAC", + "CCBBabBA", + 16 + ], + [ + "AacCcc", + "Aab", + 8 + ], + [ + "AacaaCc", + "bAaA", + 10 + ], + [ + "AacacB", + "aaBBB", + 7 + ], + [ + "AacacBC", + "CBBcAcba", + 10 + ], + [ + "AacacBb", + "AAabbcaC", + 10 + ], + [ + "Aacb", + "c", + 6 + ], + [ + "AacbAAA", + "BbcBaCCCb", + 14 + ], + [ + "Aacbaa", + "aCBbbCaAc", + 12 + ], + [ + "Aacc", + "ACAA", + 6 + ], + [ + "Aacc", + "CBAcB", + 7 + ], + [ + "AaccAB", + "baAcAcbcc", + 11 + ], + [ + "AaccABc", + "bB", + 12 + ], + [ + "AaccAaAb", + "acb", + 10 + ], + [ + "AaccB", + "ac", + 6 + ], + [ + "AaccBcc", + "BC", + 11 + ], + [ + "AaccC", + "bAbacBAa", + 10 + ], + [ + "AaccaAc", + "Bc", + 12 + ], + [ + "AaccaBa", + "B", + 12 + ], + [ + "AaccaCcBb", + "bABCca", + 13 + ], + [ + "Aaccac", + "c", + 10 + ], + [ + "AacccAa", + "AC", + 11 + ], + [ + "AacccAcC", + "ab", + 14 + ], + [ + "AacccBBcC", + "Bb", + 15 + ], + [ + "Ab", + "A", + 2 + ], + [ + "Ab", + "AA", + 2 + ], + [ + "Ab", + "AaabCAAA", + 12 + ], + [ + "Ab", + "AbACAaCAA", + 14 + ], + [ + "Ab", + "AbCCAAACc", + 14 + ], + [ + "Ab", + "AbCbAa", + 8 + ], + [ + "Ab", + "Ac", + 2 + ], + [ + "Ab", + "AcaABbc", + 10 + ], + [ + "Ab", + "BAcCa", + 8 + ], + [ + "Ab", + "BBc", + 5 + ], + [ + "Ab", + "BbcAcCCaa", + 16 + ], + [ + "Ab", + "CABa", + 5 + ], + [ + "Ab", + "CAaB", + 5 + ], + [ + "Ab", + "CCBcAbbc", + 12 + ], + [ + "Ab", + "CaBc", + 6 + ], + [ + "Ab", + "CbBCAbaBc", + 14 + ], + [ + "Ab", + "aCAccA", + 10 + ], + [ + "Ab", + "aab", + 3 + ], + [ + "Ab", + "acaCBAa", + 12 + ], + [ + "Ab", + "bA", + 4 + ], + [ + "Ab", + "bCBc", + 7 + ], + [ + "Ab", + "c", + 4 + ], + [ + "Ab", + "cBBB", + 7 + ], + [ + "Ab", + "cC", + 4 + ], + [ + "Ab", + "cCAaBbbBC", + 14 + ], + [ + "Ab", + "cCaAcc", + 10 + ], + [ + "Ab", + "caA", + 5 + ], + [ + "Ab", + "caCacCcBC", + 16 + ], + [ + "Ab", + "cbcacAC", + 12 + ], + [ + "Ab", + "ccAbB", + 6 + ], + [ + "Ab", + "ccb", + 4 + ], + [ + "AbA", + "BCBAaC", + 9 + ], + [ + "AbA", + "BbBAaA", + 8 + ], + [ + "AbA", + "CcBa", + 6 + ], + [ + "AbA", + "aAC", + 5 + ], + [ + "AbA", + "b", + 4 + ], + [ + "AbA", + "caABB", + 7 + ], + [ + "AbA", + "ccc", + 6 + ], + [ + "AbAA", + "CBba", + 6 + ], + [ + "AbAAC", + "BAAC", + 3 + ], + [ + "AbAAC", + "bbab", + 7 + ], + [ + "AbAACaAb", + "C", + 14 + ], + [ + "AbAACbbBB", + "BCbaa", + 13 + ], + [ + "AbAAbC", + "caCaAAAbA", + 11 + ], + [ + "AbAAcbBa", + "BabBBCa", + 11 + ], + [ + "AbAB", + "Ab", + 4 + ], + [ + "AbAB", + "Bcb", + 6 + ], + [ + "AbAB", + "ac", + 7 + ], + [ + "AbABCa", + "cC", + 10 + ], + [ + "AbABa", + "BAaaCB", + 9 + ], + [ + "AbACABc", + "aBCACA", + 8 + ], + [ + "AbACBCA", + "aBB", + 10 + ], + [ + "AbACaa", + "AcAba", + 6 + ], + [ + "AbACbA", + "A", + 10 + ], + [ + "AbACbAA", + "aACA", + 7 + ], + [ + "AbACccccb", + "cCbbCBA", + 15 + ], + [ + "AbAaABB", + "BcaaA", + 9 + ], + [ + "AbAaAb", + "CB", + 11 + ], + [ + "AbAaC", + "AacbbACba", + 11 + ], + [ + "AbAaCAab", + "CabaacCaA", + 9 + ], + [ + "AbAaaa", + "AaC", + 8 + ], + [ + "AbAacaaac", + "abACABAc", + 8 + ], + [ + "AbAb", + "bbccBB", + 9 + ], + [ + "AbAbCba", + "BCbA", + 8 + ], + [ + "AbAbabac", + "bc", + 12 + ], + [ + "AbAbbBaab", + "bbabb", + 10 + ], + [ + "AbAc", + "BcbBCbC", + 11 + ], + [ + "AbAcACAa", + "cBcBBA", + 11 + ], + [ + "AbAcB", + "c", + 8 + ], + [ + "AbAcbCbca", + "AcBaaB", + 13 + ], + [ + "AbAccAbBc", + "aACcAACbA", + 11 + ], + [ + "AbAccBCa", + "BACCAaA", + 10 + ], + [ + "AbB", + "AABC", + 4 + ], + [ + "AbB", + "AbCbCCc", + 9 + ], + [ + "AbB", + "BcaAbAAAb", + 13 + ], + [ + "AbB", + "bBBA", + 5 + ], + [ + "AbB", + "cAaaaB", + 8 + ], + [ + "AbB", + "cbbABB", + 7 + ], + [ + "AbBAcCAC", + "CAAbcCC", + 9 + ], + [ + "AbBB", + "AAAAC", + 8 + ], + [ + "AbBBabc", + "Bcb", + 10 + ], + [ + "AbBBbCaA", + "AaCccAAA", + 11 + ], + [ + "AbBBcaAaa", + "bcbBbcCb", + 13 + ], + [ + "AbBCAacC", + "c", + 14 + ], + [ + "AbBCAcaa", + "aa", + 12 + ], + [ + "AbBCBbA", + "abbcBb", + 5 + ], + [ + "AbBaABC", + "AabAAbc", + 6 + ], + [ + "AbBaBB", + "AAaAca", + 9 + ], + [ + "AbBaCC", + "CAb", + 10 + ], + [ + "AbBaaAb", + "ACcBcca", + 11 + ], + [ + "AbBabBa", + "bccBAbb", + 10 + ], + [ + "AbBb", + "A", + 6 + ], + [ + "AbBb", + "BbaAB", + 7 + ], + [ + "AbBbA", + "babCBABbb", + 11 + ], + [ + "AbBbAABc", + "AbAa", + 9 + ], + [ + "AbBbCb", + "bcAACCcac", + 15 + ], + [ + "AbBbb", + "aCAC", + 9 + ], + [ + "AbBbbacbC", + "CcbccAb", + 14 + ], + [ + "AbBbcb", + "Ab", + 8 + ], + [ + "AbBbcbabC", + "Ba", + 14 + ], + [ + "AbBcBaaCc", + "BbcAB", + 13 + ], + [ + "AbBcaaCCA", + "aba", + 13 + ], + [ + "AbBcb", + "BAaAcb", + 6 + ], + [ + "AbBcc", + "AaccBc", + 6 + ], + [ + "AbBcccB", + "cB", + 10 + ], + [ + "AbC", + "BccBcC", + 9 + ], + [ + "AbC", + "C", + 4 + ], + [ + "AbCAA", + "CCcCcacBa", + 14 + ], + [ + "AbCABa", + "ACBC", + 6 + ], + [ + "AbCACa", + "BcaBacAA", + 12 + ], + [ + "AbCACcBbB", + "C", + 16 + ], + [ + "AbCAc", + "BbCbcBCAb", + 12 + ], + [ + "AbCB", + "Caa", + 8 + ], + [ + "AbCB", + "acbAc", + 7 + ], + [ + "AbCB", + "caaaba", + 10 + ], + [ + "AbCBbB", + "BacABACaB", + 13 + ], + [ + "AbCBbbC", + "cACb", + 10 + ], + [ + "AbCCcAbc", + "abB", + 12 + ], + [ + "AbCCca", + "C", + 10 + ], + [ + "AbCCcacba", + "aaAaabcb", + 13 + ], + [ + "AbCa", + "CcAaCAaA", + 10 + ], + [ + "AbCa", + "aBcccBBa", + 11 + ], + [ + "AbCaA", + "bBbAB", + 8 + ], + [ + "AbCaAAbab", + "BCbCcb", + 13 + ], + [ + "AbCaC", + "AbABC", + 4 + ], + [ + "AbCaabBCB", + "Cb", + 14 + ], + [ + "AbCabbc", + "BcCaB", + 9 + ], + [ + "AbCacAacb", + "aaAAAAC", + 12 + ], + [ + "AbCb", + "A", + 6 + ], + [ + "AbCb", + "BbaBcaCb", + 10 + ], + [ + "AbCbC", + "Cc", + 7 + ], + [ + "AbCbaB", + "aCac", + 7 + ], + [ + "AbCc", + "bAaCac", + 6 + ], + [ + "AbCcBBACC", + "cACC", + 10 + ], + [ + "AbCcbaBCA", + "cccCAbbCA", + 11 + ], + [ + "Aba", + "ACA", + 3 + ], + [ + "Aba", + "C", + 6 + ], + [ + "Aba", + "aB", + 4 + ], + [ + "Aba", + "bccccA", + 11 + ], + [ + "Aba", + "cabbcC", + 9 + ], + [ + "AbaAACBCb", + "CACabC", + 13 + ], + [ + "AbaAabca", + "CBCBCA", + 12 + ], + [ + "AbaAb", + "cb", + 8 + ], + [ + "AbaB", + "aCaB", + 3 + ], + [ + "AbaBAaCbC", + "cbbacCbA", + 10 + ], + [ + "AbaBAbaB", + "cbaC", + 12 + ], + [ + "AbaBBcbAA", + "bCaaC", + 14 + ], + [ + "AbaBCa", + "B", + 10 + ], + [ + "AbaBCbaB", + "acBcb", + 10 + ], + [ + "AbaBaca", + "acAAABBB", + 13 + ], + [ + "AbaBcCba", + "accBCABAB", + 12 + ], + [ + "AbaC", + "BAAb", + 7 + ], + [ + "AbaC", + "aA", + 6 + ], + [ + "AbaCb", + "B", + 9 + ], + [ + "AbaCbBc", + "ACBAaCAcA", + 11 + ], + [ + "Abaa", + "aAcBb", + 8 + ], + [ + "Abaa", + "cbbAaabCc", + 12 + ], + [ + "AbaaAbab", + "CCaaBc", + 11 + ], + [ + "AbaaAc", + "bAba", + 8 + ], + [ + "AbaaabCBb", + "aa", + 14 + ], + [ + "Abab", + "bbbBb", + 6 + ], + [ + "Abab", + "cab", + 4 + ], + [ + "AbabBBb", + "CaAAc", + 12 + ], + [ + "AbabCC", + "CACcAabA", + 12 + ], + [ + "AbabcCbb", + "AbcCcbB", + 7 + ], + [ + "AbacAa", + "bCAAabcCa", + 10 + ], + [ + "AbacB", + "CBaCBAcBa", + 11 + ], + [ + "AbacBCc", + "B", + 12 + ], + [ + "AbacBbBCC", + "bC", + 14 + ], + [ + "AbacaaCB", + "B", + 14 + ], + [ + "AbacaaaBC", + "AacAa", + 9 + ], + [ + "Abacb", + "AcccbAb", + 8 + ], + [ + "Abb", + "Aba", + 2 + ], + [ + "Abb", + "Ac", + 4 + ], + [ + "Abb", + "BCcba", + 8 + ], + [ + "AbbA", + "BCb", + 6 + ], + [ + "AbbABab", + "baacCBb", + 11 + ], + [ + "AbbACbab", + "ccAcA", + 12 + ], + [ + "AbbAcA", + "CabbA", + 7 + ], + [ + "AbbB", + "b", + 6 + ], + [ + "AbbBbCbAC", + "bCCaBA", + 13 + ], + [ + "AbbBbb", + "BBcAbaAbB", + 11 + ], + [ + "AbbBcaaBa", + "aAAAaCAba", + 13 + ], + [ + "AbbCACaB", + "CbBBAbcB", + 9 + ], + [ + "AbbCB", + "AAC", + 6 + ], + [ + "AbbCBbCA", + "aAa", + 14 + ], + [ + "AbbCCA", + "bBBcbaAcC", + 13 + ], + [ + "AbbCCaBCB", + "AabbCB", + 9 + ], + [ + "AbbCCcAAb", + "ABCbbBCC", + 13 + ], + [ + "AbbCaCCa", + "B", + 15 + ], + [ + "AbbCbBC", + "Cc", + 11 + ], + [ + "AbbCcA", + "BaaCcBac", + 11 + ], + [ + "AbbaBacBC", + "BaCaCBcC", + 10 + ], + [ + "Abbaa", + "aCbBbc", + 8 + ], + [ + "Abbb", + "bBab", + 5 + ], + [ + "Abbbba", + "BbbaA", + 6 + ], + [ + "AbbcBbA", + "cCbCcaC", + 11 + ], + [ + "Abc", + "CBAaABCCc", + 13 + ], + [ + "Abc", + "Cab", + 5 + ], + [ + "Abc", + "ba", + 4 + ], + [ + "Abc", + "cCBCa", + 8 + ], + [ + "AbcAAAa", + "CBcBBAabc", + 11 + ], + [ + "AbcABaa", + "cCcBCb", + 10 + ], + [ + "AbcAcA", + "B", + 11 + ], + [ + "AbcB", + "AbaCBBaA", + 9 + ], + [ + "AbcBAA", + "BAAb", + 8 + ], + [ + "AbcBBaBB", + "CCBACAA", + 13 + ], + [ + "AbcBCcAA", + "abbABCcCa", + 8 + ], + [ + "AbcCACAbB", + "aBcCb", + 10 + ], + [ + "AbcCAacab", + "a", + 16 + ], + [ + "AbcCa", + "cBAaab", + 9 + ], + [ + "AbcCbbc", + "ACccaBbAB", + 10 + ], + [ + "AbcaA", + "cac", + 6 + ], + [ + "AbcaBABac", + "aA", + 14 + ], + [ + "AbcaBbBcA", + "CBbA", + 11 + ], + [ + "AbcaCAAAB", + "ac", + 15 + ], + [ + "AbcacabA", + "ABbaAC", + 10 + ], + [ + "Abcb", + "AbCCcbb", + 6 + ], + [ + "AbcbA", + "b", + 8 + ], + [ + "AbcbABc", + "bCBCabbBb", + 12 + ], + [ + "AbcbB", + "A", + 8 + ], + [ + "AbcbbC", + "bABbBAa", + 10 + ], + [ + "Abcc", + "bAABaA", + 9 + ], + [ + "Abcc", + "cAABAAa", + 11 + ], + [ + "AbccB", + "cA", + 8 + ], + [ + "Abccc", + "bba", + 8 + ], + [ + "Ac", + "ACbAa", + 7 + ], + [ + "Ac", + "AbB", + 4 + ], + [ + "Ac", + "BCB", + 5 + ], + [ + "Ac", + "BCacCBaBb", + 15 + ], + [ + "Ac", + "BaaCB", + 8 + ], + [ + "Ac", + "BacBbABCa", + 15 + ], + [ + "Ac", + "Bacc", + 5 + ], + [ + "Ac", + "BbaBaACcC", + 14 + ], + [ + "Ac", + "BccaAa", + 10 + ], + [ + "Ac", + "CA", + 4 + ], + [ + "Ac", + "CABacA", + 8 + ], + [ + "Ac", + "CABbbCA", + 11 + ], + [ + "Ac", + "CAa", + 4 + ], + [ + "Ac", + "CBCaAcBaB", + 14 + ], + [ + "Ac", + "CCA", + 5 + ], + [ + "Ac", + "CCcbBBc", + 12 + ], + [ + "Ac", + "CbBBBBbBA", + 18 + ], + [ + "Ac", + "Cbba", + 8 + ], + [ + "Ac", + "aCaa", + 6 + ], + [ + "Ac", + "aCbca", + 7 + ], + [ + "Ac", + "aaBC", + 6 + ], + [ + "Ac", + "aaCAa", + 8 + ], + [ + "Ac", + "aaaA", + 7 + ], + [ + "Ac", + "aabca", + 7 + ], + [ + "Ac", + "abcAAaB", + 11 + ], + [ + "Ac", + "acaBBbaAC", + 15 + ], + [ + "Ac", + "bAaa", + 6 + ], + [ + "Ac", + "bCCCCC", + 11 + ], + [ + "Ac", + "baAcB", + 6 + ], + [ + "Ac", + "bc", + 2 + ], + [ + "Ac", + "cB", + 4 + ], + [ + "Ac", + "cBaaaAAAb", + 16 + ], + [ + "Ac", + "cC", + 3 + ], + [ + "Ac", + "cCAcbCA", + 10 + ], + [ + "Ac", + "cCcCAAA", + 12 + ], + [ + "Ac", + "cbAAcc", + 8 + ], + [ + "AcA", + "ABAACCbc", + 12 + ], + [ + "AcA", + "BBBb", + 8 + ], + [ + "AcA", + "CcabCab", + 11 + ], + [ + "AcA", + "acA", + 1 + ], + [ + "AcA", + "bAbCc", + 7 + ], + [ + "AcA", + "bccBB", + 8 + ], + [ + "AcA", + "cCCA", + 5 + ], + [ + "AcAAAb", + "Acba", + 7 + ], + [ + "AcAABAc", + "bCaa", + 11 + ], + [ + "AcAABBc", + "c", + 12 + ], + [ + "AcAACA", + "cCb", + 8 + ], + [ + "AcAACBb", + "AbaA", + 9 + ], + [ + "AcAAbb", + "abBbA", + 9 + ], + [ + "AcAAcBaB", + "caB", + 10 + ], + [ + "AcABCCba", + "Ca", + 12 + ], + [ + "AcABa", + "aAbBBCA", + 9 + ], + [ + "AcAC", + "c", + 6 + ], + [ + "AcACB", + "aBBCbBabC", + 13 + ], + [ + "AcACCBA", + "c", + 12 + ], + [ + "AcACCaACA", + "aCAAAcABC", + 12 + ], + [ + "AcACaA", + "Ac", + 8 + ], + [ + "AcACcC", + "bABBAcC", + 8 + ], + [ + "AcACccA", + "CaacccB", + 8 + ], + [ + "AcAa", + "b", + 8 + ], + [ + "AcAaaCBA", + "abB", + 12 + ], + [ + "AcAac", + "BBCCC", + 9 + ], + [ + "AcAb", + "cCBBcBCAc", + 14 + ], + [ + "AcAbA", + "cCbbBcb", + 11 + ], + [ + "AcAbBabb", + "BBCBa", + 12 + ], + [ + "AcAbBcABC", + "baCcCcb", + 15 + ], + [ + "AcAbCB", + "ccCCaC", + 10 + ], + [ + "AcAba", + "CcBcBcC", + 11 + ], + [ + "AcAbb", + "bAAbc", + 6 + ], + [ + "AcAcB", + "aAcBAbC", + 8 + ], + [ + "AcAcCB", + "bc", + 10 + ], + [ + "AcAcCCCbb", + "BAaBC", + 14 + ], + [ + "AcAcbacA", + "cb", + 12 + ], + [ + "AcB", + "AbCbCa", + 8 + ], + [ + "AcB", + "CCABca", + 8 + ], + [ + "AcB", + "aBA", + 5 + ], + [ + "AcB", + "b", + 5 + ], + [ + "AcB", + "bCB", + 3 + ], + [ + "AcB", + "cBBaB", + 8 + ], + [ + "AcB", + "caBcC", + 7 + ], + [ + "AcB", + "caCCCaA", + 12 + ], + [ + "AcBAAa", + "ac", + 9 + ], + [ + "AcBAbbAA", + "ACCB", + 12 + ], + [ + "AcBB", + "AAAccBcA", + 10 + ], + [ + "AcBB", + "BaccCCACc", + 15 + ], + [ + "AcBB", + "bBCA", + 8 + ], + [ + "AcBB", + "cCAaaBaBb", + 12 + ], + [ + "AcBBAC", + "AaCA", + 8 + ], + [ + "AcBBACA", + "aAA", + 9 + ], + [ + "AcBBACCaC", + "abBcCAC", + 8 + ], + [ + "AcBBaB", + "aC", + 10 + ], + [ + "AcBBbbaBc", + "CC", + 16 + ], + [ + "AcBBbcb", + "ABBAAc", + 8 + ], + [ + "AcBBcabc", + "a", + 14 + ], + [ + "AcBC", + "ACabcCbBB", + 12 + ], + [ + "AcBC", + "AacAcBCcA", + 10 + ], + [ + "AcBC", + "bCbcAbbA", + 13 + ], + [ + "AcBCAcb", + "AB", + 10 + ], + [ + "AcBCCaAbc", + "ca", + 14 + ], + [ + "AcBCCb", + "aCcCAccc", + 11 + ], + [ + "AcBCaccAA", + "cBAcc", + 9 + ], + [ + "AcBCb", + "aBbaBac", + 11 + ], + [ + "AcBCcbCb", + "AAABCa", + 11 + ], + [ + "AcBCcbbaB", + "bBbC", + 14 + ], + [ + "AcBa", + "BBbCBAA", + 10 + ], + [ + "AcBaCCbCa", + "bbBBb", + 14 + ], + [ + "AcBaCCc", + "aaCcca", + 8 + ], + [ + "AcBaacCb", + "BBbbCbcC", + 13 + ], + [ + "AcBbAb", + "BabcbBbc", + 11 + ], + [ + "AcBbAc", + "cbAbC", + 7 + ], + [ + "AcBbaac", + "bA", + 11 + ], + [ + "AcBbbbB", + "ACaa", + 11 + ], + [ + "AcBc", + "abAcbb", + 7 + ], + [ + "AcBcAcA", + "aaCBAaAAa", + 11 + ], + [ + "AcBcBBB", + "a", + 13 + ], + [ + "AcBcCba", + "BCb", + 8 + ], + [ + "AcBca", + "Ab", + 7 + ], + [ + "AcBcbbA", + "ABb", + 8 + ], + [ + "AcBcbbA", + "BB", + 11 + ], + [ + "AcBcbcA", + "B", + 12 + ], + [ + "AcBccbbbC", + "CbBAAbB", + 13 + ], + [ + "AcBcccC", + "b", + 13 + ], + [ + "AcC", + "ACAbc", + 6 + ], + [ + "AcC", + "bBBA", + 8 + ], + [ + "AcC", + "bbCBACbcA", + 14 + ], + [ + "AcC", + "c", + 4 + ], + [ + "AcC", + "cBBa", + 8 + ], + [ + "AcCACC", + "bbAcCCcc", + 8 + ], + [ + "AcCACaCA", + "CBcCCB", + 11 + ], + [ + "AcCAb", + "bCBAACBC", + 12 + ], + [ + "AcCAbA", + "Bac", + 11 + ], + [ + "AcCAbcaBb", + "Cb", + 14 + ], + [ + "AcCAcCCCA", + "AcCC", + 10 + ], + [ + "AcCBA", + "a", + 9 + ], + [ + "AcCBBaBb", + "AC", + 12 + ], + [ + "AcCBba", + "CaaBCAa", + 10 + ], + [ + "AcCCAA", + "BcA", + 8 + ], + [ + "AcCCBcc", + "bbBACCa", + 13 + ], + [ + "AcCCCbb", + "AcaAc", + 9 + ], + [ + "AcCCb", + "bb", + 8 + ], + [ + "AcCCba", + "BC", + 10 + ], + [ + "AcCCcBACB", + "cb", + 15 + ], + [ + "AcCCccCB", + "baaCaAaB", + 12 + ], + [ + "AcCa", + "AAaCcC", + 8 + ], + [ + "AcCaAcccA", + "AaaaBaCBB", + 13 + ], + [ + "AcCaBCACC", + "a", + 16 + ], + [ + "AcCaC", + "CAcC", + 6 + ], + [ + "AcCaa", + "Ccab", + 6 + ], + [ + "AcCabb", + "BbCBa", + 9 + ], + [ + "AcCb", + "A", + 6 + ], + [ + "AcCbA", + "BaCcaC", + 9 + ], + [ + "AcCbA", + "aA", + 7 + ], + [ + "AcCbBb", + "CCAcb", + 7 + ], + [ + "AcCba", + "cBBbCbCc", + 12 + ], + [ + "AcCbac", + "cCBa", + 5 + ], + [ + "AcCbbbc", + "BbBbABCBc", + 12 + ], + [ + "AcCbc", + "ABBaaAc", + 10 + ], + [ + "AcCc", + "BBBAA", + 10 + ], + [ + "AcCc", + "CABBBCaCb", + 13 + ], + [ + "AcCc", + "acCbACCcc", + 11 + ], + [ + "AcCcA", + "AbCbabBc", + 11 + ], + [ + "AcCcABb", + "cCBaAc", + 9 + ], + [ + "AcCcBB", + "AAaCAaA", + 10 + ], + [ + "AcCcabBCB", + "BcA", + 15 + ], + [ + "AcCcb", + "bC", + 8 + ], + [ + "AcCccABB", + "bbCbbCB", + 12 + ], + [ + "AcCccbBBb", + "ACABAa", + 12 + ], + [ + "Aca", + "BaAA", + 6 + ], + [ + "Aca", + "CCBCCB", + 11 + ], + [ + "Aca", + "cA", + 3 + ], + [ + "AcaAAA", + "CcbbaB", + 9 + ], + [ + "AcaABAaCb", + "CcCACC", + 12 + ], + [ + "AcaABCa", + "bacAcA", + 9 + ], + [ + "AcaAcBC", + "ccbAaa", + 10 + ], + [ + "AcaBACCb", + "ACacBaBAb", + 8 + ], + [ + "AcaBAabCb", + "ACACAbaCB", + 9 + ], + [ + "AcaBC", + "ABacC", + 4 + ], + [ + "AcaBCa", + "AbBC", + 6 + ], + [ + "AcaBacc", + "CBB", + 11 + ], + [ + "AcaBcba", + "AbAbCBcC", + 10 + ], + [ + "AcaC", + "Aca", + 2 + ], + [ + "AcaCB", + "C", + 8 + ], + [ + "AcaCBB", + "cbacccC", + 11 + ], + [ + "AcaCBb", + "CCcaBC", + 8 + ], + [ + "AcaCa", + "bCA", + 7 + ], + [ + "AcaCaC", + "BB", + 12 + ], + [ + "AcaCbcC", + "aBBCccaC", + 9 + ], + [ + "AcaCc", + "CaaCBBb", + 10 + ], + [ + "AcaCc", + "aBaCb", + 5 + ], + [ + "AcaCcAc", + "acA", + 8 + ], + [ + "Acaa", + "BACcCA", + 7 + ], + [ + "AcaaA", + "bacbCca", + 10 + ], + [ + "Acaaa", + "ACccca", + 6 + ], + [ + "AcaaaaaBA", + "b", + 17 + ], + [ + "AcaaabbAC", + "AACbc", + 12 + ], + [ + "AcaabBaAc", + "aCAbBBCaA", + 10 + ], + [ + "AcaacaAAB", + "CA", + 15 + ], + [ + "AcaacbaC", + "A", + 14 + ], + [ + "Acab", + "AB", + 5 + ], + [ + "AcabA", + "BCcca", + 8 + ], + [ + "AcabBAcB", + "B", + 14 + ], + [ + "AcabCaab", + "cCCaBB", + 9 + ], + [ + "AcabcC", + "AC", + 8 + ], + [ + "Acabca", + "cCCC", + 9 + ], + [ + "AcacaAbbb", + "abB", + 13 + ], + [ + "AcacaBC", + "bbaCB", + 9 + ], + [ + "AcacaCCCc", + "aBbbcACA", + 14 + ], + [ + "AcaccB", + "aaBc", + 7 + ], + [ + "Acb", + "BcbA", + 4 + ], + [ + "Acb", + "BccCaB", + 9 + ], + [ + "Acb", + "aC", + 4 + ], + [ + "Acb", + "bAccAa", + 8 + ], + [ + "Acb", + "ccAcbaB", + 8 + ], + [ + "AcbA", + "cA", + 4 + ], + [ + "AcbAAcaA", + "Cba", + 11 + ], + [ + "AcbAB", + "cbBbAa", + 8 + ], + [ + "AcbACBAaA", + "cc", + 15 + ], + [ + "AcbB", + "C", + 7 + ], + [ + "AcbBa", + "aAABbACcB", + 13 + ], + [ + "AcbBaCb", + "bCcacBbA", + 12 + ], + [ + "AcbBaba", + "CbbCBaB", + 9 + ], + [ + "AcbBb", + "abBbA", + 5 + ], + [ + "AcbC", + "bbCaA", + 8 + ], + [ + "AcbCAC", + "CcACABBb", + 10 + ], + [ + "AcbCAc", + "aaBCaAbAa", + 12 + ], + [ + "AcbCBcc", + "aA", + 13 + ], + [ + "AcbCaCb", + "ccAABC", + 10 + ], + [ + "AcbCcBCa", + "bbbcAcB", + 11 + ], + [ + "AcbCcbCa", + "CcaABBcCa", + 11 + ], + [ + "Acba", + "aACbbBcc", + 11 + ], + [ + "AcbaCAA", + "bAaBbaca", + 10 + ], + [ + "Acbab", + "acBc", + 6 + ], + [ + "AcbbAcCa", + "cCb", + 12 + ], + [ + "AcbbC", + "B", + 9 + ], + [ + "Acbbb", + "cCbCAACA", + 13 + ], + [ + "AcbbcAB", + "cca", + 9 + ], + [ + "Acbc", + "AbCaCB", + 8 + ], + [ + "Acbc", + "Bac", + 6 + ], + [ + "AcbcBB", + "ABBBB", + 5 + ], + [ + "AcbcBcA", + "AaaBaba", + 11 + ], + [ + "AcbcCbcbA", + "ac", + 15 + ], + [ + "Acc", + "BbBCABBb", + 14 + ], + [ + "Acc", + "aAbBbBca", + 12 + ], + [ + "Acc", + "aa", + 5 + ], + [ + "Acc", + "aaBcC", + 6 + ], + [ + "Acc", + "bCbAaCab", + 13 + ], + [ + "AccA", + "BCBA", + 5 + ], + [ + "AccA", + "aC", + 6 + ], + [ + "AccAB", + "Bcb", + 7 + ], + [ + "AccABcbBC", + "AAcBcAcc", + 9 + ], + [ + "AccACBbA", + "c", + 14 + ], + [ + "AccAbcC", + "cbbB", + 10 + ], + [ + "AccBbab", + "bcAaaaA", + 10 + ], + [ + "AccC", + "BBbca", + 8 + ], + [ + "AccCBBACa", + "b", + 17 + ], + [ + "AccCCCcaa", + "aabC", + 15 + ], + [ + "AccCcAA", + "CCbAaABCA", + 13 + ], + [ + "Acca", + "C", + 7 + ], + [ + "Acca", + "aB", + 7 + ], + [ + "AccaCABA", + "CBBBc", + 13 + ], + [ + "AccaaAbbC", + "BaAB", + 13 + ], + [ + "Accb", + "baBaaCaCa", + 15 + ], + [ + "Accbbbb", + "bA", + 12 + ], + [ + "Accbcc", + "aaAAcb", + 9 + ], + [ + "Accc", + "ABbAB", + 8 + ], + [ + "AcccAC", + "BAAbCacBb", + 13 + ], + [ + "AcccAbbB", + "BaBCa", + 14 + ], + [ + "AcccBAAbC", + "CCbCb", + 13 + ], + [ + "AcccCACac", + "BAbccaC", + 11 + ], + [ + "AcccCabab", + "bBAAaBaA", + 13 + ], + [ + "AcccCb", + "BACacbcC", + 9 + ], + [ + "AcccaCb", + "CBBCcaCB", + 8 + ], + [ + "Acccac", + "acCaAcaCb", + 9 + ], + [ + "B", + "A", + 2 + ], + [ + "B", + "AAABB", + 8 + ], + [ + "B", + "AAAcc", + 10 + ], + [ + "B", + "AABBcC", + 10 + ], + [ + "B", + "AABCBb", + 10 + ], + [ + "B", + "AAC", + 6 + ], + [ + "B", + "AACA", + 8 + ], + [ + "B", + "AACaaaa", + 14 + ], + [ + "B", + "AAcaBa", + 10 + ], + [ + "B", + "AAcacbab", + 15 + ], + [ + "B", + "ABBc", + 6 + ], + [ + "B", + "ABCBcBCB", + 14 + ], + [ + "B", + "ABaAba", + 10 + ], + [ + "B", + "ABaB", + 6 + ], + [ + "B", + "ABaBba", + 10 + ], + [ + "B", + "ABaaCcBa", + 14 + ], + [ + "B", + "ABc", + 4 + ], + [ + "B", + "ACB", + 4 + ], + [ + "B", + "ACCCCCBc", + 14 + ], + [ + "B", + "ACbbC", + 9 + ], + [ + "B", + "ACbbCA", + 11 + ], + [ + "B", + "AaA", + 6 + ], + [ + "B", + "AaACbbaaB", + 16 + ], + [ + "B", + "AaAaCCbaa", + 17 + ], + [ + "B", + "AaBaBcba", + 14 + ], + [ + "B", + "AabcAaa", + 13 + ], + [ + "B", + "AacCacAb", + 15 + ], + [ + "B", + "AaccbBacB", + 16 + ], + [ + "B", + "Ab", + 3 + ], + [ + "B", + "AbBc", + 6 + ], + [ + "B", + "AbaBCcaBa", + 16 + ], + [ + "B", + "Abc", + 5 + ], + [ + "B", + "AcA", + 6 + ], + [ + "B", + "AcBA", + 6 + ], + [ + "B", + "AcBaBbCc", + 14 + ], + [ + "B", + "AcCc", + 8 + ], + [ + "B", + "AcaC", + 8 + ], + [ + "B", + "AccBacc", + 12 + ], + [ + "B", + "B", + 0 + ], + [ + "B", + "BAA", + 4 + ], + [ + "B", + "BBAbCc", + 10 + ], + [ + "B", + "BBAccA", + 10 + ], + [ + "B", + "BBBCb", + 8 + ], + [ + "B", + "BBa", + 4 + ], + [ + "B", + "BBaAcacab", + 16 + ], + [ + "B", + "BBc", + 4 + ], + [ + "B", + "BC", + 2 + ], + [ + "B", + "BCC", + 4 + ], + [ + "B", + "BCCbaBA", + 12 + ], + [ + "B", + "BCbCb", + 8 + ], + [ + "B", + "BCcC", + 6 + ], + [ + "B", + "BCcCccC", + 12 + ], + [ + "B", + "BCcb", + 6 + ], + [ + "B", + "BCcc", + 6 + ], + [ + "B", + "Ba", + 2 + ], + [ + "B", + "BaBCAaCAa", + 16 + ], + [ + "B", + "Baca", + 6 + ], + [ + "B", + "BacbcaCaC", + 16 + ], + [ + "B", + "BbcCCbAbb", + 16 + ], + [ + "B", + "BcACbAAC", + 14 + ], + [ + "B", + "BcACbbbc", + 14 + ], + [ + "B", + "BcabCBbab", + 16 + ], + [ + "B", + "C", + 2 + ], + [ + "B", + "CAAb", + 7 + ], + [ + "B", + "CAAba", + 9 + ], + [ + "B", + "CACcA", + 10 + ], + [ + "B", + "CB", + 2 + ], + [ + "B", + "CBCcAcbb", + 14 + ], + [ + "B", + "CBc", + 4 + ], + [ + "B", + "CBcA", + 6 + ], + [ + "B", + "CBcCC", + 8 + ], + [ + "B", + "CCaaCaac", + 16 + ], + [ + "B", + "CCcBBCb", + 12 + ], + [ + "B", + "Ca", + 4 + ], + [ + "B", + "CaCC", + 8 + ], + [ + "B", + "Caa", + 6 + ], + [ + "B", + "Cab", + 5 + ], + [ + "B", + "Cacbc", + 9 + ], + [ + "B", + "Cb", + 3 + ], + [ + "B", + "Cbc", + 5 + ], + [ + "B", + "CbcaAaACC", + 17 + ], + [ + "B", + "CbccCbca", + 15 + ], + [ + "B", + "Cc", + 4 + ], + [ + "B", + "CcAacCAA", + 16 + ], + [ + "B", + "CcBb", + 6 + ], + [ + "B", + "CcbBb", + 8 + ], + [ + "B", + "Ccc", + 6 + ], + [ + "B", + "a", + 2 + ], + [ + "B", + "aAA", + 6 + ], + [ + "B", + "aAACAb", + 11 + ], + [ + "B", + "aABCB", + 8 + ], + [ + "B", + "aACaCCBbA", + 16 + ], + [ + "B", + "aB", + 2 + ], + [ + "B", + "aBBbc", + 8 + ], + [ + "B", + "aBabc", + 8 + ], + [ + "B", + "aBcbCA", + 10 + ], + [ + "B", + "aBccCCBC", + 14 + ], + [ + "B", + "aC", + 4 + ], + [ + "B", + "aCBBAAc", + 12 + ], + [ + "B", + "aCBcA", + 8 + ], + [ + "B", + "aCCaAabB", + 14 + ], + [ + "B", + "aCCcbbac", + 15 + ], + [ + "B", + "aCa", + 6 + ], + [ + "B", + "aCaBABACa", + 16 + ], + [ + "B", + "aCcbCA", + 11 + ], + [ + "B", + "aaBBac", + 10 + ], + [ + "B", + "aaBCC", + 8 + ], + [ + "B", + "aab", + 5 + ], + [ + "B", + "aabBcbBa", + 14 + ], + [ + "B", + "aacbAB", + 10 + ], + [ + "B", + "abA", + 5 + ], + [ + "B", + "abAAA", + 9 + ], + [ + "B", + "abBBCBBcb", + 16 + ], + [ + "B", + "abC", + 5 + ], + [ + "B", + "abCcacAC", + 15 + ], + [ + "B", + "ac", + 4 + ], + [ + "B", + "acAbAB", + 10 + ], + [ + "B", + "acBcBba", + 12 + ], + [ + "B", + "b", + 1 + ], + [ + "B", + "bA", + 3 + ], + [ + "B", + "bAAbcbb", + 13 + ], + [ + "B", + "bABAacc", + 12 + ], + [ + "B", + "bACA", + 7 + ], + [ + "B", + "bAaAbBbcb", + 16 + ], + [ + "B", + "bAc", + 5 + ], + [ + "B", + "bB", + 2 + ], + [ + "B", + "bC", + 3 + ], + [ + "B", + "bCAbccaCA", + 17 + ], + [ + "B", + "bCCA", + 7 + ], + [ + "B", + "bCabCB", + 10 + ], + [ + "B", + "bCabcCa", + 13 + ], + [ + "B", + "ba", + 3 + ], + [ + "B", + "baba", + 7 + ], + [ + "B", + "babaa", + 9 + ], + [ + "B", + "bb", + 3 + ], + [ + "B", + "bbc", + 5 + ], + [ + "B", + "bbcbCCCb", + 15 + ], + [ + "B", + "bc", + 3 + ], + [ + "B", + "bcCBCacb", + 14 + ], + [ + "B", + "bcaAcbbB", + 14 + ], + [ + "B", + "c", + 2 + ], + [ + "B", + "cABAAb", + 10 + ], + [ + "B", + "cAaaACc", + 14 + ], + [ + "B", + "cAcCA", + 10 + ], + [ + "B", + "cAccC", + 10 + ], + [ + "B", + "cB", + 2 + ], + [ + "B", + "cBBBa", + 8 + ], + [ + "B", + "cBaCCba", + 12 + ], + [ + "B", + "cBbbaAaBC", + 16 + ], + [ + "B", + "cBcCaA", + 10 + ], + [ + "B", + "cC", + 4 + ], + [ + "B", + "cCABB", + 8 + ], + [ + "B", + "cCBAacA", + 12 + ], + [ + "B", + "cCbBBAccB", + 16 + ], + [ + "B", + "cCbcaBaca", + 16 + ], + [ + "B", + "cCcBaC", + 10 + ], + [ + "B", + "cCcbcaCcc", + 17 + ], + [ + "B", + "cabBca", + 10 + ], + [ + "B", + "cabccaAAB", + 16 + ], + [ + "B", + "cb", + 3 + ], + [ + "B", + "cbAACcCCb", + 17 + ], + [ + "B", + "cbAC", + 7 + ], + [ + "B", + "cbBbCC", + 10 + ], + [ + "B", + "cbC", + 5 + ], + [ + "B", + "cbCaba", + 11 + ], + [ + "B", + "cbCbBBaa", + 14 + ], + [ + "B", + "cbaaAAA", + 13 + ], + [ + "B", + "cbaaCAb", + 13 + ], + [ + "B", + "cbbCaccA", + 15 + ], + [ + "B", + "cbbb", + 7 + ], + [ + "B", + "cbcCa", + 9 + ], + [ + "B", + "ccABaB", + 10 + ], + [ + "B", + "ccAc", + 8 + ], + [ + "B", + "ccCbCbc", + 13 + ], + [ + "B", + "ccabaC", + 11 + ], + [ + "B", + "ccb", + 5 + ], + [ + "BA", + "A", + 2 + ], + [ + "BA", + "ABaabAbAc", + 14 + ], + [ + "BA", + "B", + 2 + ], + [ + "BA", + "BBABbCAaB", + 14 + ], + [ + "BA", + "BBcaC", + 7 + ], + [ + "BA", + "BBcacAbcb", + 14 + ], + [ + "BA", + "BCbCbbcAB", + 14 + ], + [ + "BA", + "CACCAaac", + 14 + ], + [ + "BA", + "CAaaacb", + 12 + ], + [ + "BA", + "CBC", + 4 + ], + [ + "BA", + "aACbbcba", + 14 + ], + [ + "BA", + "ab", + 4 + ], + [ + "BA", + "abCAa", + 7 + ], + [ + "BA", + "b", + 3 + ], + [ + "BA", + "bABaAca", + 10 + ], + [ + "BA", + "baCCBbC", + 12 + ], + [ + "BA", + "bbBcB", + 8 + ], + [ + "BA", + "bbbCC", + 9 + ], + [ + "BA", + "bbbaBA", + 8 + ], + [ + "BA", + "bbbbCBb", + 12 + ], + [ + "BA", + "cBABaC", + 8 + ], + [ + "BA", + "cBBbbA", + 8 + ], + [ + "BA", + "cC", + 4 + ], + [ + "BA", + "ccB", + 6 + ], + [ + "BA", + "ccCa", + 7 + ], + [ + "BAA", + "A", + 4 + ], + [ + "BAA", + "abA", + 4 + ], + [ + "BAA", + "acCAc", + 8 + ], + [ + "BAA", + "bB", + 5 + ], + [ + "BAA", + "bbcACCCAB", + 13 + ], + [ + "BAA", + "bcBC", + 7 + ], + [ + "BAA", + "cBaCaBc", + 10 + ], + [ + "BAAA", + "B", + 6 + ], + [ + "BAAA", + "BBCacaC", + 10 + ], + [ + "BAAA", + "bBAc", + 5 + ], + [ + "BAAAAAA", + "CbcbAc", + 12 + ], + [ + "BAAAACb", + "cbcACabBc", + 13 + ], + [ + "BAAAC", + "cbCBcbCC", + 12 + ], + [ + "BAAAaCCAB", + "bcCB", + 12 + ], + [ + "BAAAabC", + "CCaaaAcc", + 11 + ], + [ + "BAAAbcBa", + "aCcAAAcc", + 12 + ], + [ + "BAABC", + "bccab", + 9 + ], + [ + "BAABaC", + "Cbb", + 11 + ], + [ + "BAABabAb", + "ACC", + 14 + ], + [ + "BAAC", + "bB", + 7 + ], + [ + "BAAC", + "ccaBab", + 10 + ], + [ + "BAAC", + "ccbC", + 6 + ], + [ + "BAACAA", + "a", + 11 + ], + [ + "BAACaC", + "Cac", + 7 + ], + [ + "BAAaA", + "a", + 8 + ], + [ + "BAAaACaC", + "bb", + 15 + ], + [ + "BAAaB", + "ccAcbA", + 9 + ], + [ + "BAAaa", + "b", + 9 + ], + [ + "BAAaa", + "cacCaBc", + 11 + ], + [ + "BAAaaac", + "c", + 12 + ], + [ + "BAAaabB", + "aaAAA", + 9 + ], + [ + "BAAaac", + "BCaCcCAB", + 12 + ], + [ + "BAAacA", + "ccBAcCAc", + 11 + ], + [ + "BAAb", + "AA", + 4 + ], + [ + "BAAbAC", + "BbAccCba", + 10 + ], + [ + "BAAbCbbC", + "CCCBBc", + 11 + ], + [ + "BAAbacbC", + "aBAabc", + 9 + ], + [ + "BAAbbACC", + "CCABb", + 11 + ], + [ + "BAAcCBa", + "ACcA", + 9 + ], + [ + "BAAcaBBCa", + "AccB", + 12 + ], + [ + "BAAcac", + "Bb", + 10 + ], + [ + "BAAcc", + "CACbabA", + 11 + ], + [ + "BAB", + "AcbBaaBa", + 11 + ], + [ + "BAB", + "BB", + 2 + ], + [ + "BAB", + "Caa", + 5 + ], + [ + "BAB", + "cc", + 6 + ], + [ + "BABA", + "abbccc", + 11 + ], + [ + "BABA", + "caCca", + 8 + ], + [ + "BABB", + "bBCCBcAcb", + 13 + ], + [ + "BABBAAc", + "caAaa", + 10 + ], + [ + "BABBbCA", + "bAca", + 9 + ], + [ + "BABBc", + "BCCcAcCC", + 11 + ], + [ + "BABCB", + "AB", + 6 + ], + [ + "BABCcBbA", + "CBb", + 10 + ], + [ + "BABa", + "abbbaccAa", + 14 + ], + [ + "BABabAB", + "b", + 12 + ], + [ + "BABacA", + "abcBA", + 8 + ], + [ + "BABb", + "cC", + 8 + ], + [ + "BABbC", + "BaA", + 7 + ], + [ + "BABbb", + "Ccc", + 10 + ], + [ + "BABc", + "aB", + 5 + ], + [ + "BABcACCaC", + "CbbBaCbaC", + 10 + ], + [ + "BABcBA", + "CA", + 9 + ], + [ + "BABcBCaBc", + "CBAaBCC", + 11 + ], + [ + "BABcBcBa", + "bCBACcc", + 11 + ], + [ + "BABcaBB", + "bAaabb", + 7 + ], + [ + "BABcb", + "CcCAaC", + 11 + ], + [ + "BABcbcaa", + "BbbCcAAab", + 11 + ], + [ + "BABccba", + "cCbbbA", + 10 + ], + [ + "BAC", + "BB", + 4 + ], + [ + "BAC", + "BBacAbC", + 8 + ], + [ + "BAC", + "BaacBaC", + 9 + ], + [ + "BAC", + "BbbBaB", + 9 + ], + [ + "BAC", + "CAAaa", + 8 + ], + [ + "BAC", + "CBBbBccbc", + 15 + ], + [ + "BAC", + "b", + 5 + ], + [ + "BAC", + "babAAcA", + 10 + ], + [ + "BACACcCcc", + "cbB", + 16 + ], + [ + "BACB", + "AaCABa", + 7 + ], + [ + "BACBAACba", + "BaA", + 13 + ], + [ + "BACBCAC", + "A", + 12 + ], + [ + "BACBaB", + "Bcbc", + 8 + ], + [ + "BACBbCbB", + "ACaAAaCb", + 12 + ], + [ + "BACC", + "ab", + 7 + ], + [ + "BACCBcbaa", + "ab", + 15 + ], + [ + "BACCCAaCA", + "bCCcBb", + 12 + ], + [ + "BACCbABA", + "AAAAcAB", + 10 + ], + [ + "BACCbAac", + "AcBCaacB", + 10 + ], + [ + "BACCc", + "BbAAcA", + 7 + ], + [ + "BACCc", + "aBbbBBCa", + 12 + ], + [ + "BACaC", + "CB", + 8 + ], + [ + "BACab", + "bCA", + 6 + ], + [ + "BACb", + "AcBC", + 6 + ], + [ + "BACbAbCbC", + "BbC", + 12 + ], + [ + "BACbbaaAc", + "ACabC", + 11 + ], + [ + "BACcBbcCb", + "AcbB", + 11 + ], + [ + "BACcbBbc", + "AC", + 12 + ], + [ + "BACccabb", + "bAcAAa", + 10 + ], + [ + "BACcccA", + "aacC", + 10 + ], + [ + "BAa", + "AC", + 4 + ], + [ + "BAa", + "AbCBcA", + 9 + ], + [ + "BAa", + "CabbAabb", + 11 + ], + [ + "BAa", + "cBaAa", + 4 + ], + [ + "BAaA", + "BCB", + 6 + ], + [ + "BAaABbB", + "ABabABcbC", + 9 + ], + [ + "BAaAbCc", + "acb", + 10 + ], + [ + "BAaAcCb", + "AbbC", + 10 + ], + [ + "BAaAcbB", + "Aa", + 10 + ], + [ + "BAaBBaAb", + "bBaBcaAAa", + 9 + ], + [ + "BAaBaB", + "A", + 10 + ], + [ + "BAaBbCc", + "cacBaAcaB", + 13 + ], + [ + "BAaBbabc", + "A", + 14 + ], + [ + "BAaBcCaaa", + "ab", + 15 + ], + [ + "BAaBcCb", + "aCAA", + 11 + ], + [ + "BAaCB", + "BBb", + 7 + ], + [ + "BAaCB", + "aABACA", + 7 + ], + [ + "BAaCaA", + "acB", + 9 + ], + [ + "BAaCabCCa", + "b", + 16 + ], + [ + "BAaCbAC", + "bA", + 10 + ], + [ + "BAaCbB", + "aCcbbAAA", + 13 + ], + [ + "BAaCcc", + "bc", + 9 + ], + [ + "BAaaACaA", + "Ccbcaa", + 12 + ], + [ + "BAaaCCb", + "bCBC", + 11 + ], + [ + "BAab", + "c", + 8 + ], + [ + "BAab", + "ccaba", + 6 + ], + [ + "BAabAbBB", + "ABaBaaBa", + 10 + ], + [ + "BAabCA", + "Cb", + 10 + ], + [ + "BAabCCA", + "CCbBcb", + 11 + ], + [ + "BAac", + "bBbcaca", + 8 + ], + [ + "BAacBb", + "cABAca", + 9 + ], + [ + "BAacbBbCb", + "bBAca", + 13 + ], + [ + "BAacbaBC", + "ABCbcaCAc", + 12 + ], + [ + "BAaccAba", + "BAbBbCA", + 11 + ], + [ + "BAb", + "Aa", + 4 + ], + [ + "BAb", + "AbaccbCbC", + 14 + ], + [ + "BAb", + "BaCa", + 5 + ], + [ + "BAb", + "CCaBA", + 8 + ], + [ + "BAb", + "CbCb", + 5 + ], + [ + "BAb", + "caAAC", + 8 + ], + [ + "BAb", + "ccCAAAABC", + 15 + ], + [ + "BAbABaBbC", + "CAAcAb", + 11 + ], + [ + "BAbACcA", + "aBaaCcc", + 8 + ], + [ + "BAbAa", + "ABbCbabac", + 11 + ], + [ + "BAbAa", + "c", + 10 + ], + [ + "BAbAc", + "cBc", + 7 + ], + [ + "BAbAcABa", + "ac", + 13 + ], + [ + "BAbAcbB", + "aCAcBB", + 6 + ], + [ + "BAbBA", + "BcaCBcaBa", + 11 + ], + [ + "BAbBAB", + "ABBCCb", + 8 + ], + [ + "BAbBBaB", + "abaA", + 9 + ], + [ + "BAbBCc", + "aaAcAcAAC", + 14 + ], + [ + "BAbBc", + "ABcAAABb", + 10 + ], + [ + "BAbCCbBC", + "bc", + 13 + ], + [ + "BAbCbBacB", + "acBca", + 12 + ], + [ + "BAbCc", + "BBbcBAab", + 11 + ], + [ + "BAbCcB", + "CbacAaaa", + 14 + ], + [ + "BAbCcBB", + "ccaBBcACC", + 14 + ], + [ + "BAba", + "Bc", + 6 + ], + [ + "BAbaBCAB", + "bbCbBa", + 11 + ], + [ + "BAbaBbBc", + "baAC", + 11 + ], + [ + "BAbaCc", + "C", + 10 + ], + [ + "BAbabca", + "cBabaCb", + 8 + ], + [ + "BAbacCaa", + "CCa", + 11 + ], + [ + "BAbb", + "baBaBaB", + 9 + ], + [ + "BAbbBAA", + "ACbcccc", + 12 + ], + [ + "BAbc", + "B", + 6 + ], + [ + "BAbc", + "bABCB", + 5 + ], + [ + "BAbc", + "bcBAb", + 6 + ], + [ + "BAbcAcA", + "ccA", + 8 + ], + [ + "BAbcaBab", + "CaACACbcB", + 13 + ], + [ + "BAbccAcB", + "BA", + 12 + ], + [ + "BAbccaBA", + "CCCcaa", + 10 + ], + [ + "BAc", + "AAAccAa", + 10 + ], + [ + "BAc", + "Bbc", + 2 + ], + [ + "BAc", + "ba", + 4 + ], + [ + "BAcA", + "Ac", + 4 + ], + [ + "BAcAA", + "acbCAA", + 7 + ], + [ + "BAcAACc", + "accA", + 9 + ], + [ + "BAcAAb", + "AbC", + 10 + ], + [ + "BAcAB", + "aAacCcbac", + 13 + ], + [ + "BAcACCb", + "cccBC", + 10 + ], + [ + "BAcACcBA", + "BCAbccA", + 7 + ], + [ + "BAcAcC", + "aAcBb", + 8 + ], + [ + "BAcBa", + "CCAbBCc", + 10 + ], + [ + "BAcBcba", + "aaa", + 11 + ], + [ + "BAcCBCbaA", + "bCb", + 13 + ], + [ + "BAcCC", + "ACCaC", + 5 + ], + [ + "BAcCCBAbA", + "b", + 16 + ], + [ + "BAcCaB", + "cCBBCaa", + 10 + ], + [ + "BAcCbC", + "CAB", + 9 + ], + [ + "BAcCbaC", + "CCBBaCabB", + 14 + ], + [ + "BAcCbc", + "Cb", + 8 + ], + [ + "BAcCcAACa", + "aacaCaA", + 11 + ], + [ + "BAcaBBBc", + "a", + 14 + ], + [ + "BAcaBc", + "CAbaa", + 8 + ], + [ + "BAcaC", + "cAcaCA", + 4 + ], + [ + "BAcaab", + "ACbA", + 8 + ], + [ + "BAcab", + "BaCbca", + 7 + ], + [ + "BAcbA", + "AbA", + 4 + ], + [ + "BAcbA", + "bACBCbbCA", + 10 + ], + [ + "BAcbAcCB", + "ac", + 13 + ], + [ + "BAcbBCC", + "bBaba", + 11 + ], + [ + "BAcbCC", + "c", + 10 + ], + [ + "BAcbCc", + "B", + 10 + ], + [ + "BAcbabcB", + "aAa", + 12 + ], + [ + "BAcbbba", + "Cb", + 11 + ], + [ + "BAcbcABbA", + "baABcBCa", + 10 + ], + [ + "BAccAAA", + "AaaC", + 10 + ], + [ + "BAccAAca", + "BBCbbCcab", + 11 + ], + [ + "BAccAbCB", + "Cba", + 13 + ], + [ + "BAccCAb", + "cCBcCcac", + 11 + ], + [ + "BAccCAbC", + "BCabCBA", + 11 + ], + [ + "BAccCCcA", + "CbbBaCB", + 14 + ], + [ + "BAccCcCB", + "bcCAB", + 9 + ], + [ + "BAcca", + "cBcCBBcbC", + 13 + ], + [ + "BAccaAaC", + "cbBcAaA", + 10 + ], + [ + "BAcccCbb", + "Bb", + 12 + ], + [ + "BB", + "A", + 4 + ], + [ + "BB", + "AACBAacBB", + 14 + ], + [ + "BB", + "ABaA", + 6 + ], + [ + "BB", + "ACbbCcc", + 12 + ], + [ + "BB", + "AbAABC", + 9 + ], + [ + "BB", + "AbbbBBa", + 10 + ], + [ + "BB", + "BC", + 2 + ], + [ + "BB", + "Ba", + 2 + ], + [ + "BB", + "BbcAaACB", + 12 + ], + [ + "BB", + "CAcaACbBb", + 15 + ], + [ + "BB", + "CBabcc", + 9 + ], + [ + "BB", + "CaCBb", + 7 + ], + [ + "BB", + "CaaAAaAAc", + 18 + ], + [ + "BB", + "Cab", + 5 + ], + [ + "BB", + "Cb", + 3 + ], + [ + "BB", + "CbAAC", + 9 + ], + [ + "BB", + "CbcbCcBc", + 13 + ], + [ + "BB", + "Ccccc", + 10 + ], + [ + "BB", + "a", + 4 + ], + [ + "BB", + "aAAbcc", + 11 + ], + [ + "BB", + "aACBABa", + 10 + ], + [ + "BB", + "aACbBCaac", + 15 + ], + [ + "BB", + "aAcCCb", + 11 + ], + [ + "BB", + "aCACcBC", + 12 + ], + [ + "BB", + "aCa", + 6 + ], + [ + "BB", + "aa", + 4 + ], + [ + "BB", + "bAABCCabC", + 15 + ], + [ + "BB", + "bABBCb", + 8 + ], + [ + "BB", + "bBacaAb", + 11 + ], + [ + "BB", + "bc", + 3 + ], + [ + "BB", + "cBBcCBaAa", + 14 + ], + [ + "BB", + "cCaAaBc", + 12 + ], + [ + "BB", + "cCb", + 5 + ], + [ + "BB", + "cCc", + 6 + ], + [ + "BB", + "caBcCA", + 10 + ], + [ + "BB", + "cac", + 6 + ], + [ + "BB", + "cbb", + 4 + ], + [ + "BB", + "ccaC", + 8 + ], + [ + "BBA", + "BB", + 2 + ], + [ + "BBA", + "BCCaaAb", + 10 + ], + [ + "BBA", + "Bb", + 3 + ], + [ + "BBA", + "Bba", + 2 + ], + [ + "BBA", + "CBbBab", + 7 + ], + [ + "BBA", + "aaBbAc", + 7 + ], + [ + "BBA", + "ab", + 5 + ], + [ + "BBA", + "bAAaBCbA", + 11 + ], + [ + "BBA", + "cbcBbC", + 9 + ], + [ + "BBAACb", + "BBaBbb", + 5 + ], + [ + "BBAAb", + "abaBbac", + 10 + ], + [ + "BBAAbbCc", + "a", + 15 + ], + [ + "BBAAcAb", + "cCBCaBCA", + 12 + ], + [ + "BBAAcbba", + "aCACbcCc", + 13 + ], + [ + "BBABA", + "bbccbbb", + 11 + ], + [ + "BBABACCCB", + "c", + 17 + ], + [ + "BBABBAca", + "aCCAAcCA", + 13 + ], + [ + "BBABBaBA", + "CaAaaa", + 11 + ], + [ + "BBABBaaBB", + "CccbccAc", + 16 + ], + [ + "BBABa", + "b", + 9 + ], + [ + "BBABcC", + "aCAA", + 10 + ], + [ + "BBABccb", + "C", + 13 + ], + [ + "BBAC", + "bBCAcBaC", + 9 + ], + [ + "BBACB", + "bAb", + 6 + ], + [ + "BBACC", + "CBccc", + 6 + ], + [ + "BBACC", + "aCBBBB", + 10 + ], + [ + "BBACaBC", + "C", + 12 + ], + [ + "BBACcCbBa", + "CCCcCC", + 12 + ], + [ + "BBAbA", + "AbCaCcB", + 12 + ], + [ + "BBAbCcacC", + "aBaacBcb", + 11 + ], + [ + "BBAba", + "BCbBcB", + 9 + ], + [ + "BBAbcB", + "BBc", + 6 + ], + [ + "BBAbcBBaA", + "bCcAB", + 13 + ], + [ + "BBAbcC", + "BacA", + 7 + ], + [ + "BBAcA", + "bBcaAacbb", + 11 + ], + [ + "BBAcC", + "cC", + 6 + ], + [ + "BBB", + "BaBbB", + 4 + ], + [ + "BBB", + "Bbcbc", + 6 + ], + [ + "BBB", + "CC", + 6 + ], + [ + "BBB", + "CCCAbc", + 11 + ], + [ + "BBB", + "CcbcBc", + 9 + ], + [ + "BBB", + "aaAbb", + 8 + ], + [ + "BBBAAA", + "AA", + 8 + ], + [ + "BBBAAAcB", + "BAcAcCCA", + 11 + ], + [ + "BBBACc", + "AaBCBb", + 10 + ], + [ + "BBBAbAaAB", + "aAcBABA", + 13 + ], + [ + "BBBAccBbC", + "ccbCbAb", + 14 + ], + [ + "BBBB", + "baABcbb", + 9 + ], + [ + "BBBBCCacA", + "bbCCc", + 10 + ], + [ + "BBBBcCA", + "bBAabcB", + 10 + ], + [ + "BBBBccCCC", + "C", + 16 + ], + [ + "BBBCABbb", + "cAAaAB", + 12 + ], + [ + "BBBCab", + "abBACbBc", + 10 + ], + [ + "BBBa", + "CCabbCcaB", + 14 + ], + [ + "BBBa", + "cAa", + 6 + ], + [ + "BBBaCA", + "AAbbaaACB", + 12 + ], + [ + "BBBaaBb", + "cBcaac", + 8 + ], + [ + "BBBab", + "bAa", + 7 + ], + [ + "BBBac", + "aaCcCBcAB", + 15 + ], + [ + "BBBb", + "B", + 6 + ], + [ + "BBBbCCb", + "BbBaCc", + 6 + ], + [ + "BBBba", + "BCaC", + 8 + ], + [ + "BBBbcBcCA", + "BbBB", + 11 + ], + [ + "BBBbccc", + "A", + 14 + ], + [ + "BBBc", + "cCAba", + 9 + ], + [ + "BBBcAB", + "AbCAAbc", + 10 + ], + [ + "BBBcBAa", + "AbBCCa", + 8 + ], + [ + "BBBcBabbB", + "aCC", + 16 + ], + [ + "BBBcbBA", + "AbACCCbCb", + 14 + ], + [ + "BBBcc", + "CCbbabCb", + 12 + ], + [ + "BBBccBaaB", + "BcCB", + 11 + ], + [ + "BBC", + "CaAAAaCbB", + 16 + ], + [ + "BBC", + "Cbcbcc", + 9 + ], + [ + "BBC", + "abBBacA", + 9 + ], + [ + "BBC", + "b", + 5 + ], + [ + "BBC", + "bCBAA", + 7 + ], + [ + "BBC", + "bcCaAbb", + 11 + ], + [ + "BBC", + "cba", + 5 + ], + [ + "BBCA", + "BAcCC", + 6 + ], + [ + "BBCAbbbc", + "BAa", + 12 + ], + [ + "BBCAcb", + "Ca", + 9 + ], + [ + "BBCBb", + "aac", + 9 + ], + [ + "BBCBbc", + "CaaBB", + 9 + ], + [ + "BBCBcaBb", + "ccCcCbBC", + 11 + ], + [ + "BBCBcb", + "CBcCBcacC", + 10 + ], + [ + "BBCC", + "C", + 6 + ], + [ + "BBCCBC", + "aCbbA", + 9 + ], + [ + "BBCCBab", + "cBCaaC", + 8 + ], + [ + "BBCCCbaCB", + "AAA", + 17 + ], + [ + "BBCCaBbB", + "BaBAcCbC", + 11 + ], + [ + "BBCCccBCB", + "bCaaCA", + 13 + ], + [ + "BBCa", + "aB", + 6 + ], + [ + "BBCaa", + "AccACcAAC", + 14 + ], + [ + "BBCabCCBB", + "BcAcCaCC", + 12 + ], + [ + "BBCbB", + "BBb", + 4 + ], + [ + "BBCbB", + "CB", + 6 + ], + [ + "BBCbB", + "caABAb", + 10 + ], + [ + "BBCbc", + "BAAbcaaBc", + 11 + ], + [ + "BBCc", + "BabAb", + 7 + ], + [ + "BBCc", + "cbabab", + 10 + ], + [ + "BBCcbACcB", + "Ab", + 15 + ], + [ + "BBCccCAba", + "aCcCCBAb", + 9 + ], + [ + "BBa", + "AcBcBAb", + 9 + ], + [ + "BBa", + "Ccb", + 6 + ], + [ + "BBa", + "aCCcbCAc", + 14 + ], + [ + "BBa", + "bBaCBa", + 6 + ], + [ + "BBa", + "cbAcBBaA", + 10 + ], + [ + "BBaA", + "bAAaBAAAB", + 12 + ], + [ + "BBaAAccbb", + "CABB", + 14 + ], + [ + "BBaACBaB", + "cbcC", + 13 + ], + [ + "BBaAab", + "BCB", + 9 + ], + [ + "BBaAcAAcb", + "cbCBBCAcb", + 11 + ], + [ + "BBaAcB", + "ACbAcb", + 7 + ], + [ + "BBaBbcB", + "C", + 13 + ], + [ + "BBaCB", + "cC", + 8 + ], + [ + "BBaCCA", + "ABAcc", + 7 + ], + [ + "BBaCccb", + "Cbcbb", + 9 + ], + [ + "BBaa", + "ba", + 5 + ], + [ + "BBaaCCBA", + "AbCbC", + 12 + ], + [ + "BBaaCca", + "aaBBcbAB", + 13 + ], + [ + "BBaaa", + "B", + 8 + ], + [ + "BBaab", + "bbAcbB", + 7 + ], + [ + "BBaabcA", + "BACBAC", + 10 + ], + [ + "BBaacbcaB", + "Abacab", + 10 + ], + [ + "BBaacc", + "C", + 11 + ], + [ + "BBabBa", + "BbBBcAACB", + 13 + ], + [ + "BBabcBab", + "abB", + 10 + ], + [ + "BBabcbb", + "BbbCbA", + 6 + ], + [ + "BBac", + "cBAB", + 5 + ], + [ + "BBacAcaAa", + "Bba", + 13 + ], + [ + "BBacCaBBa", + "aa", + 14 + ], + [ + "BBacCc", + "bcaBA", + 9 + ], + [ + "BBacaABc", + "Cbbaac", + 10 + ], + [ + "BBacb", + "AbbaCBCb", + 9 + ], + [ + "BBacb", + "abCACCBBA", + 14 + ], + [ + "BBb", + "BAcaAaca", + 14 + ], + [ + "BBb", + "BBa", + 2 + ], + [ + "BBb", + "CAcBCAAc", + 14 + ], + [ + "BBb", + "aBACcCCC", + 14 + ], + [ + "BBb", + "acbCc", + 8 + ], + [ + "BBbA", + "ca", + 7 + ], + [ + "BBbAACb", + "ACCbb", + 10 + ], + [ + "BBbABAaAB", + "cAbaCba", + 13 + ], + [ + "BBbABbac", + "cAB", + 12 + ], + [ + "BBbAabbCA", + "A", + 16 + ], + [ + "BBbAbcB", + "AcAAcbABa", + 12 + ], + [ + "BBbAcaAB", + "cBB", + 12 + ], + [ + "BBbAcc", + "bBAb", + 7 + ], + [ + "BBbB", + "ccABABc", + 10 + ], + [ + "BBbBB", + "a", + 10 + ], + [ + "BBbBBbba", + "aACbb", + 12 + ], + [ + "BBbBBcacc", + "AcAa", + 15 + ], + [ + "BBbBCbBc", + "BbABC", + 9 + ], + [ + "BBbBaa", + "bbBbbbaAb", + 9 + ], + [ + "BBbCCCCbc", + "ccaAB", + 15 + ], + [ + "BBbCCcbAc", + "bABcac", + 11 + ], + [ + "BBbCbcb", + "Ab", + 12 + ], + [ + "BBba", + "a", + 6 + ], + [ + "BBbaBc", + "ac", + 8 + ], + [ + "BBbab", + "AcAB", + 8 + ], + [ + "BBbabb", + "CCbAa", + 9 + ], + [ + "BBbabbab", + "bCBBbaA", + 9 + ], + [ + "BBbb", + "BA", + 6 + ], + [ + "BBbbACc", + "A", + 12 + ], + [ + "BBbbCCBB", + "AaCcBAAA", + 15 + ], + [ + "BBbcABBbC", + "AaC", + 14 + ], + [ + "BBbcB", + "ACbA", + 8 + ], + [ + "BBbcCac", + "cbb", + 11 + ], + [ + "BBbccCCBB", + "BbaBcb", + 12 + ], + [ + "BBc", + "ABCbAA", + 9 + ], + [ + "BBc", + "BbbABAACA", + 13 + ], + [ + "BBc", + "BcccAbBaC", + 13 + ], + [ + "BBc", + "aBaCccA", + 10 + ], + [ + "BBc", + "aC", + 5 + ], + [ + "BBc", + "aCacBbCCa", + 14 + ], + [ + "BBcAAA", + "Acbc", + 10 + ], + [ + "BBcAAac", + "acCcAbc", + 9 + ], + [ + "BBcABAA", + "BbBaABaC", + 7 + ], + [ + "BBcB", + "AAacBC", + 8 + ], + [ + "BBcB", + "aAaabB", + 10 + ], + [ + "BBcB", + "acb", + 5 + ], + [ + "BBcB", + "bAcAAbac", + 12 + ], + [ + "BBcBACa", + "CBACA", + 6 + ], + [ + "BBcBCB", + "c", + 10 + ], + [ + "BBcBbCBA", + "cBaaAca", + 12 + ], + [ + "BBcBbcA", + "bBbca", + 6 + ], + [ + "BBcC", + "AcAcAcCc", + 12 + ], + [ + "BBcCBCac", + "ababc", + 12 + ], + [ + "BBcCC", + "ccB", + 7 + ], + [ + "BBcCCabAA", + "BCC", + 12 + ], + [ + "BBcCCcab", + "bCcc", + 10 + ], + [ + "BBcCa", + "BBAA", + 5 + ], + [ + "BBcCcb", + "BCb", + 6 + ], + [ + "BBca", + "b", + 7 + ], + [ + "BBcaA", + "AaCCAbCCC", + 15 + ], + [ + "BBcaA", + "b", + 9 + ], + [ + "BBcaACbB", + "AbCb", + 11 + ], + [ + "BBcaC", + "CCccCCCC", + 12 + ], + [ + "BBcaa", + "aBaa", + 4 + ], + [ + "BBcaa", + "bAAAC", + 8 + ], + [ + "BBcaa", + "ccCCcABA", + 12 + ], + [ + "BBcaaCBB", + "BbCcbC", + 10 + ], + [ + "BBcab", + "aaBc", + 8 + ], + [ + "BBcbA", + "BcBabbA", + 6 + ], + [ + "BBcbaA", + "caBbCcaa", + 9 + ], + [ + "BBcbbaB", + "ccCbbAccB", + 10 + ], + [ + "BBcbbabC", + "BB", + 12 + ], + [ + "BBcc", + "AcbAAB", + 11 + ], + [ + "BBcc", + "CCcBccBbC", + 12 + ], + [ + "BBccAb", + "aCACbAC", + 11 + ], + [ + "BBccB", + "BcB", + 4 + ], + [ + "BBccc", + "BBAbCAcbB", + 11 + ], + [ + "BBcccCAAB", + "ABABBAca", + 14 + ], + [ + "BC", + "A", + 4 + ], + [ + "BC", + "AAcba", + 9 + ], + [ + "BC", + "AC", + 2 + ], + [ + "BC", + "ACBACCA", + 10 + ], + [ + "BC", + "AaA", + 6 + ], + [ + "BC", + "AaCABABAb", + 16 + ], + [ + "BC", + "Ac", + 3 + ], + [ + "BC", + "AcCCcc", + 10 + ], + [ + "BC", + "B", + 2 + ], + [ + "BC", + "BAbC", + 4 + ], + [ + "BC", + "BC", + 0 + ], + [ + "BC", + "BCCCC", + 6 + ], + [ + "BC", + "BbAaaBAbb", + 16 + ], + [ + "BC", + "BbB", + 4 + ], + [ + "BC", + "BcB", + 3 + ], + [ + "BC", + "C", + 2 + ], + [ + "BC", + "CA", + 4 + ], + [ + "BC", + "CaaaCAcaC", + 16 + ], + [ + "BC", + "CbCC", + 5 + ], + [ + "BC", + "CcC", + 4 + ], + [ + "BC", + "CcCCCab", + 12 + ], + [ + "BC", + "CcaAbbbcb", + 16 + ], + [ + "BC", + "CccAb", + 9 + ], + [ + "BC", + "aAA", + 6 + ], + [ + "BC", + "aAbbcbac", + 14 + ], + [ + "BC", + "aCbAaAbBB", + 16 + ], + [ + "BC", + "abbba", + 9 + ], + [ + "BC", + "acAACAC", + 12 + ], + [ + "BC", + "acbCA", + 7 + ], + [ + "BC", + "acccbAAB", + 15 + ], + [ + "BC", + "bAABa", + 8 + ], + [ + "BC", + "bBBAcBbCC", + 14 + ], + [ + "BC", + "bC", + 1 + ], + [ + "BC", + "bCaCaCaa", + 13 + ], + [ + "BC", + "bCbaCa", + 9 + ], + [ + "BC", + "bCbaaaAAC", + 15 + ], + [ + "BC", + "babCAbca", + 13 + ], + [ + "BC", + "babbBBaB", + 14 + ], + [ + "BC", + "bbCA", + 5 + ], + [ + "BC", + "c", + 3 + ], + [ + "BC", + "cAAB", + 8 + ], + [ + "BC", + "cABCBCAB", + 12 + ], + [ + "BC", + "cBaACA", + 8 + ], + [ + "BC", + "cBbaAcB", + 11 + ], + [ + "BC", + "cCCBb", + 8 + ], + [ + "BC", + "cb", + 4 + ], + [ + "BC", + "cbBBB", + 8 + ], + [ + "BC", + "cba", + 5 + ], + [ + "BC", + "cbaB", + 7 + ], + [ + "BC", + "cbaCbBAC", + 12 + ], + [ + "BC", + "cbbCabA", + 11 + ], + [ + "BC", + "ccAACb", + 10 + ], + [ + "BC", + "ccccbb", + 11 + ], + [ + "BCA", + "A", + 4 + ], + [ + "BCA", + "AbcCbCaA", + 11 + ], + [ + "BCA", + "abccbBCC", + 12 + ], + [ + "BCAABABCB", + "abBbBaB", + 11 + ], + [ + "BCAABc", + "bBACcBa", + 9 + ], + [ + "BCAAbBc", + "AaABC", + 8 + ], + [ + "BCAAbbbc", + "A", + 14 + ], + [ + "BCAAcbb", + "CAaCAcBbC", + 9 + ], + [ + "BCAAccAa", + "Aaaa", + 10 + ], + [ + "BCABBA", + "AbBba", + 8 + ], + [ + "BCABBBAa", + "bCAbBA", + 6 + ], + [ + "BCABa", + "b", + 9 + ], + [ + "BCABcB", + "bcAAbCb", + 7 + ], + [ + "BCABcCCc", + "CACcbBAc", + 10 + ], + [ + "BCABccC", + "B", + 12 + ], + [ + "BCABccaB", + "cAaCbBCc", + 13 + ], + [ + "BCAC", + "CAcCB", + 6 + ], + [ + "BCACBaACB", + "aaCaC", + 11 + ], + [ + "BCACBaba", + "BbAaAbc", + 9 + ], + [ + "BCACBb", + "aCbCA", + 8 + ], + [ + "BCACb", + "CAABCa", + 8 + ], + [ + "BCACcABBB", + "C", + 16 + ], + [ + "BCACcCAC", + "aBcAaaB", + 12 + ], + [ + "BCACcb", + "AC", + 8 + ], + [ + "BCAa", + "A", + 6 + ], + [ + "BCAa", + "Cc", + 6 + ], + [ + "BCAacBCB", + "CaCC", + 9 + ], + [ + "BCAbBAbAc", + "aBcbB", + 13 + ], + [ + "BCAbBaCB", + "caCba", + 11 + ], + [ + "BCAbCB", + "ccBcBCC", + 10 + ], + [ + "BCAbaCC", + "aB", + 12 + ], + [ + "BCAbbAcBA", + "A", + 16 + ], + [ + "BCAbbBA", + "BBAAAbac", + 10 + ], + [ + "BCAbc", + "Cb", + 6 + ], + [ + "BCAbcaBCC", + "ccBacBbA", + 13 + ], + [ + "BCAbccbb", + "bAcaCCbAB", + 12 + ], + [ + "BCAc", + "BaBb", + 6 + ], + [ + "BCAcC", + "Ccc", + 5 + ], + [ + "BCAcCabcc", + "CBC", + 14 + ], + [ + "BCAcabbBc", + "bcbC", + 12 + ], + [ + "BCB", + "caa", + 6 + ], + [ + "BCB", + "ccB", + 3 + ], + [ + "BCBA", + "B", + 6 + ], + [ + "BCBA", + "bAB", + 5 + ], + [ + "BCBAACcc", + "aBB", + 14 + ], + [ + "BCBACbAbA", + "ccABAa", + 11 + ], + [ + "BCBACbCb", + "cBA", + 11 + ], + [ + "BCBAaBcA", + "baC", + 12 + ], + [ + "BCBAabB", + "Bcb", + 9 + ], + [ + "BCBAb", + "CccbcCcBb", + 13 + ], + [ + "BCBAcbbC", + "AccaABA", + 13 + ], + [ + "BCBB", + "A", + 8 + ], + [ + "BCBBA", + "ACAC", + 8 + ], + [ + "BCBBACa", + "AbbAA", + 9 + ], + [ + "BCBBbCbC", + "Ccb", + 11 + ], + [ + "BCBBbab", + "cCAccAbb", + 11 + ], + [ + "BCBBcCa", + "BCCCBbA", + 9 + ], + [ + "BCBBcaCBA", + "CbBCCBacA", + 10 + ], + [ + "BCBBccB", + "B", + 12 + ], + [ + "BCBC", + "CbaBa", + 7 + ], + [ + "BCBC", + "cabAcCaCc", + 13 + ], + [ + "BCBCAaB", + "CAcb", + 9 + ], + [ + "BCBCBaAA", + "b", + 15 + ], + [ + "BCBCBbbc", + "CBBA", + 10 + ], + [ + "BCBCbbBC", + "CACcaACaB", + 14 + ], + [ + "BCBCc", + "aBAA", + 8 + ], + [ + "BCBaACb", + "aCB", + 9 + ], + [ + "BCBaAcb", + "AaC", + 11 + ], + [ + "BCBaC", + "BC", + 6 + ], + [ + "BCBaCCabc", + "aAcbC", + 12 + ], + [ + "BCBb", + "AaaBabaAa", + 14 + ], + [ + "BCBbAABB", + "A", + 14 + ], + [ + "BCBbBCaA", + "B", + 14 + ], + [ + "BCBbc", + "AbBba", + 6 + ], + [ + "BCBbcbBC", + "ca", + 14 + ], + [ + "BCBcB", + "bbC", + 7 + ], + [ + "BCBcaACa", + "bbCaabC", + 10 + ], + [ + "BCBcabB", + "aaBaaaAac", + 14 + ], + [ + "BCC", + "AabbC", + 7 + ], + [ + "BCC", + "AbbBBa", + 10 + ], + [ + "BCC", + "CCBcABb", + 11 + ], + [ + "BCCA", + "bcbbc", + 8 + ], + [ + "BCCA", + "cBBbacAc", + 11 + ], + [ + "BCCAAbbC", + "ccCB", + 12 + ], + [ + "BCCABc", + "c", + 10 + ], + [ + "BCCAaba", + "CAbB", + 8 + ], + [ + "BCCB", + "cBca", + 7 + ], + [ + "BCCBCC", + "ACCBaa", + 6 + ], + [ + "BCCBbcAAA", + "aaaCCB", + 16 + ], + [ + "BCCCAbA", + "CabCcbc", + 10 + ], + [ + "BCCCCAB", + "BC", + 10 + ], + [ + "BCCCCBAB", + "cABCcc", + 13 + ], + [ + "BCCCb", + "cCCAc", + 6 + ], + [ + "BCCaAcbB", + "Cabccaa", + 12 + ], + [ + "BCCaB", + "cAaAcAa", + 12 + ], + [ + "BCCaBABCc", + "a", + 16 + ], + [ + "BCCaCBCAA", + "cBbCaccaA", + 9 + ], + [ + "BCCac", + "CbC", + 7 + ], + [ + "BCCbAc", + "AcCAaBcCc", + 12 + ], + [ + "BCCbCbaB", + "caa", + 13 + ], + [ + "BCCbaBBbC", + "baBAB", + 11 + ], + [ + "BCCbb", + "aAAcc", + 10 + ], + [ + "BCCbbb", + "BCaB", + 7 + ], + [ + "BCCbc", + "BBaAbbBCC", + 13 + ], + [ + "BCCbc", + "caBaAbbC", + 11 + ], + [ + "BCCcaAccC", + "bacbC", + 11 + ], + [ + "BCCcbAcBa", + "CA", + 14 + ], + [ + "BCCcbaBc", + "CcCcCAba", + 9 + ], + [ + "BCCccbaC", + "C", + 14 + ], + [ + "BCa", + "CBbbAac", + 10 + ], + [ + "BCa", + "aAbCCb", + 9 + ], + [ + "BCa", + "bbcAB", + 7 + ], + [ + "BCa", + "c", + 5 + ], + [ + "BCaABBaAB", + "acCcBAC", + 13 + ], + [ + "BCaAa", + "AC", + 8 + ], + [ + "BCaAaCac", + "aaAB", + 11 + ], + [ + "BCaAba", + "a", + 10 + ], + [ + "BCaAcAbC", + "B", + 14 + ], + [ + "BCaBAC", + "bCAcCAaB", + 10 + ], + [ + "BCaBcCB", + "BaAA", + 10 + ], + [ + "BCaCAc", + "CBAaBc", + 8 + ], + [ + "BCaCCCcc", + "ccbaBCaC", + 12 + ], + [ + "BCaCbbbc", + "aaAAa", + 14 + ], + [ + "BCaa", + "bc", + 6 + ], + [ + "BCaaAbaB", + "ccCA", + 13 + ], + [ + "BCaaab", + "cAbBBa", + 11 + ], + [ + "BCaaabC", + "Bbbcb", + 10 + ], + [ + "BCaabcbA", + "BabcCAb", + 8 + ], + [ + "BCabaB", + "BaBAbcB", + 7 + ], + [ + "BCabacC", + "a", + 12 + ], + [ + "BCabbbABB", + "BBCcABb", + 10 + ], + [ + "BCabbcCCC", + "ABcCAcbc", + 13 + ], + [ + "BCabcaAAA", + "Baa", + 12 + ], + [ + "BCacBBAAB", + "aBC", + 14 + ], + [ + "BCacBBbB", + "CBCB", + 10 + ], + [ + "BCacCB", + "bB", + 9 + ], + [ + "BCacCba", + "BaBbaAAba", + 10 + ], + [ + "BCb", + "BBa", + 4 + ], + [ + "BCb", + "bBCCa", + 6 + ], + [ + "BCb", + "bCAAAAC", + 11 + ], + [ + "BCbA", + "Bccc", + 5 + ], + [ + "BCbAAC", + "aA", + 9 + ], + [ + "BCbAcbcCB", + "cbBcba", + 11 + ], + [ + "BCbBCacaC", + "AACB", + 16 + ], + [ + "BCbBa", + "cbbBC", + 6 + ], + [ + "BCbBaa", + "aCAAbACc", + 12 + ], + [ + "BCbBca", + "bBaB", + 8 + ], + [ + "BCbCA", + "cB", + 8 + ], + [ + "BCbCccA", + "aaABcCAB", + 11 + ], + [ + "BCbaAABaa", + "AA", + 14 + ], + [ + "BCbaBAb", + "cAAAcbbaA", + 15 + ], + [ + "BCbaCbBAc", + "CBCcabcAb", + 10 + ], + [ + "BCbaaCCaa", + "cCbac", + 11 + ], + [ + "BCbaabbBC", + "abcCBbB", + 11 + ], + [ + "BCbaccCC", + "bbBc", + 11 + ], + [ + "BCbbc", + "AcCbBaACc", + 11 + ], + [ + "BCbbcbbB", + "AbbCca", + 11 + ], + [ + "BCbc", + "cbcCCcBAA", + 13 + ], + [ + "BCbcBbaC", + "babCBCACb", + 9 + ], + [ + "BCbcaACBB", + "aab", + 14 + ], + [ + "BCbccccB", + "Ca", + 14 + ], + [ + "BCc", + "AAaCBcAbc", + 13 + ], + [ + "BCc", + "AcBbBbcba", + 14 + ], + [ + "BCc", + "CAcB", + 6 + ], + [ + "BCc", + "bABBC", + 7 + ], + [ + "BCcA", + "Aac", + 6 + ], + [ + "BCcA", + "bbbCc", + 7 + ], + [ + "BCcACaB", + "CBcbcaBAA", + 11 + ], + [ + "BCcACaCca", + "cca", + 12 + ], + [ + "BCcAcb", + "bbcacbb", + 6 + ], + [ + "BCcBBAccb", + "cBB", + 12 + ], + [ + "BCcBBacB", + "B", + 14 + ], + [ + "BCcBBc", + "ca", + 10 + ], + [ + "BCcBac", + "Cb", + 9 + ], + [ + "BCcBbBAb", + "BBcBBcA", + 7 + ], + [ + "BCcBbBCc", + "A", + 16 + ], + [ + "BCcBc", + "a", + 10 + ], + [ + "BCcCB", + "CABB", + 6 + ], + [ + "BCcCC", + "baB", + 9 + ], + [ + "BCcCcaaB", + "CAaa", + 10 + ], + [ + "BCca", + "aBcCcA", + 5 + ], + [ + "BCcaAc", + "aaAAA", + 9 + ], + [ + "BCcaBbAb", + "A", + 14 + ], + [ + "BCcab", + "CA", + 7 + ], + [ + "BCcacCbcc", + "CBcabaBCC", + 11 + ], + [ + "BCcb", + "CCbBACCcc", + 12 + ], + [ + "BCcb", + "Ca", + 6 + ], + [ + "BCcb", + "bC", + 5 + ], + [ + "BCcbAc", + "B", + 10 + ], + [ + "BCcbAcBAA", + "AacbBc", + 12 + ], + [ + "BCcbBac", + "Aaa", + 12 + ], + [ + "BCcbaC", + "cB", + 9 + ], + [ + "BCcbc", + "BBa", + 7 + ], + [ + "BCccAac", + "ABB", + 12 + ], + [ + "BCccBb", + "Cc", + 8 + ], + [ + "BCccCC", + "BBA", + 10 + ], + [ + "Ba", + "AAACAbbb", + 15 + ], + [ + "Ba", + "AAaBBbBaa", + 14 + ], + [ + "Ba", + "ABBaAabBa", + 14 + ], + [ + "Ba", + "AaccaB", + 10 + ], + [ + "Ba", + "AcABCBc", + 12 + ], + [ + "Ba", + "AcB", + 6 + ], + [ + "Ba", + "AcaAcb", + 10 + ], + [ + "Ba", + "B", + 2 + ], + [ + "Ba", + "BAcBc", + 7 + ], + [ + "Ba", + "Ba", + 0 + ], + [ + "Ba", + "BcAACc", + 9 + ], + [ + "Ba", + "BcCa", + 4 + ], + [ + "Ba", + "BcbBb", + 8 + ], + [ + "Ba", + "C", + 4 + ], + [ + "Ba", + "CABbaBcb", + 12 + ], + [ + "Ba", + "CBBAAB", + 9 + ], + [ + "Ba", + "CC", + 4 + ], + [ + "Ba", + "CCCcABBC", + 14 + ], + [ + "Ba", + "CbCbCa", + 9 + ], + [ + "Ba", + "Cc", + 4 + ], + [ + "Ba", + "a", + 2 + ], + [ + "Ba", + "aA", + 3 + ], + [ + "Ba", + "aB", + 4 + ], + [ + "Ba", + "aCB", + 6 + ], + [ + "Ba", + "aCbCa", + 7 + ], + [ + "Ba", + "acBbb", + 8 + ], + [ + "Ba", + "b", + 3 + ], + [ + "Ba", + "bcBCAbcCB", + 15 + ], + [ + "Ba", + "c", + 4 + ], + [ + "Ba", + "cAaAABbCB", + 16 + ], + [ + "Ba", + "cBBa", + 4 + ], + [ + "Ba", + "cbBaCAb", + 10 + ], + [ + "Ba", + "ccBcACA", + 11 + ], + [ + "Ba", + "ccC", + 6 + ], + [ + "BaA", + "CACaA", + 6 + ], + [ + "BaA", + "CBAbbaca", + 11 + ], + [ + "BaA", + "aaBB", + 6 + ], + [ + "BaA", + "bA", + 3 + ], + [ + "BaA", + "baACAbcA", + 11 + ], + [ + "BaA", + "cbc", + 6 + ], + [ + "BaA", + "ccBAAB", + 7 + ], + [ + "BaAAA", + "BB", + 8 + ], + [ + "BaAAa", + "ca", + 8 + ], + [ + "BaAAaCBaA", + "ACCC", + 14 + ], + [ + "BaAAcCA", + "abCCBA", + 9 + ], + [ + "BaAAcc", + "aaAAbac", + 6 + ], + [ + "BaAAccaCA", + "CcABcBCc", + 12 + ], + [ + "BaAB", + "BBAB", + 2 + ], + [ + "BaAB", + "ac", + 6 + ], + [ + "BaAB", + "bBB", + 5 + ], + [ + "BaABAACBA", + "bAb", + 14 + ], + [ + "BaABCaACb", + "bCbabAaBb", + 13 + ], + [ + "BaABa", + "abC", + 7 + ], + [ + "BaABcbab", + "cAaca", + 10 + ], + [ + "BaACAcCc", + "BC", + 12 + ], + [ + "BaACB", + "bcAa", + 7 + ], + [ + "BaACBAbAC", + "Bac", + 13 + ], + [ + "BaACaCbC", + "AACACCC", + 6 + ], + [ + "BaAa", + "BBCC", + 6 + ], + [ + "BaAa", + "BcbccbCC", + 14 + ], + [ + "BaAa", + "aAbB", + 6 + ], + [ + "BaAaAaAB", + "Ccab", + 13 + ], + [ + "BaAaaAAb", + "ABBCBAcB", + 13 + ], + [ + "BaAacA", + "bACCCb", + 9 + ], + [ + "BaAb", + "AbCBcCba", + 12 + ], + [ + "BaAbCBa", + "CCCcAb", + 13 + ], + [ + "BaAbab", + "aAbaBCc", + 7 + ], + [ + "BaAbcCCB", + "CBcA", + 13 + ], + [ + "BaAcAB", + "BabCCCaC", + 10 + ], + [ + "BaAcAa", + "abaCCCBAA", + 11 + ], + [ + "BaAcAbbC", + "ABaBBAC", + 10 + ], + [ + "BaAcB", + "CBAACbCa", + 9 + ], + [ + "BaAcCbb", + "Abb", + 8 + ], + [ + "BaAcCbca", + "BcCbCAAcB", + 12 + ], + [ + "BaAcbcb", + "c", + 12 + ], + [ + "BaB", + "CACAb", + 8 + ], + [ + "BaB", + "aCACaB", + 8 + ], + [ + "BaB", + "bc", + 5 + ], + [ + "BaB", + "cBBB", + 4 + ], + [ + "BaB", + "cCacaaB", + 10 + ], + [ + "BaBA", + "cABACbbA", + 10 + ], + [ + "BaBAA", + "bbAacbA", + 9 + ], + [ + "BaBAB", + "acAaABBA", + 11 + ], + [ + "BaBABabC", + "C", + 14 + ], + [ + "BaBACA", + "ACAcbbccb", + 15 + ], + [ + "BaBAc", + "BaBABaaC", + 7 + ], + [ + "BaBB", + "aBBCaA", + 8 + ], + [ + "BaBBaABA", + "AaabaABb", + 7 + ], + [ + "BaBBb", + "BC", + 8 + ], + [ + "BaBBc", + "C", + 9 + ], + [ + "BaBBcAaAc", + "C", + 17 + ], + [ + "BaBBcbcb", + "bbabb", + 10 + ], + [ + "BaBCAbab", + "A", + 14 + ], + [ + "BaBCBC", + "BA", + 9 + ], + [ + "BaBCaCac", + "cACc", + 10 + ], + [ + "BaBCc", + "cBcCbAab", + 13 + ], + [ + "BaBa", + "bBBABAabb", + 11 + ], + [ + "BaBa", + "bCAbaAbCb", + 13 + ], + [ + "BaBaBB", + "C", + 12 + ], + [ + "BaBb", + "CC", + 8 + ], + [ + "BaBb", + "bbAbCcbBc", + 13 + ], + [ + "BaBbABA", + "ababcCAc", + 11 + ], + [ + "BaBbBb", + "cCAAb", + 10 + ], + [ + "BaBbC", + "BBACac", + 8 + ], + [ + "BaBbabbCb", + "b", + 16 + ], + [ + "BaBc", + "CB", + 6 + ], + [ + "BaBcACac", + "AbCaBA", + 11 + ], + [ + "BaBcB", + "AaabaaBAa", + 13 + ], + [ + "BaBcBbbAa", + "BCACcab", + 13 + ], + [ + "BaBcCA", + "CbCACA", + 8 + ], + [ + "BaBcCA", + "bCa", + 8 + ], + [ + "BaBcaCCbB", + "BCC", + 12 + ], + [ + "BaBcacC", + "ACB", + 12 + ], + [ + "BaC", + "ABAbc", + 6 + ], + [ + "BaC", + "ABbb", + 6 + ], + [ + "BaC", + "ACa", + 5 + ], + [ + "BaC", + "Aaba", + 6 + ], + [ + "BaC", + "BACccaaBA", + 13 + ], + [ + "BaC", + "BC", + 2 + ], + [ + "BaC", + "Bccb", + 5 + ], + [ + "BaC", + "a", + 4 + ], + [ + "BaC", + "bCbbBcB", + 12 + ], + [ + "BaC", + "baBcbC", + 7 + ], + [ + "BaC", + "cBAabACAa", + 12 + ], + [ + "BaC", + "cBaBAaCc", + 10 + ], + [ + "BaCAA", + "cbbcBBbb", + 14 + ], + [ + "BaCAAbbB", + "BAcB", + 10 + ], + [ + "BaCAB", + "cC", + 8 + ], + [ + "BaCABCB", + "bB", + 11 + ], + [ + "BaCABccaB", + "CAacBCCA", + 12 + ], + [ + "BaCAaAc", + "bCa", + 9 + ], + [ + "BaCB", + "B", + 6 + ], + [ + "BaCBa", + "BbAbaBC", + 9 + ], + [ + "BaCBaCcCC", + "aCCAc", + 11 + ], + [ + "BaCBbCB", + "acB", + 9 + ], + [ + "BaCBc", + "ABCB", + 6 + ], + [ + "BaCBcBC", + "cc", + 11 + ], + [ + "BaCCAcAb", + "Bab", + 10 + ], + [ + "BaCCC", + "CcBCabAAa", + 14 + ], + [ + "BaCCCAcb", + "bbcAaA", + 12 + ], + [ + "BaCCCbb", + "bCCaaCb", + 9 + ], + [ + "BaCCbAA", + "acCBcBB", + 10 + ], + [ + "BaCCc", + "Ca", + 8 + ], + [ + "BaCCcCcc", + "baAbcaCc", + 8 + ], + [ + "BaCa", + "bbAba", + 6 + ], + [ + "BaCaAAaca", + "CCbCb", + 15 + ], + [ + "BaCaCCABa", + "bCaC", + 11 + ], + [ + "BaCb", + "bbaA", + 7 + ], + [ + "BaCb", + "cABC", + 7 + ], + [ + "BaCbAB", + "BAcbaAC", + 6 + ], + [ + "BaCbBA", + "bcAacBaa", + 10 + ], + [ + "BaCbBCaa", + "acBCAAcA", + 11 + ], + [ + "BaCbBcbB", + "bc", + 12 + ], + [ + "BaCbbbBaa", + "CcCAcCCC", + 16 + ], + [ + "BaCc", + "Bc", + 4 + ], + [ + "BaCc", + "CCBcbAAA", + 13 + ], + [ + "BaCcBbAb", + "BcaAaBa", + 11 + ], + [ + "BaCcCcab", + "abBAcBA", + 12 + ], + [ + "BaCcCcbAc", + "BacCAaaaA", + 11 + ], + [ + "BaCcaAabB", + "BBc", + 14 + ], + [ + "BaCcaBAa", + "AaCb", + 11 + ], + [ + "BaCccaaaA", + "cBacBCA", + 12 + ], + [ + "Baa", + "A", + 5 + ], + [ + "Baa", + "AcccabBa", + 12 + ], + [ + "Baa", + "BaaBCABcB", + 12 + ], + [ + "Baa", + "CAAa", + 5 + ], + [ + "Baa", + "CBBcBcC", + 12 + ], + [ + "Baa", + "CCABBacBb", + 14 + ], + [ + "Baa", + "CbABbAcbb", + 15 + ], + [ + "Baa", + "CbCaAbc", + 10 + ], + [ + "Baa", + "bBaBbabAc", + 12 + ], + [ + "Baa", + "bBccbbaAC", + 13 + ], + [ + "Baa", + "bcAbaAACA", + 14 + ], + [ + "BaaA", + "CaBBc", + 8 + ], + [ + "BaaA", + "baBabC", + 7 + ], + [ + "BaaABa", + "bCbcaa", + 9 + ], + [ + "BaaACcAAc", + "bccAaa", + 11 + ], + [ + "BaaAaCc", + "cB", + 13 + ], + [ + "BaaB", + "C", + 8 + ], + [ + "BaaB", + "cAa", + 5 + ], + [ + "BaaBAbAb", + "baccCC", + 13 + ], + [ + "BaaBBcA", + "ccC", + 12 + ], + [ + "BaaC", + "bCb", + 7 + ], + [ + "BaaC", + "cacAcCBcC", + 13 + ], + [ + "BaaCACAaB", + "cAaCabab", + 9 + ], + [ + "BaaCBCBBc", + "ac", + 14 + ], + [ + "BaaCbAC", + "AbCAbca", + 11 + ], + [ + "BaaCcCa", + "cBACb", + 11 + ], + [ + "BaaaBcCab", + "BaBbbbAaC", + 11 + ], + [ + "BaaaC", + "accb", + 8 + ], + [ + "Baaaa", + "B", + 8 + ], + [ + "Baaaa", + "CaA", + 7 + ], + [ + "BaaababB", + "bCAcBCB", + 11 + ], + [ + "BaaacbA", + "aBCba", + 8 + ], + [ + "Baab", + "cCAAcBa", + 11 + ], + [ + "Baab", + "ccaAACcAC", + 15 + ], + [ + "BaabA", + "b", + 8 + ], + [ + "BaabAA", + "A", + 10 + ], + [ + "BaabBAcC", + "aaBCaab", + 10 + ], + [ + "BaabBcc", + "cCCAaCbc", + 12 + ], + [ + "BaabCBCC", + "CcCaB", + 14 + ], + [ + "BaabCa", + "cAaAB", + 9 + ], + [ + "BaabCb", + "CA", + 10 + ], + [ + "Baabacaa", + "bAcbccB", + 10 + ], + [ + "Baabb", + "bBbAbbA", + 7 + ], + [ + "BaabbAaA", + "BBA", + 11 + ], + [ + "BaabcAb", + "aBaC", + 10 + ], + [ + "Baac", + "AaCacBCA", + 10 + ], + [ + "Baac", + "CcBaBAb", + 9 + ], + [ + "Baac", + "a", + 6 + ], + [ + "Baac", + "aaAB", + 5 + ], + [ + "Baac", + "cCcBC", + 9 + ], + [ + "Baac", + "ccCb", + 8 + ], + [ + "BaacCABbA", + "cA", + 14 + ], + [ + "Bab", + "BaCABaCa", + 11 + ], + [ + "Bab", + "C", + 6 + ], + [ + "Bab", + "a", + 4 + ], + [ + "Bab", + "acabAacac", + 14 + ], + [ + "Bab", + "bbCaccBaB", + 13 + ], + [ + "BabA", + "a", + 6 + ], + [ + "BabA", + "cbAc", + 6 + ], + [ + "BabAAccaB", + "abb", + 13 + ], + [ + "BabACA", + "b", + 10 + ], + [ + "BabAaC", + "baCCBcC", + 9 + ], + [ + "BabAb", + "Aa", + 8 + ], + [ + "BabAbACA", + "CBAaCaaa", + 12 + ], + [ + "BabAbCA", + "CccAab", + 12 + ], + [ + "BabBBAaCa", + "AcCAccAB", + 15 + ], + [ + "BabBa", + "ABbACcCcb", + 15 + ], + [ + "BabBcA", + "ACC", + 10 + ], + [ + "BabBcBcA", + "acbCABcB", + 10 + ], + [ + "BabC", + "aABb", + 6 + ], + [ + "BabCaBca", + "AA", + 14 + ], + [ + "Baba", + "BaBCbCBB", + 10 + ], + [ + "BabaBaAA", + "AaB", + 11 + ], + [ + "BabacAbAb", + "cCaA", + 14 + ], + [ + "Babb", + "CcAa", + 8 + ], + [ + "Babb", + "aaACcbc", + 10 + ], + [ + "BabbA", + "CaCb", + 6 + ], + [ + "BabbAaaBA", + "BbCcACC", + 13 + ], + [ + "BabbAccb", + "cacBB", + 11 + ], + [ + "BabbBcBA", + "cAabCbba", + 11 + ], + [ + "BabbCBb", + "aCCcBcB", + 10 + ], + [ + "BabbCCbCA", + "AbAcBa", + 12 + ], + [ + "BabbcBAAa", + "aCacA", + 12 + ], + [ + "BabcBAbcc", + "CabA", + 12 + ], + [ + "BabcCbCA", + "cbC", + 10 + ], + [ + "BabcaC", + "ABB", + 10 + ], + [ + "Babcac", + "bcccAbBC", + 11 + ], + [ + "Bac", + "BBcaBcC", + 8 + ], + [ + "Bac", + "CbcAbAbB", + 14 + ], + [ + "Bac", + "bacBAccb", + 11 + ], + [ + "Bac", + "bbACab", + 9 + ], + [ + "BacAa", + "bBCb", + 8 + ], + [ + "BacAaaCaB", + "ccaaAb", + 10 + ], + [ + "BacAb", + "aBBaBa", + 9 + ], + [ + "BacAbA", + "caAaBbCAb", + 11 + ], + [ + "BacBBa", + "AbBBAAA", + 10 + ], + [ + "BacBCAB", + "cCBaBCc", + 10 + ], + [ + "BacBab", + "CbBABcB", + 10 + ], + [ + "BacCAaBAB", + "ccAb", + 12 + ], + [ + "BacCBBCc", + "B", + 14 + ], + [ + "BacCCaA", + "bcaBA", + 9 + ], + [ + "BacCCcbB", + "cCbaaCBB", + 12 + ], + [ + "BacCa", + "cCBBB", + 10 + ], + [ + "BacCab", + "bA", + 10 + ], + [ + "BacCabcAc", + "CCbBC", + 12 + ], + [ + "BacCccBa", + "ABCBA", + 10 + ], + [ + "BacaB", + "bbac", + 7 + ], + [ + "BacaC", + "AAC", + 6 + ], + [ + "BacaCaBB", + "bB", + 13 + ], + [ + "BacaCbA", + "caBCAa", + 9 + ], + [ + "BacbB", + "CcaACc", + 10 + ], + [ + "BacbC", + "AAabcbbCB", + 10 + ], + [ + "BacbCbBca", + "cCcCC", + 13 + ], + [ + "BacbbCaAa", + "aCACaC", + 11 + ], + [ + "Bacc", + "b", + 7 + ], + [ + "BaccACa", + "B", + 12 + ], + [ + "BaccAaacb", + "A", + 16 + ], + [ + "BaccBcCBB", + "acb", + 13 + ], + [ + "BaccCB", + "BaC", + 6 + ], + [ + "Bacca", + "Ac", + 7 + ], + [ + "BaccaCC", + "CcbA", + 11 + ], + [ + "Baccb", + "aCc", + 5 + ], + [ + "Baccba", + "cbaaBcbc", + 9 + ], + [ + "Bb", + "AAABBB", + 9 + ], + [ + "Bb", + "AAccaCAaa", + 18 + ], + [ + "Bb", + "AaA", + 6 + ], + [ + "Bb", + "AaAAc", + 10 + ], + [ + "Bb", + "B", + 2 + ], + [ + "Bb", + "BACBbABA", + 12 + ], + [ + "Bb", + "BBaBb", + 6 + ], + [ + "Bb", + "BacCBA", + 9 + ], + [ + "Bb", + "BbCBCABAC", + 14 + ], + [ + "Bb", + "Bba", + 2 + ], + [ + "Bb", + "BcBAACA", + 11 + ], + [ + "Bb", + "CC", + 4 + ], + [ + "Bb", + "CcaaBa", + 10 + ], + [ + "Bb", + "a", + 4 + ], + [ + "Bb", + "aAbbb", + 7 + ], + [ + "Bb", + "aBa", + 4 + ], + [ + "Bb", + "aBbcCbAAC", + 14 + ], + [ + "Bb", + "aCCAB", + 9 + ], + [ + "Bb", + "aCccacCB", + 15 + ], + [ + "Bb", + "aacA", + 8 + ], + [ + "Bb", + "abBaBbAa", + 12 + ], + [ + "Bb", + "b", + 2 + ], + [ + "Bb", + "bAcCbc", + 9 + ], + [ + "Bb", + "bBaCBc", + 9 + ], + [ + "Bb", + "bBbBCccc", + 12 + ], + [ + "Bb", + "bCCc", + 7 + ], + [ + "Bb", + "bCaBcC", + 10 + ], + [ + "Bb", + "bCcbbc", + 9 + ], + [ + "Bb", + "baB", + 4 + ], + [ + "Bb", + "bc", + 3 + ], + [ + "Bb", + "bcCaacCC", + 15 + ], + [ + "Bb", + "bcaBccAcb", + 14 + ], + [ + "Bb", + "cab", + 4 + ], + [ + "Bb", + "cbAACCBb", + 12 + ], + [ + "Bb", + "cbabCcbb", + 13 + ], + [ + "Bb", + "cc", + 4 + ], + [ + "BbA", + "BCaAcABab", + 14 + ], + [ + "BbA", + "C", + 6 + ], + [ + "BbA", + "aacCbAc", + 10 + ], + [ + "BbA", + "cbCcbaAAA", + 13 + ], + [ + "BbAAaA", + "AbbBccC", + 11 + ], + [ + "BbAAaA", + "bBc", + 10 + ], + [ + "BbABB", + "BBAACAa", + 9 + ], + [ + "BbABBbAa", + "acaCCbbcB", + 14 + ], + [ + "BbABC", + "c", + 9 + ], + [ + "BbABa", + "accBbBcBb", + 12 + ], + [ + "BbABaacaA", + "b", + 16 + ], + [ + "BbAC", + "CaCCBcAa", + 12 + ], + [ + "BbAC", + "caAcaab", + 11 + ], + [ + "BbACAbcB", + "aaaAB", + 11 + ], + [ + "BbAa", + "ACcA", + 7 + ], + [ + "BbAa", + "cCA", + 6 + ], + [ + "BbAaAaaa", + "cBbbcCB", + 14 + ], + [ + "BbAaCab", + "cA", + 12 + ], + [ + "BbAaa", + "Bbbcacaa", + 7 + ], + [ + "BbAac", + "b", + 8 + ], + [ + "BbAb", + "CccCbabaC", + 13 + ], + [ + "BbAbC", + "CABA", + 7 + ], + [ + "BbAbC", + "ccA", + 8 + ], + [ + "BbAbCab", + "bcabABC", + 10 + ], + [ + "BbAbCcBab", + "CAaAbcbAC", + 12 + ], + [ + "BbAbCcaAC", + "ABbcCa", + 11 + ], + [ + "BbAbbcbb", + "B", + 14 + ], + [ + "BbAbc", + "aABB", + 7 + ], + [ + "BbAcaaaa", + "bBccaac", + 8 + ], + [ + "BbAcbca", + "acCCcbBCB", + 13 + ], + [ + "BbB", + "ACbcc", + 8 + ], + [ + "BbB", + "Acaca", + 10 + ], + [ + "BbB", + "bBcb", + 5 + ], + [ + "BbBA", + "CA", + 6 + ], + [ + "BbBABCcA", + "aaC", + 13 + ], + [ + "BbBAaBa", + "b", + 12 + ], + [ + "BbBAb", + "aaCCcb", + 10 + ], + [ + "BbBAbABAc", + "cBbbBCcB", + 12 + ], + [ + "BbBBCbca", + "cbBcCbC", + 7 + ], + [ + "BbBBa", + "cC", + 10 + ], + [ + "BbBBaBBba", + "bAC", + 15 + ], + [ + "BbBBabAb", + "cbB", + 12 + ], + [ + "BbBC", + "cbbbcaA", + 9 + ], + [ + "BbBCAbb", + "baCCbBbb", + 9 + ], + [ + "BbBCbAb", + "BbCcBbbac", + 9 + ], + [ + "BbBa", + "aCaaaA", + 10 + ], + [ + "BbBa", + "caCcb", + 10 + ], + [ + "BbBaC", + "BA", + 7 + ], + [ + "BbBaCbCcB", + "baAbCbaCc", + 11 + ], + [ + "BbBaaCBbc", + "bcAAAbC", + 11 + ], + [ + "BbBacBCca", + "c", + 16 + ], + [ + "BbBbA", + "BbaaA", + 4 + ], + [ + "BbBbBAb", + "CbcCbAaCc", + 13 + ], + [ + "BbBbBa", + "A", + 11 + ], + [ + "BbBbBbC", + "cBbacbBa", + 10 + ], + [ + "BbBbbbBAb", + "AabCcBa", + 13 + ], + [ + "BbBcBc", + "bcaaC", + 9 + ], + [ + "BbBcaAC", + "cB", + 12 + ], + [ + "BbBcabAa", + "BaBAaACc", + 10 + ], + [ + "BbBcbbC", + "bCBAabbaa", + 11 + ], + [ + "BbC", + "A", + 6 + ], + [ + "BbC", + "Cc", + 5 + ], + [ + "BbC", + "aAAAc", + 9 + ], + [ + "BbC", + "aAAbca", + 9 + ], + [ + "BbC", + "bAaBabC", + 8 + ], + [ + "BbC", + "bBCcCa", + 8 + ], + [ + "BbC", + "baCACBbc", + 11 + ], + [ + "BbC", + "bcBccBcBa", + 14 + ], + [ + "BbCAACcac", + "AcCbB", + 14 + ], + [ + "BbCAACcb", + "b", + 14 + ], + [ + "BbCAaCaC", + "BBc", + 12 + ], + [ + "BbCAbc", + "aaa", + 11 + ], + [ + "BbCAcCaCC", + "CCcB", + 13 + ], + [ + "BbCAcaab", + "BCbACaABc", + 9 + ], + [ + "BbCBCB", + "bAAccbCc", + 11 + ], + [ + "BbCBCbAc", + "CcACA", + 11 + ], + [ + "BbCCAAab", + "ccCCC", + 12 + ], + [ + "BbCCABABa", + "Acaab", + 14 + ], + [ + "BbCCBBb", + "aacCb", + 9 + ], + [ + "BbCCCBCC", + "BCA", + 12 + ], + [ + "BbCCa", + "aaaCAc", + 9 + ], + [ + "BbCCcCcCc", + "AaAAA", + 18 + ], + [ + "BbCCcbc", + "BaaBCc", + 9 + ], + [ + "BbCa", + "aCC", + 6 + ], + [ + "BbCaB", + "A", + 9 + ], + [ + "BbCaBAA", + "AbAABc", + 9 + ], + [ + "BbCaCbACB", + "AAA", + 15 + ], + [ + "BbCaCcC", + "aAAcA", + 11 + ], + [ + "BbCaCcaC", + "A", + 15 + ], + [ + "BbCbC", + "bAACAAcAC", + 13 + ], + [ + "BbCbCCb", + "AbBAA", + 11 + ], + [ + "BbCbaAB", + "C", + 12 + ], + [ + "BbCbaB", + "aAcC", + 11 + ], + [ + "BbCbaBB", + "Cabb", + 8 + ], + [ + "BbCbbBc", + "b", + 12 + ], + [ + "BbCbbb", + "cCcAcBAcA", + 16 + ], + [ + "BbCbcc", + "AAACbaaAa", + 14 + ], + [ + "BbCc", + "A", + 8 + ], + [ + "BbCc", + "aac", + 6 + ], + [ + "BbCcCACB", + "cB", + 12 + ], + [ + "BbCcaBBC", + "CB", + 12 + ], + [ + "BbCccBCB", + "cCcbBb", + 9 + ], + [ + "Bba", + "ABBBcACb", + 12 + ], + [ + "Bba", + "AbAcAbCcA", + 14 + ], + [ + "Bba", + "CaBCAaaaB", + 14 + ], + [ + "Bba", + "CacBBCBA", + 12 + ], + [ + "Bba", + "aBAcbCC", + 10 + ], + [ + "Bba", + "aCCcAaAc", + 14 + ], + [ + "Bba", + "abcA", + 5 + ], + [ + "Bba", + "bcCa", + 5 + ], + [ + "BbaAB", + "BbB", + 4 + ], + [ + "BbaABacc", + "bccbc", + 11 + ], + [ + "BbaAbBCAA", + "caAcCbc", + 12 + ], + [ + "BbaAcAb", + "bacACCbC", + 9 + ], + [ + "BbaB", + "aBbAaCb", + 7 + ], + [ + "BbaB", + "bBbBAbBC", + 9 + ], + [ + "BbaBCB", + "bcA", + 9 + ], + [ + "BbaBcbacc", + "CCBAC", + 14 + ], + [ + "BbaC", + "C", + 6 + ], + [ + "BbaCBC", + "C", + 10 + ], + [ + "BbaCCCb", + "Cca", + 11 + ], + [ + "BbaCa", + "AcCC", + 8 + ], + [ + "BbaCa", + "ccbA", + 9 + ], + [ + "BbaCcBba", + "ACb", + 11 + ], + [ + "BbaCcC", + "cbCCAAbcc", + 11 + ], + [ + "Bbaa", + "abbaAA", + 6 + ], + [ + "BbaaAbAA", + "Acc", + 14 + ], + [ + "BbaaBAb", + "CBbaCbaBB", + 9 + ], + [ + "BbaacA", + "ccAbCabAa", + 12 + ], + [ + "BbaacBcbC", + "cabbCaC", + 12 + ], + [ + "Bbab", + "BCBb", + 4 + ], + [ + "BbabA", + "Cbc", + 8 + ], + [ + "BbabBc", + "AcA", + 11 + ], + [ + "BbabC", + "ab", + 6 + ], + [ + "BbabCcBA", + "AabACBc", + 9 + ], + [ + "Bbaba", + "AAa", + 7 + ], + [ + "BbabbBb", + "b", + 12 + ], + [ + "Bbac", + "bbb", + 5 + ], + [ + "BbacB", + "CBcBB", + 7 + ], + [ + "BbacCc", + "Cb", + 10 + ], + [ + "Bbacaa", + "AaAaac", + 8 + ], + [ + "Bbb", + "ACCCCc", + 12 + ], + [ + "Bbb", + "BCABAa", + 9 + ], + [ + "Bbb", + "BabbbBcCA", + 12 + ], + [ + "Bbb", + "acaCCaa", + 14 + ], + [ + "Bbb", + "bAAcBa", + 10 + ], + [ + "Bbb", + "ccacA", + 10 + ], + [ + "BbbA", + "ACccA", + 8 + ], + [ + "BbbAAB", + "baBAb", + 7 + ], + [ + "BbbABccc", + "AbaABca", + 8 + ], + [ + "BbbAC", + "bcAaBBBca", + 14 + ], + [ + "BbbAaAba", + "a", + 14 + ], + [ + "BbbB", + "Bcc", + 6 + ], + [ + "BbbBCCA", + "cBBb", + 11 + ], + [ + "BbbBCba", + "BA", + 11 + ], + [ + "BbbBcaa", + "b", + 12 + ], + [ + "BbbCAaA", + "cbbBaBaCA", + 9 + ], + [ + "BbbCAb", + "AACC", + 10 + ], + [ + "BbbCAc", + "cbaB", + 9 + ], + [ + "BbbCBcac", + "bAbaBCba", + 10 + ], + [ + "BbbCCB", + "C", + 10 + ], + [ + "BbbCCBAc", + "cbbBcCCAC", + 8 + ], + [ + "Bbba", + "AAaAB", + 9 + ], + [ + "Bbba", + "abAc", + 6 + ], + [ + "BbbaAC", + "aBABcAAaB", + 12 + ], + [ + "BbbaC", + "AbBaaAaA", + 11 + ], + [ + "BbbaabCCA", + "CbcAb", + 13 + ], + [ + "BbbacCbb", + "AaccA", + 11 + ], + [ + "BbbbCAaA", + "cACCaCcAC", + 15 + ], + [ + "BbbbbAc", + "AbbcCb", + 10 + ], + [ + "BbbbcA", + "AaCcBabb", + 14 + ], + [ + "BbbbcbbA", + "bAA", + 12 + ], + [ + "Bbbc", + "CbccaAc", + 10 + ], + [ + "BbbcABb", + "bbbcc", + 7 + ], + [ + "BbbcB", + "bbacbbc", + 8 + ], + [ + "BbbcbAcbB", + "aCaCbaAc", + 13 + ], + [ + "Bbbcbcab", + "caCaAAbc", + 14 + ], + [ + "BbcA", + "CBaA", + 5 + ], + [ + "BbcA", + "CBcAB", + 5 + ], + [ + "BbcABcB", + "AcA", + 10 + ], + [ + "BbcACB", + "BaaCCBBC", + 10 + ], + [ + "BbcACCcB", + "bcbbaaBba", + 14 + ], + [ + "BbcAbCa", + "CbbaCba", + 9 + ], + [ + "BbcAcaB", + "Aca", + 8 + ], + [ + "BbcAccCab", + "abCCaA", + 11 + ], + [ + "BbcB", + "aACCccCaA", + 16 + ], + [ + "BbcBbB", + "baacAca", + 11 + ], + [ + "BbcBbbaC", + "Ab", + 14 + ], + [ + "BbcC", + "AccAcaBaC", + 14 + ], + [ + "BbcCB", + "bBca", + 6 + ], + [ + "BbcCBaB", + "AcCcaCbb", + 11 + ], + [ + "BbcCaab", + "cACaC", + 10 + ], + [ + "BbcaBbCCb", + "acBBcc", + 11 + ], + [ + "BbcaCcaa", + "AbcccaBB", + 9 + ], + [ + "BbcaaACb", + "A", + 14 + ], + [ + "BbcbACAa", + "a", + 14 + ], + [ + "BbcbBCCA", + "AcaCB", + 12 + ], + [ + "BbcbCcCCc", + "a", + 18 + ], + [ + "Bbcbaa", + "a", + 10 + ], + [ + "BbcbbbBab", + "aaBcBcCB", + 14 + ], + [ + "BbcbccB", + "bAACb", + 10 + ], + [ + "Bbcc", + "caAACBBb", + 15 + ], + [ + "BbccBcb", + "BCAbccb", + 8 + ], + [ + "BbccC", + "CaaAC", + 8 + ], + [ + "Bbcca", + "bBcb", + 6 + ], + [ + "BbccccCC", + "aAbcc", + 12 + ], + [ + "Bc", + "A", + 4 + ], + [ + "Bc", + "AB", + 4 + ], + [ + "Bc", + "ABAccAcC", + 12 + ], + [ + "Bc", + "ABBbBBbb", + 14 + ], + [ + "Bc", + "AaA", + 6 + ], + [ + "Bc", + "AaaBaCcCc", + 14 + ], + [ + "Bc", + "AabBBC", + 9 + ], + [ + "Bc", + "Aac", + 4 + ], + [ + "Bc", + "Aba", + 5 + ], + [ + "Bc", + "Abccbabbc", + 15 + ], + [ + "Bc", + "AcAbBC", + 9 + ], + [ + "Bc", + "B", + 2 + ], + [ + "Bc", + "BACaACBC", + 13 + ], + [ + "Bc", + "BBcBCbCB", + 12 + ], + [ + "Bc", + "BcBAAAaA", + 12 + ], + [ + "Bc", + "BcaaABCcc", + 14 + ], + [ + "Bc", + "C", + 3 + ], + [ + "Bc", + "CBCccCbaA", + 14 + ], + [ + "Bc", + "CCaBABaBA", + 16 + ], + [ + "Bc", + "CaBCBbBbc", + 14 + ], + [ + "Bc", + "CbaBCbb", + 11 + ], + [ + "Bc", + "a", + 4 + ], + [ + "Bc", + "aC", + 3 + ], + [ + "Bc", + "abAbBBcc", + 12 + ], + [ + "Bc", + "abaCAABA", + 14 + ], + [ + "Bc", + "abaabAbc", + 13 + ], + [ + "Bc", + "abcbBBB", + 11 + ], + [ + "Bc", + "ac", + 2 + ], + [ + "Bc", + "acCCcb", + 10 + ], + [ + "Bc", + "acCacaaAa", + 16 + ], + [ + "Bc", + "ba", + 3 + ], + [ + "Bc", + "cC", + 3 + ], + [ + "Bc", + "cb", + 4 + ], + [ + "Bc", + "cbACCabca", + 15 + ], + [ + "BcA", + "AC", + 5 + ], + [ + "BcA", + "BAaB", + 5 + ], + [ + "BcA", + "BBc", + 4 + ], + [ + "BcA", + "CBababB", + 11 + ], + [ + "BcA", + "CcB", + 4 + ], + [ + "BcA", + "CcCb", + 6 + ], + [ + "BcA", + "a", + 5 + ], + [ + "BcA", + "bAcab", + 6 + ], + [ + "BcA", + "bBAbb", + 7 + ], + [ + "BcA", + "bbbc", + 7 + ], + [ + "BcA", + "c", + 4 + ], + [ + "BcAABaB", + "bACcAc", + 10 + ], + [ + "BcAAaA", + "BcCAbaBb", + 8 + ], + [ + "BcAB", + "aBCCcACc", + 10 + ], + [ + "BcABAc", + "BaAa", + 7 + ], + [ + "BcABaAaA", + "aCCaCcb", + 13 + ], + [ + "BcABaa", + "CaaBBaa", + 7 + ], + [ + "BcAC", + "BCAbcbCAa", + 11 + ], + [ + "BcACAA", + "aCb", + 9 + ], + [ + "BcACB", + "bCcC", + 6 + ], + [ + "BcACBaBCa", + "BCcBAbACc", + 10 + ], + [ + "BcACC", + "aa", + 9 + ], + [ + "BcAa", + "Cb", + 7 + ], + [ + "BcAaA", + "CBBc", + 9 + ], + [ + "BcAaA", + "abBbBBcB", + 14 + ], + [ + "BcAaACA", + "cbCABAaCb", + 10 + ], + [ + "BcAaCCBB", + "cBbCaBbA", + 11 + ], + [ + "BcAaCbB", + "ACB", + 8 + ], + [ + "BcAaaaC", + "cC", + 10 + ], + [ + "BcAb", + "BB", + 5 + ], + [ + "BcAb", + "CC", + 7 + ], + [ + "BcAbAbCac", + "bab", + 13 + ], + [ + "BcAbB", + "B", + 8 + ], + [ + "BcAbabBA", + "acb", + 12 + ], + [ + "BcAbabBBB", + "BA", + 14 + ], + [ + "BcAbabC", + "BBbaCAB", + 10 + ], + [ + "BcAbbcC", + "AaAcbC", + 8 + ], + [ + "BcAbc", + "BAC", + 5 + ], + [ + "BcAbc", + "BbbcbBabB", + 11 + ], + [ + "BcAbcAAC", + "A", + 14 + ], + [ + "BcAc", + "bC", + 6 + ], + [ + "BcAcAaACb", + "aAbBBB", + 14 + ], + [ + "BcAcB", + "caaAbbccB", + 12 + ], + [ + "BcAcaccb", + "cbAbAb", + 11 + ], + [ + "BcAcba", + "aA", + 10 + ], + [ + "BcAcc", + "BCaCBcAcC", + 9 + ], + [ + "BcB", + "AcCCAC", + 10 + ], + [ + "BcB", + "BBAcaAaB", + 10 + ], + [ + "BcB", + "BabAcacCA", + 14 + ], + [ + "BcB", + "aaBcAb", + 7 + ], + [ + "BcB", + "abcC", + 5 + ], + [ + "BcB", + "bcBBCbab", + 11 + ], + [ + "BcB", + "cAAc", + 8 + ], + [ + "BcB", + "ca", + 4 + ], + [ + "BcB", + "caBaaAAb", + 13 + ], + [ + "BcBA", + "CAabC", + 9 + ], + [ + "BcBA", + "baaC", + 7 + ], + [ + "BcBAAc", + "cBBaaabBB", + 14 + ], + [ + "BcBAB", + "aCaAaBb", + 9 + ], + [ + "BcBABCB", + "aaB", + 11 + ], + [ + "BcBAccAc", + "c", + 14 + ], + [ + "BcBBBA", + "ACc", + 11 + ], + [ + "BcBBC", + "cCABb", + 7 + ], + [ + "BcBBbb", + "bbBAa", + 8 + ], + [ + "BcBBbcAaA", + "CA", + 15 + ], + [ + "BcBC", + "c", + 6 + ], + [ + "BcBCA", + "cAaCC", + 8 + ], + [ + "BcBCAAAcc", + "abc", + 15 + ], + [ + "BcBCBBC", + "BbC", + 9 + ], + [ + "BcBCbAc", + "cBBCaC", + 8 + ], + [ + "BcBCcbccc", + "BcBC", + 10 + ], + [ + "BcBCccAa", + "acb", + 13 + ], + [ + "BcBa", + "aCB", + 5 + ], + [ + "BcBaAcAAc", + "bccBBBcBc", + 11 + ], + [ + "BcBaC", + "acBbcABAa", + 11 + ], + [ + "BcBab", + "CbCACC", + 10 + ], + [ + "BcBb", + "acAABAA", + 10 + ], + [ + "BcBbA", + "Ccba", + 5 + ], + [ + "BcBbAbABB", + "a", + 17 + ], + [ + "BcBbbCAa", + "baACcBCcC", + 14 + ], + [ + "BcBbbCa", + "AcCBAa", + 9 + ], + [ + "BcBbc", + "CcABb", + 6 + ], + [ + "BcBc", + "cBBcc", + 6 + ], + [ + "BcBcAbbc", + "babC", + 11 + ], + [ + "BcBcBc", + "cAAc", + 8 + ], + [ + "BcC", + "ABabcbC", + 8 + ], + [ + "BcC", + "Ac", + 4 + ], + [ + "BcC", + "C", + 4 + ], + [ + "BcC", + "acbcaAaa", + 13 + ], + [ + "BcC", + "bAbB", + 7 + ], + [ + "BcCA", + "ccaBaaAac", + 14 + ], + [ + "BcCAABBB", + "BCabBB", + 6 + ], + [ + "BcCAaAC", + "acCaB", + 8 + ], + [ + "BcCAaBc", + "Bcab", + 7 + ], + [ + "BcCAbB", + "ACaccCB", + 10 + ], + [ + "BcCAbaACc", + "aCAAcCBC", + 12 + ], + [ + "BcCAbcbA", + "bccAbCC", + 7 + ], + [ + "BcCAcA", + "BbB", + 10 + ], + [ + "BcCBABA", + "accb", + 10 + ], + [ + "BcCBAaC", + "b", + 13 + ], + [ + "BcCBbabcc", + "cCb", + 12 + ], + [ + "BcCBc", + "ba", + 9 + ], + [ + "BcCBcaAcc", + "B", + 16 + ], + [ + "BcCBcaCbC", + "CA", + 15 + ], + [ + "BcCCAA", + "caaA", + 7 + ], + [ + "BcCCBaaa", + "AaaCBb", + 12 + ], + [ + "BcCCCAA", + "b", + 13 + ], + [ + "BcCCaaaA", + "BccaAabBa", + 9 + ], + [ + "BcCCbCac", + "AcAa", + 12 + ], + [ + "BcCa", + "AB", + 8 + ], + [ + "BcCa", + "cB", + 6 + ], + [ + "BcCaACC", + "cbACaA", + 9 + ], + [ + "BcCaACCc", + "aaaCb", + 11 + ], + [ + "BcCaBbCAC", + "AbaAc", + 12 + ], + [ + "BcCaBc", + "CCAAB", + 8 + ], + [ + "BcCaCacc", + "CaCbbbc", + 10 + ], + [ + "BcCaaC", + "AcC", + 8 + ], + [ + "BcCb", + "aBacB", + 6 + ], + [ + "BcCbAcCb", + "CAA", + 12 + ], + [ + "BcCbCCCa", + "CBCC", + 9 + ], + [ + "BcCbaBabc", + "abcbB", + 13 + ], + [ + "BcCbbb", + "ccBCb", + 6 + ], + [ + "BcCc", + "bBb", + 7 + ], + [ + "BcCcBBb", + "aCCC", + 10 + ], + [ + "BcCcBcAb", + "aacbBbC", + 13 + ], + [ + "BcCca", + "CCcCaaB", + 8 + ], + [ + "Bca", + "AcbCBab", + 10 + ], + [ + "Bca", + "Bb", + 4 + ], + [ + "Bca", + "Bcc", + 2 + ], + [ + "Bca", + "CAAbA", + 9 + ], + [ + "Bca", + "CcBc", + 6 + ], + [ + "Bca", + "aAAccCBB", + 14 + ], + [ + "Bca", + "aCbbBBcCb", + 14 + ], + [ + "Bca", + "ac", + 4 + ], + [ + "Bca", + "c", + 4 + ], + [ + "BcaAAbACa", + "cAaacBB", + 13 + ], + [ + "BcaAaC", + "B", + 10 + ], + [ + "BcaB", + "A", + 7 + ], + [ + "BcaB", + "ABC", + 7 + ], + [ + "BcaB", + "AcAacA", + 8 + ], + [ + "BcaB", + "CAba", + 7 + ], + [ + "BcaB", + "cc", + 6 + ], + [ + "BcaBACC", + "aabAcbb", + 10 + ], + [ + "BcaBACbA", + "cB", + 12 + ], + [ + "BcaBAc", + "CAcBaCC", + 10 + ], + [ + "BcaBb", + "bABA", + 6 + ], + [ + "BcaBcab", + "aAaA", + 10 + ], + [ + "BcaC", + "abbAb", + 8 + ], + [ + "BcaC", + "ba", + 5 + ], + [ + "BcaC", + "ccaCBCB", + 8 + ], + [ + "BcaCBAbA", + "BA", + 12 + ], + [ + "BcaCC", + "CAbBbB", + 12 + ], + [ + "BcaCCbb", + "bbb", + 9 + ], + [ + "BcaCc", + "AaCABAACA", + 13 + ], + [ + "Bcaa", + "C", + 7 + ], + [ + "Bcaa", + "CbBABaa", + 8 + ], + [ + "Bcaa", + "cbbBcc", + 10 + ], + [ + "BcaaAc", + "AcaCCb", + 8 + ], + [ + "BcaaBBa", + "bBBCAAb", + 12 + ], + [ + "BcaaBcB", + "cCBbbAAcA", + 13 + ], + [ + "Bcaaba", + "ACbcA", + 10 + ], + [ + "Bcaabc", + "Bba", + 8 + ], + [ + "BcaacbcC", + "CBBBbCCA", + 12 + ], + [ + "Bcab", + "AcABabB", + 8 + ], + [ + "Bcab", + "abA", + 6 + ], + [ + "BcabB", + "BcbCc", + 6 + ], + [ + "BcabBB", + "BbAbbbA", + 7 + ], + [ + "BcabCA", + "b", + 10 + ], + [ + "BcabCac", + "BbAAaca", + 9 + ], + [ + "Bcaba", + "aCcbA", + 6 + ], + [ + "BcabaABA", + "CAbB", + 10 + ], + [ + "Bcac", + "CcCAbaaa", + 12 + ], + [ + "Bcac", + "ccc", + 4 + ], + [ + "BcacAC", + "abBc", + 9 + ], + [ + "BcacAaca", + "acbAbc", + 10 + ], + [ + "BcacCB", + "cbabacC", + 9 + ], + [ + "BcacaBC", + "ACCac", + 9 + ], + [ + "BcacabbA", + "Bcb", + 10 + ], + [ + "Bcacc", + "b", + 9 + ], + [ + "Bcb", + "AaAcacaB", + 13 + ], + [ + "Bcb", + "Cc", + 4 + ], + [ + "Bcb", + "aBBC", + 6 + ], + [ + "BcbAACc", + "aBBcA", + 11 + ], + [ + "BcbAAb", + "ab", + 9 + ], + [ + "BcbAC", + "AcC", + 6 + ], + [ + "BcbAaaacc", + "cc", + 14 + ], + [ + "BcbB", + "BbcbBbBC", + 8 + ], + [ + "BcbB", + "aB", + 6 + ], + [ + "BcbBcB", + "bCbb", + 7 + ], + [ + "BcbCA", + "bCc", + 6 + ], + [ + "BcbCAAba", + "CCaa", + 10 + ], + [ + "BcbCBCA", + "BcBa", + 7 + ], + [ + "BcbCC", + "b", + 8 + ], + [ + "BcbCCcc", + "Abb", + 12 + ], + [ + "BcbCabC", + "A", + 13 + ], + [ + "BcbCcB", + "cbaccCa", + 9 + ], + [ + "Bcba", + "ACa", + 5 + ], + [ + "Bcbb", + "baaAa", + 9 + ], + [ + "BcbbA", + "BBb", + 5 + ], + [ + "BcbbC", + "ccaBAABCC", + 12 + ], + [ + "BcbcA", + "bcBcAAcB", + 8 + ], + [ + "BcbcB", + "Ccc", + 6 + ], + [ + "Bcc", + "Ab", + 6 + ], + [ + "Bcc", + "BCAbacBc", + 10 + ], + [ + "Bcc", + "aacb", + 6 + ], + [ + "Bcc", + "baAa", + 7 + ], + [ + "BccABAbcA", + "Aba", + 13 + ], + [ + "BccACA", + "aa", + 10 + ], + [ + "BccAcAbbC", + "caC", + 13 + ], + [ + "BccBA", + "BACCAC", + 7 + ], + [ + "BccBAAbb", + "aac", + 14 + ], + [ + "BccBCbAA", + "BBCCC", + 10 + ], + [ + "BccBaAaa", + "AACAbbbbB", + 16 + ], + [ + "BccBac", + "CCabACa", + 10 + ], + [ + "BccC", + "ac", + 6 + ], + [ + "BccC", + "bCA", + 6 + ], + [ + "BccCa", + "caaBBaaB", + 14 + ], + [ + "BccCab", + "Cbcbac", + 8 + ], + [ + "BccCacBC", + "BBbA", + 13 + ], + [ + "BccCcA", + "BcCBCAB", + 6 + ], + [ + "Bcca", + "AAb", + 8 + ], + [ + "BccaA", + "cCC", + 7 + ], + [ + "BccaA", + "ccc", + 6 + ], + [ + "BccaBbB", + "AA", + 13 + ], + [ + "Bccac", + "CcbaCCa", + 9 + ], + [ + "Bccb", + "AcbC", + 6 + ], + [ + "BccbB", + "bbccA", + 7 + ], + [ + "BccbBCAA", + "AA", + 12 + ], + [ + "BccbBaCbb", + "CbcBBCAA", + 11 + ], + [ + "BccbacAAc", + "CAac", + 12 + ], + [ + "BccbbACa", + "acCAbCAc", + 10 + ], + [ + "BcccAC", + "cccBbBaac", + 12 + ], + [ + "BcccBB", + "bABCc", + 10 + ], + [ + "BcccBBb", + "Cbacbaa", + 11 + ], + [ + "C", + "A", + 2 + ], + [ + "C", + "AAA", + 6 + ], + [ + "C", + "AAAb", + 8 + ], + [ + "C", + "AAB", + 6 + ], + [ + "C", + "AAacba", + 11 + ], + [ + "C", + "AAbb", + 8 + ], + [ + "C", + "ABA", + 6 + ], + [ + "C", + "ABAB", + 8 + ], + [ + "C", + "ABAcBAabA", + 17 + ], + [ + "C", + "ABCB", + 6 + ], + [ + "C", + "ABCbbbBAC", + 16 + ], + [ + "C", + "ABabc", + 9 + ], + [ + "C", + "ACCCcAcAa", + 16 + ], + [ + "C", + "ACc", + 4 + ], + [ + "C", + "ACcbaABc", + 14 + ], + [ + "C", + "AaAAa", + 10 + ], + [ + "C", + "AaAa", + 8 + ], + [ + "C", + "AaAbcaBb", + 15 + ], + [ + "C", + "AabbbC", + 10 + ], + [ + "C", + "AacCCABaB", + 16 + ], + [ + "C", + "AacaaaB", + 13 + ], + [ + "C", + "Ab", + 4 + ], + [ + "C", + "AbBbBACab", + 16 + ], + [ + "C", + "AbBcCcBAb", + 16 + ], + [ + "C", + "AbC", + 4 + ], + [ + "C", + "AbCCaCbc", + 14 + ], + [ + "C", + "Abb", + 6 + ], + [ + "C", + "AbbCcBb", + 12 + ], + [ + "C", + "Acbc", + 7 + ], + [ + "C", + "B", + 2 + ], + [ + "C", + "BAAbAbCa", + 14 + ], + [ + "C", + "BAAc", + 7 + ], + [ + "C", + "BACcBbAcB", + 16 + ], + [ + "C", + "BBBaAcB", + 13 + ], + [ + "C", + "BBacaaA", + 13 + ], + [ + "C", + "BC", + 2 + ], + [ + "C", + "BCCbA", + 8 + ], + [ + "C", + "BCba", + 6 + ], + [ + "C", + "Ba", + 4 + ], + [ + "C", + "BaA", + 6 + ], + [ + "C", + "BaBCCBcb", + 14 + ], + [ + "C", + "BaBcbbCBA", + 16 + ], + [ + "C", + "BaaC", + 6 + ], + [ + "C", + "BabbcCBa", + 14 + ], + [ + "C", + "BacccCB", + 12 + ], + [ + "C", + "BbABc", + 9 + ], + [ + "C", + "BbAaAAC", + 12 + ], + [ + "C", + "BbBBcc", + 11 + ], + [ + "C", + "BbCBCB", + 10 + ], + [ + "C", + "BbcCCbAC", + 14 + ], + [ + "C", + "BcB", + 5 + ], + [ + "C", + "BcBBBbAaa", + 17 + ], + [ + "C", + "BcCAcacAB", + 16 + ], + [ + "C", + "BcCB", + 6 + ], + [ + "C", + "BcacCaB", + 12 + ], + [ + "C", + "BccAaa", + 11 + ], + [ + "C", + "BccaAa", + 11 + ], + [ + "C", + "C", + 0 + ], + [ + "C", + "CA", + 2 + ], + [ + "C", + "CAAbCa", + 10 + ], + [ + "C", + "CABBB", + 8 + ], + [ + "C", + "CAaAa", + 8 + ], + [ + "C", + "CAbAABb", + 12 + ], + [ + "C", + "CBBCaABbB", + 16 + ], + [ + "C", + "CBaacB", + 10 + ], + [ + "C", + "CBc", + 4 + ], + [ + "C", + "CC", + 2 + ], + [ + "C", + "CCA", + 4 + ], + [ + "C", + "CCBbccA", + 12 + ], + [ + "C", + "CCc", + 4 + ], + [ + "C", + "CCcbbCBCc", + 16 + ], + [ + "C", + "CCcc", + 6 + ], + [ + "C", + "CCccCaCa", + 14 + ], + [ + "C", + "Ca", + 2 + ], + [ + "C", + "CaA", + 4 + ], + [ + "C", + "Caab", + 6 + ], + [ + "C", + "Cacaacba", + 14 + ], + [ + "C", + "Cb", + 2 + ], + [ + "C", + "CbaCBcAb", + 14 + ], + [ + "C", + "CbbBCbC", + 12 + ], + [ + "C", + "Cc", + 2 + ], + [ + "C", + "CcAcCA", + 10 + ], + [ + "C", + "CcBBCbBB", + 14 + ], + [ + "C", + "CcCBBbB", + 12 + ], + [ + "C", + "a", + 2 + ], + [ + "C", + "aA", + 4 + ], + [ + "C", + "aAABBb", + 12 + ], + [ + "C", + "aACBaca", + 12 + ], + [ + "C", + "aAaCB", + 8 + ], + [ + "C", + "aAaaBcaB", + 15 + ], + [ + "C", + "aAabb", + 10 + ], + [ + "C", + "aAccAABB", + 15 + ], + [ + "C", + "aBAaa", + 10 + ], + [ + "C", + "aBbab", + 10 + ], + [ + "C", + "aCBccCbBc", + 16 + ], + [ + "C", + "aCCBcbcBc", + 16 + ], + [ + "C", + "aCCC", + 6 + ], + [ + "C", + "aCcB", + 6 + ], + [ + "C", + "aCcccA", + 10 + ], + [ + "C", + "aa", + 4 + ], + [ + "C", + "aaBA", + 8 + ], + [ + "C", + "aaBAbA", + 12 + ], + [ + "C", + "aaBabBB", + 14 + ], + [ + "C", + "aaCAC", + 8 + ], + [ + "C", + "aaCBb", + 8 + ], + [ + "C", + "aaaBcBBc", + 15 + ], + [ + "C", + "aaab", + 8 + ], + [ + "C", + "aab", + 6 + ], + [ + "C", + "aacCCabb", + 14 + ], + [ + "C", + "abBbAcaCc", + 16 + ], + [ + "C", + "abbbAaaC", + 14 + ], + [ + "C", + "abcBBB", + 11 + ], + [ + "C", + "ac", + 3 + ], + [ + "C", + "b", + 2 + ], + [ + "C", + "bAAAccb", + 13 + ], + [ + "C", + "bAAaCBaCa", + 16 + ], + [ + "C", + "bAaABACCb", + 16 + ], + [ + "C", + "bAbAcCAc", + 14 + ], + [ + "C", + "bBABA", + 10 + ], + [ + "C", + "bBB", + 6 + ], + [ + "C", + "bBacA", + 9 + ], + [ + "C", + "bBcbbCabb", + 16 + ], + [ + "C", + "bC", + 2 + ], + [ + "C", + "bCcAcACba", + 16 + ], + [ + "C", + "ba", + 4 + ], + [ + "C", + "baAA", + 8 + ], + [ + "C", + "baAba", + 10 + ], + [ + "C", + "baCABab", + 12 + ], + [ + "C", + "baaA", + 8 + ], + [ + "C", + "baaB", + 8 + ], + [ + "C", + "baaCbCb", + 12 + ], + [ + "C", + "bbABBCA", + 12 + ], + [ + "C", + "bbABcCb", + 12 + ], + [ + "C", + "bbCab", + 8 + ], + [ + "C", + "bbacCaabC", + 16 + ], + [ + "C", + "bbbcaAb", + 13 + ], + [ + "C", + "bc", + 3 + ], + [ + "C", + "bcAca", + 9 + ], + [ + "C", + "bcBAABAAC", + 16 + ], + [ + "C", + "bcBcA", + 9 + ], + [ + "C", + "bcC", + 4 + ], + [ + "C", + "bcCAb", + 8 + ], + [ + "C", + "bcaAb", + 9 + ], + [ + "C", + "bcacBAC", + 12 + ], + [ + "C", + "bcb", + 5 + ], + [ + "C", + "bcbBba", + 11 + ], + [ + "C", + "bcbacaAA", + 15 + ], + [ + "C", + "bccC", + 6 + ], + [ + "C", + "bccccCA", + 12 + ], + [ + "C", + "c", + 1 + ], + [ + "C", + "cAc", + 5 + ], + [ + "C", + "cAcBCbCb", + 14 + ], + [ + "C", + "cAcaABCA", + 14 + ], + [ + "C", + "cBAA", + 7 + ], + [ + "C", + "cBBB", + 7 + ], + [ + "C", + "cBc", + 5 + ], + [ + "C", + "cBccbAA", + 13 + ], + [ + "C", + "cC", + 2 + ], + [ + "C", + "cCBAAB", + 10 + ], + [ + "C", + "cCaACAAC", + 14 + ], + [ + "C", + "cCbA", + 6 + ], + [ + "C", + "cCbbABa", + 12 + ], + [ + "C", + "cCcabCbBb", + 16 + ], + [ + "C", + "ca", + 3 + ], + [ + "C", + "caCACCCaB", + 16 + ], + [ + "C", + "cabCa", + 8 + ], + [ + "C", + "cacACaBB", + 14 + ], + [ + "C", + "cacBaACc", + 14 + ], + [ + "C", + "cbACabc", + 12 + ], + [ + "C", + "cbCCB", + 8 + ], + [ + "C", + "cbcC", + 6 + ], + [ + "C", + "cc", + 3 + ], + [ + "C", + "ccBAbBBC", + 14 + ], + [ + "C", + "ccBaC", + 8 + ], + [ + "C", + "ccaCbbCbb", + 16 + ], + [ + "C", + "ccabC", + 8 + ], + [ + "C", + "ccbBbCCc", + 14 + ], + [ + "CA", + "A", + 2 + ], + [ + "CA", + "AAa", + 4 + ], + [ + "CA", + "Aaacbb", + 11 + ], + [ + "CA", + "AcC", + 5 + ], + [ + "CA", + "BBaB", + 7 + ], + [ + "CA", + "BcAcbBC", + 11 + ], + [ + "CA", + "C", + 2 + ], + [ + "CA", + "CBCAa", + 6 + ], + [ + "CA", + "CacbbBaa", + 13 + ], + [ + "CA", + "CcBcBAB", + 10 + ], + [ + "CA", + "CcbCaBB", + 11 + ], + [ + "CA", + "a", + 3 + ], + [ + "CA", + "aAABAaC", + 12 + ], + [ + "CA", + "aABBa", + 8 + ], + [ + "CA", + "abbCC", + 8 + ], + [ + "CA", + "acbb", + 7 + ], + [ + "CA", + "bAbAAcbA", + 13 + ], + [ + "CA", + "bAcBcACC", + 13 + ], + [ + "CA", + "bCBaBBaB", + 13 + ], + [ + "CA", + "bbbB", + 8 + ], + [ + "CA", + "cAaaaA", + 9 + ], + [ + "CA", + "caCBCcaaA", + 14 + ], + [ + "CAA", + "CCbc", + 6 + ], + [ + "CAA", + "CbCcba", + 9 + ], + [ + "CAA", + "bb", + 6 + ], + [ + "CAA", + "c", + 5 + ], + [ + "CAA", + "cACAaa", + 7 + ], + [ + "CAA", + "cAaabbacA", + 13 + ], + [ + "CAAA", + "bb", + 8 + ], + [ + "CAAAAAcCB", + "bC", + 16 + ], + [ + "CAAAB", + "ba", + 9 + ], + [ + "CAAABaBAc", + "CaaC", + 12 + ], + [ + "CAAACa", + "AAcbAAc", + 9 + ], + [ + "CAAACaAC", + "aABBcbcBa", + 15 + ], + [ + "CAAAaBAa", + "BCBc", + 14 + ], + [ + "CAAAba", + "CacBaB", + 8 + ], + [ + "CAAAbb", + "cCaCb", + 8 + ], + [ + "CAAB", + "CbcCbBBA", + 12 + ], + [ + "CAABAAcBC", + "bccb", + 14 + ], + [ + "CAABAAcbB", + "ABBa", + 13 + ], + [ + "CAABCaaBa", + "aBCACcBca", + 12 + ], + [ + "CAABCcAAa", + "ccCB", + 15 + ], + [ + "CAABbaA", + "AbBaAac", + 10 + ], + [ + "CAAC", + "cacABc", + 7 + ], + [ + "CAACAAC", + "Ab", + 12 + ], + [ + "CAACAbca", + "Bb", + 14 + ], + [ + "CAACCAc", + "CAAB", + 8 + ], + [ + "CAACb", + "CA", + 6 + ], + [ + "CAAa", + "cBCC", + 7 + ], + [ + "CAAaBaAC", + "AaccBBB", + 13 + ], + [ + "CAAaaA", + "CaAaACca", + 7 + ], + [ + "CAAab", + "b", + 8 + ], + [ + "CAAabac", + "Accbbc", + 8 + ], + [ + "CAAbCbCcC", + "B", + 17 + ], + [ + "CAAbb", + "caC", + 8 + ], + [ + "CAAbbAbb", + "BC", + 15 + ], + [ + "CAAcBB", + "aCBCbAAb", + 13 + ], + [ + "CAAccAcC", + "abA", + 13 + ], + [ + "CAB", + "ABabCcc", + 12 + ], + [ + "CAB", + "ABccbC", + 10 + ], + [ + "CAB", + "CABba", + 4 + ], + [ + "CAB", + "CcbB", + 4 + ], + [ + "CAB", + "aababa", + 10 + ], + [ + "CAB", + "bC", + 6 + ], + [ + "CAB", + "cAbbABAac", + 13 + ], + [ + "CABA", + "CaabCCc", + 10 + ], + [ + "CABAAa", + "abC", + 10 + ], + [ + "CABACacA", + "cAbaaABC", + 10 + ], + [ + "CABACcA", + "B", + 12 + ], + [ + "CABAa", + "aac", + 8 + ], + [ + "CABAcBa", + "b", + 13 + ], + [ + "CABBAa", + "bbBcabcC", + 13 + ], + [ + "CABBBAa", + "B", + 12 + ], + [ + "CABBBCaa", + "ACb", + 12 + ], + [ + "CABBBb", + "cacaAACa", + 14 + ], + [ + "CABC", + "cCbacA", + 9 + ], + [ + "CABCAcaC", + "B", + 14 + ], + [ + "CABa", + "B", + 6 + ], + [ + "CABaCc", + "BCaaaAAcb", + 11 + ], + [ + "CABab", + "baB", + 6 + ], + [ + "CABacBA", + "BaACb", + 10 + ], + [ + "CABb", + "bCcBaCba", + 10 + ], + [ + "CABbABcBA", + "aBaAccabA", + 10 + ], + [ + "CABbbC", + "CbACCAcc", + 11 + ], + [ + "CABcAaAb", + "bb", + 13 + ], + [ + "CABcaa", + "C", + 10 + ], + [ + "CABcaa", + "aCaBABAab", + 10 + ], + [ + "CABcabCAa", + "AcbaBcCA", + 11 + ], + [ + "CABcbCAc", + "ABAcc", + 9 + ], + [ + "CABcbbA", + "BBbcA", + 8 + ], + [ + "CABcbcacA", + "cb", + 14 + ], + [ + "CAC", + "aCAAaABb", + 12 + ], + [ + "CAC", + "abCCACaBc", + 12 + ], + [ + "CAC", + "bbcbB", + 9 + ], + [ + "CAC", + "caBAa", + 7 + ], + [ + "CAC", + "ccBaBcb", + 11 + ], + [ + "CACAACccb", + "bc", + 16 + ], + [ + "CACABBA", + "cCACAcCcC", + 10 + ], + [ + "CACACAcBB", + "aBAACCc", + 12 + ], + [ + "CACAabc", + "cbAaBbbbB", + 13 + ], + [ + "CACAcaAC", + "ACcACc", + 8 + ], + [ + "CACAcbC", + "Cc", + 10 + ], + [ + "CACB", + "Cbc", + 5 + ], + [ + "CACBb", + "C", + 8 + ], + [ + "CACC", + "bCa", + 6 + ], + [ + "CACCAAB", + "bACabAC", + 8 + ], + [ + "CACCAb", + "abBBB", + 10 + ], + [ + "CACCCBA", + "cabaac", + 12 + ], + [ + "CACCc", + "ba", + 9 + ], + [ + "CACaAcc", + "acaBbCbb", + 13 + ], + [ + "CACaCBBc", + "a", + 14 + ], + [ + "CACaabcBb", + "aCabba", + 10 + ], + [ + "CACacaABb", + "b", + 16 + ], + [ + "CACb", + "ABbb", + 6 + ], + [ + "CACb", + "AabbaCa", + 11 + ], + [ + "CACbA", + "bCbbC", + 8 + ], + [ + "CACbaCB", + "caacaaC", + 9 + ], + [ + "CACbaaCa", + "bBacacB", + 12 + ], + [ + "CACbbBBc", + "Bc", + 12 + ], + [ + "CACbc", + "Cc", + 6 + ], + [ + "CACbcAcac", + "cCAcA", + 10 + ], + [ + "CACbcba", + "caABaCb", + 10 + ], + [ + "CACcBabCc", + "baAbbaBB", + 13 + ], + [ + "CACcCacC", + "ABcC", + 10 + ], + [ + "CACccCAC", + "B", + 16 + ], + [ + "CAa", + "Ab", + 4 + ], + [ + "CAa", + "CabcCc", + 9 + ], + [ + "CAa", + "aaCAbaBc", + 10 + ], + [ + "CAa", + "bABABaCA", + 12 + ], + [ + "CAa", + "bBAaB", + 6 + ], + [ + "CAa", + "bca", + 4 + ], + [ + "CAa", + "cbC", + 5 + ], + [ + "CAaAAaA", + "abAbbcbaC", + 14 + ], + [ + "CAaAAbaaB", + "CAcBB", + 11 + ], + [ + "CAaAcb", + "bBCBB", + 11 + ], + [ + "CAaBAA", + "AacAcBABB", + 11 + ], + [ + "CAaBB", + "Cc", + 8 + ], + [ + "CAaBBACa", + "CAaacBccC", + 9 + ], + [ + "CAaBC", + "b", + 9 + ], + [ + "CAaBbbaBA", + "BBB", + 13 + ], + [ + "CAaC", + "AaBCacAba", + 14 + ], + [ + "CAaC", + "BcccCbBb", + 13 + ], + [ + "CAaCBbcab", + "CbC", + 13 + ], + [ + "CAaCc", + "AA", + 7 + ], + [ + "CAaCcabB", + "cBABCb", + 11 + ], + [ + "CAaCccCc", + "A", + 14 + ], + [ + "CAaa", + "bcABBaAb", + 10 + ], + [ + "CAaa", + "cbabABAC", + 12 + ], + [ + "CAaaB", + "b", + 9 + ], + [ + "CAaabBcC", + "a", + 14 + ], + [ + "CAaabbCac", + "bAC", + 14 + ], + [ + "CAaabcaB", + "cabCacbaA", + 12 + ], + [ + "CAabABbAa", + "bcbaCa", + 13 + ], + [ + "CAabac", + "baaABbB", + 10 + ], + [ + "CAabb", + "b", + 8 + ], + [ + "CAabb", + "cACACBaBB", + 10 + ], + [ + "CAac", + "aaB", + 5 + ], + [ + "CAac", + "cccBB", + 9 + ], + [ + "CAacAB", + "a", + 10 + ], + [ + "CAaccC", + "cacbc", + 6 + ], + [ + "CAb", + "ACCCAcaA", + 12 + ], + [ + "CAb", + "Aca", + 6 + ], + [ + "CAb", + "BCcBBAA", + 10 + ], + [ + "CAb", + "C", + 4 + ], + [ + "CAb", + "CCAcbAB", + 8 + ], + [ + "CAb", + "CcCbCBC", + 10 + ], + [ + "CAb", + "acAc", + 5 + ], + [ + "CAb", + "bab", + 3 + ], + [ + "CAb", + "cA", + 3 + ], + [ + "CAbAAaAA", + "abc", + 13 + ], + [ + "CAbAc", + "ABBCCB", + 10 + ], + [ + "CAbB", + "acC", + 7 + ], + [ + "CAbBBBB", + "BCcaBbabB", + 10 + ], + [ + "CAbBCCBAC", + "baA", + 14 + ], + [ + "CAbBab", + "cbABcBc", + 10 + ], + [ + "CAbBc", + "bBcAcCA", + 11 + ], + [ + "CAbBc", + "bCAcbba", + 7 + ], + [ + "CAbC", + "AaAc", + 6 + ], + [ + "CAbC", + "cBAC", + 5 + ], + [ + "CAbCBaBb", + "cAbcBa", + 6 + ], + [ + "CAbCCC", + "c", + 11 + ], + [ + "CAbCabbA", + "BbAABACba", + 12 + ], + [ + "CAbCcc", + "babBA", + 9 + ], + [ + "CAba", + "abbB", + 6 + ], + [ + "CAbaAbBBC", + "CbBBAbB", + 9 + ], + [ + "CAbaAcCCC", + "AcbB", + 14 + ], + [ + "CAbaBb", + "A", + 10 + ], + [ + "CAbaa", + "cc", + 9 + ], + [ + "CAbacbba", + "cBCccabA", + 10 + ], + [ + "CAbbAA", + "BAAaCbC", + 12 + ], + [ + "CAbbAAcC", + "CBBACbBCB", + 12 + ], + [ + "CAbbBCcB", + "Abca", + 10 + ], + [ + "CAbbBbC", + "ABcaca", + 11 + ], + [ + "CAbbbAC", + "bcB", + 11 + ], + [ + "CAbbcCABA", + "CbB", + 12 + ], + [ + "CAbc", + "B", + 7 + ], + [ + "CAbcA", + "CAcaccACb", + 10 + ], + [ + "CAbcACCB", + "BB", + 13 + ], + [ + "CAbcBAa", + "bccBACB", + 10 + ], + [ + "CAbcCca", + "cAc", + 9 + ], + [ + "CAbcaBbca", + "ccaacBaaa", + 12 + ], + [ + "CAc", + "AAcacbcC", + 12 + ], + [ + "CAc", + "AbbAbbcc", + 12 + ], + [ + "CAc", + "BAB", + 4 + ], + [ + "CAc", + "ab", + 5 + ], + [ + "CAc", + "b", + 6 + ], + [ + "CAc", + "bbaaac", + 9 + ], + [ + "CAcAaCcaC", + "AAa", + 12 + ], + [ + "CAcAbaaA", + "ccBcBc", + 12 + ], + [ + "CAcAcC", + "BAbCb", + 9 + ], + [ + "CAcAca", + "CCbb", + 9 + ], + [ + "CAcAcbbAc", + "Cca", + 13 + ], + [ + "CAcB", + "cBcaaCcB", + 10 + ], + [ + "CAcBbCccb", + "BBAbAbAB", + 15 + ], + [ + "CAcBbbAaA", + "aACbbCbaa", + 9 + ], + [ + "CAcBbcbCa", + "bcBc", + 12 + ], + [ + "CAcBcBC", + "abCaCb", + 11 + ], + [ + "CAcC", + "CBcB", + 4 + ], + [ + "CAcCACCA", + "cAAa", + 10 + ], + [ + "CAcCaCBCC", + "aac", + 14 + ], + [ + "CAca", + "AaBACbAab", + 13 + ], + [ + "CAcaABcab", + "aCCbC", + 14 + ], + [ + "CAcaaa", + "bbccA", + 9 + ], + [ + "CAcaacBB", + "c", + 14 + ], + [ + "CAcb", + "B", + 7 + ], + [ + "CAcb", + "aBCBAb", + 8 + ], + [ + "CAcbBA", + "bBAaba", + 9 + ], + [ + "CAcbBB", + "CCcCBA", + 6 + ], + [ + "CAcbBacb", + "AbbbB", + 10 + ], + [ + "CAcbCCaaC", + "bccB", + 14 + ], + [ + "CAcbCbc", + "bAcCA", + 8 + ], + [ + "CAcbbabC", + "abAccCaac", + 11 + ], + [ + "CAcbcA", + "BBca", + 8 + ], + [ + "CAcbcB", + "Ab", + 8 + ], + [ + "CAcc", + "b", + 8 + ], + [ + "CAccAaAcc", + "bccbAB", + 12 + ], + [ + "CAccBaAC", + "abbAAaB", + 13 + ], + [ + "CAccBaaCc", + "BCAb", + 15 + ], + [ + "CAccBbA", + "bcBbBcBc", + 13 + ], + [ + "CAccac", + "aAc", + 8 + ], + [ + "CB", + "A", + 4 + ], + [ + "CB", + "ABac", + 6 + ], + [ + "CB", + "AC", + 4 + ], + [ + "CB", + "ACbbcbb", + 11 + ], + [ + "CB", + "ACcCCBAC", + 12 + ], + [ + "CB", + "AaAAcBc", + 11 + ], + [ + "CB", + "AaBCab", + 9 + ], + [ + "CB", + "BCc", + 4 + ], + [ + "CB", + "BaCaB", + 6 + ], + [ + "CB", + "BccbcabCB", + 14 + ], + [ + "CB", + "C", + 2 + ], + [ + "CB", + "CAAAb", + 7 + ], + [ + "CB", + "CCba", + 5 + ], + [ + "CB", + "CaAcA", + 8 + ], + [ + "CB", + "CcCaAccb", + 13 + ], + [ + "CB", + "a", + 4 + ], + [ + "CB", + "aAa", + 6 + ], + [ + "CB", + "aBCBaBbC", + 12 + ], + [ + "CB", + "aCbcAc", + 9 + ], + [ + "CB", + "ac", + 4 + ], + [ + "CB", + "bAa", + 6 + ], + [ + "CB", + "bBAAbcb", + 12 + ], + [ + "CB", + "bCB", + 2 + ], + [ + "CB", + "bCaCCcaCA", + 16 + ], + [ + "CB", + "ba", + 4 + ], + [ + "CB", + "baBCcCc", + 12 + ], + [ + "CB", + "bbcbCB", + 8 + ], + [ + "CB", + "bcaab", + 8 + ], + [ + "CB", + "c", + 3 + ], + [ + "CB", + "cACca", + 8 + ], + [ + "CB", + "cBCbAB", + 8 + ], + [ + "CB", + "cCB", + 2 + ], + [ + "CB", + "cbb", + 4 + ], + [ + "CB", + "cbbBbACCA", + 15 + ], + [ + "CBA", + "ba", + 4 + ], + [ + "CBA", + "cbcBb", + 7 + ], + [ + "CBAA", + "ABBaBcB", + 11 + ], + [ + "CBAA", + "Ba", + 5 + ], + [ + "CBAA", + "bcc", + 7 + ], + [ + "CBAABa", + "BAaBA", + 4 + ], + [ + "CBAAC", + "CBAac", + 2 + ], + [ + "CBAAa", + "cccbAb", + 9 + ], + [ + "CBAAaac", + "bcAAb", + 10 + ], + [ + "CBAAbCc", + "bbaABBB", + 9 + ], + [ + "CBAAbbBC", + "CbBAaAc", + 10 + ], + [ + "CBAAc", + "AAA", + 6 + ], + [ + "CBAAc", + "bCbBbAAaC", + 9 + ], + [ + "CBAB", + "A", + 6 + ], + [ + "CBABAbcBA", + "b", + 16 + ], + [ + "CBABAcBc", + "cAb", + 12 + ], + [ + "CBABCacb", + "aCCbbBABA", + 14 + ], + [ + "CBABaa", + "cBbcCcc", + 11 + ], + [ + "CBABbbCa", + "BbAaA", + 11 + ], + [ + "CBABbcCCC", + "cCAC", + 12 + ], + [ + "CBABc", + "cCc", + 7 + ], + [ + "CBABccBbc", + "caAaaACbb", + 13 + ], + [ + "CBACAA", + "CCc", + 8 + ], + [ + "CBACAcC", + "aCbbc", + 10 + ], + [ + "CBACa", + "BACaBBaB", + 10 + ], + [ + "CBACbcB", + "a", + 13 + ], + [ + "CBACc", + "baCCCA", + 9 + ], + [ + "CBACcAccA", + "aAb", + 15 + ], + [ + "CBAaAC", + "ccbC", + 9 + ], + [ + "CBAaBaC", + "bCccccCb", + 14 + ], + [ + "CBAaCB", + "acabB", + 8 + ], + [ + "CBAaCa", + "Cabb", + 8 + ], + [ + "CBAaCc", + "cBBBBBaC", + 11 + ], + [ + "CBAabb", + "bbCabaBa", + 10 + ], + [ + "CBAacCa", + "Bccbacb", + 11 + ], + [ + "CBAb", + "cCAcbc", + 7 + ], + [ + "CBAbCcaB", + "aC", + 13 + ], + [ + "CBAba", + "CbbabA", + 5 + ], + [ + "CBAbabb", + "BABa", + 7 + ], + [ + "CBAbba", + "ba", + 8 + ], + [ + "CBAcAAbcb", + "BBBBBbABA", + 15 + ], + [ + "CBAcC", + "acbabBA", + 11 + ], + [ + "CBAccB", + "cb", + 9 + ], + [ + "CBAccab", + "cbB", + 11 + ], + [ + "CBB", + "Aa", + 6 + ], + [ + "CBB", + "BbAaC", + 9 + ], + [ + "CBB", + "CB", + 2 + ], + [ + "CBB", + "CCA", + 4 + ], + [ + "CBB", + "CcbCb", + 6 + ], + [ + "CBB", + "baBB", + 4 + ], + [ + "CBB", + "cAcBBccA", + 11 + ], + [ + "CBB", + "cBccaB", + 7 + ], + [ + "CBBABB", + "BbBb", + 6 + ], + [ + "CBBABC", + "AABcaac", + 10 + ], + [ + "CBBAbACb", + "CCBCbAbcB", + 8 + ], + [ + "CBBBA", + "bB", + 7 + ], + [ + "CBBBBaAC", + "ca", + 13 + ], + [ + "CBBBBbbcC", + "BaAbCaB", + 14 + ], + [ + "CBBBb", + "bCbaBBAb", + 7 + ], + [ + "CBBBbCbcA", + "ABCCcABc", + 13 + ], + [ + "CBBC", + "ACaca", + 8 + ], + [ + "CBBC", + "cB", + 5 + ], + [ + "CBBCAAB", + "b", + 13 + ], + [ + "CBBCAb", + "CBA", + 6 + ], + [ + "CBBCCAB", + "AbCa", + 10 + ], + [ + "CBBCCa", + "c", + 11 + ], + [ + "CBBCabAa", + "Cca", + 11 + ], + [ + "CBBaCc", + "CbbA", + 7 + ], + [ + "CBBab", + "acBCcA", + 9 + ], + [ + "CBBabb", + "BBCcBb", + 7 + ], + [ + "CBBacBacB", + "caCCccBc", + 12 + ], + [ + "CBBbAb", + "Bbab", + 5 + ], + [ + "CBBba", + "bcCcb", + 9 + ], + [ + "CBBbaAcbc", + "ABcABaAAc", + 11 + ], + [ + "CBBbbbabc", + "baAaBC", + 12 + ], + [ + "CBBbcaaC", + "bABABABB", + 13 + ], + [ + "CBBc", + "abCABc", + 6 + ], + [ + "CBBcACB", + "abBbC", + 9 + ], + [ + "CBBcBcc", + "A", + 14 + ], + [ + "CBBcbA", + "ccbBAca", + 9 + ], + [ + "CBBcbaBc", + "C", + 14 + ], + [ + "CBBcbaC", + "bBcAAbaaC", + 9 + ], + [ + "CBBccBCCB", + "bacCC", + 11 + ], + [ + "CBBccc", + "aBabc", + 8 + ], + [ + "CBC", + "ACB", + 4 + ], + [ + "CBC", + "CAbCabB", + 9 + ], + [ + "CBC", + "aCaAaAC", + 10 + ], + [ + "CBCAA", + "C", + 8 + ], + [ + "CBCAABa", + "BBcCbBa", + 7 + ], + [ + "CBCAACB", + "bBBcAAB", + 7 + ], + [ + "CBCACAa", + "ABbA", + 10 + ], + [ + "CBCAaCA", + "BbaAbac", + 10 + ], + [ + "CBCAacCc", + "cCb", + 12 + ], + [ + "CBCB", + "aAAaA", + 10 + ], + [ + "CBCBBA", + "acc", + 11 + ], + [ + "CBCBBCAA", + "ABC", + 12 + ], + [ + "CBCBC", + "aBccbCB", + 8 + ], + [ + "CBCBCC", + "BAbc", + 8 + ], + [ + "CBCC", + "CbaaA", + 7 + ], + [ + "CBCCBC", + "aACAA", + 10 + ], + [ + "CBCCBCcCb", + "Ba", + 16 + ], + [ + "CBCCCB", + "cacbAbbaA", + 15 + ], + [ + "CBCCCbaB", + "cacAbc", + 12 + ], + [ + "CBCCacb", + "a", + 12 + ], + [ + "CBCCbcc", + "BbbBcbCBc", + 11 + ], + [ + "CBCCcCbA", + "CBaAa", + 11 + ], + [ + "CBCa", + "aaAbbabA", + 13 + ], + [ + "CBCa", + "b", + 7 + ], + [ + "CBCaA", + "ab", + 8 + ], + [ + "CBCaAaBaa", + "acBbAa", + 12 + ], + [ + "CBCaB", + "caa", + 7 + ], + [ + "CBCacBBBB", + "BcabACCb", + 12 + ], + [ + "CBCb", + "AabACCa", + 11 + ], + [ + "CBCb", + "aABc", + 7 + ], + [ + "CBCbBa", + "abaCaB", + 9 + ], + [ + "CBCbbc", + "CcBCBCcbA", + 9 + ], + [ + "CBCbccaA", + "AbCbbCba", + 9 + ], + [ + "CBCc", + "bbcCBBAcb", + 12 + ], + [ + "CBCcB", + "BAa", + 8 + ], + [ + "CBCcBbb", + "ccaCBbA", + 8 + ], + [ + "CBCcccacB", + "BA", + 15 + ], + [ + "CBa", + "ACaba", + 5 + ], + [ + "CBa", + "CAaB", + 4 + ], + [ + "CBa", + "CBBCccc", + 10 + ], + [ + "CBa", + "CacAccB", + 11 + ], + [ + "CBa", + "c", + 5 + ], + [ + "CBaAAa", + "caCcaBAA", + 9 + ], + [ + "CBaAC", + "CBb", + 6 + ], + [ + "CBaAbaaAA", + "CbbcC", + 13 + ], + [ + "CBaB", + "aabBAbbC", + 12 + ], + [ + "CBaBBAa", + "AaAcA", + 10 + ], + [ + "CBaBCABC", + "AAA", + 13 + ], + [ + "CBaBaCcB", + "cBaAbBAAa", + 12 + ], + [ + "CBaBaa", + "AAB", + 9 + ], + [ + "CBaCAbC", + "BbCaCC", + 7 + ], + [ + "CBaCBba", + "bcABB", + 10 + ], + [ + "CBaCCAB", + "cBcBAcA", + 10 + ], + [ + "CBaCabccB", + "Bba", + 14 + ], + [ + "CBaaA", + "AacbaC", + 10 + ], + [ + "CBaaAcac", + "aacbcbACc", + 13 + ], + [ + "CBaaB", + "CbaAcB", + 4 + ], + [ + "CBaaBc", + "Ba", + 8 + ], + [ + "CBaaCaCa", + "c", + 15 + ], + [ + "CBaaaBB", + "B", + 12 + ], + [ + "CBaab", + "BaabBcba", + 10 + ], + [ + "CBaab", + "bBB", + 7 + ], + [ + "CBaabAB", + "CBCC", + 10 + ], + [ + "CBaacACCC", + "a", + 16 + ], + [ + "CBab", + "CB", + 4 + ], + [ + "CBab", + "aBcc", + 6 + ], + [ + "CBabACCaA", + "A", + 16 + ], + [ + "CBabCbbaC", + "AcbAc", + 12 + ], + [ + "CBacA", + "acBCCaCB", + 10 + ], + [ + "CBacaCBCB", + "aB", + 14 + ], + [ + "CBacbacB", + "CaBa", + 9 + ], + [ + "CBb", + "Cbb", + 1 + ], + [ + "CBb", + "a", + 6 + ], + [ + "CBb", + "bBbcc", + 6 + ], + [ + "CBb", + "bCaaaA", + 10 + ], + [ + "CBbA", + "bAaCbBC", + 10 + ], + [ + "CBbAAa", + "aabB", + 10 + ], + [ + "CBbAB", + "A", + 8 + ], + [ + "CBbAB", + "bbb", + 6 + ], + [ + "CBbACca", + "CbCAcB", + 7 + ], + [ + "CBbACcbC", + "bCcbBAb", + 12 + ], + [ + "CBbBB", + "C", + 8 + ], + [ + "CBbBB", + "CBCBcCBa", + 8 + ], + [ + "CBbBBCaBB", + "AbAca", + 13 + ], + [ + "CBbCA", + "aca", + 8 + ], + [ + "CBbCCB", + "BAbC", + 8 + ], + [ + "CBbCCCa", + "Ca", + 10 + ], + [ + "CBbCCb", + "CACbBBb", + 8 + ], + [ + "CBbCbbBc", + "ABaA", + 14 + ], + [ + "CBbaAcAAc", + "ccAACB", + 12 + ], + [ + "CBbaAca", + "bB", + 12 + ], + [ + "CBbaB", + "B", + 8 + ], + [ + "CBbaBBCaC", + "baaCcA", + 12 + ], + [ + "CBbaC", + "c", + 9 + ], + [ + "CBbaCa", + "cbbcAa", + 6 + ], + [ + "CBbaCbB", + "AcCcACbA", + 10 + ], + [ + "CBbabC", + "abB", + 8 + ], + [ + "CBbb", + "C", + 6 + ], + [ + "CBbb", + "a", + 8 + ], + [ + "CBbbbaB", + "B", + 12 + ], + [ + "CBbc", + "BaAABc", + 9 + ], + [ + "CBbcBAAB", + "cAbCAA", + 8 + ], + [ + "CBbcbb", + "cBCaC", + 8 + ], + [ + "CBc", + "Bcab", + 6 + ], + [ + "CBc", + "CBAAacc", + 8 + ], + [ + "CBc", + "abaCa", + 8 + ], + [ + "CBc", + "cAabccaaA", + 14 + ], + [ + "CBcA", + "CCA", + 3 + ], + [ + "CBcAAaccC", + "abBcAbaBC", + 10 + ], + [ + "CBcAaCCCb", + "aB", + 15 + ], + [ + "CBcAbAA", + "BabCcAAc", + 11 + ], + [ + "CBcAcB", + "CbbAAA", + 7 + ], + [ + "CBcB", + "a", + 8 + ], + [ + "CBcB", + "bcCBCCcbb", + 11 + ], + [ + "CBcBA", + "cBbCb", + 7 + ], + [ + "CBcBBABcc", + "bbBcabA", + 13 + ], + [ + "CBcBC", + "cAbCAAbcA", + 13 + ], + [ + "CBcBaaACB", + "cCc", + 14 + ], + [ + "CBcBb", + "c", + 8 + ], + [ + "CBcC", + "bCBBbBa", + 10 + ], + [ + "CBcC", + "c", + 6 + ], + [ + "CBcCC", + "aB", + 8 + ], + [ + "CBca", + "cC", + 6 + ], + [ + "CBcaAa", + "A", + 10 + ], + [ + "CBcaAba", + "AaAAaaCaA", + 13 + ], + [ + "CBcaBCc", + "AcBcb", + 9 + ], + [ + "CBcabbbba", + "bCCBc", + 15 + ], + [ + "CBcacCbCC", + "bCAaab", + 13 + ], + [ + "CBcb", + "Acb", + 4 + ], + [ + "CBcb", + "CbcABCA", + 8 + ], + [ + "CBcb", + "cBAac", + 7 + ], + [ + "CBcbCbc", + "cBBaBc", + 7 + ], + [ + "CBcbCcaBc", + "BAACAaC", + 11 + ], + [ + "CBcba", + "abCA", + 7 + ], + [ + "CBcbab", + "ABBabBC", + 9 + ], + [ + "CBcbbaC", + "ABcCa", + 8 + ], + [ + "CBcbcAAA", + "aaAbb", + 14 + ], + [ + "CBcbcbC", + "c", + 12 + ], + [ + "CBccaC", + "CAAa", + 8 + ], + [ + "CBccaCaaa", + "ccbBabcBB", + 15 + ], + [ + "CBccac", + "aB", + 10 + ], + [ + "CBccbaAbb", + "bBaacaC", + 14 + ], + [ + "CBccbbc", + "ACaAaCB", + 13 + ], + [ + "CC", + "AA", + 4 + ], + [ + "CC", + "AAaacAc", + 12 + ], + [ + "CC", + "ACACbCABB", + 14 + ], + [ + "CC", + "ACBaCAb", + 10 + ], + [ + "CC", + "ACaaB", + 8 + ], + [ + "CC", + "ACcCAaabb", + 14 + ], + [ + "CC", + "AacCBBc", + 11 + ], + [ + "CC", + "BaAACABCC", + 14 + ], + [ + "CC", + "BaBcaCbb", + 13 + ], + [ + "CC", + "Bc", + 3 + ], + [ + "CC", + "CBC", + 2 + ], + [ + "CC", + "CCAAAbab", + 12 + ], + [ + "CC", + "CCc", + 2 + ], + [ + "CC", + "Cb", + 2 + ], + [ + "CC", + "CbbBb", + 8 + ], + [ + "CC", + "CcAc", + 5 + ], + [ + "CC", + "a", + 4 + ], + [ + "CC", + "aACcC", + 6 + ], + [ + "CC", + "aBC", + 4 + ], + [ + "CC", + "aBbc", + 7 + ], + [ + "CC", + "aa", + 4 + ], + [ + "CC", + "bA", + 4 + ], + [ + "CC", + "c", + 3 + ], + [ + "CC", + "cBBB", + 7 + ], + [ + "CC", + "cBcCbBcab", + 15 + ], + [ + "CC", + "cCabBBC", + 10 + ], + [ + "CC", + "cCbBB", + 7 + ], + [ + "CCA", + "A", + 4 + ], + [ + "CCA", + "BaaabCA", + 10 + ], + [ + "CCA", + "CBaCacAB", + 10 + ], + [ + "CCA", + "aba", + 5 + ], + [ + "CCA", + "bb", + 6 + ], + [ + "CCAA", + "ABBCA", + 8 + ], + [ + "CCAA", + "BcCbBA", + 7 + ], + [ + "CCAA", + "babAabAA", + 12 + ], + [ + "CCAA", + "cAcC", + 7 + ], + [ + "CCAABbAAc", + "CA", + 14 + ], + [ + "CCAABc", + "BacACaaB", + 11 + ], + [ + "CCAACAbaa", + "BA", + 16 + ], + [ + "CCAAaB", + "CaCCcCa", + 10 + ], + [ + "CCAAbabbc", + "cc", + 15 + ], + [ + "CCAAcCCcA", + "ABcACaBc", + 13 + ], + [ + "CCAAcb", + "C", + 10 + ], + [ + "CCAB", + "bbc", + 8 + ], + [ + "CCABAAB", + "BCABbccB", + 8 + ], + [ + "CCABBCcbB", + "CabB", + 11 + ], + [ + "CCABCCAaa", + "CBCc", + 11 + ], + [ + "CCABb", + "Bcc", + 9 + ], + [ + "CCABcCBAa", + "bbBa", + 13 + ], + [ + "CCABcaC", + "caCc", + 10 + ], + [ + "CCABcbBaC", + "cAAaAAaC", + 11 + ], + [ + "CCAC", + "aBAccBA", + 11 + ], + [ + "CCACb", + "BABcBc", + 10 + ], + [ + "CCACbACc", + "Aabbbac", + 11 + ], + [ + "CCACcaCB", + "ABbCACbB", + 11 + ], + [ + "CCAa", + "aACCbaaac", + 11 + ], + [ + "CCAb", + "ACCBba", + 6 + ], + [ + "CCAbBcABA", + "cAaCBc", + 12 + ], + [ + "CCAbBcb", + "baCB", + 10 + ], + [ + "CCAbCAb", + "BaCCaa", + 10 + ], + [ + "CCAbaC", + "BBbcaa", + 10 + ], + [ + "CCAbaCBa", + "bcccA", + 12 + ], + [ + "CCAcAB", + "ccAAbCa", + 9 + ], + [ + "CCAcACA", + "acBA", + 9 + ], + [ + "CCAcBAa", + "aCacBaBAA", + 8 + ], + [ + "CCAcC", + "BbcBbC", + 9 + ], + [ + "CCAcCB", + "cBcaBBBA", + 11 + ], + [ + "CCAcaC", + "CcaB", + 6 + ], + [ + "CCAcaaca", + "cC", + 13 + ], + [ + "CCAcacccc", + "cAcabcB", + 9 + ], + [ + "CCAcbCb", + "ACCa", + 9 + ], + [ + "CCAcbbCBc", + "CcAbBCa", + 8 + ], + [ + "CCB", + "ACbaAACcB", + 12 + ], + [ + "CCB", + "Cc", + 3 + ], + [ + "CCB", + "abaAB", + 8 + ], + [ + "CCB", + "baCac", + 8 + ], + [ + "CCBA", + "Aaaaa", + 9 + ], + [ + "CCBAA", + "a", + 9 + ], + [ + "CCBABcc", + "BCABb", + 8 + ], + [ + "CCBAC", + "bCCcaCAc", + 9 + ], + [ + "CCBAbAC", + "bCA", + 10 + ], + [ + "CCBAc", + "BC", + 7 + ], + [ + "CCBAcaacb", + "aCCAA", + 13 + ], + [ + "CCBBCBccb", + "BBAa", + 14 + ], + [ + "CCBBbB", + "CbcAACa", + 11 + ], + [ + "CCBBccC", + "c", + 12 + ], + [ + "CCBC", + "CBCaABac", + 9 + ], + [ + "CCBCAaAA", + "CaC", + 12 + ], + [ + "CCBCBCcc", + "bcCccbAbb", + 13 + ], + [ + "CCBCb", + "c", + 9 + ], + [ + "CCBaAaB", + "bACAC", + 11 + ], + [ + "CCBaAaB", + "cbcCBca", + 11 + ], + [ + "CCBaAcba", + "BaaaCAA", + 11 + ], + [ + "CCBaBbbBC", + "a", + 16 + ], + [ + "CCBaCcAa", + "ccBbACCb", + 10 + ], + [ + "CCBbAAAB", + "CCcaBabc", + 11 + ], + [ + "CCBbACc", + "bbbaacBb", + 12 + ], + [ + "CCBbB", + "cBBBACC", + 10 + ], + [ + "CCBbBC", + "bAcCCab", + 12 + ], + [ + "CCBbabCB", + "bCcaB", + 10 + ], + [ + "CCBbca", + "bc", + 8 + ], + [ + "CCBcB", + "ACbbCa", + 8 + ], + [ + "CCBca", + "BacCaaAA", + 12 + ], + [ + "CCBcbbC", + "cB", + 11 + ], + [ + "CCC", + "CcaB", + 5 + ], + [ + "CCC", + "baaaC", + 8 + ], + [ + "CCCAABC", + "CC", + 10 + ], + [ + "CCCAbbBCb", + "cBBaaBab", + 12 + ], + [ + "CCCAc", + "BaccCaB", + 9 + ], + [ + "CCCBAbcc", + "accAb", + 10 + ], + [ + "CCCBBcab", + "ACCA", + 11 + ], + [ + "CCCBCaAcC", + "aaCcAbAa", + 14 + ], + [ + "CCCBbbbC", + "aCb", + 12 + ], + [ + "CCCBcBBaa", + "aBACAbAA", + 14 + ], + [ + "CCCBccaB", + "ccBcc", + 8 + ], + [ + "CCCC", + "bBcAaAbaC", + 15 + ], + [ + "CCCCA", + "BBACa", + 7 + ], + [ + "CCCCA", + "bAcAbbC", + 13 + ], + [ + "CCCCA", + "cccAC", + 7 + ], + [ + "CCCCAbaa", + "CaBaccBB", + 14 + ], + [ + "CCCCBC", + "ABbBc", + 9 + ], + [ + "CCCCCa", + "BCACA", + 7 + ], + [ + "CCCCaBCA", + "Bacbcab", + 13 + ], + [ + "CCCCbaC", + "b", + 12 + ], + [ + "CCCCbbba", + "BAbcAbcBb", + 14 + ], + [ + "CCCCcA", + "bbCCbbc", + 10 + ], + [ + "CCCCcBAC", + "acACBcC", + 9 + ], + [ + "CCCa", + "bAb", + 8 + ], + [ + "CCCaABacb", + "bacBBcAA", + 14 + ], + [ + "CCCaCcA", + "bAcAC", + 10 + ], + [ + "CCCaCcbBA", + "bAbc", + 15 + ], + [ + "CCCaaCaBA", + "AbAbBCb", + 15 + ], + [ + "CCCabaaab", + "cAbca", + 12 + ], + [ + "CCCacacb", + "CAcCBcA", + 10 + ], + [ + "CCCb", + "C", + 6 + ], + [ + "CCCbBBB", + "A", + 14 + ], + [ + "CCCba", + "BbAcaC", + 10 + ], + [ + "CCCbbAA", + "a", + 13 + ], + [ + "CCCbbaA", + "cCB", + 10 + ], + [ + "CCCbbcc", + "BCA", + 12 + ], + [ + "CCCc", + "CBbBBaBBa", + 16 + ], + [ + "CCCcBBba", + "CcBbcAa", + 9 + ], + [ + "CCCcBaA", + "CCCBAAAcA", + 9 + ], + [ + "CCa", + "Ca", + 2 + ], + [ + "CCa", + "CbabbAAcA", + 14 + ], + [ + "CCa", + "bbbCBA", + 9 + ], + [ + "CCa", + "cCAcbAbCC", + 14 + ], + [ + "CCaA", + "c", + 7 + ], + [ + "CCaAA", + "BBACcB", + 11 + ], + [ + "CCaAAb", + "cBa", + 9 + ], + [ + "CCaABbBaA", + "CcACab", + 11 + ], + [ + "CCaAcb", + "ABbBCaCa", + 13 + ], + [ + "CCaBAcbAC", + "CBbc", + 11 + ], + [ + "CCaBBA", + "bAbBB", + 8 + ], + [ + "CCaCAAbA", + "AA", + 12 + ], + [ + "CCaCACBcb", + "caC", + 13 + ], + [ + "CCaCAaA", + "BCAaCaa", + 7 + ], + [ + "CCaCB", + "CAAA", + 7 + ], + [ + "CCaCBcAC", + "bcCa", + 12 + ], + [ + "CCaCCC", + "CACcBa", + 8 + ], + [ + "CCaCaCc", + "BcCba", + 11 + ], + [ + "CCaCaCcBA", + "a", + 16 + ], + [ + "CCaCabaAa", + "CcccAacAC", + 11 + ], + [ + "CCaCb", + "bAcaCbCAb", + 11 + ], + [ + "CCaCbaaaC", + "CaCBCBBc", + 10 + ], + [ + "CCaaBbbA", + "BAAc", + 14 + ], + [ + "CCaaCbCab", + "aBbB", + 13 + ], + [ + "CCaaaaccC", + "AABcca", + 12 + ], + [ + "CCaacBa", + "a", + 12 + ], + [ + "CCabA", + "acbCAAbb", + 10 + ], + [ + "CCabA", + "bbaacCA", + 10 + ], + [ + "CCabCAac", + "ABABA", + 12 + ], + [ + "CCabcCbc", + "BCbc", + 9 + ], + [ + "CCac", + "a", + 6 + ], + [ + "CCacAac", + "bbBbaC", + 11 + ], + [ + "CCacAbCcb", + "bCbBa", + 14 + ], + [ + "CCacBca", + "C", + 12 + ], + [ + "CCacCb", + "cBAC", + 8 + ], + [ + "CCacab", + "Aa", + 9 + ], + [ + "CCacbb", + "CAb", + 7 + ], + [ + "CCacbcB", + "Cca", + 9 + ], + [ + "CCb", + "BAbBAaB", + 12 + ], + [ + "CCb", + "CAAAbca", + 10 + ], + [ + "CCb", + "bAc", + 6 + ], + [ + "CCb", + "bcabBaB", + 11 + ], + [ + "CCb", + "cbCcaAaac", + 15 + ], + [ + "CCbA", + "AABAC", + 7 + ], + [ + "CCbA", + "BbBCabCaa", + 13 + ], + [ + "CCbAAa", + "BcABbC", + 11 + ], + [ + "CCbABCC", + "a", + 13 + ], + [ + "CCbACCBa", + "BbbaBbC", + 12 + ], + [ + "CCbAaaA", + "aB", + 12 + ], + [ + "CCbAbC", + "cAacBCa", + 10 + ], + [ + "CCbAbbBB", + "abcaA", + 13 + ], + [ + "CCbB", + "Aa", + 8 + ], + [ + "CCbB", + "cbaBCBBA", + 10 + ], + [ + "CCbBBB", + "CACCacCBc", + 12 + ], + [ + "CCbBCC", + "BbCAbcAA", + 12 + ], + [ + "CCbBbaBa", + "BcbccCAc", + 13 + ], + [ + "CCbCBCbB", + "cCbbB", + 7 + ], + [ + "CCbCbA", + "ABACcA", + 8 + ], + [ + "CCbCbcacC", + "bBC", + 13 + ], + [ + "CCba", + "aBaAcAaCC", + 15 + ], + [ + "CCba", + "caCAcc", + 9 + ], + [ + "CCba", + "cbaCcba", + 7 + ], + [ + "CCbaB", + "Ba", + 7 + ], + [ + "CCbaBB", + "CCaccA", + 8 + ], + [ + "CCbaCC", + "aBbcaa", + 10 + ], + [ + "CCbaaBcAB", + "a", + 16 + ], + [ + "CCbaba", + "ACbaBAAb", + 8 + ], + [ + "CCbb", + "aBbacACBa", + 14 + ], + [ + "CCbbACcA", + "CBabaAA", + 9 + ], + [ + "CCbbAabCa", + "cAABb", + 13 + ], + [ + "CCbbBabb", + "aBBcc", + 13 + ], + [ + "CCbbaBC", + "cBC", + 9 + ], + [ + "CCbbca", + "bAC", + 9 + ], + [ + "CCbbcaCBA", + "CCaAaBBAB", + 10 + ], + [ + "CCbcAac", + "a", + 12 + ], + [ + "CCbcBC", + "aabbAc", + 9 + ], + [ + "CCbcBca", + "B", + 12 + ], + [ + "CCbcCAc", + "Bb", + 12 + ], + [ + "CCbcb", + "cBaC", + 8 + ], + [ + "CCbcc", + "aBaccBcB", + 11 + ], + [ + "CCc", + "A", + 6 + ], + [ + "CCc", + "Ac", + 4 + ], + [ + "CCc", + "Bc", + 4 + ], + [ + "CCc", + "Cacbb", + 6 + ], + [ + "CCc", + "bCcbAbbAB", + 14 + ], + [ + "CCcAC", + "CABCA", + 7 + ], + [ + "CCcACBCC", + "B", + 14 + ], + [ + "CCcACc", + "cACCCA", + 8 + ], + [ + "CCcAaaba", + "CCCBcABca", + 9 + ], + [ + "CCcAc", + "abaaaCCc", + 13 + ], + [ + "CCcB", + "aCAaBc", + 8 + ], + [ + "CCcBAB", + "CacAbbcbB", + 11 + ], + [ + "CCcBAcC", + "bB", + 12 + ], + [ + "CCcBbAb", + "aBbC", + 10 + ], + [ + "CCcBcaB", + "cBbb", + 9 + ], + [ + "CCcCaaAC", + "CAAbccC", + 12 + ], + [ + "CCcCb", + "B", + 9 + ], + [ + "CCcCbacba", + "AAa", + 15 + ], + [ + "CCcCcABB", + "cAcBabacb", + 13 + ], + [ + "CCcCcccAB", + "BAaACCCAB", + 11 + ], + [ + "CCca", + "Acc", + 5 + ], + [ + "CCca", + "a", + 6 + ], + [ + "CCcaBCabB", + "cAabc", + 11 + ], + [ + "CCcaC", + "bbCACAA", + 10 + ], + [ + "CCcaCca", + "CabacBb", + 9 + ], + [ + "CCcaaBaB", + "ccaAabb", + 9 + ], + [ + "CCcaaabbc", + "BCaCC", + 13 + ], + [ + "CCcabA", + "bCCAAcaBC", + 9 + ], + [ + "CCcacbB", + "Babb", + 9 + ], + [ + "CCcb", + "C", + 6 + ], + [ + "CCcbB", + "abB", + 6 + ], + [ + "CCcbBBC", + "aAAaCCCc", + 14 + ], + [ + "CCcbC", + "ACcBBBBc", + 10 + ], + [ + "CCcbC", + "a", + 10 + ], + [ + "CCcbCACab", + "Accba", + 11 + ], + [ + "CCcbcaaaA", + "ccaaAbaaB", + 12 + ], + [ + "CCccAAB", + "CCaaa", + 8 + ], + [ + "CCccB", + "AACBAbC", + 11 + ], + [ + "CCcca", + "baAaCccaa", + 10 + ], + [ + "CCccaAA", + "b", + 14 + ], + [ + "CCccbb", + "babC", + 10 + ], + [ + "Ca", + "ABCCb", + 8 + ], + [ + "Ca", + "AaCcCBaab", + 14 + ], + [ + "Ca", + "Aac", + 4 + ], + [ + "Ca", + "BAAABca", + 11 + ], + [ + "Ca", + "BACaCCb", + 10 + ], + [ + "Ca", + "BAc", + 5 + ], + [ + "Ca", + "BBAA", + 7 + ], + [ + "Ca", + "BCB", + 4 + ], + [ + "Ca", + "BCBbBaAC", + 12 + ], + [ + "Ca", + "BaCCAcBAa", + 14 + ], + [ + "Ca", + "BabB", + 6 + ], + [ + "Ca", + "BababbBb", + 14 + ], + [ + "Ca", + "BbCB", + 6 + ], + [ + "Ca", + "Bbb", + 6 + ], + [ + "Ca", + "CAbc", + 5 + ], + [ + "Ca", + "CBAbac", + 8 + ], + [ + "Ca", + "CBB", + 4 + ], + [ + "Ca", + "CBaA", + 4 + ], + [ + "Ca", + "CCAB", + 5 + ], + [ + "Ca", + "CCaCBaaA", + 12 + ], + [ + "Ca", + "CCbaAA", + 8 + ], + [ + "Ca", + "CCbb", + 6 + ], + [ + "Ca", + "Cb", + 2 + ], + [ + "Ca", + "aACCba", + 8 + ], + [ + "Ca", + "aB", + 4 + ], + [ + "Ca", + "aBBA", + 7 + ], + [ + "Ca", + "aac", + 4 + ], + [ + "Ca", + "acBacBcAA", + 15 + ], + [ + "Ca", + "b", + 4 + ], + [ + "Ca", + "bAbBbbAa", + 14 + ], + [ + "Ca", + "bBCA", + 5 + ], + [ + "Ca", + "bbBAAAa", + 12 + ], + [ + "Ca", + "c", + 3 + ], + [ + "Ca", + "cCBCBbAAa", + 14 + ], + [ + "Ca", + "caBabaBc", + 13 + ], + [ + "Ca", + "ccCaCCC", + 10 + ], + [ + "CaA", + "BcBcAb", + 9 + ], + [ + "CaA", + "CCBBA", + 6 + ], + [ + "CaA", + "aBa", + 5 + ], + [ + "CaA", + "aBcbcb", + 11 + ], + [ + "CaA", + "bBcCBcbcc", + 16 + ], + [ + "CaA", + "bCABC", + 7 + ], + [ + "CaA", + "bCcBaAaA", + 10 + ], + [ + "CaA", + "c", + 5 + ], + [ + "CaAA", + "bacCbbA", + 10 + ], + [ + "CaAABbc", + "c", + 12 + ], + [ + "CaAAC", + "c", + 9 + ], + [ + "CaAACB", + "bB", + 10 + ], + [ + "CaAAa", + "AcCbA", + 9 + ], + [ + "CaAAbcCa", + "a", + 14 + ], + [ + "CaAB", + "BCaCaCB", + 7 + ], + [ + "CaAB", + "Ba", + 6 + ], + [ + "CaAB", + "bCb", + 7 + ], + [ + "CaABBc", + "b", + 11 + ], + [ + "CaABCBA", + "bBb", + 11 + ], + [ + "CaABa", + "B", + 8 + ], + [ + "CaABbACB", + "BA", + 12 + ], + [ + "CaABbC", + "AAacAc", + 9 + ], + [ + "CaAC", + "BBCbCcaaC", + 11 + ], + [ + "CaAC", + "aCAABab", + 9 + ], + [ + "CaAC", + "acCBbcbA", + 13 + ], + [ + "CaACB", + "B", + 8 + ], + [ + "CaACbacC", + "BAbabAAB", + 12 + ], + [ + "CaACbc", + "CBA", + 8 + ], + [ + "CaACcBACc", + "aAcc", + 10 + ], + [ + "CaAa", + "Bababa", + 7 + ], + [ + "CaAa", + "ccAC", + 5 + ], + [ + "CaAaAC", + "ACa", + 9 + ], + [ + "CaAaACCc", + "BaabbBCbC", + 12 + ], + [ + "CaAaBbB", + "cbAb", + 9 + ], + [ + "CaAaa", + "cAAACBa", + 7 + ], + [ + "CaAab", + "BBbcbaa", + 12 + ], + [ + "CaAabcABB", + "ACcBacC", + 14 + ], + [ + "CaAbC", + "bC", + 6 + ], + [ + "CaAbbBa", + "c", + 13 + ], + [ + "CaAc", + "Ac", + 4 + ], + [ + "CaAc", + "abB", + 6 + ], + [ + "CaAcCAb", + "cABCaAA", + 9 + ], + [ + "CaAcCa", + "bBabcCCC", + 10 + ], + [ + "CaAca", + "bcAcBAABA", + 13 + ], + [ + "CaAcb", + "aBbACB", + 8 + ], + [ + "CaAcc", + "ACc", + 5 + ], + [ + "CaB", + "B", + 4 + ], + [ + "CaB", + "C", + 4 + ], + [ + "CaB", + "acbCAcC", + 11 + ], + [ + "CaB", + "bbAaAaBB", + 12 + ], + [ + "CaB", + "cabbb", + 6 + ], + [ + "CaBA", + "cbcAB", + 7 + ], + [ + "CaBAAB", + "BBAC", + 8 + ], + [ + "CaBAAB", + "a", + 10 + ], + [ + "CaBAAaBB", + "AACB", + 10 + ], + [ + "CaBAbCb", + "baA", + 10 + ], + [ + "CaBAcBCcb", + "A", + 16 + ], + [ + "CaBAcc", + "AACCaA", + 11 + ], + [ + "CaBAcc", + "bAcBbA", + 11 + ], + [ + "CaBB", + "aCCBbcaba", + 13 + ], + [ + "CaBB", + "babABcb", + 9 + ], + [ + "CaBBBa", + "Cc", + 10 + ], + [ + "CaBBBbBB", + "CAAaC", + 13 + ], + [ + "CaBBCccC", + "bAac", + 13 + ], + [ + "CaBBaA", + "a", + 10 + ], + [ + "CaBBabBcc", + "b", + 16 + ], + [ + "CaBBcC", + "BA", + 10 + ], + [ + "CaBBccac", + "accCBA", + 11 + ], + [ + "CaBC", + "CaaacC", + 6 + ], + [ + "CaBCACabA", + "CbcABbc", + 10 + ], + [ + "CaBCBbC", + "ccbBbaC", + 8 + ], + [ + "CaBCCcaCc", + "CaC", + 12 + ], + [ + "CaBaaBcA", + "C", + 14 + ], + [ + "CaBacBc", + "c", + 12 + ], + [ + "CaBba", + "bbcCaAa", + 10 + ], + [ + "CaBbac", + "A", + 11 + ], + [ + "CaBcAc", + "CAAab", + 8 + ], + [ + "CaBcBAbB", + "BABCCca", + 12 + ], + [ + "CaBcaA", + "bcC", + 9 + ], + [ + "CaBccaAc", + "bBa", + 12 + ], + [ + "CaC", + "aC", + 2 + ], + [ + "CaCA", + "bcBA", + 6 + ], + [ + "CaCAAbCcB", + "aBCc", + 11 + ], + [ + "CaCABCcbA", + "bB", + 16 + ], + [ + "CaCACCaA", + "BCBBaaBCA", + 13 + ], + [ + "CaCAbBA", + "bCBb", + 10 + ], + [ + "CaCB", + "AB", + 5 + ], + [ + "CaCB", + "bAb", + 6 + ], + [ + "CaCBa", + "ACabCC", + 8 + ], + [ + "CaCBa", + "abBaa", + 6 + ], + [ + "CaCBaaC", + "bcACBa", + 8 + ], + [ + "CaCBbBBb", + "bab", + 12 + ], + [ + "CaCBc", + "cAC", + 6 + ], + [ + "CaCCbB", + "babA", + 8 + ], + [ + "CaCCbc", + "BAbcCcBB", + 11 + ], + [ + "CaCa", + "Aab", + 6 + ], + [ + "CaCa", + "CCccAab", + 9 + ], + [ + "CaCa", + "CcbcAAC", + 10 + ], + [ + "CaCa", + "abCCa", + 6 + ], + [ + "CaCaAB", + "aBb", + 9 + ], + [ + "CaCb", + "bCab", + 4 + ], + [ + "CaCbBcCCC", + "CaBBab", + 11 + ], + [ + "CaCbbCAcb", + "aABcAc", + 10 + ], + [ + "CaCbbaB", + "ABB", + 10 + ], + [ + "CaCc", + "A", + 7 + ], + [ + "CaCc", + "ccBbc", + 7 + ], + [ + "CaCcAAb", + "baCBCB", + 9 + ], + [ + "CaCcB", + "AA", + 9 + ], + [ + "CaCcBa", + "cAc", + 8 + ], + [ + "CaCcaa", + "CABabbB", + 11 + ], + [ + "CaCcbBA", + "CAbccCaa", + 9 + ], + [ + "Caa", + "Acc", + 6 + ], + [ + "Caa", + "BAcBcBCbb", + 16 + ], + [ + "Caa", + "abbAb", + 9 + ], + [ + "CaaABcBab", + "acACccaCa", + 12 + ], + [ + "CaaAaAa", + "bbaabAc", + 9 + ], + [ + "CaaAaAaba", + "aaCBc", + 13 + ], + [ + "CaaAbCcAb", + "bc", + 14 + ], + [ + "CaaBAab", + "CcC", + 12 + ], + [ + "CaaBcB", + "Caaa", + 6 + ], + [ + "CaaC", + "AAbBaC", + 7 + ], + [ + "CaaCA", + "A", + 8 + ], + [ + "CaaCbCB", + "c", + 13 + ], + [ + "Caaa", + "BBaBcA", + 9 + ], + [ + "CaaaCabaC", + "CAa", + 13 + ], + [ + "Caaab", + "a", + 8 + ], + [ + "CaaabAbc", + "ACc", + 12 + ], + [ + "CaaabCbB", + "ABcB", + 11 + ], + [ + "Caaabcc", + "babAaAC", + 10 + ], + [ + "Caaac", + "AAbbccabb", + 15 + ], + [ + "Caab", + "BacAc", + 7 + ], + [ + "CaacAaCb", + "AbccbacB", + 10 + ], + [ + "CaacCb", + "acbcCABBB", + 13 + ], + [ + "Caacaccbc", + "bCcbB", + 13 + ], + [ + "CaacccACC", + "baBCAaCA", + 12 + ], + [ + "Cab", + "AbBCbcBbc", + 14 + ], + [ + "Cab", + "AcC", + 6 + ], + [ + "Cab", + "BCA", + 5 + ], + [ + "Cab", + "BaB", + 3 + ], + [ + "Cab", + "c", + 5 + ], + [ + "CabA", + "b", + 6 + ], + [ + "CabAabA", + "cbCaBBb", + 10 + ], + [ + "CabAbcCB", + "acbCa", + 10 + ], + [ + "CabB", + "AACcCBcab", + 14 + ], + [ + "CabB", + "BabB", + 2 + ], + [ + "CabB", + "Ccba", + 4 + ], + [ + "CabBCa", + "AaCaab", + 10 + ], + [ + "CabBacbC", + "C", + 14 + ], + [ + "CabBbab", + "Bc", + 12 + ], + [ + "CabBbc", + "BbBCbcBca", + 12 + ], + [ + "CabBcC", + "AaAcCAC", + 9 + ], + [ + "CabCB", + "AaCBCab", + 8 + ], + [ + "CabCaAABb", + "BbaB", + 12 + ], + [ + "CabCb", + "BcCbacbb", + 10 + ], + [ + "Caba", + "AA", + 6 + ], + [ + "Caba", + "CBbBBbCC", + 12 + ], + [ + "CabaAAcbc", + "aA", + 14 + ], + [ + "CabaAaab", + "Bccb", + 13 + ], + [ + "CabaBCc", + "acBcBb", + 11 + ], + [ + "CabaCAC", + "bAaa", + 10 + ], + [ + "CababAAcB", + "a", + 16 + ], + [ + "Cabb", + "aBbABaA", + 11 + ], + [ + "CabbAAABB", + "ccA", + 15 + ], + [ + "CabbACc", + "ABCCca", + 10 + ], + [ + "CabbB", + "BabCaAC", + 10 + ], + [ + "CabbB", + "CbBbAccbc", + 12 + ], + [ + "CabbCaa", + "bbaBbcacC", + 10 + ], + [ + "CabbaAba", + "CABaCBAbA", + 9 + ], + [ + "CabbbAACB", + "Babac", + 12 + ], + [ + "CabcB", + "aBAbaA", + 9 + ], + [ + "CabcBaC", + "Cac", + 8 + ], + [ + "CabcaABb", + "cBBc", + 12 + ], + [ + "CabcaBcB", + "bAABcbCA", + 12 + ], + [ + "Cabcb", + "Ba", + 8 + ], + [ + "Cabccb", + "BBBA", + 11 + ], + [ + "Cac", + "A", + 5 + ], + [ + "Cac", + "BAcb", + 5 + ], + [ + "Cac", + "BCcCCac", + 8 + ], + [ + "Cac", + "Bc", + 4 + ], + [ + "Cac", + "CBaacCcCb", + 12 + ], + [ + "Cac", + "cC", + 4 + ], + [ + "Cac", + "cbACbC", + 9 + ], + [ + "CacA", + "BaBabBAA", + 12 + ], + [ + "CacA", + "CacbC", + 4 + ], + [ + "CacAb", + "acCAb", + 4 + ], + [ + "CacAcAa", + "ACbcC", + 10 + ], + [ + "CacAcBc", + "caABbAAAA", + 13 + ], + [ + "CacBBBC", + "B", + 12 + ], + [ + "CacBCa", + "CcbcBA", + 7 + ], + [ + "CacBaCbca", + "aaBaaaBc", + 11 + ], + [ + "CacBbbB", + "CaCbbaBbb", + 7 + ], + [ + "CacBc", + "bBbAC", + 9 + ], + [ + "CacC", + "AcC", + 3 + ], + [ + "CacCbaAcc", + "bCaAaCB", + 13 + ], + [ + "CacaAc", + "aAbC", + 8 + ], + [ + "Cacab", + "AaBCAa", + 8 + ], + [ + "Cacab", + "c", + 8 + ], + [ + "CacacC", + "AABCCAA", + 12 + ], + [ + "Cacb", + "ac", + 4 + ], + [ + "CacbB", + "BCbcBaa", + 9 + ], + [ + "CacbC", + "bBbCaBBB", + 11 + ], + [ + "CacbC", + "cCAAcCa", + 9 + ], + [ + "CacbCAccB", + "AAaBBcbBc", + 14 + ], + [ + "Cacc", + "AACc", + 4 + ], + [ + "CaccAC", + "BabBAaC", + 8 + ], + [ + "CaccBca", + "aCbb", + 10 + ], + [ + "CaccaAbbC", + "BBbC", + 13 + ], + [ + "CaccaCCa", + "cc", + 12 + ], + [ + "Cb", + "A", + 4 + ], + [ + "Cb", + "AACB", + 5 + ], + [ + "Cb", + "AACCbcBc", + 12 + ], + [ + "Cb", + "ABcAcaCcC", + 16 + ], + [ + "Cb", + "ACCBAaCaA", + 15 + ], + [ + "Cb", + "AaBCBCc", + 11 + ], + [ + "Cb", + "Aaa", + 6 + ], + [ + "Cb", + "AacabCcB", + 13 + ], + [ + "Cb", + "Ac", + 4 + ], + [ + "Cb", + "BABB", + 7 + ], + [ + "Cb", + "BBcAabC", + 11 + ], + [ + "Cb", + "Ba", + 4 + ], + [ + "Cb", + "BaaB", + 7 + ], + [ + "Cb", + "BbCaabCCC", + 14 + ], + [ + "Cb", + "CA", + 2 + ], + [ + "Cb", + "CCcBaAB", + 11 + ], + [ + "Cb", + "Cbb", + 2 + ], + [ + "Cb", + "CcBccBacC", + 15 + ], + [ + "Cb", + "aBcaCb", + 8 + ], + [ + "Cb", + "aCBCcAACA", + 15 + ], + [ + "Cb", + "aaAbc", + 8 + ], + [ + "Cb", + "aaCBBaB", + 11 + ], + [ + "Cb", + "aaaB", + 7 + ], + [ + "Cb", + "abBaC", + 8 + ], + [ + "Cb", + "abaaaCABA", + 15 + ], + [ + "Cb", + "acBbAAca", + 13 + ], + [ + "Cb", + "acbbabbCC", + 15 + ], + [ + "Cb", + "bcCCBA", + 9 + ], + [ + "Cb", + "c", + 3 + ], + [ + "Cb", + "cAbcbCA", + 11 + ], + [ + "Cb", + "cAcCbBA", + 10 + ], + [ + "Cb", + "cB", + 2 + ], + [ + "Cb", + "cBcB", + 6 + ], + [ + "Cb", + "caacb", + 7 + ], + [ + "Cb", + "cbCBBbacA", + 14 + ], + [ + "Cb", + "cbcbccA", + 11 + ], + [ + "CbA", + "AAC", + 6 + ], + [ + "CbA", + "BBCBaCC", + 10 + ], + [ + "CbA", + "BBcBB", + 8 + ], + [ + "CbA", + "baaA", + 6 + ], + [ + "CbA", + "c", + 5 + ], + [ + "CbA", + "caBAbA", + 7 + ], + [ + "CbAABaAC", + "A", + 14 + ], + [ + "CbAACACcC", + "ba", + 15 + ], + [ + "CbAACa", + "AaCaAccab", + 11 + ], + [ + "CbAAaAB", + "BCaBCcaaC", + 12 + ], + [ + "CbAAb", + "bacAabaAB", + 11 + ], + [ + "CbAAcAb", + "caC", + 11 + ], + [ + "CbAAcC", + "ABbcaCa", + 10 + ], + [ + "CbABACcaB", + "Babb", + 14 + ], + [ + "CbABC", + "b", + 8 + ], + [ + "CbABaBaAC", + "acaC", + 12 + ], + [ + "CbAC", + "A", + 6 + ], + [ + "CbAC", + "c", + 7 + ], + [ + "CbACaB", + "aBcA", + 9 + ], + [ + "CbACb", + "bcABcCC", + 10 + ], + [ + "CbACc", + "BbabcaBCA", + 13 + ], + [ + "CbACcABba", + "baABAcBc", + 12 + ], + [ + "CbAaAaaa", + "ACbBACbbc", + 13 + ], + [ + "CbAaabbC", + "A", + 14 + ], + [ + "CbAacB", + "c", + 10 + ], + [ + "CbAacBB", + "CacbBBbBA", + 12 + ], + [ + "CbAb", + "baBac", + 8 + ], + [ + "CbAbBaC", + "aCAaACb", + 11 + ], + [ + "CbAba", + "CbaA", + 4 + ], + [ + "CbAbb", + "Bc", + 9 + ], + [ + "CbAc", + "a", + 7 + ], + [ + "CbAcAC", + "bcbCB", + 8 + ], + [ + "CbAcB", + "aac", + 7 + ], + [ + "CbAcBCcaa", + "ccABcacAc", + 11 + ], + [ + "CbAcCb", + "AC", + 8 + ], + [ + "CbAcaAAA", + "accCbbACC", + 15 + ], + [ + "CbAcc", + "AABbcBBc", + 11 + ], + [ + "CbB", + "ABcCCBb", + 10 + ], + [ + "CbB", + "B", + 4 + ], + [ + "CbB", + "BB", + 3 + ], + [ + "CbB", + "BBb", + 4 + ], + [ + "CbB", + "ababacC", + 11 + ], + [ + "CbB", + "bAaaABc", + 12 + ], + [ + "CbB", + "cCb", + 4 + ], + [ + "CbBABACb", + "BCcBAA", + 10 + ], + [ + "CbBABBaA", + "BacaabAc", + 13 + ], + [ + "CbBAb", + "cB", + 7 + ], + [ + "CbBB", + "CcBCBCcAC", + 12 + ], + [ + "CbBBB", + "BCCcb", + 9 + ], + [ + "CbBBaC", + "ab", + 10 + ], + [ + "CbBBcCCBb", + "B", + 16 + ], + [ + "CbBCCbbB", + "cC", + 13 + ], + [ + "CbBa", + "a", + 6 + ], + [ + "CbBa", + "aAb", + 7 + ], + [ + "CbBabA", + "CcCB", + 9 + ], + [ + "CbBb", + "aBccCBA", + 11 + ], + [ + "CbBbC", + "CCCbbC", + 5 + ], + [ + "CbBbbba", + "BaCCABCcC", + 15 + ], + [ + "CbBbc", + "C", + 8 + ], + [ + "CbBc", + "aAaAAbBA", + 12 + ], + [ + "CbBcB", + "b", + 8 + ], + [ + "CbC", + "AcacbAB", + 11 + ], + [ + "CbC", + "BCAc", + 5 + ], + [ + "CbC", + "Bc", + 4 + ], + [ + "CbC", + "CbB", + 2 + ], + [ + "CbC", + "b", + 4 + ], + [ + "CbCAAbb", + "bAAaC", + 8 + ], + [ + "CbCABcaC", + "aCcAACB", + 12 + ], + [ + "CbCACA", + "baAac", + 8 + ], + [ + "CbCAa", + "cCbabBb", + 10 + ], + [ + "CbCB", + "BAcbcAba", + 11 + ], + [ + "CbCBBb", + "cAbACcA", + 11 + ], + [ + "CbCBC", + "cacCccCb", + 11 + ], + [ + "CbCBCAB", + "bBBcC", + 9 + ], + [ + "CbCBcCAaa", + "aAAaAa", + 14 + ], + [ + "CbCCCc", + "BcBCaba", + 10 + ], + [ + "CbCCbAccA", + "cCbBB", + 13 + ], + [ + "CbCCcCCaB", + "bcaaab", + 11 + ], + [ + "CbCa", + "cBBaCacA", + 10 + ], + [ + "CbCaAaB", + "acaa", + 9 + ], + [ + "CbCaB", + "ACbaa", + 6 + ], + [ + "CbCaB", + "AcbAa", + 7 + ], + [ + "CbCaBCb", + "bcA", + 10 + ], + [ + "CbCaa", + "ABCBCcAb", + 10 + ], + [ + "CbCaa", + "ba", + 6 + ], + [ + "CbCaaaC", + "bAcBcBaAa", + 12 + ], + [ + "CbCb", + "Bbc", + 5 + ], + [ + "CbCbaBa", + "AbacBbabc", + 10 + ], + [ + "CbCbcC", + "Ca", + 10 + ], + [ + "CbCc", + "ACC", + 5 + ], + [ + "CbCc", + "Cabbb", + 6 + ], + [ + "CbCcAaBaA", + "cbC", + 13 + ], + [ + "CbCcAcAc", + "AcBaCaAC", + 11 + ], + [ + "CbCcBAb", + "c", + 12 + ], + [ + "CbCcBC", + "CacBbCAc", + 10 + ], + [ + "CbCcBac", + "AAbAC", + 11 + ], + [ + "Cba", + "ABBBbaab", + 12 + ], + [ + "Cba", + "AcbAbB", + 8 + ], + [ + "Cba", + "C", + 4 + ], + [ + "Cba", + "CCCbbbC", + 10 + ], + [ + "Cba", + "CCaaAa", + 8 + ], + [ + "Cba", + "CcBCCAAAA", + 14 + ], + [ + "Cba", + "aABbaC", + 8 + ], + [ + "CbaA", + "CAbCBaB", + 8 + ], + [ + "CbaA", + "CBb", + 5 + ], + [ + "CbaABAC", + "aBCBCBc", + 12 + ], + [ + "CbaAaBCaB", + "AcCcB", + 12 + ], + [ + "CbaAcBb", + "C", + 12 + ], + [ + "CbaAcc", + "acaCaaccA", + 10 + ], + [ + "CbaBA", + "ab", + 7 + ], + [ + "CbaBBcBaB", + "bB", + 14 + ], + [ + "CbaBa", + "CBC", + 6 + ], + [ + "CbaBb", + "CCCbcB", + 8 + ], + [ + "CbaBba", + "BC", + 10 + ], + [ + "CbaBcAcbC", + "cc", + 14 + ], + [ + "CbaC", + "B", + 7 + ], + [ + "CbaCA", + "BAbaAacA", + 9 + ], + [ + "CbaCABBb", + "cAB", + 11 + ], + [ + "CbaCABcbA", + "BbaCbC", + 10 + ], + [ + "CbaCAaCc", + "a", + 14 + ], + [ + "CbaCAcCba", + "BCcaB", + 12 + ], + [ + "CbaCbCA", + "ccBBAcBc", + 12 + ], + [ + "CbaaA", + "BA", + 7 + ], + [ + "CbaaBA", + "AcccAAaac", + 14 + ], + [ + "CbabAACaC", + "cABaCcbB", + 13 + ], + [ + "CbabACa", + "A", + 12 + ], + [ + "CbabAac", + "aaaB", + 9 + ], + [ + "CbabAcC", + "cbb", + 9 + ], + [ + "CbabBBBcc", + "c", + 16 + ], + [ + "CbabBCCab", + "bB", + 14 + ], + [ + "CbabC", + "CacBB", + 7 + ], + [ + "CbabbBbb", + "BaAABCC", + 11 + ], + [ + "Cbabc", + "aACbC", + 7 + ], + [ + "CbabcB", + "CBcbAcb", + 6 + ], + [ + "CbacAab", + "c", + 12 + ], + [ + "CbacCAB", + "cCC", + 10 + ], + [ + "CbacCCb", + "caaC", + 9 + ], + [ + "CbacCaCAB", + "Cbbba", + 12 + ], + [ + "Cbacaca", + "BACAc", + 8 + ], + [ + "Cbb", + "Ababcb", + 8 + ], + [ + "Cbb", + "Bb", + 3 + ], + [ + "Cbb", + "CACbbBCb", + 10 + ], + [ + "Cbb", + "CBaABccaA", + 14 + ], + [ + "Cbb", + "abcb", + 4 + ], + [ + "Cbb", + "bACBBCCaa", + 14 + ], + [ + "Cbb", + "caCCACBaB", + 14 + ], + [ + "Cbb", + "cbAcABac", + 12 + ], + [ + "CbbA", + "cAACAA", + 9 + ], + [ + "CbbAACbC", + "ccaABC", + 9 + ], + [ + "CbbABCCA", + "CBCABCA", + 5 + ], + [ + "CbbAb", + "B", + 9 + ], + [ + "CbbAbbac", + "bCBbca", + 10 + ], + [ + "CbbAcaCbC", + "CAbBbcAc", + 11 + ], + [ + "CbbB", + "A", + 8 + ], + [ + "CbbB", + "C", + 6 + ], + [ + "CbbBA", + "BBCabAB", + 10 + ], + [ + "CbbBAbcA", + "CcCCAaBb", + 12 + ], + [ + "CbbBAcCba", + "BBbbCaBA", + 11 + ], + [ + "CbbBaBcB", + "cCabACB", + 10 + ], + [ + "CbbCCacBc", + "cbbAcacab", + 8 + ], + [ + "CbbaAacC", + "bcbcbbaBa", + 13 + ], + [ + "CbbaCBAb", + "aBCBBcC", + 13 + ], + [ + "CbbbBb", + "BA", + 10 + ], + [ + "CbbbaA", + "aacCccBbB", + 15 + ], + [ + "CbbbaACba", + "aCBba", + 12 + ], + [ + "Cbbbab", + "AABAbbc", + 11 + ], + [ + "Cbbc", + "bcaAb", + 9 + ], + [ + "Cbbc", + "cba", + 5 + ], + [ + "CbbcBcAc", + "BbC", + 12 + ], + [ + "CbbcCBacB", + "AaC", + 15 + ], + [ + "Cbbcbb", + "bBaBCBA", + 10 + ], + [ + "CbbcbcabC", + "AcBcBcba", + 10 + ], + [ + "Cbbcc", + "B", + 9 + ], + [ + "Cbbcc", + "CcbA", + 6 + ], + [ + "CbbccBAaB", + "cCA", + 13 + ], + [ + "Cbbccc", + "BcA", + 9 + ], + [ + "Cbc", + "A", + 6 + ], + [ + "Cbc", + "BAa", + 6 + ], + [ + "Cbc", + "BaabcacC", + 12 + ], + [ + "Cbc", + "CBbAc", + 4 + ], + [ + "Cbc", + "aCCbab", + 8 + ], + [ + "Cbc", + "baB", + 6 + ], + [ + "Cbc", + "bcB", + 4 + ], + [ + "Cbc", + "caACbaC", + 9 + ], + [ + "Cbc", + "cbabc", + 5 + ], + [ + "CbcAB", + "AabCABAb", + 9 + ], + [ + "CbcAaaBa", + "aACA", + 12 + ], + [ + "CbcB", + "CBcC", + 3 + ], + [ + "CbcBCbB", + "CAAacC", + 11 + ], + [ + "CbcBb", + "aaCACAAcC", + 15 + ], + [ + "CbcBcb", + "AabcC", + 9 + ], + [ + "CbcBcbC", + "aCa", + 13 + ], + [ + "CbcCAAc", + "aaBBbbc", + 12 + ], + [ + "CbcCAB", + "bbaCCcc", + 9 + ], + [ + "CbcCB", + "c", + 8 + ], + [ + "CbcCBB", + "aB", + 10 + ], + [ + "CbcCCa", + "BB", + 11 + ], + [ + "CbcCcA", + "aCaacCaAa", + 10 + ], + [ + "Cbca", + "cABAAaaA", + 12 + ], + [ + "CbcaC", + "bCcabCa", + 8 + ], + [ + "Cbcb", + "AA", + 8 + ], + [ + "Cbcb", + "AaCc", + 7 + ], + [ + "Cbcb", + "CbBAa", + 6 + ], + [ + "CbcbA", + "bccbACcC", + 10 + ], + [ + "CbcbB", + "aC", + 9 + ], + [ + "CbcbBCBC", + "cBB", + 10 + ], + [ + "CbcbC", + "a", + 10 + ], + [ + "Cbcbb", + "cccAB", + 6 + ], + [ + "Cbcbcbc", + "CbaccAcCC", + 9 + ], + [ + "Cbcc", + "BbAcAabc", + 10 + ], + [ + "Cbcc", + "bbBBA", + 8 + ], + [ + "CbccCAcBA", + "acCA", + 12 + ], + [ + "CbccaACc", + "AC", + 12 + ], + [ + "Cc", + "AAbABC", + 11 + ], + [ + "Cc", + "AAcbCAca", + 12 + ], + [ + "Cc", + "ABaC", + 7 + ], + [ + "Cc", + "AC", + 3 + ], + [ + "Cc", + "ACCcCa", + 8 + ], + [ + "Cc", + "AaCCC", + 7 + ], + [ + "Cc", + "BBCBCbbb", + 13 + ], + [ + "Cc", + "BbAbcC", + 10 + ], + [ + "Cc", + "Bc", + 2 + ], + [ + "Cc", + "Bccba", + 7 + ], + [ + "Cc", + "CABACbAcB", + 14 + ], + [ + "Cc", + "CAaCbCA", + 11 + ], + [ + "Cc", + "CCCCAaC", + 11 + ], + [ + "Cc", + "CCbCaAb", + 11 + ], + [ + "Cc", + "Ccb", + 2 + ], + [ + "Cc", + "a", + 4 + ], + [ + "Cc", + "aAABCBacb", + 14 + ], + [ + "Cc", + "aAAccbC", + 11 + ], + [ + "Cc", + "aBBCbc", + 8 + ], + [ + "Cc", + "aCAcaCca", + 12 + ], + [ + "Cc", + "bB", + 4 + ], + [ + "Cc", + "baABAABbC", + 17 + ], + [ + "Cc", + "bbacA", + 8 + ], + [ + "Cc", + "bc", + 2 + ], + [ + "Cc", + "c", + 2 + ], + [ + "Cc", + "cAaAAbc", + 11 + ], + [ + "Cc", + "cBbbBbcA", + 13 + ], + [ + "Cc", + "ca", + 3 + ], + [ + "Cc", + "ccAC", + 5 + ], + [ + "Cc", + "ccCb", + 5 + ], + [ + "CcA", + "BbcbaAB", + 10 + ], + [ + "CcA", + "aCA", + 3 + ], + [ + "CcA", + "bAAbca", + 9 + ], + [ + "CcA", + "bBccA", + 5 + ], + [ + "CcA", + "bCCBb", + 7 + ], + [ + "CcA", + "cBAbaC", + 9 + ], + [ + "CcAA", + "bcACbc", + 8 + ], + [ + "CcAAAAC", + "abCa", + 12 + ], + [ + "CcAAAB", + "bBAbaC", + 9 + ], + [ + "CcAAAcCb", + "cAcBcBBC", + 11 + ], + [ + "CcAABaC", + "BbC", + 10 + ], + [ + "CcAABbc", + "BCaAbab", + 9 + ], + [ + "CcAACB", + "b", + 11 + ], + [ + "CcAAaC", + "CAbaab", + 7 + ], + [ + "CcAB", + "BbcCBc", + 8 + ], + [ + "CcAB", + "accAaa", + 7 + ], + [ + "CcABACACb", + "abBBbCAA", + 12 + ], + [ + "CcABBccA", + "cc", + 12 + ], + [ + "CcABb", + "a", + 9 + ], + [ + "CcABbabBA", + "cCA", + 14 + ], + [ + "CcABcAaa", + "Ba", + 12 + ], + [ + "CcACAbc", + "AbBB", + 11 + ], + [ + "CcACbaCA", + "abbBaBbBa", + 16 + ], + [ + "CcACcCAa", + "AaB", + 13 + ], + [ + "CcAa", + "aAbBCCc", + 13 + ], + [ + "CcAa", + "ccBC", + 5 + ], + [ + "CcAaA", + "cbcC", + 8 + ], + [ + "CcAaB", + "babCBbCAC", + 15 + ], + [ + "CcAaCABA", + "cbaBaCCCb", + 12 + ], + [ + "CcAaCcbcB", + "aBbaCAAC", + 13 + ], + [ + "CcAac", + "Cabaac", + 5 + ], + [ + "CcAacBAaC", + "bCACB", + 12 + ], + [ + "CcAb", + "b", + 6 + ], + [ + "CcAbBcBaa", + "CBc", + 12 + ], + [ + "CcAbaaA", + "cCa", + 10 + ], + [ + "CcAbcaB", + "bcA", + 9 + ], + [ + "CcAc", + "AAaaABABA", + 16 + ], + [ + "CcAcAB", + "aabAabCcb", + 14 + ], + [ + "CcAcCcC", + "cACbbac", + 10 + ], + [ + "CcAcCcc", + "A", + 12 + ], + [ + "CcB", + "AbcBBCAac", + 14 + ], + [ + "CcB", + "Ac", + 4 + ], + [ + "CcB", + "Ccb", + 1 + ], + [ + "CcB", + "ba", + 6 + ], + [ + "CcB", + "cab", + 4 + ], + [ + "CcB", + "cbc", + 5 + ], + [ + "CcBABc", + "cAAC", + 7 + ], + [ + "CcBACb", + "ac", + 10 + ], + [ + "CcBAaAa", + "aCCcB", + 12 + ], + [ + "CcBB", + "bab", + 7 + ], + [ + "CcBBA", + "B", + 8 + ], + [ + "CcBBAb", + "aBCAAc", + 10 + ], + [ + "CcBBCAAC", + "A", + 14 + ], + [ + "CcBBbac", + "Bb", + 10 + ], + [ + "CcBCBaa", + "bbbbB", + 11 + ], + [ + "CcBCCaAbC", + "aAaCaCA", + 14 + ], + [ + "CcBCCbABb", + "BbBCbccaC", + 14 + ], + [ + "CcBCaacCB", + "CAbCcbA", + 11 + ], + [ + "CcBCc", + "C", + 8 + ], + [ + "CcBCc", + "cBcba", + 7 + ], + [ + "CcBCccbb", + "abaAAAa", + 15 + ], + [ + "CcBaBb", + "BAabCcBcB", + 12 + ], + [ + "CcBaa", + "abBac", + 6 + ], + [ + "CcBabaabc", + "bCB", + 15 + ], + [ + "CcBacBC", + "aa", + 12 + ], + [ + "CcBb", + "AAB", + 6 + ], + [ + "CcBb", + "BaAcbA", + 9 + ], + [ + "CcBbC", + "A", + 10 + ], + [ + "CcBbaCbb", + "abcBCCBab", + 11 + ], + [ + "CcBbbaB", + "baCb", + 11 + ], + [ + "CcBc", + "aBabCbbcB", + 13 + ], + [ + "CcBc", + "cabcACa", + 10 + ], + [ + "CcBcABAC", + "AAC", + 10 + ], + [ + "CcBcBCBaa", + "CBA", + 13 + ], + [ + "CcC", + "CBAacacBa", + 13 + ], + [ + "CcC", + "CBbcac", + 7 + ], + [ + "CcCAA", + "cB", + 8 + ], + [ + "CcCABC", + "ACA", + 8 + ], + [ + "CcCACCb", + "CbAbCccC", + 11 + ], + [ + "CcCAaCAaB", + "ABbAAaBb", + 12 + ], + [ + "CcCAbB", + "abb", + 8 + ], + [ + "CcCAbCBc", + "cbaCBaB", + 11 + ], + [ + "CcCB", + "ABbCBAc", + 10 + ], + [ + "CcCB", + "bcaabaccc", + 14 + ], + [ + "CcCB", + "cbBaC", + 8 + ], + [ + "CcCBA", + "CbAbcb", + 9 + ], + [ + "CcCBCcCA", + "BbCcaa", + 10 + ], + [ + "CcCBb", + "aBBC", + 8 + ], + [ + "CcCBc", + "BCBb", + 6 + ], + [ + "CcCC", + "b", + 8 + ], + [ + "CcCCAB", + "CCbB", + 6 + ], + [ + "CcCCBACCa", + "ABAABAAAb", + 14 + ], + [ + "CcCCacbB", + "aACA", + 13 + ], + [ + "CcCaA", + "baa", + 7 + ], + [ + "CcCaaaaA", + "bCbbACC", + 13 + ], + [ + "CcCacAB", + "BCCBacCcb", + 10 + ], + [ + "CcCacbC", + "ACBabB", + 9 + ], + [ + "CcCbB", + "BABCbb", + 7 + ], + [ + "CcCbCA", + "aBcA", + 8 + ], + [ + "CcCbCbBbc", + "ccabB", + 11 + ], + [ + "CcCba", + "BCb", + 6 + ], + [ + "CcCbaAbB", + "aBbcaCB", + 11 + ], + [ + "CcCbcaC", + "CcBbaCbCb", + 9 + ], + [ + "CcCc", + "cBAACa", + 9 + ], + [ + "CcCcAB", + "aCBbA", + 9 + ], + [ + "CcCcAc", + "baBacaB", + 11 + ], + [ + "CcCcBB", + "bcbBbcAbB", + 11 + ], + [ + "CcCcCCAb", + "BCBBaABab", + 13 + ], + [ + "CcCca", + "bCaBAB", + 10 + ], + [ + "CcCcaB", + "aAacCCb", + 10 + ], + [ + "CcCcbCb", + "BBBCBaCC", + 12 + ], + [ + "CcCcbCb", + "cC", + 10 + ], + [ + "CcCccCbbc", + "cabBaCB", + 14 + ], + [ + "Cca", + "AbbaBABCA", + 16 + ], + [ + "Cca", + "AcacBcc", + 10 + ], + [ + "Cca", + "a", + 4 + ], + [ + "Cca", + "b", + 6 + ], + [ + "Cca", + "bAbaCBB", + 12 + ], + [ + "CcaAB", + "B", + 8 + ], + [ + "CcaABA", + "a", + 10 + ], + [ + "CcaAC", + "BaCbAbaAa", + 12 + ], + [ + "CcaACB", + "BcBbAAA", + 10 + ], + [ + "CcaACCA", + "bCacCB", + 8 + ], + [ + "CcaAa", + "cCC", + 8 + ], + [ + "CcaAbCCC", + "AbCBCb", + 10 + ], + [ + "CcaAcb", + "bcBAa", + 8 + ], + [ + "CcaAcc", + "babAAAcbB", + 13 + ], + [ + "CcaB", + "BbaCbBbCB", + 14 + ], + [ + "CcaBA", + "ACCCABCBC", + 12 + ], + [ + "CcaBaBC", + "bacAbAab", + 11 + ], + [ + "CcaBaa", + "A", + 11 + ], + [ + "CcaBcBAB", + "Ac", + 13 + ], + [ + "CcaBcC", + "A", + 11 + ], + [ + "CcaCAaabB", + "CABCacccB", + 11 + ], + [ + "CcaCCAAA", + "BCAaCCcc", + 10 + ], + [ + "CcaCa", + "BAa", + 7 + ], + [ + "CcaCabAcb", + "BaaaBAC", + 10 + ], + [ + "CcaCacbba", + "bccAB", + 13 + ], + [ + "CcaCbbA", + "CcB", + 9 + ], + [ + "CcaCcBc", + "cbaCa", + 9 + ], + [ + "Ccaa", + "AabAcaC", + 10 + ], + [ + "CcaaA", + "aaCAA", + 7 + ], + [ + "CcaaAAbc", + "aABCaCacc", + 13 + ], + [ + "CcaaBC", + "abBCaC", + 10 + ], + [ + "Ccaaa", + "cc", + 7 + ], + [ + "CcaaaAB", + "bc", + 12 + ], + [ + "Ccaaaa", + "ABbbbb", + 12 + ], + [ + "Ccaaabccc", + "cA", + 15 + ], + [ + "CcaabbCCA", + "Bbc", + 14 + ], + [ + "Ccab", + "AC", + 7 + ], + [ + "Ccab", + "Cba", + 4 + ], + [ + "Ccab", + "abBAaBB", + 11 + ], + [ + "Ccab", + "abbBCaBb", + 11 + ], + [ + "CcabACcb", + "cCAaC", + 10 + ], + [ + "Ccac", + "cbCCA", + 8 + ], + [ + "CcacB", + "cCBbaCB", + 7 + ], + [ + "CcacBabBC", + "CaBBaB", + 8 + ], + [ + "CcacC", + "AbbacBa", + 10 + ], + [ + "CcacCABB", + "ba", + 14 + ], + [ + "CcacCaA", + "CaAC", + 8 + ], + [ + "CcacaBB", + "B", + 12 + ], + [ + "CcacbC", + "cCb", + 7 + ], + [ + "Ccb", + "AcbABaCC", + 12 + ], + [ + "Ccb", + "CAA", + 4 + ], + [ + "Ccb", + "CbAcC", + 6 + ], + [ + "Ccb", + "c", + 4 + ], + [ + "CcbA", + "BccAc", + 6 + ], + [ + "CcbA", + "aabCBb", + 10 + ], + [ + "CcbA", + "bABb", + 7 + ], + [ + "CcbAACA", + "bcc", + 11 + ], + [ + "CcbAACB", + "acBBAcac", + 10 + ], + [ + "CcbAAaA", + "ccBCaCa", + 8 + ], + [ + "CcbACBA", + "A", + 12 + ], + [ + "CcbACC", + "bbaC", + 7 + ], + [ + "CcbAaBcAA", + "bAcac", + 11 + ], + [ + "CcbAc", + "AcbbBBBa", + 12 + ], + [ + "CcbBAbCcC", + "AAb", + 14 + ], + [ + "CcbBaCC", + "ABcA", + 11 + ], + [ + "CcbBac", + "BCAAA", + 10 + ], + [ + "CcbBbBBA", + "AbB", + 12 + ], + [ + "CcbBbbbbb", + "bb", + 14 + ], + [ + "CcbBc", + "cBAcacB", + 11 + ], + [ + "CcbBcAC", + "BbCcAbcbB", + 11 + ], + [ + "CcbBcaA", + "bbcca", + 8 + ], + [ + "CcbBcc", + "bbcc", + 5 + ], + [ + "CcbCA", + "a", + 9 + ], + [ + "CcbCA", + "aabAA", + 6 + ], + [ + "CcbCbB", + "CACab", + 7 + ], + [ + "CcbCbCA", + "BcaCaCAb", + 8 + ], + [ + "CcbCc", + "AC", + 8 + ], + [ + "CcbCc", + "cBbbc", + 5 + ], + [ + "CcbCcC", + "c", + 10 + ], + [ + "CcbaccBCb", + "bbCBA", + 13 + ], + [ + "CcbbABb", + "CbCbacB", + 8 + ], + [ + "CcbbaAbbB", + "bc", + 16 + ], + [ + "Ccbbb", + "CBAcACB", + 9 + ], + [ + "CcbbbC", + "cBacabCb", + 11 + ], + [ + "CcbbbCbc", + "BbCAaBaB", + 15 + ], + [ + "CcbbcABBa", + "BcAcCcBCB", + 13 + ], + [ + "Ccbc", + "ABAaaBAb", + 15 + ], + [ + "Ccc", + "B", + 6 + ], + [ + "Ccc", + "BABa", + 8 + ], + [ + "Ccc", + "BcaBAc", + 8 + ], + [ + "Ccc", + "CBB", + 4 + ], + [ + "Ccc", + "CC", + 3 + ], + [ + "Ccc", + "aaaaCC", + 10 + ], + [ + "Ccc", + "ac", + 4 + ], + [ + "Ccc", + "bb", + 6 + ], + [ + "CccABA", + "AAbBcA", + 10 + ], + [ + "CccACCB", + "AcbaC", + 9 + ], + [ + "CccAaB", + "baCaBcCa", + 12 + ], + [ + "CccAb", + "aABBc", + 10 + ], + [ + "CccAcbbca", + "c", + 16 + ], + [ + "CccBBBcB", + "bBbbCa", + 11 + ], + [ + "CccBBbC", + "Bba", + 10 + ], + [ + "CccBa", + "B", + 8 + ], + [ + "CccBa", + "caBA", + 5 + ], + [ + "CccBaaAB", + "C", + 14 + ], + [ + "CccBbBCb", + "a", + 16 + ], + [ + "CccBbca", + "BacAbCBbC", + 12 + ], + [ + "CccCAa", + "CcBcBCCc", + 8 + ], + [ + "CccCCa", + "Ba", + 10 + ], + [ + "CccCa", + "aA", + 9 + ], + [ + "CccCb", + "baA", + 10 + ], + [ + "Ccca", + "A", + 7 + ], + [ + "CccaABbBb", + "aAcB", + 12 + ], + [ + "CccaBBCb", + "BBbaaCCC", + 12 + ], + [ + "Cccaa", + "BbbC", + 10 + ], + [ + "CccacCBb", + "ccBa", + 10 + ], + [ + "CccbABBbb", + "B", + 16 + ], + [ + "Cccbbc", + "CCcBcBa", + 7 + ], + [ + "Cccc", + "BaCbb", + 9 + ], + [ + "CcccAB", + "aCBAB", + 7 + ], + [ + "CcccAbbA", + "BCBcBc", + 12 + ], + [ + "CcccBacc", + "BCAcB", + 11 + ], + [ + "CcccBcCB", + "cCAb", + 12 + ], + [ + "a", + "AA", + 3 + ], + [ + "a", + "AABaCbca", + 14 + ], + [ + "a", + "AACaBBAC", + 14 + ], + [ + "a", + "ABCaa", + 8 + ], + [ + "a", + "ABaaBCCb", + 14 + ], + [ + "a", + "ABab", + 6 + ], + [ + "a", + "ABcB", + 7 + ], + [ + "a", + "ACbcCBCB", + 15 + ], + [ + "a", + "ACbcaA", + 10 + ], + [ + "a", + "AaAA", + 6 + ], + [ + "a", + "AaABacaAA", + 16 + ], + [ + "a", + "AaAbBca", + 12 + ], + [ + "a", + "AabBCaab", + 14 + ], + [ + "a", + "AabCbCb", + 12 + ], + [ + "a", + "AaccB", + 8 + ], + [ + "a", + "Ab", + 3 + ], + [ + "a", + "AbBBaB", + 10 + ], + [ + "a", + "AbCb", + 7 + ], + [ + "a", + "AbCc", + 7 + ], + [ + "a", + "AbbbBacAa", + 16 + ], + [ + "a", + "AbcCaCbac", + 16 + ], + [ + "a", + "AcABaA", + 10 + ], + [ + "a", + "AcACCA", + 11 + ], + [ + "a", + "AcCbc", + 9 + ], + [ + "a", + "AcaB", + 6 + ], + [ + "a", + "B", + 2 + ], + [ + "a", + "BA", + 3 + ], + [ + "a", + "BABAAcBBc", + 17 + ], + [ + "a", + "BABaBa", + 10 + ], + [ + "a", + "BAC", + 5 + ], + [ + "a", + "BAaAbAc", + 12 + ], + [ + "a", + "BAabccCBc", + 16 + ], + [ + "a", + "BAbaaa", + 10 + ], + [ + "a", + "BAbb", + 7 + ], + [ + "a", + "BBAcbBB", + 13 + ], + [ + "a", + "BBBaCab", + 12 + ], + [ + "a", + "BBC", + 6 + ], + [ + "a", + "BBaCbBAC", + 14 + ], + [ + "a", + "BBcca", + 8 + ], + [ + "a", + "BCA", + 5 + ], + [ + "a", + "BCBab", + 8 + ], + [ + "a", + "BaAAAcAbc", + 16 + ], + [ + "a", + "BaAcABBC", + 14 + ], + [ + "a", + "BaBcaC", + 10 + ], + [ + "a", + "BaC", + 4 + ], + [ + "a", + "BaabBAabb", + 16 + ], + [ + "a", + "BbCaCa", + 10 + ], + [ + "a", + "BbcACCA", + 13 + ], + [ + "a", + "Bbcba", + 8 + ], + [ + "a", + "BcAb", + 7 + ], + [ + "a", + "BcBABC", + 11 + ], + [ + "a", + "BcBaAacC", + 14 + ], + [ + "a", + "Bca", + 4 + ], + [ + "a", + "Bcabba", + 10 + ], + [ + "a", + "BccCbbAb", + 15 + ], + [ + "a", + "C", + 2 + ], + [ + "a", + "CA", + 3 + ], + [ + "a", + "CABAcBbc", + 15 + ], + [ + "a", + "CABCB", + 9 + ], + [ + "a", + "CAbC", + 7 + ], + [ + "a", + "CAba", + 6 + ], + [ + "a", + "CAcCAa", + 10 + ], + [ + "a", + "CAcc", + 7 + ], + [ + "a", + "CBA", + 5 + ], + [ + "a", + "CBABB", + 9 + ], + [ + "a", + "CBAa", + 6 + ], + [ + "a", + "CBBCcB", + 12 + ], + [ + "a", + "CBBcA", + 9 + ], + [ + "a", + "CBaCAC", + 10 + ], + [ + "a", + "CBbaCCbC", + 14 + ], + [ + "a", + "CCBc", + 8 + ], + [ + "a", + "CCcaC", + 8 + ], + [ + "a", + "Ca", + 2 + ], + [ + "a", + "CaAaC", + 8 + ], + [ + "a", + "CaBacbb", + 12 + ], + [ + "a", + "CaabacC", + 12 + ], + [ + "a", + "CbBc", + 8 + ], + [ + "a", + "CbbcAB", + 11 + ], + [ + "a", + "CcBBaaCc", + 14 + ], + [ + "a", + "CcBbabBAc", + 16 + ], + [ + "a", + "CcaACcB", + 12 + ], + [ + "a", + "Ccca", + 6 + ], + [ + "a", + "a", + 0 + ], + [ + "a", + "aA", + 2 + ], + [ + "a", + "aAACAbCC", + 14 + ], + [ + "a", + "aACABcAb", + 14 + ], + [ + "a", + "aAbaB", + 8 + ], + [ + "a", + "aBB", + 4 + ], + [ + "a", + "aBBCb", + 8 + ], + [ + "a", + "aBaCbb", + 10 + ], + [ + "a", + "aBb", + 4 + ], + [ + "a", + "aC", + 2 + ], + [ + "a", + "aCCCCcab", + 14 + ], + [ + "a", + "aCaBBABa", + 14 + ], + [ + "a", + "aCaC", + 6 + ], + [ + "a", + "aCb", + 4 + ], + [ + "a", + "aCcBaAa", + 12 + ], + [ + "a", + "aa", + 2 + ], + [ + "a", + "aaBbC", + 8 + ], + [ + "a", + "aaCA", + 6 + ], + [ + "a", + "ab", + 2 + ], + [ + "a", + "abACBBAcC", + 16 + ], + [ + "a", + "abBbBba", + 12 + ], + [ + "a", + "abCaAaCbC", + 16 + ], + [ + "a", + "aba", + 4 + ], + [ + "a", + "abcac", + 8 + ], + [ + "a", + "acAcBAbaB", + 16 + ], + [ + "a", + "acBcAA", + 10 + ], + [ + "a", + "accCCCc", + 12 + ], + [ + "a", + "b", + 2 + ], + [ + "a", + "bA", + 3 + ], + [ + "a", + "bAAaB", + 8 + ], + [ + "a", + "bABBa", + 8 + ], + [ + "a", + "bACca", + 8 + ], + [ + "a", + "bAbAccb", + 13 + ], + [ + "a", + "bAccbcaAB", + 16 + ], + [ + "a", + "bBAca", + 8 + ], + [ + "a", + "bBBC", + 8 + ], + [ + "a", + "bBBCaccC", + 14 + ], + [ + "a", + "bBCb", + 8 + ], + [ + "a", + "bBbC", + 8 + ], + [ + "a", + "bBbCABa", + 12 + ], + [ + "a", + "bC", + 4 + ], + [ + "a", + "bCBacAcC", + 14 + ], + [ + "a", + "bCC", + 6 + ], + [ + "a", + "bCCbaccA", + 14 + ], + [ + "a", + "bCbA", + 7 + ], + [ + "a", + "bCbBab", + 10 + ], + [ + "a", + "bCcaCbc", + 12 + ], + [ + "a", + "bCcaaCbB", + 14 + ], + [ + "a", + "ba", + 2 + ], + [ + "a", + "baa", + 4 + ], + [ + "a", + "baacacaba", + 16 + ], + [ + "a", + "babcA", + 8 + ], + [ + "a", + "babcCAA", + 12 + ], + [ + "a", + "bb", + 4 + ], + [ + "a", + "bbAc", + 7 + ], + [ + "a", + "bbB", + 6 + ], + [ + "a", + "bbBA", + 7 + ], + [ + "a", + "bbBBcaBc", + 14 + ], + [ + "a", + "bbBbAaAAc", + 16 + ], + [ + "a", + "bbCCaACb", + 14 + ], + [ + "a", + "bbCaca", + 10 + ], + [ + "a", + "bbaC", + 6 + ], + [ + "a", + "bbbAcAB", + 13 + ], + [ + "a", + "bbbBAABC", + 15 + ], + [ + "a", + "bbcCBcB", + 14 + ], + [ + "a", + "bcAca", + 8 + ], + [ + "a", + "bcAcacCB", + 14 + ], + [ + "a", + "bcaAcBAbC", + 16 + ], + [ + "a", + "bcb", + 6 + ], + [ + "a", + "bcc", + 6 + ], + [ + "a", + "c", + 2 + ], + [ + "a", + "cAC", + 5 + ], + [ + "a", + "cACBACca", + 14 + ], + [ + "a", + "cACaCa", + 10 + ], + [ + "a", + "cAa", + 4 + ], + [ + "a", + "cBAaa", + 8 + ], + [ + "a", + "cBbBbab", + 12 + ], + [ + "a", + "cC", + 4 + ], + [ + "a", + "cCBA", + 7 + ], + [ + "a", + "cCCa", + 6 + ], + [ + "a", + "cCCbaa", + 10 + ], + [ + "a", + "cCaCcb", + 10 + ], + [ + "a", + "cCacac", + 10 + ], + [ + "a", + "cCcBbCBAa", + 16 + ], + [ + "a", + "cabbcaBBc", + 16 + ], + [ + "a", + "cb", + 4 + ], + [ + "a", + "cbAcAab", + 12 + ], + [ + "a", + "cbAcCB", + 11 + ], + [ + "a", + "cbBaC", + 8 + ], + [ + "a", + "cbCcbC", + 12 + ], + [ + "a", + "cbaCbbBb", + 14 + ], + [ + "a", + "cbbAaCA", + 12 + ], + [ + "a", + "ccACCaBAa", + 16 + ], + [ + "a", + "ccACbaC", + 12 + ], + [ + "a", + "ccAbAC", + 11 + ], + [ + "a", + "ccAbbaBA", + 14 + ], + [ + "a", + "ccCBaCcB", + 14 + ], + [ + "a", + "ccac", + 6 + ], + [ + "aA", + "AAc", + 3 + ], + [ + "aA", + "ABcACbCcb", + 15 + ], + [ + "aA", + "ACcbCBA", + 11 + ], + [ + "aA", + "AacACCa", + 10 + ], + [ + "aA", + "B", + 4 + ], + [ + "aA", + "BabBBcCbC", + 16 + ], + [ + "aA", + "Babb", + 6 + ], + [ + "aA", + "BcAcB", + 8 + ], + [ + "aA", + "CAacB", + 8 + ], + [ + "aA", + "CBABBaAB", + 12 + ], + [ + "aA", + "CaBbbbA", + 10 + ], + [ + "aA", + "CcAabB", + 10 + ], + [ + "aA", + "CcaAb", + 6 + ], + [ + "aA", + "a", + 2 + ], + [ + "aA", + "aCAcAb", + 8 + ], + [ + "aA", + "aCBb", + 6 + ], + [ + "aA", + "aCacaAA", + 10 + ], + [ + "aA", + "aaACBacA", + 12 + ], + [ + "aA", + "aaC", + 3 + ], + [ + "aA", + "abbAaBC", + 10 + ], + [ + "aA", + "abbBBbc", + 12 + ], + [ + "aA", + "bAc", + 4 + ], + [ + "aA", + "bB", + 4 + ], + [ + "aA", + "bBCBC", + 10 + ], + [ + "aA", + "baCBAbCa", + 12 + ], + [ + "aA", + "cABBaA", + 8 + ], + [ + "aA", + "cBc", + 6 + ], + [ + "aA", + "cabbabbA", + 12 + ], + [ + "aA", + "ccbcB", + 10 + ], + [ + "aAA", + "aACbBCCb", + 12 + ], + [ + "aAA", + "ac", + 4 + ], + [ + "aAA", + "bAc", + 4 + ], + [ + "aAAA", + "AaAC", + 4 + ], + [ + "aAAAA", + "c", + 10 + ], + [ + "aAAAAC", + "CbaaCCBBa", + 14 + ], + [ + "aAAAAaAA", + "cbAabBb", + 13 + ], + [ + "aAAABb", + "BABBaB", + 9 + ], + [ + "aAAAC", + "bC", + 8 + ], + [ + "aAAAaAb", + "AAC", + 10 + ], + [ + "aAAAabBca", + "AB", + 14 + ], + [ + "aAAAcCbCB", + "CcBbABBB", + 15 + ], + [ + "aAAB", + "A", + 6 + ], + [ + "aAAB", + "bcabAb", + 7 + ], + [ + "aAAB", + "c", + 8 + ], + [ + "aAAB", + "cCAB", + 4 + ], + [ + "aAABCcCcC", + "CbBBCAB", + 14 + ], + [ + "aAABbBACb", + "aACb", + 10 + ], + [ + "aAACC", + "ccbbaBA", + 13 + ], + [ + "aAACaCa", + "ACbabAc", + 11 + ], + [ + "aAAaAAaa", + "ACBcbbcaA", + 14 + ], + [ + "aAAaaaB", + "BABbaaAbA", + 10 + ], + [ + "aAAabaAbA", + "bcCBbaBB", + 13 + ], + [ + "aAAacaaC", + "a", + 14 + ], + [ + "aAAb", + "aCAA", + 4 + ], + [ + "aAAbBbaA", + "BaaaAbb", + 11 + ], + [ + "aAAbCbCcC", + "cBC", + 14 + ], + [ + "aAAbbA", + "bcbbaBa", + 10 + ], + [ + "aAAc", + "AccBbB", + 10 + ], + [ + "aAAcA", + "BBBABa", + 9 + ], + [ + "aAAcA", + "bb", + 10 + ], + [ + "aAAcAA", + "aCAA", + 5 + ], + [ + "aAAcAa", + "CbCAa", + 7 + ], + [ + "aAAcAaa", + "bAAACb", + 8 + ], + [ + "aAAcB", + "AbcACA", + 8 + ], + [ + "aAAcBBba", + "BcaAcACb", + 11 + ], + [ + "aAAcCCbCB", + "C", + 16 + ], + [ + "aAAcCaCc", + "caACAaAa", + 10 + ], + [ + "aAAcCb", + "bbAbA", + 10 + ], + [ + "aAAcabaab", + "cAACAbb", + 8 + ], + [ + "aAAcbACab", + "bCcAaBbCC", + 15 + ], + [ + "aAAcc", + "aaAC", + 4 + ], + [ + "aAB", + "AAaAcCaa", + 12 + ], + [ + "aAB", + "AbCBB", + 7 + ], + [ + "aAB", + "AcaAB", + 4 + ], + [ + "aAB", + "CAbaA", + 7 + ], + [ + "aAB", + "CCBAcbaCC", + 15 + ], + [ + "aAB", + "aBcbbB", + 8 + ], + [ + "aAB", + "aCCcaBCC", + 11 + ], + [ + "aAB", + "bcbccABCA", + 14 + ], + [ + "aABA", + "cCac", + 8 + ], + [ + "aABAB", + "AaaBBcCA", + 11 + ], + [ + "aABACAa", + "CcBBbA", + 10 + ], + [ + "aABAaA", + "C", + 12 + ], + [ + "aABAb", + "BbBCC", + 8 + ], + [ + "aABAb", + "ca", + 9 + ], + [ + "aABAbab", + "a", + 12 + ], + [ + "aABAc", + "AabAabaB", + 10 + ], + [ + "aABAcCbbA", + "aB", + 14 + ], + [ + "aABB", + "CCAaaccbA", + 14 + ], + [ + "aABB", + "aCac", + 6 + ], + [ + "aABB", + "cb", + 7 + ], + [ + "aABBBBCc", + "cBAcbb", + 13 + ], + [ + "aABBBBa", + "CaBaCCBB", + 11 + ], + [ + "aABBCc", + "cc", + 9 + ], + [ + "aABBacCA", + "BBbacBc", + 9 + ], + [ + "aABBbA", + "aCBbC", + 6 + ], + [ + "aABBbaB", + "CbbBCbacb", + 10 + ], + [ + "aABCAa", + "bBbC", + 9 + ], + [ + "aABCB", + "cAB", + 6 + ], + [ + "aABCCcbB", + "CBbaCcbc", + 9 + ], + [ + "aABCaca", + "Bb", + 12 + ], + [ + "aABCbCba", + "CabccBaB", + 11 + ], + [ + "aABaBA", + "BC", + 10 + ], + [ + "aABaBbAAA", + "B", + 16 + ], + [ + "aABb", + "BC", + 6 + ], + [ + "aABbAa", + "bCbBcC", + 10 + ], + [ + "aABbBbCca", + "ABCCBAbAc", + 12 + ], + [ + "aABbaA", + "bcBC", + 10 + ], + [ + "aABbac", + "AbBbABAAb", + 12 + ], + [ + "aABbccca", + "BcaA", + 11 + ], + [ + "aABcACB", + "A", + 12 + ], + [ + "aABccB", + "A", + 10 + ], + [ + "aAC", + "BA", + 4 + ], + [ + "aAC", + "CAbbaab", + 11 + ], + [ + "aAC", + "CbC", + 4 + ], + [ + "aAC", + "b", + 6 + ], + [ + "aAC", + "bACCbC", + 8 + ], + [ + "aAC", + "bcaBacc", + 10 + ], + [ + "aACABAcCa", + "ccCB", + 13 + ], + [ + "aACBA", + "Aa", + 7 + ], + [ + "aACBACA", + "BaCBa", + 8 + ], + [ + "aACBBcBC", + "A", + 14 + ], + [ + "aACCA", + "Cb", + 8 + ], + [ + "aACCAAb", + "Bca", + 12 + ], + [ + "aACCBc", + "AcbAaCB", + 9 + ], + [ + "aACCa", + "ACccABA", + 10 + ], + [ + "aACCbAA", + "AAaa", + 9 + ], + [ + "aACCc", + "BBcc", + 7 + ], + [ + "aACCc", + "cbaBAC", + 10 + ], + [ + "aACaaA", + "bcACaCbA", + 8 + ], + [ + "aACacACa", + "aaBccaB", + 10 + ], + [ + "aACacCb", + "C", + 12 + ], + [ + "aACaccCAC", + "cC", + 14 + ], + [ + "aACbAC", + "AB", + 9 + ], + [ + "aACbBBcb", + "b", + 14 + ], + [ + "aACbbbaBC", + "aBBACCB", + 13 + ], + [ + "aACcaBb", + "ABabA", + 9 + ], + [ + "aACcbCa", + "b", + 12 + ], + [ + "aAa", + "BbBb", + 8 + ], + [ + "aAa", + "CBCBAabb", + 12 + ], + [ + "aAa", + "Cc", + 6 + ], + [ + "aAa", + "bCaaCBcAa", + 12 + ], + [ + "aAa", + "bc", + 6 + ], + [ + "aAaA", + "b", + 8 + ], + [ + "aAaAcc", + "B", + 12 + ], + [ + "aAaAccAb", + "BCAbabcAA", + 11 + ], + [ + "aAaBBCb", + "Cbbc", + 11 + ], + [ + "aAaBaBBBc", + "cAcCCCA", + 16 + ], + [ + "aAaBaBa", + "bBbBc", + 10 + ], + [ + "aAaCB", + "cCc", + 8 + ], + [ + "aAaCCCc", + "bbcBabaAA", + 16 + ], + [ + "aAaCaaAc", + "BCaA", + 10 + ], + [ + "aAaa", + "bb", + 8 + ], + [ + "aAaaAcCA", + "cBbcccB", + 13 + ], + [ + "aAaaBbbb", + "bCca", + 14 + ], + [ + "aAaaC", + "aCAC", + 5 + ], + [ + "aAaaCBa", + "aaCBCc", + 8 + ], + [ + "aAaaaA", + "aBACbaBAC", + 10 + ], + [ + "aAaabBcb", + "AcCAcBA", + 12 + ], + [ + "aAab", + "C", + 8 + ], + [ + "aAab", + "bCaB", + 5 + ], + [ + "aAab", + "bbaacCCB", + 12 + ], + [ + "aAabC", + "ccbBa", + 9 + ], + [ + "aAabCAc", + "aBCb", + 9 + ], + [ + "aAabCCBaa", + "bBC", + 14 + ], + [ + "aAabbbbB", + "CBAACBCCb", + 13 + ], + [ + "aAabbcCaA", + "A", + 16 + ], + [ + "aAac", + "cCBbCAaBB", + 14 + ], + [ + "aAacB", + "cAcbcBa", + 8 + ], + [ + "aAacBcCBa", + "cba", + 13 + ], + [ + "aAb", + "A", + 4 + ], + [ + "aAb", + "a", + 4 + ], + [ + "aAb", + "b", + 4 + ], + [ + "aAbA", + "bccaaaCAB", + 13 + ], + [ + "aAbABAA", + "bCBAcabBb", + 14 + ], + [ + "aAbAC", + "cACcCa", + 8 + ], + [ + "aAbACaBAA", + "CB", + 14 + ], + [ + "aAbAb", + "cBBabc", + 8 + ], + [ + "aAbAcCb", + "c", + 12 + ], + [ + "aAbBB", + "AC", + 8 + ], + [ + "aAbBaBcAA", + "C", + 17 + ], + [ + "aAbCAcbCC", + "BAAaA", + 14 + ], + [ + "aAbCBA", + "aCBaCABc", + 9 + ], + [ + "aAbCCACCb", + "AbcacbAb", + 11 + ], + [ + "aAbCCC", + "Aca", + 9 + ], + [ + "aAbCa", + "bABc", + 6 + ], + [ + "aAbCbCa", + "AabbBCbA", + 8 + ], + [ + "aAbCbbaba", + "CcBbccaB", + 14 + ], + [ + "aAbCc", + "cCa", + 8 + ], + [ + "aAba", + "BbcbcCBcC", + 16 + ], + [ + "aAbaBA", + "aBAAAA", + 7 + ], + [ + "aAbaBBba", + "Cb", + 14 + ], + [ + "aAbaCAA", + "cAB", + 11 + ], + [ + "aAbaaBA", + "bcCbaccBA", + 10 + ], + [ + "aAbb", + "bBBaACCBC", + 13 + ], + [ + "aAbbABabC", + "cbACbB", + 12 + ], + [ + "aAbbBab", + "caaA", + 11 + ], + [ + "aAbbCAA", + "BBbBcbA", + 8 + ], + [ + "aAbbCbCCa", + "AAB", + 14 + ], + [ + "aAbbCbcb", + "CBc", + 11 + ], + [ + "aAbbaB", + "a", + 10 + ], + [ + "aAbbacAb", + "BccBb", + 11 + ], + [ + "aAbbcbA", + "A", + 12 + ], + [ + "aAbcCaA", + "CCc", + 11 + ], + [ + "aAbcaABCA", + "BCAcbCab", + 13 + ], + [ + "aAbcaAa", + "CCababB", + 11 + ], + [ + "aAbcbbB", + "baba", + 10 + ], + [ + "aAc", + "aBbBCbac", + 11 + ], + [ + "aAc", + "abbAbaC", + 9 + ], + [ + "aAcA", + "A", + 6 + ], + [ + "aAcA", + "ACABBCB", + 10 + ], + [ + "aAcAAb", + "ACC", + 9 + ], + [ + "aAcAAbB", + "cAbbBaCBb", + 13 + ], + [ + "aAcAabacA", + "aCbBcA", + 9 + ], + [ + "aAcAacaba", + "caACb", + 11 + ], + [ + "aAcAbaCac", + "bBc", + 14 + ], + [ + "aAcAcAbaB", + "bcCCa", + 13 + ], + [ + "aAcB", + "aaaCC", + 6 + ], + [ + "aAcB", + "caC", + 6 + ], + [ + "aAcBACBB", + "a", + 14 + ], + [ + "aAcBC", + "aCca", + 6 + ], + [ + "aAcBbABB", + "AA", + 12 + ], + [ + "aAcBbAaBC", + "BbCbaCcaC", + 14 + ], + [ + "aAcCAaC", + "b", + 14 + ], + [ + "aAcCCcAc", + "baabCBAaA", + 12 + ], + [ + "aAcCccBB", + "AaAa", + 14 + ], + [ + "aAcaAaB", + "AcC", + 10 + ], + [ + "aAcaBaA", + "ACcaaA", + 5 + ], + [ + "aAcaaac", + "BaABaBaBA", + 10 + ], + [ + "aAcb", + "AABbA", + 5 + ], + [ + "aAcb", + "aABC", + 4 + ], + [ + "aAcbA", + "cbcAAC", + 8 + ], + [ + "aAcbBb", + "CbcccCBcC", + 14 + ], + [ + "aAcbBcca", + "A", + 14 + ], + [ + "aAcba", + "acBcBBBAc", + 12 + ], + [ + "aAcba", + "b", + 8 + ], + [ + "aAcc", + "CACC", + 4 + ], + [ + "aAccac", + "ACABCBBAC", + 12 + ], + [ + "aAccbCaba", + "BCBbBc", + 14 + ], + [ + "aB", + "A", + 3 + ], + [ + "aB", + "AA", + 3 + ], + [ + "aB", + "AB", + 1 + ], + [ + "aB", + "ABAb", + 5 + ], + [ + "aB", + "ACcb", + 6 + ], + [ + "aB", + "Aabaaa", + 9 + ], + [ + "aB", + "AccabABB", + 12 + ], + [ + "aB", + "B", + 2 + ], + [ + "aB", + "BBcbB", + 8 + ], + [ + "aB", + "BCc", + 6 + ], + [ + "aB", + "Bca", + 6 + ], + [ + "aB", + "C", + 4 + ], + [ + "aB", + "CAAaa", + 8 + ], + [ + "aB", + "CbccACC", + 13 + ], + [ + "aB", + "Cc", + 4 + ], + [ + "aB", + "CcAcCC", + 11 + ], + [ + "aB", + "aCbBBc", + 8 + ], + [ + "aB", + "aaAbCb", + 9 + ], + [ + "aB", + "aaaAb", + 7 + ], + [ + "aB", + "aabCACc", + 11 + ], + [ + "aB", + "abAC", + 5 + ], + [ + "aB", + "bB", + 2 + ], + [ + "aB", + "bBB", + 4 + ], + [ + "aB", + "bb", + 3 + ], + [ + "aB", + "bbbca", + 9 + ], + [ + "aB", + "bbc", + 5 + ], + [ + "aB", + "bc", + 4 + ], + [ + "aB", + "bcb", + 5 + ], + [ + "aB", + "cB", + 2 + ], + [ + "aB", + "cCC", + 6 + ], + [ + "aB", + "cc", + 4 + ], + [ + "aBA", + "BAaAaaCBc", + 14 + ], + [ + "aBAAA", + "CBCAbbcAA", + 10 + ], + [ + "aBAAB", + "BacBABcAc", + 10 + ], + [ + "aBAAaB", + "caAc", + 9 + ], + [ + "aBAAacaa", + "acabBBcBC", + 13 + ], + [ + "aBAAbCcB", + "BCB", + 10 + ], + [ + "aBAAcbCc", + "cBCBBcAAB", + 14 + ], + [ + "aBAB", + "aaCc", + 6 + ], + [ + "aBAB", + "bbaAbA", + 8 + ], + [ + "aBABa", + "bacAa", + 6 + ], + [ + "aBABaABc", + "AAaCCC", + 10 + ], + [ + "aBABcaABA", + "CBcbB", + 12 + ], + [ + "aBACC", + "a", + 8 + ], + [ + "aBACa", + "c", + 9 + ], + [ + "aBACba", + "C", + 10 + ], + [ + "aBACcAA", + "B", + 12 + ], + [ + "aBAa", + "aaB", + 5 + ], + [ + "aBAaC", + "cCACbbBb", + 14 + ], + [ + "aBAabaAB", + "cacCbb", + 13 + ], + [ + "aBAabba", + "AaBABbCBc", + 9 + ], + [ + "aBAacCbbb", + "aBaCaAc", + 10 + ], + [ + "aBAacc", + "aAaAAcABC", + 10 + ], + [ + "aBAb", + "aBBbAac", + 8 + ], + [ + "aBAbAC", + "aABAbCAc", + 5 + ], + [ + "aBAbaaab", + "CCbbAaba", + 11 + ], + [ + "aBAbbB", + "aCacbAB", + 7 + ], + [ + "aBAbbcbb", + "bcBAb", + 11 + ], + [ + "aBAcABbb", + "Ca", + 14 + ], + [ + "aBAcBa", + "CbBcCaa", + 9 + ], + [ + "aBAcBbC", + "aaAaBac", + 7 + ], + [ + "aBAca", + "AACACCcAa", + 11 + ], + [ + "aBAcabBA", + "BBACb", + 9 + ], + [ + "aBAcba", + "bBaaCcB", + 10 + ], + [ + "aBAccCaba", + "CAcabBBbC", + 14 + ], + [ + "aBAcca", + "ABAABbCa", + 8 + ], + [ + "aBB", + "AaAbaAaBb", + 13 + ], + [ + "aBB", + "AaCaB", + 6 + ], + [ + "aBB", + "Acbc", + 6 + ], + [ + "aBB", + "aC", + 4 + ], + [ + "aBB", + "aa", + 4 + ], + [ + "aBB", + "bbCAAaBBB", + 12 + ], + [ + "aBBA", + "BAAc", + 6 + ], + [ + "aBBABAaAA", + "AbCAcbAA", + 10 + ], + [ + "aBBABaaaa", + "bCC", + 17 + ], + [ + "aBBABca", + "bA", + 11 + ], + [ + "aBBACC", + "caaCB", + 9 + ], + [ + "aBBAc", + "aCAA", + 6 + ], + [ + "aBBB", + "AcBcBa", + 7 + ], + [ + "aBBBA", + "CcbaAB", + 9 + ], + [ + "aBBBABAB", + "BBA", + 10 + ], + [ + "aBBBACB", + "Aa", + 12 + ], + [ + "aBBBAaA", + "bB", + 11 + ], + [ + "aBBBB", + "AaBb", + 6 + ], + [ + "aBBBCCBbb", + "BCBca", + 12 + ], + [ + "aBBBaBaAa", + "cCCB", + 16 + ], + [ + "aBBC", + "AbabA", + 7 + ], + [ + "aBBC", + "CcbcBBCb", + 10 + ], + [ + "aBBC", + "cCbbBa", + 9 + ], + [ + "aBBCBac", + "AbacacaCC", + 12 + ], + [ + "aBBCBbab", + "bb", + 12 + ], + [ + "aBBCaCCAB", + "ccBAcA", + 12 + ], + [ + "aBBCbAb", + "cACACb", + 10 + ], + [ + "aBBCbB", + "aCabbA", + 8 + ], + [ + "aBBCcabCb", + "CCacAacc", + 14 + ], + [ + "aBBaB", + "BCaccaa", + 10 + ], + [ + "aBBaB", + "bACCbA", + 11 + ], + [ + "aBBaCCab", + "CBcAbcC", + 12 + ], + [ + "aBBaa", + "a", + 8 + ], + [ + "aBBabAb", + "bACbcAa", + 11 + ], + [ + "aBBac", + "BbAa", + 6 + ], + [ + "aBBacABac", + "CbCbc", + 13 + ], + [ + "aBBb", + "BCAaBa", + 9 + ], + [ + "aBBbA", + "aaa", + 7 + ], + [ + "aBBbB", + "bbBaba", + 7 + ], + [ + "aBBbba", + "bCCcacbB", + 14 + ], + [ + "aBBc", + "BccbaCCac", + 14 + ], + [ + "aBBcABB", + "BBcAbCBAB", + 8 + ], + [ + "aBBcBAab", + "CcCBBA", + 12 + ], + [ + "aBBcBBA", + "cC", + 12 + ], + [ + "aBBcBcb", + "BBBca", + 6 + ], + [ + "aBBcaB", + "AbBAa", + 6 + ], + [ + "aBBccB", + "abcac", + 7 + ], + [ + "aBBcca", + "bABb", + 10 + ], + [ + "aBBcccbCB", + "bCcab", + 12 + ], + [ + "aBC", + "AABCBaAAC", + 13 + ], + [ + "aBC", + "CcaCAB", + 10 + ], + [ + "aBC", + "aACCc", + 6 + ], + [ + "aBC", + "aaaCcCcB", + 12 + ], + [ + "aBC", + "bABBAa", + 9 + ], + [ + "aBC", + "bC", + 3 + ], + [ + "aBC", + "baCCa", + 6 + ], + [ + "aBC", + "bc", + 4 + ], + [ + "aBCA", + "AbbcbAb", + 9 + ], + [ + "aBCA", + "CBaAa", + 6 + ], + [ + "aBCAa", + "BbCCAbAAb", + 12 + ], + [ + "aBCAcBAa", + "BC", + 12 + ], + [ + "aBCAcBCAC", + "AABaCbBcA", + 12 + ], + [ + "aBCAccBCB", + "b", + 17 + ], + [ + "aBCBA", + "b", + 9 + ], + [ + "aBCBBCa", + "aCCAaa", + 8 + ], + [ + "aBCBC", + "bCccB", + 8 + ], + [ + "aBCBC", + "bbB", + 7 + ], + [ + "aBCBabb", + "a", + 12 + ], + [ + "aBCCcc", + "caAAc", + 10 + ], + [ + "aBCaB", + "cB", + 7 + ], + [ + "aBCaCa", + "aCA", + 7 + ], + [ + "aBCaCbC", + "BcaaBaABA", + 13 + ], + [ + "aBCab", + "cAacAcCaA", + 12 + ], + [ + "aBCababA", + "ccbABc", + 11 + ], + [ + "aBCac", + "CbCBbaAB", + 11 + ], + [ + "aBCb", + "acabaC", + 8 + ], + [ + "aBCbb", + "C", + 8 + ], + [ + "aBCbbB", + "bCcBb", + 7 + ], + [ + "aBCbcCBc", + "cBaCAc", + 10 + ], + [ + "aBCc", + "bABcbCa", + 9 + ], + [ + "aBCcAc", + "cCAaaAc", + 10 + ], + [ + "aBCcAcAC", + "BCcBaca", + 8 + ], + [ + "aBCcCBaa", + "b", + 15 + ], + [ + "aBCcCBbcC", + "ba", + 16 + ], + [ + "aBCcaBAaC", + "bBbcaCC", + 10 + ], + [ + "aBa", + "cBCbb", + 8 + ], + [ + "aBa", + "caB", + 4 + ], + [ + "aBaA", + "caBAC", + 5 + ], + [ + "aBaAaAcC", + "CAbBbbabc", + 13 + ], + [ + "aBaAcCBBA", + "AcAcc", + 12 + ], + [ + "aBaAcCC", + "Bc", + 10 + ], + [ + "aBaB", + "CBBbc", + 7 + ], + [ + "aBaB", + "bBAaA", + 6 + ], + [ + "aBaBC", + "CBbcaaBA", + 10 + ], + [ + "aBaBbA", + "CCabBBAaC", + 11 + ], + [ + "aBaBbCcc", + "aB", + 12 + ], + [ + "aBaBc", + "CcCAb", + 10 + ], + [ + "aBaC", + "A", + 7 + ], + [ + "aBaC", + "aa", + 4 + ], + [ + "aBaC", + "accAbAC", + 8 + ], + [ + "aBaCCAb", + "cbBC", + 11 + ], + [ + "aBaCaA", + "Aa", + 9 + ], + [ + "aBaCaC", + "CbaCccAc", + 9 + ], + [ + "aBaCaabAc", + "bCcACb", + 13 + ], + [ + "aBaCba", + "baB", + 8 + ], + [ + "aBaa", + "CBaBAABbB", + 12 + ], + [ + "aBaa", + "Cb", + 7 + ], + [ + "aBaaA", + "aAAaABCB", + 9 + ], + [ + "aBaabC", + "bCBbAabAC", + 9 + ], + [ + "aBaabCba", + "bCCaB", + 12 + ], + [ + "aBaabaB", + "AA", + 12 + ], + [ + "aBaacBbca", + "cCc", + 14 + ], + [ + "aBabACAA", + "acABb", + 12 + ], + [ + "aBabBcb", + "bAbA", + 10 + ], + [ + "aBabbaBbc", + "cbaccAb", + 12 + ], + [ + "aBac", + "c", + 6 + ], + [ + "aBacAAcA", + "aaCB", + 11 + ], + [ + "aBacBB", + "cb", + 9 + ], + [ + "aBacC", + "ACCCcAb", + 11 + ], + [ + "aBacCB", + "abccAac", + 9 + ], + [ + "aBaccCAa", + "aBcacaaCb", + 10 + ], + [ + "aBb", + "BCBAabbac", + 13 + ], + [ + "aBb", + "Ccb", + 4 + ], + [ + "aBb", + "aaac", + 6 + ], + [ + "aBb", + "bbcbbbAaC", + 15 + ], + [ + "aBb", + "cbCaBb", + 6 + ], + [ + "aBb", + "cbaB", + 6 + ], + [ + "aBbA", + "bbaaAbab", + 11 + ], + [ + "aBbACC", + "cCcccBCC", + 12 + ], + [ + "aBbAcCBCA", + "cCBab", + 12 + ], + [ + "aBbB", + "BacA", + 8 + ], + [ + "aBbB", + "aCcCb", + 7 + ], + [ + "aBbBA", + "aAcC", + 8 + ], + [ + "aBbBB", + "aBCc", + 6 + ], + [ + "aBbBCbBAa", + "Ac", + 16 + ], + [ + "aBbBa", + "bAa", + 6 + ], + [ + "aBbBbAbC", + "Abc", + 11 + ], + [ + "aBbBbaBA", + "AbbBCccC", + 10 + ], + [ + "aBbBbcBcb", + "abaBAAAAB", + 12 + ], + [ + "aBbC", + "bCbBAcBc", + 12 + ], + [ + "aBbCABBA", + "Caa", + 12 + ], + [ + "aBbCACaCb", + "A", + 16 + ], + [ + "aBbCBCaBC", + "aCcc", + 12 + ], + [ + "aBbCaa", + "cbAC", + 9 + ], + [ + "aBbCbbbBA", + "aA", + 14 + ], + [ + "aBbCbcCAb", + "BcAC", + 12 + ], + [ + "aBbCc", + "AcACCCba", + 12 + ], + [ + "aBba", + "BcAcCBB", + 12 + ], + [ + "aBbaAACBA", + "BacacbaC", + 12 + ], + [ + "aBbaAB", + "bbCc", + 9 + ], + [ + "aBbaBcC", + "c", + 12 + ], + [ + "aBbaCcbC", + "bbbbcab", + 10 + ], + [ + "aBbaaCcaA", + "bBABBAbCc", + 13 + ], + [ + "aBbabCc", + "CaCB", + 10 + ], + [ + "aBbb", + "Bb", + 4 + ], + [ + "aBbb", + "cccAbB", + 9 + ], + [ + "aBbbA", + "AAC", + 9 + ], + [ + "aBbbAB", + "C", + 12 + ], + [ + "aBbbCab", + "AaCBacaba", + 10 + ], + [ + "aBbbbBA", + "BcbbBBA", + 5 + ], + [ + "aBbcCBaCc", + "C", + 16 + ], + [ + "aBbcCba", + "BbAcaBac", + 9 + ], + [ + "aBc", + "A", + 5 + ], + [ + "aBc", + "Bac", + 4 + ], + [ + "aBc", + "bCCc", + 6 + ], + [ + "aBc", + "baC", + 5 + ], + [ + "aBcAA", + "ABc", + 5 + ], + [ + "aBcAABabC", + "cab", + 12 + ], + [ + "aBcAaAccc", + "BacacaA", + 12 + ], + [ + "aBcAabAA", + "aBcB", + 9 + ], + [ + "aBcAc", + "bCCbbCc", + 11 + ], + [ + "aBcB", + "c", + 6 + ], + [ + "aBcBBBA", + "BbBBaAAab", + 12 + ], + [ + "aBcBBCAbb", + "AAA", + 15 + ], + [ + "aBcBaa", + "bbB", + 9 + ], + [ + "aBcBbbCb", + "cAcBCca", + 11 + ], + [ + "aBcC", + "CCAAA", + 10 + ], + [ + "aBcCABCc", + "BCbbcC", + 9 + ], + [ + "aBcCBbAc", + "cAbABaAa", + 12 + ], + [ + "aBcCCbA", + "cbA", + 8 + ], + [ + "aBcCcbaAC", + "aBBbBaB", + 11 + ], + [ + "aBca", + "BacbCCcB", + 11 + ], + [ + "aBca", + "CBAcCcCA", + 11 + ], + [ + "aBcaCC", + "c", + 10 + ], + [ + "aBcaaBB", + "BAbBaACbc", + 12 + ], + [ + "aBcbABAAa", + "bCCc", + 16 + ], + [ + "aBcbCAb", + "abA", + 8 + ], + [ + "aBcba", + "cAbCC", + 9 + ], + [ + "aBccABa", + "baBbCAAB", + 9 + ], + [ + "aBccAbac", + "CAaaCbb", + 14 + ], + [ + "aBccacCBa", + "ccbc", + 12 + ], + [ + "aBcccC", + "CccbACAcc", + 12 + ], + [ + "aC", + "AABacca", + 11 + ], + [ + "aC", + "ABABACC", + 11 + ], + [ + "aC", + "AcaBa", + 8 + ], + [ + "aC", + "AcaCBcaCB", + 14 + ], + [ + "aC", + "BBbbBcA", + 13 + ], + [ + "aC", + "BCABB", + 8 + ], + [ + "aC", + "Ba", + 4 + ], + [ + "aC", + "BaACBc", + 8 + ], + [ + "aC", + "BacccCb", + 10 + ], + [ + "aC", + "BcACaaCa", + 12 + ], + [ + "aC", + "BcB", + 5 + ], + [ + "aC", + "CAAcA", + 8 + ], + [ + "aC", + "CBbB", + 8 + ], + [ + "aC", + "CCA", + 4 + ], + [ + "aC", + "CaAA", + 6 + ], + [ + "aC", + "a", + 2 + ], + [ + "aC", + "aACaBBaB", + 12 + ], + [ + "aC", + "abCBcb", + 8 + ], + [ + "aC", + "abcabC", + 8 + ], + [ + "aC", + "bA", + 4 + ], + [ + "aC", + "bABAabc", + 11 + ], + [ + "aC", + "bBAAbCAcB", + 15 + ], + [ + "aC", + "bBaaaA", + 10 + ], + [ + "aC", + "bCBA", + 6 + ], + [ + "aC", + "baaaCC", + 8 + ], + [ + "aC", + "babcC", + 6 + ], + [ + "aC", + "bbc", + 5 + ], + [ + "aC", + "caCbAcaCc", + 14 + ], + [ + "aC", + "cbCaCaB", + 10 + ], + [ + "aCA", + "BCcBBaBc", + 13 + ], + [ + "aCA", + "CAACacC", + 10 + ], + [ + "aCA", + "bAaaC", + 8 + ], + [ + "aCA", + "babAaaBaC", + 14 + ], + [ + "aCA", + "baccbBBC", + 13 + ], + [ + "aCA", + "cBCc", + 6 + ], + [ + "aCAAA", + "aB", + 8 + ], + [ + "aCAAAaAba", + "BAbB", + 14 + ], + [ + "aCAABBBBb", + "BBABB", + 11 + ], + [ + "aCAACBcA", + "aA", + 12 + ], + [ + "aCAAaaa", + "AcbacaACb", + 12 + ], + [ + "aCAAbCBbA", + "cBc", + 15 + ], + [ + "aCAAcbC", + "CAaAcCcCc", + 10 + ], + [ + "aCAB", + "AbabAb", + 7 + ], + [ + "aCAB", + "BA", + 6 + ], + [ + "aCAB", + "CBAC", + 6 + ], + [ + "aCABAcBac", + "CCBCcbCC", + 10 + ], + [ + "aCABBC", + "ABb", + 7 + ], + [ + "aCABaAaC", + "BCaaABcC", + 9 + ], + [ + "aCABaBca", + "c", + 14 + ], + [ + "aCABaa", + "C", + 10 + ], + [ + "aCACACAC", + "baAbACC", + 8 + ], + [ + "aCACBBC", + "aCACBaAac", + 7 + ], + [ + "aCACBaaA", + "acCcCB", + 10 + ], + [ + "aCACCca", + "aBA", + 10 + ], + [ + "aCACa", + "baBbABB", + 10 + ], + [ + "aCACcbaa", + "CbAbBCBb", + 14 + ], + [ + "aCAa", + "bBCCcc", + 10 + ], + [ + "aCAaCba", + "AabAc", + 9 + ], + [ + "aCAab", + "CCCcCCAA", + 13 + ], + [ + "aCAacCAAa", + "abAcBacA", + 10 + ], + [ + "aCAb", + "Aaac", + 6 + ], + [ + "aCAbCC", + "bca", + 9 + ], + [ + "aCAbCC", + "cCABABac", + 10 + ], + [ + "aCAbCaCca", + "cACA", + 12 + ], + [ + "aCAbaBab", + "BABbA", + 11 + ], + [ + "aCAbca", + "B", + 11 + ], + [ + "aCAcBbbC", + "acaAbACAB", + 12 + ], + [ + "aCAcaCB", + "CbC", + 10 + ], + [ + "aCAcacc", + "aA", + 10 + ], + [ + "aCAcbA", + "aa", + 9 + ], + [ + "aCAcbabB", + "Aabbca", + 12 + ], + [ + "aCAccCa", + "AaBca", + 8 + ], + [ + "aCB", + "A", + 5 + ], + [ + "aCB", + "aAaCbBcA", + 10 + ], + [ + "aCB", + "aBABAaccA", + 14 + ], + [ + "aCB", + "aBcAb", + 6 + ], + [ + "aCB", + "abCbBA", + 6 + ], + [ + "aCB", + "b", + 5 + ], + [ + "aCB", + "cCcaBbc", + 10 + ], + [ + "aCBA", + "BCcCccBA", + 10 + ], + [ + "aCBA", + "cCCbcA", + 7 + ], + [ + "aCBAabBa", + "aA", + 12 + ], + [ + "aCBAb", + "B", + 8 + ], + [ + "aCBAbb", + "c", + 11 + ], + [ + "aCBB", + "bAb", + 7 + ], + [ + "aCBBAAB", + "bcbCAAAbc", + 11 + ], + [ + "aCBBBAab", + "CBcBbBcb", + 9 + ], + [ + "aCBBCcC", + "aBaCAa", + 8 + ], + [ + "aCBBabca", + "bCaaC", + 11 + ], + [ + "aCBBbb", + "cb", + 9 + ], + [ + "aCBBcBBC", + "c", + 14 + ], + [ + "aCBCCa", + "AaaABcA", + 10 + ], + [ + "aCBCc", + "bacBbaaaA", + 13 + ], + [ + "aCBa", + "cAA", + 6 + ], + [ + "aCBaABA", + "ba", + 11 + ], + [ + "aCBaAC", + "a", + 10 + ], + [ + "aCBaacBc", + "cacacCBB", + 10 + ], + [ + "aCBb", + "Ba", + 6 + ], + [ + "aCBb", + "CaAc", + 8 + ], + [ + "aCBb", + "CaBb", + 4 + ], + [ + "aCBbABbA", + "CbBcaA", + 10 + ], + [ + "aCBbACC", + "cAaBBC", + 10 + ], + [ + "aCBbBAcBB", + "aabBbCcaB", + 9 + ], + [ + "aCBbBc", + "aBabCbbaC", + 10 + ], + [ + "aCBbcccA", + "cCA", + 11 + ], + [ + "aCBc", + "ABAAaC", + 10 + ], + [ + "aCBc", + "BCcc", + 4 + ], + [ + "aCBcB", + "bBCCAC", + 10 + ], + [ + "aCBcaC", + "A", + 11 + ], + [ + "aCC", + "AB", + 5 + ], + [ + "aCC", + "Ac", + 4 + ], + [ + "aCC", + "abABbCBc", + 11 + ], + [ + "aCC", + "cBAcBCcb", + 12 + ], + [ + "aCC", + "cCCcBaa", + 10 + ], + [ + "aCCAA", + "C", + 8 + ], + [ + "aCCAB", + "bbCB", + 6 + ], + [ + "aCCABabA", + "aCa", + 10 + ], + [ + "aCCAcCbA", + "cBbCc", + 13 + ], + [ + "aCCAccAA", + "CB", + 14 + ], + [ + "aCCBBB", + "cccacc", + 10 + ], + [ + "aCCBCcA", + "aAc", + 10 + ], + [ + "aCCBCcCC", + "acBCbA", + 9 + ], + [ + "aCCC", + "ACbCcBa", + 8 + ], + [ + "aCCCBAC", + "AB", + 11 + ], + [ + "aCCCBCBa", + "a", + 14 + ], + [ + "aCCCBaAAa", + "CcBb", + 13 + ], + [ + "aCCCCB", + "cCABAcbA", + 12 + ], + [ + "aCCCCbCc", + "B", + 15 + ], + [ + "aCCCCcCb", + "AAB", + 14 + ], + [ + "aCCCab", + "BAaBcaC", + 10 + ], + [ + "aCCCc", + "aBabBBaa", + 14 + ], + [ + "aCCaA", + "aAAbb", + 8 + ], + [ + "aCCaA", + "cAA", + 6 + ], + [ + "aCCaBCcb", + "BAcc", + 12 + ], + [ + "aCCaC", + "CcacABac", + 10 + ], + [ + "aCCbBBbCB", + "baAcABCC", + 13 + ], + [ + "aCCbbAC", + "BAcbcB", + 11 + ], + [ + "aCCbbbaaB", + "abbC", + 12 + ], + [ + "aCCc", + "bAa", + 8 + ], + [ + "aCCc", + "cbaB", + 8 + ], + [ + "aCCcABBCB", + "bbC", + 14 + ], + [ + "aCCcBBABA", + "bABaacb", + 15 + ], + [ + "aCCcBCCBc", + "a", + 16 + ], + [ + "aCCcbaa", + "B", + 13 + ], + [ + "aCCccBBa", + "baAB", + 14 + ], + [ + "aCa", + "B", + 6 + ], + [ + "aCa", + "BCcC", + 6 + ], + [ + "aCa", + "bbbbCBA", + 11 + ], + [ + "aCaAAACa", + "AcCAA", + 10 + ], + [ + "aCaAAb", + "BCBccaB", + 10 + ], + [ + "aCaABBc", + "aCAbcbB", + 8 + ], + [ + "aCaABbAb", + "aBc", + 12 + ], + [ + "aCaAb", + "aCc", + 6 + ], + [ + "aCaAcCBb", + "baCcbb", + 9 + ], + [ + "aCaAcaC", + "BBCcCcbc", + 11 + ], + [ + "aCaB", + "BacaaAcBA", + 11 + ], + [ + "aCaB", + "C", + 6 + ], + [ + "aCaBAC", + "ab", + 9 + ], + [ + "aCaBBa", + "B", + 10 + ], + [ + "aCaBCB", + "a", + 10 + ], + [ + "aCaBCCB", + "cAccc", + 10 + ], + [ + "aCaBa", + "aC", + 6 + ], + [ + "aCaBacB", + "AAbcaB", + 9 + ], + [ + "aCaC", + "BBcc", + 7 + ], + [ + "aCaC", + "CAAaCAB", + 9 + ], + [ + "aCaC", + "accBcAAbA", + 14 + ], + [ + "aCaCAAac", + "CcCAB", + 10 + ], + [ + "aCaCBaaBb", + "CC", + 14 + ], + [ + "aCaCacAba", + "cbaAbBB", + 13 + ], + [ + "aCaCccca", + "CbACAc", + 11 + ], + [ + "aCaa", + "BAbB", + 8 + ], + [ + "aCaaC", + "ABacC", + 5 + ], + [ + "aCaac", + "AcabAC", + 6 + ], + [ + "aCaacB", + "CA", + 9 + ], + [ + "aCaacacbC", + "b", + 16 + ], + [ + "aCab", + "AcccBcB", + 11 + ], + [ + "aCab", + "BaaacBbbA", + 12 + ], + [ + "aCabBacCB", + "AAc", + 14 + ], + [ + "aCabCBBB", + "CBBcBbBBB", + 10 + ], + [ + "aCabCbbb", + "CBCCCcA", + 12 + ], + [ + "aCabaA", + "bABbaB", + 8 + ], + [ + "aCabbbCA", + "Ba", + 14 + ], + [ + "aCabcb", + "bBBb", + 9 + ], + [ + "aCacA", + "BbAB", + 9 + ], + [ + "aCacCa", + "baABcBcAb", + 12 + ], + [ + "aCacCcAaC", + "CbAbBa", + 14 + ], + [ + "aCacbC", + "CcbABcb", + 10 + ], + [ + "aCb", + "AaBaAc", + 10 + ], + [ + "aCb", + "BB", + 5 + ], + [ + "aCb", + "a", + 4 + ], + [ + "aCb", + "cCbBC", + 6 + ], + [ + "aCbA", + "CcCccBc", + 11 + ], + [ + "aCbA", + "bBaC", + 8 + ], + [ + "aCbAa", + "AABbbcCAC", + 13 + ], + [ + "aCbBAA", + "BcCC", + 11 + ], + [ + "aCbBAB", + "bcCBb", + 8 + ], + [ + "aCbBB", + "B", + 8 + ], + [ + "aCbBCaB", + "aCcAcaAbB", + 9 + ], + [ + "aCbBb", + "CBbA", + 6 + ], + [ + "aCbCBB", + "CAaACaA", + 11 + ], + [ + "aCbCBc", + "ABaBAcAcC", + 13 + ], + [ + "aCbCcCBCB", + "CBbc", + 13 + ], + [ + "aCba", + "aCba", + 0 + ], + [ + "aCbaa", + "BacbBccb", + 11 + ], + [ + "aCbb", + "bBcCBAAcc", + 15 + ], + [ + "aCbbCCCB", + "AaAC", + 13 + ], + [ + "aCbbaBAa", + "BbB", + 11 + ], + [ + "aCbbc", + "BBCAb", + 8 + ], + [ + "aCbbcAcB", + "accCaaB", + 9 + ], + [ + "aCbca", + "AbAc", + 7 + ], + [ + "aCbcb", + "cBcacaa", + 11 + ], + [ + "aCbcc", + "cBCbBAbB", + 12 + ], + [ + "aCbcca", + "CBbCaaCC", + 11 + ], + [ + "aCbccb", + "cAAab", + 9 + ], + [ + "aCc", + "AaAbC", + 7 + ], + [ + "aCc", + "BCbb", + 6 + ], + [ + "aCc", + "CCABBCAa", + 13 + ], + [ + "aCc", + "aAB", + 4 + ], + [ + "aCc", + "bbAcb", + 8 + ], + [ + "aCc", + "bbCcccb", + 10 + ], + [ + "aCc", + "caCAC", + 5 + ], + [ + "aCcA", + "a", + 6 + ], + [ + "aCcA", + "aC", + 4 + ], + [ + "aCcA", + "aaAAaaCBa", + 13 + ], + [ + "aCcA", + "cABb", + 8 + ], + [ + "aCcAAA", + "AbaBACBC", + 13 + ], + [ + "aCcABAc", + "BC", + 11 + ], + [ + "aCcABCB", + "b", + 13 + ], + [ + "aCcACA", + "baCcaaAa", + 7 + ], + [ + "aCcAaAba", + "aCAca", + 8 + ], + [ + "aCcAbB", + "cBCAB", + 7 + ], + [ + "aCcB", + "CCbcBaBBb", + 12 + ], + [ + "aCcBbCAC", + "CACbBb", + 11 + ], + [ + "aCcCC", + "A", + 9 + ], + [ + "aCcCCa", + "CcCabCc", + 8 + ], + [ + "aCcCabC", + "BcACaABCC", + 10 + ], + [ + "aCcCcAaA", + "baaAacBAb", + 13 + ], + [ + "aCcCca", + "cBCbCCAcb", + 11 + ], + [ + "aCcaAABb", + "BCACAAb", + 8 + ], + [ + "aCcaCAA", + "AabCbBBAC", + 12 + ], + [ + "aCcacbabC", + "a", + 16 + ], + [ + "aCcbAb", + "CbbcbaCAB", + 11 + ], + [ + "aCcbB", + "aA", + 8 + ], + [ + "aCcbBbAcC", + "bBBc", + 11 + ], + [ + "aCcbaCbca", + "bcBCCcbC", + 12 + ], + [ + "aCcbb", + "ccAabbABa", + 13 + ], + [ + "aCcbbaccb", + "AaCaCaCC", + 12 + ], + [ + "aCcbcBBA", + "CaBccABCa", + 11 + ], + [ + "aCcbcb", + "AcaacBCc", + 10 + ], + [ + "aCcc", + "baCACCa", + 8 + ], + [ + "aCccAaBa", + "BAaBcB", + 12 + ], + [ + "aCccAb", + "bBbaAcCC", + 13 + ], + [ + "aCccBB", + "CCa", + 9 + ], + [ + "aCccaaABC", + "CcBac", + 11 + ], + [ + "aCcccAaba", + "ccBbaA", + 11 + ], + [ + "aa", + "AACcaACB", + 13 + ], + [ + "aa", + "AcBCa", + 7 + ], + [ + "aa", + "Bbaba", + 6 + ], + [ + "aa", + "Bbc", + 6 + ], + [ + "aa", + "BcaBA", + 7 + ], + [ + "aa", + "C", + 4 + ], + [ + "aa", + "CBcC", + 8 + ], + [ + "aa", + "CbB", + 6 + ], + [ + "aa", + "CbBC", + 8 + ], + [ + "aa", + "a", + 2 + ], + [ + "aa", + "aABACBBb", + 13 + ], + [ + "aa", + "aBC", + 4 + ], + [ + "aa", + "aBcaaCAC", + 12 + ], + [ + "aa", + "aCbbAaaCa", + 14 + ], + [ + "aa", + "aaAaA", + 6 + ], + [ + "aa", + "aaAbcb", + 8 + ], + [ + "aa", + "aabCb", + 6 + ], + [ + "aa", + "abCBc", + 8 + ], + [ + "aa", + "abCbAb", + 9 + ], + [ + "aa", + "acAAbAAB", + 13 + ], + [ + "aa", + "bAaB", + 5 + ], + [ + "aa", + "bB", + 4 + ], + [ + "aa", + "bBAaBCA", + 11 + ], + [ + "aa", + "bCaaBbCA", + 12 + ], + [ + "aa", + "baCCBCBB", + 14 + ], + [ + "aa", + "bbAabBcCc", + 15 + ], + [ + "aa", + "bbcAC", + 9 + ], + [ + "aa", + "cAaabc", + 8 + ], + [ + "aa", + "cB", + 4 + ], + [ + "aa", + "cBB", + 6 + ], + [ + "aa", + "cBcBAAbA", + 14 + ], + [ + "aa", + "cCAB", + 7 + ], + [ + "aa", + "cCB", + 6 + ], + [ + "aa", + "caccAcB", + 11 + ], + [ + "aa", + "cb", + 4 + ], + [ + "aa", + "cbbcCB", + 12 + ], + [ + "aaA", + "A", + 4 + ], + [ + "aaA", + "ACBACBC", + 11 + ], + [ + "aaA", + "bCCA", + 6 + ], + [ + "aaA", + "bcaaAB", + 6 + ], + [ + "aaAA", + "aab", + 4 + ], + [ + "aaAAaaAaB", + "BAA", + 14 + ], + [ + "aaAAbbAC", + "cCbBbCAab", + 14 + ], + [ + "aaAAcC", + "cabbCa", + 9 + ], + [ + "aaAB", + "cAa", + 6 + ], + [ + "aaAB", + "cCaBCcB", + 10 + ], + [ + "aaABAbAC", + "CCAB", + 12 + ], + [ + "aaABAbb", + "CC", + 14 + ], + [ + "aaABB", + "Ccb", + 9 + ], + [ + "aaABCC", + "acABACB", + 6 + ], + [ + "aaABCb", + "CABbAaa", + 12 + ], + [ + "aaABaaA", + "CaaaBBabc", + 9 + ], + [ + "aaABbbCbc", + "B", + 16 + ], + [ + "aaAC", + "Ac", + 5 + ], + [ + "aaAC", + "bccBBBb", + 14 + ], + [ + "aaACa", + "cbAAaCB", + 9 + ], + [ + "aaAaAcbBC", + "A", + 16 + ], + [ + "aaAaaaAb", + "bcBc", + 16 + ], + [ + "aaAabBCbc", + "B", + 16 + ], + [ + "aaAabCaAa", + "BbbbcA", + 13 + ], + [ + "aaAabbbaC", + "bbbCaAbC", + 14 + ], + [ + "aaAac", + "cBCCc", + 8 + ], + [ + "aaAbaBbBa", + "baaCCa", + 12 + ], + [ + "aaAbcbBa", + "BbBbaaC", + 12 + ], + [ + "aaAc", + "bbCBca", + 10 + ], + [ + "aaAccCbC", + "BBa", + 15 + ], + [ + "aaB", + "bAAa", + 6 + ], + [ + "aaB", + "cccCAcAaC", + 15 + ], + [ + "aaBA", + "ABccb", + 9 + ], + [ + "aaBA", + "aca", + 5 + ], + [ + "aaBABCc", + "Aaa", + 10 + ], + [ + "aaBABaCc", + "acBaac", + 7 + ], + [ + "aaBABbCAc", + "cCb", + 16 + ], + [ + "aaBB", + "BbbBbcAB", + 12 + ], + [ + "aaBB", + "bAaAaAB", + 8 + ], + [ + "aaBB", + "bBabBA", + 7 + ], + [ + "aaBBa", + "caaB", + 6 + ], + [ + "aaBC", + "BacaAB", + 8 + ], + [ + "aaBCAaaaB", + "aaACA", + 10 + ], + [ + "aaBCC", + "BAAaBb", + 9 + ], + [ + "aaBCCa", + "CA", + 9 + ], + [ + "aaBCCbAac", + "AcaaBacc", + 13 + ], + [ + "aaBa", + "BabbcC", + 9 + ], + [ + "aaBaAaCa", + "aBbcAcAb", + 11 + ], + [ + "aaBaC", + "bcCAbac", + 9 + ], + [ + "aaBaCc", + "BCAaCCb", + 9 + ], + [ + "aaBaa", + "AAb", + 7 + ], + [ + "aaBaa", + "ccba", + 7 + ], + [ + "aaBaaAcBA", + "aCcBCAc", + 12 + ], + [ + "aaBabACBC", + "CCCc", + 15 + ], + [ + "aaBabbBA", + "aAbB", + 9 + ], + [ + "aaBacaB", + "BCABCCCbc", + 13 + ], + [ + "aaBbAaBA", + "BAc", + 12 + ], + [ + "aaBbb", + "aA", + 7 + ], + [ + "aaBbcA", + "CBcb", + 8 + ], + [ + "aaBbcbb", + "C", + 13 + ], + [ + "aaBcBaBa", + "aaBaaaB", + 6 + ], + [ + "aaBccACb", + "bCCBccC", + 10 + ], + [ + "aaC", + "ABcAca", + 9 + ], + [ + "aaC", + "BAbaCaa", + 9 + ], + [ + "aaC", + "CaCbcCc", + 10 + ], + [ + "aaC", + "Cc", + 5 + ], + [ + "aaC", + "aCa", + 4 + ], + [ + "aaC", + "caBBCb", + 8 + ], + [ + "aaCA", + "B", + 8 + ], + [ + "aaCAA", + "BcbBC", + 10 + ], + [ + "aaCABbb", + "CCcbBAb", + 9 + ], + [ + "aaCACa", + "Bb", + 12 + ], + [ + "aaCAcbc", + "AaaAAa", + 9 + ], + [ + "aaCAccaAB", + "CcCABcA", + 10 + ], + [ + "aaCC", + "B", + 8 + ], + [ + "aaCCC", + "ABAcC", + 6 + ], + [ + "aaCCCc", + "cCAaaAbAb", + 14 + ], + [ + "aaCCa", + "CAA", + 7 + ], + [ + "aaCCbaCaB", + "cCCcCAccC", + 13 + ], + [ + "aaCCcbCA", + "acAcb", + 9 + ], + [ + "aaCa", + "aCBca", + 5 + ], + [ + "aaCa", + "cAAca", + 5 + ], + [ + "aaCaABa", + "ba", + 11 + ], + [ + "aaCaABbc", + "B", + 14 + ], + [ + "aaCaBaA", + "aBcAa", + 8 + ], + [ + "aaCaa", + "Ac", + 8 + ], + [ + "aaCacA", + "CbabcA", + 8 + ], + [ + "aaCacAAb", + "CcCAc", + 10 + ], + [ + "aaCacaBC", + "BBaCcA", + 11 + ], + [ + "aaCacaba", + "ACbc", + 11 + ], + [ + "aaCbAAB", + "ABc", + 12 + ], + [ + "aaCbabb", + "Bc", + 13 + ], + [ + "aaCbabc", + "C", + 12 + ], + [ + "aaCbbAAc", + "BaBa", + 12 + ], + [ + "aaCcAcc", + "aC", + 10 + ], + [ + "aaCcaBcB", + "ccAaAbaBC", + 12 + ], + [ + "aaCcaC", + "AAABCccC", + 8 + ], + [ + "aaCccbAaa", + "B", + 17 + ], + [ + "aaa", + "ABAbCbC", + 12 + ], + [ + "aaa", + "abABCcbA", + 12 + ], + [ + "aaa", + "abaC", + 4 + ], + [ + "aaa", + "baa", + 2 + ], + [ + "aaa", + "cCbC", + 8 + ], + [ + "aaaA", + "BaCacbbc", + 12 + ], + [ + "aaaA", + "aaaabAC", + 6 + ], + [ + "aaaAAb", + "abCC", + 10 + ], + [ + "aaaAAcb", + "cbABcb", + 8 + ], + [ + "aaaABb", + "BBCCbBaba", + 14 + ], + [ + "aaaACcA", + "aaa", + 8 + ], + [ + "aaaAac", + "CCAaC", + 7 + ], + [ + "aaaAb", + "ACc", + 9 + ], + [ + "aaaB", + "b", + 7 + ], + [ + "aaaBBB", + "CcACCaCB", + 13 + ], + [ + "aaaBCAbBc", + "baCaab", + 12 + ], + [ + "aaaBaBCAb", + "BaBccAb", + 9 + ], + [ + "aaaBbAC", + "baBCAc", + 7 + ], + [ + "aaaBbBA", + "a", + 12 + ], + [ + "aaaC", + "aca", + 4 + ], + [ + "aaaCAAcAA", + "cCacb", + 13 + ], + [ + "aaaCBacCc", + "aA", + 15 + ], + [ + "aaaCaBcCC", + "ACAbBb", + 13 + ], + [ + "aaaCaaC", + "C", + 12 + ], + [ + "aaaCaaCb", + "bC", + 14 + ], + [ + "aaaCb", + "bcBbaB", + 11 + ], + [ + "aaaCbC", + "AabAc", + 8 + ], + [ + "aaaCcB", + "A", + 11 + ], + [ + "aaaa", + "CacBC", + 8 + ], + [ + "aaaaB", + "aCBcbC", + 9 + ], + [ + "aaaaBBCc", + "aCcCcaab", + 14 + ], + [ + "aaaaBCc", + "bcbaB", + 10 + ], + [ + "aaaaC", + "CBb", + 10 + ], + [ + "aaabCBaAB", + "cbBccA", + 14 + ], + [ + "aaac", + "BAaB", + 5 + ], + [ + "aaacCCB", + "aaAaacA", + 8 + ], + [ + "aab", + "CcCcaACB", + 12 + ], + [ + "aab", + "ab", + 2 + ], + [ + "aab", + "b", + 4 + ], + [ + "aab", + "bBb", + 4 + ], + [ + "aab", + "ccbCc", + 8 + ], + [ + "aabAA", + "CBCcBcac", + 14 + ], + [ + "aabACB", + "AACA", + 7 + ], + [ + "aabACB", + "Ca", + 10 + ], + [ + "aabAacba", + "bCbcBC", + 11 + ], + [ + "aabAbBc", + "AbCaCcCB", + 13 + ], + [ + "aabBAA", + "CbaACAbAA", + 10 + ], + [ + "aabBBcB", + "CbACCCc", + 13 + ], + [ + "aabCAAc", + "CBaA", + 10 + ], + [ + "aabCAcB", + "BBCA", + 9 + ], + [ + "aabaCc", + "Acab", + 9 + ], + [ + "aabb", + "a", + 6 + ], + [ + "aabbA", + "bA", + 6 + ], + [ + "aabbCAA", + "AaAAAbbC", + 11 + ], + [ + "aabbacA", + "AbC", + 10 + ], + [ + "aabbcABba", + "AC", + 16 + ], + [ + "aabc", + "A", + 7 + ], + [ + "aabc", + "Baab", + 4 + ], + [ + "aabcA", + "cabBcCc", + 8 + ], + [ + "aabcBcB", + "Ab", + 11 + ], + [ + "aabcaAA", + "abccacA", + 6 + ], + [ + "aabcbBc", + "BaCaaaA", + 12 + ], + [ + "aabcbaaac", + "BbCbccCc", + 11 + ], + [ + "aac", + "BCC", + 5 + ], + [ + "aac", + "BbCaCBbBc", + 14 + ], + [ + "aac", + "C", + 5 + ], + [ + "aac", + "a", + 4 + ], + [ + "aacA", + "CABCabaa", + 12 + ], + [ + "aacAaCCA", + "CBCbB", + 13 + ], + [ + "aacAaCCAb", + "BBCAB", + 13 + ], + [ + "aacAcb", + "baAacaCAB", + 9 + ], + [ + "aacB", + "ABcCcBAca", + 13 + ], + [ + "aacB", + "CACAABc", + 10 + ], + [ + "aacB", + "c", + 6 + ], + [ + "aacBAabc", + "caCBa", + 9 + ], + [ + "aacBc", + "aBCaCbAc", + 8 + ], + [ + "aacC", + "Ba", + 6 + ], + [ + "aacC", + "Bc", + 6 + ], + [ + "aacC", + "CAbBAbCaA", + 14 + ], + [ + "aacCCb", + "ccBAc", + 10 + ], + [ + "aacCc", + "CaB", + 8 + ], + [ + "aacaA", + "Bbaa", + 7 + ], + [ + "aacaa", + "cCB", + 8 + ], + [ + "aacaaCbBc", + "BCab", + 13 + ], + [ + "aacabaBA", + "cbbA", + 9 + ], + [ + "aacac", + "ABBCabcc", + 10 + ], + [ + "aacacAaB", + "aAbbA", + 11 + ], + [ + "aacb", + "BCcAb", + 6 + ], + [ + "aacbAAC", + "BbabBAC", + 8 + ], + [ + "aacbAcA", + "acbccC", + 6 + ], + [ + "aacbCABAC", + "AAbaCAbbA", + 11 + ], + [ + "aacbb", + "BBAbAC", + 10 + ], + [ + "aacc", + "acACBAa", + 10 + ], + [ + "aaccAaABA", + "BAbBacAC", + 14 + ], + [ + "aacccAccB", + "BBCAb", + 14 + ], + [ + "ab", + "ABca", + 6 + ], + [ + "ab", + "ACA", + 5 + ], + [ + "ab", + "AaB", + 3 + ], + [ + "ab", + "B", + 3 + ], + [ + "ab", + "BAabAa", + 8 + ], + [ + "ab", + "BAacbb", + 8 + ], + [ + "ab", + "Ba", + 4 + ], + [ + "ab", + "BbaBBa", + 9 + ], + [ + "ab", + "CACABaC", + 12 + ], + [ + "ab", + "CCbaA", + 8 + ], + [ + "ab", + "CbBACbc", + 11 + ], + [ + "ab", + "CbcbcbB", + 12 + ], + [ + "ab", + "CcB", + 5 + ], + [ + "ab", + "CcacBC", + 9 + ], + [ + "ab", + "aBBAbbaB", + 12 + ], + [ + "ab", + "aBCbBCACa", + 14 + ], + [ + "ab", + "abAbbc", + 8 + ], + [ + "ab", + "abB", + 2 + ], + [ + "ab", + "bAbCcBAaA", + 15 + ], + [ + "ab", + "baBcBa", + 9 + ], + [ + "ab", + "bb", + 2 + ], + [ + "ab", + "c", + 4 + ], + [ + "ab", + "cAAabacc", + 12 + ], + [ + "ab", + "cABcCCb", + 11 + ], + [ + "ab", + "cCbbbCa", + 12 + ], + [ + "ab", + "cbcb", + 6 + ], + [ + "ab", + "ccb", + 4 + ], + [ + "ab", + "cccaaCAA", + 14 + ], + [ + "abA", + "AaacB", + 8 + ], + [ + "abA", + "BAB", + 5 + ], + [ + "abA", + "abca", + 3 + ], + [ + "abA", + "bAAcC", + 8 + ], + [ + "abA", + "bBbaa", + 7 + ], + [ + "abA", + "bbbAcabb", + 12 + ], + [ + "abA", + "cAbCCcbb", + 13 + ], + [ + "abA", + "cBaaC", + 8 + ], + [ + "abAAB", + "BAbACC", + 7 + ], + [ + "abAAcaCa", + "c", + 14 + ], + [ + "abAB", + "AaCBBbb", + 10 + ], + [ + "abABAA", + "aBABbaabA", + 8 + ], + [ + "abABcAaC", + "aCbAcb", + 10 + ], + [ + "abABcb", + "b", + 10 + ], + [ + "abAC", + "BAC", + 3 + ], + [ + "abACAC", + "CBCaaCcA", + 12 + ], + [ + "abACC", + "BbCbBb", + 10 + ], + [ + "abACCBBa", + "abb", + 11 + ], + [ + "abACCaABb", + "aAcbb", + 10 + ], + [ + "abACb", + "Ca", + 8 + ], + [ + "abACbBAaA", + "cAAcCaCaB", + 13 + ], + [ + "abACccA", + "bBaCBC", + 9 + ], + [ + "abAaAaCCC", + "cACCbAbCC", + 13 + ], + [ + "abAaBAAC", + "ABca", + 11 + ], + [ + "abAacCCaa", + "acbCAC", + 11 + ], + [ + "abAbAcCc", + "b", + 14 + ], + [ + "abAbbBC", + "CAbbBbBb", + 8 + ], + [ + "abAc", + "bC", + 5 + ], + [ + "abAcBBbBA", + "CabccbcaA", + 11 + ], + [ + "abAccAbba", + "caCACCC", + 14 + ], + [ + "abAccBA", + "ACAbbABaa", + 12 + ], + [ + "abAccCbBa", + "cbccBbBac", + 8 + ], + [ + "abB", + "BAAA", + 7 + ], + [ + "abB", + "BACbb", + 6 + ], + [ + "abB", + "BcaBabA", + 10 + ], + [ + "abB", + "a", + 4 + ], + [ + "abB", + "cbBaACaAB", + 14 + ], + [ + "abBA", + "AACBCAbA", + 11 + ], + [ + "abBAA", + "bA", + 6 + ], + [ + "abBAAcaA", + "AACCC", + 11 + ], + [ + "abBAAcbb", + "bCCcbb", + 8 + ], + [ + "abBAB", + "aa", + 7 + ], + [ + "abBABBA", + "BBCBccbaA", + 12 + ], + [ + "abBAaBa", + "bbaBCcbc", + 11 + ], + [ + "abBAaCacb", + "bbaca", + 10 + ], + [ + "abBAaCcc", + "ccCACB", + 12 + ], + [ + "abBAc", + "bB", + 6 + ], + [ + "abBAc", + "bBB", + 6 + ], + [ + "abBB", + "BbaBbCbb", + 10 + ], + [ + "abBBCc", + "b", + 10 + ], + [ + "abBBaAcBc", + "bbCaB", + 11 + ], + [ + "abBBaBbCc", + "Cac", + 14 + ], + [ + "abBBac", + "BAc", + 7 + ], + [ + "abBBb", + "A", + 9 + ], + [ + "abBBb", + "ACAAc", + 9 + ], + [ + "abBBbB", + "b", + 10 + ], + [ + "abBCACBBc", + "B", + 16 + ], + [ + "abBCBAB", + "cCA", + 10 + ], + [ + "abBCCb", + "AbaCcCcA", + 9 + ], + [ + "abBCCcc", + "BcBCCbCbb", + 11 + ], + [ + "abBCac", + "cCcbACcC", + 11 + ], + [ + "abBCcCbA", + "bC", + 12 + ], + [ + "abBa", + "cbabba", + 5 + ], + [ + "abBaA", + "CA", + 8 + ], + [ + "abBaAAc", + "BB", + 11 + ], + [ + "abBaBcabA", + "BBBAabAba", + 10 + ], + [ + "abBaCbB", + "CaCaacbC", + 9 + ], + [ + "abBabAb", + "Aacb", + 9 + ], + [ + "abBacbc", + "cAccCaaa", + 15 + ], + [ + "abBacca", + "bAACac", + 10 + ], + [ + "abBb", + "ABCaACb", + 10 + ], + [ + "abBb", + "BAABc", + 7 + ], + [ + "abBb", + "c", + 8 + ], + [ + "abBb", + "cAB", + 6 + ], + [ + "abBbBB", + "Aaccb", + 10 + ], + [ + "abBbC", + "aaccbac", + 9 + ], + [ + "abBbCBBa", + "C", + 14 + ], + [ + "abBbb", + "BBA", + 7 + ], + [ + "abBbba", + "CBbAAb", + 9 + ], + [ + "abBc", + "AABBCcBcc", + 12 + ], + [ + "abBc", + "bcbbCABca", + 12 + ], + [ + "abBcB", + "CbCaCca", + 10 + ], + [ + "abBcBBCA", + "BCCCacAac", + 15 + ], + [ + "abBcaBa", + "bcBCCc", + 11 + ], + [ + "abC", + "ACAbb", + 7 + ], + [ + "abC", + "Cc", + 5 + ], + [ + "abC", + "aAabBb", + 8 + ], + [ + "abC", + "b", + 4 + ], + [ + "abC", + "bccBAC", + 9 + ], + [ + "abC", + "bccabCBc", + 10 + ], + [ + "abC", + "cBA", + 5 + ], + [ + "abC", + "cCB", + 6 + ], + [ + "abCA", + "Acb", + 6 + ], + [ + "abCA", + "cc", + 7 + ], + [ + "abCAA", + "BAcbCCBc", + 11 + ], + [ + "abCAAcbCB", + "acCabB", + 9 + ], + [ + "abCAaA", + "AcbaAB", + 8 + ], + [ + "abCB", + "BABBbAABA", + 13 + ], + [ + "abCBB", + "Bb", + 7 + ], + [ + "abCBCB", + "baCaaa", + 10 + ], + [ + "abCC", + "CA", + 6 + ], + [ + "abCC", + "CCcCAcaA", + 13 + ], + [ + "abCCAacb", + "babaA", + 12 + ], + [ + "abCCBB", + "baB", + 8 + ], + [ + "abCCBBC", + "aAc", + 11 + ], + [ + "abCCCBaAC", + "CCBbCbBA", + 13 + ], + [ + "abCCaAca", + "bBCBCaC", + 10 + ], + [ + "abCCbBBaA", + "Ab", + 15 + ], + [ + "abCCc", + "BcbBabBc", + 12 + ], + [ + "abCCcAcBB", + "BaAAAaAB", + 13 + ], + [ + "abCa", + "ABA", + 5 + ], + [ + "abCaBC", + "bCCaCaA", + 10 + ], + [ + "abCaC", + "acc", + 6 + ], + [ + "abCaCAbB", + "aCaba", + 8 + ], + [ + "abCabA", + "ACba", + 6 + ], + [ + "abCabbccb", + "CcAcA", + 14 + ], + [ + "abCacaaC", + "aC", + 12 + ], + [ + "abCb", + "cBCBaA", + 8 + ], + [ + "abCbBCcAa", + "a", + 16 + ], + [ + "abCbaCC", + "CAabaabBB", + 14 + ], + [ + "abCbbba", + "CcbBCBAA", + 12 + ], + [ + "abCc", + "Ca", + 6 + ], + [ + "abCcAbbC", + "B", + 15 + ], + [ + "abCcCCC", + "BbAaca", + 11 + ], + [ + "abCcCc", + "ACacA", + 8 + ], + [ + "abCcaA", + "aca", + 6 + ], + [ + "abCcaABB", + "AbcBb", + 8 + ], + [ + "aba", + "CBb", + 5 + ], + [ + "aba", + "CBbbaAb", + 10 + ], + [ + "aba", + "aCccA", + 7 + ], + [ + "aba", + "acaB", + 4 + ], + [ + "aba", + "bcCCa", + 8 + ], + [ + "aba", + "cBB", + 5 + ], + [ + "abaAAA", + "cAbB", + 10 + ], + [ + "abaACCcC", + "abacCBA", + 7 + ], + [ + "abaAaccC", + "CCaacc", + 8 + ], + [ + "abaAbb", + "a", + 10 + ], + [ + "abaBCABc", + "aCcccB", + 11 + ], + [ + "abaBaCca", + "aAcAbaB", + 12 + ], + [ + "abaC", + "Bba", + 4 + ], + [ + "abaCACccC", + "BBBBB", + 17 + ], + [ + "abaCBABaB", + "abacbacc", + 9 + ], + [ + "abaCCbcA", + "AcBBc", + 11 + ], + [ + "abaCbB", + "cC", + 10 + ], + [ + "abaCbaCB", + "AAcC", + 11 + ], + [ + "abaCbaa", + "aCCaCCA", + 9 + ], + [ + "abaaACcB", + "cBBCBBAbC", + 16 + ], + [ + "abab", + "CcaCC", + 8 + ], + [ + "abab", + "aCAAaaaB", + 11 + ], + [ + "abab", + "acBbA", + 6 + ], + [ + "ababA", + "CBBC", + 8 + ], + [ + "ababACcC", + "BC", + 13 + ], + [ + "ababCBBbB", + "CbC", + 14 + ], + [ + "ababa", + "CABCcCcb", + 14 + ], + [ + "ababcCb", + "aCAbCc", + 7 + ], + [ + "abacA", + "ABb", + 8 + ], + [ + "abacABc", + "babCCAbb", + 8 + ], + [ + "abacAc", + "BacbCaaB", + 11 + ], + [ + "abacBBa", + "CbCAa", + 9 + ], + [ + "abacBbaCC", + "a", + 16 + ], + [ + "abacCb", + "b", + 10 + ], + [ + "abaca", + "cAB", + 9 + ], + [ + "abacaCbBc", + "bACcACB", + 10 + ], + [ + "abaccABb", + "cBcCc", + 12 + ], + [ + "abaccaAcA", + "B", + 17 + ], + [ + "abacccc", + "Cba", + 10 + ], + [ + "abb", + "B", + 5 + ], + [ + "abb", + "CbBBAc", + 9 + ], + [ + "abb", + "aC", + 4 + ], + [ + "abb", + "bbbcaca", + 10 + ], + [ + "abbAAABBb", + "CbCcB", + 14 + ], + [ + "abbAcB", + "BbaaBc", + 8 + ], + [ + "abbBBCcB", + "aaa", + 14 + ], + [ + "abbBbAbcc", + "c", + 16 + ], + [ + "abbBbbbcC", + "aBbBaB", + 10 + ], + [ + "abbCBA", + "aA", + 8 + ], + [ + "abbCBA", + "bb", + 8 + ], + [ + "abbCBabac", + "CBAB", + 12 + ], + [ + "abbCaBB", + "Ab", + 11 + ], + [ + "abbCaBC", + "aBcc", + 9 + ], + [ + "abbCbbBB", + "cbbaaca", + 12 + ], + [ + "abbCc", + "bC", + 6 + ], + [ + "abbaA", + "BabaAAaBb", + 11 + ], + [ + "abbaBaaBc", + "CAcBbc", + 13 + ], + [ + "abbaBbc", + "aABbAcc", + 8 + ], + [ + "abbaCcba", + "Cba", + 10 + ], + [ + "abbbA", + "ACAcc", + 9 + ], + [ + "abbbBAbAA", + "aCACcAbA", + 10 + ], + [ + "abbbCCCaC", + "acBCBABcC", + 13 + ], + [ + "abbbb", + "cCcbBcb", + 9 + ], + [ + "abbbc", + "C", + 9 + ], + [ + "abbbca", + "BBAcBbAB", + 12 + ], + [ + "abbcABB", + "ac", + 10 + ], + [ + "abbcB", + "ABbcCB", + 4 + ], + [ + "abbcBac", + "bACca", + 9 + ], + [ + "abbccAa", + "AcC", + 10 + ], + [ + "abbccb", + "cbCb", + 7 + ], + [ + "abc", + "AbC", + 2 + ], + [ + "abc", + "BAAb", + 7 + ], + [ + "abc", + "C", + 5 + ], + [ + "abc", + "aB", + 3 + ], + [ + "abcAA", + "CAAabC", + 11 + ], + [ + "abcAaaccb", + "cAcAAcbcA", + 11 + ], + [ + "abcAbB", + "AbcCacc", + 8 + ], + [ + "abcAcaB", + "acBcBb", + 7 + ], + [ + "abcAcbbb", + "CaABc", + 13 + ], + [ + "abcB", + "AaCcAbCa", + 11 + ], + [ + "abcB", + "bbab", + 5 + ], + [ + "abcBaBb", + "cBBa", + 8 + ], + [ + "abcBacc", + "ACBa", + 8 + ], + [ + "abcBb", + "BBbcccBB", + 9 + ], + [ + "abcBbBAb", + "cB", + 12 + ], + [ + "abcC", + "BCc", + 5 + ], + [ + "abcCAB", + "CbCcC", + 8 + ], + [ + "abcCBAc", + "ACCBaA", + 7 + ], + [ + "abcCaBcCB", + "AAba", + 15 + ], + [ + "abcCbBCA", + "Ba", + 13 + ], + [ + "abca", + "cbaccAA", + 9 + ], + [ + "abcaCB", + "CCaaAB", + 8 + ], + [ + "abcb", + "a", + 6 + ], + [ + "abcbbC", + "BBc", + 9 + ], + [ + "abcbbbcb", + "b", + 14 + ], + [ + "abccC", + "bbcBab", + 8 + ], + [ + "abccCBabC", + "AbABaaabc", + 10 + ], + [ + "abccCaB", + "Bbaa", + 10 + ], + [ + "abccCcAa", + "BCbb", + 13 + ], + [ + "abccaB", + "aCABaba", + 9 + ], + [ + "abccaC", + "ABbCB", + 9 + ], + [ + "abccaCcBB", + "AAB", + 14 + ], + [ + "abccbCC", + "BAc", + 11 + ], + [ + "ac", + "A", + 3 + ], + [ + "ac", + "AAaCcbBa", + 12 + ], + [ + "ac", + "AAcCbbCca", + 15 + ], + [ + "ac", + "ACAcCa", + 9 + ], + [ + "ac", + "ACCBbAcCc", + 15 + ], + [ + "ac", + "B", + 4 + ], + [ + "ac", + "BAab", + 6 + ], + [ + "ac", + "BCCBbccb", + 14 + ], + [ + "ac", + "BCc", + 4 + ], + [ + "ac", + "BaBbbAA", + 12 + ], + [ + "ac", + "BcCACbB", + 12 + ], + [ + "ac", + "CBA", + 6 + ], + [ + "ac", + "CccBcaB", + 12 + ], + [ + "ac", + "aB", + 2 + ], + [ + "ac", + "aBBCacBbC", + 14 + ], + [ + "ac", + "aC", + 1 + ], + [ + "ac", + "aCcCC", + 6 + ], + [ + "ac", + "aaCaCaca", + 12 + ], + [ + "ac", + "ab", + 2 + ], + [ + "ac", + "acacCAbBA", + 14 + ], + [ + "ac", + "bACaaCCCC", + 15 + ], + [ + "ac", + "bAbaCCcBC", + 14 + ], + [ + "ac", + "bCCaAaa", + 12 + ], + [ + "ac", + "cBAcc", + 7 + ], + [ + "acA", + "AaCCAaabc", + 13 + ], + [ + "acA", + "aABBBACc", + 12 + ], + [ + "acA", + "ac", + 2 + ], + [ + "acA", + "cBcBb", + 8 + ], + [ + "acAA", + "bc", + 6 + ], + [ + "acAAABBa", + "cbcC", + 13 + ], + [ + "acAACBA", + "BcbaCbbA", + 8 + ], + [ + "acAB", + "ccbAAAa", + 10 + ], + [ + "acABCCA", + "BCcABBAa", + 9 + ], + [ + "acABc", + "BBcABbCba", + 11 + ], + [ + "acAC", + "A", + 6 + ], + [ + "acACCB", + "AAcBCba", + 9 + ], + [ + "acAaaAcba", + "BabCcbCbb", + 15 + ], + [ + "acAaaaBc", + "BacCBc", + 10 + ], + [ + "acAabACa", + "acaCB", + 8 + ], + [ + "acAabBbA", + "cabCb", + 8 + ], + [ + "acAacCc", + "BBCC", + 11 + ], + [ + "acAb", + "cc", + 6 + ], + [ + "acAbabcBa", + "ABABcaB", + 11 + ], + [ + "acAc", + "ABa", + 6 + ], + [ + "acAc", + "CcBCaa", + 9 + ], + [ + "acAc", + "aBbCaB", + 8 + ], + [ + "acAcBCCa", + "BaB", + 13 + ], + [ + "acAcCAc", + "cAbab", + 9 + ], + [ + "acAcaC", + "B", + 12 + ], + [ + "acAcabB", + "baaacbB", + 7 + ], + [ + "acAcbcAC", + "ccaCbCcc", + 8 + ], + [ + "acB", + "ABcAcBAcb", + 13 + ], + [ + "acB", + "acb", + 1 + ], + [ + "acB", + "bCccA", + 8 + ], + [ + "acB", + "cbabb", + 7 + ], + [ + "acBA", + "BcAaBCBAA", + 11 + ], + [ + "acBAcc", + "AAabb", + 10 + ], + [ + "acBB", + "AaaAbB", + 7 + ], + [ + "acBB", + "cA", + 6 + ], + [ + "acBBAb", + "bCcAbBCAC", + 11 + ], + [ + "acBBAbacB", + "aaCBB", + 12 + ], + [ + "acBBBbAcb", + "BAa", + 14 + ], + [ + "acBBCC", + "ccAbcBbCb", + 10 + ], + [ + "acBBCc", + "bcCaAbAa", + 13 + ], + [ + "acBBbBa", + "Cba", + 9 + ], + [ + "acBBcCC", + "ABbb", + 10 + ], + [ + "acBCBcabA", + "caCbca", + 9 + ], + [ + "acBCCbab", + "ccA", + 12 + ], + [ + "acBCa", + "BabbAcb", + 10 + ], + [ + "acBa", + "AC", + 6 + ], + [ + "acBaA", + "ABBCC", + 7 + ], + [ + "acBaAacc", + "bbab", + 13 + ], + [ + "acBaBCab", + "ac", + 12 + ], + [ + "acBaBaC", + "bbccbac", + 10 + ], + [ + "acBacCbAc", + "bBBACbaAA", + 11 + ], + [ + "acBb", + "CA", + 7 + ], + [ + "acBb", + "bCBAAB", + 8 + ], + [ + "acBbAc", + "aaaBCA", + 8 + ], + [ + "acBbCca", + "acA", + 9 + ], + [ + "acBcCcaB", + "CC", + 13 + ], + [ + "acBcabcAC", + "Aac", + 13 + ], + [ + "acC", + "AABcbaC", + 9 + ], + [ + "acC", + "AcBAaC", + 7 + ], + [ + "acC", + "CabBabaBC", + 14 + ], + [ + "acC", + "b", + 6 + ], + [ + "acC", + "baBCBCbAC", + 13 + ], + [ + "acC", + "baaCBCBC", + 11 + ], + [ + "acCA", + "C", + 6 + ], + [ + "acCA", + "aabBCaaaC", + 13 + ], + [ + "acCAAaCB", + "ba", + 14 + ], + [ + "acCAB", + "BccCcCbB", + 10 + ], + [ + "acCAa", + "c", + 8 + ], + [ + "acCAabBcc", + "Bb", + 16 + ], + [ + "acCAb", + "abB", + 7 + ], + [ + "acCAcc", + "cccCCacCC", + 10 + ], + [ + "acCB", + "B", + 6 + ], + [ + "acCBAcb", + "acaBcA", + 6 + ], + [ + "acCBBA", + "caccBbabC", + 9 + ], + [ + "acCBBb", + "aCbc", + 7 + ], + [ + "acCBCbaBC", + "bCabBc", + 11 + ], + [ + "acCBaaAc", + "BAAABCA", + 13 + ], + [ + "acCC", + "BAB", + 8 + ], + [ + "acCCABBb", + "Bbcca", + 13 + ], + [ + "acCCCaAb", + "acCCbAcca", + 9 + ], + [ + "acCCab", + "aB", + 9 + ], + [ + "acCCbA", + "CCAbCbBA", + 9 + ], + [ + "acCCbBcc", + "aAAaA", + 14 + ], + [ + "acCCcaC", + "AcbBabcCb", + 13 + ], + [ + "acCCcab", + "ccab", + 6 + ], + [ + "acCa", + "aaAcAbBbB", + 14 + ], + [ + "acCaAA", + "BAbcAcbC", + 13 + ], + [ + "acCaABcbb", + "A", + 16 + ], + [ + "acCaC", + "abBACBCB", + 10 + ], + [ + "acCaCa", + "aBa", + 8 + ], + [ + "acCb", + "aACBa", + 5 + ], + [ + "acCbBBa", + "BbbACCccb", + 15 + ], + [ + "acCbCCCBb", + "B", + 16 + ], + [ + "acCbbBB", + "bcca", + 11 + ], + [ + "acCc", + "A", + 7 + ], + [ + "acCc", + "AABBa", + 9 + ], + [ + "acCcA", + "CBBcaBCCA", + 11 + ], + [ + "acCcAcB", + "b", + 13 + ], + [ + "acCcB", + "BBCCBA", + 7 + ], + [ + "acCcaAA", + "b", + 14 + ], + [ + "acCcacB", + "cbAccBcb", + 10 + ], + [ + "acCcc", + "AC", + 7 + ], + [ + "aca", + "ACAB", + 5 + ], + [ + "aca", + "AaAc", + 6 + ], + [ + "aca", + "cAAbacBCa", + 12 + ], + [ + "aca", + "cbCCabAaB", + 14 + ], + [ + "acaA", + "C", + 7 + ], + [ + "acaA", + "aCbcACABa", + 11 + ], + [ + "acaAAAbc", + "B", + 15 + ], + [ + "acaABAcA", + "Cacca", + 10 + ], + [ + "acaAcb", + "cbc", + 8 + ], + [ + "acaBA", + "baBCbCBBC", + 13 + ], + [ + "acaBcacBc", + "b", + 17 + ], + [ + "acaBccaC", + "C", + 14 + ], + [ + "acaC", + "bccCAccA", + 12 + ], + [ + "acaCAaBB", + "Bc", + 14 + ], + [ + "acaCBBB", + "A", + 13 + ], + [ + "acaCaA", + "BCB", + 10 + ], + [ + "acaCaC", + "c", + 10 + ], + [ + "acaCbC", + "acaabCaa", + 6 + ], + [ + "acaCcaAB", + "ABAaaaba", + 12 + ], + [ + "acaCcbcB", + "CcbACBBc", + 10 + ], + [ + "acaCcbcc", + "C", + 14 + ], + [ + "acaa", + "AabBa", + 6 + ], + [ + "acaaBAA", + "cBBbaAB", + 10 + ], + [ + "acaaCC", + "cAACA", + 6 + ], + [ + "acaaaCCA", + "AaC", + 11 + ], + [ + "acaacAbB", + "bCbb", + 12 + ], + [ + "acaaca", + "cBACccCa", + 11 + ], + [ + "acaacb", + "ccCbBca", + 10 + ], + [ + "acabCb", + "ccaCbA", + 6 + ], + [ + "acabaBCbB", + "cab", + 12 + ], + [ + "acabcA", + "AB", + 10 + ], + [ + "acacA", + "bcBACcaa", + 10 + ], + [ + "acacbA", + "A", + 10 + ], + [ + "acacbABa", + "bCbcBa", + 9 + ], + [ + "acacbBa", + "BAbA", + 10 + ], + [ + "acb", + "AabAbA", + 8 + ], + [ + "acb", + "BCCa", + 7 + ], + [ + "acb", + "cBCB", + 6 + ], + [ + "acbA", + "AcBCbCa", + 8 + ], + [ + "acbA", + "aAaBCAB", + 9 + ], + [ + "acbAA", + "bc", + 8 + ], + [ + "acbACbbA", + "a", + 14 + ], + [ + "acbAbacc", + "caAcB", + 10 + ], + [ + "acbB", + "aBAb", + 5 + ], + [ + "acbBBC", + "caB", + 8 + ], + [ + "acbBaAAb", + "bbBcABBaC", + 13 + ], + [ + "acbBaC", + "CaA", + 9 + ], + [ + "acbBc", + "abB", + 4 + ], + [ + "acbC", + "ABABaBbca", + 13 + ], + [ + "acbC", + "bbBbcaaA", + 13 + ], + [ + "acbCA", + "A", + 8 + ], + [ + "acbCA", + "cBb", + 7 + ], + [ + "acbCBb", + "acAbCCaBc", + 8 + ], + [ + "acbCaaAaB", + "CaCc", + 14 + ], + [ + "acbCabC", + "cBACbcBC", + 10 + ], + [ + "acbaBaA", + "BBcA", + 9 + ], + [ + "acbabAa", + "CaAbcbc", + 10 + ], + [ + "acbabbaB", + "c", + 14 + ], + [ + "acbb", + "AAa", + 7 + ], + [ + "acbb", + "AbCaBCcBA", + 13 + ], + [ + "acbb", + "BbAca", + 9 + ], + [ + "acbb", + "CBaCBcCbA", + 12 + ], + [ + "acbbA", + "AcCAAbc", + 9 + ], + [ + "acbbAbA", + "CCCaccab", + 13 + ], + [ + "acbbcABBB", + "BcCBbb", + 11 + ], + [ + "acbc", + "aBAbB", + 6 + ], + [ + "acbcAa", + "BaCC", + 10 + ], + [ + "acbcBbc", + "B", + 12 + ], + [ + "acbcCAC", + "ACaAab", + 11 + ], + [ + "acbcCab", + "BAb", + 10 + ], + [ + "acbcaacbc", + "CbccCCBcb", + 11 + ], + [ + "acbcbbcc", + "CcAbaCCbb", + 13 + ], + [ + "acbccACA", + "b", + 14 + ], + [ + "acbccBcB", + "CCcc", + 10 + ], + [ + "acbccCB", + "bbbaaCAAB", + 12 + ], + [ + "acc", + "C", + 5 + ], + [ + "acc", + "bAac", + 5 + ], + [ + "accA", + "CBBaA", + 8 + ], + [ + "accAA", + "C", + 9 + ], + [ + "accABbb", + "cBbAAbaC", + 12 + ], + [ + "accAac", + "CCcacb", + 7 + ], + [ + "accAb", + "bcbAbB", + 6 + ], + [ + "accAcbAa", + "B", + 15 + ], + [ + "accB", + "bBbAb", + 9 + ], + [ + "accBC", + "bCCCCab", + 10 + ], + [ + "accBbcCaa", + "aaCcaBACB", + 12 + ], + [ + "accC", + "CCcbBc", + 8 + ], + [ + "accC", + "aa", + 6 + ], + [ + "accCAcACa", + "AABbCCc", + 14 + ], + [ + "accCCaCB", + "aCBba", + 11 + ], + [ + "accCCcA", + "cBbA", + 10 + ], + [ + "accCaB", + "aBaB", + 6 + ], + [ + "accCbbC", + "cBBcaa", + 12 + ], + [ + "acca", + "BBCAcbaba", + 13 + ], + [ + "acca", + "BBcCC", + 7 + ], + [ + "accaACAAc", + "cbbCABA", + 12 + ], + [ + "accaaAcAb", + "ccB", + 13 + ], + [ + "accaaC", + "CbA", + 10 + ], + [ + "accab", + "AAaAcA", + 9 + ], + [ + "accabbcB", + "aa", + 12 + ], + [ + "accac", + "C", + 9 + ], + [ + "accb", + "CBCAcCaBB", + 13 + ], + [ + "accbAA", + "Ba", + 10 + ], + [ + "accbAa", + "Ca", + 9 + ], + [ + "accbBac", + "caC", + 9 + ], + [ + "accbCcBAc", + "cbA", + 12 + ], + [ + "accbaAc", + "ACAaCAAA", + 11 + ], + [ + "accbb", + "CCcC", + 7 + ], + [ + "acccAB", + "cb", + 9 + ], + [ + "acccC", + "baaaaB", + 10 + ], + [ + "acccaAaB", + "C", + 15 + ], + [ + "acccc", + "cAAC", + 7 + ], + [ + "b", + "A", + 2 + ], + [ + "b", + "AAbAaCb", + 12 + ], + [ + "b", + "ABBBC", + 9 + ], + [ + "b", + "ABbCCBC", + 12 + ], + [ + "b", + "ABc", + 5 + ], + [ + "b", + "ABcba", + 8 + ], + [ + "b", + "ACAAC", + 10 + ], + [ + "b", + "ACBbAcabc", + 16 + ], + [ + "b", + "ACBbCBcCC", + 16 + ], + [ + "b", + "ACCaA", + 10 + ], + [ + "b", + "Aa", + 4 + ], + [ + "b", + "AaAbBCA", + 12 + ], + [ + "b", + "Aac", + 6 + ], + [ + "b", + "AacaBc", + 11 + ], + [ + "b", + "AbaC", + 6 + ], + [ + "b", + "Abcba", + 8 + ], + [ + "b", + "Ac", + 4 + ], + [ + "b", + "AcCbB", + 8 + ], + [ + "b", + "AcCbBca", + 12 + ], + [ + "b", + "AcaaCaAa", + 16 + ], + [ + "b", + "B", + 1 + ], + [ + "b", + "BBA", + 5 + ], + [ + "b", + "BBaaabB", + 12 + ], + [ + "b", + "BBbaBcba", + 14 + ], + [ + "b", + "BCBaC", + 9 + ], + [ + "b", + "BCcAABb", + 12 + ], + [ + "b", + "Ba", + 3 + ], + [ + "b", + "BaC", + 5 + ], + [ + "b", + "BaCbBc", + 10 + ], + [ + "b", + "Bac", + 5 + ], + [ + "b", + "BacBAc", + 11 + ], + [ + "b", + "Bb", + 2 + ], + [ + "b", + "BbA", + 4 + ], + [ + "b", + "BbBacbb", + 12 + ], + [ + "b", + "BbacaCc", + 12 + ], + [ + "b", + "BbbcAcAab", + 16 + ], + [ + "b", + "Bc", + 3 + ], + [ + "b", + "BcBA", + 7 + ], + [ + "b", + "BcBCAAaCB", + 17 + ], + [ + "b", + "BcC", + 5 + ], + [ + "b", + "BcCa", + 7 + ], + [ + "b", + "C", + 2 + ], + [ + "b", + "CAB", + 5 + ], + [ + "b", + "CACCBca", + 13 + ], + [ + "b", + "CBABaBAB", + 15 + ], + [ + "b", + "CBCCa", + 9 + ], + [ + "b", + "CBCCcC", + 11 + ], + [ + "b", + "CBCcaBBAB", + 17 + ], + [ + "b", + "CBaBaaCbB", + 16 + ], + [ + "b", + "CBaabA", + 10 + ], + [ + "b", + "CCBABBcC", + 15 + ], + [ + "b", + "CCC", + 6 + ], + [ + "b", + "CCbCA", + 8 + ], + [ + "b", + "Ca", + 4 + ], + [ + "b", + "CaCBcA", + 11 + ], + [ + "b", + "Cabacc", + 10 + ], + [ + "b", + "CabccCBCB", + 16 + ], + [ + "b", + "CacAbAAac", + 16 + ], + [ + "b", + "CbA", + 4 + ], + [ + "b", + "CbcBbA", + 10 + ], + [ + "b", + "Cc", + 4 + ], + [ + "b", + "CcAABbA", + 12 + ], + [ + "b", + "CcabBB", + 10 + ], + [ + "b", + "CcacCcAcC", + 18 + ], + [ + "b", + "CcbCACB", + 12 + ], + [ + "b", + "Ccbc", + 6 + ], + [ + "b", + "a", + 2 + ], + [ + "b", + "aA", + 4 + ], + [ + "b", + "aABBCcC", + 13 + ], + [ + "b", + "aABccbaCc", + 16 + ], + [ + "b", + "aACCCCba", + 14 + ], + [ + "b", + "aACCa", + 10 + ], + [ + "b", + "aAaBA", + 9 + ], + [ + "b", + "aAacBab", + 12 + ], + [ + "b", + "aB", + 3 + ], + [ + "b", + "aBCAac", + 11 + ], + [ + "b", + "aBCBC", + 9 + ], + [ + "b", + "aBCbacab", + 14 + ], + [ + "b", + "aBaCa", + 9 + ], + [ + "b", + "aBc", + 5 + ], + [ + "b", + "aC", + 4 + ], + [ + "b", + "aCBA", + 7 + ], + [ + "b", + "aCCa", + 8 + ], + [ + "b", + "aa", + 4 + ], + [ + "b", + "aabCCBca", + 14 + ], + [ + "b", + "aabbAb", + 10 + ], + [ + "b", + "aabbcC", + 10 + ], + [ + "b", + "ab", + 2 + ], + [ + "b", + "abB", + 4 + ], + [ + "b", + "abBBcb", + 10 + ], + [ + "b", + "abCCBCA", + 12 + ], + [ + "b", + "abcACcaa", + 14 + ], + [ + "b", + "abcBccCa", + 14 + ], + [ + "b", + "abca", + 6 + ], + [ + "b", + "ac", + 4 + ], + [ + "b", + "acACCaAca", + 18 + ], + [ + "b", + "acC", + 6 + ], + [ + "b", + "aca", + 6 + ], + [ + "b", + "acaabCC", + 12 + ], + [ + "b", + "b", + 0 + ], + [ + "b", + "bACAbB", + 10 + ], + [ + "b", + "bBAccb", + 10 + ], + [ + "b", + "bBBC", + 6 + ], + [ + "b", + "bBbaAA", + 10 + ], + [ + "b", + "bC", + 2 + ], + [ + "b", + "bCA", + 4 + ], + [ + "b", + "bCBCAbaCA", + 16 + ], + [ + "b", + "bCCbA", + 8 + ], + [ + "b", + "bCaAC", + 8 + ], + [ + "b", + "bCaCAB", + 10 + ], + [ + "b", + "bCabBB", + 10 + ], + [ + "b", + "bCbBbcA", + 12 + ], + [ + "b", + "bCcbAb", + 10 + ], + [ + "b", + "baBACBaaB", + 16 + ], + [ + "b", + "baCCbBC", + 12 + ], + [ + "b", + "baabBABb", + 14 + ], + [ + "b", + "baabbaCBB", + 16 + ], + [ + "b", + "baaccc", + 10 + ], + [ + "b", + "babBAcb", + 12 + ], + [ + "b", + "bb", + 2 + ], + [ + "b", + "bcBACbaBb", + 16 + ], + [ + "b", + "bcCa", + 6 + ], + [ + "b", + "bcCcAcbc", + 14 + ], + [ + "b", + "c", + 2 + ], + [ + "b", + "cABCaB", + 11 + ], + [ + "b", + "cAC", + 6 + ], + [ + "b", + "cAbBbBcc", + 14 + ], + [ + "b", + "cAbaBABBc", + 16 + ], + [ + "b", + "cAbaCBAa", + 14 + ], + [ + "b", + "cBAAAcaA", + 15 + ], + [ + "b", + "cBBcbAabb", + 16 + ], + [ + "b", + "cBCCAcb", + 12 + ], + [ + "b", + "cBCCa", + 9 + ], + [ + "b", + "cBCaCAc", + 13 + ], + [ + "b", + "cBbaBbcB", + 14 + ], + [ + "b", + "cBbbBaAa", + 14 + ], + [ + "b", + "cCAACba", + 12 + ], + [ + "b", + "cCaccAaCb", + 16 + ], + [ + "b", + "cCcbA", + 8 + ], + [ + "b", + "caA", + 6 + ], + [ + "b", + "caAb", + 6 + ], + [ + "b", + "caAccaCbb", + 16 + ], + [ + "b", + "caB", + 5 + ], + [ + "b", + "cabBbcA", + 12 + ], + [ + "b", + "cacbbBAb", + 14 + ], + [ + "b", + "cb", + 2 + ], + [ + "b", + "cbAa", + 6 + ], + [ + "b", + "cbAaCa", + 10 + ], + [ + "b", + "cbcBAcCbA", + 16 + ], + [ + "b", + "ccCaacc", + 14 + ], + [ + "b", + "ccCbaaaa", + 14 + ], + [ + "b", + "ccaC", + 8 + ], + [ + "b", + "ccbbcb", + 10 + ], + [ + "bA", + "AAAcACAbA", + 14 + ], + [ + "bA", + "AAbB", + 6 + ], + [ + "bA", + "ABaC", + 6 + ], + [ + "bA", + "ACBbbA", + 8 + ], + [ + "bA", + "ACBbccB", + 12 + ], + [ + "bA", + "ACbbBB", + 10 + ], + [ + "bA", + "Accc", + 8 + ], + [ + "bA", + "BAcCabc", + 11 + ], + [ + "bA", + "BCCcccB", + 13 + ], + [ + "bA", + "BabcCBCaa", + 15 + ], + [ + "bA", + "BbCaCBca", + 13 + ], + [ + "bA", + "BbbcbCCB", + 14 + ], + [ + "bA", + "Bbca", + 5 + ], + [ + "bA", + "C", + 4 + ], + [ + "bA", + "CAbABbBC", + 12 + ], + [ + "bA", + "CB", + 4 + ], + [ + "bA", + "CBCBa", + 8 + ], + [ + "bA", + "CbAaAaB", + 10 + ], + [ + "bA", + "CbcAAc", + 8 + ], + [ + "bA", + "CcAAcbcBC", + 16 + ], + [ + "bA", + "aAc", + 4 + ], + [ + "bA", + "aBCCcAcb", + 13 + ], + [ + "bA", + "aCb", + 6 + ], + [ + "bA", + "aCcbbc", + 10 + ], + [ + "bA", + "bAbcaAA", + 10 + ], + [ + "bA", + "bAc", + 2 + ], + [ + "bA", + "ba", + 1 + ], + [ + "bA", + "bb", + 2 + ], + [ + "bA", + "cAcCABaAb", + 15 + ], + [ + "bA", + "cCAABbBb", + 14 + ], + [ + "bA", + "cCC", + 6 + ], + [ + "bA", + "cabc", + 6 + ], + [ + "bA", + "ccCbcC", + 10 + ], + [ + "bAA", + "AabBB", + 8 + ], + [ + "bAA", + "BBAAaBCbC", + 13 + ], + [ + "bAA", + "C", + 6 + ], + [ + "bAA", + "CBaBcBb", + 12 + ], + [ + "bAA", + "CCBAc", + 7 + ], + [ + "bAA", + "abBBBAA", + 8 + ], + [ + "bAA", + "baacAbb", + 9 + ], + [ + "bAA", + "bcbBccAb", + 12 + ], + [ + "bAAA", + "C", + 8 + ], + [ + "bAAA", + "bBAAC", + 4 + ], + [ + "bAAAA", + "c", + 10 + ], + [ + "bAAAAbaCa", + "abacbC", + 12 + ], + [ + "bAAACbbb", + "Aaa", + 12 + ], + [ + "bAAACccC", + "CC", + 12 + ], + [ + "bAAAacAc", + "caC", + 12 + ], + [ + "bAAAbABC", + "CBCbBa", + 12 + ], + [ + "bAAB", + "bC", + 6 + ], + [ + "bAABaCA", + "A", + 12 + ], + [ + "bAABb", + "CaBBB", + 6 + ], + [ + "bAABcbbB", + "CbACCB", + 11 + ], + [ + "bAAC", + "AabbbaBA", + 13 + ], + [ + "bAAC", + "Acb", + 6 + ], + [ + "bAAC", + "aaB", + 6 + ], + [ + "bAACcACbc", + "Aaa", + 14 + ], + [ + "bAAa", + "AaABbabC", + 11 + ], + [ + "bAAa", + "bCAbbaa", + 7 + ], + [ + "bAAaCba", + "bAca", + 7 + ], + [ + "bAAaaaa", + "BC", + 13 + ], + [ + "bAAbAab", + "CBcA", + 12 + ], + [ + "bAAbCa", + "C", + 10 + ], + [ + "bAAbb", + "A", + 8 + ], + [ + "bAAc", + "CCAbca", + 8 + ], + [ + "bAAc", + "cCBAba", + 9 + ], + [ + "bAAcA", + "bbc", + 6 + ], + [ + "bAAccABbb", + "aAc", + 13 + ], + [ + "bAAccaCC", + "A", + 14 + ], + [ + "bAB", + "ACBcB", + 7 + ], + [ + "bAB", + "CbAA", + 4 + ], + [ + "bAB", + "bbABC", + 4 + ], + [ + "bABAC", + "BCaabaCBC", + 12 + ], + [ + "bABACCCB", + "abB", + 12 + ], + [ + "bABAaB", + "cCCCBbc", + 13 + ], + [ + "bABAaCBBA", + "BAbA", + 11 + ], + [ + "bABAaba", + "A", + 12 + ], + [ + "bABAba", + "BBBAccBA", + 9 + ], + [ + "bABAbbCbB", + "bA", + 14 + ], + [ + "bABAcCCb", + "abCaB", + 11 + ], + [ + "bABBCCC", + "bbCbBc", + 10 + ], + [ + "bABBa", + "bA", + 6 + ], + [ + "bABBcAc", + "bAC", + 9 + ], + [ + "bABC", + "AbBca", + 7 + ], + [ + "bABCBcAc", + "cBCAA", + 10 + ], + [ + "bABCC", + "aBCa", + 5 + ], + [ + "bABCCbbbC", + "cABACBBa", + 10 + ], + [ + "bABCaBCa", + "CaBbB", + 10 + ], + [ + "bABCbaB", + "ACCaCBc", + 10 + ], + [ + "bABa", + "BA", + 5 + ], + [ + "bABa", + "BAB", + 3 + ], + [ + "bABaCBBAa", + "Aa", + 14 + ], + [ + "bABb", + "acA", + 7 + ], + [ + "bABb", + "ccCA", + 8 + ], + [ + "bABbAB", + "ABCBCACc", + 11 + ], + [ + "bABbAC", + "bcBaCcAb", + 10 + ], + [ + "bABbBCB", + "cb", + 12 + ], + [ + "bABbaB", + "cAcAbbCAb", + 11 + ], + [ + "bABbaBAA", + "C", + 16 + ], + [ + "bABbbABBB", + "cAcb", + 14 + ], + [ + "bABbbbC", + "a", + 13 + ], + [ + "bABbcCab", + "ccccaa", + 11 + ], + [ + "bAC", + "AcAbc", + 7 + ], + [ + "bAC", + "bCcbAabb", + 12 + ], + [ + "bACA", + "ACaAAAaC", + 12 + ], + [ + "bACA", + "CAc", + 5 + ], + [ + "bACA", + "c", + 7 + ], + [ + "bACACAAa", + "cbBbC", + 14 + ], + [ + "bACAb", + "Cbc", + 8 + ], + [ + "bACAcc", + "BacBcAB", + 9 + ], + [ + "bACB", + "cBccC", + 8 + ], + [ + "bACBAC", + "BCCABaC", + 6 + ], + [ + "bACBBaA", + "bBACB", + 8 + ], + [ + "bACBCbb", + "b", + 12 + ], + [ + "bACBa", + "BCC", + 7 + ], + [ + "bACBaAcAb", + "C", + 16 + ], + [ + "bACBabABB", + "aCA", + 13 + ], + [ + "bACBcB", + "accBbaCcC", + 13 + ], + [ + "bACCaCcBb", + "aBaBcb", + 11 + ], + [ + "bACaBC", + "bA", + 8 + ], + [ + "bACaCCBAB", + "aaBacb", + 13 + ], + [ + "bACaCcC", + "aBCac", + 8 + ], + [ + "bACab", + "aBcBcAB", + 10 + ], + [ + "bACac", + "Bc", + 7 + ], + [ + "bACac", + "cBAacCAcB", + 10 + ], + [ + "bACbAA", + "B", + 11 + ], + [ + "bACbBbc", + "CbcAcBcA", + 11 + ], + [ + "bACc", + "bcA", + 5 + ], + [ + "bACc", + "cCb", + 6 + ], + [ + "bACcABb", + "BaC", + 10 + ], + [ + "bACcCaaC", + "AAbAbb", + 13 + ], + [ + "bAa", + "AaaACbAcc", + 14 + ], + [ + "bAa", + "AcAabb", + 8 + ], + [ + "bAa", + "bCbBCABBA", + 13 + ], + [ + "bAaA", + "aACcbaB", + 10 + ], + [ + "bAaAA", + "BacbCbBaa", + 14 + ], + [ + "bAaAAAa", + "cabAbA", + 9 + ], + [ + "bAaABAAC", + "AB", + 12 + ], + [ + "bAaAcA", + "cbC", + 11 + ], + [ + "bAaB", + "B", + 6 + ], + [ + "bAaB", + "acCCbb", + 11 + ], + [ + "bAaBAA", + "a", + 10 + ], + [ + "bAaBAcac", + "AABcaa", + 7 + ], + [ + "bAaBC", + "CBcaaaBa", + 10 + ], + [ + "bAaBa", + "C", + 10 + ], + [ + "bAaBaCBAB", + "CCbCCC", + 15 + ], + [ + "bAaBb", + "bCAA", + 7 + ], + [ + "bAaBbBc", + "AC", + 11 + ], + [ + "bAaBbc", + "cbbcAaab", + 10 + ], + [ + "bAaBc", + "a", + 8 + ], + [ + "bAaBc", + "cbCCBBbcc", + 12 + ], + [ + "bAaC", + "ABA", + 6 + ], + [ + "bAaC", + "BAaACAB", + 7 + ], + [ + "bAaCCa", + "CbCBB", + 10 + ], + [ + "bAaCa", + "bCBaABcbB", + 13 + ], + [ + "bAaCab", + "caBC", + 9 + ], + [ + "bAaa", + "AAcCbAC", + 11 + ], + [ + "bAaa", + "bBAAb", + 5 + ], + [ + "bAaaA", + "AbA", + 6 + ], + [ + "bAaaB", + "CABBC", + 8 + ], + [ + "bAaaB", + "bABBaC", + 6 + ], + [ + "bAaabaAc", + "cBaa", + 12 + ], + [ + "bAab", + "CcC", + 8 + ], + [ + "bAab", + "bAc", + 4 + ], + [ + "bAabAbC", + "A", + 12 + ], + [ + "bAabBACA", + "cCca", + 14 + ], + [ + "bAabBBCb", + "CbaCbA", + 12 + ], + [ + "bAabCAB", + "aA", + 10 + ], + [ + "bAabb", + "AaacAAA", + 11 + ], + [ + "bAabbC", + "babAb", + 6 + ], + [ + "bAabcA", + "bA", + 8 + ], + [ + "bAabcbc", + "BaAabcAaa", + 9 + ], + [ + "bAabccAa", + "aCbaB", + 12 + ], + [ + "bAac", + "ccACbbAA", + 13 + ], + [ + "bAacA", + "BBaACC", + 8 + ], + [ + "bAacAb", + "bacccBCc", + 10 + ], + [ + "bAacAbBca", + "C", + 17 + ], + [ + "bAacC", + "bbaAb", + 6 + ], + [ + "bAaccCB", + "c", + 12 + ], + [ + "bAb", + "ABCCAACCA", + 15 + ], + [ + "bAb", + "CCBAA", + 7 + ], + [ + "bAbACaC", + "bABcCAb", + 6 + ], + [ + "bAbAaC", + "BAbbA", + 6 + ], + [ + "bAbAabcab", + "CAabcBa", + 10 + ], + [ + "bAbAbcA", + "CAABBca", + 8 + ], + [ + "bAbBC", + "AACcbCca", + 11 + ], + [ + "bAbBCbaB", + "acacab", + 11 + ], + [ + "bAbBaCBcb", + "BaA", + 14 + ], + [ + "bAbBbbBbb", + "cAaacAC", + 16 + ], + [ + "bAbCAABaB", + "ACcBAccB", + 11 + ], + [ + "bAbCbCcCA", + "aBB", + 15 + ], + [ + "bAbCcB", + "aCAbA", + 10 + ], + [ + "bAba", + "CaAAb", + 8 + ], + [ + "bAbaCC", + "Bba", + 7 + ], + [ + "bAbab", + "CbCAcBcB", + 10 + ], + [ + "bAbb", + "acAbCBb", + 8 + ], + [ + "bAbb", + "b", + 6 + ], + [ + "bAbb", + "bAaAcBBab", + 11 + ], + [ + "bAbbB", + "AaACcBacC", + 14 + ], + [ + "bAbbaa", + "cCBC", + 11 + ], + [ + "bAbbbbBc", + "cABcBBC", + 9 + ], + [ + "bAbbbc", + "aCA", + 11 + ], + [ + "bAbc", + "Accbacc", + 9 + ], + [ + "bAbcBB", + "A", + 10 + ], + [ + "bAbcCaA", + "bACaCAcbC", + 11 + ], + [ + "bAbcCab", + "ABC", + 9 + ], + [ + "bAbcCbAA", + "BcBb", + 11 + ], + [ + "bAbcbA", + "CBCBCac", + 11 + ], + [ + "bAc", + "aaC", + 4 + ], + [ + "bAcA", + "bb", + 6 + ], + [ + "bAcAABA", + "aAAbca", + 9 + ], + [ + "bAcAAbA", + "BABabc", + 8 + ], + [ + "bAcAAbb", + "aC", + 12 + ], + [ + "bAcAbc", + "CAAcaaBa", + 10 + ], + [ + "bAcAc", + "BaC", + 7 + ], + [ + "bAcB", + "bcBC", + 4 + ], + [ + "bAcB", + "bccaA", + 6 + ], + [ + "bAcBAaaCa", + "aABCccaBa", + 12 + ], + [ + "bAcBAbBcC", + "B", + 16 + ], + [ + "bAcBBBABa", + "caA", + 14 + ], + [ + "bAcBCCbac", + "accBBAB", + 12 + ], + [ + "bAcBabbC", + "ca", + 12 + ], + [ + "bAcBbAb", + "cbaBA", + 9 + ], + [ + "bAcBbaA", + "AaBaaA", + 6 + ], + [ + "bAcC", + "Acac", + 5 + ], + [ + "bAcC", + "bba", + 6 + ], + [ + "bAcCAa", + "C", + 10 + ], + [ + "bAcCaCc", + "abbCcb", + 11 + ], + [ + "bAcCb", + "Bbcacac", + 9 + ], + [ + "bAcCbACCB", + "aA", + 15 + ], + [ + "bAcCcACA", + "ABCAaBB", + 11 + ], + [ + "bAcaAbA", + "baCCbBca", + 10 + ], + [ + "bAcac", + "BBbAbCccA", + 11 + ], + [ + "bAcb", + "aa", + 7 + ], + [ + "bAcb", + "cAbbb", + 6 + ], + [ + "bAcbAbBAC", + "CbBc", + 12 + ], + [ + "bAcbBbcb", + "cCcBBCaa", + 11 + ], + [ + "bAcc", + "aCcABC", + 9 + ], + [ + "bAccA", + "ccBaaCA", + 9 + ], + [ + "bAccCabB", + "aABCAAaAA", + 13 + ], + [ + "bAccCbb", + "BaAAcB", + 10 + ], + [ + "bAcca", + "CBbcBBBba", + 13 + ], + [ + "bAccaACaa", + "CCcBCaA", + 10 + ], + [ + "bAccaCCA", + "aABAABBCb", + 13 + ], + [ + "bAcccAC", + "ABC", + 10 + ], + [ + "bAcccAbaA", + "CBcCAbcC", + 11 + ], + [ + "bAcccaB", + "BccACBBC", + 10 + ], + [ + "bB", + "ACaAbC", + 10 + ], + [ + "bB", + "BCbCb", + 7 + ], + [ + "bB", + "Baa", + 5 + ], + [ + "bB", + "BbbbBAb", + 10 + ], + [ + "bB", + "Bcba", + 6 + ], + [ + "bB", + "CAAcBb", + 10 + ], + [ + "bB", + "CbbC", + 5 + ], + [ + "bB", + "aACCab", + 11 + ], + [ + "bB", + "aBaccb", + 10 + ], + [ + "bB", + "aBbC", + 6 + ], + [ + "bB", + "aCACcaA", + 14 + ], + [ + "bB", + "aaAABaCCc", + 16 + ], + [ + "bB", + "ac", + 4 + ], + [ + "bB", + "acACccAac", + 18 + ], + [ + "bB", + "bAAacb", + 9 + ], + [ + "bB", + "bBCccbcC", + 12 + ], + [ + "bB", + "bBbbcb", + 8 + ], + [ + "bB", + "bCbBAAbCc", + 14 + ], + [ + "bB", + "ba", + 2 + ], + [ + "bB", + "baAbcC", + 9 + ], + [ + "bB", + "bcBbACaa", + 12 + ], + [ + "bB", + "c", + 4 + ], + [ + "bB", + "cBCcc", + 8 + ], + [ + "bB", + "cCBa", + 6 + ], + [ + "bB", + "caBc", + 6 + ], + [ + "bB", + "caCCACb", + 13 + ], + [ + "bBA", + "AB", + 4 + ], + [ + "bBA", + "B", + 4 + ], + [ + "bBA", + "abBCbccC", + 12 + ], + [ + "bBA", + "bCaaa", + 7 + ], + [ + "bBA", + "cBB", + 4 + ], + [ + "bBAA", + "cAb", + 6 + ], + [ + "bBAAba", + "C", + 12 + ], + [ + "bBAAcb", + "BCcBABB", + 10 + ], + [ + "bBAB", + "ccaa", + 7 + ], + [ + "bBABbBc", + "Bb", + 10 + ], + [ + "bBAC", + "aCBb", + 8 + ], + [ + "bBACA", + "bbcaBB", + 8 + ], + [ + "bBACBA", + "CA", + 8 + ], + [ + "bBACBABc", + "ca", + 14 + ], + [ + "bBACCbCB", + "B", + 14 + ], + [ + "bBACbaC", + "aabAbaCC", + 9 + ], + [ + "bBAa", + "CBBbBaa", + 7 + ], + [ + "bBAa", + "CabC", + 8 + ], + [ + "bBAa", + "cCcCCCA", + 13 + ], + [ + "bBAaBAcB", + "CbCaaAbAA", + 12 + ], + [ + "bBAaBcAaB", + "ACcb", + 13 + ], + [ + "bBAaC", + "acBaAcC", + 8 + ], + [ + "bBAaCB", + "bcBbCBBca", + 12 + ], + [ + "bBAaCc", + "Aca", + 9 + ], + [ + "bBAac", + "AcccCbBc", + 14 + ], + [ + "bBAacA", + "aBCbaAAA", + 10 + ], + [ + "bBAbCA", + "cBAaaCACb", + 10 + ], + [ + "bBAba", + "B", + 8 + ], + [ + "bBAbac", + "cBbAaccb", + 10 + ], + [ + "bBAc", + "Bc", + 4 + ], + [ + "bBAcAbca", + "baB", + 12 + ], + [ + "bBAcBA", + "aAa", + 9 + ], + [ + "bBAcC", + "abbBbCBc", + 10 + ], + [ + "bBAcC", + "c", + 8 + ], + [ + "bBAca", + "Ba", + 6 + ], + [ + "bBAca", + "b", + 8 + ], + [ + "bBAca", + "c", + 8 + ], + [ + "bBB", + "ABACC", + 8 + ], + [ + "bBB", + "AcaBacAc", + 14 + ], + [ + "bBB", + "BCCBCB", + 7 + ], + [ + "bBB", + "Cba", + 5 + ], + [ + "bBB", + "bACbcbAc", + 12 + ], + [ + "bBBABBb", + "Cbab", + 10 + ], + [ + "bBBAa", + "c", + 10 + ], + [ + "bBBBB", + "AacCA", + 10 + ], + [ + "bBBBBAa", + "AbbBAB", + 8 + ], + [ + "bBBBCb", + "B", + 10 + ], + [ + "bBBBabC", + "abBacCCBA", + 13 + ], + [ + "bBBBc", + "AB", + 8 + ], + [ + "bBBC", + "B", + 6 + ], + [ + "bBBC", + "abAbBBcab", + 11 + ], + [ + "bBBCABbB", + "caaCC", + 14 + ], + [ + "bBBCAbB", + "BcBCBCcBc", + 10 + ], + [ + "bBBa", + "abbCAA", + 8 + ], + [ + "bBBaB", + "cacCCbAA", + 14 + ], + [ + "bBBaC", + "Bcc", + 7 + ], + [ + "bBBaaBC", + "bcbcbBba", + 11 + ], + [ + "bBBaaBb", + "a", + 12 + ], + [ + "bBBaabc", + "caaBba", + 10 + ], + [ + "bBBabac", + "aaAA", + 11 + ], + [ + "bBBb", + "Accaab", + 10 + ], + [ + "bBBb", + "aAacAbca", + 14 + ], + [ + "bBBbAcBB", + "BBAbBAb", + 9 + ], + [ + "bBBbBbbbB", + "cc", + 18 + ], + [ + "bBBbCa", + "BcC", + 8 + ], + [ + "bBBbaB", + "caAaaC", + 10 + ], + [ + "bBBbaBbCa", + "a", + 16 + ], + [ + "bBBbacabB", + "Ba", + 14 + ], + [ + "bBBbcc", + "bcA", + 8 + ], + [ + "bBBc", + "ABBbACc", + 8 + ], + [ + "bBBc", + "BAAb", + 7 + ], + [ + "bBBcA", + "caB", + 8 + ], + [ + "bBBcaca", + "CBBBbABcb", + 10 + ], + [ + "bBC", + "AB", + 4 + ], + [ + "bBC", + "aaCaCAcBc", + 15 + ], + [ + "bBC", + "bcaBACB", + 8 + ], + [ + "bBC", + "cAB", + 6 + ], + [ + "bBC", + "cBaCcbca", + 12 + ], + [ + "bBCA", + "AAc", + 7 + ], + [ + "bBCA", + "bCCACBc", + 8 + ], + [ + "bBCA", + "babCCBAC", + 9 + ], + [ + "bBCA", + "cBaabcA", + 9 + ], + [ + "bBCAB", + "ACcBBBBab", + 13 + ], + [ + "bBCABBA", + "BCabbCAB", + 9 + ], + [ + "bBCAbAb", + "caAbA", + 8 + ], + [ + "bBCAbBba", + "CbbBbbC", + 10 + ], + [ + "bBCAcBcCA", + "B", + 16 + ], + [ + "bBCBa", + "cbAAB", + 8 + ], + [ + "bBCBaBCaa", + "bA", + 15 + ], + [ + "bBCBaC", + "aABCAca", + 10 + ], + [ + "bBCBabBC", + "cCAc", + 12 + ], + [ + "bBCC", + "baAbacaA", + 12 + ], + [ + "bBCCA", + "aa", + 9 + ], + [ + "bBCCCCABc", + "babacAA", + 13 + ], + [ + "bBCCCb", + "ACc", + 9 + ], + [ + "bBCCbAb", + "a", + 13 + ], + [ + "bBCaABB", + "CbACac", + 10 + ], + [ + "bBCaCCC", + "ABccBC", + 8 + ], + [ + "bBCaabcAA", + "CBABCC", + 13 + ], + [ + "bBCac", + "BA", + 7 + ], + [ + "bBCacbCA", + "BaCBcCBC", + 10 + ], + [ + "bBCb", + "bBcCA", + 4 + ], + [ + "bBCbAAaAC", + "CCbCCA", + 12 + ], + [ + "bBCbBa", + "AbBaACCB", + 10 + ], + [ + "bBCbb", + "aCcaC", + 9 + ], + [ + "bBCc", + "ccB", + 7 + ], + [ + "bBCcaAAa", + "caabAbC", + 13 + ], + [ + "bBa", + "Bba", + 2 + ], + [ + "bBa", + "b", + 4 + ], + [ + "bBa", + "bBcAca", + 6 + ], + [ + "bBa", + "bCA", + 3 + ], + [ + "bBa", + "cACaAbBa", + 10 + ], + [ + "bBa", + "ccBcbcAcc", + 15 + ], + [ + "bBaABCcC", + "ABA", + 12 + ], + [ + "bBaACBaB", + "BCBBABbb", + 10 + ], + [ + "bBaAaA", + "Bb", + 10 + ], + [ + "bBaAaBc", + "BAbcaACBc", + 8 + ], + [ + "bBaAaC", + "cCBACaaA", + 10 + ], + [ + "bBaAb", + "bACbbacAB", + 10 + ], + [ + "bBaBCAAaa", + "CACBAc", + 13 + ], + [ + "bBaBCCcA", + "bb", + 13 + ], + [ + "bBaBCaAaB", + "bCAbCbbB", + 10 + ], + [ + "bBaBCcBcB", + "cabcc", + 11 + ], + [ + "bBaBa", + "BC", + 8 + ], + [ + "bBaBbAbAB", + "aBAAbCCBA", + 12 + ], + [ + "bBaBbb", + "BaCAbbb", + 7 + ], + [ + "bBaC", + "BAaAcb", + 8 + ], + [ + "bBaCbcC", + "bA", + 11 + ], + [ + "bBaCcAA", + "cAbb", + 12 + ], + [ + "bBaaBb", + "Caa", + 8 + ], + [ + "bBaaBcBbb", + "cABAaA", + 15 + ], + [ + "bBaaCABa", + "bcbaBcbC", + 11 + ], + [ + "bBaaCa", + "bAAC", + 6 + ], + [ + "bBaabbbbc", + "cbA", + 16 + ], + [ + "bBaacaC", + "bccaBC", + 8 + ], + [ + "bBabAcBA", + "ccaAbA", + 9 + ], + [ + "bBabB", + "BabcBBB", + 8 + ], + [ + "bBabC", + "ABc", + 7 + ], + [ + "bBabaAC", + "ccaCb", + 12 + ], + [ + "bBac", + "AbaAcAaB", + 11 + ], + [ + "bBac", + "BBCc", + 3 + ], + [ + "bBacbBcb", + "bCbc", + 9 + ], + [ + "bBb", + "AA", + 6 + ], + [ + "bBb", + "AbCaacc", + 12 + ], + [ + "bBb", + "CaaCCCaA", + 16 + ], + [ + "bBb", + "Ccac", + 8 + ], + [ + "bBb", + "a", + 6 + ], + [ + "bBb", + "aCC", + 6 + ], + [ + "bBb", + "baAC", + 6 + ], + [ + "bBb", + "cBA", + 4 + ], + [ + "bBb", + "caBaCBa", + 11 + ], + [ + "bBbA", + "C", + 8 + ], + [ + "bBbA", + "CBcBAA", + 7 + ], + [ + "bBbA", + "caBBABaBa", + 13 + ], + [ + "bBbAAa", + "aabbb", + 10 + ], + [ + "bBbB", + "AcbAA", + 8 + ], + [ + "bBbB", + "a", + 8 + ], + [ + "bBbBBaBAc", + "c", + 16 + ], + [ + "bBbBBba", + "Bbc", + 10 + ], + [ + "bBbBb", + "B", + 8 + ], + [ + "bBbCA", + "abBBcBBbc", + 12 + ], + [ + "bBbCCca", + "aCaCBCB", + 11 + ], + [ + "bBba", + "aAA", + 7 + ], + [ + "bBba", + "abB", + 6 + ], + [ + "bBbaA", + "cBCBCcCCB", + 15 + ], + [ + "bBbaC", + "bc", + 7 + ], + [ + "bBbaCba", + "aBBccAaB", + 10 + ], + [ + "bBbabBAC", + "bCBa", + 11 + ], + [ + "bBbacbBCA", + "BA", + 14 + ], + [ + "bBbbCA", + "bACbBAa", + 8 + ], + [ + "bBbbbc", + "c", + 10 + ], + [ + "bBbcAAba", + "cC", + 14 + ], + [ + "bBbcBBCa", + "b", + 14 + ], + [ + "bBbcCC", + "aacCBb", + 10 + ], + [ + "bBbcCC", + "acabCCb", + 9 + ], + [ + "bBbcbA", + "BcBCabB", + 9 + ], + [ + "bBbcbA", + "cBaCcCbB", + 10 + ], + [ + "bBbcbBa", + "BbA", + 9 + ], + [ + "bBc", + "A", + 6 + ], + [ + "bBc", + "ACA", + 6 + ], + [ + "bBc", + "CC", + 5 + ], + [ + "bBc", + "bCbA", + 5 + ], + [ + "bBc", + "bbcA", + 3 + ], + [ + "bBcA", + "BaCba", + 7 + ], + [ + "bBcAACbB", + "bCba", + 10 + ], + [ + "bBcABCb", + "BAca", + 9 + ], + [ + "bBcAa", + "Ca", + 7 + ], + [ + "bBcAcBabB", + "ccaCAb", + 11 + ], + [ + "bBcAccBAc", + "cAc", + 12 + ], + [ + "bBcB", + "Ca", + 7 + ], + [ + "bBcBAbB", + "A", + 12 + ], + [ + "bBcBAca", + "BcbC", + 8 + ], + [ + "bBcBc", + "C", + 9 + ], + [ + "bBcCCbcb", + "CAAcacCaC", + 14 + ], + [ + "bBcCaAb", + "CbbbCa", + 9 + ], + [ + "bBcCbAAa", + "c", + 14 + ], + [ + "bBcaaaCAC", + "ACBbc", + 16 + ], + [ + "bBcb", + "bCCac", + 7 + ], + [ + "bBcba", + "bba", + 4 + ], + [ + "bBcbaB", + "BaCCbc", + 10 + ], + [ + "bBcbaBAbB", + "AAb", + 13 + ], + [ + "bBcbaBbAB", + "c", + 16 + ], + [ + "bBcc", + "CcBABA", + 10 + ], + [ + "bBcc", + "aAc", + 6 + ], + [ + "bBccACBbA", + "CaCCCB", + 12 + ], + [ + "bBccBc", + "Caac", + 9 + ], + [ + "bBccC", + "bAcc", + 4 + ], + [ + "bBccb", + "bccAbBbC", + 10 + ], + [ + "bBccb", + "caBAA", + 10 + ], + [ + "bBccbBbCb", + "AAAbbaBA", + 15 + ], + [ + "bBccbBccC", + "c", + 16 + ], + [ + "bBccbb", + "AaABa", + 11 + ], + [ + "bBcccB", + "bcCAca", + 7 + ], + [ + "bC", + "A", + 4 + ], + [ + "bC", + "ACBBaBa", + 12 + ], + [ + "bC", + "Abc", + 3 + ], + [ + "bC", + "BAAcb", + 8 + ], + [ + "bC", + "BAa", + 5 + ], + [ + "bC", + "BAcaBCAcc", + 15 + ], + [ + "bC", + "BCbcCA", + 8 + ], + [ + "bC", + "BacAcb", + 10 + ], + [ + "bC", + "CC", + 2 + ], + [ + "bC", + "CbBcAbcc", + 13 + ], + [ + "bC", + "aBaCA", + 7 + ], + [ + "bC", + "aCCbCABBB", + 14 + ], + [ + "bC", + "aaBBCAA", + 11 + ], + [ + "bC", + "aaCbaAcB", + 13 + ], + [ + "bC", + "acCcbcB", + 11 + ], + [ + "bC", + "b", + 2 + ], + [ + "bC", + "bBBBC", + 6 + ], + [ + "bC", + "bBb", + 4 + ], + [ + "bC", + "bC", + 0 + ], + [ + "bC", + "baAAcb", + 9 + ], + [ + "bC", + "bac", + 3 + ], + [ + "bC", + "bbBb", + 6 + ], + [ + "bC", + "c", + 3 + ], + [ + "bC", + "cABAac", + 10 + ], + [ + "bC", + "cABcACACa", + 15 + ], + [ + "bC", + "cAaCb", + 8 + ], + [ + "bC", + "cBbB", + 6 + ], + [ + "bC", + "cBcCbcc", + 11 + ], + [ + "bC", + "cBcCcAa", + 11 + ], + [ + "bC", + "cccCBcaba", + 16 + ], + [ + "bCA", + "BbCaCc", + 7 + ], + [ + "bCA", + "ac", + 5 + ], + [ + "bCA", + "bCB", + 2 + ], + [ + "bCA", + "ba", + 3 + ], + [ + "bCA", + "cbcbAc", + 7 + ], + [ + "bCAAA", + "Cc", + 8 + ], + [ + "bCAAA", + "cBabbA", + 9 + ], + [ + "bCAAABc", + "Aabab", + 11 + ], + [ + "bCAABaCbc", + "Cab", + 12 + ], + [ + "bCAABccA", + "AaacCBBcB", + 13 + ], + [ + "bCAAC", + "bBAaCcc", + 7 + ], + [ + "bCAACaCa", + "CcAACaC", + 5 + ], + [ + "bCAAaBbCA", + "CbaAcB", + 13 + ], + [ + "bCAB", + "bAAA", + 4 + ], + [ + "bCABB", + "bA", + 6 + ], + [ + "bCABBCAA", + "bBCbcCc", + 11 + ], + [ + "bCABBcB", + "bBbACbaBb", + 11 + ], + [ + "bCABa", + "AaAa", + 6 + ], + [ + "bCABaBAcc", + "CBbaCCba", + 13 + ], + [ + "bCAC", + "ABCcc", + 6 + ], + [ + "bCAC", + "caCa", + 6 + ], + [ + "bCAC", + "cbCcb", + 6 + ], + [ + "bCACBA", + "cbac", + 10 + ], + [ + "bCACBB", + "Bcb", + 9 + ], + [ + "bCACa", + "c", + 9 + ], + [ + "bCACaaC", + "Cacaaa", + 6 + ], + [ + "bCACaaa", + "cC", + 11 + ], + [ + "bCACb", + "B", + 9 + ], + [ + "bCACbA", + "Cb", + 8 + ], + [ + "bCAaCB", + "aCAbcCbB", + 8 + ], + [ + "bCAaCbac", + "a", + 14 + ], + [ + "bCAaccaCC", + "b", + 16 + ], + [ + "bCAbAAcA", + "bBBCAabAc", + 10 + ], + [ + "bCAbab", + "ca", + 9 + ], + [ + "bCAbb", + "CBAcBAcBB", + 12 + ], + [ + "bCAc", + "BaCAaaBa", + 11 + ], + [ + "bCAcBA", + "CBBcCCA", + 10 + ], + [ + "bCAcC", + "BbaccaCA", + 10 + ], + [ + "bCAcC", + "BcA", + 6 + ], + [ + "bCAcc", + "cbCAB", + 6 + ], + [ + "bCB", + "CCabaC", + 9 + ], + [ + "bCB", + "abaaAbbAC", + 15 + ], + [ + "bCB", + "bCAca", + 6 + ], + [ + "bCB", + "cBAB", + 5 + ], + [ + "bCBA", + "BccBbcAA", + 10 + ], + [ + "bCBAAa", + "c", + 11 + ], + [ + "bCBBBCbc", + "CAbbbB", + 10 + ], + [ + "bCBBBcB", + "BBaBCBbC", + 10 + ], + [ + "bCBBa", + "CccaaC", + 9 + ], + [ + "bCBBbCc", + "bBaAB", + 10 + ], + [ + "bCBC", + "AbaCcaCB", + 10 + ], + [ + "bCBC", + "CcbaCaA", + 10 + ], + [ + "bCBC", + "aCAbcbCC", + 10 + ], + [ + "bCBCAB", + "C", + 10 + ], + [ + "bCBCACBb", + "bBacBCaA", + 11 + ], + [ + "bCBCBcbc", + "bA", + 14 + ], + [ + "bCBCaAc", + "a", + 12 + ], + [ + "bCBCbC", + "A", + 12 + ], + [ + "bCBa", + "Ba", + 4 + ], + [ + "bCBaAb", + "CABCCcb", + 10 + ], + [ + "bCBaabC", + "ACb", + 10 + ], + [ + "bCBabCa", + "cAbaccCC", + 11 + ], + [ + "bCBacABab", + "ccAacAaCc", + 11 + ], + [ + "bCBacaAB", + "ACbABB", + 10 + ], + [ + "bCBb", + "aaAbAbbC", + 11 + ], + [ + "bCBb", + "cBabcA", + 9 + ], + [ + "bCBb", + "cBcccaA", + 12 + ], + [ + "bCBbAaAb", + "BabA", + 11 + ], + [ + "bCBbAac", + "CBcca", + 8 + ], + [ + "bCBbAcC", + "CBbBbb", + 8 + ], + [ + "bCBbC", + "CA", + 8 + ], + [ + "bCBbaCCA", + "BAAab", + 13 + ], + [ + "bCBbabb", + "BcCBA", + 10 + ], + [ + "bCBbbCb", + "ABcbAba", + 10 + ], + [ + "bCBc", + "bbBACc", + 6 + ], + [ + "bCBcB", + "bCAbccACb", + 10 + ], + [ + "bCBcBBc", + "b", + 12 + ], + [ + "bCBca", + "cAABaCCa", + 11 + ], + [ + "bCBcabA", + "CAcbbcBcB", + 14 + ], + [ + "bCBcba", + "ABAcbc", + 8 + ], + [ + "bCBcccbC", + "Cbbbc", + 10 + ], + [ + "bCC", + "A", + 6 + ], + [ + "bCC", + "ABBbCCC", + 8 + ], + [ + "bCC", + "AaC", + 4 + ], + [ + "bCC", + "BB", + 5 + ], + [ + "bCC", + "a", + 6 + ], + [ + "bCC", + "aCbBaaCB", + 12 + ], + [ + "bCC", + "b", + 4 + ], + [ + "bCC", + "bbBaCaCAc", + 12 + ], + [ + "bCC", + "cB", + 5 + ], + [ + "bCCAACA", + "bc", + 11 + ], + [ + "bCCAAbAa", + "aC", + 14 + ], + [ + "bCCACBb", + "BcAA", + 10 + ], + [ + "bCCAc", + "bcBCABabC", + 10 + ], + [ + "bCCBACAa", + "cCB", + 11 + ], + [ + "bCCBC", + "CbbbACC", + 10 + ], + [ + "bCCBaBa", + "BC", + 11 + ], + [ + "bCCBbBAA", + "ACBbbBc", + 9 + ], + [ + "bCCCABcb", + "BaBbbAb", + 12 + ], + [ + "bCCCa", + "aA", + 9 + ], + [ + "bCCCbccc", + "aCAA", + 14 + ], + [ + "bCCa", + "AabcB", + 9 + ], + [ + "bCCa", + "BccBa", + 5 + ], + [ + "bCCa", + "bCACAaBb", + 8 + ], + [ + "bCCaA", + "cbAbaAA", + 8 + ], + [ + "bCCaAbBB", + "CcC", + 13 + ], + [ + "bCCaBa", + "AcCabCAc", + 9 + ], + [ + "bCCaCc", + "abAcCA", + 9 + ], + [ + "bCCaa", + "cA", + 8 + ], + [ + "bCCaaCaa", + "cbcc", + 14 + ], + [ + "bCCab", + "B", + 9 + ], + [ + "bCCaccbbB", + "cC", + 15 + ], + [ + "bCCb", + "cBBaBB", + 10 + ], + [ + "bCCbA", + "bcBC", + 6 + ], + [ + "bCCbACBBB", + "CA", + 14 + ], + [ + "bCCbB", + "aaBcCbAA", + 10 + ], + [ + "bCCbCc", + "aACbC", + 6 + ], + [ + "bCCbcA", + "caACc", + 10 + ], + [ + "bCCcAcAB", + "aCAB", + 10 + ], + [ + "bCCcc", + "aACCcacB", + 8 + ], + [ + "bCCccC", + "aabAb", + 12 + ], + [ + "bCa", + "Cb", + 4 + ], + [ + "bCa", + "aBbb", + 7 + ], + [ + "bCa", + "bBACAbba", + 10 + ], + [ + "bCa", + "baba", + 4 + ], + [ + "bCa", + "cbacb", + 7 + ], + [ + "bCa", + "ccaCaCCc", + 12 + ], + [ + "bCaA", + "AABbbA", + 9 + ], + [ + "bCaA", + "CBc", + 6 + ], + [ + "bCaAC", + "ACaaABaCa", + 10 + ], + [ + "bCaBAc", + "bbbBcbcAc", + 10 + ], + [ + "bCaBBBA", + "caCcBBCcB", + 12 + ], + [ + "bCaBBC", + "cacbBC", + 6 + ], + [ + "bCaBbCAC", + "aaCAcCAbC", + 11 + ], + [ + "bCaCAabc", + "bBaB", + 11 + ], + [ + "bCaCAcaC", + "bcccaa", + 8 + ], + [ + "bCaCCC", + "cAbabbACA", + 14 + ], + [ + "bCaCcB", + "ABbbcCABC", + 12 + ], + [ + "bCaCcac", + "CBbCAA", + 10 + ], + [ + "bCaa", + "a", + 6 + ], + [ + "bCaa", + "aBC", + 7 + ], + [ + "bCaabBC", + "CCABcA", + 10 + ], + [ + "bCabAacbC", + "BAB", + 14 + ], + [ + "bCabCc", + "CA", + 9 + ], + [ + "bCabaAAB", + "A", + 14 + ], + [ + "bCabbAacb", + "Cba", + 12 + ], + [ + "bCabbC", + "CAacA", + 9 + ], + [ + "bCac", + "C", + 6 + ], + [ + "bCac", + "cC", + 6 + ], + [ + "bCacAbBa", + "a", + 14 + ], + [ + "bCacBA", + "abB", + 8 + ], + [ + "bCacCcB", + "aAabBBbC", + 13 + ], + [ + "bCaccBAa", + "CB", + 12 + ], + [ + "bCb", + "aCBc", + 5 + ], + [ + "bCb", + "ab", + 4 + ], + [ + "bCb", + "ccA", + 5 + ], + [ + "bCbA", + "ab", + 6 + ], + [ + "bCbAAAB", + "AccbcBaB", + 10 + ], + [ + "bCbAB", + "BcBB", + 5 + ], + [ + "bCbACcbc", + "b", + 14 + ], + [ + "bCbAa", + "acCBABB", + 9 + ], + [ + "bCbAbcBc", + "b", + 14 + ], + [ + "bCbB", + "b", + 6 + ], + [ + "bCbBabC", + "cbacBCaBc", + 10 + ], + [ + "bCbBacaA", + "aBAaBCaa", + 11 + ], + [ + "bCbBbbbC", + "a", + 16 + ], + [ + "bCbBbcAb", + "ba", + 13 + ], + [ + "bCbCAbB", + "CABBbb", + 9 + ], + [ + "bCbCCCAcb", + "a", + 17 + ], + [ + "bCbCbb", + "AAACCCB", + 11 + ], + [ + "bCbCccc", + "abbAbC", + 11 + ], + [ + "bCbaACBbb", + "bCCccaa", + 13 + ], + [ + "bCbaAc", + "BA", + 9 + ], + [ + "bCbaaBc", + "CbAAcaBc", + 7 + ], + [ + "bCbac", + "aAcBABbaA", + 13 + ], + [ + "bCbac", + "b", + 8 + ], + [ + "bCbacABa", + "cCaac", + 10 + ], + [ + "bCbb", + "bbCBabAC", + 9 + ], + [ + "bCbbb", + "AcBc", + 8 + ], + [ + "bCbbba", + "AaBbc", + 9 + ], + [ + "bCbc", + "ABb", + 6 + ], + [ + "bCbc", + "CCb", + 4 + ], + [ + "bCbcB", + "AbAab", + 9 + ], + [ + "bCbcCBA", + "BCbBacC", + 9 + ], + [ + "bCbcCC", + "BbbABA", + 9 + ], + [ + "bCbcCbAC", + "ccccC", + 10 + ], + [ + "bCbca", + "bcbBCBa", + 6 + ], + [ + "bCbcaAAAA", + "A", + 16 + ], + [ + "bCbcaca", + "bBBAcc", + 8 + ], + [ + "bCbcbbBbC", + "CaC", + 14 + ], + [ + "bCc", + "BabBBCb", + 10 + ], + [ + "bCc", + "CCBcAA", + 8 + ], + [ + "bCc", + "CCCcCBcC", + 12 + ], + [ + "bCc", + "aaAABCCaB", + 14 + ], + [ + "bCc", + "aabbbaaB", + 14 + ], + [ + "bCc", + "bccB", + 3 + ], + [ + "bCcABC", + "aAcBcCC", + 10 + ], + [ + "bCcACCBa", + "cac", + 12 + ], + [ + "bCcACCaC", + "AbcaC", + 9 + ], + [ + "bCcB", + "BBB", + 5 + ], + [ + "bCcBA", + "CcAbcCbaA", + 11 + ], + [ + "bCcBB", + "cBb", + 5 + ], + [ + "bCcC", + "ACBaB", + 8 + ], + [ + "bCcCb", + "ccbBac", + 10 + ], + [ + "bCcCcCCCB", + "BCAaAa", + 15 + ], + [ + "bCca", + "CCaaaBbc", + 12 + ], + [ + "bCcaA", + "cACB", + 9 + ], + [ + "bCcaBbac", + "bBa", + 10 + ], + [ + "bCcaCc", + "A", + 11 + ], + [ + "bCcaCcC", + "bcCB", + 8 + ], + [ + "bCcaaa", + "B", + 11 + ], + [ + "bCcaabCB", + "bbAbcaAA", + 13 + ], + [ + "bCcacBCA", + "aACBccC", + 11 + ], + [ + "bCcbAaBBc", + "c", + 16 + ], + [ + "bCcbCCbb", + "bBbacaC", + 11 + ], + [ + "bCcbbaCab", + "BC", + 15 + ], + [ + "bCcbbc", + "bBBCB", + 9 + ], + [ + "bCcbc", + "acBCAcc", + 9 + ], + [ + "bCcc", + "CAbacbA", + 10 + ], + [ + "bCccbbbc", + "bBABAaBAc", + 13 + ], + [ + "ba", + "ABbbC", + 8 + ], + [ + "ba", + "BA", + 2 + ], + [ + "ba", + "BAABBb", + 10 + ], + [ + "ba", + "BacAAbA", + 11 + ], + [ + "ba", + "BbAaaCACc", + 14 + ], + [ + "ba", + "BcCAAC", + 10 + ], + [ + "ba", + "Bcc", + 5 + ], + [ + "ba", + "CCccCCa", + 12 + ], + [ + "ba", + "CaAbAb", + 9 + ], + [ + "ba", + "Cba", + 2 + ], + [ + "ba", + "Cca", + 4 + ], + [ + "ba", + "Ccac", + 6 + ], + [ + "ba", + "a", + 2 + ], + [ + "ba", + "aBABA", + 8 + ], + [ + "ba", + "aBaABB", + 9 + ], + [ + "ba", + "aCcaCbAbB", + 15 + ], + [ + "ba", + "bBC", + 4 + ], + [ + "ba", + "bBCC", + 6 + ], + [ + "ba", + "baABc", + 6 + ], + [ + "ba", + "baCAbB", + 8 + ], + [ + "ba", + "cA", + 3 + ], + [ + "ba", + "cABA", + 6 + ], + [ + "ba", + "cBbCCaB", + 10 + ], + [ + "ba", + "cBc", + 5 + ], + [ + "ba", + "cCCBcC", + 11 + ], + [ + "ba", + "caBBCAca", + 13 + ], + [ + "ba", + "cbBbb", + 8 + ], + [ + "ba", + "ccb", + 6 + ], + [ + "baA", + "AbAAaCaa", + 11 + ], + [ + "baA", + "B", + 5 + ], + [ + "baA", + "BBB", + 5 + ], + [ + "baA", + "a", + 4 + ], + [ + "baA", + "aCaaB", + 7 + ], + [ + "baA", + "cAaBCBaCB", + 15 + ], + [ + "baA", + "cBBcccca", + 14 + ], + [ + "baA", + "ccABbC", + 10 + ], + [ + "baAA", + "BBAaCacBC", + 14 + ], + [ + "baAA", + "cbbbCcbac", + 15 + ], + [ + "baAAA", + "CCcC", + 10 + ], + [ + "baAAABbC", + "BcACCbBa", + 11 + ], + [ + "baAAAbAC", + "caBb", + 12 + ], + [ + "baAAa", + "cACaAB", + 8 + ], + [ + "baAAc", + "bC", + 7 + ], + [ + "baAAcCa", + "CCABACcBC", + 12 + ], + [ + "baABBcc", + "cbcaaABc", + 9 + ], + [ + "baABCaCA", + "CABCbCAC", + 8 + ], + [ + "baABaBaAA", + "CAabb", + 13 + ], + [ + "baABbAcC", + "BcCBCCC", + 10 + ], + [ + "baAC", + "bCbAbBCA", + 10 + ], + [ + "baACCCcaA", + "Cb", + 16 + ], + [ + "baACca", + "bBcB", + 8 + ], + [ + "baAa", + "AbAab", + 6 + ], + [ + "baAaCAcc", + "acAbB", + 11 + ], + [ + "baAbCAc", + "baAacAac", + 5 + ], + [ + "baAbaAaba", + "cBA", + 15 + ], + [ + "baAbabAb", + "CC", + 16 + ], + [ + "baAcABAC", + "aabCBbBcB", + 13 + ], + [ + "baAcB", + "bbcb", + 5 + ], + [ + "baAcCcaaa", + "ccAA", + 12 + ], + [ + "baAca", + "aaAcCc", + 6 + ], + [ + "baAcaA", + "C", + 11 + ], + [ + "baAcab", + "B", + 11 + ], + [ + "baAcc", + "AbacACCCb", + 10 + ], + [ + "baB", + "BbccbbBbC", + 14 + ], + [ + "baB", + "acbAB", + 5 + ], + [ + "baB", + "accCa", + 10 + ], + [ + "baB", + "cA", + 5 + ], + [ + "baB", + "ca", + 4 + ], + [ + "baB", + "ccBACC", + 10 + ], + [ + "baBA", + "aa", + 5 + ], + [ + "baBAC", + "aA", + 6 + ], + [ + "baBAbccc", + "BCBaC", + 11 + ], + [ + "baBB", + "bCCaAca", + 10 + ], + [ + "baBB", + "cacbb", + 6 + ], + [ + "baBBC", + "BbBAA", + 7 + ], + [ + "baBBCAAc", + "bBaabAccc", + 11 + ], + [ + "baBBCcBCa", + "ABbBca", + 9 + ], + [ + "baBBaa", + "BCABAa", + 6 + ], + [ + "baBBbAb", + "bACa", + 10 + ], + [ + "baBBbCbBC", + "cBABabC", + 11 + ], + [ + "baBBcaBcC", + "CCCb", + 16 + ], + [ + "baBBcb", + "BBCbCCb", + 9 + ], + [ + "baBCcbbA", + "BBacCC", + 11 + ], + [ + "baBa", + "aA", + 5 + ], + [ + "baBa", + "bBbcAAb", + 10 + ], + [ + "baBa", + "cCbbbcBC", + 12 + ], + [ + "baBaABBb", + "bBcB", + 10 + ], + [ + "baBaaC", + "A", + 11 + ], + [ + "baBaaC", + "bBbAcc", + 7 + ], + [ + "baBbC", + "ACAc", + 8 + ], + [ + "baBbcCbaC", + "C", + 16 + ], + [ + "baBc", + "BAAbCa", + 8 + ], + [ + "baBcAAC", + "aBAAbACa", + 8 + ], + [ + "baBcC", + "Abc", + 6 + ], + [ + "baBcCCBBA", + "bbacBaaaC", + 14 + ], + [ + "baBcCCBb", + "Bccacb", + 9 + ], + [ + "baBcaAc", + "bc", + 10 + ], + [ + "baBcbccA", + "bCCa", + 11 + ], + [ + "baBccBC", + "C", + 12 + ], + [ + "baBccccb", + "AAbbaBCb", + 11 + ], + [ + "baC", + "AABBAb", + 10 + ], + [ + "baC", + "AB", + 5 + ], + [ + "baC", + "BBa", + 5 + ], + [ + "baC", + "CAaCCcbb", + 12 + ], + [ + "baC", + "CC", + 4 + ], + [ + "baC", + "caBcB", + 7 + ], + [ + "baCA", + "Bc", + 6 + ], + [ + "baCACBCac", + "a", + 16 + ], + [ + "baCAaAaBa", + "bcBcCab", + 12 + ], + [ + "baCAbc", + "CCc", + 8 + ], + [ + "baCB", + "B", + 6 + ], + [ + "baCBACbBa", + "b", + 16 + ], + [ + "baCBC", + "CAAcaCaB", + 12 + ], + [ + "baCBbCB", + "abc", + 9 + ], + [ + "baCCCCBB", + "b", + 14 + ], + [ + "baCCCcC", + "bbcAcABac", + 13 + ], + [ + "baCCbAB", + "CbBAC", + 9 + ], + [ + "baCCc", + "BcACCa", + 6 + ], + [ + "baCCc", + "ab", + 8 + ], + [ + "baCa", + "BAabaACcC", + 12 + ], + [ + "baCa", + "CCAABc", + 11 + ], + [ + "baCaAAB", + "aCB", + 8 + ], + [ + "baCaCa", + "caC", + 7 + ], + [ + "baCaCcbAb", + "bCB", + 13 + ], + [ + "baCb", + "CAbA", + 7 + ], + [ + "baCbCAB", + "aCBccCcC", + 11 + ], + [ + "baCbCC", + "ACCbA", + 8 + ], + [ + "baCbbCb", + "bACAc", + 8 + ], + [ + "baCbc", + "aaAbAb", + 8 + ], + [ + "baCbcACBc", + "CACcBb", + 11 + ], + [ + "baCc", + "BbcaaB", + 8 + ], + [ + "baCcA", + "bbbBcB", + 8 + ], + [ + "baCcaaC", + "cCCA", + 10 + ], + [ + "baCcb", + "BaCacAa", + 7 + ], + [ + "baa", + "ACbBca", + 8 + ], + [ + "baa", + "BaC", + 3 + ], + [ + "baaABBcBb", + "ABcacAC", + 14 + ], + [ + "baaAC", + "aCAb", + 6 + ], + [ + "baaAaBB", + "ccABBac", + 12 + ], + [ + "baaAb", + "bAcBabC", + 8 + ], + [ + "baaAcA", + "B", + 11 + ], + [ + "baaBAA", + "CbabaCCAA", + 8 + ], + [ + "baaBAAabB", + "cCB", + 16 + ], + [ + "baaBBBb", + "aC", + 12 + ], + [ + "baaBabb", + "cAaB", + 9 + ], + [ + "baaBb", + "CBbaBAbC", + 9 + ], + [ + "baaBcaa", + "CcCCbb", + 13 + ], + [ + "baaCCcAB", + "cCA", + 11 + ], + [ + "baaCaac", + "aCAaCCbA", + 11 + ], + [ + "baaCbA", + "ccBAAcC", + 12 + ], + [ + "baaa", + "CaCbCb", + 10 + ], + [ + "baaa", + "bBbBAAC", + 10 + ], + [ + "baaaAc", + "CBBbBBB", + 13 + ], + [ + "baaaCA", + "aab", + 8 + ], + [ + "baaaaabB", + "BCbbccA", + 15 + ], + [ + "baaacac", + "ABa", + 11 + ], + [ + "baab", + "aABbABC", + 11 + ], + [ + "baabA", + "cACC", + 9 + ], + [ + "baabaBaab", + "aCcA", + 15 + ], + [ + "baac", + "ACBbca", + 10 + ], + [ + "baacACa", + "bccCB", + 8 + ], + [ + "baacBbA", + "AaaCCbaaA", + 9 + ], + [ + "baacCc", + "CACBAA", + 11 + ], + [ + "baacaCcb", + "BBbbbAaBA", + 16 + ], + [ + "baacabBAA", + "aaaaBaB", + 9 + ], + [ + "bab", + "Cc", + 6 + ], + [ + "bab", + "CcBC", + 7 + ], + [ + "bab", + "a", + 4 + ], + [ + "bab", + "aabbAaBca", + 13 + ], + [ + "babA", + "AB", + 6 + ], + [ + "babA", + "bCb", + 4 + ], + [ + "babA", + "cccABCc", + 12 + ], + [ + "babAAaBC", + "Cb", + 14 + ], + [ + "babAaAA", + "bc", + 12 + ], + [ + "babAaBA", + "BbcBAbC", + 10 + ], + [ + "babBBB", + "b", + 10 + ], + [ + "babBa", + "BB", + 7 + ], + [ + "babBbAbAC", + "CbabB", + 12 + ], + [ + "babC", + "BCB", + 6 + ], + [ + "babC", + "CABB", + 6 + ], + [ + "babCABaa", + "BbcCAab", + 9 + ], + [ + "babCB", + "bcCCaABBC", + 12 + ], + [ + "babCBb", + "AAcbb", + 7 + ], + [ + "babCCCc", + "cCAbbca", + 12 + ], + [ + "babCCa", + "c", + 11 + ], + [ + "babCabCab", + "caac", + 13 + ], + [ + "babCbcc", + "AaAAcA", + 10 + ], + [ + "baba", + "abBcbAc", + 9 + ], + [ + "babaBA", + "CbcBbBabB", + 11 + ], + [ + "babaC", + "CbAaAA", + 8 + ], + [ + "babaCBaa", + "BbCa", + 9 + ], + [ + "babaa", + "C", + 10 + ], + [ + "babaaCBCC", + "BbaaAc", + 10 + ], + [ + "babb", + "AcB", + 6 + ], + [ + "babbA", + "BabaAAaB", + 9 + ], + [ + "babbCaaCB", + "cCaAAaBc", + 15 + ], + [ + "babbCbC", + "AA", + 13 + ], + [ + "babbCc", + "acbACaB", + 10 + ], + [ + "babbbbA", + "acA", + 10 + ], + [ + "babbcccAA", + "bAA", + 12 + ], + [ + "babcacA", + "c", + 12 + ], + [ + "bac", + "CBC", + 5 + ], + [ + "bac", + "CcBc", + 6 + ], + [ + "bac", + "Ccbca", + 8 + ], + [ + "bac", + "aacAc", + 6 + ], + [ + "bac", + "ba", + 2 + ], + [ + "bacABcBc", + "ABccc", + 8 + ], + [ + "bacAbca", + "aBCACc", + 9 + ], + [ + "bacBCbBb", + "ccAbcABA", + 12 + ], + [ + "bacBCc", + "cBcBB", + 8 + ], + [ + "bacBaBc", + "AcbcAcA", + 10 + ], + [ + "bacC", + "bbAbaCAB", + 11 + ], + [ + "bacCACCA", + "b", + 14 + ], + [ + "bacCAa", + "b", + 10 + ], + [ + "bacCCCccC", + "CCBbcacA", + 13 + ], + [ + "bacCbaBcB", + "Cab", + 13 + ], + [ + "bacCcBC", + "aBcbcccB", + 10 + ], + [ + "bacaCC", + "BAaAbcAC", + 10 + ], + [ + "bacaCcA", + "BcBAc", + 9 + ], + [ + "bacabaAA", + "AAACCBaC", + 13 + ], + [ + "bacacBa", + "BCAAaAA", + 11 + ], + [ + "bacbABaB", + "bCABBAc", + 10 + ], + [ + "bacbCAbbb", + "BaA", + 13 + ], + [ + "bacc", + "aAcAA", + 7 + ], + [ + "baccAA", + "C", + 11 + ], + [ + "baccAa", + "abCCB", + 9 + ], + [ + "baccBAA", + "ccCCbcCC", + 13 + ], + [ + "baccBBc", + "CAb", + 12 + ], + [ + "baccBbBaa", + "AbcaCcbb", + 12 + ], + [ + "baccCaCA", + "CAccb", + 11 + ], + [ + "baccaBc", + "AccABCbCC", + 11 + ], + [ + "baccccCB", + "cC", + 12 + ], + [ + "bb", + "A", + 4 + ], + [ + "bb", + "AABcCbCB", + 13 + ], + [ + "bb", + "AC", + 4 + ], + [ + "bb", + "Aabc", + 6 + ], + [ + "bb", + "AbC", + 4 + ], + [ + "bb", + "AbCC", + 6 + ], + [ + "bb", + "AbCCbbB", + 10 + ], + [ + "bb", + "Acaa", + 8 + ], + [ + "bb", + "B", + 3 + ], + [ + "bb", + "BAbcaB", + 9 + ], + [ + "bb", + "BCAC", + 7 + ], + [ + "bb", + "Bbccc", + 7 + ], + [ + "bb", + "CC", + 4 + ], + [ + "bb", + "a", + 4 + ], + [ + "bb", + "aBcCcc", + 11 + ], + [ + "bb", + "aaBBCc", + 10 + ], + [ + "bb", + "acCc", + 8 + ], + [ + "bb", + "accBa", + 9 + ], + [ + "bb", + "bA", + 2 + ], + [ + "bb", + "bAc", + 4 + ], + [ + "bb", + "bCbAcbaC", + 12 + ], + [ + "bb", + "baAB", + 5 + ], + [ + "bb", + "cAbBAbC", + 10 + ], + [ + "bb", + "cB", + 3 + ], + [ + "bb", + "cBC", + 5 + ], + [ + "bb", + "cbAacCaca", + 16 + ], + [ + "bb", + "cbBbABC", + 10 + ], + [ + "bbA", + "ABC", + 5 + ], + [ + "bbA", + "BBCabAA", + 9 + ], + [ + "bbA", + "BcCC", + 7 + ], + [ + "bbA", + "CB", + 5 + ], + [ + "bbA", + "bBbaababa", + 13 + ], + [ + "bbA", + "cCabBCAc", + 11 + ], + [ + "bbAA", + "B", + 7 + ], + [ + "bbAA", + "CC", + 8 + ], + [ + "bbAAaBb", + "AcC", + 12 + ], + [ + "bbAAb", + "BBBaaACAb", + 10 + ], + [ + "bbAAc", + "bBaCb", + 6 + ], + [ + "bbAAcAca", + "CCbBaccCB", + 12 + ], + [ + "bbAAcBca", + "bcA", + 11 + ], + [ + "bbAAcbCb", + "bAabCBAAC", + 12 + ], + [ + "bbAB", + "AaABCCB", + 10 + ], + [ + "bbAB", + "AbabABcC", + 8 + ], + [ + "bbAB", + "cBb", + 6 + ], + [ + "bbABACAbC", + "Ccabbc", + 13 + ], + [ + "bbABBC", + "CaAA", + 10 + ], + [ + "bbABCBA", + "ACb", + 9 + ], + [ + "bbABCBCbc", + "b", + 16 + ], + [ + "bbABab", + "CAcaba", + 8 + ], + [ + "bbABc", + "Bac", + 6 + ], + [ + "bbABcBBc", + "Aa", + 14 + ], + [ + "bbABcb", + "c", + 10 + ], + [ + "bbACABB", + "acC", + 12 + ], + [ + "bbACCcCbb", + "CCcC", + 10 + ], + [ + "bbAa", + "CbCaC", + 6 + ], + [ + "bbAa", + "cBcBBC", + 10 + ], + [ + "bbAaC", + "bBcCcAB", + 10 + ], + [ + "bbAaCaBAB", + "aabBc", + 13 + ], + [ + "bbAabBcca", + "BbaabB", + 8 + ], + [ + "bbAaccbAb", + "aCbBAAc", + 14 + ], + [ + "bbAb", + "B", + 7 + ], + [ + "bbAb", + "baaCABAa", + 11 + ], + [ + "bbAb", + "caAABCaac", + 15 + ], + [ + "bbAbAcaCA", + "cacab", + 13 + ], + [ + "bbAbB", + "ccAaCc", + 10 + ], + [ + "bbAbBc", + "AbcAbaBa", + 8 + ], + [ + "bbAbC", + "BBa", + 7 + ], + [ + "bbAbC", + "BcAcaCaaa", + 13 + ], + [ + "bbAbcBA", + "cAbbcBC", + 8 + ], + [ + "bbAc", + "bB", + 5 + ], + [ + "bbAcBabb", + "b", + 14 + ], + [ + "bbAcCaAC", + "CccacCABB", + 12 + ], + [ + "bbAcaA", + "caCabBaAB", + 13 + ], + [ + "bbAcabA", + "bBcbCb", + 9 + ], + [ + "bbB", + "Ac", + 6 + ], + [ + "bbB", + "aCbba", + 6 + ], + [ + "bbB", + "cAcbbBb", + 8 + ], + [ + "bbBA", + "B", + 6 + ], + [ + "bbBACc", + "AcCCBa", + 12 + ], + [ + "bbBAab", + "caAaBCbCb", + 14 + ], + [ + "bbBAbaa", + "aa", + 10 + ], + [ + "bbBBA", + "A", + 8 + ], + [ + "bbBBaaC", + "aCB", + 12 + ], + [ + "bbBBbc", + "ab", + 10 + ], + [ + "bbBCCCAbA", + "aAcCBBc", + 14 + ], + [ + "bbBCCaa", + "AACaAbAA", + 14 + ], + [ + "bbBCaaAC", + "bBAc", + 9 + ], + [ + "bbBCcbcbc", + "BBBaCcbBc", + 7 + ], + [ + "bbBa", + "BBcbc", + 7 + ], + [ + "bbBa", + "Cc", + 8 + ], + [ + "bbBaAcBaC", + "Bcc", + 13 + ], + [ + "bbBaCacbC", + "BAAaBcCa", + 13 + ], + [ + "bbBbA", + "Bbca", + 6 + ], + [ + "bbBbB", + "bBCaCbABa", + 11 + ], + [ + "bbBbBc", + "ABbBbc", + 5 + ], + [ + "bbBbCcBa", + "ba", + 12 + ], + [ + "bbBbaaaBB", + "C", + 18 + ], + [ + "bbBbabc", + "ABACBBacC", + 11 + ], + [ + "bbBbbcCA", + "AaabaCabb", + 15 + ], + [ + "bbBbc", + "Bab", + 7 + ], + [ + "bbBc", + "AaCcBCbCb", + 15 + ], + [ + "bbBcBCA", + "acaba", + 11 + ], + [ + "bbBcCc", + "BAc", + 8 + ], + [ + "bbBcab", + "cAabaC", + 10 + ], + [ + "bbBcbAba", + "b", + 14 + ], + [ + "bbBccBaCB", + "AcbAaCc", + 12 + ], + [ + "bbC", + "A", + 6 + ], + [ + "bbC", + "AAccbCBAA", + 14 + ], + [ + "bbC", + "BCAC", + 5 + ], + [ + "bbC", + "BacBCA", + 8 + ], + [ + "bbC", + "acBbCB", + 7 + ], + [ + "bbCAAb", + "aBAAcAc", + 9 + ], + [ + "bbCABbc", + "aCa", + 11 + ], + [ + "bbCACA", + "cBc", + 10 + ], + [ + "bbCAaBb", + "babcacCA", + 10 + ], + [ + "bbCAaa", + "cBaBbCa", + 11 + ], + [ + "bbCBCca", + "bC", + 10 + ], + [ + "bbCBacAB", + "aC", + 13 + ], + [ + "bbCBcBBcb", + "bCCCaa", + 13 + ], + [ + "bbCC", + "cAbBccA", + 9 + ], + [ + "bbCCAA", + "b", + 10 + ], + [ + "bbCCCBc", + "CCBAcbab", + 14 + ], + [ + "bbCCaccaA", + "ba", + 14 + ], + [ + "bbCCbBA", + "A", + 12 + ], + [ + "bbCCcABa", + "CBBbC", + 13 + ], + [ + "bbCCcCc", + "BbaabbaCc", + 11 + ], + [ + "bbCa", + "BC", + 5 + ], + [ + "bbCa", + "abBbA", + 6 + ], + [ + "bbCaAB", + "CCACcabAC", + 12 + ], + [ + "bbCaBabbC", + "CaBABbacC", + 10 + ], + [ + "bbCaaC", + "BbaBCcbCA", + 11 + ], + [ + "bbCaaCA", + "BcBBB", + 12 + ], + [ + "bbCabA", + "BaBBA", + 8 + ], + [ + "bbCabcAA", + "bc", + 12 + ], + [ + "bbCbAaBaA", + "CbAaAAc", + 9 + ], + [ + "bbCbCB", + "AaBB", + 9 + ], + [ + "bbCbaACb", + "Abbc", + 11 + ], + [ + "bbCbcaBA", + "AB", + 13 + ], + [ + "bbCc", + "aBaC", + 6 + ], + [ + "bbCcAA", + "ccaBAabBA", + 14 + ], + [ + "bbCcBaA", + "aAbCCabAb", + 11 + ], + [ + "bbCcaABc", + "a", + 14 + ], + [ + "bbCcc", + "BBAAA", + 8 + ], + [ + "bbCccBCCa", + "cacaCc", + 12 + ], + [ + "bba", + "ABBcccabc", + 14 + ], + [ + "bba", + "Bb", + 3 + ], + [ + "bba", + "C", + 6 + ], + [ + "bba", + "CBACA", + 8 + ], + [ + "bba", + "aCCAABCcb", + 17 + ], + [ + "bba", + "bbba", + 2 + ], + [ + "bbaA", + "CaCcCbb", + 14 + ], + [ + "bbaACCB", + "bAcAccABC", + 10 + ], + [ + "bbaAaaacc", + "CbaACB", + 11 + ], + [ + "bbaAcBA", + "cBbcaa", + 10 + ], + [ + "bbaB", + "Acc", + 8 + ], + [ + "bbaBAccC", + "cBBAb", + 11 + ], + [ + "bbaBBCc", + "C", + 12 + ], + [ + "bbaBCaAb", + "cCbA", + 12 + ], + [ + "bbaBaAc", + "cABCaA", + 9 + ], + [ + "bbaBbCbCb", + "aBccbBC", + 11 + ], + [ + "bbaBbaAbC", + "acaccca", + 15 + ], + [ + "bbaBc", + "bAbbCc", + 6 + ], + [ + "bbaCbCB", + "CCCA", + 10 + ], + [ + "bbaCbbC", + "c", + 13 + ], + [ + "bbaCc", + "CbCaBAaC", + 11 + ], + [ + "bbaCcBccb", + "C", + 16 + ], + [ + "bbab", + "ACB", + 7 + ], + [ + "bbab", + "CbbCbCbcC", + 12 + ], + [ + "bbabAbca", + "cBbbAB", + 10 + ], + [ + "bbabBcc", + "CAa", + 12 + ], + [ + "bbabC", + "abBAaAba", + 9 + ], + [ + "bbabC", + "cBb", + 7 + ], + [ + "bbabc", + "AAccbABAc", + 12 + ], + [ + "bbacC", + "aabaacA", + 8 + ], + [ + "bbacacb", + "acbCBaAcC", + 12 + ], + [ + "bbacccaa", + "AbBABACaa", + 9 + ], + [ + "bbb", + "ABC", + 5 + ], + [ + "bbb", + "C", + 6 + ], + [ + "bbb", + "aCBc", + 7 + ], + [ + "bbbA", + "bccbbcCCB", + 12 + ], + [ + "bbbABA", + "a", + 11 + ], + [ + "bbbACCCa", + "CBbcC", + 10 + ], + [ + "bbbAaBB", + "B", + 12 + ], + [ + "bbbAaCac", + "cCc", + 12 + ], + [ + "bbbAcbcC", + "aBBACacb", + 9 + ], + [ + "bbbB", + "ABAc", + 7 + ], + [ + "bbbB", + "AbABcABAB", + 12 + ], + [ + "bbbB", + "BB", + 5 + ], + [ + "bbbB", + "aC", + 8 + ], + [ + "bbbB", + "cCCcBcbCb", + 14 + ], + [ + "bbbBBBBCb", + "BcaBa", + 14 + ], + [ + "bbbBbA", + "bbA", + 6 + ], + [ + "bbbBbCBcB", + "bbcBcCC", + 9 + ], + [ + "bbbCa", + "CCB", + 8 + ], + [ + "bbbCccAA", + "bBAa", + 10 + ], + [ + "bbbaABaac", + "abaCbCB", + 13 + ], + [ + "bbbaAa", + "b", + 10 + ], + [ + "bbbaB", + "AC", + 9 + ], + [ + "bbbaa", + "CBBccC", + 10 + ], + [ + "bbbaaBba", + "Baa", + 11 + ], + [ + "bbbaacbb", + "cBbaC", + 10 + ], + [ + "bbbacBbBc", + "bcbcbCCC", + 10 + ], + [ + "bbbb", + "AbaACaC", + 12 + ], + [ + "bbbbbAB", + "aaAaCb", + 13 + ], + [ + "bbbbc", + "BBbC", + 5 + ], + [ + "bbbcAB", + "aBAbBBa", + 11 + ], + [ + "bbbcAcCAC", + "AaBbcCCcb", + 12 + ], + [ + "bbbcB", + "bcCc", + 6 + ], + [ + "bbbcBa", + "CAAccB", + 10 + ], + [ + "bbbcCB", + "aAbBABa", + 10 + ], + [ + "bbbca", + "cBbcabCC", + 9 + ], + [ + "bbbcbBAbc", + "AAacbcc", + 12 + ], + [ + "bbbcbcbc", + "Cbbaac", + 10 + ], + [ + "bbc", + "ACBaABaCC", + 15 + ], + [ + "bbc", + "BcCBaBcCc", + 14 + ], + [ + "bbc", + "CcB", + 6 + ], + [ + "bbc", + "bCCcBAac", + 11 + ], + [ + "bbc", + "c", + 4 + ], + [ + "bbcA", + "AAC", + 7 + ], + [ + "bbcACCca", + "bAABA", + 11 + ], + [ + "bbcAbBa", + "CaaA", + 11 + ], + [ + "bbcAbca", + "CCCCc", + 11 + ], + [ + "bbcAcbBab", + "CBacCca", + 12 + ], + [ + "bbcB", + "BaAbbccBb", + 10 + ], + [ + "bbcBAa", + "baaacc", + 10 + ], + [ + "bbcBCa", + "CaBabc", + 11 + ], + [ + "bbcCA", + "CAb", + 8 + ], + [ + "bbcCBACB", + "bcBBCaBc", + 10 + ], + [ + "bbcCBbAA", + "CcAc", + 12 + ], + [ + "bbcCC", + "BcbCc", + 6 + ], + [ + "bbcCCcB", + "b", + 12 + ], + [ + "bbca", + "AbbcaAcCa", + 10 + ], + [ + "bbca", + "acaBbbB", + 11 + ], + [ + "bbcaACCab", + "aACBab", + 8 + ], + [ + "bbcaAb", + "b", + 10 + ], + [ + "bbcaCab", + "B", + 13 + ], + [ + "bbcaaBCA", + "BCaaC", + 8 + ], + [ + "bbcaacCC", + "bCCcBACB", + 11 + ], + [ + "bbcab", + "bABaa", + 6 + ], + [ + "bbcabAB", + "BcC", + 11 + ], + [ + "bbcb", + "abAA", + 6 + ], + [ + "bbcbB", + "bCbAab", + 7 + ], + [ + "bbcbBccAa", + "bA", + 14 + ], + [ + "bbcbCcAB", + "cacaCCaBA", + 10 + ], + [ + "bbcc", + "B", + 7 + ], + [ + "bbcc", + "CcBBaABa", + 14 + ], + [ + "bbccAcBCB", + "CbacCAaB", + 11 + ], + [ + "bbccC", + "bac", + 6 + ], + [ + "bbccCa", + "baacc", + 7 + ], + [ + "bbccCcA", + "CbbbC", + 10 + ], + [ + "bbccaca", + "bb", + 10 + ], + [ + "bbccb", + "ABCA", + 8 + ], + [ + "bbccbB", + "Cbccbcb", + 5 + ], + [ + "bbccbBB", + "cbCB", + 8 + ], + [ + "bc", + "ACcCb", + 8 + ], + [ + "bc", + "Aa", + 4 + ], + [ + "bc", + "Ac", + 2 + ], + [ + "bc", + "BBAA", + 7 + ], + [ + "bc", + "BcA", + 3 + ], + [ + "bc", + "BcbBaCaCB", + 15 + ], + [ + "bc", + "CACcCBc", + 11 + ], + [ + "bc", + "CBBaAccB", + 13 + ], + [ + "bc", + "CaBa", + 7 + ], + [ + "bc", + "CcCaaABa", + 14 + ], + [ + "bc", + "a", + 4 + ], + [ + "bc", + "aAA", + 6 + ], + [ + "bc", + "aACBcB", + 9 + ], + [ + "bc", + "aBacc", + 7 + ], + [ + "bc", + "aBbAa", + 8 + ], + [ + "bc", + "acb", + 4 + ], + [ + "bc", + "b", + 2 + ], + [ + "bc", + "bA", + 2 + ], + [ + "bc", + "bBCcbA", + 8 + ], + [ + "bc", + "c", + 2 + ], + [ + "bc", + "cBb", + 5 + ], + [ + "bc", + "caaabc", + 8 + ], + [ + "bc", + "ccAB", + 6 + ], + [ + "bcA", + "ABbA", + 5 + ], + [ + "bcA", + "BBB", + 5 + ], + [ + "bcA", + "aabaAAB", + 10 + ], + [ + "bcA", + "acacbc", + 9 + ], + [ + "bcA", + "cbaACAB", + 9 + ], + [ + "bcAA", + "BaCc", + 7 + ], + [ + "bcAABAab", + "CB", + 13 + ], + [ + "bcAABbbBa", + "ABcccc", + 14 + ], + [ + "bcAABcb", + "c", + 12 + ], + [ + "bcAAbc", + "B", + 11 + ], + [ + "bcAB", + "a", + 7 + ], + [ + "bcAB", + "bcB", + 2 + ], + [ + "bcABAABcB", + "Cc", + 15 + ], + [ + "bcABAbBC", + "c", + 14 + ], + [ + "bcABAcB", + "bcCBCAbCa", + 9 + ], + [ + "bcAC", + "AAaC", + 5 + ], + [ + "bcACABab", + "c", + 14 + ], + [ + "bcACBbBaC", + "baaAbBbB", + 10 + ], + [ + "bcACa", + "bbaAcCBB", + 10 + ], + [ + "bcACbCa", + "aaaBBAbC", + 13 + ], + [ + "bcAa", + "BC", + 6 + ], + [ + "bcAa", + "b", + 6 + ], + [ + "bcAaAabA", + "abBB", + 13 + ], + [ + "bcAaBABA", + "CABac", + 10 + ], + [ + "bcAaBAaC", + "AaC", + 10 + ], + [ + "bcAaBCc", + "ca", + 10 + ], + [ + "bcAaaC", + "cbA", + 9 + ], + [ + "bcAabc", + "BAABb", + 7 + ], + [ + "bcAb", + "BcbbbA", + 7 + ], + [ + "bcAbAAAAb", + "abCBaAABc", + 11 + ], + [ + "bcAbACaB", + "cCaCAC", + 10 + ], + [ + "bcAbacAbc", + "BccaBBCbb", + 12 + ], + [ + "bcAc", + "BCBcaBCaB", + 13 + ], + [ + "bcAca", + "A", + 8 + ], + [ + "bcAcba", + "CcAc", + 6 + ], + [ + "bcAcbccB", + "Abbac", + 10 + ], + [ + "bcAccA", + "bbabbAcC", + 11 + ], + [ + "bcAccab", + "CCACcbcC", + 10 + ], + [ + "bcB", + "AaCbCccbb", + 13 + ], + [ + "bcB", + "BaAACbA", + 11 + ], + [ + "bcB", + "CAAABA", + 10 + ], + [ + "bcB", + "aaAaBccC", + 13 + ], + [ + "bcB", + "bAAAACCA", + 13 + ], + [ + "bcB", + "cAbcb", + 5 + ], + [ + "bcBA", + "bBBAb", + 4 + ], + [ + "bcBAAc", + "BccA", + 7 + ], + [ + "bcBACabcB", + "abBA", + 14 + ], + [ + "bcBAab", + "baaaBbcc", + 11 + ], + [ + "bcBAcAC", + "aCCC", + 10 + ], + [ + "bcBBC", + "c", + 8 + ], + [ + "bcBBCbAbA", + "bcCCb", + 10 + ], + [ + "bcBBaA", + "ABCBb", + 9 + ], + [ + "bcBBabABb", + "BcbaBBBCa", + 11 + ], + [ + "bcBBbB", + "bbbCC", + 8 + ], + [ + "bcBCAaaaB", + "CC", + 15 + ], + [ + "bcBCBC", + "aCB", + 8 + ], + [ + "bcBCCAB", + "ABCbc", + 10 + ], + [ + "bcBCCB", + "cAb", + 9 + ], + [ + "bcBCCcbA", + "c", + 14 + ], + [ + "bcBCa", + "CBccAbA", + 10 + ], + [ + "bcBa", + "abaBbcaba", + 11 + ], + [ + "bcBaBB", + "CB", + 9 + ], + [ + "bcBaCc", + "aaCBa", + 9 + ], + [ + "bcBaa", + "BcBB", + 5 + ], + [ + "bcBabcbc", + "accAbcB", + 8 + ], + [ + "bcBac", + "BB", + 7 + ], + [ + "bcBbAB", + "aCAbA", + 7 + ], + [ + "bcBbACCB", + "BAbcCc", + 10 + ], + [ + "bcBbACCac", + "CB", + 15 + ], + [ + "bcBbBCB", + "BcCbb", + 8 + ], + [ + "bcBbbAcac", + "ACbccac", + 9 + ], + [ + "bcBbc", + "caC", + 7 + ], + [ + "bcBc", + "A", + 8 + ], + [ + "bcBcaa", + "BbbCAaBB", + 10 + ], + [ + "bcBccBCBA", + "ABaCCAba", + 13 + ], + [ + "bcBccbcC", + "aC", + 14 + ], + [ + "bcC", + "ACabb", + 9 + ], + [ + "bcC", + "Ac", + 4 + ], + [ + "bcC", + "CACac", + 8 + ], + [ + "bcC", + "CAaCbCbb", + 13 + ], + [ + "bcC", + "CacBAbbac", + 15 + ], + [ + "bcC", + "a", + 6 + ], + [ + "bcC", + "abBBcB", + 8 + ], + [ + "bcC", + "acaAAaCbb", + 14 + ], + [ + "bcC", + "bBCaBBC", + 9 + ], + [ + "bcC", + "bbbCCABb", + 11 + ], + [ + "bcC", + "cBBBBCac", + 13 + ], + [ + "bcC", + "cCAAb", + 8 + ], + [ + "bcC", + "ccBcB", + 7 + ], + [ + "bcCAAA", + "CCCBbBAC", + 11 + ], + [ + "bcCAaA", + "AbCabC", + 9 + ], + [ + "bcCAb", + "CbBccBC", + 9 + ], + [ + "bcCAcAa", + "cB", + 12 + ], + [ + "bcCAcbC", + "AcAAbc", + 7 + ], + [ + "bcCB", + "a", + 8 + ], + [ + "bcCBAB", + "AaBAcB", + 8 + ], + [ + "bcCBBcb", + "CaCbaa", + 11 + ], + [ + "bcCBC", + "cb", + 7 + ], + [ + "bcCBaCAAc", + "aBb", + 16 + ], + [ + "bcCBaCb", + "aaaacC", + 11 + ], + [ + "bcCCAbB", + "BBcCAcA", + 8 + ], + [ + "bcCCa", + "BACbBaAcC", + 13 + ], + [ + "bcCCaa", + "cBcC", + 9 + ], + [ + "bcCCcbA", + "AAaAcCCA", + 12 + ], + [ + "bcCa", + "bCCC", + 3 + ], + [ + "bcCaaAB", + "cAAc", + 9 + ], + [ + "bcCaacC", + "BbcaccCCB", + 10 + ], + [ + "bcCab", + "BcCABbCAC", + 10 + ], + [ + "bcCacAA", + "CcbB", + 10 + ], + [ + "bcCacCaa", + "ABc", + 14 + ], + [ + "bcCacba", + "Bc", + 11 + ], + [ + "bcCb", + "aCbcBbCc", + 10 + ], + [ + "bcCbB", + "bAbCc", + 8 + ], + [ + "bcCbBCbaB", + "BC", + 14 + ], + [ + "bcCbaa", + "BbAACBAaC", + 10 + ], + [ + "bcCbbc", + "baAA", + 10 + ], + [ + "bcCc", + "AAA", + 8 + ], + [ + "bcCcBB", + "BaAccBC", + 8 + ], + [ + "bcCcaCCca", + "AaCc", + 12 + ], + [ + "bcCcbAAc", + "Ca", + 13 + ], + [ + "bca", + "AB", + 6 + ], + [ + "bca", + "AcaaBC", + 8 + ], + [ + "bca", + "CACBbcC", + 10 + ], + [ + "bca", + "bccb", + 4 + ], + [ + "bcaA", + "aBaCBBAa", + 12 + ], + [ + "bcaAAbCbC", + "CCcAA", + 13 + ], + [ + "bcaABAa", + "Ac", + 12 + ], + [ + "bcaACb", + "bAbCaCB", + 8 + ], + [ + "bcaAa", + "BbCcc", + 9 + ], + [ + "bcaAaaAC", + "acaaABc", + 8 + ], + [ + "bcaAba", + "BabBAaccb", + 14 + ], + [ + "bcaB", + "bCBCBAA", + 9 + ], + [ + "bcaBBAC", + "Cb", + 12 + ], + [ + "bcaCA", + "ACA", + 5 + ], + [ + "bcaCA", + "aB", + 8 + ], + [ + "bcaCAABcB", + "bBAbCcbc", + 12 + ], + [ + "bcaCB", + "AbaACbaC", + 10 + ], + [ + "bcaCBa", + "BaAbAB", + 9 + ], + [ + "bcaCa", + "cCbccbA", + 9 + ], + [ + "bcaCbBA", + "bcBabAb", + 8 + ], + [ + "bcaCbCbaA", + "bbCBb", + 11 + ], + [ + "bcaCcB", + "cAab", + 8 + ], + [ + "bcaa", + "CC", + 7 + ], + [ + "bcaa", + "aaBA", + 7 + ], + [ + "bcaaA", + "BAAacaAba", + 11 + ], + [ + "bcaaABBA", + "baa", + 10 + ], + [ + "bcaaAcbC", + "aBACAcaab", + 13 + ], + [ + "bcaaBaAb", + "b", + 14 + ], + [ + "bcaacb", + "acCb", + 7 + ], + [ + "bcab", + "a", + 6 + ], + [ + "bcab", + "cCAabCBCb", + 13 + ], + [ + "bcabAA", + "BAAcca", + 9 + ], + [ + "bcabC", + "Aac", + 7 + ], + [ + "bcabb", + "aAaA", + 8 + ], + [ + "bcac", + "CCAAbCbC", + 12 + ], + [ + "bcb", + "A", + 6 + ], + [ + "bcb", + "aBabCcB", + 9 + ], + [ + "bcb", + "abACACc", + 11 + ], + [ + "bcbA", + "AcbAcbCaB", + 11 + ], + [ + "bcbAAA", + "a", + 11 + ], + [ + "bcbAAAB", + "a", + 13 + ], + [ + "bcbACB", + "CBbcbCca", + 9 + ], + [ + "bcbACCA", + "AabaACAcB", + 11 + ], + [ + "bcbACCC", + "c", + 12 + ], + [ + "bcbAbCbB", + "caA", + 12 + ], + [ + "bcbAc", + "BACCccbCb", + 13 + ], + [ + "bcbBA", + "baaaa", + 7 + ], + [ + "bcbBAABCa", + "cBAcaa", + 10 + ], + [ + "bcbCAaAC", + "bB", + 13 + ], + [ + "bcbCAc", + "caccbaABC", + 11 + ], + [ + "bcbCB", + "c", + 8 + ], + [ + "bcbCcCa", + "CCCbACb", + 11 + ], + [ + "bcbaAACb", + "aBAcA", + 11 + ], + [ + "bcbaBBB", + "AA", + 13 + ], + [ + "bcbaccb", + "ACACCB", + 9 + ], + [ + "bcbb", + "cBcACA", + 9 + ], + [ + "bcbb", + "cCCBaaA", + 12 + ], + [ + "bcbbABC", + "CAAaa", + 11 + ], + [ + "bcbbB", + "ABB", + 7 + ], + [ + "bcbbBc", + "cCc", + 8 + ], + [ + "bcbbCBabA", + "cCccbccBA", + 12 + ], + [ + "bcbc", + "AAA", + 8 + ], + [ + "bcbc", + "Ca", + 7 + ], + [ + "bcbcAC", + "bBAaaacb", + 12 + ], + [ + "bcc", + "C", + 5 + ], + [ + "bcc", + "CaACa", + 9 + ], + [ + "bcc", + "bACb", + 5 + ], + [ + "bcc", + "bbcCcAa", + 8 + ], + [ + "bccA", + "C", + 7 + ], + [ + "bccA", + "aAb", + 8 + ], + [ + "bccA", + "ab", + 8 + ], + [ + "bccABAba", + "acacC", + 13 + ], + [ + "bccAC", + "BbbBcA", + 8 + ], + [ + "bccAbaA", + "aAc", + 12 + ], + [ + "bccAbcbcb", + "CbbcbbA", + 11 + ], + [ + "bccB", + "BcacaCB", + 7 + ], + [ + "bccB", + "cAcb", + 5 + ], + [ + "bccBABc", + "bAacbb", + 10 + ], + [ + "bccBBB", + "aAbbbbC", + 11 + ], + [ + "bccBBCbBa", + "bbbaabCbB", + 11 + ], + [ + "bccBBc", + "cCcbac", + 6 + ], + [ + "bccBBccA", + "bB", + 12 + ], + [ + "bccBCCAC", + "bABBCAabC", + 9 + ], + [ + "bccBCCBc", + "ACBaa", + 13 + ], + [ + "bccBa", + "CAcbB", + 7 + ], + [ + "bccBbA", + "aBCcBBBC", + 9 + ], + [ + "bccCA", + "BaBAAba", + 11 + ], + [ + "bccCAbbB", + "ccC", + 10 + ], + [ + "bccCBab", + "c", + 12 + ], + [ + "bccCCAaBB", + "bBa", + 14 + ], + [ + "bccCaBCC", + "Bb", + 14 + ], + [ + "bcca", + "AaAaabA", + 12 + ], + [ + "bccaCBb", + "bAbcabA", + 9 + ], + [ + "bccaCCC", + "CCBCBbAAA", + 16 + ], + [ + "bccaa", + "C", + 9 + ], + [ + "bccaab", + "cAcc", + 9 + ], + [ + "bccbAbab", + "b", + 14 + ], + [ + "bccbBCcCA", + "B", + 16 + ], + [ + "bccbBb", + "ccba", + 6 + ], + [ + "bccbC", + "bBC", + 5 + ], + [ + "bccbbCC", + "bBccA", + 9 + ], + [ + "bccbbbCB", + "BbcABC", + 10 + ], + [ + "bccbccAc", + "bBC", + 12 + ], + [ + "bccc", + "ACcca", + 5 + ], + [ + "bcccAA", + "bCBAbca", + 10 + ], + [ + "bcccAaBca", + "BaC", + 14 + ], + [ + "bcccBBCaA", + "bcAcaCcb", + 10 + ], + [ + "bcccC", + "aAAaa", + 10 + ], + [ + "bcccCCCbc", + "Bac", + 15 + ], + [ + "c", + "A", + 2 + ], + [ + "c", + "AA", + 4 + ], + [ + "c", + "AABacBC", + 12 + ], + [ + "c", + "AAbBBaBB", + 16 + ], + [ + "c", + "AB", + 4 + ], + [ + "c", + "ABA", + 6 + ], + [ + "c", + "ABBaCCBBB", + 17 + ], + [ + "c", + "ABBbBBCBA", + 17 + ], + [ + "c", + "ABbCb", + 9 + ], + [ + "c", + "ABbaaba", + 14 + ], + [ + "c", + "AC", + 3 + ], + [ + "c", + "ACABa", + 9 + ], + [ + "c", + "ACCB", + 7 + ], + [ + "c", + "ACCcbCCbC", + 16 + ], + [ + "c", + "ACbBBC", + 11 + ], + [ + "c", + "Aa", + 4 + ], + [ + "c", + "AaABA", + 10 + ], + [ + "c", + "Aaa", + 6 + ], + [ + "c", + "AaaaBbBC", + 15 + ], + [ + "c", + "AaabCAb", + 13 + ], + [ + "c", + "Ab", + 4 + ], + [ + "c", + "AbaCAAA", + 13 + ], + [ + "c", + "AcCACaAA", + 14 + ], + [ + "c", + "AcCCBAcaB", + 16 + ], + [ + "c", + "Acb", + 4 + ], + [ + "c", + "AcccbCacB", + 16 + ], + [ + "c", + "B", + 2 + ], + [ + "c", + "BA", + 4 + ], + [ + "c", + "BAAAAbB", + 14 + ], + [ + "c", + "BACA", + 7 + ], + [ + "c", + "BACbAC", + 11 + ], + [ + "c", + "BAbBbAc", + 12 + ], + [ + "c", + "BAbb", + 8 + ], + [ + "c", + "BAcBb", + 8 + ], + [ + "c", + "BB", + 4 + ], + [ + "c", + "BBAACAb", + 13 + ], + [ + "c", + "BBABBC", + 11 + ], + [ + "c", + "BBBcBccc", + 14 + ], + [ + "c", + "BBbbc", + 8 + ], + [ + "c", + "BBbcbbac", + 14 + ], + [ + "c", + "BBcBc", + 8 + ], + [ + "c", + "BC", + 3 + ], + [ + "c", + "BCA", + 5 + ], + [ + "c", + "BCAAcBC", + 12 + ], + [ + "c", + "BCAbCb", + 11 + ], + [ + "c", + "BCBc", + 6 + ], + [ + "c", + "BCa", + 5 + ], + [ + "c", + "BCcbaB", + 10 + ], + [ + "c", + "Ba", + 4 + ], + [ + "c", + "BaAaaA", + 12 + ], + [ + "c", + "BaBacca", + 12 + ], + [ + "c", + "BaCCBaCAC", + 17 + ], + [ + "c", + "BaaBcAcBb", + 16 + ], + [ + "c", + "BaaccC", + 10 + ], + [ + "c", + "BabCcAAcA", + 16 + ], + [ + "c", + "BbABABaBC", + 17 + ], + [ + "c", + "BbCa", + 7 + ], + [ + "c", + "BbaAacbb", + 14 + ], + [ + "c", + "BbaCAAcBa", + 16 + ], + [ + "c", + "BbcBb", + 8 + ], + [ + "c", + "Bbcc", + 6 + ], + [ + "c", + "Bc", + 2 + ], + [ + "c", + "BcBa", + 6 + ], + [ + "c", + "Bcb", + 4 + ], + [ + "c", + "Bcccb", + 8 + ], + [ + "c", + "C", + 1 + ], + [ + "c", + "CB", + 3 + ], + [ + "c", + "CBABBC", + 11 + ], + [ + "c", + "CBAcCA", + 10 + ], + [ + "c", + "CBBAbbA", + 13 + ], + [ + "c", + "CBCBb", + 9 + ], + [ + "c", + "CBCaBa", + 11 + ], + [ + "c", + "CBb", + 5 + ], + [ + "c", + "CCCBaaCB", + 15 + ], + [ + "c", + "CCCCCC", + 11 + ], + [ + "c", + "Ca", + 3 + ], + [ + "c", + "CaAb", + 7 + ], + [ + "c", + "CaCa", + 7 + ], + [ + "c", + "Cab", + 5 + ], + [ + "c", + "CabC", + 7 + ], + [ + "c", + "CbAC", + 7 + ], + [ + "c", + "CbabCAaAc", + 16 + ], + [ + "c", + "CcAABA", + 10 + ], + [ + "c", + "CccaaAa", + 12 + ], + [ + "c", + "a", + 2 + ], + [ + "c", + "aAAa", + 8 + ], + [ + "c", + "aAAc", + 6 + ], + [ + "c", + "aACbcB", + 10 + ], + [ + "c", + "aAbACcCA", + 14 + ], + [ + "c", + "aAbab", + 10 + ], + [ + "c", + "aB", + 4 + ], + [ + "c", + "aBAC", + 7 + ], + [ + "c", + "aBBbbc", + 10 + ], + [ + "c", + "aBaaA", + 10 + ], + [ + "c", + "aBb", + 6 + ], + [ + "c", + "aBcABcCCb", + 16 + ], + [ + "c", + "aBcBBCBbc", + 16 + ], + [ + "c", + "aCa", + 5 + ], + [ + "c", + "aa", + 4 + ], + [ + "c", + "aaCBCbC", + 13 + ], + [ + "c", + "aabcBAAcc", + 16 + ], + [ + "c", + "aabcBcA", + 12 + ], + [ + "c", + "aacABB", + 10 + ], + [ + "c", + "aacb", + 6 + ], + [ + "c", + "aaccAca", + 12 + ], + [ + "c", + "ab", + 4 + ], + [ + "c", + "abAa", + 8 + ], + [ + "c", + "abCAcC", + 10 + ], + [ + "c", + "abCCAB", + 11 + ], + [ + "c", + "abCc", + 6 + ], + [ + "c", + "ac", + 2 + ], + [ + "c", + "accbaBBAB", + 16 + ], + [ + "c", + "b", + 2 + ], + [ + "c", + "bAAccBAa", + 14 + ], + [ + "c", + "bAaBAbcCc", + 16 + ], + [ + "c", + "bAcacAbb", + 14 + ], + [ + "c", + "bBAaCa", + 11 + ], + [ + "c", + "bBAcaCaca", + 16 + ], + [ + "c", + "bBBCbaB", + 13 + ], + [ + "c", + "bBBa", + 8 + ], + [ + "c", + "bBab", + 8 + ], + [ + "c", + "bBcCbcABC", + 16 + ], + [ + "c", + "bBcCca", + 10 + ], + [ + "c", + "bCBC", + 7 + ], + [ + "c", + "bCC", + 5 + ], + [ + "c", + "bCCAbcBBa", + 16 + ], + [ + "c", + "bCCa", + 7 + ], + [ + "c", + "bCabaBC", + 13 + ], + [ + "c", + "bCc", + 4 + ], + [ + "c", + "bCcA", + 6 + ], + [ + "c", + "baaAB", + 10 + ], + [ + "c", + "baaABBC", + 13 + ], + [ + "c", + "baaAccba", + 14 + ], + [ + "c", + "bbBbbBBa", + 16 + ], + [ + "c", + "bbCccAAC", + 14 + ], + [ + "c", + "bc", + 2 + ], + [ + "c", + "bcAbC", + 8 + ], + [ + "c", + "bcBAcAcB", + 14 + ], + [ + "c", + "bcbBbCcb", + 14 + ], + [ + "c", + "bccAAa", + 10 + ], + [ + "c", + "c", + 0 + ], + [ + "c", + "cAABBb", + 10 + ], + [ + "c", + "cAACa", + 8 + ], + [ + "c", + "cABc", + 6 + ], + [ + "c", + "cAaCBc", + 10 + ], + [ + "c", + "cAacbCCA", + 14 + ], + [ + "c", + "cBCCAbA", + 12 + ], + [ + "c", + "cBacAc", + 10 + ], + [ + "c", + "cBcCBb", + 10 + ], + [ + "c", + "cCAB", + 6 + ], + [ + "c", + "cCCbbBBcA", + 16 + ], + [ + "c", + "cCcaBbcba", + 16 + ], + [ + "c", + "ca", + 2 + ], + [ + "c", + "caC", + 4 + ], + [ + "c", + "caa", + 4 + ], + [ + "c", + "cabA", + 6 + ], + [ + "c", + "cb", + 2 + ], + [ + "c", + "cbCaBBab", + 14 + ], + [ + "c", + "cbCbAa", + 10 + ], + [ + "c", + "cbbBbCcC", + 14 + ], + [ + "c", + "cbcaAAC", + 12 + ], + [ + "c", + "cc", + 2 + ], + [ + "c", + "ccB", + 4 + ], + [ + "c", + "ccCBaBAC", + 14 + ], + [ + "c", + "ccc", + 4 + ], + [ + "cA", + "A", + 2 + ], + [ + "cA", + "AAbaCB", + 10 + ], + [ + "cA", + "AC", + 4 + ], + [ + "cA", + "Ac", + 4 + ], + [ + "cA", + "Acc", + 4 + ], + [ + "cA", + "BABCB", + 8 + ], + [ + "cA", + "BBc", + 6 + ], + [ + "cA", + "BaaBCbA", + 11 + ], + [ + "cA", + "Bbb", + 6 + ], + [ + "cA", + "BbbAAccB", + 14 + ], + [ + "cA", + "Bc", + 4 + ], + [ + "cA", + "C", + 3 + ], + [ + "cA", + "CAaCAABb", + 13 + ], + [ + "cA", + "CAaacAcc", + 12 + ], + [ + "cA", + "CbaAccc", + 11 + ], + [ + "cA", + "Cbbb", + 7 + ], + [ + "cA", + "CcC", + 4 + ], + [ + "cA", + "aB", + 4 + ], + [ + "cA", + "aC", + 4 + ], + [ + "cA", + "ab", + 4 + ], + [ + "cA", + "abAAcc", + 10 + ], + [ + "cA", + "abC", + 6 + ], + [ + "cA", + "bBCAbCa", + 11 + ], + [ + "cA", + "bCbbBCAC", + 13 + ], + [ + "cA", + "bCcCBcCA", + 12 + ], + [ + "cA", + "bcaCcbC", + 11 + ], + [ + "cA", + "cABaBCb", + 10 + ], + [ + "cA", + "cBaAB", + 6 + ], + [ + "cA", + "cCBababA", + 12 + ], + [ + "cA", + "ccCBbBab", + 13 + ], + [ + "cA", + "cca", + 3 + ], + [ + "cAA", + "AaaCaBcCb", + 16 + ], + [ + "cAA", + "AbBC", + 8 + ], + [ + "cAA", + "a", + 5 + ], + [ + "cAA", + "cb", + 4 + ], + [ + "cAAA", + "cBaC", + 5 + ], + [ + "cAAAA", + "bcA", + 8 + ], + [ + "cAAAC", + "aBAaCcCca", + 13 + ], + [ + "cAAAacbCB", + "BBaACbA", + 12 + ], + [ + "cAAAbabc", + "BCAacBCBc", + 10 + ], + [ + "cAAAbbc", + "AAbb", + 6 + ], + [ + "cAAAcB", + "bbAB", + 8 + ], + [ + "cAAB", + "ABCaCbCbC", + 15 + ], + [ + "cAAB", + "Aab", + 4 + ], + [ + "cAABaBAa", + "bAaBaBA", + 5 + ], + [ + "cAAC", + "CAB", + 5 + ], + [ + "cAACA", + "CaABCCbAb", + 10 + ], + [ + "cAACAC", + "BC", + 10 + ], + [ + "cAACaCc", + "AcabA", + 9 + ], + [ + "cAACb", + "BCAcca", + 8 + ], + [ + "cAACbABCc", + "CCbAB", + 9 + ], + [ + "cAACbb", + "aAaC", + 7 + ], + [ + "cAACcAA", + "bAaCcacb", + 8 + ], + [ + "cAAa", + "caABcbBAB", + 12 + ], + [ + "cAAaB", + "BBaAb", + 7 + ], + [ + "cAAaBAAA", + "aCb", + 14 + ], + [ + "cAAaCbCc", + "CC", + 12 + ], + [ + "cAAaba", + "bcb", + 10 + ], + [ + "cAAabcCCA", + "aAa", + 13 + ], + [ + "cAAbB", + "AAcaBaa", + 10 + ], + [ + "cAAbBA", + "caCcABAB", + 9 + ], + [ + "cAAbBBaCa", + "CabcB", + 12 + ], + [ + "cAAbC", + "BAbbb", + 6 + ], + [ + "cAAba", + "CCBAabaAB", + 10 + ], + [ + "cAAc", + "bCcCCB", + 10 + ], + [ + "cAAcABCcb", + "BCBB", + 13 + ], + [ + "cAAcB", + "ca", + 7 + ], + [ + "cAAcCCAc", + "Cb", + 14 + ], + [ + "cAAcaabB", + "A", + 14 + ], + [ + "cAAccaaB", + "BBbbAcBc", + 15 + ], + [ + "cAB", + "B", + 4 + ], + [ + "cAB", + "BCAAacb", + 10 + ], + [ + "cAB", + "CaCbCca", + 11 + ], + [ + "cAB", + "CbacbCcbB", + 14 + ], + [ + "cAB", + "b", + 5 + ], + [ + "cAB", + "baabbCA", + 12 + ], + [ + "cAB", + "cbbaBbCb", + 11 + ], + [ + "cAB", + "cccBBCBC", + 12 + ], + [ + "cAB", + "cccCCCCb", + 13 + ], + [ + "cABA", + "CA", + 5 + ], + [ + "cABAABc", + "acabCCb", + 11 + ], + [ + "cABABAACA", + "cBaBCcaa", + 10 + ], + [ + "cABAa", + "c", + 8 + ], + [ + "cABAcaBb", + "BCBaBCBc", + 11 + ], + [ + "cABBAaCac", + "aAacACCcc", + 10 + ], + [ + "cABBCaBbc", + "bBAaBcACC", + 13 + ], + [ + "cABBc", + "B", + 8 + ], + [ + "cABBcacb", + "bBcCcCAA", + 13 + ], + [ + "cABC", + "B", + 6 + ], + [ + "cABCAB", + "bAcb", + 8 + ], + [ + "cABCAc", + "cBCBa", + 6 + ], + [ + "cABCAca", + "ABaCCCbcc", + 12 + ], + [ + "cABCBbbCA", + "ABBccc", + 11 + ], + [ + "cABCa", + "CaA", + 7 + ], + [ + "cABaA", + "aAAbaBb", + 9 + ], + [ + "cABaAaA", + "BAB", + 10 + ], + [ + "cABaBAC", + "acCa", + 12 + ], + [ + "cABaCAbbA", + "A", + 16 + ], + [ + "cABac", + "BaCc", + 6 + ], + [ + "cABbACA", + "BbccCBCBc", + 15 + ], + [ + "cABba", + "AAC", + 8 + ], + [ + "cABc", + "AaccB", + 7 + ], + [ + "cABcABc", + "cAcBBca", + 6 + ], + [ + "cABcBcb", + "cbBCa", + 8 + ], + [ + "cABcb", + "Cac", + 6 + ], + [ + "cABcba", + "BAab", + 8 + ], + [ + "cAC", + "BCaCAbaCa", + 13 + ], + [ + "cAC", + "C", + 4 + ], + [ + "cAC", + "CABbA", + 7 + ], + [ + "cAC", + "CAcABbA", + 10 + ], + [ + "cAC", + "bAa", + 4 + ], + [ + "cAC", + "cBBac", + 6 + ], + [ + "cACAACCbc", + "CAcABb", + 10 + ], + [ + "cACABcACC", + "CAc", + 12 + ], + [ + "cACACbC", + "BbCcAa", + 11 + ], + [ + "cACAabC", + "BbcbBcAAa", + 14 + ], + [ + "cACBCaC", + "C", + 12 + ], + [ + "cACBCcCBb", + "bBaaBaCbC", + 14 + ], + [ + "cACBaaB", + "bccBcBaB", + 9 + ], + [ + "cACCBBcb", + "Ba", + 14 + ], + [ + "cACCaa", + "aBcCaab", + 7 + ], + [ + "cACCc", + "ccAa", + 7 + ], + [ + "cACa", + "aBBcB", + 9 + ], + [ + "cACa", + "cAbCCA", + 5 + ], + [ + "cACaB", + "cAbbaBB", + 6 + ], + [ + "cACac", + "cBCBbbbB", + 12 + ], + [ + "cACbCBB", + "bBbAC", + 12 + ], + [ + "cACbaCc", + "AacCACBaB", + 11 + ], + [ + "cACbaba", + "acAACBcCC", + 11 + ], + [ + "cACbbC", + "Cba", + 8 + ], + [ + "cACbbb", + "ccacBccbA", + 11 + ], + [ + "cACbcb", + "b", + 10 + ], + [ + "cACc", + "Cc", + 4 + ], + [ + "cACc", + "bAba", + 6 + ], + [ + "cACcA", + "Caccc", + 5 + ], + [ + "cACcAcc", + "bbB", + 14 + ], + [ + "cACca", + "acabb", + 9 + ], + [ + "cACcaA", + "bB", + 12 + ], + [ + "cACccAacA", + "bcCBabCc", + 14 + ], + [ + "cAa", + "BacbCAbcA", + 13 + ], + [ + "cAa", + "Bbcb", + 8 + ], + [ + "cAa", + "C", + 5 + ], + [ + "cAa", + "aCBcCAcc", + 12 + ], + [ + "cAa", + "aCacc", + 8 + ], + [ + "cAa", + "caBcA", + 6 + ], + [ + "cAa", + "cacAbaAb", + 10 + ], + [ + "cAa", + "ccccAcaBC", + 12 + ], + [ + "cAaA", + "CbbaAaba", + 10 + ], + [ + "cAaAA", + "CaCbcbab", + 13 + ], + [ + "cAaAA", + "acAc", + 7 + ], + [ + "cAaAABAcb", + "bAba", + 14 + ], + [ + "cAaACBaa", + "cbBabcaac", + 11 + ], + [ + "cAaACc", + "ccbBB", + 10 + ], + [ + "cAaAaaB", + "CBCbBccBa", + 15 + ], + [ + "cAaAc", + "ABCACcb", + 10 + ], + [ + "cAaB", + "C", + 7 + ], + [ + "cAaB", + "aBaBA", + 6 + ], + [ + "cAaBCBbAA", + "aAc", + 14 + ], + [ + "cAaBaA", + "a", + 10 + ], + [ + "cAaC", + "BcbAbbCb", + 10 + ], + [ + "cAaC", + "CbB", + 7 + ], + [ + "cAaCAc", + "ac", + 8 + ], + [ + "cAaCaAc", + "bBBBbaC", + 12 + ], + [ + "cAaCaC", + "ABBa", + 8 + ], + [ + "cAaCc", + "c", + 8 + ], + [ + "cAaaAAA", + "BBCAa", + 11 + ], + [ + "cAaaaBCA", + "AcABc", + 10 + ], + [ + "cAaaacAB", + "aAbCB", + 10 + ], + [ + "cAaaacaa", + "ABB", + 14 + ], + [ + "cAaacB", + "AaCBbACB", + 10 + ], + [ + "cAaaca", + "bAbbc", + 8 + ], + [ + "cAabACa", + "bcaabaaAb", + 9 + ], + [ + "cAabCA", + "CbACAAcb", + 11 + ], + [ + "cAaba", + "a", + 8 + ], + [ + "cAabcacaa", + "aA", + 15 + ], + [ + "cAacAaBBC", + "bBaBAbcaC", + 12 + ], + [ + "cAaca", + "AcacAA", + 7 + ], + [ + "cAaca", + "BcCcBBBA", + 13 + ], + [ + "cAacc", + "BBCaCA", + 9 + ], + [ + "cAacc", + "a", + 8 + ], + [ + "cAacccAb", + "ccbbBACA", + 13 + ], + [ + "cAb", + "B", + 5 + ], + [ + "cAb", + "BAbABAC", + 10 + ], + [ + "cAb", + "BBAAc", + 8 + ], + [ + "cAb", + "CBABCa", + 8 + ], + [ + "cAb", + "CbBBaA", + 10 + ], + [ + "cAb", + "caBACCa", + 10 + ], + [ + "cAbA", + "Cacc", + 6 + ], + [ + "cAbA", + "aAAACA", + 8 + ], + [ + "cAbABbab", + "aCA", + 13 + ], + [ + "cAbAC", + "BAAc", + 5 + ], + [ + "cAbACB", + "bAcCC", + 8 + ], + [ + "cAbACcCaa", + "cBa", + 13 + ], + [ + "cAbAb", + "acBC", + 9 + ], + [ + "cAbAbA", + "bACaC", + 9 + ], + [ + "cAbAbAAAa", + "A", + 16 + ], + [ + "cAbBB", + "AbaaC", + 8 + ], + [ + "cAbBaAAAA", + "BabaaBAa", + 9 + ], + [ + "cAbBaB", + "AcB", + 8 + ], + [ + "cAbaBbBC", + "a", + 14 + ], + [ + "cAbab", + "Ba", + 7 + ], + [ + "cAbabb", + "bC", + 10 + ], + [ + "cAbbB", + "aBBcaAA", + 13 + ], + [ + "cAbbBCAcB", + "aabbaC", + 11 + ], + [ + "cAbbBaca", + "CbCAcBaC", + 12 + ], + [ + "cAbbBbcC", + "CB", + 13 + ], + [ + "cAbbb", + "cCbaBAAAc", + 13 + ], + [ + "cAbcAcBA", + "AcA", + 10 + ], + [ + "cAbcaCccC", + "ABbAbbcB", + 12 + ], + [ + "cAbcbB", + "AAABaac", + 11 + ], + [ + "cAbccBCBc", + "AAACAcbAc", + 12 + ], + [ + "cAc", + "AbBCcaBB", + 13 + ], + [ + "cAc", + "BCCcCaBac", + 13 + ], + [ + "cAc", + "CB", + 5 + ], + [ + "cAc", + "bBb", + 6 + ], + [ + "cAc", + "bCaCaCb", + 11 + ], + [ + "cAc", + "bcb", + 6 + ], + [ + "cAc", + "cAAcaa", + 6 + ], + [ + "cAcA", + "CaaB", + 6 + ], + [ + "cAcAA", + "cbAcBbB", + 8 + ], + [ + "cAcABAAc", + "aABaBBc", + 9 + ], + [ + "cAcACcBb", + "cacbCccC", + 7 + ], + [ + "cAcAb", + "aBbAA", + 8 + ], + [ + "cAcAcAcCc", + "caaAAcbc", + 7 + ], + [ + "cAcAcCB", + "ccbcbBbC", + 10 + ], + [ + "cAcBBaBCb", + "C", + 16 + ], + [ + "cAcBCb", + "abCbAAB", + 11 + ], + [ + "cAcBCcBba", + "A", + 16 + ], + [ + "cAcBbA", + "aBabBACbB", + 13 + ], + [ + "cAcBbB", + "ABBcacBBA", + 10 + ], + [ + "cAcCAB", + "CBBbc", + 11 + ], + [ + "cAcCBCa", + "CCA", + 9 + ], + [ + "cAcCBcBBa", + "CacACBBBB", + 8 + ], + [ + "cAcCCBCBA", + "ccCcbcbc", + 8 + ], + [ + "cAcCbCcAB", + "caC", + 13 + ], + [ + "cAcCcbaaA", + "CcBaa", + 9 + ], + [ + "cAca", + "BAcbCbC", + 10 + ], + [ + "cAcaAa", + "caAAaab", + 7 + ], + [ + "cAcaBAAC", + "BBB", + 14 + ], + [ + "cAcaaa", + "BaBaBC", + 9 + ], + [ + "cAcaaca", + "aaCBA", + 10 + ], + [ + "cAcabCbCB", + "BacBcbABb", + 11 + ], + [ + "cAcabaB", + "Cc", + 11 + ], + [ + "cAcac", + "acbA", + 7 + ], + [ + "cAcbABCBB", + "aABc", + 12 + ], + [ + "cAcbBCBac", + "bCbAAacB", + 13 + ], + [ + "cAcbbccAc", + "AABAaBbCb", + 16 + ], + [ + "cAcc", + "aCa", + 6 + ], + [ + "cAcc", + "bCAAcaCC", + 10 + ], + [ + "cAcc", + "cc", + 4 + ], + [ + "cAccCB", + "aAABca", + 9 + ], + [ + "cAccaBCAb", + "BBcBCCAbb", + 12 + ], + [ + "cAccaaABa", + "bCAcB", + 13 + ], + [ + "cAcccAA", + "bCAb", + 11 + ], + [ + "cAcccaCaA", + "abaB", + 14 + ], + [ + "cAcccacab", + "BbCCbaB", + 13 + ], + [ + "cB", + "AbAB", + 6 + ], + [ + "cB", + "AbBbCA", + 10 + ], + [ + "cB", + "B", + 2 + ], + [ + "cB", + "BACbBc", + 9 + ], + [ + "cB", + "BAcB", + 4 + ], + [ + "cB", + "BBaBaaccC", + 16 + ], + [ + "cB", + "BcAaA", + 8 + ], + [ + "cB", + "C", + 3 + ], + [ + "cB", + "CAA", + 5 + ], + [ + "cB", + "CAAA", + 7 + ], + [ + "cB", + "CAacabCCC", + 15 + ], + [ + "cB", + "CBBB", + 5 + ], + [ + "cB", + "CCBCABaaA", + 15 + ], + [ + "cB", + "Cc", + 3 + ], + [ + "cB", + "CcBAc", + 6 + ], + [ + "cB", + "a", + 4 + ], + [ + "cB", + "aACCba", + 10 + ], + [ + "cB", + "aacbcC", + 9 + ], + [ + "cB", + "acaaAABB", + 12 + ], + [ + "cB", + "b", + 3 + ], + [ + "cB", + "ba", + 4 + ], + [ + "cB", + "baacB", + 6 + ], + [ + "cB", + "bc", + 4 + ], + [ + "cB", + "cABAbCBc", + 12 + ], + [ + "cB", + "cCAB", + 4 + ], + [ + "cB", + "caAAbbBC", + 12 + ], + [ + "cB", + "caBAAa", + 8 + ], + [ + "cBA", + "Ac", + 6 + ], + [ + "cBA", + "CB", + 3 + ], + [ + "cBA", + "a", + 5 + ], + [ + "cBA", + "ac", + 6 + ], + [ + "cBA", + "b", + 5 + ], + [ + "cBA", + "bbaBac", + 9 + ], + [ + "cBA", + "cAaabbab", + 12 + ], + [ + "cBAA", + "AAB", + 6 + ], + [ + "cBAA", + "aCabbA", + 8 + ], + [ + "cBAAB", + "CCa", + 8 + ], + [ + "cBAABa", + "bA", + 9 + ], + [ + "cBAABccc", + "caBcbAAa", + 13 + ], + [ + "cBAAbCcC", + "Bcc", + 11 + ], + [ + "cBAAcAbAC", + "bBaacb", + 10 + ], + [ + "cBAB", + "cBBcaCCb", + 10 + ], + [ + "cBABaBCc", + "BAABc", + 7 + ], + [ + "cBABaac", + "baCbbb", + 12 + ], + [ + "cBABbBAaA", + "c", + 16 + ], + [ + "cBABba", + "AaAccABac", + 12 + ], + [ + "cBABca", + "aBcB", + 7 + ], + [ + "cBACBAAcA", + "BBCbCCBb", + 13 + ], + [ + "cBACa", + "cbaabBBcA", + 12 + ], + [ + "cBACc", + "cBbBaCb", + 7 + ], + [ + "cBACcBBC", + "AbaaBbcC", + 11 + ], + [ + "cBACcbCaa", + "ab", + 15 + ], + [ + "cBAa", + "cCCBaAc", + 8 + ], + [ + "cBAaBcaB", + "b", + 15 + ], + [ + "cBAacbBa", + "aaBb", + 11 + ], + [ + "cBAbCaBA", + "ccaB", + 9 + ], + [ + "cBAba", + "B", + 8 + ], + [ + "cBAbaa", + "CaccCBA", + 12 + ], + [ + "cBAbb", + "ca", + 7 + ], + [ + "cBAbbCaCc", + "aaCA", + 13 + ], + [ + "cBAbca", + "aaACbA", + 9 + ], + [ + "cBAc", + "BA", + 4 + ], + [ + "cBAc", + "aCCcbbbcC", + 13 + ], + [ + "cBAcAb", + "BCabCbcc", + 13 + ], + [ + "cBAcAbaa", + "AAC", + 12 + ], + [ + "cBAcBBc", + "cCcaaa", + 10 + ], + [ + "cBAcCCb", + "cCcAc", + 9 + ], + [ + "cBAcb", + "CaAAACba", + 10 + ], + [ + "cBB", + "BacacBcCA", + 14 + ], + [ + "cBB", + "Bc", + 4 + ], + [ + "cBB", + "bAACA", + 10 + ], + [ + "cBB", + "bCbBBC", + 7 + ], + [ + "cBBA", + "AcAAcABa", + 11 + ], + [ + "cBBA", + "Cca", + 6 + ], + [ + "cBBABC", + "bCcbaAB", + 9 + ], + [ + "cBBABC", + "cCCcBcBc", + 9 + ], + [ + "cBBAac", + "CbbbcB", + 9 + ], + [ + "cBBBABBA", + "AAAbAAAB", + 13 + ], + [ + "cBBBCA", + "caacccAbA", + 13 + ], + [ + "cBBBCacAB", + "Accab", + 13 + ], + [ + "cBBBcbA", + "cB", + 10 + ], + [ + "cBBC", + "acaC", + 6 + ], + [ + "cBBCBaCbA", + "CBcBaAc", + 10 + ], + [ + "cBBCCAca", + "ABCcCa", + 8 + ], + [ + "cBBCb", + "cBBbaaC", + 8 + ], + [ + "cBBCbB", + "aCbBaCaB", + 8 + ], + [ + "cBBaBaAac", + "C", + 17 + ], + [ + "cBBaaCbBb", + "baCCBa", + 11 + ], + [ + "cBBabaAc", + "b", + 14 + ], + [ + "cBBac", + "ba", + 7 + ], + [ + "cBBacC", + "aBcCAC", + 8 + ], + [ + "cBBbAaabA", + "aaA", + 12 + ], + [ + "cBBbB", + "BCcAACb", + 11 + ], + [ + "cBBbBaBcA", + "bbCCCc", + 13 + ], + [ + "cBBbCbc", + "ACccCBaCB", + 13 + ], + [ + "cBBbbaBcb", + "Cc", + 15 + ], + [ + "cBBbbbb", + "acBbaBca", + 10 + ], + [ + "cBBbbcAC", + "bCaBbbcbB", + 9 + ], + [ + "cBBbbcacb", + "BcCcaaBA", + 13 + ], + [ + "cBBbca", + "BbA", + 7 + ], + [ + "cBBcAccA", + "AcbbAC", + 11 + ], + [ + "cBBcBb", + "BaCb", + 7 + ], + [ + "cBBcaBcB", + "Aa", + 14 + ], + [ + "cBBcbC", + "bcCBacb", + 8 + ], + [ + "cBC", + "cACacBc", + 9 + ], + [ + "cBC", + "cCa", + 4 + ], + [ + "cBCA", + "C", + 6 + ], + [ + "cBCACaCaC", + "ccBBBCaac", + 9 + ], + [ + "cBCAaac", + "cB", + 10 + ], + [ + "cBCAacA", + "aaAcA", + 8 + ], + [ + "cBCAcCccC", + "cbB", + 15 + ], + [ + "cBCAcbC", + "CcAbCbbaA", + 13 + ], + [ + "cBCB", + "bbabCAAbB", + 13 + ], + [ + "cBCBCbCa", + "bcCAaCCa", + 10 + ], + [ + "cBCBacA", + "AcbBaA", + 7 + ], + [ + "cBCBbCBa", + "c", + 14 + ], + [ + "cBCCABcC", + "Baa", + 13 + ], + [ + "cBCCB", + "a", + 10 + ], + [ + "cBCCBcaBa", + "AcAAba", + 13 + ], + [ + "cBCCC", + "A", + 10 + ], + [ + "cBCCcBc", + "ccaBCB", + 9 + ], + [ + "cBCaB", + "BBbAA", + 7 + ], + [ + "cBCab", + "aaABbC", + 10 + ], + [ + "cBCacA", + "abcbAbAa", + 11 + ], + [ + "cBCb", + "aCbCc", + 6 + ], + [ + "cBCb", + "b", + 6 + ], + [ + "cBCb", + "cAA", + 6 + ], + [ + "cBCbCAc", + "ACacAB", + 9 + ], + [ + "cBCbCca", + "aaB", + 13 + ], + [ + "cBCbabA", + "bbBbABB", + 9 + ], + [ + "cBCbbBBbc", + "aCaBBbbb", + 10 + ], + [ + "cBCbcCC", + "ACBbBBc", + 10 + ], + [ + "cBCc", + "AcaCBCb", + 8 + ], + [ + "cBCcAbcac", + "aBcac", + 10 + ], + [ + "cBCcbccaa", + "bBbCaBB", + 13 + ], + [ + "cBa", + "b", + 5 + ], + [ + "cBa", + "bAAaA", + 8 + ], + [ + "cBaAA", + "baaCbca", + 11 + ], + [ + "cBaAAB", + "BcbB", + 8 + ], + [ + "cBaAACacA", + "bBa", + 14 + ], + [ + "cBaABacb", + "ACb", + 11 + ], + [ + "cBaB", + "BabB", + 4 + ], + [ + "cBaB", + "CBBBC", + 5 + ], + [ + "cBaBBAB", + "B", + 12 + ], + [ + "cBaBBCc", + "BacCACa", + 10 + ], + [ + "cBaBaABBB", + "bcc", + 17 + ], + [ + "cBaBbCBC", + "cAAB", + 11 + ], + [ + "cBaBc", + "bbC", + 7 + ], + [ + "cBaC", + "BacB", + 5 + ], + [ + "cBaC", + "bA", + 6 + ], + [ + "cBaCA", + "CAcabbb", + 11 + ], + [ + "cBaCAbCC", + "B", + 14 + ], + [ + "cBaCAccbc", + "bbccAcCBB", + 10 + ], + [ + "cBaCCC", + "ab", + 10 + ], + [ + "cBaCaBC", + "Cb", + 11 + ], + [ + "cBaCab", + "BC", + 8 + ], + [ + "cBaa", + "BAab", + 5 + ], + [ + "cBaaA", + "Bb", + 8 + ], + [ + "cBaaCabBb", + "aCACcCbbb", + 11 + ], + [ + "cBaaaBBac", + "bcaAbBcC", + 10 + ], + [ + "cBaab", + "acAacAbCc", + 11 + ], + [ + "cBab", + "ACCAB", + 7 + ], + [ + "cBabB", + "C", + 9 + ], + [ + "cBabBCaca", + "C", + 16 + ], + [ + "cBabBcab", + "CbcAAAa", + 12 + ], + [ + "cBabCaA", + "aaAbAaac", + 10 + ], + [ + "cBabbBCBb", + "C", + 16 + ], + [ + "cBac", + "A", + 7 + ], + [ + "cBacCAB", + "B", + 12 + ], + [ + "cBacabC", + "aAAaB", + 10 + ], + [ + "cBacac", + "c", + 10 + ], + [ + "cBb", + "BbbA", + 5 + ], + [ + "cBb", + "CAbcC", + 7 + ], + [ + "cBb", + "acAB", + 5 + ], + [ + "cBbACBB", + "bCCCAbbC", + 12 + ], + [ + "cBbACC", + "ACa", + 8 + ], + [ + "cBbAcabB", + "aA", + 14 + ], + [ + "cBbC", + "abAabbbc", + 12 + ], + [ + "cBbCAACc", + "bBB", + 13 + ], + [ + "cBbCAB", + "cbCb", + 5 + ], + [ + "cBbCAa", + "cacaBBAb", + 11 + ], + [ + "cBbCAaA", + "AB", + 12 + ], + [ + "cBbCAb", + "bC", + 8 + ], + [ + "cBbCBAAA", + "AbBbc", + 12 + ], + [ + "cBbCCC", + "BCba", + 8 + ], + [ + "cBbCc", + "ccaCBA", + 8 + ], + [ + "cBbCccaC", + "bA", + 13 + ], + [ + "cBba", + "cb", + 4 + ], + [ + "cBbaACaa", + "cC", + 12 + ], + [ + "cBbaCbc", + "ccaaca", + 9 + ], + [ + "cBbaaac", + "b", + 12 + ], + [ + "cBbaab", + "BbbBcC", + 9 + ], + [ + "cBbaccBA", + "c", + 14 + ], + [ + "cBbb", + "CCcCaa", + 10 + ], + [ + "cBbb", + "abCb", + 5 + ], + [ + "cBbbA", + "A", + 8 + ], + [ + "cBbbA", + "B", + 8 + ], + [ + "cBbbBAAb", + "AAaa", + 13 + ], + [ + "cBbbBBacb", + "cCaCCcAac", + 14 + ], + [ + "cBbbBCaa", + "AaBc", + 13 + ], + [ + "cBbbCa", + "CB", + 9 + ], + [ + "cBbbcBB", + "bAACBBb", + 10 + ], + [ + "cBbcBaCBC", + "abA", + 15 + ], + [ + "cBbcCAC", + "b", + 12 + ], + [ + "cBbcCb", + "caBBbb", + 7 + ], + [ + "cBbcaBAAc", + "bB", + 14 + ], + [ + "cBbcbAAb", + "aba", + 13 + ], + [ + "cBbcbcBB", + "AccaA", + 12 + ], + [ + "cBbccCcc", + "aaCBabcBB", + 15 + ], + [ + "cBbccb", + "caaABcBb", + 9 + ], + [ + "cBc", + "BCaAAAbB", + 14 + ], + [ + "cBc", + "CAAC", + 6 + ], + [ + "cBc", + "aAc", + 4 + ], + [ + "cBc", + "aBaAAc", + 8 + ], + [ + "cBc", + "aabbAc", + 9 + ], + [ + "cBc", + "aac", + 4 + ], + [ + "cBc", + "c", + 4 + ], + [ + "cBcA", + "bC", + 6 + ], + [ + "cBcAAbbcb", + "BAbb", + 10 + ], + [ + "cBcACAA", + "a", + 13 + ], + [ + "cBcACb", + "Bc", + 8 + ], + [ + "cBcAaACbc", + "abbB", + 14 + ], + [ + "cBcBaaC", + "AbC", + 11 + ], + [ + "cBcBbCC", + "aBbCaba", + 11 + ], + [ + "cBcBbCbbA", + "cBa", + 13 + ], + [ + "cBcBbb", + "Acb", + 8 + ], + [ + "cBcCaaAcb", + "baccACCCc", + 13 + ], + [ + "cBcCbcCa", + "aCa", + 12 + ], + [ + "cBcCccaAa", + "BCaCCabbA", + 12 + ], + [ + "cBcaAaC", + "bACBCAB", + 12 + ], + [ + "cBcaAbBCb", + "baCbbaa", + 12 + ], + [ + "cBcaAbc", + "aa", + 11 + ], + [ + "cBcaAcaaA", + "CACbcC", + 14 + ], + [ + "cBcaBcBc", + "aBbBCACc", + 11 + ], + [ + "cBcaCcCA", + "Bcc", + 10 + ], + [ + "cBcabC", + "bAAAabb", + 10 + ], + [ + "cBcbACbBB", + "ACBc", + 12 + ], + [ + "cBcbBAbAC", + "A", + 16 + ], + [ + "cBcbC", + "AAbBcCa", + 10 + ], + [ + "cBcbcaCb", + "cCbcCB", + 6 + ], + [ + "cBcc", + "bAcAcaaba", + 14 + ], + [ + "cBcc", + "cacc", + 2 + ], + [ + "cBccaAac", + "aab", + 12 + ], + [ + "cBccaBCB", + "Caa", + 13 + ], + [ + "cBccacbCA", + "bBCBAca", + 11 + ], + [ + "cBccbc", + "CAbA", + 9 + ], + [ + "cBcccCAC", + "BA", + 12 + ], + [ + "cC", + "A", + 4 + ], + [ + "cC", + "ABCCCa", + 9 + ], + [ + "cC", + "AbAacCAc", + 12 + ], + [ + "cC", + "AccAAABc", + 13 + ], + [ + "cC", + "BBAabcC", + 10 + ], + [ + "cC", + "BBc", + 5 + ], + [ + "cC", + "BBcaACaA", + 12 + ], + [ + "cC", + "BCABBCAaC", + 15 + ], + [ + "cC", + "BaAbcaBc", + 13 + ], + [ + "cC", + "BaBaccb", + 11 + ], + [ + "cC", + "CBA", + 5 + ], + [ + "cC", + "CacCac", + 8 + ], + [ + "cC", + "CcACcacbB", + 14 + ], + [ + "cC", + "CcBbb", + 8 + ], + [ + "cC", + "aACbbCAB", + 13 + ], + [ + "cC", + "aBABCBbc", + 14 + ], + [ + "cC", + "aCCcAAca", + 13 + ], + [ + "cC", + "aaBaBbAA", + 16 + ], + [ + "cC", + "acAb", + 6 + ], + [ + "cC", + "b", + 4 + ], + [ + "cC", + "bA", + 4 + ], + [ + "cC", + "bACBaA", + 10 + ], + [ + "cC", + "bacAcbc", + 11 + ], + [ + "cC", + "bcaCBaB", + 10 + ], + [ + "cC", + "c", + 2 + ], + [ + "cC", + "cA", + 2 + ], + [ + "cC", + "cABAc", + 7 + ], + [ + "cC", + "cAbc", + 5 + ], + [ + "cC", + "cBA", + 4 + ], + [ + "cC", + "cBCAccaCb", + 14 + ], + [ + "cC", + "cabcBCB", + 10 + ], + [ + "cC", + "cccaBa", + 9 + ], + [ + "cCA", + "c", + 4 + ], + [ + "cCAABb", + "ACbaBC", + 7 + ], + [ + "cCAACb", + "bbC", + 10 + ], + [ + "cCAAacBb", + "a", + 14 + ], + [ + "cCAAc", + "ABbCcbBb", + 14 + ], + [ + "cCAAc", + "AC", + 7 + ], + [ + "cCAB", + "AAaCAABab", + 12 + ], + [ + "cCABbAb", + "Ab", + 10 + ], + [ + "cCABbaBaA", + "aacC", + 15 + ], + [ + "cCAC", + "bCbbbbAC", + 10 + ], + [ + "cCACAb", + "AbC", + 10 + ], + [ + "cCACCc", + "cccbAabc", + 9 + ], + [ + "cCACc", + "BbAcaB", + 9 + ], + [ + "cCAa", + "CBA", + 5 + ], + [ + "cCAaabBbA", + "bCCCcacb", + 13 + ], + [ + "cCAac", + "BBA", + 8 + ], + [ + "cCAacBCc", + "CCBBaA", + 11 + ], + [ + "cCAacCCbA", + "bCACCa", + 9 + ], + [ + "cCAbA", + "aBBcaccbA", + 11 + ], + [ + "cCAbABC", + "bbACCaACB", + 13 + ], + [ + "cCAbACcc", + "AbBAccbb", + 11 + ], + [ + "cCAbCacAA", + "BcABBAC", + 12 + ], + [ + "cCAbbaA", + "CcACabA", + 8 + ], + [ + "cCAbcA", + "BABABC", + 10 + ], + [ + "cCAcAcbaB", + "BccBaCC", + 13 + ], + [ + "cCAcCAB", + "bcccCbbab", + 11 + ], + [ + "cCAcaabBA", + "bBcbCCBa", + 13 + ], + [ + "cCAcbBBa", + "AaBca", + 10 + ], + [ + "cCAcbcB", + "c", + 12 + ], + [ + "cCB", + "BBCAbba", + 11 + ], + [ + "cCB", + "BCcab", + 7 + ], + [ + "cCB", + "CC", + 3 + ], + [ + "cCB", + "CbBa", + 5 + ], + [ + "cCB", + "a", + 6 + ], + [ + "cCB", + "aC", + 4 + ], + [ + "cCBA", + "abcA", + 6 + ], + [ + "cCBACb", + "abBccaC", + 11 + ], + [ + "cCBAbCCa", + "a", + 14 + ], + [ + "cCBAbaC", + "aAbaAa", + 10 + ], + [ + "cCBAcCB", + "CbaCB", + 6 + ], + [ + "cCBBAb", + "cBcaC", + 7 + ], + [ + "cCBBBc", + "Cc", + 8 + ], + [ + "cCBBaa", + "CBC", + 8 + ], + [ + "cCBCBAc", + "ABCBCBbCB", + 9 + ], + [ + "cCBCa", + "AcacaBCAc", + 10 + ], + [ + "cCBCbB", + "Cc", + 9 + ], + [ + "cCBCcbB", + "cCBaAaBc", + 8 + ], + [ + "cCBaAac", + "C", + 12 + ], + [ + "cCBaCc", + "CcbBBbB", + 10 + ], + [ + "cCBac", + "bb", + 9 + ], + [ + "cCBbAC", + "abAbC", + 8 + ], + [ + "cCBbAaabb", + "BCc", + 16 + ], + [ + "cCBbAcCAC", + "acBCCbA", + 12 + ], + [ + "cCBbAccB", + "abA", + 12 + ], + [ + "cCBbbB", + "bbaaCCBbB", + 11 + ], + [ + "cCBc", + "ABbacCc", + 10 + ], + [ + "cCBc", + "cAcBb", + 5 + ], + [ + "cCBcABc", + "ac", + 11 + ], + [ + "cCBcACBa", + "cACcB", + 10 + ], + [ + "cCBcACCa", + "BACAAaa", + 11 + ], + [ + "cCBca", + "cbCacC", + 6 + ], + [ + "cCBcb", + "BCbAABAba", + 12 + ], + [ + "cCBcbCaCb", + "cbBCa", + 10 + ], + [ + "cCC", + "BbacaAb", + 12 + ], + [ + "cCC", + "CB", + 4 + ], + [ + "cCC", + "accaCCCcb", + 12 + ], + [ + "cCC", + "bAbcCcbcb", + 13 + ], + [ + "cCC", + "bB", + 6 + ], + [ + "cCC", + "bCBbABBCc", + 14 + ], + [ + "cCC", + "baAAb", + 10 + ], + [ + "cCC", + "cABBc", + 7 + ], + [ + "cCC", + "cCCbaac", + 8 + ], + [ + "cCCAbbbaa", + "BccccA", + 15 + ], + [ + "cCCAc", + "ccAbbb", + 9 + ], + [ + "cCCBBaBC", + "bcaA", + 13 + ], + [ + "cCCBCAaA", + "baB", + 13 + ], + [ + "cCCBc", + "bAccaAcB", + 11 + ], + [ + "cCCCC", + "cBC", + 6 + ], + [ + "cCCCaA", + "cCbB", + 8 + ], + [ + "cCCCaac", + "BCAcCb", + 11 + ], + [ + "cCCCbA", + "CcBBBBb", + 11 + ], + [ + "cCCCbb", + "ABccCcA", + 10 + ], + [ + "cCCCbbbAa", + "cA", + 14 + ], + [ + "cCCa", + "AccBc", + 7 + ], + [ + "cCCaA", + "BcACCA", + 6 + ], + [ + "cCCaAAAC", + "aBbcC", + 12 + ], + [ + "cCCaCB", + "CaaAcbCAB", + 12 + ], + [ + "cCCaccA", + "bc", + 12 + ], + [ + "cCCaccAC", + "abAACcCa", + 12 + ], + [ + "cCCb", + "C", + 6 + ], + [ + "cCCb", + "cAcBa", + 6 + ], + [ + "cCCbBCBab", + "CCcaca", + 11 + ], + [ + "cCCbb", + "BCBbbcA", + 8 + ], + [ + "cCCbc", + "bCCC", + 5 + ], + [ + "cCCbcbabA", + "aaCCc", + 14 + ], + [ + "cCCcABBB", + "aBBbABcb", + 11 + ], + [ + "cCCcCCcAa", + "bBCab", + 15 + ], + [ + "cCCcaaC", + "aBC", + 10 + ], + [ + "cCa", + "BacAaCAcA", + 13 + ], + [ + "cCa", + "C", + 4 + ], + [ + "cCa", + "CAACAAaC", + 11 + ], + [ + "cCa", + "aBBcbc", + 10 + ], + [ + "cCa", + "aCBab", + 6 + ], + [ + "cCa", + "aaCCAAAb", + 12 + ], + [ + "cCa", + "bBAcCcB", + 10 + ], + [ + "cCa", + "cBBcaCB", + 9 + ], + [ + "cCaA", + "CCaca", + 4 + ], + [ + "cCaA", + "cBcccC", + 9 + ], + [ + "cCaAAACA", + "cBBbacc", + 12 + ], + [ + "cCaAAacCc", + "cbCaBcA", + 12 + ], + [ + "cCaABCAAA", + "A", + 16 + ], + [ + "cCaAb", + "aBaAB", + 5 + ], + [ + "cCaAbAABA", + "CCabcAAbb", + 8 + ], + [ + "cCaAc", + "ABAbbBBB", + 15 + ], + [ + "cCaAcACaa", + "caaBcBc", + 11 + ], + [ + "cCaAcCBca", + "ABcc", + 12 + ], + [ + "cCaAcbA", + "aBCBcCCba", + 12 + ], + [ + "cCaB", + "ABABAbBBc", + 15 + ], + [ + "cCaBBaAb", + "aA", + 12 + ], + [ + "cCaBBaCAC", + "bC", + 15 + ], + [ + "cCaBBba", + "aa", + 10 + ], + [ + "cCaBCbb", + "BcaCB", + 8 + ], + [ + "cCaBb", + "Ac", + 9 + ], + [ + "cCaC", + "AC", + 5 + ], + [ + "cCaCA", + "BBBbbCAC", + 12 + ], + [ + "cCaCBBBCb", + "B", + 16 + ], + [ + "cCaCCB", + "BcBac", + 9 + ], + [ + "cCaCaB", + "AbcA", + 10 + ], + [ + "cCaCaaaA", + "abcCcc", + 14 + ], + [ + "cCaCc", + "aA", + 8 + ], + [ + "cCaa", + "CcCbcbA", + 9 + ], + [ + "cCaaBCb", + "A", + 13 + ], + [ + "cCaab", + "a", + 8 + ], + [ + "cCabB", + "AbbA", + 8 + ], + [ + "cCabB", + "AcaaACCb", + 11 + ], + [ + "cCabC", + "aBcB", + 8 + ], + [ + "cCabaBC", + "aB", + 10 + ], + [ + "cCabc", + "cabcccAB", + 10 + ], + [ + "cCabcBa", + "abccAaAB", + 12 + ], + [ + "cCac", + "AacBAaA", + 10 + ], + [ + "cCacacaa", + "CACbAbcC", + 13 + ], + [ + "cCacba", + "aAbAbBAc", + 13 + ], + [ + "cCb", + "AccbcbcB", + 11 + ], + [ + "cCb", + "BaC", + 6 + ], + [ + "cCb", + "BabCcAA", + 12 + ], + [ + "cCb", + "aCaBbBbC", + 12 + ], + [ + "cCb", + "accCcBAbA", + 12 + ], + [ + "cCb", + "cC", + 2 + ], + [ + "cCbAB", + "bcaac", + 8 + ], + [ + "cCbAB", + "cca", + 6 + ], + [ + "cCbAc", + "baB", + 7 + ], + [ + "cCbAcaB", + "AaBB", + 10 + ], + [ + "cCbBAc", + "bbBBBCa", + 10 + ], + [ + "cCbBBAbC", + "BCa", + 13 + ], + [ + "cCbBa", + "AAcCCaAB", + 11 + ], + [ + "cCbBacc", + "aBAbCAc", + 11 + ], + [ + "cCbBb", + "aacaBa", + 9 + ], + [ + "cCbBb", + "baAAacaCA", + 17 + ], + [ + "cCbBbc", + "acaAbC", + 8 + ], + [ + "cCbC", + "B", + 7 + ], + [ + "cCbC", + "CcBaA", + 7 + ], + [ + "cCbC", + "aC", + 6 + ], + [ + "cCbCCbCaA", + "acBBbCcC", + 12 + ], + [ + "cCbCaCBa", + "aBBAAbaA", + 13 + ], + [ + "cCbCac", + "CBAB", + 8 + ], + [ + "cCbCcaCc", + "cAbbAab", + 10 + ], + [ + "cCbCcbcbA", + "cbb", + 12 + ], + [ + "cCbaACaB", + "AB", + 12 + ], + [ + "cCbaAcaa", + "BCcCcb", + 12 + ], + [ + "cCbaB", + "aCCbc", + 7 + ], + [ + "cCbbAaaba", + "CccBCA", + 14 + ], + [ + "cCbbAbBC", + "acaAcBC", + 9 + ], + [ + "cCbbBC", + "bca", + 10 + ], + [ + "cCbbCB", + "AaBb", + 9 + ], + [ + "cCbbCBc", + "aBBAbba", + 12 + ], + [ + "cCbbCaA", + "A", + 12 + ], + [ + "cCbbabAC", + "B", + 15 + ], + [ + "cCbbccCb", + "bAcCc", + 10 + ], + [ + "cCbcABAa", + "ABaACa", + 11 + ], + [ + "cCbcB", + "bBBcbbA", + 10 + ], + [ + "cCbcbc", + "Aa", + 12 + ], + [ + "cCbccbB", + "abaaBc", + 11 + ], + [ + "cCbccbbaC", + "aCBAcBB", + 11 + ], + [ + "cCc", + "ABcAcB", + 8 + ], + [ + "cCc", + "BbaaC", + 9 + ], + [ + "cCc", + "CAbAabcAc", + 14 + ], + [ + "cCc", + "CccC", + 4 + ], + [ + "cCcA", + "CBaCbc", + 9 + ], + [ + "cCcAAC", + "cCbaCa", + 7 + ], + [ + "cCcAAbbcB", + "A", + 16 + ], + [ + "cCcAB", + "BCCa", + 6 + ], + [ + "cCcACAaCB", + "cccb", + 12 + ], + [ + "cCcAbbBbc", + "CBbaAb", + 12 + ], + [ + "cCcAcAa", + "c", + 12 + ], + [ + "cCcBC", + "CBBAbBAcC", + 13 + ], + [ + "cCcBCcbcb", + "C", + 16 + ], + [ + "cCcC", + "aAcbBCb", + 10 + ], + [ + "cCcCAb", + "bAaCcaCCB", + 11 + ], + [ + "cCcCaCaCB", + "AC", + 15 + ], + [ + "cCcCcB", + "b", + 11 + ], + [ + "cCcaAbCC", + "cAAaCC", + 7 + ], + [ + "cCcaBA", + "C", + 10 + ], + [ + "cCcaBBB", + "BCaCCcB", + 10 + ], + [ + "cCcaC", + "CCAcbCcCc", + 11 + ], + [ + "cCcaCa", + "AAcb", + 10 + ], + [ + "cCcaabba", + "A", + 15 + ], + [ + "cCcabc", + "bc", + 8 + ], + [ + "cCcb", + "BBaAbbaAb", + 16 + ], + [ + "cCcbAbcb", + "AcCbcAAA", + 12 + ], + [ + "cCcbBCCBC", + "cC", + 14 + ], + [ + "cCcbCCB", + "BAbABC", + 12 + ], + [ + "cCcbCaaC", + "cbbcccbca", + 12 + ], + [ + "cCcbaCca", + "baBCABcC", + 13 + ], + [ + "cCcbaba", + "cCBBA", + 7 + ], + [ + "cCcc", + "a", + 8 + ], + [ + "cCccBaa", + "CBcBA", + 7 + ], + [ + "cCccCaCbA", + "caABbAcB", + 13 + ], + [ + "cCccaA", + "abb", + 12 + ], + [ + "cCccbA", + "BBabb", + 10 + ], + [ + "cCccbB", + "abAcB", + 8 + ], + [ + "cCcccAAb", + "aa", + 14 + ], + [ + "cCcccC", + "CBcBaAAc", + 12 + ], + [ + "cCcccCA", + "bcCACb", + 9 + ], + [ + "ca", + "AC", + 4 + ], + [ + "ca", + "ACA", + 4 + ], + [ + "ca", + "ACACb", + 8 + ], + [ + "ca", + "ACa", + 3 + ], + [ + "ca", + "Aaaa", + 6 + ], + [ + "ca", + "BAacabbab", + 14 + ], + [ + "ca", + "BB", + 4 + ], + [ + "ca", + "BBcaAbBcb", + 14 + ], + [ + "ca", + "BacCBC", + 10 + ], + [ + "ca", + "BbA", + 5 + ], + [ + "ca", + "BbBbBC", + 12 + ], + [ + "ca", + "BbCACC", + 10 + ], + [ + "ca", + "Bcbc", + 6 + ], + [ + "ca", + "BccA", + 5 + ], + [ + "ca", + "C", + 3 + ], + [ + "ca", + "CA", + 2 + ], + [ + "ca", + "CAC", + 4 + ], + [ + "ca", + "CCcbBaaab", + 14 + ], + [ + "ca", + "CbCaAaB", + 11 + ], + [ + "ca", + "a", + 2 + ], + [ + "ca", + "aBAA", + 7 + ], + [ + "ca", + "aC", + 4 + ], + [ + "ca", + "aaAbcACCc", + 15 + ], + [ + "ca", + "acccCcBc", + 14 + ], + [ + "ca", + "bAAaCCAc", + 14 + ], + [ + "ca", + "bBcAABbbB", + 15 + ], + [ + "ca", + "bbb", + 6 + ], + [ + "ca", + "cBC", + 4 + ], + [ + "ca", + "cCbba", + 6 + ], + [ + "ca", + "ccBBaba", + 10 + ], + [ + "caA", + "B", + 6 + ], + [ + "caA", + "CcCcAA", + 7 + ], + [ + "caA", + "aCAbCCCA", + 12 + ], + [ + "caA", + "aCcCbbAAC", + 13 + ], + [ + "caA", + "bBbac", + 8 + ], + [ + "caA", + "bCACCACC", + 12 + ], + [ + "caA", + "bbaA", + 4 + ], + [ + "caA", + "bbbBA", + 8 + ], + [ + "caA", + "cbAacAbbc", + 12 + ], + [ + "caAA", + "bAbaa", + 7 + ], + [ + "caAAAB", + "ab", + 9 + ], + [ + "caAAAcaA", + "CacaAcA", + 6 + ], + [ + "caAACbc", + "CBBCABbbb", + 13 + ], + [ + "caAB", + "A", + 6 + ], + [ + "caAB", + "BCAcbbB", + 10 + ], + [ + "caAB", + "CAaAab", + 6 + ], + [ + "caABabc", + "ACbbCCC", + 12 + ], + [ + "caABb", + "AaCaBC", + 7 + ], + [ + "caAC", + "CbCcB", + 8 + ], + [ + "caAC", + "bBBBCCBA", + 14 + ], + [ + "caACABAA", + "aa", + 13 + ], + [ + "caACBa", + "BbbBcAc", + 13 + ], + [ + "caACCBAaa", + "bbAcA", + 13 + ], + [ + "caACCac", + "a", + 12 + ], + [ + "caAa", + "abBcaB", + 10 + ], + [ + "caAa", + "acaCcBc", + 10 + ], + [ + "caAaBaBc", + "bcbCa", + 14 + ], + [ + "caAaa", + "ac", + 8 + ], + [ + "caAaaaaa", + "ccc", + 14 + ], + [ + "caAac", + "ACcACAbc", + 9 + ], + [ + "caAbAAcBb", + "c", + 16 + ], + [ + "caAbAcACB", + "CBCCaAaaB", + 13 + ], + [ + "caAbBBAbC", + "bacC", + 13 + ], + [ + "caAbBCb", + "bbaaaCc", + 11 + ], + [ + "caAbbBa", + "bAC", + 12 + ], + [ + "caAbbb", + "bbCACabaC", + 13 + ], + [ + "caAc", + "baCBB", + 8 + ], + [ + "caAcCc", + "ACc", + 6 + ], + [ + "caAcbbaCB", + "cbcBc", + 12 + ], + [ + "caAcc", + "BBbabc", + 9 + ], + [ + "caAcccaAb", + "Acab", + 10 + ], + [ + "caB", + "CbAcacAa", + 12 + ], + [ + "caB", + "ccBA", + 4 + ], + [ + "caBAA", + "aCbc", + 8 + ], + [ + "caBABbbC", + "CB", + 13 + ], + [ + "caBAabAcC", + "aCA", + 14 + ], + [ + "caBAbAAc", + "bcCBBa", + 12 + ], + [ + "caBAbaAb", + "ba", + 12 + ], + [ + "caBAcB", + "BaaaBA", + 9 + ], + [ + "caBBA", + "Bc", + 8 + ], + [ + "caBBA", + "CacB", + 5 + ], + [ + "caBBaBAB", + "acAbaa", + 11 + ], + [ + "caBBbaaA", + "bB", + 13 + ], + [ + "caBC", + "CBaaBaAa", + 11 + ], + [ + "caBCBaBC", + "aABaB", + 8 + ], + [ + "caBCac", + "b", + 11 + ], + [ + "caBCc", + "CBCabB", + 9 + ], + [ + "caBa", + "CaBabAC", + 7 + ], + [ + "caBa", + "CcbccA", + 9 + ], + [ + "caBaB", + "BcA", + 8 + ], + [ + "caBaCAb", + "AacCAA", + 8 + ], + [ + "caBaCc", + "cCCA", + 8 + ], + [ + "caBb", + "AaCbCAAa", + 12 + ], + [ + "caBb", + "BCBC", + 6 + ], + [ + "caBb", + "b", + 6 + ], + [ + "caBbCaa", + "caCCbb", + 8 + ], + [ + "caBbCbbBB", + "aBCBCabca", + 11 + ], + [ + "caBbCcBC", + "cccBaB", + 11 + ], + [ + "caBbac", + "bac", + 6 + ], + [ + "caBbbB", + "BcBBA", + 9 + ], + [ + "caBbbcBa", + "b", + 14 + ], + [ + "caBc", + "bA", + 7 + ], + [ + "caBc", + "bBA", + 6 + ], + [ + "caBc", + "cc", + 4 + ], + [ + "caBcAA", + "ccacac", + 7 + ], + [ + "caBcacCb", + "bBc", + 12 + ], + [ + "caC", + "BCbb", + 7 + ], + [ + "caC", + "BbBcA", + 9 + ], + [ + "caC", + "babCacB", + 10 + ], + [ + "caC", + "bb", + 6 + ], + [ + "caC", + "cCbAaa", + 8 + ], + [ + "caCAB", + "CBA", + 7 + ], + [ + "caCAC", + "AcaaCaacc", + 10 + ], + [ + "caCAb", + "BaC", + 6 + ], + [ + "caCAccca", + "bACc", + 11 + ], + [ + "caCBA", + "aBBBBcA", + 10 + ], + [ + "caCBC", + "BcBBabA", + 11 + ], + [ + "caCBC", + "ac", + 7 + ], + [ + "caCBba", + "cBA", + 7 + ], + [ + "caCBbaBB", + "a", + 14 + ], + [ + "caCBbc", + "A", + 11 + ], + [ + "caCC", + "CaBC", + 3 + ], + [ + "caCCAa", + "Baabb", + 10 + ], + [ + "caCCAcaa", + "aC", + 12 + ], + [ + "caCCC", + "bCBaAAbC", + 11 + ], + [ + "caCCaa", + "acabCacA", + 7 + ], + [ + "caCCbCc", + "aCBcC", + 7 + ], + [ + "caCCcc", + "cbaCAacc", + 6 + ], + [ + "caCaBB", + "ccbBaBA", + 8 + ], + [ + "caCaBbcAA", + "baCcAB", + 10 + ], + [ + "caCaCAaaC", + "ABB", + 16 + ], + [ + "caCabcBa", + "CAcCABcc", + 10 + ], + [ + "caCb", + "BcAcABAB", + 11 + ], + [ + "caCb", + "bAbBCb", + 7 + ], + [ + "caCbAAcb", + "aaBbAB", + 9 + ], + [ + "caCbCBBb", + "A", + 15 + ], + [ + "caCbbC", + "cCcc", + 7 + ], + [ + "caCbbcbC", + "CBAAbCC", + 10 + ], + [ + "caCbc", + "B", + 9 + ], + [ + "caCcCaaB", + "AacCbcca", + 11 + ], + [ + "caa", + "CCAcAcaab", + 12 + ], + [ + "caa", + "b", + 6 + ], + [ + "caa", + "bAB", + 5 + ], + [ + "caa", + "bacAa", + 5 + ], + [ + "caaA", + "BaCbcC", + 10 + ], + [ + "caaAABaac", + "AC", + 15 + ], + [ + "caaACA", + "cB", + 10 + ], + [ + "caaAa", + "CaBaAbcB", + 9 + ], + [ + "caaAa", + "bbC", + 10 + ], + [ + "caaAbbc", + "Aa", + 11 + ], + [ + "caaB", + "bAc", + 7 + ], + [ + "caaB", + "cB", + 4 + ], + [ + "caaBA", + "C", + 9 + ], + [ + "caaBAabC", + "BACB", + 11 + ], + [ + "caaBCC", + "Ab", + 10 + ], + [ + "caaBCc", + "CCb", + 9 + ], + [ + "caaCCAa", + "bCccbA", + 11 + ], + [ + "caaCaBcBb", + "aCabaa", + 11 + ], + [ + "caaCbaB", + "CCc", + 11 + ], + [ + "caaCc", + "b", + 10 + ], + [ + "caaCcc", + "cbA", + 9 + ], + [ + "caaa", + "AcCc", + 8 + ], + [ + "caaa", + "bCabaCaa", + 9 + ], + [ + "caaaCb", + "AabC", + 7 + ], + [ + "caaaaC", + "CaaBCCaC", + 7 + ], + [ + "caaab", + "BCC", + 10 + ], + [ + "caaac", + "AaBbACcBa", + 13 + ], + [ + "caab", + "C", + 7 + ], + [ + "caabACbb", + "bb", + 12 + ], + [ + "caabB", + "AbaCbac", + 10 + ], + [ + "caabaABA", + "cbacccaA", + 10 + ], + [ + "caabcbcBB", + "abaAAaC", + 14 + ], + [ + "caac", + "ABb", + 7 + ], + [ + "caacAABaB", + "CbBcbaB", + 10 + ], + [ + "caacaccC", + "BAaccaBA", + 11 + ], + [ + "caacbaAc", + "Abccbc", + 10 + ], + [ + "cab", + "CB", + 4 + ], + [ + "cab", + "a", + 4 + ], + [ + "cab", + "abCCb", + 7 + ], + [ + "cab", + "bAaaBBA", + 11 + ], + [ + "cab", + "bBbCABC", + 11 + ], + [ + "cabAA", + "c", + 8 + ], + [ + "cabACCCCb", + "b", + 16 + ], + [ + "cabACc", + "bBBAb", + 9 + ], + [ + "cabBAaaB", + "BbCCBaBc", + 12 + ], + [ + "cabBC", + "A", + 9 + ], + [ + "cabBCa", + "C", + 10 + ], + [ + "cabBCcBbA", + "BbCbAAaAb", + 16 + ], + [ + "cabBaA", + "cCa", + 8 + ], + [ + "cabC", + "BBCabBCa", + 9 + ], + [ + "cabC", + "BCBb", + 7 + ], + [ + "cabC", + "Baccb", + 7 + ], + [ + "cabCCAcC", + "cCcA", + 9 + ], + [ + "cabCCaBca", + "BCbBc", + 11 + ], + [ + "cabCaba", + "a", + 12 + ], + [ + "cabCb", + "cB", + 7 + ], + [ + "cabCcbB", + "BBbAcBaac", + 13 + ], + [ + "cabaBBb", + "AbC", + 11 + ], + [ + "cabaa", + "ccBCAbAB", + 10 + ], + [ + "cababBBaB", + "AcBCAC", + 14 + ], + [ + "cabac", + "BCa", + 8 + ], + [ + "cabb", + "CACcCcac", + 14 + ], + [ + "cabb", + "a", + 6 + ], + [ + "cabbBBA", + "CCab", + 11 + ], + [ + "cabbBc", + "aCccAAcc", + 12 + ], + [ + "cabbbACA", + "bcaCC", + 11 + ], + [ + "cabc", + "BCBcBca", + 10 + ], + [ + "cabc", + "CaaBBAABA", + 14 + ], + [ + "cabcBCc", + "BABAC", + 10 + ], + [ + "cabca", + "aAaC", + 8 + ], + [ + "cac", + "BBbaabba", + 14 + ], + [ + "cac", + "bb", + 6 + ], + [ + "cac", + "bcBa", + 6 + ], + [ + "cac", + "cBaaBa", + 8 + ], + [ + "cacABBaB", + "BaBAabBbA", + 11 + ], + [ + "cacAaA", + "bc", + 10 + ], + [ + "cacAaaaBb", + "Aba", + 14 + ], + [ + "cacAbbcCB", + "AcCcAaBC", + 11 + ], + [ + "cacAc", + "cAcaAB", + 5 + ], + [ + "cacB", + "BCcbAAcbB", + 11 + ], + [ + "cacB", + "c", + 6 + ], + [ + "cacBAA", + "BbbCCab", + 12 + ], + [ + "cacBAc", + "bbaBB", + 10 + ], + [ + "cacBaAa", + "aBBBCbbbA", + 15 + ], + [ + "cacBab", + "AC", + 10 + ], + [ + "cacCCaB", + "ABCc", + 10 + ], + [ + "caca", + "bCbbAa", + 9 + ], + [ + "cacaAbAAA", + "bc", + 16 + ], + [ + "cacaAcc", + "bc", + 12 + ], + [ + "cacaBc", + "BaabCbbB", + 12 + ], + [ + "cacaaBc", + "caAacCAAa", + 11 + ], + [ + "cacaabBCa", + "AACaccAc", + 13 + ], + [ + "cacacBAbC", + "C", + 16 + ], + [ + "cacaca", + "aB", + 10 + ], + [ + "cacbAAaCa", + "b", + 16 + ], + [ + "cacbB", + "AbBaCB", + 9 + ], + [ + "cacbCcbcB", + "cc", + 14 + ], + [ + "cacbc", + "BbAACAAC", + 13 + ], + [ + "cacc", + "CAccbBacC", + 11 + ], + [ + "caccA", + "ccc", + 4 + ], + [ + "caccbc", + "ab", + 8 + ], + [ + "cb", + "AAaBCAa", + 13 + ], + [ + "cb", + "AAaC", + 8 + ], + [ + "cb", + "ABAcbc", + 8 + ], + [ + "cb", + "ABccA", + 8 + ], + [ + "cb", + "ACABcb", + 8 + ], + [ + "cb", + "Aa", + 4 + ], + [ + "cb", + "AbBAaB", + 10 + ], + [ + "cb", + "BCcbbACB", + 12 + ], + [ + "cb", + "BabCaAAB", + 14 + ], + [ + "cb", + "BbBCACCBb", + 15 + ], + [ + "cb", + "C", + 3 + ], + [ + "cb", + "CAAbCCB", + 11 + ], + [ + "cb", + "CABBCa", + 10 + ], + [ + "cb", + "CCACAAB", + 12 + ], + [ + "cb", + "aBcA", + 6 + ], + [ + "cb", + "aCBAaabaa", + 15 + ], + [ + "cb", + "aCBaacAac", + 16 + ], + [ + "cb", + "aCC", + 5 + ], + [ + "cb", + "aaBcCC", + 10 + ], + [ + "cb", + "acBAC", + 7 + ], + [ + "cb", + "bCBcaCbA", + 12 + ], + [ + "cb", + "bcCCC", + 8 + ], + [ + "cb", + "cBCaBCC", + 11 + ], + [ + "cb", + "cCabBBabA", + 14 + ], + [ + "cb", + "cb", + 0 + ], + [ + "cb", + "cccCa", + 8 + ], + [ + "cbA", + "AaAaB", + 8 + ], + [ + "cbA", + "AcACCa", + 9 + ], + [ + "cbA", + "BbCBABAA", + 12 + ], + [ + "cbA", + "aBcCCAAca", + 14 + ], + [ + "cbA", + "aaab", + 7 + ], + [ + "cbAA", + "BAbBc", + 8 + ], + [ + "cbAAaBcc", + "cABABCbb", + 10 + ], + [ + "cbAAcB", + "a", + 11 + ], + [ + "cbAAcC", + "cacC", + 5 + ], + [ + "cbAB", + "CAABa", + 5 + ], + [ + "cbABBb", + "acBA", + 9 + ], + [ + "cbABc", + "cbcAaCb", + 7 + ], + [ + "cbAC", + "C", + 6 + ], + [ + "cbACAbc", + "aABCAbA", + 8 + ], + [ + "cbACcAC", + "bbcAcbB", + 10 + ], + [ + "cbACcBBC", + "cABbcbbaB", + 12 + ], + [ + "cbACcb", + "cabbaAC", + 10 + ], + [ + "cbAaB", + "babCC", + 9 + ], + [ + "cbAaBc", + "Acc", + 8 + ], + [ + "cbAaBcCA", + "C", + 14 + ], + [ + "cbAaCBbB", + "A", + 14 + ], + [ + "cbAabcC", + "BBBCAc", + 11 + ], + [ + "cbAbAaBbc", + "bC", + 15 + ], + [ + "cbAbBBAA", + "bcaBbAc", + 9 + ], + [ + "cbAbCBA", + "acABcaC", + 10 + ], + [ + "cbAbCBBB", + "AAaaa", + 14 + ], + [ + "cbAbCaA", + "cbAcBB", + 7 + ], + [ + "cbAbaCc", + "BACabcaa", + 11 + ], + [ + "cbAbcCA", + "ccC", + 8 + ], + [ + "cbAbcaB", + "cc", + 10 + ], + [ + "cbAbcbAbB", + "AccBbbcA", + 13 + ], + [ + "cbAbcbb", + "caCaAB", + 10 + ], + [ + "cbAc", + "BABcCB", + 9 + ], + [ + "cbAc", + "bacb", + 5 + ], + [ + "cbAcCBb", + "ABac", + 10 + ], + [ + "cbAcbcccB", + "CCC", + 15 + ], + [ + "cbB", + "Aaa", + 6 + ], + [ + "cbB", + "CbC", + 3 + ], + [ + "cbB", + "caCaB", + 6 + ], + [ + "cbBA", + "BcBBccBA", + 9 + ], + [ + "cbBA", + "aCcbB", + 6 + ], + [ + "cbBAAc", + "bcABA", + 8 + ], + [ + "cbBAB", + "AbcBc", + 8 + ], + [ + "cbBABc", + "Cbba", + 7 + ], + [ + "cbBACACa", + "aA", + 13 + ], + [ + "cbBACBccA", + "Ca", + 15 + ], + [ + "cbBAba", + "AcAca", + 8 + ], + [ + "cbBAbb", + "CbaaC", + 8 + ], + [ + "cbBB", + "cAb", + 5 + ], + [ + "cbBB", + "cAba", + 5 + ], + [ + "cbBBbB", + "BBBBcaA", + 9 + ], + [ + "cbBBbca", + "cACABCb", + 10 + ], + [ + "cbBBcACBb", + "AACbBbbb", + 13 + ], + [ + "cbBCCaA", + "AccCcBc", + 11 + ], + [ + "cbBCacA", + "cB", + 10 + ], + [ + "cbBCb", + "CAAbBA", + 9 + ], + [ + "cbBa", + "aaCC", + 8 + ], + [ + "cbBa", + "cBaB", + 4 + ], + [ + "cbBa", + "cccB", + 6 + ], + [ + "cbBaBcAc", + "Aab", + 13 + ], + [ + "cbBaCAC", + "CCcCCc", + 10 + ], + [ + "cbBac", + "c", + 8 + ], + [ + "cbBacA", + "bCaaaBaa", + 12 + ], + [ + "cbBb", + "AABA", + 6 + ], + [ + "cbBb", + "CCaa", + 7 + ], + [ + "cbBbAbB", + "cC", + 12 + ], + [ + "cbBbCACCC", + "Ccc", + 14 + ], + [ + "cbBba", + "ccb", + 6 + ], + [ + "cbBbbb", + "BccCbaAAB", + 13 + ], + [ + "cbBc", + "A", + 8 + ], + [ + "cbBcCAa", + "acab", + 11 + ], + [ + "cbBcCCbAA", + "caAbaCaBc", + 14 + ], + [ + "cbBcaCB", + "bA", + 11 + ], + [ + "cbBcbaccC", + "CaAACBAAA", + 16 + ], + [ + "cbBcbc", + "ABBBAAc", + 9 + ], + [ + "cbBcc", + "CACaBC", + 10 + ], + [ + "cbC", + "AcABa", + 7 + ], + [ + "cbC", + "bCCBBa", + 10 + ], + [ + "cbCAbaAa", + "ccBCaC", + 11 + ], + [ + "cbCB", + "AABCCaaA", + 13 + ], + [ + "cbCBABAAb", + "a", + 17 + ], + [ + "cbCBAcacb", + "CAa", + 12 + ], + [ + "cbCBC", + "cbcaCaaba", + 11 + ], + [ + "cbCBccbcc", + "BcCbA", + 11 + ], + [ + "cbCC", + "B", + 7 + ], + [ + "cbCCBBCB", + "baBaCbC", + 11 + ], + [ + "cbCCBccBb", + "BaAaC", + 16 + ], + [ + "cbCCC", + "AACACB", + 8 + ], + [ + "cbCCbABBB", + "CcaABC", + 11 + ], + [ + "cbCCbBac", + "CAaA", + 12 + ], + [ + "cbCa", + "CBA", + 5 + ], + [ + "cbCa", + "bbcacb", + 7 + ], + [ + "cbCaB", + "cbBaCb", + 5 + ], + [ + "cbCbAaCCa", + "ACAAb", + 13 + ], + [ + "cbCbaC", + "BACcBC", + 8 + ], + [ + "cbCbaCcaC", + "AaaaBC", + 14 + ], + [ + "cbCbb", + "A", + 10 + ], + [ + "cbCbbCAAc", + "caccc", + 12 + ], + [ + "cbCbcC", + "B", + 11 + ], + [ + "cbCcAAcA", + "AcaAaCcCa", + 13 + ], + [ + "cbCcCAc", + "babCC", + 9 + ], + [ + "cbCcCB", + "BCABA", + 9 + ], + [ + "cbCcCbA", + "cB", + 11 + ], + [ + "cbCcb", + "AacBB", + 8 + ], + [ + "cbCcbca", + "BCB", + 10 + ], + [ + "cbCccca", + "AAA", + 13 + ], + [ + "cba", + "A", + 5 + ], + [ + "cba", + "b", + 4 + ], + [ + "cba", + "bBCaaCc", + 11 + ], + [ + "cba", + "cAAbc", + 6 + ], + [ + "cba", + "caABABbab", + 12 + ], + [ + "cba", + "ccAB", + 5 + ], + [ + "cbaAa", + "cBbbcB", + 8 + ], + [ + "cbaAc", + "cAAc", + 3 + ], + [ + "cbaBACBAc", + "a", + 16 + ], + [ + "cbaBCAAC", + "BAAcCc", + 12 + ], + [ + "cbaBCbbcC", + "BcAaabac", + 12 + ], + [ + "cbaBbCa", + "BBACcA", + 10 + ], + [ + "cbaC", + "BBCaACabc", + 13 + ], + [ + "cbaC", + "aaA", + 6 + ], + [ + "cbaCABA", + "BbAaBccBC", + 11 + ], + [ + "cbaCCA", + "cbCABA", + 6 + ], + [ + "cbaCCCa", + "a", + 12 + ], + [ + "cbaCacac", + "AcBBbAA", + 13 + ], + [ + "cbaCcaA", + "C", + 12 + ], + [ + "cbaa", + "aaBcCBcBa", + 13 + ], + [ + "cbaaA", + "cBaa", + 3 + ], + [ + "cbaabCcC", + "bB", + 13 + ], + [ + "cbabAA", + "Bc", + 11 + ], + [ + "cbabBbc", + "AbcacAC", + 11 + ], + [ + "cbabC", + "CBbaAa", + 7 + ], + [ + "cbabbC", + "aCacAcBAC", + 13 + ], + [ + "cbac", + "cCbBcACa", + 10 + ], + [ + "cbacAAAA", + "BcAbcAbc", + 12 + ], + [ + "cbacAAacc", + "ab", + 16 + ], + [ + "cbacAC", + "BaaABAbC", + 10 + ], + [ + "cbacACa", + "AcBbcBaC", + 10 + ], + [ + "cbacB", + "CaBAa", + 9 + ], + [ + "cbacCCab", + "Acbc", + 12 + ], + [ + "cbacCabBB", + "CCcc", + 14 + ], + [ + "cbb", + "BBbB", + 5 + ], + [ + "cbb", + "CcAaAC", + 10 + ], + [ + "cbb", + "bBcABAcCC", + 15 + ], + [ + "cbb", + "cbccAbA", + 8 + ], + [ + "cbbABaCBa", + "Ca", + 14 + ], + [ + "cbbAa", + "aBBcAa", + 6 + ], + [ + "cbbAaC", + "cbc", + 7 + ], + [ + "cbbAbB", + "BABaBA", + 9 + ], + [ + "cbbAbaaB", + "CCababBa", + 10 + ], + [ + "cbbBAC", + "AacCcb", + 12 + ], + [ + "cbbBbA", + "AbCAACc", + 12 + ], + [ + "cbbBbb", + "b", + 10 + ], + [ + "cbbBbbbCA", + "bBB", + 13 + ], + [ + "cbbBcBbBb", + "BaABc", + 14 + ], + [ + "cbbBcC", + "C", + 10 + ], + [ + "cbbCAaB", + "A", + 12 + ], + [ + "cbbCbC", + "cBaCCABb", + 10 + ], + [ + "cbbCcbA", + "baCBaBab", + 12 + ], + [ + "cbbaCA", + "aCAbbbCcA", + 9 + ], + [ + "cbbaCb", + "ABaCaB", + 8 + ], + [ + "cbbaaC", + "BcAAbAba", + 11 + ], + [ + "cbbabc", + "BaAaacCAc", + 14 + ], + [ + "cbbacB", + "b", + 10 + ], + [ + "cbbb", + "CC", + 7 + ], + [ + "cbbb", + "bBcCaCAC", + 14 + ], + [ + "cbbbB", + "abacb", + 7 + ], + [ + "cbbbBBcb", + "cA", + 14 + ], + [ + "cbbbaBbC", + "CbAABAC", + 8 + ], + [ + "cbbbc", + "c", + 8 + ], + [ + "cbbc", + "aCBAcAc", + 10 + ], + [ + "cbbc", + "abBBCAB", + 10 + ], + [ + "cbbcBC", + "BAB", + 9 + ], + [ + "cbbcBcCCb", + "bc", + 14 + ], + [ + "cbc", + "AACAAbCCB", + 14 + ], + [ + "cbc", + "BcbaA", + 6 + ], + [ + "cbc", + "CbBbACB", + 10 + ], + [ + "cbc", + "b", + 4 + ], + [ + "cbc", + "cBCcBaC", + 9 + ], + [ + "cbcA", + "ABBAcC", + 9 + ], + [ + "cbcAA", + "c", + 8 + ], + [ + "cbcACAabb", + "bccBcbAaA", + 13 + ], + [ + "cbcAaAAb", + "BBccBbCA", + 13 + ], + [ + "cbcAb", + "CCCbbbcba", + 13 + ], + [ + "cbcB", + "Aba", + 6 + ], + [ + "cbcB", + "bCaaaAba", + 14 + ], + [ + "cbcBacB", + "acbB", + 9 + ], + [ + "cbcBbb", + "cBa", + 8 + ], + [ + "cbcBcc", + "bbCcA", + 7 + ], + [ + "cbcC", + "aABacCCC", + 11 + ], + [ + "cbcCA", + "bBAaba", + 10 + ], + [ + "cbcCBcbc", + "acBa", + 12 + ], + [ + "cbcCcBa", + "CbbACa", + 8 + ], + [ + "cbcaB", + "bCaAAc", + 9 + ], + [ + "cbcabBAac", + "bcBCbCBb", + 13 + ], + [ + "cbcb", + "bba", + 6 + ], + [ + "cbcbCCA", + "AaaCCabcc", + 15 + ], + [ + "cbcbaCBb", + "BccCAb", + 9 + ], + [ + "cbcbbC", + "BAaa", + 11 + ], + [ + "cbcbcaCC", + "BaBbbB", + 14 + ], + [ + "cbcccaCc", + "acAAAa", + 13 + ], + [ + "cc", + "AAAbBbC", + 13 + ], + [ + "cc", + "ABBaccbbB", + 14 + ], + [ + "cc", + "ABa", + 6 + ], + [ + "cc", + "ABaA", + 8 + ], + [ + "cc", + "BAbbAab", + 14 + ], + [ + "cc", + "BAbc", + 6 + ], + [ + "cc", + "BBacCb", + 9 + ], + [ + "cc", + "BCbaB", + 9 + ], + [ + "cc", + "BaBcBAAc", + 12 + ], + [ + "cc", + "BaC", + 5 + ], + [ + "cc", + "Bbca", + 6 + ], + [ + "cc", + "CAbbcb", + 9 + ], + [ + "cc", + "CCAcCb", + 9 + ], + [ + "cc", + "Ccb", + 3 + ], + [ + "cc", + "aAaBCaA", + 13 + ], + [ + "cc", + "aB", + 4 + ], + [ + "cc", + "aBA", + 6 + ], + [ + "cc", + "acCcCabA", + 12 + ], + [ + "cc", + "b", + 4 + ], + [ + "cc", + "bACABABA", + 15 + ], + [ + "cc", + "baCaAAAc", + 13 + ], + [ + "cc", + "bacAbacB", + 12 + ], + [ + "cc", + "bcbA", + 6 + ], + [ + "cc", + "cABcAA", + 8 + ], + [ + "cc", + "cACac", + 6 + ], + [ + "cc", + "cAbba", + 8 + ], + [ + "cc", + "cB", + 2 + ], + [ + "cc", + "cCac", + 4 + ], + [ + "cc", + "cCbbAcaA", + 12 + ], + [ + "cc", + "cb", + 2 + ], + [ + "cc", + "ccBc", + 4 + ], + [ + "cc", + "ccCab", + 6 + ], + [ + "ccA", + "AcCccAcc", + 10 + ], + [ + "ccA", + "CBAaC", + 7 + ], + [ + "ccA", + "b", + 6 + ], + [ + "ccAABB", + "ccCBBbC", + 7 + ], + [ + "ccAAb", + "BaABC", + 8 + ], + [ + "ccAAbaA", + "BaA", + 9 + ], + [ + "ccAAbbbaB", + "AABBCc", + 12 + ], + [ + "ccAB", + "BcA", + 4 + ], + [ + "ccABA", + "BcBBcbBA", + 8 + ], + [ + "ccABBAA", + "bCCac", + 12 + ], + [ + "ccABcAA", + "AABBaBaA", + 11 + ], + [ + "ccAC", + "CAACBCaCA", + 13 + ], + [ + "ccAC", + "bA", + 6 + ], + [ + "ccACAAcb", + "AbabCa", + 12 + ], + [ + "ccACABbAc", + "BCbAb", + 12 + ], + [ + "ccACB", + "CBbabcAC", + 11 + ], + [ + "ccACBAaBa", + "ca", + 14 + ], + [ + "ccACBca", + "Cb", + 11 + ], + [ + "ccACC", + "CcaAba", + 7 + ], + [ + "ccACC", + "aCc", + 6 + ], + [ + "ccACaABcb", + "CBBCCABCc", + 10 + ], + [ + "ccACaBbAC", + "cBaAaB", + 11 + ], + [ + "ccACcAAB", + "AbCbaCAbb", + 13 + ], + [ + "ccACcbb", + "abCcBCb", + 9 + ], + [ + "ccAa", + "bca", + 4 + ], + [ + "ccAa", + "c", + 6 + ], + [ + "ccAaACB", + "BCabaaA", + 11 + ], + [ + "ccAaAbccA", + "AAbAAb", + 12 + ], + [ + "ccAaC", + "bAaaACAB", + 11 + ], + [ + "ccAcAC", + "c", + 10 + ], + [ + "ccAcAb", + "AbABBCbBA", + 14 + ], + [ + "ccAcb", + "bcAb", + 4 + ], + [ + "ccAcccc", + "ccCca", + 7 + ], + [ + "ccB", + "B", + 4 + ], + [ + "ccB", + "CcBAB", + 5 + ], + [ + "ccB", + "aAa", + 6 + ], + [ + "ccB", + "b", + 5 + ], + [ + "ccB", + "cAaAbaa", + 11 + ], + [ + "ccB", + "cBbCACaAB", + 13 + ], + [ + "ccBA", + "B", + 6 + ], + [ + "ccBA", + "CAac", + 7 + ], + [ + "ccBACbca", + "Abb", + 12 + ], + [ + "ccBAc", + "baaaCaaB", + 14 + ], + [ + "ccBB", + "BcC", + 6 + ], + [ + "ccBBA", + "BbACCaA", + 11 + ], + [ + "ccBBbAa", + "b", + 12 + ], + [ + "ccBBbb", + "cBbBb", + 4 + ], + [ + "ccBBbcC", + "CAbacA", + 10 + ], + [ + "ccBC", + "AaBAA", + 8 + ], + [ + "ccBC", + "Baba", + 7 + ], + [ + "ccBC", + "b", + 7 + ], + [ + "ccBC", + "cbba", + 5 + ], + [ + "ccBCAaBc", + "bBCCCAA", + 12 + ], + [ + "ccBCAbC", + "abC", + 9 + ], + [ + "ccBCBBcac", + "BCabBA", + 12 + ], + [ + "ccBCC", + "AbbC", + 7 + ], + [ + "ccBCCCa", + "BaCaB", + 10 + ], + [ + "ccBCCaBcB", + "AcC", + 14 + ], + [ + "ccBCCc", + "aBb", + 10 + ], + [ + "ccBCbBcC", + "ca", + 14 + ], + [ + "ccBCcCaA", + "bcbBAcaBA", + 10 + ], + [ + "ccBa", + "ACCaABcCa", + 12 + ], + [ + "ccBaC", + "AC", + 7 + ], + [ + "ccBaaAa", + "Cc", + 11 + ], + [ + "ccBab", + "BACC", + 9 + ], + [ + "ccBabcaBA", + "CBbc", + 11 + ], + [ + "ccBacCAbB", + "cCCcA", + 11 + ], + [ + "ccBbAABB", + "ccb", + 10 + ], + [ + "ccBbAaB", + "Cc", + 11 + ], + [ + "ccBbaCAb", + "Bcc", + 13 + ], + [ + "ccBbab", + "cCbBc", + 7 + ], + [ + "ccBbbbcB", + "Aacaaa", + 16 + ], + [ + "ccBcb", + "bbcACCb", + 9 + ], + [ + "ccC", + "Ab", + 6 + ], + [ + "ccC", + "CBcCaAbAc", + 13 + ], + [ + "ccC", + "aaCB", + 6 + ], + [ + "ccC", + "bBBAA", + 10 + ], + [ + "ccCAA", + "aCC", + 7 + ], + [ + "ccCAcCB", + "baAAbBac", + 14 + ], + [ + "ccCBAa", + "AABbcA", + 10 + ], + [ + "ccCBCc", + "caBcB", + 7 + ], + [ + "ccCBbC", + "BbbA", + 9 + ], + [ + "ccCC", + "A", + 8 + ], + [ + "ccCC", + "CCaBcACBC", + 11 + ], + [ + "ccCC", + "aab", + 8 + ], + [ + "ccCCAaa", + "B", + 14 + ], + [ + "ccCCBAaaC", + "Bb", + 16 + ], + [ + "ccCCBbCaA", + "AcACCaB", + 10 + ], + [ + "ccCCCaaCb", + "BaAbBB", + 16 + ], + [ + "ccCCacA", + "ACAAa", + 10 + ], + [ + "ccCa", + "acbBa", + 6 + ], + [ + "ccCaa", + "aCBCa", + 7 + ], + [ + "ccCab", + "CCAb", + 4 + ], + [ + "ccCbBB", + "AAcabCa", + 10 + ], + [ + "ccCba", + "aacAbCBb", + 11 + ], + [ + "ccCbbCBA", + "AaCBcAb", + 12 + ], + [ + "ccCbc", + "aCbBCcb", + 10 + ], + [ + "ccCbcCcA", + "aAA", + 14 + ], + [ + "ccCbcaaAC", + "abB", + 16 + ], + [ + "ccCcAbCA", + "AB", + 13 + ], + [ + "ccCcAccA", + "CCABcCCbc", + 13 + ], + [ + "ccCcBaA", + "caC", + 10 + ], + [ + "ccCcBbc", + "aaacAca", + 12 + ], + [ + "ccCcBcC", + "CBCCCBAA", + 9 + ], + [ + "ccCcabcb", + "baAb", + 12 + ], + [ + "ccCccbABc", + "b", + 16 + ], + [ + "cca", + "ABc", + 6 + ], + [ + "cca", + "aa", + 4 + ], + [ + "cca", + "bACA", + 6 + ], + [ + "ccaA", + "AcBa", + 5 + ], + [ + "ccaA", + "Ca", + 5 + ], + [ + "ccaABaccb", + "AAaaCBb", + 10 + ], + [ + "ccaACAC", + "BAc", + 11 + ], + [ + "ccaAba", + "Ccb", + 7 + ], + [ + "ccaAc", + "B", + 10 + ], + [ + "ccaAcCB", + "ccBB", + 8 + ], + [ + "ccaB", + "BbcB", + 6 + ], + [ + "ccaBC", + "CbbbB", + 8 + ], + [ + "ccaCA", + "ABBbb", + 10 + ], + [ + "ccaCAB", + "cCbcb", + 7 + ], + [ + "ccaCAcAB", + "acbb", + 11 + ], + [ + "ccaCAcBb", + "ccCbb", + 7 + ], + [ + "ccaCCCcCC", + "AACAc", + 13 + ], + [ + "ccaCaABb", + "bA", + 14 + ], + [ + "ccaCc", + "babb", + 8 + ], + [ + "ccaCcAAaB", + "BcAAaB", + 8 + ], + [ + "ccaCcB", + "B", + 10 + ], + [ + "ccaaBCB", + "AAA", + 12 + ], + [ + "ccaaaa", + "Aa", + 9 + ], + [ + "ccaab", + "acbbC", + 8 + ], + [ + "ccaac", + "BCBBb", + 9 + ], + [ + "ccab", + "C", + 7 + ], + [ + "ccab", + "aabBBcABa", + 14 + ], + [ + "ccabaB", + "CACba", + 7 + ], + [ + "ccabaCa", + "ccAac", + 6 + ], + [ + "ccababcA", + "abc", + 10 + ], + [ + "ccacAbBBB", + "Cb", + 15 + ], + [ + "ccacb", + "cbCA", + 7 + ], + [ + "ccb", + "A", + 6 + ], + [ + "ccb", + "Aa", + 6 + ], + [ + "ccb", + "Aca", + 4 + ], + [ + "ccb", + "acBAcb", + 6 + ], + [ + "ccb", + "acBccbAcA", + 12 + ], + [ + "ccbA", + "ba", + 5 + ], + [ + "ccbAAA", + "BAaca", + 9 + ], + [ + "ccbAACAc", + "CaaCBcBAa", + 14 + ], + [ + "ccbABAA", + "BBaccbb", + 13 + ], + [ + "ccbACa", + "cabACc", + 4 + ], + [ + "ccbAaCA", + "cCccaCCA", + 7 + ], + [ + "ccbAb", + "bbbCaACa", + 12 + ], + [ + "ccbAbA", + "cbcaBb", + 8 + ], + [ + "ccbAbBB", + "cBcbA", + 8 + ], + [ + "ccbAcC", + "AbBCcaacB", + 12 + ], + [ + "ccbBbcb", + "bCbCAC", + 10 + ], + [ + "ccbC", + "ACccBAc", + 8 + ], + [ + "ccbC", + "CB", + 6 + ], + [ + "ccbC", + "aCAA", + 7 + ], + [ + "ccbCC", + "AcCcCcb", + 8 + ], + [ + "ccbCa", + "bcBBcb", + 8 + ], + [ + "ccbCaACca", + "BBcaA", + 12 + ], + [ + "ccbCbbB", + "b", + 12 + ], + [ + "ccba", + "CAbbBc", + 9 + ], + [ + "ccba", + "Caa", + 5 + ], + [ + "ccba", + "bA", + 5 + ], + [ + "ccba", + "cbc", + 4 + ], + [ + "ccbabbC", + "AB", + 12 + ], + [ + "ccbbBbAb", + "CBc", + 13 + ], + [ + "ccbbabB", + "bCcaCa", + 11 + ], + [ + "ccbbcB", + "ACbcaaA", + 11 + ], + [ + "ccbbccCAC", + "CcccBcBC", + 10 + ], + [ + "ccbcABBCb", + "aAcAacC", + 12 + ], + [ + "ccbcAbBbc", + "a", + 17 + ], + [ + "ccbcB", + "CBaCAAcBc", + 12 + ], + [ + "ccbcBBAbB", + "cBccCc", + 13 + ], + [ + "ccbcCaab", + "cCaAAB", + 10 + ], + [ + "ccbcCbcB", + "CCCa", + 12 + ], + [ + "ccbcba", + "CA", + 10 + ], + [ + "ccc", + "AcBcbBcb", + 10 + ], + [ + "ccc", + "BbcCbB", + 9 + ], + [ + "ccc", + "CACABCbb", + 13 + ], + [ + "ccc", + "ac", + 4 + ], + [ + "ccc", + "baAABCbcc", + 13 + ], + [ + "ccc", + "c", + 4 + ], + [ + "ccc", + "cabACBAbB", + 15 + ], + [ + "cccA", + "cbBAbbB", + 10 + ], + [ + "cccAAA", + "AcB", + 10 + ], + [ + "cccAABCcA", + "ba", + 16 + ], + [ + "cccAC", + "ABCaa", + 8 + ], + [ + "cccAaccc", + "bcCC", + 12 + ], + [ + "cccAbC", + "cBbCAb", + 7 + ], + [ + "cccB", + "bACBcb", + 8 + ], + [ + "cccB", + "c", + 6 + ], + [ + "cccBAA", + "cbB", + 8 + ], + [ + "cccBAbb", + "bCCacb", + 9 + ], + [ + "cccBBAC", + "bbbcB", + 12 + ], + [ + "cccBBCc", + "CAaaAb", + 13 + ], + [ + "cccC", + "ACbB", + 7 + ], + [ + "cccC", + "bbbA", + 8 + ], + [ + "cccCabBA", + "AaBc", + 12 + ], + [ + "cccCbbcc", + "CcC", + 11 + ], + [ + "cccCccaC", + "b", + 16 + ], + [ + "ccca", + "cCcCAa", + 5 + ], + [ + "cccaAb", + "Abb", + 9 + ], + [ + "cccaCbA", + "cBcCBBbaA", + 9 + ], + [ + "cccaaa", + "BaABcbcca", + 14 + ], + [ + "cccacB", + "abBB", + 10 + ], + [ + "cccb", + "AABc", + 8 + ], + [ + "cccb", + "CbBAAbA", + 11 + ], + [ + "cccbABcc", + "a", + 15 + ], + [ + "cccbB", + "bcCAcaaba", + 11 + ], + [ + "cccbaBC", + "cbACAac", + 10 + ], + [ + "cccbabBb", + "ACc", + 13 + ], + [ + "cccbbA", + "bbcAc", + 10 + ], + [ + "cccbcaCC", + "AbCACBcCb", + 13 + ], + [ + "cccbcaaaC", + "baCcCc", + 14 + ], + [ + "ccccBa", + "abcbaaCbA", + 13 + ], + [ + "ccccCBcCA", + "Cbbab", + 15 + ], + [ + "ccccCCc", + "aabB", + 14 + ], + [ + "ccccaCA", + "Cb", + 12 + ], + [ + "cccccb", + "a", + 12 + ], + [ + "ccccccAcc", + "AB", + 16 + ] +] \ No newline at end of file diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py index fa5f1e30355..e3e2bd29d92 100644 --- a/Lib/test/test_traceback.py +++ b/Lib/test/test_traceback.py @@ -5205,7 +5205,6 @@ def CHECK(a, b, expected): CHECK("AttributeError", "AttributeErrorTests", 10) CHECK("ABA", "AAB", 4) - @unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: crates/pylib/Lib/test/levenshtein_examples.json is missing. Run `make regen-test-levenshtein` @support.requires_resource('cpu') def test_levenshtein_distance_short_circuit(self): if not LEVENSHTEIN_DATA_FILE.is_file(): From 9bd84b2f135dbd4b19897c0891df058cc1fd3a18 Mon Sep 17 00:00:00 2001 From: youknowdot Date: Sat, 24 Oct 2026 23:12:50 -0700 Subject: [PATCH 06/18] Handle full-width operands in the None-return scanner (#9347) Use decoded logical instructions rather than the preceding raw operand byte. Check referenced constants by None identity and decode common constants without panicking. Assisted-by: OpenAI Codex:model unavailable --- crates/vm/src/vm/crossinterp.rs | 47 ++++++++++++++++----------------- 1 file changed, 23 insertions(+), 24 deletions(-) diff --git a/crates/vm/src/vm/crossinterp.rs b/crates/vm/src/vm/crossinterp.rs index 1fb46a64356..3c2697be545 100644 --- a/crates/vm/src/vm/crossinterp.rs +++ b/crates/vm/src/vm/crossinterp.rs @@ -14,12 +14,15 @@ use crate::{ PyMemoryView, PyStr, PyStrInterned, PyTuple, }, bytecode::{ - BorrowedConstant, CodeFlags, Constant, Instruction, + CodeFlags, Instruction, oparg::{CommonConstant, OpArg, OpArgState}, }, protocol::PyBuffer, }; -use core::sync::atomic::{AtomicBool, Ordering}; +use core::{ + borrow::Borrow, + sync::atomic::{AtomicBool, Ordering}, +}; use num_traits::ToPrimitive; use rustpython_common::wtf8::Wtf8Buf; @@ -828,31 +831,27 @@ pub(crate) fn code_returns_only_none(code: &Py) -> bool { if !is_pure_function(code) { return false; } - let units = &code.instructions; - // Look up None in co_consts. - let none_index = code - .constants - .iter() - .position(|c| matches!(c.borrow_constant(), BorrowedConstant::None)); - // We don't worry about EXTENDED_ARG for now. - for (at, op, _) in walk_instructions(code) { - if !matches!(op, Instruction::ReturnValue) { - continue; - } - debug_assert_ne!(at, 0); - let prev_op = units.read_op(at - 1).deoptimize(); - let prev_arg = u32::from(u8::from(units.read_arg(at - 1))); - if matches!(prev_op, Instruction::LoadCommonConstant { .. }) - && prev_arg == u32::from(CommonConstant::None) - { + let mut previous_loads_none = false; + for (_, instruction, arg) in walk_instructions(code) { + if matches!(instruction, Instruction::ExtendedArg) { continue; } - if matches!(prev_op, Instruction::LoadConst { .. }) - && none_index.is_some_and(|none_index| prev_arg as usize == none_index) - { - continue; + if matches!(instruction, Instruction::ReturnValue) && !previous_loads_none { + return false; } - return false; + previous_loads_none = match instruction { + Instruction::LoadCommonConstant { idx } => { + matches!(idx.try_get(arg), Ok(CommonConstant::None)) + } + Instruction::LoadConst { consti } => code + .constants + .get(consti.get(arg).as_usize()) + .is_some_and(|constant| { + let object: &PyObject = constant.borrow(); + object.is(&crate::Context::genesis().none) + }), + _ => false, + }; } true } From 46c0663a0652cddf033e2c0066119b80a478dfe3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:43:58 +0900 Subject: [PATCH 07/18] build(deps): bump reviewdog/action-actionlint from 1.80.2 to 1.80.4 (#9348) Bumps [reviewdog/action-actionlint](https://github.com/reviewdog/action-actionlint) from 1.80.2 to 1.80.4. - [Release notes](https://github.com/reviewdog/action-actionlint/releases) - [Commits](https://github.com/reviewdog/action-actionlint/compare/3cbf14efd750961dba35fccbf00f9e14028cb1f5...419a49378266e892f40c773b4f2fc6de9aa87499) --- updated-dependencies: - dependency-name: reviewdog/action-actionlint dependency-version: 1.80.4 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 81fabc359aa..fedaa927e5b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -791,7 +791,7 @@ jobs: components: rustfmt - name: actionlint - uses: reviewdog/action-actionlint@3cbf14efd750961dba35fccbf00f9e14028cb1f5 # v1.80.2 + uses: reviewdog/action-actionlint@419a49378266e892f40c773b4f2fc6de9aa87499 # v1.80.4 - name: zizmor uses: zizmorcore/zizmor-action@85b2b9fb8e3325e7a23d7d0a9d7ba5ae0f999e3e # v0.7.1 From ef6e2dbaee626e7ea2f24900867fcd4f5aa6ab56 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 16:44:21 +0900 Subject: [PATCH 08/18] build(deps): bump mach2 from 0.4.3 to 0.7.0 (#9349) Bumps [mach2](https://github.com/JohnTitor/mach2) from 0.4.3 to 0.7.0. - [Release notes](https://github.com/JohnTitor/mach2/releases) - [Commits](https://github.com/JohnTitor/mach2/compare/0.4.3...0.7.0) --- updated-dependencies: - dependency-name: mach2 dependency-version: 0.7.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 10 ++++++++-- Cargo.toml | 2 +- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cbb8189bf0..036e0ceb23d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2368,6 +2368,12 @@ dependencies = [ "libc", ] +[[package]] +name = "mach2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0d28e293f2b8c9d2b2d1c0193bd3c1cbdc0d2cf396888dc01162f7cf9d6c3f3" + [[package]] name = "malachite-base" version = "0.14.0" @@ -3450,7 +3456,7 @@ checksum = "e6b6ebd13bc009aef9cd476c1310d49ac354d36e240cf1bd753290f3dc7199a7" dependencies = [ "bitflags 1.3.2", "libc", - "mach2", + "mach2 0.4.3", "windows-sys 0.52.0", ] @@ -3859,7 +3865,7 @@ dependencies = [ "libffi", "libloading 0.9.0", "mac_address", - "mach2", + "mach2 0.7.0", "memchr", "memmap2", "nix", diff --git a/Cargo.toml b/Cargo.toml index 638a86a72de..89bebf31d8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -259,7 +259,7 @@ log = "0.4.30" lz4_flex = "0.14" nix = { version = "0.31", features = ["fs", "user", "process", "term", "time", "signal", "ioctl", "socket", "sched", "zerocopy", "dir", "hostname", "net", "poll"] } mac_address = "1.1.3" -mach2 = "0.4.3" +mach2 = "0.7.0" malachite-bigint = "0.14.0" malachite-q = "0.14.0" malachite-base = "0.14.0" From 0cb9f136e5d12cf84081ca9b29f19dacac76e22d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:26:47 +0000 Subject: [PATCH 09/18] build(deps): bump memmap2 from 0.9.11 to 0.9.12 (#9350) Bumps [memmap2](https://github.com/RazrFalcon/memmap2-rs) from 0.9.11 to 0.9.12. - [Changelog](https://github.com/RazrFalcon/memmap2-rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/RazrFalcon/memmap2-rs/compare/v0.9.11...v0.9.12) --- updated-dependencies: - dependency-name: memmap2 dependency-version: 0.9.12 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 036e0ceb23d..862eaae773a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2465,9 +2465,9 @@ checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" [[package]] name = "memmap2" -version = "0.9.11" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +checksum = "6b0ca1155a9ecc25aa12e8313a119f32edf0255fea2f44839c4ac0a12b2ac157" dependencies = [ "libc", ] From cb92931f9f1237eb45bf0efbed3b3f65742d3b8a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:42:18 +0000 Subject: [PATCH 10/18] build(deps): bump @xterm/addon-fit from 0.10.0 to 0.11.0 in /wasm/demo (#9352) Bumps [@xterm/addon-fit](https://github.com/xtermjs/xterm.js) from 0.10.0 to 0.11.0. - [Release notes](https://github.com/xtermjs/xterm.js/releases) - [Commits](https://github.com/xtermjs/xterm.js/compare/0.10...0.11) --- updated-dependencies: - dependency-name: "@xterm/addon-fit" dependency-version: 0.11.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- wasm/demo/package-lock.json | 13 +++++-------- wasm/demo/package.json | 2 +- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/wasm/demo/package-lock.json b/wasm/demo/package-lock.json index a592f4692f3..dafd90c16f1 100644 --- a/wasm/demo/package-lock.json +++ b/wasm/demo/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@codemirror/lang-python": "^6.1.6", - "@xterm/addon-fit": "^0.10.0", + "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^5.3.0", "codemirror": "^6.0.1", "upgrade": "^1.1.0", @@ -1309,13 +1309,10 @@ } }, "node_modules/@xterm/addon-fit": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", - "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", - "license": "MIT", - "peerDependencies": { - "@xterm/xterm": "^5.0.0" - } + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.11.0.tgz", + "integrity": "sha512-jYcgT6xtVYhnhgxh3QgYDnnNMYTcf8ElbxxFzX0IZo+vabQqSPAjC3c1wJrKB5E19VwQei89QCiZZP86DCPF7g==", + "license": "MIT" }, "node_modules/@xterm/xterm": { "version": "5.5.0", diff --git a/wasm/demo/package.json b/wasm/demo/package.json index 2aa9e5867ae..c91b9444a3d 100644 --- a/wasm/demo/package.json +++ b/wasm/demo/package.json @@ -5,7 +5,7 @@ "main": "index.js", "dependencies": { "@codemirror/lang-python": "^6.1.6", - "@xterm/addon-fit": "^0.10.0", + "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^5.3.0", "codemirror": "^6.0.1", "upgrade": "^1.1.0", From b7bebe638c8a44dc2a7ec90cfd6c17912ec88876 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 09:50:23 +0000 Subject: [PATCH 11/18] build(deps): bump object from 0.37.3 to 0.40.0 (#9351) Bumps [object](https://github.com/gimli-rs/object) from 0.37.3 to 0.40.0. - [Changelog](https://github.com/gimli-rs/object/blob/main/CHANGELOG.md) - [Commits](https://github.com/gimli-rs/object/compare/0.37.3...v0.40.0) --- updated-dependencies: - dependency-name: object dependency-version: 0.40.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 13 +++++++++++-- Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 862eaae773a..9df79f76465 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,7 +170,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4087686b4b0a3427190bae57a1d9a478dbb2d40c5dc1bd6e2b6d797913bdd348" dependencies = [ - "object", + "object 0.37.3", ] [[package]] @@ -2647,6 +2647,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "object" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd229a0361b9d0d4396176e02d65897f487eebeab7caa6d443855ee152ca0b9c" +dependencies = [ + "memchr", +] + [[package]] name = "oid-registry" version = "0.8.1" @@ -3871,7 +3880,7 @@ dependencies = [ "nix", "num-traits", "num_cpus", - "object", + "object 0.40.0", "parking_lot", "paste", "pkcs8 0.11.0", diff --git a/Cargo.toml b/Cargo.toml index 89bebf31d8d..516f8e4e084 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -272,7 +272,7 @@ num-integer = "0.1.46" num-traits = "0.2" num_cpus = "1.17.0" num_enum = { version = "0.7", default-features = false } -object = { version = "0.37.3", default-features = false, features = ["read_core", "elf", "macho", "pe", "std", "unaligned"] } +object = { version = "0.40.0", default-features = false, features = ["read_core", "elf", "macho", "pe", "std", "unaligned"] } openssl = "0.10.80" openssl-sys = "0.9.110" optional = "0.5" From bb08d9acfcdbf1088c4213b251749cb624be37ee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:03:09 +0000 Subject: [PATCH 12/18] build(deps): bump xterm-readline from 1.1.2 to 1.3.1 in /wasm/demo (#9354) Bumps [xterm-readline](https://github.com/strtok/xterm-readline) from 1.1.2 to 1.3.1. - [Changelog](https://github.com/strtok/xterm-readline/blob/main/CHANGELOG.md) - [Commits](https://github.com/strtok/xterm-readline/commits/v1.3.1) --- updated-dependencies: - dependency-name: xterm-readline dependency-version: 1.3.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- wasm/demo/package-lock.json | 10 +++++----- wasm/demo/package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/wasm/demo/package-lock.json b/wasm/demo/package-lock.json index dafd90c16f1..cdf1410e88a 100644 --- a/wasm/demo/package-lock.json +++ b/wasm/demo/package-lock.json @@ -14,7 +14,7 @@ "@xterm/xterm": "^5.3.0", "codemirror": "^6.0.1", "upgrade": "^1.1.0", - "xterm-readline": "^1.1.2" + "xterm-readline": "^1.3.1" }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "^1.7.0", @@ -6359,15 +6359,15 @@ } }, "node_modules/xterm-readline": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/xterm-readline/-/xterm-readline-1.1.2.tgz", - "integrity": "sha512-1+W2nVuQvCYz9OUYwFBiolrSQUui51aDDyacKXt4PuxeBHqzvabQEJ2kwdBDzsmOjz5BwlDTAjJmYpH2OGqLFA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/xterm-readline/-/xterm-readline-1.3.1.tgz", + "integrity": "sha512-5dyP/rP3oj6Cm0Cf4g+h+AfzK+1nTC0uD48iI99NyJBnyzmXK8uKci7E30576hD+LpqclQb0mLYarWj6mKXw5w==", "license": "MIT", "dependencies": { "string-width": "^4" }, "peerDependencies": { - "@xterm/xterm": "^5.5.0" + "@xterm/xterm": "^5.5.0 || ^6.0.0" } }, "node_modules/xterm-readline/node_modules/emoji-regex": { diff --git a/wasm/demo/package.json b/wasm/demo/package.json index c91b9444a3d..d944ca376e0 100644 --- a/wasm/demo/package.json +++ b/wasm/demo/package.json @@ -9,7 +9,7 @@ "@xterm/xterm": "^5.3.0", "codemirror": "^6.0.1", "upgrade": "^1.1.0", - "xterm-readline": "^1.1.2" + "xterm-readline": "^1.3.1" }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "^1.7.0", From 636a9679db03cb3dc1f6c9d71498b03924148df8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:09:00 +0000 Subject: [PATCH 13/18] build(deps-dev): bump mini-css-extract-plugin in /wasm/demo (#9353) Bumps [mini-css-extract-plugin](https://github.com/webpack/mini-css-extract-plugin) from 2.9.2 to 2.10.2. - [Release notes](https://github.com/webpack/mini-css-extract-plugin/releases) - [Changelog](https://github.com/webpack/mini-css-extract-plugin/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/mini-css-extract-plugin/compare/v2.9.2...v2.10.2) --- updated-dependencies: - dependency-name: mini-css-extract-plugin dependency-version: 2.10.2 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- wasm/demo/package-lock.json | 8 ++++---- wasm/demo/package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/wasm/demo/package-lock.json b/wasm/demo/package-lock.json index cdf1410e88a..eb254f7330b 100644 --- a/wasm/demo/package-lock.json +++ b/wasm/demo/package-lock.json @@ -20,7 +20,7 @@ "@wasm-tool/wasm-pack-plugin": "^1.7.0", "css-loader": "^7.1.2", "html-webpack-plugin": "^5.6.3", - "mini-css-extract-plugin": "^2.9.2", + "mini-css-extract-plugin": "^2.10.2", "serve": "^14.2.6", "webpack": "^5.105.0", "webpack-cli": "^6.0.1", @@ -4030,9 +4030,9 @@ } }, "node_modules/mini-css-extract-plugin": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.9.2.tgz", - "integrity": "sha512-GJuACcS//jtq4kCtd5ii/M0SZf7OZRH+BxdqXZHaJfb8TJiVl+NgQRPwiYt2EuqeSkNydn/7vP+bcE27C5mb9w==", + "version": "2.10.2", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-2.10.2.tgz", + "integrity": "sha512-AOSS0IdEB95ayVkxn5oGzNQwqAi2J0Jb/kKm43t7H73s8+f5873g0yuj0PNvK4dO75mu5DHg4nlgp4k6Kga8eg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/wasm/demo/package.json b/wasm/demo/package.json index d944ca376e0..b402564f912 100644 --- a/wasm/demo/package.json +++ b/wasm/demo/package.json @@ -15,7 +15,7 @@ "@wasm-tool/wasm-pack-plugin": "^1.7.0", "css-loader": "^7.1.2", "html-webpack-plugin": "^5.6.3", - "mini-css-extract-plugin": "^2.9.2", + "mini-css-extract-plugin": "^2.10.2", "serve": "^14.2.6", "webpack": "^5.105.0", "webpack-cli": "^6.0.1", From 901c0146ccd85362a9c229ae61e084df7e1e1577 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 25 Oct 2026 10:14:25 +0000 Subject: [PATCH 14/18] build(deps-dev): bump css-loader from 7.1.2 to 7.1.5 in /wasm/demo (#9355) Bumps [css-loader](https://github.com/webpack/css-loader) from 7.1.2 to 7.1.5. - [Release notes](https://github.com/webpack/css-loader/releases) - [Changelog](https://github.com/webpack/css-loader/blob/main/CHANGELOG.md) - [Commits](https://github.com/webpack/css-loader/compare/v7.1.2...v7.1.5) --- updated-dependencies: - dependency-name: css-loader dependency-version: 7.1.5 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- wasm/demo/package-lock.json | 14 +++++++------- wasm/demo/package.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/wasm/demo/package-lock.json b/wasm/demo/package-lock.json index eb254f7330b..2026a1e49f6 100644 --- a/wasm/demo/package-lock.json +++ b/wasm/demo/package-lock.json @@ -18,7 +18,7 @@ }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "^1.7.0", - "css-loader": "^7.1.2", + "css-loader": "^7.1.5", "html-webpack-plugin": "^5.6.3", "mini-css-extract-plugin": "^2.10.2", "serve": "^14.2.6", @@ -2310,20 +2310,20 @@ } }, "node_modules/css-loader": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", - "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.5.tgz", + "integrity": "sha512-Q7iAfQkU2twNBryKX/vGAlE+GAmkF7quhSzAGNK8fBimxk3+tqg245rH52UPb9dioBolBc8rP0ihmHBaDTJQBA==", "dev": true, "license": "MIT", "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.33", + "postcss": "^8.4.40", "postcss-modules-extract-imports": "^3.1.0", "postcss-modules-local-by-default": "^4.0.5", "postcss-modules-scope": "^3.2.0", "postcss-modules-values": "^4.0.0", "postcss-value-parser": "^4.2.0", - "semver": "^7.5.4" + "semver": "^7.6.3" }, "engines": { "node": ">= 18.12.0" @@ -2333,7 +2333,7 @@ "url": "https://opencollective.com/webpack" }, "peerDependencies": { - "@rspack/core": "0.x || 1.x", + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", "webpack": "^5.27.0" }, "peerDependenciesMeta": { diff --git a/wasm/demo/package.json b/wasm/demo/package.json index b402564f912..bee298c38f4 100644 --- a/wasm/demo/package.json +++ b/wasm/demo/package.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@wasm-tool/wasm-pack-plugin": "^1.7.0", - "css-loader": "^7.1.2", + "css-loader": "^7.1.5", "html-webpack-plugin": "^5.6.3", "mini-css-extract-plugin": "^2.10.2", "serve": "^14.2.6", From 901eb883a7b8fa0477f49eb60fecdea77ae4abfc Mon Sep 17 00:00:00 2001 From: youknowdot Date: Sun, 25 Oct 2026 03:16:32 -0700 Subject: [PATCH 15/18] Add canonical sampling profiler CLI for offline replay (#9358) Import the CLI, entry point, error type, coordinator dependency, and CLI tests with scripts/update_lib migrate from CPython 3.15.0 at d220409a13b77de7a6c30abe16ea90db01528edc. This frontend follows the existing offline collector/codec support. Live attach, run, and dump remain unsupported and unvalidated. Assisted-by: OpenAI Codex: model version unavailable --- Lib/profiling/sampling/__main__.py | 73 + Lib/profiling/sampling/_sync_coordinator.py | 256 +++ Lib/profiling/sampling/cli.py | 1462 +++++++++++++++++ Lib/profiling/sampling/errors.py | 19 + .../test_sampling_profiler/test_cli.py | 1083 ++++++++++++ 5 files changed, 2893 insertions(+) create mode 100644 Lib/profiling/sampling/__main__.py create mode 100644 Lib/profiling/sampling/_sync_coordinator.py create mode 100644 Lib/profiling/sampling/cli.py create mode 100644 Lib/profiling/sampling/errors.py create mode 100644 Lib/test/test_profiling/test_sampling_profiler/test_cli.py diff --git a/Lib/profiling/sampling/__main__.py b/Lib/profiling/sampling/__main__.py new file mode 100644 index 00000000000..a45b645eae0 --- /dev/null +++ b/Lib/profiling/sampling/__main__.py @@ -0,0 +1,73 @@ +"""Run the sampling profiler from the command line.""" + +import sys + +MACOS_PERMISSION_ERROR = """\ +🔒 Permission Error: Unable to access process memory on macOS + +Tachyon needs elevated permissions to profile processes. Try one of these solutions: + +1. Try running again with elevated permissions by running 'sudo -E !!'. + +2. If targeting system Python processes: + Note: Apple's System Integrity Protection (SIP) may block access to system + Python binaries. Consider using a user-installed Python instead. +""" + +LINUX_PERMISSION_ERROR = """ +🔒 Tachyon was unable to access process memory. This could be because tachyon +has insufficient privileges (the required capability is CAP_SYS_PTRACE). +Unprivileged processes cannot trace processes that they cannot send signals +to or those running set-user-ID/set-group-ID programs, for security reasons. + +If your uid matches the uid of the target process you want to analyze, you +can do one of the following to get 'ptrace' scope permissions: + +* If you are running inside a Docker container, you need to make sure you + start the container using the '--cap-add=SYS_PTRACE' or '--privileged' + command line arguments. Notice that this may not be enough if you are not + running as 'root' inside the Docker container as you may need to disable + hardening (see next points). + +* Try running again with elevated permissions by running 'sudo -E !!'. + +* You can disable kernel hardening for the current session temporarily (until + a reboot happens) by running 'echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope'. +""" + +WINDOWS_PERMISSION_ERROR = """ +🔒 Tachyon requires administrator rights to access process memory on Windows. +Please run your command prompt as Administrator and try again. +""" + +GENERIC_PERMISSION_ERROR = """ +🔒 Tachyon was unable to access the target process due to operating +system restrictions or missing privileges. +""" + +from .cli import main +from .errors import SamplingUnknownProcessError, SamplingModuleNotFoundError, SamplingScriptNotFoundError + +def handle_permission_error(): + """Handle PermissionError by displaying appropriate error message.""" + if sys.platform == "darwin": + print(MACOS_PERMISSION_ERROR, file=sys.stderr) + elif sys.platform.startswith("linux"): + print(LINUX_PERMISSION_ERROR, file=sys.stderr) + elif sys.platform.startswith("win"): + print(WINDOWS_PERMISSION_ERROR, file=sys.stderr) + else: + print(GENERIC_PERMISSION_ERROR, file=sys.stderr) + sys.exit(1) + +if __name__ == '__main__': + try: + main() + except PermissionError: + handle_permission_error() + except SamplingUnknownProcessError as err: + print(f"Tachyon cannot find the process: {err}", file=sys.stderr) + sys.exit(1) + except (SamplingModuleNotFoundError, SamplingScriptNotFoundError) as err: + print(f"Tachyon cannot find the target: {err}", file=sys.stderr) + sys.exit(1) diff --git a/Lib/profiling/sampling/_sync_coordinator.py b/Lib/profiling/sampling/_sync_coordinator.py new file mode 100644 index 00000000000..a1cce314b33 --- /dev/null +++ b/Lib/profiling/sampling/_sync_coordinator.py @@ -0,0 +1,256 @@ +""" +Internal synchronization coordinator for the sample profiler. + +This module is used internally by the sample profiler to coordinate +the startup of target processes. It should not be called directly by users. +""" + +import importlib.util +import os +import sys +import socket +import runpy +import time +import types +from typing import List, NoReturn + + +class CoordinatorError(Exception): + """Base exception for coordinator errors.""" + pass + + +class ArgumentError(CoordinatorError): + """Raised when invalid arguments are provided.""" + pass + + +class SyncError(CoordinatorError): + """Raised when synchronization with profiler fails.""" + pass + + +class TargetError(CoordinatorError): + """Raised when target execution fails.""" + pass + + +def _validate_arguments(args: List[str]) -> tuple[int, str, List[str]]: + """ + Validate and parse command line arguments. + + Args: + args: Command line arguments including script name + + Returns: + Tuple of (sync_port, working_directory, target_args) + + Raises: + ArgumentError: If arguments are invalid + """ + if len(args) < 4: + raise ArgumentError( + "Insufficient arguments. Expected: [args...]" + ) + + try: + sync_port = int(args[1]) + if not (1 <= sync_port <= 65535): + raise ValueError("Port out of range") + except ValueError as e: + raise ArgumentError(f"Invalid sync port '{args[1]}': {e}") from e + + cwd = args[2] + if not os.path.isdir(cwd): + raise ArgumentError(f"Working directory does not exist: {cwd}") + + target_args = args[3:] + if not target_args: + raise ArgumentError("No target specified") + + return sync_port, cwd, target_args + + +# Constants for socket communication +_MAX_RETRIES = 3 +_INITIAL_RETRY_DELAY_SEC = 0.1 +_SOCKET_TIMEOUT_SEC = 2.0 +_READY_MESSAGE = b"ready" + + +def _signal_readiness(sync_port: int) -> None: + """ + Signal readiness to the profiler via TCP socket. + + Args: + sync_port: Port number where profiler is listening + + Raises: + SyncError: If unable to signal readiness + """ + last_error = None + + for attempt in range(_MAX_RETRIES): + try: + # Use context manager for automatic cleanup + with socket.create_connection(("127.0.0.1", sync_port), timeout=_SOCKET_TIMEOUT_SEC) as sock: + sock.send(_READY_MESSAGE) + return + except (socket.error, OSError) as e: + last_error = e + if attempt < _MAX_RETRIES - 1: + # Exponential backoff before retry + time.sleep(_INITIAL_RETRY_DELAY_SEC * (2 ** attempt)) + + # If we get here, all retries failed + raise SyncError(f"Failed to signal readiness after {_MAX_RETRIES} attempts: {last_error}") from last_error + + +def _setup_environment(cwd: str) -> None: + """ + Set up the execution environment. + + Args: + cwd: Working directory to change to + + Raises: + TargetError: If unable to set up environment + """ + try: + os.chdir(cwd) + except OSError as e: + raise TargetError(f"Failed to change to directory {cwd}: {e}") from e + + # Add current directory to sys.path if not present (for module imports) + if cwd not in sys.path: + sys.path.insert(0, cwd) + + +def _execute_module(module_name: str, module_args: List[str]) -> None: + """ + Execute a Python module. + + Args: + module_name: Name of the module to execute + module_args: Arguments to pass to the module + + Raises: + TargetError: If module cannot be found + """ + # Replace sys.argv to match how Python normally runs modules + # When running 'python -m module args', sys.argv is ["__main__.py", "args"] + sys.argv = ["__main__.py"] + module_args + + try: + runpy.run_module(module_name, run_name="__main__", alter_sys=True) + except ImportError as e: + raise TargetError(f"Module '{module_name}' not found: {e}") from e + # Let other exceptions (including SystemExit) propagate naturally + # so Python prints the full traceback to stderr + + +def _execute_script(script_path: str, script_args: List[str], cwd: str) -> None: + """ + Execute a Python script. + + Args: + script_path: Path to the script to execute + script_args: Arguments to pass to the script + cwd: Current working directory for path resolution + + Raises: + TargetError: If script execution fails + """ + # Make script path absolute if it isn't already + if not os.path.isabs(script_path): + script_path = os.path.join(cwd, script_path) + + if not os.path.isfile(script_path): + raise TargetError(f"Script not found: {script_path}") + + # Replace sys.argv to match original script call + sys.argv = [script_path] + script_args + + try: + with open(script_path, 'rb') as f: + source_code = f.read() + + except FileNotFoundError as e: + raise TargetError(f"Script file not found: {script_path}") from e + except PermissionError as e: + raise TargetError(f"Permission denied reading script: {script_path}") from e + + main_module = types.ModuleType("__main__") + main_module.__file__ = script_path + main_module.__builtins__ = __builtins__ + # gh-140729: Create a __mp_main__ module to allow pickling + sys.modules['__main__'] = sys.modules['__mp_main__'] = main_module + + try: + code = compile(source_code, script_path, 'exec', module='__main__') + except SyntaxError as e: + raise TargetError(f"Syntax error in script {script_path}: {e}") from e + + # Execute the script - let exceptions propagate naturally so Python + # prints the full traceback to stderr + exec(code, main_module.__dict__) + + +def main() -> NoReturn: + """ + Main coordinator function. + + This function coordinates the startup of a target Python process + with the sample profiler by signaling when the process is ready + to be profiled. + """ + # Phase 1: Parse arguments and set up environment + # Errors here are coordinator errors, not script errors + try: + # Parse and validate arguments + sync_port, cwd, target_args = _validate_arguments(sys.argv) + + # Set up execution environment + _setup_environment(cwd) + + # Determine execution type and validate target exists + is_module = target_args[0] == "-m" + if is_module: + if len(target_args) < 2: + raise ArgumentError("Module name required after -m") + module_name = target_args[1] + module_args = target_args[2:] + + if importlib.util.find_spec(module_name) is None: + raise TargetError(f"Module not found: {module_name}") + else: + script_path = target_args[0] + script_args = target_args[1:] + # Match the path resolution logic in _execute_script + check_path = script_path if os.path.isabs(script_path) else os.path.join(cwd, script_path) + if not os.path.isfile(check_path): + raise TargetError(f"Script not found: {script_path}") + + # Signal readiness to profiler + _signal_readiness(sync_port) + + except CoordinatorError as e: + print(f"Profiler coordinator error: {e}", file=sys.stderr) + sys.exit(1) + except KeyboardInterrupt: + print("Interrupted", file=sys.stderr) + sys.exit(1) + + # Phase 2: Execute the target script/module + # Let exceptions propagate naturally so Python prints full tracebacks + if is_module: + _execute_module(module_name, module_args) + else: + _execute_script(script_path, script_args, cwd) + + # Normal exit + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/Lib/profiling/sampling/cli.py b/Lib/profiling/sampling/cli.py new file mode 100644 index 00000000000..67adf4c9350 --- /dev/null +++ b/Lib/profiling/sampling/cli.py @@ -0,0 +1,1462 @@ +"""Command-line interface for the sampling profiler.""" + +import argparse +import importlib.util +import locale +import os +import re +import selectors +import socket +import subprocess +import sys +import time +import webbrowser +from contextlib import nullcontext + +from .errors import SamplingUnknownProcessError, SamplingModuleNotFoundError, SamplingScriptNotFoundError +from .sample import sample, sample_live, dump_stack, _is_process_running +from .dump import print_stack_dump +from .pstats_collector import PstatsCollector +from .stack_collector import CollapsedStackCollector, FlamegraphCollector, DiffFlamegraphCollector +from .heatmap_collector import HeatmapCollector +from .gecko_collector import GeckoCollector +from .jsonl_collector import JsonlCollector +from .binary_collector import BinaryCollector +from .binary_reader import BinaryReader +from .constants import ( + MICROSECONDS_PER_SECOND, + PROFILING_MODE_ALL, + PROFILING_MODE_WALL, + PROFILING_MODE_CPU, + PROFILING_MODE_GIL, + PROFILING_MODE_EXCEPTION, + SORT_MODE_NSAMPLES, + SORT_MODE_TOTTIME, + SORT_MODE_CUMTIME, + SORT_MODE_SAMPLE_PCT, + SORT_MODE_CUMUL_PCT, + SORT_MODE_NSAMPLES_CUMUL, +) + +try: + from ._child_monitor import ChildProcessMonitor +except ImportError: + # _remote_debugging module not available on this platform (e.g., WASI) + ChildProcessMonitor = None + +try: + from .live_collector import LiveStatsCollector +except ImportError: + LiveStatsCollector = None + + +class CustomFormatter( + argparse.ArgumentDefaultsHelpFormatter, + argparse.RawDescriptionHelpFormatter, +): + """Custom formatter that combines default values display with raw description formatting.""" + pass + + +class DiffFlamegraphAction(argparse.Action): + """Custom action for --diff-flamegraph that sets both format and baseline path.""" + def __call__(self, parser, namespace, values, option_string=None): + namespace.format = 'diff_flamegraph' + namespace.diff_baseline = values + + +_HELP_DESCRIPTION = """Sample a process's stack frames and generate profiling data. + +Examples: + # Run and profile a script + `python -m profiling.sampling run script.py arg1 arg2` + + # Attach to a running process + `python -m profiling.sampling attach 1234` + + # Dump a running process's current stack + `python -m profiling.sampling dump 1234` + + # Live interactive mode for a script + `python -m profiling.sampling run --live script.py` + + # Live interactive mode for a running process + `python -m profiling.sampling attach --live 1234` + +Use `python -m profiling.sampling --help` for command-specific help.""" + + +# Constants for socket synchronization +_SYNC_TIMEOUT_SEC = 5.0 +_PROCESS_KILL_TIMEOUT_SEC = 2.0 +_READY_MESSAGE = b"ready" +_RECV_BUFFER_SIZE = 1024 +_BINARY_PROFILE_HEADER_SIZE = 64 +_BINARY_PROFILE_MAGICS = (b"HCAT", b"TACH") + +# Format configuration +FORMAT_EXTENSIONS = { + "pstats": "pstats", + "collapsed": "txt", + "flamegraph": "html", + "diff_flamegraph": "html", + "gecko": "json", + "heatmap": "html", + "jsonl": "jsonl", + "binary": "bin", +} + +COLLECTOR_MAP = { + "pstats": PstatsCollector, + "collapsed": CollapsedStackCollector, + "flamegraph": FlamegraphCollector, + "diff_flamegraph": DiffFlamegraphCollector, + "gecko": GeckoCollector, + "heatmap": HeatmapCollector, + "jsonl": JsonlCollector, + "binary": BinaryCollector, +} + +BROWSER_COMPATIBLE_FORMATS = ("flamegraph", "diff_flamegraph", "heatmap") + + +def _setup_child_monitor(args, parent_pid): + # Build CLI args for child profilers (excluding --subprocesses to avoid recursion) + child_cli_args = _build_child_profiler_args(args) + + # Build output pattern + output_pattern = _build_output_pattern(args) + + return ChildProcessMonitor( + pid=parent_pid, + cli_args=child_cli_args, + output_pattern=output_pattern, + ) + + +def _get_child_monitor_context(args, pid): + if getattr(args, 'subprocesses', False): + return _setup_child_monitor(args, pid) + return nullcontext() + + +def _build_child_profiler_args(args): + child_args = [] + + # Sampling options + hz = MICROSECONDS_PER_SECOND // args.sample_interval_usec + child_args.extend(["-r", str(hz)]) + if args.duration is not None: + child_args.extend(["-d", str(args.duration)]) + if args.all_threads: + child_args.append("-a") + if args.realtime_stats: + child_args.append("--realtime-stats") + if args.native: + child_args.append("--native") + if not args.gc: + child_args.append("--no-gc") + if args.opcodes: + child_args.append("--opcodes") + if args.async_aware: + child_args.append("--async-aware") + async_mode = getattr(args, 'async_mode', 'running') + if async_mode != "running": + child_args.extend(["--async-mode", async_mode]) + + # Mode options + mode = getattr(args, 'mode', 'wall') + if mode != "wall": + child_args.extend(["--mode", mode]) + + # Format options (skip pstats as it's the default) + if args.format == "diff_flamegraph": + child_args.extend(["--diff-flamegraph", args.diff_baseline]) + elif args.format != "pstats": + child_args.append(f"--{args.format}") + + return child_args + + +def _build_output_pattern(args): + """Build output filename pattern for child profilers. + + The pattern uses {pid} as a placeholder which will be replaced with the + actual child PID using str.replace(), so user filenames with braces are safe. + """ + if args.outfile: + # User specified output - add PID to filename + base, ext = os.path.splitext(args.outfile) + if ext: + return f"{base}_{{pid}}{ext}" + else: + return f"{args.outfile}_{{pid}}" + else: + # Use default pattern based on format (consistent _ separator) + extension = FORMAT_EXTENSIONS.get(args.format, "txt") + if args.format == "heatmap": + return "heatmap_{pid}" + if args.format == "pstats": + # pstats defaults to stdout, but for subprocesses we need files + return "profile_{pid}.pstats" + return f"{args.format}_{{pid}}.{extension}" + + +def _parse_mode(mode_string): + """Convert mode string to mode constant.""" + mode_map = { + "wall": PROFILING_MODE_WALL, + "cpu": PROFILING_MODE_CPU, + "gil": PROFILING_MODE_GIL, + "exception": PROFILING_MODE_EXCEPTION, + } + return mode_map[mode_string] + + +def _check_process_died(process): + """Check if process died and raise an error with stderr if available.""" + if process.poll() is None: + return + + # Process died - try to get stderr for error message + stderr_msg = "" + if process.stderr: + try: + stderr_msg = process.stderr.read().decode().strip() + except (OSError, UnicodeDecodeError): + pass + + if stderr_msg: + raise RuntimeError(stderr_msg) + raise RuntimeError(f"Process exited with code {process.returncode}") + + +def _wait_for_ready_signal(sync_sock, process, timeout): + """Wait for the ready signal from the subprocess, checking for early death.""" + deadline = time.monotonic() + timeout + sel = selectors.DefaultSelector() + sel.register(sync_sock, selectors.EVENT_READ) + + try: + while True: + _check_process_died(process) + + remaining = deadline - time.monotonic() + if remaining <= 0: + raise socket.timeout("timed out") + + if not sel.select(timeout=min(0.1, remaining)): + continue + + conn, _ = sync_sock.accept() + try: + ready_signal = conn.recv(_RECV_BUFFER_SIZE) + finally: + conn.close() + + if ready_signal != _READY_MESSAGE: + raise RuntimeError(f"Invalid ready signal received: {ready_signal!r}") + return + finally: + sel.close() + + +def _run_with_sync(original_cmd, suppress_output=False): + """Run a command with socket-based synchronization and return the process.""" + # Create a TCP socket for synchronization with better socket options + with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sync_sock: + # Set SO_REUSEADDR to avoid "Address already in use" errors + sync_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sync_sock.bind(("127.0.0.1", 0)) # Let OS choose a free port + sync_port = sync_sock.getsockname()[1] + sync_sock.listen(1) + sync_sock.settimeout(_SYNC_TIMEOUT_SEC) + + # Get current working directory to preserve it + cwd = os.getcwd() + + # Build command using the sync coordinator + target_args = original_cmd[1:] # Remove python executable + cmd = ( + sys.executable, + "-m", + "profiling.sampling._sync_coordinator", + str(sync_port), + cwd, + ) + tuple(target_args) + + # Start the process with coordinator + # When suppress_output=True (live mode), capture stderr so we can + # report errors if the process dies before signaling ready. + # When suppress_output=False (normal mode), let stderr inherit so + # script errors print to the terminal. + popen_kwargs = {} + if suppress_output: + popen_kwargs["stdin"] = subprocess.DEVNULL + popen_kwargs["stdout"] = subprocess.DEVNULL + popen_kwargs["stderr"] = subprocess.PIPE + + process = subprocess.Popen(cmd, **popen_kwargs) + + try: + _wait_for_ready_signal(sync_sock, process, _SYNC_TIMEOUT_SEC) + except socket.timeout: + # If we timeout, kill the process and raise an error + if process.poll() is None: + process.terminate() + try: + process.wait(timeout=_PROCESS_KILL_TIMEOUT_SEC) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + raise RuntimeError( + "Process failed to signal readiness within timeout" + ) + + return process + + +_RATE_PATTERN = re.compile(r''' + ^ # Start of string + ( # Group 1: The numeric value + \d+ # One or more digits (integer part) + (?:\.\d+)? # Optional: decimal point followed by digits + ) # Examples: "10", "0.5", "100.25" + ( # Group 2: Optional unit suffix + hz # "hz" - hertz + | khz # "khz" - kilohertz + | k # "k" - shorthand for kilohertz + )? # Suffix is optional (bare number = Hz) + $ # End of string + ''', re.VERBOSE | re.IGNORECASE) + + +def _parse_sampling_rate(rate_str: str) -> int: + """Parse sampling rate string to microseconds.""" + rate_str = rate_str.strip().lower() + + match = _RATE_PATTERN.match(rate_str) + if not match: + raise argparse.ArgumentTypeError( + f"Invalid sampling rate format: {rate_str}. " + "Expected: number followed by optional suffix (hz, khz, k) with no spaces (e.g., 10khz)" + ) + + number_part = match.group(1) + suffix = match.group(2) or '' + + # Determine multiplier based on suffix + suffix_map = { + 'hz': 1, + 'khz': 1000, + 'k': 1000, + } + multiplier = suffix_map.get(suffix, 1) + hz = float(number_part) * multiplier + if hz <= 0: + raise argparse.ArgumentTypeError(f"Sampling rate must be positive: {rate_str}") + + interval_usec = int(MICROSECONDS_PER_SECOND / hz) + if interval_usec < 1: + raise argparse.ArgumentTypeError(f"Sampling rate too high: {rate_str}") + + return interval_usec + + +def _add_sampling_options(parser): + """Add sampling configuration options to a parser.""" + sampling_group = parser.add_argument_group("Sampling configuration") + sampling_group.add_argument( + "-r", + "--sampling-rate", + type=_parse_sampling_rate, + default="1khz", + metavar="RATE", + dest="sample_interval_usec", + help="sampling rate (e.g., 10000, 10khz, 10k)", + ) + sampling_group.add_argument( + "-d", + "--duration", + type=int, + default=None, + metavar="SECONDS", + help="Sampling duration (default: run to completion)", + ) + sampling_group.add_argument( + "-a", + "--all-threads", + action="store_true", + help="Sample all threads in the process instead of just the main thread", + ) + sampling_group.add_argument( + "--realtime-stats", + action="store_true", + help="Print real-time sampling statistics (Hz, mean, min, max) during profiling", + ) + sampling_group.add_argument( + "--native", + action="store_true", + help='Include artificial `` frames to denote calls to non-Python code', + ) + sampling_group.add_argument( + "--no-gc", + action="store_false", + dest="gc", + help='Don\'t include artificial `` frames to denote active garbage collection', + ) + sampling_group.add_argument( + "--opcodes", + action="store_true", + help="Gather bytecode opcode information for instruction-level profiling " + "(shows which bytecode instructions are executing, including specializations).", + ) + sampling_group.add_argument( + "--async-aware", + action="store_true", + help="Enable async-aware profiling (uses task-based stack reconstruction)", + ) + sampling_group.add_argument( + "--subprocesses", + action="store_true", + help="Also profile subprocesses. Each subprocess gets its own profiler and output file.", + ) + sampling_group.add_argument( + "--blocking", + action="store_true", + help="Stop all threads in target process before sampling to get consistent snapshots. " + "Uses thread_suspend on macOS and ptrace on Linux. Adds overhead but ensures memory " + "reads are from a frozen state.", + ) + + +def _add_mode_options(parser): + """Add mode options to a parser.""" + mode_group = parser.add_argument_group("Mode options") + mode_group.add_argument( + "--mode", + choices=["wall", "cpu", "gil", "exception"], + default="wall", + help="Sampling mode: wall (all samples), cpu (only samples when thread is on CPU), " + "gil (only samples when thread holds the GIL), " + "exception (only samples when thread has an active exception). " + "Incompatible with `--async-aware`", + ) + mode_group.add_argument( + "--async-mode", + choices=["running", "all"], + default="running", + help='Async profiling mode: "running" (only running task) ' + 'or "all" (all tasks including waiting). Requires `--async-aware`', + ) + + +def _add_format_options(parser, include_compression=True, include_binary=True): + """Add output format options to a parser.""" + output_group = parser.add_argument_group("Output options") + format_group = output_group.add_mutually_exclusive_group() + format_group.add_argument( + "--pstats", + action="store_const", + const="pstats", + dest="format", + help="Generate pstats output (default)", + ) + format_group.add_argument( + "--collapsed", + action="store_const", + const="collapsed", + dest="format", + help="Generate collapsed stack traces for flamegraphs", + ) + format_group.add_argument( + "--flamegraph", + action="store_const", + const="flamegraph", + dest="format", + help="Generate interactive HTML flamegraph visualization", + ) + format_group.add_argument( + "--gecko", + action="store_const", + const="gecko", + dest="format", + help="Generate Gecko format for Firefox Profiler", + ) + format_group.add_argument( + "--heatmap", + action="store_const", + const="heatmap", + dest="format", + help="Generate interactive HTML heatmap visualization with line-level sample counts", + ) + format_group.add_argument( + "--diff-flamegraph", + metavar="BASELINE", + action=DiffFlamegraphAction, + help="Generate differential flamegraph comparing current profile to `BASELINE` binary file", + ) + format_group.add_argument( + "--jsonl", + action="store_const", + const="jsonl", + dest="format", + help="Generate newline-delimited JSON (JSONL) for programmatic consumers", + ) + if include_binary: + format_group.add_argument( + "--binary", + action="store_const", + const="binary", + dest="format", + help="Generate high-performance binary format (use `replay` command to convert)", + ) + parser.set_defaults(format="pstats", diff_baseline=None) + + if include_compression: + output_group.add_argument( + "--compression", + choices=["auto", "zstd", "none"], + default="auto", + help="Compression for binary format: auto (use zstd if available), zstd, none", + ) + + output_group.add_argument( + "-o", + "--output", + dest="outfile", + help="Output path (default: `stdout` for `pstats` text; with `-o`, `pstats` is binary). " + "Auto-generated for other formats. For heatmap: directory name (default: `heatmap_PID`)", + ) + output_group.add_argument( + "--browser", + action="store_true", + help=( + "Automatically open HTML output " + f"({', '.join('--' + f.replace('_', '-') for f in BROWSER_COMPATIBLE_FORMATS)}) " + "in browser. " + "When using `--subprocesses`, only the main process opens the browser" + ), + ) + + +def _add_pstats_options(parser): + """Add pstats-specific display options to a parser.""" + pstats_group = parser.add_argument_group("pstats format options") + pstats_group.add_argument( + "--sort", + choices=[ + "nsamples", + "tottime", + "cumtime", + "sample-pct", + "cumul-pct", + "nsamples-cumul", + "name", + ], + default=None, + help="Sort order for pstats output (default: nsamples)", + ) + pstats_group.add_argument( + "-l", + "--limit", + type=int, + default=None, + help="Limit the number of rows in the output (default: 15)", + ) + pstats_group.add_argument( + "--no-summary", + action="store_true", + help="Disable the summary section in the pstats output", + ) + + +def _add_dump_options(parser): + """Add one-shot stack dump options to a parser.""" + dump_group = parser.add_argument_group("Dump options") + dump_group.add_argument( + "-a", + "--all-threads", + action="store_true", + help="Dump all threads in the process instead of just the main thread", + ) + dump_group.add_argument( + "--native", + action="store_true", + help='Include artificial `` frames to denote calls to non-Python code', + ) + dump_group.add_argument( + "--no-gc", + action="store_false", + dest="gc", + help='Don\'t include artificial `` frames to denote active garbage collection', + ) + dump_group.add_argument( + "--opcodes", + action="store_true", + help="Show bytecode opcode names when available.", + ) + dump_group.add_argument( + "--async-aware", + action="store_true", + help="Enable async-aware stack reconstruction", + ) + dump_group.add_argument( + "--async-mode", + choices=["running", "all"], + default=argparse.SUPPRESS, + help='Async stack mode: "running" (only running task) ' + 'or "all" (all tasks including waiting, default for dump). ' + "Requires `--async-aware`", + ) + dump_group.add_argument( + "--blocking", + action="store_true", + help="Stop all threads in target process before dumping the stack.", + ) + + +def _sort_to_mode(sort_choice): + """Convert sort choice string to SORT_MODE constant.""" + sort_map = { + "nsamples": SORT_MODE_NSAMPLES, + "tottime": SORT_MODE_TOTTIME, + "cumtime": SORT_MODE_CUMTIME, + "sample-pct": SORT_MODE_SAMPLE_PCT, + "cumul-pct": SORT_MODE_CUMUL_PCT, + "nsamples-cumul": SORT_MODE_NSAMPLES_CUMUL, + "name": -1, + } + return sort_map.get(sort_choice, SORT_MODE_NSAMPLES) + + +def _capture_config_from_args(args): + return { + "all_threads": args.all_threads, + "native": args.native, + "gc": args.gc, + "opcodes": args.opcodes, + "blocking": args.blocking, + } + + +def _create_collector(format_type, sample_interval_usec, skip_idle, opcodes=False, + mode=None, output_file=None, compression='auto', + diff_baseline=None, capture_config=None): + """Create the appropriate collector based on format type. + + Args: + format_type: The output format ('pstats', 'collapsed', 'flamegraph', + 'gecko', 'heatmap', 'jsonl', 'binary', 'diff_flamegraph') + sample_interval_usec: Sampling interval in microseconds + skip_idle: Whether to skip idle samples + opcodes: Whether to collect opcode information (only used by gecko format + for creating interval markers in Firefox Profiler) + mode: Profiling mode for collectors that expose it in metadata + output_file: Output file path (required for binary format) + compression: Compression type for binary format ('auto', 'zstd', 'none') + diff_baseline: Path to baseline binary file for differential flamegraph + capture_config: Capture feature mapping for binary profiles and diffs + + Returns: + A collector instance of the appropriate type + """ + collector_class = COLLECTOR_MAP.get(format_type) + if collector_class is None: + raise ValueError(f"Unknown format: {format_type}") + + if format_type == "diff_flamegraph": + if diff_baseline is None: + raise ValueError("Differential flamegraph requires a baseline file") + if not os.path.exists(diff_baseline): + raise ValueError(f"Baseline file not found: {diff_baseline}") + return collector_class( + sample_interval_usec, + baseline_binary_path=diff_baseline, + skip_idle=skip_idle, + mode=mode, + capture_config=capture_config, + ) + + # Binary format requires output file and compression + if format_type == "binary": + if output_file is None: + raise ValueError("Binary format requires an output file") + return collector_class(output_file, sample_interval_usec, skip_idle=skip_idle, + compression=compression, mode=mode, + capture_config=capture_config) + + # Gecko format never skips idle (it needs both GIL and CPU data) + # and is the only format that uses opcodes for interval markers + if format_type == "gecko": + skip_idle = False + return collector_class(sample_interval_usec, skip_idle=skip_idle, opcodes=opcodes) + + if format_type == "jsonl": + return collector_class( + sample_interval_usec, skip_idle=skip_idle, mode=mode + ) + + return collector_class(sample_interval_usec, skip_idle=skip_idle) + + +def _generate_output_filename(format_type, pid): + """Generate output filename based on format and PID. + + Args: + format_type: The output format + pid: Process ID + + Returns: + Generated filename + """ + extension = FORMAT_EXTENSIONS.get(format_type, "txt") + # For heatmap, use cleaner directory name without extension + if format_type == "heatmap": + return f"heatmap_{pid}" + return f"{format_type}_{pid}.{extension}" + + +def _open_in_browser(path): + """Open a file or directory in the default web browser. + + Args: + path: File path or directory path to open + + For directories (heatmap), opens the index.html file inside. + """ + abs_path = os.path.abspath(path) + + # For heatmap directories, open the index.html file + if os.path.isdir(abs_path): + index_path = os.path.join(abs_path, 'index.html') + if os.path.exists(index_path): + abs_path = index_path + else: + print(f"Warning: Could not find index.html in {path}", file=sys.stderr) + return + + file_url = f"file://{abs_path}" + try: + webbrowser.open(file_url) + except Exception as e: + print(f"Warning: Could not open browser: {e}", file=sys.stderr) + + +def _validate_replay_input_file(filename): + """Validate that the replay input looks like a sampling binary profile.""" + try: + with open(filename, "rb") as file: + header = file.read(_BINARY_PROFILE_HEADER_SIZE) + except OSError as exc: + sys.exit(f"Error: Could not read input file {filename}: {exc}") + + if ( + len(header) < _BINARY_PROFILE_HEADER_SIZE + or header[:4] not in _BINARY_PROFILE_MAGICS + ): + sys.exit( + "Error: Input file is not a binary sampling profile. " + "The replay command only accepts files created with --binary" + ) + + +def _replay_with_reader(args, reader): + """Replay samples from an open binary reader.""" + info = reader.get_info() + interval = info['sample_interval_us'] + + print(f"Replaying {info['sample_count']} samples from {args.input_file}") + print(f" Sample interval: {interval} us") + print( + " Compression: " + f"{'zstd' if info.get('compression_type', 0) == 1 else 'none'}" + ) + + collector = _create_collector( + args.format, interval, skip_idle=False, + mode=info.get("mode"), + diff_baseline=args.diff_baseline, + capture_config=info.get("capture_config"), + ) + + def progress_callback(current, total): + if total > 0: + pct = current / total + bar_width = 40 + filled = int(bar_width * pct) + bar = '█' * filled + '░' * (bar_width - filled) + print( + f"\r [{bar}] {pct*100:5.1f}% ({current:,}/{total:,})", + end="", + flush=True, + ) + + count = reader.replay_samples(collector, progress_callback) + if hasattr(collector, "set_replay_stats"): + collector.set_replay_stats(info) + if hasattr(collector, "set_mode"): + collector.set_mode(info.get("mode")) + print() + + if args.format == "pstats": + if args.outfile: + collector.export(args.outfile) + else: + sort_choice = ( + args.sort if args.sort is not None else "nsamples" + ) + limit = args.limit if args.limit is not None else 15 + sort_mode = _sort_to_mode(sort_choice) + collector.print_stats( + sort_mode, limit, not args.no_summary, + info.get("mode") if info.get("mode") is not None + else PROFILING_MODE_WALL + ) + else: + filename = ( + args.outfile + or _generate_output_filename(args.format, os.getpid()) + ) + export_ok = collector.export(filename) + + # Auto-open browser for HTML output if --browser flag is set + if ( + export_ok + and args.format in BROWSER_COMPATIBLE_FORMATS + and getattr(args, 'browser', False) + ): + _open_in_browser(filename) + + print(f"Replayed {count} samples") + + +def _handle_output(collector, args, pid, mode): + """Handle output for the collector based on format and arguments. + + Args: + collector: The collector instance with profiling data + args: Parsed command-line arguments + pid: Process ID (for generating filenames) + mode: Profiling mode used + """ + if args.format == "binary": + # Binary format already wrote to file incrementally, just finalize + collector.export(None) + filename = collector.filename + print(f"Binary profile written to {filename} ({collector.total_samples} samples)") + elif args.format == "pstats": + if args.outfile: + # If outfile is a directory, generate filename inside it + if os.path.isdir(args.outfile): + filename = os.path.join(args.outfile, _generate_output_filename(args.format, pid)) + collector.export(filename) + else: + collector.export(args.outfile) + else: + # Print to stdout with defaults applied + sort_choice = args.sort if args.sort is not None else "nsamples" + limit = args.limit if args.limit is not None else 15 + sort_mode = _sort_to_mode(sort_choice) + collector.print_stats( + sort_mode, limit, not args.no_summary, mode + ) + else: + # Export to file + if args.outfile and os.path.isdir(args.outfile): + # If outfile is a directory, generate filename inside it + filename = os.path.join(args.outfile, _generate_output_filename(args.format, pid)) + else: + filename = args.outfile or _generate_output_filename(args.format, pid) + export_ok = collector.export(filename) + + # Auto-open browser for HTML output if --browser flag is set + if ( + export_ok + and args.format in BROWSER_COMPATIBLE_FORMATS + and getattr(args, 'browser', False) + ): + _open_in_browser(filename) + + +def _validate_args(args, parser): + """Validate format-specific options and live mode requirements. + + Args: + args: Parsed command-line arguments + parser: ArgumentParser instance for error reporting + """ + command = getattr(args, 'command', None) + + if command == "replay": + return + + # Check if live mode is available + if hasattr(args, 'live') and args.live and LiveStatsCollector is None: + parser.error( + "Live mode requires the curses module, which is not available." + ) + + # --subprocesses is incompatible with --live + if hasattr(args, 'subprocesses') and args.subprocesses: + if ChildProcessMonitor is None: + parser.error( + "--subprocesses is not available on this platform " + "(requires _remote_debugging module)." + ) + if hasattr(args, 'live') and args.live: + parser.error("--subprocesses is incompatible with --live mode.") + + # Async-aware mode is incompatible with options that need thread data. + if getattr(args, 'async_aware', False): + issues = [] + if getattr(args, 'native', False): + issues.append("--native") + if not getattr(args, 'gc', True): + issues.append("--no-gc") + if getattr(args, 'format', None) == "binary": + issues.append("--binary") + if hasattr(args, 'mode') and args.mode != "wall": + issues.append(f"--mode={args.mode}") + if hasattr(args, 'all_threads') and args.all_threads: + issues.append("--all-threads") + if issues: + parser.error( + f"Options {', '.join(issues)} are incompatible with --async-aware. " + "Async-aware profiling uses task-based stack reconstruction." + ) + + # --async-mode requires --async-aware when explicitly set + if not getattr(args, 'async_aware', False): + if command == "dump": + # dump uses SUPPRESS default, so attr only exists if user passed it + if hasattr(args, 'async_mode'): + parser.error("--async-mode requires --async-aware to be enabled.") + elif hasattr(args, 'async_mode') and args.async_mode != "running": + parser.error("--async-mode requires --async-aware to be enabled.") + + if command == "dump": + return + + # Warn about blocking mode with aggressive sampling intervals + if args.blocking and args.sample_interval_usec < 100: + print( + f"Warning: --blocking with a {args.sample_interval_usec} µs interval will stop all threads " + f"{1_000_000 // args.sample_interval_usec} times per second. " + "Consider using --sampling-rate 1khz or lower to reduce overhead.", + file=sys.stderr + ) + + # Live mode is incompatible with format options + if hasattr(args, 'live') and args.live: + if args.format != "pstats": + format_flag = f"--{args.format}" + parser.error( + f"--live is incompatible with {format_flag}. Live mode uses a TUI interface." + ) + + # Live mode is also incompatible with pstats-specific options + issues = [] + if args.sort is not None: + issues.append("--sort") + if args.limit is not None: + issues.append("--limit") + if args.no_summary: + issues.append("--no-summary") + + if issues: + parser.error( + f"Options {', '.join(issues)} are incompatible with --live. " + "Live mode uses a TUI interface with its own controls." + ) + return + + # Validate gecko mode doesn't use non-wall mode + if args.format == "gecko" and getattr(args, 'mode', 'wall') != "wall": + parser.error( + "--mode option is incompatible with --gecko. " + "Gecko format automatically includes both GIL-holding and CPU status analysis." + ) + + # Validate --opcodes is only used with compatible formats + opcodes_compatible_formats = ("live", "gecko", "flamegraph", "diff_flamegraph", "heatmap", "binary") + if getattr(args, 'opcodes', False) and args.format not in opcodes_compatible_formats: + parser.error( + f"--opcodes is only compatible with {', '.join('--' + f for f in opcodes_compatible_formats)}." + ) + + # Validate pstats-specific options are only used with pstats format + if args.format != "pstats": + issues = [] + if args.sort is not None: + issues.append("--sort") + if args.limit is not None: + issues.append("--limit") + if args.no_summary: + issues.append("--no-summary") + + if issues: + format_flag = f"--{args.format}" + parser.error( + f"Options {', '.join(issues)} are only valid with --pstats, not {format_flag}" + ) + + +def main(): + """Main entry point for the CLI.""" + # Set locale for number formatting, restore on exit + old_locale = locale.setlocale(locale.LC_ALL, None) + locale.setlocale(locale.LC_ALL, "") + try: + _main() + finally: + locale.setlocale(locale.LC_ALL, old_locale) + + +def _main(): + # Create the main parser + parser = argparse.ArgumentParser( + description=_HELP_DESCRIPTION, + formatter_class=CustomFormatter, + ) + + # Create subparsers for commands + subparsers = parser.add_subparsers( + dest="command", required=True, help="Command to run" + ) + + # === RUN COMMAND === + run_parser = subparsers.add_parser( + "run", + help="Run and profile a script or module", + formatter_class=CustomFormatter, + description="""Run and profile a Python script or module + +Examples: + # Run and profile a module + `python -m profiling.sampling run -m mymodule arg1 arg2` + + # Generate flamegraph from a script + `python -m profiling.sampling run --flamegraph -o output.html script.py` + + # Profile with custom rate and duration + `python -m profiling.sampling run -r 5khz -d 30 script.py` + + # Save collapsed stacks to file + `python -m profiling.sampling run --collapsed -o stacks.txt script.py` + + # Live interactive mode for a script + `python -m profiling.sampling run --live script.py`""", + ) + run_parser.add_argument( + "-m", + "--module", + action="store_true", + help="Run target as a module (like `python -m`)", + ) + run_parser.add_argument( + "target", + help="Script file or module name to profile", + ) + run_parser.add_argument( + "args", + nargs=argparse.REMAINDER, + help="Arguments to pass to the script or module", + ) + run_parser.add_argument( + "--live", + action="store_true", + help="Interactive TUI profiler (top-like interface, press 'q' to quit, 's' to cycle sort)", + ) + _add_sampling_options(run_parser) + _add_mode_options(run_parser) + _add_format_options(run_parser) + _add_pstats_options(run_parser) + + # === ATTACH COMMAND === + attach_parser = subparsers.add_parser( + "attach", + help="Attach to and profile a running process", + formatter_class=CustomFormatter, + description="""Attach to a running process and profile it + +Examples: + # Profile all threads, sort by total time + `python -m profiling.sampling attach -a --sort tottime 1234` + + # Live interactive mode for a running process + `python -m profiling.sampling attach --live 1234`""", + ) + attach_parser.add_argument( + "pid", + type=int, + help="Process ID to attach to", + ) + attach_parser.add_argument( + "--live", + action="store_true", + help="Interactive TUI profiler (top-like interface, press 'q' to quit, 's' to cycle sort)", + ) + _add_sampling_options(attach_parser) + _add_mode_options(attach_parser) + _add_format_options(attach_parser) + _add_pstats_options(attach_parser) + + # === DUMP COMMAND === + dump_parser = subparsers.add_parser( + "dump", + help="Dump a running process's current stack", + formatter_class=CustomFormatter, + description="""Dump a running process's current Python stack + +Examples: + # Dump the main thread stack + `python -m profiling.sampling dump 1234` + + # Dump all thread stacks + `python -m profiling.sampling dump -a 1234`""", + ) + dump_parser.add_argument( + "pid", + type=int, + help="Process ID to dump", + ) + _add_dump_options(dump_parser) + + # === REPLAY COMMAND === + replay_parser = subparsers.add_parser( + "replay", + help="Replay a binary profile and convert to another format", + formatter_class=CustomFormatter, + description="""Replay a binary profile file and convert to another format + +Examples: + # Convert binary to flamegraph + `python -m profiling.sampling replay --flamegraph -o output.html profile.bin` + + # Convert binary to pstats and print to stdout + `python -m profiling.sampling replay profile.bin` + + # Convert binary to gecko format + `python -m profiling.sampling replay --gecko -o profile.json profile.bin`""", + ) + replay_parser.add_argument( + "input_file", + help="Binary profile file to replay", + ) + _add_format_options(replay_parser, include_compression=False, include_binary=False) + _add_pstats_options(replay_parser) + + # Parse arguments + args = parser.parse_args() + + # Validate arguments + _validate_args(args, parser) + + # Command dispatch table + command_handlers = { + "run": _handle_run, + "attach": _handle_attach, + "dump": _handle_dump, + "replay": _handle_replay, + } + + # Execute the appropriate command + handler = command_handlers.get(args.command) + if handler: + handler(args) + else: + parser.error(f"Unknown command: {args.command}") + + +def _handle_attach(args): + """Handle the 'attach' command.""" + if not _is_process_running(args.pid): + raise SamplingUnknownProcessError(args.pid) + # Check if live mode is requested + if args.live: + _handle_live_attach(args, args.pid) + return + + # Use PROFILING_MODE_ALL for gecko format + mode = ( + PROFILING_MODE_ALL + if args.format == "gecko" + else _parse_mode(args.mode) + ) + + # Determine skip_idle based on mode + skip_idle = ( + mode != PROFILING_MODE_WALL if mode != PROFILING_MODE_ALL else False + ) + + output_file = None + if args.format == "binary": + output_file = args.outfile or _generate_output_filename(args.format, args.pid) + + # Create the appropriate collector + collector = _create_collector( + args.format, args.sample_interval_usec, skip_idle, args.opcodes, mode, + output_file=output_file, + compression=getattr(args, 'compression', 'auto'), + diff_baseline=args.diff_baseline, + capture_config=_capture_config_from_args(args), + ) + + with _get_child_monitor_context(args, args.pid): + collector = sample( + args.pid, + collector, + duration_sec=args.duration, + all_threads=args.all_threads, + realtime_stats=args.realtime_stats, + mode=mode, + async_aware=args.async_mode if args.async_aware else None, + native=args.native, + gc=args.gc, + opcodes=args.opcodes, + blocking=args.blocking, + ) + _handle_output(collector, args, args.pid, mode) + + +def _handle_dump(args): + if not _is_process_running(args.pid): + raise SamplingUnknownProcessError(args.pid) + + # Async-aware reconstruction requires wall mode so every thread is sampled, + # not just those holding the GIL or on CPU. + mode = PROFILING_MODE_WALL if args.async_aware else PROFILING_MODE_ALL + async_mode = getattr(args, "async_mode", "all") if args.async_aware else None + try: + stack_frames = dump_stack( + args.pid, + all_threads=args.all_threads, + mode=mode, + async_aware=async_mode, + native=args.native, + gc=args.gc, + opcodes=args.opcodes, + blocking=args.blocking, + ) + except ProcessLookupError: + sys.exit( + f"No stack dump collected - process {args.pid} exited before " + "its stack could be read." + ) + + print_stack_dump(stack_frames, pid=args.pid) + + +def _handle_run(args): + """Handle the 'run' command.""" + # Validate target exists before launching subprocess + if args.module: + # Temporarily add cwd to sys.path so we can find modules in the + # current directory, matching the coordinator's behavior + cwd = os.getcwd() + added_cwd = False + if cwd not in sys.path: + sys.path.insert(0, cwd) + added_cwd = True + try: + if importlib.util.find_spec(args.target) is None: + raise SamplingModuleNotFoundError(args.target) + finally: + if added_cwd: + sys.path.remove(cwd) + else: + if not os.path.exists(args.target): + raise SamplingScriptNotFoundError(args.target) + + # Check if live mode is requested + if args.live: + _handle_live_run(args) + return + + # Build the command to run + if args.module: + cmd = (sys.executable, "-m", args.target, *args.args) + else: + cmd = (sys.executable, args.target, *args.args) + + # Run with synchronization + try: + process = _run_with_sync(cmd, suppress_output=False) + except RuntimeError as e: + sys.exit(f"Error: {e}") + + # Use PROFILING_MODE_ALL for gecko format + mode = ( + PROFILING_MODE_ALL + if args.format == "gecko" + else _parse_mode(args.mode) + ) + + # Determine skip_idle based on mode + skip_idle = ( + mode != PROFILING_MODE_WALL if mode != PROFILING_MODE_ALL else False + ) + + output_file = None + if args.format == "binary": + output_file = args.outfile or _generate_output_filename(args.format, process.pid) + + # Create the appropriate collector + collector = _create_collector( + args.format, args.sample_interval_usec, skip_idle, args.opcodes, mode, + output_file=output_file, + compression=getattr(args, 'compression', 'auto'), + diff_baseline=args.diff_baseline, + capture_config=_capture_config_from_args(args), + ) + + with _get_child_monitor_context(args, process.pid): + try: + collector = sample( + process.pid, + collector, + duration_sec=args.duration, + all_threads=args.all_threads, + realtime_stats=args.realtime_stats, + mode=mode, + async_aware=args.async_mode if args.async_aware else None, + native=args.native, + gc=args.gc, + opcodes=args.opcodes, + blocking=args.blocking, + ) + _handle_output(collector, args, process.pid, mode) + finally: + # Terminate the main subprocess - child profilers finish when their + # target processes exit + if process.poll() is None: + process.terminate() + try: + process.wait(timeout=_PROCESS_KILL_TIMEOUT_SEC) + except subprocess.TimeoutExpired: + process.kill() + process.wait() + + +def _handle_live_attach(args, pid): + """Handle live mode for an existing process.""" + mode = _parse_mode(args.mode) + + # Determine skip_idle based on mode + skip_idle = mode != PROFILING_MODE_WALL + + # Create live collector with default settings + collector = LiveStatsCollector( + args.sample_interval_usec, + skip_idle=skip_idle, + sort_by="tottime", # Default initial sort + limit=20, # Default limit + pid=pid, + mode=mode, + opcodes=args.opcodes, + async_aware=args.async_mode if args.async_aware else None, + ) + + # Sample in live mode + sample_live( + pid, + collector, + duration_sec=args.duration, + all_threads=args.all_threads, + realtime_stats=args.realtime_stats, + mode=mode, + async_aware=args.async_mode if args.async_aware else None, + native=args.native, + gc=args.gc, + opcodes=args.opcodes, + blocking=args.blocking, + ) + + +def _handle_live_run(args): + """Handle live mode for running a script/module.""" + # Build the command to run + if args.module: + cmd = (sys.executable, "-m", args.target, *args.args) + else: + cmd = (sys.executable, args.target, *args.args) + + # Run with synchronization, suppressing output for live mode + try: + process = _run_with_sync(cmd, suppress_output=True) + except RuntimeError as e: + sys.exit(f"Error: {e}") + + mode = _parse_mode(args.mode) + + # Determine skip_idle based on mode + skip_idle = mode != PROFILING_MODE_WALL + + # Create live collector with default settings + collector = LiveStatsCollector( + args.sample_interval_usec, + skip_idle=skip_idle, + sort_by="tottime", # Default initial sort + limit=20, # Default limit + pid=process.pid, + mode=mode, + opcodes=args.opcodes, + async_aware=args.async_mode if args.async_aware else None, + ) + + # Profile the subprocess in live mode + try: + sample_live( + process.pid, + collector, + duration_sec=args.duration, + all_threads=args.all_threads, + realtime_stats=args.realtime_stats, + mode=mode, + async_aware=args.async_mode if args.async_aware else None, + native=args.native, + gc=args.gc, + opcodes=args.opcodes, + blocking=args.blocking, + ) + finally: + # Clean up the subprocess and get any error output + returncode = process.poll() + if returncode is None: + # Process still running - terminate it + process.terminate() + try: + process.wait(timeout=_PROCESS_KILL_TIMEOUT_SEC) + except subprocess.TimeoutExpired: + process.kill() + # Ensure process is fully terminated + process.wait() + # Read any stderr output (tracebacks, errors, etc.) + if process.stderr: + with process.stderr: + try: + stderr = process.stderr.read() + if stderr: + print(stderr.decode(), file=sys.stderr) + except (OSError, ValueError): + # Ignore errors if pipe is already closed + pass + + +def _handle_replay(args): + """Handle the 'replay' command - convert binary profile to another format.""" + if not os.path.exists(args.input_file): + sys.exit(f"Error: Input file not found: {args.input_file}") + + _validate_replay_input_file(args.input_file) + + try: + with BinaryReader(args.input_file) as reader: + _replay_with_reader(args, reader) + except (OSError, ValueError) as exc: + sys.exit(f"Error: {exc}") + + +if __name__ == "__main__": + main() diff --git a/Lib/profiling/sampling/errors.py b/Lib/profiling/sampling/errors.py new file mode 100644 index 00000000000..0832ad2d438 --- /dev/null +++ b/Lib/profiling/sampling/errors.py @@ -0,0 +1,19 @@ +"""Custom exceptions for the sampling profiler.""" + +class SamplingProfilerError(Exception): + """Base exception for sampling profiler errors.""" + +class SamplingUnknownProcessError(SamplingProfilerError): + def __init__(self, pid): + self.pid = pid + super().__init__(f"Process with PID '{pid}' does not exist.") + +class SamplingScriptNotFoundError(SamplingProfilerError): + def __init__(self, script_path): + self.script_path = script_path + super().__init__(f"Script '{script_path}' not found.") + +class SamplingModuleNotFoundError(SamplingProfilerError): + def __init__(self, module_name): + self.module_name = module_name + super().__init__(f"Module '{module_name}' not found.") diff --git a/Lib/test/test_profiling/test_sampling_profiler/test_cli.py b/Lib/test/test_profiling/test_sampling_profiler/test_cli.py new file mode 100644 index 00000000000..e5dcd72c27e --- /dev/null +++ b/Lib/test/test_profiling/test_sampling_profiler/test_cli.py @@ -0,0 +1,1083 @@ +"""Tests for sampling profiler CLI argument parsing and functionality.""" + +import io +import json +import os +import subprocess +import sys +import tempfile +import unittest +from types import SimpleNamespace +from unittest import mock + +try: + import _remote_debugging # noqa: F401 +except ImportError: + raise unittest.SkipTest( + "Test only runs when _remote_debugging is available" + ) + +from test.support import ( + force_not_colorized, + is_emscripten, + requires_remote_subprocess_debugging, +) + +from profiling.sampling.binary_reader import BinaryReader +from profiling.sampling.cli import ( + FORMAT_EXTENSIONS, + _create_collector, + _generate_output_filename, + _handle_output, + _replay_with_reader, + main, +) +from profiling.sampling.constants import ( + PROFILING_MODE_ALL, + PROFILING_MODE_CPU, + PROFILING_MODE_WALL, +) +from profiling.sampling.errors import SamplingScriptNotFoundError, SamplingModuleNotFoundError, SamplingUnknownProcessError +from profiling.sampling.jsonl_collector import JsonlCollector + +class TestSampleProfilerCLI(unittest.TestCase): + def _setup_sync_mocks(self, mock_socket, mock_popen): + """Helper to set up socket and process mocks for coordinator tests.""" + # Mock the sync socket with context manager support + mock_sock_instance = mock.MagicMock() + mock_sock_instance.getsockname.return_value = ("127.0.0.1", 12345) + + # Mock the connection with context manager support + mock_conn = mock.MagicMock() + mock_conn.recv.return_value = b"ready" + mock_conn.__enter__.return_value = mock_conn + mock_conn.__exit__.return_value = None + + # Mock accept() to return (connection, address) and support indexing + mock_accept_result = mock.MagicMock() + mock_accept_result.__getitem__.return_value = ( + mock_conn # [0] returns the connection + ) + mock_sock_instance.accept.return_value = mock_accept_result + + # Mock socket with context manager support + mock_sock_instance.__enter__.return_value = mock_sock_instance + mock_sock_instance.__exit__.return_value = None + mock_socket.return_value = mock_sock_instance + + # Mock the subprocess + mock_process = mock.MagicMock() + mock_process.pid = 12345 + mock_process.poll.return_value = None + mock_popen.return_value = mock_process + return mock_process + + def _verify_coordinator_command(self, mock_popen, expected_target_args): + """Helper to verify the coordinator command was called correctly.""" + args, kwargs = mock_popen.call_args + coordinator_cmd = args[0] + self.assertEqual(coordinator_cmd[0], sys.executable) + self.assertEqual(coordinator_cmd[1], "-m") + self.assertEqual( + coordinator_cmd[2], "profiling.sampling._sync_coordinator" + ) + self.assertEqual(coordinator_cmd[3], "12345") # port + # cwd is coordinator_cmd[4] + self.assertEqual(coordinator_cmd[5:], expected_target_args) + + @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") + @requires_remote_subprocess_debugging() + def test_cli_module_argument_parsing(self): + test_args = ["profiling.sampling.cli", "run", "-m", "mymodule"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + mock.patch("subprocess.Popen") as mock_popen, + mock.patch("socket.socket") as mock_socket, + mock.patch("profiling.sampling.cli._wait_for_ready_signal"), + mock.patch("importlib.util.find_spec", return_value=True), + ): + self._setup_sync_mocks(mock_socket, mock_popen) + main() + + self._verify_coordinator_command(mock_popen, ("-m", "mymodule")) + # Verify sample was called once (exact arguments will vary with the new API) + mock_sample.assert_called_once() + + @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") + @requires_remote_subprocess_debugging() + def test_cli_module_with_arguments(self): + test_args = [ + "profiling.sampling.cli", + "run", + "-m", + "mymodule", + "arg1", + "arg2", + "--flag", + ] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + mock.patch("subprocess.Popen") as mock_popen, + mock.patch("socket.socket") as mock_socket, + mock.patch("profiling.sampling.cli._wait_for_ready_signal"), + mock.patch("importlib.util.find_spec", return_value=True), + ): + self._setup_sync_mocks(mock_socket, mock_popen) + main() + + self._verify_coordinator_command( + mock_popen, ("-m", "mymodule", "arg1", "arg2", "--flag") + ) + mock_sample.assert_called_once() + + @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") + def test_cli_script_argument_parsing(self): + test_args = ["profiling.sampling.cli", "run", "myscript.py"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + mock.patch("subprocess.Popen") as mock_popen, + mock.patch("socket.socket") as mock_socket, + mock.patch("profiling.sampling.cli._wait_for_ready_signal"), + mock.patch("os.path.exists", return_value=True), + ): + self._setup_sync_mocks(mock_socket, mock_popen) + main() + + self._verify_coordinator_command(mock_popen, ("myscript.py",)) + mock_sample.assert_called_once() + + @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") + def test_cli_script_with_arguments(self): + test_args = [ + "profiling.sampling.cli", + "run", + "myscript.py", + "arg1", + "arg2", + "--flag", + ] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + mock.patch("subprocess.Popen") as mock_popen, + mock.patch("socket.socket") as mock_socket, + mock.patch("profiling.sampling.cli._wait_for_ready_signal"), + mock.patch("os.path.exists", return_value=True), + ): + # Use the helper to set up mocks consistently + mock_process = self._setup_sync_mocks(mock_socket, mock_popen) + # Override specific behavior for this test + mock_process.wait.side_effect = [ + subprocess.TimeoutExpired(test_args, 0.1), + None, + ] + + main() + + # Verify the coordinator command was called + args, kwargs = mock_popen.call_args + coordinator_cmd = args[0] + self.assertEqual(coordinator_cmd[0], sys.executable) + self.assertEqual(coordinator_cmd[1], "-m") + self.assertEqual( + coordinator_cmd[2], "profiling.sampling._sync_coordinator" + ) + self.assertEqual(coordinator_cmd[3], "12345") # port + # cwd is coordinator_cmd[4] + self.assertEqual( + coordinator_cmd[5:], ("myscript.py", "arg1", "arg2", "--flag") + ) + + def test_cli_mutually_exclusive_pid_module(self): + # In new CLI, attach and run are separate subcommands, so this test + # verifies that mixing them causes an error + test_args = [ + "profiling.sampling.cli", + "attach", # attach subcommand uses PID + "12345", + "-m", # -m is only for run subcommand + "mymodule", + ] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("unrecognized arguments", error_msg) + + def test_cli_mutually_exclusive_pid_script(self): + # In new CLI, you can't mix attach (PID) with run (script) + # This would be caught by providing a PID to run subcommand + test_args = ["profiling.sampling.cli", "run", "12345"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SamplingScriptNotFoundError) as cm, + ): + main() + + # Verify the error is about the non-existent script + self.assertIn("12345", str(cm.exception)) + + def test_cli_no_target_specified(self): + # In new CLI, must specify a subcommand + test_args = ["profiling.sampling.cli", "-d", "5"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("invalid choice", error_msg) + + @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") + @requires_remote_subprocess_debugging() + def test_cli_module_with_profiler_options(self): + test_args = [ + "profiling.sampling.cli", + "run", + "-r", + "1000", + "-d", + "30", + "-a", + "--sort", + "tottime", + "-l", + "20", + "-m", + "mymodule", + ] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + mock.patch("subprocess.Popen") as mock_popen, + mock.patch("socket.socket") as mock_socket, + mock.patch("profiling.sampling.cli._wait_for_ready_signal"), + mock.patch("importlib.util.find_spec", return_value=True), + ): + self._setup_sync_mocks(mock_socket, mock_popen) + main() + + self._verify_coordinator_command(mock_popen, ("-m", "mymodule")) + mock_sample.assert_called_once() + + @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") + def test_cli_script_with_profiler_options(self): + """Test script with various profiler options.""" + test_args = [ + "profiling.sampling.cli", + "run", + "-r", + "500", + "-d", + "60", + "--collapsed", + "-o", + "output.txt", + "myscript.py", + "scriptarg", + ] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + mock.patch("subprocess.Popen") as mock_popen, + mock.patch("socket.socket") as mock_socket, + mock.patch("profiling.sampling.cli._wait_for_ready_signal"), + mock.patch("os.path.exists", return_value=True), + ): + self._setup_sync_mocks(mock_socket, mock_popen) + main() + + self._verify_coordinator_command( + mock_popen, ("myscript.py", "scriptarg") + ) + # Verify profiler was called + mock_sample.assert_called_once() + + def test_cli_empty_module_name(self): + test_args = ["profiling.sampling.cli", "run", "-m"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("required: target", error_msg) # argparse error for missing positional arg + + @unittest.skipIf(is_emscripten, "socket.SO_REUSEADDR does not exist") + @requires_remote_subprocess_debugging() + def test_cli_long_module_option(self): + test_args = [ + "profiling.sampling.cli", + "run", + "-m", + "mymodule", + "arg1", + ] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + mock.patch("subprocess.Popen") as mock_popen, + mock.patch("socket.socket") as mock_socket, + mock.patch("profiling.sampling.cli._wait_for_ready_signal"), + mock.patch("importlib.util.find_spec", return_value=True), + ): + self._setup_sync_mocks(mock_socket, mock_popen) + main() + + self._verify_coordinator_command( + mock_popen, ("-m", "mymodule", "arg1") + ) + + def test_cli_complex_script_arguments(self): + test_args = [ + "profiling.sampling.cli", + "run", + "script.py", + "--input", + "file.txt", + "-v", + "--output=/tmp/out", + "positional", + ] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + mock.patch( + "profiling.sampling.cli._run_with_sync" + ) as mock_run_with_sync, + mock.patch("os.path.exists", return_value=True), + ): + mock_process = mock.MagicMock() + mock_process.pid = 12345 + mock_process.wait.side_effect = [ + subprocess.TimeoutExpired(test_args, 0.1), + None, + ] + mock_process.poll.return_value = None + mock_run_with_sync.return_value = mock_process + + main() + + mock_run_with_sync.assert_called_once_with( + ( + sys.executable, + "script.py", + "--input", + "file.txt", + "-v", + "--output=/tmp/out", + "positional", + ), + suppress_output=False + ) + + def test_cli_collapsed_format_validation(self): + """Test that CLI properly validates incompatible options with collapsed format.""" + test_cases = [ + # Test sort option is invalid with collapsed + ( + [ + "profiling.sampling.cli", + "attach", + "12345", + "--collapsed", + "--sort", + "tottime", # Changed from nsamples (default) to trigger validation + ], + "sort", + ), + # Test limit option is invalid with collapsed + ( + [ + "profiling.sampling.cli", + "attach", + "12345", + "--collapsed", + "-l", + "20", + ], + "limit", + ), + # Test no-summary option is invalid with collapsed + ( + [ + "profiling.sampling.cli", + "attach", + "12345", + "--collapsed", + "--no-summary", + ], + "summary", + ), + ] + + for test_args, expected_error_keyword in test_cases: + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + mock.patch("profiling.sampling.cli.sample"), # Prevent actual profiling + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error code + error_msg = mock_stderr.getvalue() + self.assertIn("error:", error_msg) + self.assertIn("only valid with --pstats", error_msg) + + def test_cli_default_collapsed_filename(self): + """Test that collapsed format gets a default filename when not specified.""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--collapsed"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli._is_process_running", return_value=True), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + ): + main() + + # Check that sample was called (exact filename depends on implementation) + mock_sample.assert_called_once() + + def test_cli_custom_output_filenames(self): + """Test custom output filenames for both formats.""" + test_cases = [ + ( + [ + "profiling.sampling.cli", + "attach", + "12345", + "--pstats", + "-o", + "custom.pstats", + ], + "custom.pstats", + "pstats", + ), + ( + [ + "profiling.sampling.cli", + "attach", + "12345", + "--collapsed", + "-o", + "custom.txt", + ], + "custom.txt", + "collapsed", + ), + ] + + for test_args, expected_filename, expected_format in test_cases: + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli._is_process_running", return_value=True), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + ): + main() + + mock_sample.assert_called_once() + + def test_cli_missing_required_arguments(self): + """Test that CLI requires subcommand.""" + with ( + mock.patch("sys.argv", ["profiling.sampling.cli"]), + mock.patch("sys.stderr", io.StringIO()), + ): + with self.assertRaises(SystemExit): + main() + + def test_cli_mutually_exclusive_format_options(self): + """Test that pstats and collapsed options are mutually exclusive.""" + with ( + mock.patch( + "sys.argv", + [ + "profiling.sampling.cli", + "attach", + "12345", + "--pstats", + "--collapsed", + ], + ), + mock.patch("sys.stderr", io.StringIO()), + ): + with self.assertRaises(SystemExit): + main() + + def test_argument_parsing_basic(self): + test_args = ["profiling.sampling.cli", "attach", "12345"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli._is_process_running", return_value=True), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + ): + main() + + mock_sample.assert_called_once() + + def _run_dump_cli( + self, + *cli_args, + dump_return=None, + dump_side_effect=None, + process_running=True, + ): + """Run main() for a `dump` invocation, returning (mock_dump, mock_print).""" + argv = ["profiling.sampling.cli", "dump", *cli_args] + dump_kwargs = {} + if dump_side_effect is not None: + dump_kwargs["side_effect"] = dump_side_effect + else: + dump_kwargs["return_value"] = [] if dump_return is None else dump_return + with ( + mock.patch("sys.argv", argv), + mock.patch( + "profiling.sampling.cli._is_process_running", + return_value=process_running, + ), + mock.patch("profiling.sampling.cli.dump_stack", **dump_kwargs) as mock_dump_stack, + mock.patch("profiling.sampling.cli.print_stack_dump") as mock_print_stack_dump, + ): + main() + return mock_dump_stack, mock_print_stack_dump + + def _run_dump_cli_expecting_exit(self, *cli_args, capture="stderr"): + """Run main() for a `dump` invocation expected to SystemExit, return (cm, captured_text).""" + argv = ["profiling.sampling.cli", "dump", *cli_args] + buf = io.StringIO() + stream = "sys.stderr" if capture == "stderr" else "sys.stdout" + with ( + mock.patch("sys.argv", argv), + mock.patch(stream, buf), + self.assertRaises(SystemExit) as cm, + ): + main() + return cm, buf.getvalue() + + def test_cli_dump_subcommand(self): + stack_frames = [mock.sentinel.stack_frames] + mock_dump_stack, mock_print_stack_dump = self._run_dump_cli( + "12345", dump_return=stack_frames + ) + + mock_dump_stack.assert_called_once() + self.assertEqual(mock_dump_stack.call_args.args, (12345,)) + call_kwargs = mock_dump_stack.call_args.kwargs + self.assertFalse(call_kwargs["all_threads"]) + self.assertIsNone(call_kwargs["async_aware"]) + self.assertFalse(call_kwargs["native"]) + self.assertTrue(call_kwargs["gc"]) + self.assertFalse(call_kwargs["opcodes"]) + self.assertFalse(call_kwargs["blocking"]) + self.assertEqual(call_kwargs["mode"], PROFILING_MODE_ALL) + mock_print_stack_dump.assert_called_once_with(stack_frames, pid=12345) + + def test_cli_dump_subcommand_options(self): + mock_dump_stack, _ = self._run_dump_cli( + "-a", "--native", "--no-gc", "--opcodes", "--blocking", "12345" + ) + + call_kwargs = mock_dump_stack.call_args.kwargs + self.assertTrue(call_kwargs["all_threads"]) + self.assertTrue(call_kwargs["native"]) + self.assertFalse(call_kwargs["gc"]) + self.assertTrue(call_kwargs["opcodes"]) + self.assertTrue(call_kwargs["blocking"]) + self.assertEqual(call_kwargs["mode"], PROFILING_MODE_ALL) + + def test_cli_dump_rejects_mode_option(self): + cm, stderr = self._run_dump_cli_expecting_exit("12345", "--mode", "cpu") + self.assertEqual(cm.exception.code, 2) + self.assertIn("unrecognized arguments: --mode", stderr) + + def test_cli_dump_async_aware_defaults_to_all(self): + mock_dump_stack, _ = self._run_dump_cli("--async-aware", "12345") + call_kwargs = mock_dump_stack.call_args.kwargs + self.assertEqual(call_kwargs["async_aware"], "all") + self.assertEqual(call_kwargs["mode"], PROFILING_MODE_WALL) + + def test_cli_dump_async_mode_all_is_forwarded(self): + mock_dump_stack, _ = self._run_dump_cli( + "--async-aware", "--async-mode", "all", "12345" + ) + call_kwargs = mock_dump_stack.call_args.kwargs + self.assertEqual(call_kwargs["async_aware"], "all") + self.assertEqual(call_kwargs["mode"], PROFILING_MODE_WALL) + + def test_cli_dump_async_mode_running_is_forwarded(self): + mock_dump_stack, _ = self._run_dump_cli( + "--async-aware", "--async-mode", "running", "12345" + ) + call_kwargs = mock_dump_stack.call_args.kwargs + self.assertEqual(call_kwargs["async_aware"], "running") + self.assertEqual(call_kwargs["mode"], PROFILING_MODE_WALL) + + def test_cli_dump_async_mode_requires_async_aware(self): + cm, stderr = self._run_dump_cli_expecting_exit( + "--async-mode", "running", "12345" + ) + self.assertEqual(cm.exception.code, 2) + self.assertIn("--async-mode requires --async-aware", stderr) + + def test_cli_dump_rejects_async_aware_with_all_threads(self): + cm, stderr = self._run_dump_cli_expecting_exit( + "--async-aware", "-a", "12345" + ) + self.assertEqual(cm.exception.code, 2) + self.assertIn("--all-threads", stderr) + self.assertIn("incompatible with --async-aware", stderr) + + def test_cli_dump_rejects_async_aware_with_native(self): + cm, stderr = self._run_dump_cli_expecting_exit( + "--async-aware", "--native", "12345" + ) + self.assertEqual(cm.exception.code, 2) + self.assertIn("--native", stderr) + self.assertIn("incompatible with --async-aware", stderr) + + def test_cli_dump_rejects_async_aware_with_no_gc(self): + cm, stderr = self._run_dump_cli_expecting_exit( + "--async-aware", "--no-gc", "12345" + ) + self.assertEqual(cm.exception.code, 2) + self.assertIn("--no-gc", stderr) + self.assertIn("incompatible with --async-aware", stderr) + + def test_cli_dump_unknown_process(self): + with self.assertRaises(SamplingUnknownProcessError): + self._run_dump_cli("12345", process_running=False) + + def test_cli_dump_process_exits_before_snapshot(self): + with self.assertRaises(SystemExit) as cm: + self._run_dump_cli("12345", dump_side_effect=ProcessLookupError) + self.assertIn( + "No stack dump collected - process 12345 exited", + str(cm.exception.code), + ) + + @force_not_colorized + def test_cli_dump_help_lists_dump_options_without_mode(self): + cm, stdout = self._run_dump_cli_expecting_exit("--help", capture="stdout") + self.assertEqual(cm.exception.code, 0) + self.assertIn("--async-mode {running,all}", stdout) + self.assertIn("--opcodes", stdout) + self.assertNotIn("--mode {wall,cpu,gil,exception}", stdout) + + def test_sort_options(self): + sort_options = [ + ("nsamples", 0), + ("tottime", 1), + ("cumtime", 2), + ("sample-pct", 3), + ("cumul-pct", 4), + ("name", -1), + ] + + for option, expected_sort_value in sort_options: + test_args = ["profiling.sampling.cli", "attach", "12345", "--sort", option] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli._is_process_running", return_value=True), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + ): + main() + + mock_sample.assert_called_once() + mock_sample.reset_mock() + + def test_async_aware_flag_defaults_to_running(self): + """Test --async-aware flag enables async profiling with default 'running' mode.""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli._is_process_running", return_value=True), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + ): + main() + + mock_sample.assert_called_once() + # Verify async_aware was passed with default "running" mode + call_kwargs = mock_sample.call_args[1] + self.assertEqual(call_kwargs.get("async_aware"), "running") + + def test_handle_output_browser_not_opened_when_export_fails(self): + for format_type in ("flamegraph", "diff_flamegraph", "heatmap"): + with self.subTest(format=format_type): + collector = mock.MagicMock() + collector.export.return_value = False + args = SimpleNamespace( + format=format_type, + outfile="profile.html", + browser=True, + ) + + with ( + mock.patch("profiling.sampling.cli.os.path.isdir", return_value=False), + mock.patch("profiling.sampling.cli._open_in_browser") as mock_open, + ): + _handle_output(collector, args, pid=12345, mode=0) + + collector.export.assert_called_once_with("profile.html") + mock_open.assert_not_called() + + def test_async_aware_with_async_mode_all(self): + """Test --async-aware with --async-mode all.""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--async-mode", "all"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli._is_process_running", return_value=True), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + ): + main() + + mock_sample.assert_called_once() + call_kwargs = mock_sample.call_args[1] + self.assertEqual(call_kwargs.get("async_aware"), "all") + + def test_async_aware_default_is_none(self): + """Test async_aware defaults to None when --async-aware not specified.""" + test_args = ["profiling.sampling.cli", "attach", "12345"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("profiling.sampling.cli._is_process_running", return_value=True), + mock.patch("profiling.sampling.cli.sample") as mock_sample, + ): + main() + + mock_sample.assert_called_once() + call_kwargs = mock_sample.call_args[1] + self.assertIsNone(call_kwargs.get("async_aware")) + + def test_async_mode_invalid_choice(self): + """Test --async-mode with invalid choice raises error.""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--async-mode", "invalid"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()), + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + + def test_async_mode_requires_async_aware(self): + """Test --async-mode without --async-aware raises error.""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--async-mode", "all"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("--async-mode requires --async-aware", error_msg) + + def test_async_aware_incompatible_with_native(self): + """Test --async-aware is incompatible with --native.""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--native"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("--native", error_msg) + self.assertIn("incompatible with --async-aware", error_msg) + + def test_async_aware_incompatible_with_no_gc(self): + """Test --async-aware is incompatible with --no-gc.""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--no-gc"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("--no-gc", error_msg) + self.assertIn("incompatible with --async-aware", error_msg) + + def test_async_aware_incompatible_with_both_native_and_no_gc(self): + """Test --async-aware is incompatible with both --native and --no-gc.""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--native", "--no-gc"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("--native", error_msg) + self.assertIn("--no-gc", error_msg) + self.assertIn("incompatible with --async-aware", error_msg) + + def test_async_aware_incompatible_with_mode(self): + """Test --async-aware is incompatible with --mode (non-wall).""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--mode", "cpu"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("--mode=cpu", error_msg) + self.assertIn("incompatible with --async-aware", error_msg) + + def test_async_aware_incompatible_with_all_threads(self): + """Test --async-aware is incompatible with --all-threads.""" + test_args = ["profiling.sampling.cli", "attach", "12345", "--async-aware", "--all-threads"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("--all-threads", error_msg) + self.assertIn("incompatible with --async-aware", error_msg) + + def test_async_aware_incompatible_with_binary(self): + """Test --async-aware is incompatible with --binary.""" + test_args = ["profiling.sampling.cli", "attach", "12345", + "--async-aware", "--binary"] + + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) # argparse error + error_msg = mock_stderr.getvalue() + self.assertIn("--binary", error_msg) + self.assertIn("incompatible with --async-aware", error_msg) + + @unittest.skipIf(is_emscripten, "subprocess not available") + def test_run_nonexistent_script_exits_cleanly(self): + """Test that running a non-existent script exits with a clean error.""" + with mock.patch("sys.argv", ["profiling.sampling.cli", "run", "/nonexistent/script.py"]): + with self.assertRaisesRegex(SamplingScriptNotFoundError, "Script '[\\w/.]+' not found."): + main() + + @unittest.skipIf(is_emscripten, "subprocess not available") + def test_run_nonexistent_module_exits_cleanly(self): + """Test that running a non-existent module exits with a clean error.""" + with mock.patch("sys.argv", ["profiling.sampling.cli", "run", "-m", "nonexistent_module_xyz"]): + with self.assertRaisesRegex(SamplingModuleNotFoundError, "Module '[\\w/.]+' not found."): + main() + + @unittest.skipIf(is_emscripten, "subprocess not available") + def test_cli_attach_nonexistent_pid(self): + fake_pid = "99999" + with mock.patch("sys.argv", ["profiling.sampling.cli", "attach", fake_pid]): + with self.assertRaises(SamplingUnknownProcessError) as cm: + main() + + self.assertIn(fake_pid, str(cm.exception)) + + def test_cli_replay_rejects_non_binary_profile(self): + with tempfile.TemporaryDirectory() as tempdir: + profile = os.path.join(tempdir, "output.prof") + with open(profile, "wb") as file: + file.write(b"not a binary sampling profile") + + with mock.patch("sys.argv", ["profiling.sampling.cli", "replay", profile]): + with self.assertRaises(SystemExit) as cm: + main() + + error = str(cm.exception) + self.assertIn("not a binary sampling profile", error) + self.assertIn("--binary", error) + + def test_cli_replay_reader_errors_exit_cleanly(self): + with tempfile.TemporaryDirectory() as tempdir: + profile = os.path.join(tempdir, "output.bin") + with open(profile, "wb") as file: + file.write(b"HCAT" + (b"\0" * 60)) + + with ( + mock.patch("sys.argv", ["profiling.sampling.cli", "replay", profile]), + mock.patch( + "profiling.sampling.cli.BinaryReader", + side_effect=ValueError("Unsupported format version 2"), + ), + ): + with self.assertRaises(SystemExit) as cm: + main() + + self.assertEqual( + str(cm.exception), + "Error: Unsupported format version 2", + ) + + def test_cli_replay_propagates_recorded_mode(self): + reader = mock.MagicMock() + reader.get_info.return_value = { + "sample_interval_us": 1000, + "sample_count": 0, + "compression_type": 0, + "mode": PROFILING_MODE_CPU, + "capture_config": {"all_threads": True}, + } + reader.replay_samples.return_value = 0 + collector = mock.MagicMock() + collector.export.return_value = True + args = SimpleNamespace( + format="diff_flamegraph", + input_file="current.bin", + diff_baseline="baseline.bin", + outfile="diff.html", + browser=False, + ) + + with mock.patch( + "profiling.sampling.cli._create_collector", + return_value=collector, + ) as create_collector: + _replay_with_reader(args, reader) + + create_collector.assert_called_once_with( + "diff_flamegraph", + 1000, + skip_idle=False, + mode=PROFILING_MODE_CPU, + diff_baseline="baseline.bin", + capture_config={"all_threads": True}, + ) + + def test_cli_jsonl_format_mutually_exclusive_with_pstats(self): + """--jsonl and --pstats cannot be combined (mutually exclusive group).""" + with ( + mock.patch( + "sys.argv", + [ + "profiling.sampling.cli", + "attach", + "12345", + "--jsonl", + "--pstats", + ], + ), + mock.patch("sys.stderr", io.StringIO()), + ): + with self.assertRaises(SystemExit): + main() + + def test_cli_jsonl_extension_in_format_extensions(self): + """FORMAT_EXTENSIONS maps 'jsonl' -> 'jsonl' so default filenames work.""" + self.assertEqual(FORMAT_EXTENSIONS["jsonl"], "jsonl") + self.assertEqual(_generate_output_filename("jsonl", 12345), "jsonl_12345.jsonl") + + def test_cli_jsonl_create_collector_propagates_mode(self): + """_create_collector('jsonl', ..., mode=X) lands X in the meta record.""" + collector = _create_collector( + "jsonl", + sample_interval_usec=1000, + skip_idle=False, + mode=PROFILING_MODE_CPU, + ) + self.assertIsInstance(collector, JsonlCollector) + + with tempfile.NamedTemporaryFile(suffix=".jsonl", delete=False) as f: + jsonl_path = f.name + self.addCleanup(os.unlink, jsonl_path) + collector.export(jsonl_path) + with open(jsonl_path, "r", encoding="utf-8") as f: + records = [json.loads(line) for line in f] + meta = next(r for r in records if r["type"] == "meta") + self.assertEqual(meta["mode"], "cpu") + + def test_cli_binary_create_collector_propagates_mode(self): + with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f: + binary_path = f.name + self.addCleanup(os.unlink, binary_path) + collector = _create_collector( + "binary", + sample_interval_usec=1000, + skip_idle=True, + mode=PROFILING_MODE_CPU, + output_file=binary_path, + compression="none", + capture_config={"native": True}, + ) + collector.export(None) + + with BinaryReader(binary_path) as reader: + info = reader.get_info() + self.assertEqual(info["mode"], PROFILING_MODE_CPU) + self.assertTrue(info["capture_config"]["native"]) + + def test_cli_jsonl_rejects_opcodes_combination(self): + """--opcodes is incompatible with --jsonl per opcodes_compatible_formats.""" + test_args = [ + "profiling.sampling.cli", + "attach", + "12345", + "--jsonl", + "--opcodes", + ] + with ( + mock.patch("sys.argv", test_args), + mock.patch("sys.stderr", io.StringIO()) as mock_stderr, + mock.patch("profiling.sampling.cli.sample"), + self.assertRaises(SystemExit) as cm, + ): + main() + + self.assertEqual(cm.exception.code, 2) + self.assertIn("--opcodes", mock_stderr.getvalue()) From 0f74d1cd145fb09e8d16c10f9aead1b305e836bb Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Sun, 25 Oct 2026 19:29:20 +0900 Subject: [PATCH 16/18] ci: remove OSCCA pull request triage workflow (#9361) Stop automatically labeling OSCCA participants' pull requests with z-ca-2026 and assigning them, and drop the matching zizmor ignore entry. Assisted-by: Claude Code:claude-opus-5-5 --- .github/workflows/oscca-pr.yml | 70 ---------------------------------- .github/zizmor.yml | 3 -- 2 files changed, 73 deletions(-) delete mode 100644 .github/workflows/oscca-pr.yml diff --git a/.github/workflows/oscca-pr.yml b/.github/workflows/oscca-pr.yml deleted file mode 100644 index 38b67625ce0..00000000000 --- a/.github/workflows/oscca-pr.yml +++ /dev/null @@ -1,70 +0,0 @@ -name: Manage OSCCA pull requests - -on: - pull_request_target: - types: [opened] - -permissions: {} - -jobs: - label-and-assign: - name: Label and assign OSCCA pull request - runs-on: ubuntu-slim - timeout-minutes: 5 - permissions: - pull-requests: write - steps: - - name: Label and assign pull request - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - script: | - const osccaUsers = new Set( - [ - "2jiyong", - "chestnut1717", - "devyubin", - "fregataa", - "hyoinandout", - "HyoJongPark", - "JaceJung-dev", - "jinmay", - "jiwahn", - "kangdora", - "kim-jaedeok", - "kyokuping", - "leehanjeong", - "lms0806", - "lsahn-gh", - "moreal", - "name-of-okja", - "rlaisqls", - "seungje0612", - "shAn-kor", - "sigmaith", - "teddygood", - "widehyo1", - "YangSiJun528", - "zzarbttoo", - ].map((login) => login.toLowerCase()), - ); - const pullRequest = context.payload.pull_request; - const author = pullRequest.user.login; - - if (!osccaUsers.has(author.toLowerCase())) { - core.info(`${author} is not an OSCCA participant; skipping.`); - return; - } - - const issue = { - ...context.repo, - issue_number: pullRequest.number, - }; - - await github.rest.issues.addLabels({ - ...issue, - labels: ["z-ca-2026"], - }); - await github.rest.issues.addAssignees({ - ...issue, - assignees: [author], - }); diff --git a/.github/zizmor.yml b/.github/zizmor.yml index 43d8f16039c..2aaa611ad06 100644 --- a/.github/zizmor.yml +++ b/.github/zizmor.yml @@ -1,9 +1,6 @@ rules: dangerous-triggers: ignore: - # pull_request_target is needed to label and assign PRs from forks with issues: write. - # The workflow does not check out or execute pull request code. - - oscca-pr.yml:3 # workflow_run is needed to post a PR comment from a fork's run, which is # handed a read-only token however the triggering workflow is configured. # The workflow does not check out or execute pull request code: it reads From 16413a974699fbefe3d381ba04c4fc107af65462 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Mon, 31 Aug 2026 07:04:03 +0900 Subject: [PATCH 17/18] bench: cover type cache invalidation Assisted-by: Codex:gpt-5.6-sol --- benches/microbenchmarks/type_cache_invalidation.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 benches/microbenchmarks/type_cache_invalidation.py diff --git a/benches/microbenchmarks/type_cache_invalidation.py b/benches/microbenchmarks/type_cache_invalidation.py new file mode 100644 index 00000000000..6f2ff53bfed --- /dev/null +++ b/benches/microbenchmarks/type_cache_invalidation.py @@ -0,0 +1,14 @@ +class Target: + value = 0 + + +def mutate_type(iterations, target=Target): + for value in range(iterations): + target.value + target.value = value + return target.value + + +# --- + +mutate_type(ITERATIONS) From 293a7e8589a470a4bca9063b2afb082ef2bcf405 Mon Sep 17 00:00:00 2001 From: Lee Dogeon Date: Mon, 31 Aug 2026 07:33:17 +0900 Subject: [PATCH 18/18] perf: invalidate type cache by version tag Assisted-by: Codex:gpt-5.6-sol --- crates/vm/src/builtins/type.rs | 163 ++++++++++++++++++++++++++++----- 1 file changed, 140 insertions(+), 23 deletions(-) diff --git a/crates/vm/src/builtins/type.rs b/crates/vm/src/builtins/type.rs index b450fe7d0ab..65d9f909c30 100644 --- a/crates/vm/src/builtins/type.rs +++ b/crates/vm/src/builtins/type.rs @@ -130,9 +130,9 @@ struct TypeCacheEntry { name: AtomicPtr, /// Cached lookup result as raw pointer. null = empty. /// The cache holds a **borrowed** pointer (no refcount increment). - /// Safety: `type_cache_clear()` nullifies all entries during GC, - /// and `type_cache_clear_version()` nullifies entries when a type - /// is modified — both before the source dict entry is removed. + /// Safety: version tags are never reused, readers validate the current + /// type version, and published values use QSBR-delayed reclamation. + /// `type_cache_clear()` still nullifies all entries during GC. /// Types are always part of reference cycles (via `mro` self-reference) /// so they are always collected by the cyclic GC (never refcount-freed). value: AtomicPtr, @@ -240,21 +240,6 @@ fn type_cache_hash(version: u32, name: &'static PyStrInterned) -> usize { ((version ^ name_hash) as usize) & TYPE_CACHE_MASK } -/// Invalidate cache entries for a specific version tag. -/// Called from modified() when a type is changed. -fn type_cache_clear_version(version: u32) { - for entry in TYPE_CACHE.iter() { - if entry.version.load(Ordering::Relaxed) == version { - entry.begin_write(); - if entry.version.load(Ordering::Relaxed) == version { - entry.version.store(0, Ordering::Release); - entry.clear_value(); - } - entry.end_write(); - } - } -} - /// Clear all method cache entries (_PyType_ClearCache). /// Called during GC collection to nullify borrowed pointers before /// the collector breaks cycles. @@ -759,8 +744,7 @@ impl PyType { /// Invalidate this type's version tag and cascade to all subclasses. fn modified_inner(&self) { - let old_version = self.tp_version_tag.load(Ordering::Acquire); - if old_version == 0 { + if self.tp_version_tag.load(Ordering::Acquire) == 0 { return; } let subclasses = self.subclasses.read(); @@ -770,9 +754,9 @@ impl PyType { } } self.tp_version_tag.store(0, Ordering::SeqCst); - // Nullify borrowed pointers in cache entries for this version - // so they don't dangle after the dict is modified. - type_cache_clear_version(old_version); + // Old cache entries are unreachable because version tags are never + // reused. Their borrowed values use QSBR-delayed reclamation, so a + // reader that observed the old version can still try-incref safely. if let Some(ext) = self.heaptype_ext.as_ref() { ext.specialization_cache.invalidate_for_type_modified(); } @@ -3930,6 +3914,7 @@ fn mangle_name(class_name: &str, name: &str) -> String { #[cfg(test)] mod tests { use super::*; + use crate::Interpreter; #[test] fn type_cache_rejects_invalidated_versions() { @@ -4001,4 +3986,136 @@ mod tests { map_ids(Ok(vec![a, b, object])) ); } + + #[test] + fn version_invalidation_makes_stale_cache_entry_unreachable() { + Interpreter::without_stdlib(Default::default()).enter(|vm| { + let typ = PyType::new_simple_heap( + "TypeCacheInvalidationTest", + vm.ctx.types.object_type, + &vm.ctx, + ) + .unwrap(); + let name = vm.ctx.intern_str("cached_value"); + let initial: PyObjectRef = vm.ctx.new_int(1).into(); + let replacement: PyObjectRef = vm.ctx.new_int(2).into(); + typ.set_attr(name, initial.clone()); + + let (cached, old_version) = typ.lookup_ref_and_version_interned(name, vm); + assert!(cached.is_some_and(|value| value.is(&initial))); + assert_ne!(old_version, 0); + let old_entry = &TYPE_CACHE[type_cache_hash(old_version, name)]; + assert_eq!(old_entry.version.load(Ordering::Acquire), old_version); + + PyType::with_type_lock(vm, || { + typ.modified_inner(); + typ.attributes.insert(name, replacement.clone()); + }); + + assert_eq!(typ.tp_version_tag.load(Ordering::Acquire), 0); + assert_eq!( + old_entry.version.load(Ordering::Acquire), + old_version, + "per-type invalidation should leave the unreachable old cache entry in place" + ); + + let (refreshed, new_version) = typ.lookup_ref_and_version_interned(name, vm); + assert!(refreshed.is_some_and(|value| value.is(&replacement))); + assert!(new_version > old_version); + }); + } + + #[test] + fn miri_test_type_cache_version_invalidation() { + let context = Context::genesis(); + let typ = context.types.object_type.to_owned(); + let name = context.intern_str("miri_cached_value"); + let cached: PyObjectRef = context.new_int(1).into(); + typ.set_attr(name, cached.clone()); + let version = typ.assign_version_tag_inner(); + assert_ne!(version, 0); + + let entry = &TYPE_CACHE[type_cache_hash(version, name)]; + entry.begin_write(); + entry.version.store(0, Ordering::Release); + cached.mark_cache_published(); + let cached_ptr = &*cached as *const PyObject as *mut PyObject; + entry.value.store(cached_ptr, Ordering::Relaxed); + entry + .name + .store(name as *const PyStrInterned as *mut _, Ordering::Relaxed); + entry.version.store(version, Ordering::Release); + entry.end_write(); + + typ.tp_version_tag.store(0, Ordering::SeqCst); + assert_eq!(typ.tp_version_tag.load(Ordering::Acquire), 0); + assert_eq!(entry.version.load(Ordering::Acquire), version); + let owned = unsafe { PyObject::try_to_owned_from_ptr(cached_ptr) } + .expect("a stale cache reader can still safely try-incref"); + assert!(owned.is(&cached)); + drop(owned); + + entry.begin_write(); + entry.version.store(0, Ordering::Release); + entry.clear_value(); + entry.end_write(); + typ.attributes.remove(name); + } + + #[cfg(feature = "threading")] + #[test] + fn concurrent_type_cache_reads_survive_version_only_invalidation() { + use alloc::sync::Arc; + use core::sync::atomic::{AtomicBool, Ordering}; + + Interpreter::without_stdlib(Default::default()).enter(|vm| { + let typ = PyType::new_simple_heap( + "ConcurrentTypeCacheInvalidationTest", + vm.ctx.types.object_type, + &vm.ctx, + ) + .unwrap(); + let name = vm.ctx.intern_str("cached_value"); + let first: PyObjectRef = vm.ctx.new_int(1).into(); + let second: PyObjectRef = vm.ctx.new_int(2).into(); + typ.set_attr(name, first.clone()); + assert!( + typ.lookup_ref_and_version_interned(name, vm) + .0 + .is_some_and(|value| value.is(&first)) + ); + + let stop = Arc::new(AtomicBool::new(false)); + let reader_type = typ.clone(); + let reader_first = first.clone(); + let reader_second = second.clone(); + let reader_stop = Arc::clone(&stop); + let reader = vm.start_thread(move |thread_vm| { + while !reader_stop.load(Ordering::Acquire) { + let value = reader_type + .lookup_ref_and_version_interned(name, thread_vm) + .0 + .expect("the test type always has cached_value"); + assert!(value.is(&reader_first) || value.is(&reader_second)); + thread_vm.check_signals().unwrap(); + } + }); + + for i in 0..10_000 { + let value = if i & 1 == 0 { + first.clone() + } else { + second.clone() + }; + PyType::with_type_lock(vm, || { + typ.modified_inner(); + typ.attributes.insert(name, value); + }); + assert!(typ.lookup_ref_and_version_interned(name, vm).0.is_some()); + vm.check_signals().unwrap(); + } + stop.store(true, Ordering::Release); + reader.join().unwrap(); + }); + } }